Categories
React TypeScript

Using tableLayout CSS property with TypeScript

Using table-layout property with React and TypeScript was giving a type error like below:

ERROR in /var/jenkins/workspace/dev/resources/packages/sites/table.tsx
[tsl] ERROR in /var/jenkins/workspace/dev/resources/packages/sites/table.tsx(79,25)
      TS2322: Type '{ padding: number; width: string; margin: string; tableLayout: string; }' is not assignable to type 'CSSProperties'.
  Types of property 'tableLayout' are incompatible.
    Type 'string' is not assignable to type 'TableLayout'.

This was the styling code:

const getListStyle = () => ({
    padding: 8,
    width: '100%',
    margin: '0px auto',
    tableLayout: 'auto',
});

React/Typescript JSX code:

<table style={getListStyle()}>
    <Header />
    <Body />
</table>

For some reason, table-layout doesn’t seem to accept string values. Although, as per the doc, it should be accepted.

This is the full typescript error reported by the IDE on the style attribute:

TS2322: Type ‘{ padding: number; width: string; margin: string; tableLayout: string; }’ is not assignable to type ‘CSSProperties’.   Types of property ‘tableLayout’ are incompatible.     Type ‘string’ is not assignable to type ‘TableLayout’. index.d.ts(1765, 9): The expected type comes from property ‘style’ which is declared here on type ‘DetailedHTMLProps, HTMLTableElement>’

The fix was to simply add the return type CSSProperties explicity like this:

const getListStyle = (): CSSProperties => ({
    padding: 8,
    width: '100%',
    margin: '0px auto',
    tableLayout: 'auto',
});

Categories
Web Development

CSS Flip Animation for Font Awesome Icons

This flip animation will be a perfect use-case for enable/disable UI actions.

We’ll be using rotateY transform function to achieve this flip animation.

Here is a simple HTML page with a few font awesome icons in both enabled and disabled states (we’ll be adding CSS for these states later) and jquery included for click actions.

<html>
  <head>
    <script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" integrity="sha512-+4zCK9k+qNFUR5X+cKL9EIR+ZOhtIloNl9GIKS57V1MyNsYpYcUrUeQc9vNfzsWfV28IaLL3i96P9sdNyeRssA==" crossorigin="anonymous" />
  </head>
  <body>
    <i class="fa fa-envelope enabled"></i>
    <i class="fa fa-comment-alt enabled"></i>
    <i class="fa fa-camera disabled"></i>
    <i class="fa fa-bell disabled"></i>
  </body> 
</html>

Here is the CSS for enabled and disabled states with the rotateY animation.

.flip {
    animation: flip-icon 0.3s ease-in-out;
}

body {
  text-align: center; 
  width: 100%;
}

.fa {
  cursor: pointer;
  font-size: 48px; 
  margin: 24px;
}

.fa.enabled {
     color: #f44336;
 }
 .fa.disabled {
     color: grey;
 }

@keyframes flip-icon {
    0% {
        transform: rotateY(180deg);
    }
    100% {
        transform: rotateY(0deg);
    }
}

Here is the relevant click actions that will add or remove appropriate classes to start and reset the animation.

 $('body').on('click', '.fa.enabled, .fa.disabled', function iconClick() {
            const icon = $(this);
            icon.addClass('flip');
            setTimeout(() => { icon.removeClass('flip'); }, 300);
            if (icon.hasClass('enabled')) {
                setTimeout(() => {  
                  icon.removeClass('enabled');
                  icon.addClass('disabled'); 
                }, 150);
            } else {
              setTimeout(() => {  
                  icon.removeClass('disabled');
                  icon.addClass('enabled'); 
                }, 150);
            }
        });

Once you have the above code, you’ll achieve something like this:

Full Source Code can be found in this pen.

Categories
Elasticsearch

Shrinking indices in Elasticsearch

Optimizing Elasticsearch: How Many Shards per Index? | Qbox HES

The Problem

Today, we started receiving the following error from our production Elasticsearch cluster when a new index was about to be created:

{
  "error": {
    "root_cause": [
      {
        "type": "validation_exception",
        "reason": "Validation Failed: 1: this action would add [10] total shards, but this cluster currently has [991]/[1000] maximum shards open;"
      }
    ],
    "type": "validation_exception",
    "reason": "Validation Failed: 1: this action would add [10] total shards, but this cluster currently has [991]/[1000] maximum shards open;"
  },
  "status": 400
}

The error description was obvious that we would breach the shard limit of 1,000 when creating a new index.

Confirming the number from the error message using _cat/shards endpoint, we see that we had 991 shards in our only data node.

