info@lasmart.biz

december 26, 2023

Table plugin development and moving it project to project in ApacheSuperset

In continuation of the plugin development for ApacheSuperset topic, I had an idea to create a chart oriented to solve a narrow task for testing the work of custom visualizations and checking the implemented features. This article will describe the process of developing a table with the ability to gradient fill cells in a selected row with specified colors.

Let’s create a simple plugin, call it «super-table» and plug it into the ApacheSuperset dev environment. Instructions can be found at this post: Running a dev environment and developing a simple plugin in Apache Superset.

The plugin is developed in TypeScript, an open-source language based on JavaScript, in which the definition of static types has been added. As a table we will use a ready-made component from Ant Design library for creating web applications, which is developed based on React by Alibaba Group. Documentation can be found at https://ant.design/components/table.

Install the necessary dependencies in the directory with the plugin:

npm i @antdesign/icons

npm i antd

If an error occurs try installing via the force command, example:

 npm i @antdesign/icons –force

Run dev (npm run dev) and import the table into the plugin source code (src folder, SuperTable.tsx file):

import { Table, Tag } from ‘antd’;

import type { ColumnsType } from ‘antd/es/table’;

Next, before the return expression, we insert the example described in the component documentation:

  const dataSource = [

    {

      key: ‘1’,

      name: ‘Mike’,

      age: 32,

      address: ’10 Downing Street’,

    },

    {

      key: ‘2’,

      name: ‘John’,

      age: 42,

      address: ’10 Downing Street’,

    },

  ];

   const columns = [

    {

      title: ‘Name’,

      dataIndex: ‘name’,

      key: ‘name’,

    },

    {

      title: ‘Age’,

      dataIndex: ‘age’,

      key: ‘age’,

    },

    {

      title: ‘Address’,

      dataIndex: ‘address’,

      key: ‘address’,

    },

  ];

   return (

    <Styles

      ref={rootElem}

      boldText={props.boldText}

      headerFontSize={props.headerFontSize}

      height={height}

      width={width}

    >

      <Table dataSource={dataSource} columns={columns} />

    </Styles>

  );

}

Save the result, saving will start the plugin and dev environment Superset build. The following problems may occur during the build:

— not all necessary libraries are installed, this is solved via:

npm i <library name>

-error «can’t resolve process/ browser …». Run the command in the plugin directory and restart the dev environment:

npm install —save-dev process 

Once built in the SuperSet dev environment, the plugin will display the table:

Next, we need to connect data from the dataset to the visualization. Let’s create a test dataset for testing:

 SELECT ‘2022’    as name, 14000 as Jan, 14 as Feb, 5  as Mar, 10 as Apr, 35 as May, 16 as Jun, 15 as Jul, 65  as Aug, 2  as Sep, 2 as Oct, 3 as Nov, 6 as Dec

union all 

SELECT ‘2023’    as name, 13000 as Jan, 15 as Feb, 10 as Mar, 7  as Apr, 33 as May, 18 as Jun, 19 as Jul, 55  as Aug, 1  as Sep, 4 as Oct, 3 as Nov, 3 as Dec

union all 

SELECT ‘Прирост’ as name, 6 as Jan, 5  as Feb, 4  as Mar, 3 as Apr, 2 as May, 1  as Jun, 0  as Jul, -2 as Aug, -4 as Sep, -6 as Oct, -8 as Nov, -10 as Dec

 

Let’s return to the plugin directory. In the example code, the constant dataSource is the displayed data, the constant columns contains the properties of the displayed columns. Instead of dataSource, let’s substitute the sample table from Superset, data:

  const data22: DataType[] = data;

  return (

    <Styles

      ref={rootElem}

      boldText={props.boldText}

      headerFontSize={props.headerFontSize}

      height={height}

      width={width}

    >

      <Table dataSource={data22} size=»small»/>

    </Styles>

  );

Save the changes and wait for the build. Set up month-to-month data output in SuperSet using the developed plugin:

To color a certain row in the table, let’s create a simple interface with several parameters that we will enter in the «CUSTOMIZE» tab. To do this, in the «plagin» folder find the controlPanel.ts file and add the description of input windows to the config constant:
— col_name – column name for selecting the customization row;
— row_name – string content for customization;
— color1 –fill color to the right of the set relative_number value;
— color2 – fill color to the left of the set relative_number value;
— relative_number – the number against which the values in the cells are compared.

controlSetRows: [

        [

          {

            name: ‘col_name’,

            config: {

              type: ‘TextControl’,

              default: ‘met’,

              renderTrigger: true,

              label: t(‘Column for customize’),

            },

          },

        ],

        [

          {

            name: ‘row_name’,

            config: {

              type: ‘TextControl’,

              default: ‘Прирост’,

              renderTrigger: true,

              label: t(‘Row for customize’),

            },

          },

        ],

        [

          {

            name: ‘color1’,

            config: {

              type: ‘TextControl’,

              default: ‘#F79689’,

              renderTrigger: true,

              label: t(‘Color < ‘),

            },

          },

        ],

        [

          {

            name: ‘color2’,

            config: {

              type: ‘TextControl’,

              default: ‘#C0F789’,

              renderTrigger: true,

              label: t(‘Color >= ‘),

            },

          },

        ],

        [

          {

            name: ‘relative_number’,

            config: {

              type: ‘TextControl’,

              default: ‘0’,

              renderTrigger: true,

              label: t(‘Relative Number’),

            },

          },

        ],

 

After assembly, the following will be displayed in the CUSTOMIZE menu:

Next, the values from the interface need to be passed into the main file and applied in the columns constant to do so:
— transformProps.ts file to complete:

  const { width, height, formData, queriesData } = chartProps;

  const { boldText, headerFontSize, colName, rowName, color1, color2, relativeNumber } = formData;

  const data = queriesData[0].data as TimeseriesDataRecord[];

  console.log(‘formData via TransformProps.ts’, formData);

  return {

    width,

    height,

    data,

    boldText,

    headerFontSize,

    colName,

    rowName,

    color1, 

    color2,

    relativeNumber,

  };

 

}

 

