From ae988d00b0d1a8aac8dc54f808610a25ffe2c2ca Mon Sep 17 00:00:00 2001 From: YANG QIA <2013xile@gmail.com> Date: Wed, 27 Sep 2023 15:50:04 +0800 Subject: [PATCH] refactor(bi): improve chart frontend api (#2721) * refactor: improve chart frontend api * chore: remove redundant import * fix: rename chartType * chore: add migration check * fix: add migration * fix: change version * chore: update --- .../src/client/DataVisualization.tsx | 24 +++++ .../src/client/Settings.tsx | 43 --------- .../src/client/chart/antd/statistic.ts | 1 - .../src/client/chart/chart.ts | 41 ++------- .../src/client/chart/configs.ts | 14 ++- .../src/client/chart/group.ts | 86 ++++++++++++++++++ .../src/client/chart/library.tsx | 89 ------------------- .../src/client/configure/ChartConfigure.tsx | 2 +- .../src/client/index.tsx | 55 ++++-------- .../src/client/renderer/ChartRenderer.tsx | 5 +- .../20230926211750-rename-charttype.ts | 32 +++++++ .../src/server/plugin.ts | 9 ++ 12 files changed, 189 insertions(+), 212 deletions(-) create mode 100644 packages/plugins/@nocobase/plugin-data-visualization/src/client/DataVisualization.tsx delete mode 100644 packages/plugins/@nocobase/plugin-data-visualization/src/client/Settings.tsx create mode 100644 packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/group.ts delete mode 100644 packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/library.tsx create mode 100644 packages/plugins/@nocobase/plugin-data-visualization/src/server/migrations/20230926211750-rename-charttype.ts diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/DataVisualization.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/DataVisualization.tsx new file mode 100644 index 000000000..9c9c7cec6 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/DataVisualization.tsx @@ -0,0 +1,24 @@ +import { SchemaInitializerContext, SchemaInitializerProvider } from '@nocobase/client'; +import React, { useContext } from 'react'; +import { ChartInitializers } from './block'; +import { useChartsTranslation } from './locale'; + +export const DataVisualization: React.FC = (props) => { + const { t } = useChartsTranslation(); + const initializers = useContext(SchemaInitializerContext); + const children = initializers.BlockInitializers.items[0].children; + const has = children.some((initializer) => initializer.component === 'ChartV2BlockInitializer'); + if (!has) { + children.push({ + key: 'chart-v2', + type: 'item', + title: t('Charts'), + component: 'ChartV2BlockInitializer', + }); + } + return ( + + {props.children} + + ); +}; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/Settings.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/Settings.tsx deleted file mode 100644 index 96efb6840..000000000 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/Settings.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { CheckOutlined } from '@ant-design/icons'; -import { Form, FormItem } from '@formily/antd-v5'; -import { css } from '@nocobase/client'; -import { Button, Card } from 'antd'; -import cls from 'classnames'; -import React, { useContext } from 'react'; -import { useChartsTranslation } from './locale'; -import { ChartLibraryContext, useToggleChartLibrary } from './chart/library'; - -export const Settings = () => { - const { t } = useChartsTranslation(); - const libraries = useContext(ChartLibraryContext); - const { toggle } = useToggleChartLibrary(); - const list = Object.entries(libraries).map(([library, { enabled }]) => { - return ( - - ); - }); - return ( - -
- {list} -
-
- ); -}; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/statistic.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/statistic.ts index b29f20407..b790d29e5 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/statistic.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/statistic.ts @@ -1,4 +1,3 @@ -import { ISchema } from '@formily/react'; import { AntdChart } from './antd'; import { Statistic as AntdStatistic } from 'antd'; import { lang } from '../../locale'; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/chart.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/chart.ts index c5a1e2564..5aa5ef746 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/chart.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/chart.ts @@ -3,7 +3,7 @@ import { FieldOption } from '../hooks'; import { QueryProps } from '../renderer'; import { parseField } from '../utils'; import { ISchema } from '@formily/react'; -import configs, { AnySchemaProperties, ConfigProps } from './configs'; +import configs, { AnySchemaProperties, Config } from './configs'; export type RenderProps = { data: any[]; @@ -21,21 +21,6 @@ export interface ChartType { title: string; component: React.FC; schema: ISchema; - infer: ( - fields: FieldOption[], - { - measures, - dimensions, - }: { - measures?: QueryProps['measures']; - dimensions?: QueryProps['dimensions']; - }, - ) => { - xField: FieldOption; - yField: FieldOption; - seriesField: FieldOption; - yFields: FieldOption[]; - }; init?: ( fields: FieldOption[], query: { @@ -46,38 +31,25 @@ export interface ChartType { general?: any; advanced?: any; }; - /** - * getProps - * Accept the information that the chart component needs to render, - * process it and return the props of the chart component. - */ - getProps: (props: RenderProps) => any; + render: (props: RenderProps) => React.FC; getReference?: () => { title: string; link: string; }; - render: (props: RenderProps) => React.FC; } -type Config = ( - | (ConfigProps & { - property?: string; - }) - | string -)[]; - export type ChartProps = { name: string; title: string; component: React.FC; - config?: Config; + config?: Config[]; }; export class Chart implements ChartType { name: string; title: string; component: React.FC; - config: Config; + config: Config[]; configs = new Map(); constructor({ name, title, component, config }: ChartProps) { @@ -184,6 +156,11 @@ export class Chart implements ChartType { return { xField, yField, seriesField, yFields }; } + /** + * getProps + * Accept the information that the chart component needs to render, + * process it and return the props of the chart component. + */ getProps(props: RenderProps) { return props; } diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/configs.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/configs.ts index 2c4f3e93c..83387f886 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/configs.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/configs.ts @@ -11,10 +11,16 @@ export type FieldConfigProps = Partial<{ export type AnySchemaProperties = SchemaProperties; export type ConfigProps = FieldConfigProps | AnySchemaProperties | (() => AnySchemaProperties); +export type Config = + | (ConfigProps & { + property?: string; + }) + | string; + const selectField = ({ name, title, required, defaultValue }: FieldConfigProps) => { return { - [name]: { - title: lang(title), + [name || 'field']: { + title: lang(title || 'Field'), type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Select', @@ -27,8 +33,8 @@ const selectField = ({ name, title, required, defaultValue }: FieldConfigProps) const booleanField = ({ name, title, defaultValue = false }: FieldConfigProps) => { return { - [name]: { - 'x-content': lang(title), + [name || 'field']: { + 'x-content': lang(title || 'Field'), type: 'boolean', 'x-decorator': 'FormItem', 'x-component': 'Checkbox', diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/group.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/group.ts new file mode 100644 index 000000000..91624d8dc --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/group.ts @@ -0,0 +1,86 @@ +import { usePlugin } from '@nocobase/client'; +import { ChartType } from './chart'; +import DataVisualizationPlugin from '..'; +import { lang } from '../locale'; + +export class ChartGroup { + charts: Map = new Map(); + + setGroup(name: string, charts: ChartType[]) { + this.charts.set(name, charts); + } + + addGroup(name: string, charts: ChartType[]) { + if (this.charts.has(name)) { + throw new Error(`[data-visualization] Chart group "${name}" already exists`); + } + this.setGroup(name, charts); + } + + add(group: string, chart: ChartType) { + if (!this.charts.has(group)) { + this.setGroup(group, []); + } + this.charts.get(group)?.push(chart); + } + + getChartTypes(): { + label: string; + children: { + key: string; + label: string; + value: string; + }[]; + }[] { + const result = []; + this.charts.forEach((charts, group) => { + const children = charts.map((chart) => ({ + key: `${group}.${chart.name}`, + label: lang(chart.title), + value: `${group}.${chart.name}`, + })); + result.push({ + label: lang(group), + children, + }); + }); + return result; + } + + getCharts(): { + [key: string]: ChartType; + } { + const result = {}; + this.charts.forEach((charts, group) => { + charts.forEach((chart) => { + result[`${group}.${chart.name}`] = chart; + }); + }); + return result; + } + + getChart(type: string): ChartType { + const charts = this.getCharts(); + return charts[type]; + } +} + +export const useChartTypes = () => { + const plugin = usePlugin(DataVisualizationPlugin); + return plugin.charts.getChartTypes(); +}; + +export const useDefaultChartType = () => { + const chartTypes = useChartTypes(); + return chartTypes[0]?.children?.[0]?.value; +}; + +export const useCharts = () => { + const plugin = usePlugin(DataVisualizationPlugin); + return plugin.charts.getCharts(); +}; + +export const useChart = (type: string) => { + const plugin = usePlugin(DataVisualizationPlugin); + return plugin.charts.getChart(type); +}; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/library.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/library.tsx deleted file mode 100644 index cef1bb675..000000000 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/library.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import React, { createContext, useContext } from 'react'; -import { lang } from '../locale'; -import { ChartType } from './chart'; - -export type ChartLibraries = { - [library: string]: { - enabled: boolean; - charts: ChartType[]; - }; -}; - -export const ChartLibraryContext = createContext({}); - -export const useCharts = (): { - [name: string]: ChartType; -} => { - const library = useContext(ChartLibraryContext); - return Object.values(library) - .filter((l) => l.enabled) - .reduce((allCharts, l) => { - const charts = Object.values(l.charts); - return { - ...allCharts, - ...charts.reduce((all, chart) => { - return { - ...all, - [chart.name]: chart, - }; - }, {}), - }; - }, {}); -}; - -export const useChartTypes = (): { - label: string; - children: { - key: string; - label: string; - value: string; - }[]; -}[] => { - const library = useContext(ChartLibraryContext); - return Object.entries(library) - .filter(([_, l]) => l.enabled) - .reduce((charts, [name, l]) => { - const children = Object.values(l.charts).map((chart) => ({ - key: chart.name, - label: lang(chart.title), - value: chart.name, - })); - return [ - ...charts, - { - label: lang(name), - children, - }, - ]; - }, []); -}; - -export const useDefaultChartType = () => { - const chartTypes = useChartTypes(); - return chartTypes[0]?.children?.[0]?.value; -}; - -export const useToggleChartLibrary = () => { - const ctx = useContext(ChartLibraryContext); - return { - toggle: (library: string) => { - ctx[library].enabled = !ctx[library].enabled; - }, - }; -}; - -export const ChartLibraryProvider: React.FC<{ - name: string; - charts: ChartType[]; -}> = (props) => { - const { children, charts, name } = props; - const ctx = useContext(ChartLibraryContext); - const library = { - ...ctx, - [name]: { - charts, - enabled: true, - }, - }; - return {children}; -}; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/ChartConfigure.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/ChartConfigure.tsx index 2e2188730..47a463730 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/ChartConfigure.tsx +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/ChartConfigure.tsx @@ -29,7 +29,7 @@ import { useChartsTranslation } from '../locale'; import { ChartRenderer, ChartRendererContext } from '../renderer'; import { createRendererSchema, getField, getSelectedFields } from '../utils'; import { getConfigSchema, querySchema, transformSchema } from './schemas/configure'; -import { useChartTypes, useCharts, useDefaultChartType } from '../chart/library'; +import { useChartTypes, useCharts, useDefaultChartType } from '../chart/group'; import { FilterDynamicComponent } from './FilterDynamicComponent'; const { Paragraph, Text } = Typography; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/index.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/index.tsx index 988e6e757..60a2f8728 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/index.tsx @@ -1,51 +1,28 @@ -import { Plugin, SchemaComponentOptions, SchemaInitializerContext, SchemaInitializerProvider } from '@nocobase/client'; -import React, { useContext } from 'react'; -import { ChartInitializers, ChartV2Block, ChartV2BlockDesigner, ChartV2BlockInitializer } from './block'; -import { useChartsTranslation } from './locale'; -import { ChartRenderer, ChartRendererProvider } from './renderer'; -import { ChartLibraryProvider } from './chart/library'; +import { Plugin } from '@nocobase/client'; +import { DataVisualization } from './DataVisualization'; +import { ChartGroup } from './chart/group'; import g2plot from './chart/g2plot'; import antd from './chart/antd'; - -const DataVisualization: React.FC = (props) => { - const { t } = useChartsTranslation(); - const initializers = useContext(SchemaInitializerContext); - const children = initializers.BlockInitializers.items[0].children; - const has = children.some((initializer) => initializer.component === 'ChartV2BlockInitializer'); - if (!has) { - children.push({ - key: 'chart-v2', - type: 'item', - title: t('Charts'), - component: 'ChartV2BlockInitializer', - }); - } - return ( - - - - {props.children} - - - - ); -}; +import { ChartV2Block, ChartV2BlockDesigner, ChartV2BlockInitializer } from './block'; +import { ChartRenderer, ChartRendererProvider } from './renderer'; class DataVisualizationPlugin extends Plugin { + public charts: ChartGroup = new ChartGroup(); + async load() { + this.charts.setGroup('Built-in', [...g2plot, ...antd]); + + this.app.addComponents({ + ChartV2BlockInitializer, + ChartRenderer, + ChartV2BlockDesigner, + ChartV2Block, + ChartRendererProvider, + }); this.app.addProvider(DataVisualization); } } export default DataVisualizationPlugin; -export { ChartLibraryProvider }; export { Chart } from './chart/chart'; export type { ChartType } from './chart/chart'; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRenderer.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRenderer.tsx index 92ce7f99c..8bdd18b1e 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRenderer.tsx +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRenderer.tsx @@ -14,8 +14,8 @@ import { ChartConfigContext } from '../configure/ChartConfigure'; import { useData, useFieldTransformer, useFieldsWithAssociation } from '../hooks'; import { useChartsTranslation } from '../locale'; import { createRendererSchema, getField } from '../utils'; -import { useCharts } from '../chart/library'; import { ChartRendererContext } from './ChartRendererProvider'; +import { useChart } from '../chart/group'; const { Paragraph, Text } = Typography; export const ChartRenderer: React.FC & { @@ -30,8 +30,7 @@ export const ChartRenderer: React.FC & { const advanced = config?.advanced || {}; const api = useAPIClient(); - const charts = useCharts(); - const chart = charts[config?.chartType]; + const chart = useChart(config?.chartType); const locale = api.auth.getLocale(); const transformers = useFieldTransformer(transform, locale); const Component = chart?.render({ diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/migrations/20230926211750-rename-charttype.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/migrations/20230926211750-rename-charttype.ts new file mode 100644 index 000000000..cf51b8a4b --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/migrations/20230926211750-rename-charttype.ts @@ -0,0 +1,32 @@ +import { Migration } from '@nocobase/server'; +import { Repository } from '@nocobase/database'; + +export default class RenameChartTypeMigration extends Migration { + async up() { + const result = await this.app.version.satisfies('<=0.14.0-alpha.7'); + + if (!result) { + return; + } + + const r = this.db.getRepository('uiSchemas'); + const items = await r.find({ + filter: { + 'schema.x-decorator': 'ChartRendererProvider', + }, + }); + + await this.db.sequelize.transaction(async (transaction) => { + for (const item of items) { + const schema = item.schema; + const chartType = schema['x-decorator-props']?.config?.chartType; + if (!chartType || chartType.startsWith('Built-in.')) { + continue; + } + schema['x-decorator-props'].config.chartType = `Built-in.${chartType}`; + item.set('schema', schema); + await item.save({ transaction }); + } + }); + } +} diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/plugin.ts index 492dbe6ea..18cda06e9 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/server/plugin.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/plugin.ts @@ -1,6 +1,7 @@ import { Cache, createCache } from '@nocobase/cache'; import { InstallOptions, Plugin } from '@nocobase/server'; import { query } from './actions/query'; +import { resolve } from 'path'; export class DataVisualizationPlugin extends Plugin { cache: Cache; @@ -18,6 +19,14 @@ export class DataVisualizationPlugin extends Plugin { } async load() { + this.db.addMigrations({ + namespace: 'data-visulization', + directory: resolve(__dirname, 'migrations'), + context: { + plugin: this, + }, + }); + this.cache = createCache({ ttl: 30, // seconds max: 1000,