$ curl -s https://<aws_es_url>.es.amazonaws.com:443/_cat/shards | wc -l
991

We had about 99 indices and each index had 5 shards with one replica which contributes to 5 shards as well. So a total of 10 shards per index. We can confirm that by checking the index endpoint:

$ curl -s https://<aws_es_url>.es.amazonaws.com:443/<index_name>?pretty"

which shows the following output (shortened for brevity):

{
  "settings": {
    "number_of_shards": "5",
    "number_of_replicas": "1"
  }
}

Looking around in AWS help docs, they have suggested three solutions:

Suggested fixes

The 7.x versions of Elasticsearch have a default setting of no more than 1,000 shards per node. Elasticsearch throws an error if a request, such as creating a new index, would cause you to exceed this limit. If you encounter this error, you have several options:

  • Add more data nodes to the cluster.
  • Increase the _cluster/settings/cluster.max_shards_per_node setting.
  • Use the _shrink API to reduce the number of shards on the node.

We chose the shrink option because all our indices are small enough that they do not need 5 shards.

How to Shrink?

It is a 3 step process:

Step 1: Block writes on the current index

$ curl -XPUT -H 'Content-Type: application/json' https://<aws_es_url>.es.amazonaws.com:443/<current_index_name>/_settings -d'{
  "settings": {
    "index.number_of_replicas": 0,                                
    "index.routing.allocation.require._name": "shrink_node_name", 
    "index.blocks.write": true                                    
  }
}'

Step 2: Start shrinking with the new shard count

$ curl -XPOST -H 'Content-Type: application/json' https://<aws_es_url>.es.amazonaws.com:443/<current_index_name>/_shrink/<new_index_name> -d'{
  "settings": {
    "index.number_of_replicas": 1,
    "index.number_of_shards": 1, 
    "index.routing.allocation.require._name": null,
    "index.blocks.write": null
  }
}'

You can track the progress of the shrinking via the /_cat/recovery endpoint. Once the shrinking is complete, you can verify the document count via the _cat/indices endpoint.

Once you are happy with the shrinking, go to the next step.

Step 3: Delete the old index

$ curl -XDELETE https://<aws_es_url>.es.amazonaws.com:443/<current_index_name>

You can run the above commands for multiple indices through a shell script like below (place the index names in /tmp/indices.txt as one index name per line):

while read source; do
   <curl command>
done </tmp/indices.txt

Permanent Fix

All the above 3 steps only fixes the existing indices. We’ll need to make some code changes to ensure new indices created from now on is also created with the new setting of one shard.

Include settings.number_of_shards and settings.number_of_replicas in the request payload along with mappings when creating a new index. PHP code for reference:

[
    'mappings' => [
        'properties' => [
            .......
        ],
    ],
    'settings' => [
        'number_of_shards' => 1,
        'number_of_replicas' => 1,
    ]
];

You are now done! 👏

You have successfully fixed both existing indices and new indices.

Further Reading

Categories
AWS

Static Websites with AWS CloudFront and S3

Amazon S3 + Amazon CloudFront: A Match Made in the Cloud | Networking &  Content Delivery

Why CloudFront & S3 is better for hosting static sites?

AWS CloudFront is a CDN that can be used to serve static HTML sites backed by S3 storage.

S3 storage is very cheap. Combined with CloudFront, you can make your sites serve in low latency speeds.

Deploy to S3 and start CloudFront cache invalidation

Why automated deployment?

Automated deployments allows your changes to be made available on the live site instantly and automatically whenever you push to the repository.

You can forget about copying the code manually and uploading to your S3 bucket.

Why trigger cache invalidation?

Once the files are copied to S3, we need to trigger an invalidation for the cache in CloudFront. Otherwise, CloudFront will continue to server the old content from its cache.

We’ll see examples and code snippets for both Bitbucket and Github below.

Using Bitbucket Pipelines

Pipelines is Bitbucket’s CI/CD tool.

Steps to setup deployment:

  • Create a file named bitbucket-pipelines.yml in the project directory.
  • Paste the below code in the file and give your AWS_Access_key_ID, AWS_Secret_access_key, $CloudFront_Distribution_Id values through
  • After adding your AWS key save and push your changes into the bitbucket repo, it is done!
image: node:10.15.0