— file types.ts to complete:

export interface SuperTableStylesProps {

  height: number;

  width: number;

  headerFontSize: keyof typeof supersetTheme.typography.sizes;

  boldText: boolean;

  colName: string;

  rowName: string;

  color1: string;

  color2: string;

  relativeNumber: number;

 

 

— the final code in the SuperTable.tsx file looks as follows:

import React, { useEffect, createRef } from ‘react’;

import { styled } from ‘@superset-ui/core’;

import { SuperTableProps, SuperTableStylesProps } from ‘./types’;

import { Table, Tag } from ‘antd’;

import type { ColumnsType } from ‘antd/es/table’;

const Styles = styled.div<SuperTableStylesProps>`

  height: ${({ height }) => height}px;

  width: ${({ width }) => width}px;

  colName: ${({ colName }) => colName};

  color1: ${({ color1 }) => color1};

  color2: ${({ color2 }) => color2};

  rowName: ${({ rowName }) => rowName};

  padding: 2px;

`;

var numberFormat = new Intl.NumberFormat(‘ru-RU’);

 

function hexToRGB(hex = ‘FF0000’, alpha = 0.1) {

  var r = parseInt(hex.slice(1, 3), 16),

      g = parseInt(hex.slice(3, 5), 16),

      b = parseInt(hex.slice(5, 7), 16);

 

  return «rgba(» + r + «, » + g + «, » + b + «, » + alpha + «)»;

}

function getColorRGB(

  hex=’FF0000′, 

  val=1, 

  arr=[1,2,3],

  isMax=1,

  relativeNumber=0) {

  var min = isMax == 1 ? Math.min(…arr) : relativeNumber,

      max = isMax == 1 ? relativeNumber : Math.max(…arr),

      alpha = isMax == 1 ? (max — val)/(max — min) : (val — min)/(max — min) ; //

  console.log(‘Plugin’, min, max,val);

  return hexToRGB(hex, alpha);

}

export default function SuperTable(props: SuperTableProps) {

  const { data, height, width} = props;

 

  const rootElem = createRef<HTMLDivElement>();

 

  useEffect(() => {

    const root = rootElem.current as HTMLElement;

    console.log(‘Plugin element’, root);

  });

 

  interface DataType {

    [data: string]: any;

  }

  const columns: ColumnsType<DataType> = [];  

  //console.log(‘Plugin color’, props.color2);

  for (let colval in Object.keys(data[0])) {

    let coltitle  = Object.keys(data[0])[colval];

    

    if (coltitle != props.colName) {

      columns.push({

        title: coltitle,

        dataIndex: coltitle,

        key: coltitle,

        render: (met, key) => {

          let c = ‘white’;

          if (props.rowName.split(‘, ‘).includes(key[props.colName]) )

          {

            const result = Object.values(data.reduce((res, obj) => obj.name == props.rowName ? obj : res, {})); 

            var arr_val: number[] = [];

 

            for (var i = 1; i < result.length; i++) {

              arr_val.push(result[i]);

            }

            

            c = (met >= props.relativeNumber) ? 

            getColorRGB( props.color2, met, arr_val, 0, props.relativeNumber) :

            getColorRGB( props.color1, met, arr_val, 1, props.relativeNumber) ; //props.color2

          }

          

          return (

            <Tag style={{display: ‘block’, background: c, border:  ‘none’}} key={met} >

              {numberFormat.format(met)}

            </Tag>

          );

        }

      })

    }

    else {

      columns.push({

        title: coltitle,

        dataIndex: coltitle,

        key: coltitle,

      })

    }

  }

  const data22: DataType[] = data;

  return (

 

    <Styles

      ref={rootElem}

      boldText={props.boldText}

      headerFontSize={props.headerFontSize}

      height={height}

      width={width}

    >

      <Table columns={columns} dataSource={data22} size=»small»/>

    </Styles>

  );

}

 

Once built, the plugin will display the following:

To be able to connect the developed plugin to any Superset project, we will publish it in the npmjs.com repository. To do this, you need to register and create an organization inside your account (for testing we created @superset-cat). Next, in the folder with the built plugin, run the command:

 

npm login

 

And then fill in the requested account information (login, password, mail key, etc.).

Run the plugin initialization command:

 

npm init

 

And enter the package parameters (name, including organization, version, license, Keywords and others):

— package name: (@superset-cat/super-table) @superset-cat/super-table:

In the plugin’s package.json file, replace private: true with false

Publish the plugin to the public with the command:

 

npm publish

Once the process is successful, log into your npm account and check for the plugin, link to super-table: https://www.npmjs.com/package/@superset-cat/super-table:

 

Connect the plugin to the project and build a new image. In the superset/superset-frontend directory install the plugin by following the link

npm i @superset-cat/super-table 

After installation, import the plugin, following all the rules from the last article:

 

 import { SuperTable } from ‘super-table’;

new SuperTable().configure({ key: ‘super-table’ }),

You need to check that the key matches the keyword in the published plugin:

 

Run npm run dev-server and check for the plugin.

Stop dev-server and SuperSet container, start building a new image

sudo docker build -f Dockerfile —force-rm -t apache/superset:${TAG:-latest-<Номер образа>} /home/juls/tst/superset 

After the deployment, in the file docker-compose-non-dev.yml change the image version to the newly built one and save.
Run Superset:

sudo docker-compose -f docker-compose-non-dev.yml up –d

For more information, please get in touch here:

Contact Form main