блог о bi, №1 в рунете

Разработка плагина таблицы и перенос его из проекта в проект в ApacheSuperset.

В продолжение темы о разработке плагина для ApacheSuperset, возникла идея создать чарт, ориентированный под решения узкой задачи для тестирования работы кастомных визуализаций и проверки реализуемых возможностей. В этой статье будет описан процесс разработки таблицы с возможностью градиентной заливки ячеек в выбранной строке заданными цветами.

Создадим простой плагин, назовем его “super-table” и подключим его к dev среде ApacheSuperset. Инструкцию можно найти по ссылке.

Разработка плагина ведется на языке TypeScript с открытым исходным кодом, основанном на JavaScript, в который добавлены определение статические типы. В качестве таблицы будем использовать готовый компонент из библиотеки Ant Design для создания веб-приложений, которая разработана на основе React компанией Alibaba Group. Документацию можно найти по ссылке по ссылке.

Устанавливаем необходимые зависимости в каталоге с плагином:

npm i @ant-design/icons
npm i antd
Если возникает ошибка попробуйте установить через команду force, пример:

npm i @ant-design/icons –force
Запускаем dev (npm run dev) и импортируем таблицу в исходный код плагина (папка src, файл SuperTable.tsx):

import { Table, Tag } from 'antd';
import type { ColumnsType } from 'antd/es/table';
Далее до выражения return вставляем пример, описанный в документации на компонент:

 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>
  );
}
Сохраняем результат, при сохранение будет запущена сборка плагина и dev среды Superset. При сборке могут возникнуть следующие проблемы:
- не все необходимые библиотеки установлены, это решается через:

npm i <название библиотеки>
- ошибка «can't resolve process/ browser …». Выполните команду в каталоге плагина и перезапустите dev среду:

npm install --save-dev process 
После сборки в dev среде SuperSet плагин будет отображать таблицу:
Далее необходимо к визуализации подключить данные из датасета. Создадим тестовый датасет для проверки:

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
Возвращаемся в каталог плагина. В коде примера константа dataSource – отображаемые данные, константа columns – содержит свойства выводимых колонок. Вместо dataSource подставим в таблицу пробы из Superset, data:

  const { data, height, width} = props;
  const data22: DataType[] = data;
  return (
    <Styles
      ref={rootElem}
      boldText={props.boldText}
      headerFontSize={props.headerFontSize}
      height={height}
      width={width}
    >
      <Table dataSource={data22} size="small"/>
    </Styles>
  );
Сохраняем изменения и дожидаемся сборки. Настраиваем в SuperSet вывод данных из месяца в месяц используя разработанный плагин:
Чтобы подкрашивать определенную строку в таблице, создадим простой интерфейс с несколькими параметрами, которые будем вводить во вкладке «CUSTOMIZE». Для этого в папке «plagin» находим файл controlPanel.ts и добавляем в константу config описание окон ввода:
  • col_name – имя колонки для выбора строки кастомизации;
  • row_name – содержание строки для кастомизации;
  • color1 – цвет заливки правее заданного значения relative_number;
  • color2 – цвет заливки левее заданного значения relative_number;
  • relative_number – число, относительно которого сравниваются значения в ячейках.

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'),
            },
          },
        ],
После сборки в меню CUSTOMIZE будет отображаться следующее:
Далее значения из интерфейса необходимо передать в основной файл и применить их в константе columns для этого:
файл transformProps.ts дополнить:

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,
  };
}
файл types.ts дополнить:

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;
} 
итоговый код в файле SuperTable.tsx выглядит следующим образом:

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>
  );
}
После сборки плагин будет отображать следующее:
Чтобы была возможность подключить разработанный плагин к любому проекту Superset опубликуем его в репозиторий npmjs.com. Для этого необходимо зарегистрироваться и внутри аккаунта создать организацию (для тестирования мы создали @superset-cat). Далее в папке с построенным плагином запускаем команду:

npm login
И далее заполняем запрашиваемую информацию об аккаунте (логин, пароль, ключ с почты и пр.).
Запускаем команду инициализации плагина:

npm init
И вбиваем параметры пакета (название, с учетом организации, версию, лицензию, Keywords и другие):
- package name: (@superset-cat/super-table) @superset-cat/super-table:
В файле package.json плагина необходимо заменить private: true на false
Публикуем плагин в открытый доступ командой:

npm publish
После успешного выполнения процесса, заходим в аккаунт npm и проверяем наличие плагина, ссылка на super-table тут.
Подключаем плагин к проекту и собираем новый образ. В директории superset/superset-frontend устанавливаем плагин по ссылке

npm i @superset-cat/super-table 
После установки импортируем плагин, соблюдая все правила из прошлой статьи:

import { SuperTable } from 'super-table';
new SuperTable().configure({ key: 'super-table' }),
Необходимо проверить, чтобы key совпадало с ключевым словом в опубликованном плагине:
Запускаем npm run dev-server и проверяем наличие плагина.
Останавливаем dev-server и контейнер SuperSet, запускаем сборку нового образа

sudo docker build -f Dockerfile --force-rm -t apache/superset:${TAG:-latest-<Номер образа>} /home/juls/tst/superset 
После деплоя в файле docker-compose-non-dev.yml меняем версию образа на только что собранную и сохраняем.
Запускаем Superset:

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