pipelines:
  default:
    - step:
        name: Deploy to S3
        deployment: production
        script:
          - pipe: atlassian/aws-s3-deploy:0.4.4
            variables:
              AWS_ACCESS_KEY_ID: $AWS_Access_key_ID
              AWS_SECRET_ACCESS_KEY: $AWS_Secret_access_key
              AWS_DEFAULT_REGION: 'us-east-1'
              S3_BUCKET: 'jiga.com.au'
              LOCAL_PATH: $BITBUCKET_CLONE_DIR
              ACL: 'public-read'
    - step:
        name: Invalidate CloudFront cache
        script:
          - pipe: atlassian/aws-cloudfront-invalidate:0.3.3
            variables:
              AWS_ACCESS_KEY_ID: $AWS_Access_key_ID
              AWS_SECRET_ACCESS_KEY: $AWS_Secret_access_key
              AWS_DEFAULT_REGION: 'us-east-1'
              DISTRIBUTION_ID: $CloudFront_Distribution_Id

You can add the variables directly in the YML file but it is recommended to add them through ‘Repository variables’ under Pipelines Settings in Repository settings:

Using GitHub Actions

Actions is GitHub’s CI/CD tool.

Steps to setup deployment:

  • Create a file deploy-to-s3.yml in the project directory.
  • Add the required variables to secrets
  • After adding your secrets, push your changes into the Github repo and see the magic!
name: Deploy Website
'on':
  push:
    branches:
      - master
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - name: Deploy to S3
        uses: jakejarvis/s3-sync-action@master
        with:
          args: '--acl public-read --delete'
        env:
          AWS_S3_BUCKET: '${{ secrets.AWS_PRODUCTION_BUCKET_NAME }}'
          AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}'
          AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}'
          AWS_REGION: '${{ secrets.AWS_REGION }}'
          SOURCE_DIR: build
      - name: Invalidate CloudFront Cache
        uses: awact/cloudfront-action@master
        env:
          SOURCE_PATH: ./public
          AWS_REGION: us-east-1
          AWS_ACCESS_KEY_ID: '${{ secrets.AWS_ACCESS_KEY_ID }}'
          AWS_SECRET_ACCESS_KEY: '${{ secrets.AWS_SECRET_ACCESS_KEY }}'
          DISTRIBUTION_ID: '${{ secrets.DISTRIBUTION_ID }}'
 

Now that you know how to automatically deploy to S3 on every change to the repository, you can learn some two tricks about routing options with S3 hosting.

Route pages without index.html filename suffix

By default, CloudFront expects the full URL to match with the actual path in S3. For example, if you have a path <bucket_url>/<subdirectory>/index.html as below, you will need to open https://example.com/subdirectory/index.html in your browser to open the file.

AWS S3 FILE NAVIGATION

If we try to open the URL without index.html suffix, we will get AccessDenied error like below.

But there is a small trick that we can apply to open just https://example.com/subdirectory from your browser and let CloudFront/S3 serve the index.html automatically.

From an SEO perspective, it is also good to strip out the HTML suffix anyway, because the overall URL length is smaller which search engines like.

So here is the trick, the Origin Domain Name has to be updated in CloudFront in the following format:

CLOUDFRONT DISTRIBUTION -> ORIGIN AND ORIGIN GROUPS

Click ‘Edit’:

EDIT ORIGIN FORM

Change the Origin Domain Name from bucket.s3.amazonaws.com to bucket.s3-website-us-east-1.amazonaws.com (based on your region name)

Once the CloudFront Distribution is updated after the above settings change, try opening the subdirectory URL directly and it will work:

Route pages without *.html filename suffix

It is also possible to route a file such as <bucket_path>/page.html to an URL example.com/page. Yet again, this is beneficial for SEO purposes and your URL looks neat.

To get this working, follow these two steps:

  1. Remove .html suffix/extension from your original file either before uploading to S3 or after.
  2. Override the metadata Content-type of this file to text/html. Select the file -> Actions -> Change Metadata. (The default value for files without an extension is binary/octet-stream which triggers a download when opened from the browser)

Now you should be able to access your URL without the HTML suffix:

If you have more than one file to update the metadata or if you are deploying from Bitbucket Pipelines or Github Actions, you can automate this update on Metadata. Example step:

    - step:
        name: Update Metadata on HTML files
        image: fuinorg/atlassian-default-image-awscli:latest
        script:
          - >-
            while read file; do
              AWS_ACCESS_KEY_ID=$AWS_Access_key_ID AWS_SECRET_ACCESS_KEY=$AWS_Secret_access_key AWS_DEFAULT_REGION=us-east-1 aws s3 cp --content-type="text/html" --metadata-directive="REPLACE" --acl=public-read s3://<bucket>/$file s3://jiga.com.au/$file
            done <$BITBUCKET_CLONE_DIR/files_to_update_in_s3.txt

files_to_update_in_s3.txt should contain the list of files that needs to be metadata set. You can generate this file dynamically locally using gulp.

The doctype has to be set correctly in the HTML file for this to work. Example:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
Hello world
</body>
</html>

