Разработка плагина таблицы и перенос его из проекта в проект в 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