Note there is a little difference between the previous subdirectory approach and this one. We will have a trailing slash in the earlier approach and no slash in this one.

Categories
Java

MojoFailureException: Fix Maven’s Compilation Failure:

Today, I faced a compilation failure in Bitbucket pipelines for a simple Java project. The project compiles successfully in the local machine.

The stack trace of the failure was not useful at all:

------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  9.655 s
[INFO] Finished at: 2020-10-14T22:24:42Z
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:testCompile (default-cli) on project automation: Compilation failure -> [Help 1]
org.apache.maven.lifecycle.LifecycleExecutionException: Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:testCompile (default-cli) on project automation: Compilation failure
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:215)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:156)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:148)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:117)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:81)
    at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build (SingleThreadedBuilder.java:56)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute (LifecycleStarter.java:128)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:305)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:192)
    at org.apache.maven.DefaultMaven.execute (DefaultMaven.java:105)
    at org.apache.maven.cli.MavenCli.execute (MavenCli.java:957)
    at org.apache.maven.cli.MavenCli.doMain (MavenCli.java:289)
    at org.apache.maven.cli.MavenCli.main (MavenCli.java:193)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (Native Method)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62)
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke (Method.java:566)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced (Launcher.java:282)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch (Launcher.java:225)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode (Launcher.java:406)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main (Launcher.java:347)
Caused by: org.apache.maven.plugin.compiler.CompilationFailureException: Compilation failure
    at org.apache.maven.plugin.compiler.AbstractCompilerMojo.execute (AbstractCompilerMojo.java:1224)
    at org.apache.maven.plugin.compiler.TestCompilerMojo.execute (TestCompilerMojo.java:180)
    at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo (DefaultBuildPluginManager.java:137)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:210)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:156)
    at org.apache.maven.lifecycle.internal.MojoExecutor.execute (MojoExecutor.java:148)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:117)
    at org.apache.maven.lifecycle.internal.LifecycleModuleBuilder.buildProject (LifecycleModuleBuilder.java:81)
    at org.apache.maven.lifecycle.internal.builder.singlethreaded.SingleThreadedBuilder.build (SingleThreadedBuilder.java:56)
    at org.apache.maven.lifecycle.internal.LifecycleStarter.execute (LifecycleStarter.java:128)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:305)
    at org.apache.maven.DefaultMaven.doExecute (DefaultMaven.java:192)
    at org.apache.maven.DefaultMaven.execute (DefaultMaven.java:105)
    at org.apache.maven.cli.MavenCli.execute (MavenCli.java:957)
    at org.apache.maven.cli.MavenCli.doMain (MavenCli.java:289)
    at org.apache.maven.cli.MavenCli.main (MavenCli.java:193)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0 (Native Method)
    at jdk.internal.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62)
    at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke (Method.java:566)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launchEnhanced (Launcher.java:282)
    at org.codehaus.plexus.classworlds.launcher.Launcher.launch (Launcher.java:225)
    at org.codehaus.plexus.classworlds.launcher.Launcher.mainWithExitCode (Launcher.java:406)
    at org.codehaus.plexus.classworlds.launcher.Launcher.main (Launcher.java:347)
[ERROR] 
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

The stack trace doesn’t report any file names or lines numbers. This means the compilation error is not with the source code itself.

Reading the help doc gave me some clue about the problem. After spending some time in investigation, I found that this issue occurred because I was using a custom executable for maven-compiler-plugin that includes a environment variable:

<plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <source>11</source>
          <target>11</target>
          <executable>${env.JAVA_11_HOME}/bin/javac</executable>
          <fork>true</fork>
        </configuration>
</plugin>

Since this environment variable was present in my local system but was not set in Bitbucket pipelines, I was getting the above exception. Setting the environment variable like below in my bitbucket-pipelines.yml file resolved the issue:

image: maven:3.6.3-openjdk-11
pipelines:
  default:
    - step:
        script:
          - JAVA_11_HOME=/usr/local/openjdk-11 mvn -e -X clean compile compiler:testCompile

If you are using Jenkins/Ubuntu, the path to Java may be different. Use sudo update-alternatives --config java to find out the path. Eg. on my Ubuntu, Java 11 is installed into /usr/lib/jvm/java-11-openjdk-amd64

Categories
IntelliJ MySQL

IntelliJ – Connect to MySQL running in Vagrant

The Database feature of IntelliJ/PHPStorm is very powerful compared to the MySQL CLI. It allows your edit your data from the UI easily without writing any MySQL commands.

Here is how you can connect your MySQL server running in a vagrant machine to the IntelliJ’s database feature and improve your productivity during development.

Create a new MySQL Data Source with these basic configuration:

Fill the basic details such as the data source name, host, port, user name, password and the database name.

Make sure you add ?useSSL=false to the JDBC URL after the form is filled. MySQL 5.7+ has SSL enabled by default.

Full JDBC url: jdbc:mysql://localhost:3306/development_database?useSSL=false

Now, with this default configuration, the connection will not work because we need to set the SSH configuration to allow IntelliJ to talk to the MySQL server running within the vagrant VM.

Goto the SSH/SSL tab, select ‘Use SSH tunnel’ and fill-in the details like this:

The value for Private key file should be the one from IdentityFile from your project vagrant directory using this command:

~/Dropbox/Projects/myproject
❯ vagrant ssh-config
Host default
  HostName 127.0.0.1
  User vagrant
  Port 2222
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /Users/lrajasekaran/Dropbox/Projects/myproject/.vagrant/machines/default/virtualbox/private_key/private_key
  IdentitiesOnly yes
  LogLevel FATAL

Now click ‘Test Connection’ from this dialog to ensure the SSH connection is working as expected. Once the SSH connection is setup, close this dialog, go to ‘General’ tab and click ‘Test Connection’ to test the actual database connection using the SSH connection made previously. Once this successful, goto ‘Schemas’ tab and select your database name and click ‘Apply’.

Now you can view all your tables from the Database tool window on the right. Right click on any table and click ‘Jump to Editor’ to see the table rows:

Any value can be edited directly from this UI and then saved to the DB by right clicking the row and clicking ‘Submit’

Categories
React

React – Render String with HTML tags as HTML

TRY IT LIVE

Click the button below to see a live example of this example that you can edit and try for yourself.

CODE SANDBOX

There are often times when you have a string with some HTML tags such as strong that you want to render as HTML on the DOM. Most solutions online recommend using dangerouslySetInnerHTML but that is dangerous as the name suggests. The proper way to render HTML from string is to use FormattedMessage from the formatjs library (react-intl version 5).

Here is a code example:

import React from "react";
import "./styles.css";
import { IntlProvider, FormattedMessage } from "react-intl";

export default function App() {
  return (
    <IntlProvider>
      <div className="App" style={{fontSize: 24}}>
        <FormattedMessage
          id="app.greeting"
          description="Bold text example"
          defaultMessage="Look here, I can include HTML tags in plain string and render them as HTML: <b>Bold</b>, <i>Italics</i> and <a>links too</a>."
          values={{
            b: (chunks) => <b>{chunks}</b>,
            i: (chunks) => <i>{chunks}</i>,
            a: (chunks) => (
              <a class="external_link" target="_blank" href="https://jiga.dev/">
                {chunks}
              </a>
            )
          }}
        />
      </div>
    </IntlProvider>
  );
}

This should show an output as:

Play with code sample in this link: https://codesandbox.io/s/react-render-html-from-props-string-c9ogm

You can also pass the defaultMessage and values as prop values from other components.

In the above example, I have used the version 5 from the react-intl library. In the previous versions, the API is a little different. Follow the official documentation for more options such as using child elements.

Categories
PHP

Debug slow PHP applications using IntelliJ or PHPStorm

It is frustrating when your PHP application is really slow and you do not know which part of your code is taking up too much time.

Google considers 2 seconds as the optimal loading time for a fast loading website.

Enabling PHP XDebug Profiler

PHP’s Xdebug extension can be used to profile your web requests and CLI requests and understand why they are slow and which method is causing the bottleneck.

Follow this link to install XDebug on the server on which your PHP application is running: https://xdebug.org/docs/install

Once XDebug is installed successfully, open the xdebug configuration file and add the following line:

xdebug.profiler_enable = 1

Once you add the above line, the configuration file should look like this:

vagrant@dev:/etc/php/7.2/cli/conf.d$ cat 20-xdebug.ini
zend_extension=xdebug.so
xdebug.show_error_trace = 1
xdebug.profiler_enable = 1

The above file only enables the profile for CLI requests. For enabling the profile for web requests, add the same line into the following file:

vagrant@dev:/etc/php/7.2/fpm/conf.d$ cat 20-xdebug.ini
zend_extension=xdebug.so
xdebug.show_error_trace = 1
xdebug.profiler_enable = 1

Once the files are saved, restart the PHP FPM service:

service php7.2-fpm restart

Now you can open your phpinfo file and verify if the profiler is now enabled:

Generating the profile file

You can now open the slow webpage and PHP XDebug will create a profiler in the profiler_output_dir mentioned above (i.e. ./tmp) which can also be customized in the xdebug ini file.

I opened the homepage of my PHP application and found these files in my /tmp directory:

root@dev:/tmp# ls cache* -gGltrh
-rw-r--r-- 1 1.6K Sep 23 10:29 cachegrind.out.3315.01c00e
-rw-r--r-- 1 1.2M Sep 23 10:29 cachegrind.out.3315
-rw-r--r-- 1 2.1M Sep 23 10:31 cachegrind.out.3312
-rw-r--r-- 1 1.8K Sep 23 10:31 cachegrind.out.3312.0df7d7
-rw-r--r-- 1 1.2M Sep 23 10:31 cachegrind.out.3315.05bc8d
-rw-r--r-- 1 1.2M Sep 23 10:31 cachegrind.out.3312.073921
-rw-r--r-- 1 2.0K Sep 23 10:31 cachegrind.out.3312.03dc36

Analyzing the generated files

Once these files are generated, you can open them in IntelliJ or PHPStorm for analysis:

Once you have finished the analysis, make sure to remove or comment the line xdebug.profiler_enable in both your ini files and restart your FPM service. Otherwise the tmp directory will get filled very quickly.

Other Free Tools

Apart from paid tools such as IntelliJ/PHPStorm, there are some free tools to analyze the generated cachegrind files:

Categories
Database MySQL

5 Reasons MySQL Foreign Key constraints fail to create

Finding out why Foreign key creation fail

When MySQL is unable to create a Foreign Key, it throws out this generic error message:

ERROR 1215 (HY000): Cannot add foreign key constraint

– The most useful error message ever.

Fortunately, MySQL has this useful command that can give the actual reason about why it could not create the Foreign Key.

mysql> SHOW ENGINE INNODB STATUS;

That will print out lots of output but the part we are interested in is under the heading ‘LATEST FOREIGN KEY ERROR’:

------------------------
LATEST FOREIGN KEY ERROR
------------------------
2020-08-29 13:40:56 0x7f3cb452e700 Error in foreign key constraint of table test_database/my_table:
there is no index in referenced table which would contain
the columns as the first columns, or the data types in the
referenced table do not match the ones in table. Constraint:
,
CONSTRAINT idx_name FOREIGN KEY (employee_id) REFERENCES employees (id)
The index in the foreign key in table is idx_name
Please refer to http://dev.mysql.com/doc/refman/5.7/en/innodb-foreign-key-constraints.html for correct foreign key definition.

This output could give you some clue about the actual reason why MySQL could not create your Foreign Key

Reason #1 – Missing unique index on the referenced table

This is probably the most common reason why MySQL won’t create your Foreign Key constraint. Let’s look at an example with a new database and new tables:

In the all below examples, we’ll use a simple ‘Employee to Department” relationship:

mysql> CREATE DATABASE foreign_key_1;
Query OK, 1 row affected (0.00 sec)
mysql> USE foreign_key_1;
Database changed

mysql> CREATE TABLE employees(
    ->     id int,
    ->     name varchar(20),
    ->     department_id int
    -> );
Query OK, 0 rows affected (0.08 sec)

mysql> CREATE TABLE departments(
    ->     id int,
    ->     name varchar(20)
    -> );
Query OK, 0 rows affected (0.07 sec)

As you may have noticed, we have not created the table with PRIMARY KEY or unique indexes. Now let’s try to create Foreign Key constraint between employees.department_id column and departments.id column:

mysql> ALTER TABLE employees ADD CONSTRAINT fk_department_id FOREIGN KEY idx_employees_department_id (department_id) REFERENCES departments(id);
ERROR 1215 (HY000): Cannot add foreign key constraint

Let’s look at the detailed error:

mysql> SHOW ENGINE INNODB STATUS;
------------------------
LATEST FOREIGN KEY ERROR
------------------------
2020-08-31 09:25:13 0x7fddc805f700 Error in foreign key constraint of table foreign_key_1/#sql-5ed_49b:
FOREIGN KEY idx_employees_department_id (department_id) REFERENCES departments(id):
Cannot find an index in the referenced table where the
referenced columns appear as the first columns, or column types
in the table and the referenced table do not match for constraint.
Note that the internal storage type of ENUM and SET changed in
tables created with >= InnoDB-4.1.12, and such columns in old tables
cannot be referenced by such columns in new tables.
Please refer to http://dev.mysql.com/doc/refman/5.7/en/innodb-foreign-key-constraints.html for correct foreign key definition.

This is because we don’t have any unique index on the referenced table i.e. departments. We have two ways of fixing this:

Option 1: Primary Keys

Let’s fix this by adding a primary key departments.id

mysql> ALTER TABLE departments ADD PRIMARY KEY (id);
Query OK, 0 rows affected (0.20 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE employees ADD CONSTRAINT fk_department_id FOREIGN KEY idx_employees_department_id (department_id) REFERENCES departments(id);
Query OK, 0 rows affected (0.19 sec)
Records: 0  Duplicates: 0  Warnings: 0

Option 2: Unique Index

mysql> CREATE UNIQUE INDEX idx_department_id ON departments(id);
Query OK, 0 rows affected (0.13 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE employees ADD CONSTRAINT fk_department_id FOREIGN KEY idx_employees_department_id (department_id) REFERENCES departments(id);
Query OK, 0 rows affected (0.21 sec)
Records: 0  Duplicates: 0  Warnings: 0

Reason #2 – Different data types on the columns

MySQL requires the columns involved in the foreign key to be of the same data types.

mysql> CREATE DATABASE foreign_key_1;
Query OK, 1 row affected (0.00 sec)

mysql> USE foreign_key_1;
Database changed

mysql> CREATE TABLE employees(
    ->     id int,
    ->     name varchar(20),
    ->     department_id int,
    ->     PRIMARY KEY (id)
    -> );
Query OK, 0 rows affected (0.06 sec)

mysql> CREATE TABLE departments(
    ->     id char(20),
    ->     name varchar(20),
    ->     PRIMARY KEY (id)
    -> );
Query OK, 0 rows affected (0.07 sec)

You may have noticed that employees.department_id is int while departments.id is char(20). Let’s try to create a foreign key now:

mysql> ALTER TABLE employees ADD CONSTRAINT fk_department_id FOREIGN KEY idx_employees_department_id (department_id) REFERENCES departments(id);
ERROR 1215 (HY000): Cannot add foreign key constraint

Let’s fix the type of departments.id and try to create the foreign key again:

mysql> ALTER TABLE departments MODIFY id INT;
Query OK, 0 rows affected (0.18 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE employees ADD CONSTRAINT fk_department_id FOREIGN KEY idx_employees_department_id (department_id) REFERENCES departments(id);
Query OK, 0 rows affected (0.26 sec)
Records: 0  Duplicates: 0  Warnings: 0

It works now!

Reason #3 – Different collation/charset type on the table

This is a surprising reason and hard to find out. Let’s create two tables with different collation (or also called charset):

Let’s start from scratch to explain this scenario:

mysql> CREATE DATABASE foreign_key_1;                                                                                        Query OK, 1 row affected (0.00 sec)

mysql> USE foreign_key_1;                                                                                                    Database changed

mysql> CREATE TABLE employees(
    ->     id int,
    ->     name varchar(20),
    ->     department_id int,
    ->     PRIMARY KEY (id)
    -> ) ENGINE=InnoDB CHARACTER SET=utf8;
Query OK, 0 rows affected (0.06 sec)

mysql> CREATE TABLE departments(
    ->     id int,
    ->     name varchar(20),
    ->     PRIMARY KEY (id)
    -> ) ENGINE=InnoDB CHARACTER SET=latin1;
Query OK, 0 rows affected (0.08 sec)

You may notice that we are using a different character set (utf8 and latin1` for both these tables. Let’s try to create the foreign key:

mysql> ALTER TABLE employees ADD CONSTRAINT fk_department_id FOREIGN KEY idx_employees_department_id (department_id) REFERENCES departments(id);
ERROR 1215 (HY000): Cannot add foreign key constraint

It failed because of different character sets. Let’s fix that.

mysql> SET foreign_key_checks = 0; ALTER TABLE departments CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; SET foreign_key_checks = 1;
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.18 sec)
Records: 0  Duplicates: 0  Warnings: 0

Query OK, 0 rows affected (0.00 sec)

mysql> ALTER TABLE employees ADD CONSTRAINT fk_department_id FOREIGN KEY idx_employees_department_id (department_id) REFERENCES departments(id);
Query OK, 0 rows affected (0.20 sec)
Records: 0  Duplicates: 0  Warnings: 0

If you have many tables with a different collation/character set, use this script to generate a list of commands to fix all tables at once:

mysql --database=your_database -B -N -e "SHOW TABLES" | awk '{print "SET foreign_key_checks = 0; ALTER TABLE", $1, "CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; SET foreign_key_checks = 1; "}'

Reason #4 – Different collation types on the columns

This is a rare reason, similar to reason #3 above but at a column level.

Let’s try to reproduce this from scratch:

mysql> CREATE DATABASE foreign_key_1;                                                                                        Query OK, 1 row affected (0.00 sec)

mysql> USE foreign_key_1;                                                                                                    Database changed

mysql> CREATE TABLE employees(
    ->     id int,
    ->     name varchar(20),
    ->     department_id char(26) CHARACTER SET utf8,
    ->     PRIMARY KEY (id)
    -> );
Query OK, 0 rows affected (0.07 sec)

mysql> CREATE TABLE departments(
    ->     id char(26) CHARACTER SET latin1,
    ->     name varchar(20),
    ->     PRIMARY KEY (id)
    -> );
Query OK, 0 rows affected (0.08 sec)

We are using a different character set for employees.department_id and departments.id (utf8 and latin1). Let’s check if the Foreign Key can be created:

mysql> ALTER TABLE employees ADD CONSTRAINT fk_department_id FOREIGN KEY idx_employees_department_id (department_id) REFERENCES departments(id);
ERROR 1215 (HY000): Cannot add foreign key constraint

Nope, as expected. Let’s fix that by changing the character set of departments.id to match with employees.department_id:

mysql> ALTER TABLE departments MODIFY id CHAR(26) CHARACTER SET utf8;
Query OK, 0 rows affected (0.20 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE employees ADD CONSTRAINT fk_department_id FOREIGN KEY idx_employees_department_id (department_id) REFERENCES departments(id);
Query OK, 0 rows affected (0.20 sec)
Records: 0  Duplicates: 0  Warnings: 0

It works now!

Reason #5 -Inconsistent data

This would be the most obvious reason. A foreign key is to ensure that your data remains consistent between the parent and the child table. So when you are creating the foreign key, the existing data is expected to be already consistent.

Let’s setup some inconsistent data to reproduce this problem:

mysql> CREATE DATABASE foreign_key_1;                                                                                        Query OK, 1 row affected (0.00 sec)

mysql> USE foreign_key_1;                                                                                                    Database changed

mysql> CREATE TABLE employees(
    ->     id int,
    ->     name varchar(20),
    ->     department_id int,
    ->     PRIMARY KEY (id)
    -> );
Query OK, 0 rows affected (0.06 sec)

mysql> CREATE TABLE departments(
    ->     id int,
    ->     name varchar(20),
    ->     PRIMARY KEY (id)
    -> );
Query OK, 0 rows affected (0.08 sec)

Let’s insert a department_id in employees table that will not exist in departments.id:

mysql> INSERT INTO employees VALUES (1, 'Amber', 145);
Query OK, 1 row affected (0.01 sec)

Let’s create a foreign key now and see if it works:

mysql> ALTER TABLE employees ADD CONSTRAINT fk_department_id FOREIGN KEY idx_employees_department_id (department_id) REFERENCES departments(id);

ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`foreign_key_1`.`#sql-5ed_49b`, CONSTRAINT `fk_department_id` FOREIGN KEY (`department_id`) REFERENCES `departments` (`id`))

This error message is atleast more useful. We can fix this in two ways. Either by adding the missing department in departments table or by deleting all the employees with the missing department. We’ll do the first option now:

mysql> INSERT INTO departments VALUES (145, 'HR');
Query OK, 1 row affected (0.00 sec)

Let’s try to create the Foreign Key again:

mysql> ALTER TABLE employees ADD CONSTRAINT fk_department_id FOREIGN KEY idx_employees_department_id (department_id) REFERENCES departments(id);
Query OK, 1 row affected (0.24 sec)
Records: 1  Duplicates: 0  Warnings: 0

It worked this time.

So we have seen 5 different ways a Foreign Key creation can fail and possible solutions of how we can fix them. If you have encountered a reason not listed above, add them in the comments.

Categories
jQuery Web Development

jQuery DataTable: Sorting dynamic data

Try this code sample online!

Code samples for the this post is available in GitHub and can be previewed live using the link below.

TRY LIVE

jQuery DataTable is a powerful library to super charge your HTML tables. By default DataTable can sort your data based on the content of the cells. But if you are loading your table data dynamically and want to apply a custom sorting based on the DOM data, you can use DataTable’s custom sorting code:

function applyDomBasedSorting() {
    $.fn.dataTable.ext.order['data-sort'] = function(settings, col) {
        return this.api().column(col, {
            order: 'index'
        }).nodes().map(function(td, i) {
            return $(td).attr('data-sort');
        });
    }
}

data-sort could be anything. You can define your own sorting logic here. In this example, I would like to sort name by their second names. I’m setting this value into the data-sort attribute of the td cells.

Once you have defined your custom sorting logic like above, then you can use that when initializing your data table.

applyDomBasedSorting();
$('#employees').DataTable(
      {
        "columnDefs": [
          { "orderDataType": "data-sort", "targets": [1]}
        ],
      }
  );

GitHub Source code link: https://github.com/jiga-eng/jquery-code-samples/tree/master/datatable-code-samples/sorting