@nocobase/plugin-data-visualization 0.10.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +88 -0
  3. package/client.d.ts +3 -0
  4. package/client.js +65 -0
  5. package/lib/client/Settings.d.ts +2 -0
  6. package/lib/client/Settings.js +81 -0
  7. package/lib/client/block/ChartBlock.d.ts +2 -0
  8. package/lib/client/block/ChartBlock.js +73 -0
  9. package/lib/client/block/ChartBlockDesigner.d.ts +2 -0
  10. package/lib/client/block/ChartBlockDesigner.js +35 -0
  11. package/lib/client/block/ChartBlockInitializer.d.ts +6 -0
  12. package/lib/client/block/ChartBlockInitializer.js +114 -0
  13. package/lib/client/block/ChartConfigure.d.ts +33 -0
  14. package/lib/client/block/ChartConfigure.js +501 -0
  15. package/lib/client/block/formatters.d.ts +15 -0
  16. package/lib/client/block/formatters.js +58 -0
  17. package/lib/client/block/index.d.ts +4 -0
  18. package/lib/client/block/index.js +49 -0
  19. package/lib/client/block/schemas/configure.d.ts +4 -0
  20. package/lib/client/block/schemas/configure.js +492 -0
  21. package/lib/client/block/transformers.d.ts +6 -0
  22. package/lib/client/block/transformers.js +69 -0
  23. package/lib/client/hooks.d.ts +312 -0
  24. package/lib/client/hooks.js +275 -0
  25. package/lib/client/index.d.ts +5 -0
  26. package/lib/client/index.js +70 -0
  27. package/lib/client/locale/en-US.d.ts +23 -0
  28. package/lib/client/locale/en-US.js +29 -0
  29. package/lib/client/locale/index.d.ts +3 -0
  30. package/lib/client/locale/index.js +39 -0
  31. package/lib/client/locale/ja-JP.d.ts +2 -0
  32. package/lib/client/locale/ja-JP.js +8 -0
  33. package/lib/client/locale/pt-BR.d.ts +23 -0
  34. package/lib/client/locale/pt-BR.js +29 -0
  35. package/lib/client/locale/ru-RU.d.ts +2 -0
  36. package/lib/client/locale/ru-RU.js +8 -0
  37. package/lib/client/locale/tr-TR.d.ts +2 -0
  38. package/lib/client/locale/tr-TR.js +8 -0
  39. package/lib/client/locale/zh-CN.d.ts +70 -0
  40. package/lib/client/locale/zh-CN.js +76 -0
  41. package/lib/client/renderer/ChartLibrary.d.ts +71 -0
  42. package/lib/client/renderer/ChartLibrary.js +140 -0
  43. package/lib/client/renderer/ChartRenderer.d.ts +7 -0
  44. package/lib/client/renderer/ChartRenderer.js +258 -0
  45. package/lib/client/renderer/ChartRendererProvider.d.ts +43 -0
  46. package/lib/client/renderer/ChartRendererProvider.js +38 -0
  47. package/lib/client/renderer/index.d.ts +4 -0
  48. package/lib/client/renderer/index.js +49 -0
  49. package/lib/client/renderer/library/AntdLibrary.d.ts +2 -0
  50. package/lib/client/renderer/library/AntdLibrary.js +123 -0
  51. package/lib/client/renderer/library/G2PlotLibrary.d.ts +2 -0
  52. package/lib/client/renderer/library/G2PlotLibrary.js +288 -0
  53. package/lib/client/renderer/library/index.d.ts +3 -0
  54. package/lib/client/renderer/library/index.js +15 -0
  55. package/lib/client/utils.d.ts +96 -0
  56. package/lib/client/utils.js +137 -0
  57. package/lib/index.d.ts +1 -0
  58. package/lib/index.js +13 -0
  59. package/lib/server/actions/formatter.d.ts +3 -0
  60. package/lib/server/actions/formatter.js +44 -0
  61. package/lib/server/actions/query.d.ts +86 -0
  62. package/lib/server/actions/query.js +326 -0
  63. package/lib/server/index.d.ts +1 -0
  64. package/lib/server/index.js +13 -0
  65. package/lib/server/plugin.d.ts +13 -0
  66. package/lib/server/plugin.js +64 -0
  67. package/package.json +23 -0
  68. package/server.d.ts +3 -0
  69. package/server.js +65 -0
  70. package/src/client/Settings.tsx +43 -0
  71. package/src/client/__tests__/chart-configure.test.tsx +14 -0
  72. package/src/client/__tests__/chart-library.test.ts +78 -0
  73. package/src/client/__tests__/chart-renderer.test.tsx +30 -0
  74. package/src/client/__tests__/hooks.test.ts +261 -0
  75. package/src/client/block/ChartBlock.tsx +22 -0
  76. package/src/client/block/ChartBlockDesigner.tsx +19 -0
  77. package/src/client/block/ChartBlockInitializer.tsx +83 -0
  78. package/src/client/block/ChartConfigure.tsx +450 -0
  79. package/src/client/block/formatters.ts +70 -0
  80. package/src/client/block/index.ts +4 -0
  81. package/src/client/block/schemas/configure.ts +474 -0
  82. package/src/client/block/transformers.ts +52 -0
  83. package/src/client/hooks.ts +239 -0
  84. package/src/client/index.tsx +41 -0
  85. package/src/client/locale/en-US.ts +23 -0
  86. package/src/client/locale/index.ts +19 -0
  87. package/src/client/locale/ja-JP.ts +1 -0
  88. package/src/client/locale/pt-BR.ts +23 -0
  89. package/src/client/locale/ru-RU.ts +1 -0
  90. package/src/client/locale/tr-TR.ts +1 -0
  91. package/src/client/locale/zh-CN.ts +71 -0
  92. package/src/client/renderer/ChartLibrary.tsx +178 -0
  93. package/src/client/renderer/ChartRenderer.tsx +201 -0
  94. package/src/client/renderer/ChartRendererProvider.tsx +58 -0
  95. package/src/client/renderer/index.ts +4 -0
  96. package/src/client/renderer/library/AntdLibrary.tsx +94 -0
  97. package/src/client/renderer/library/G2PlotLibrary.tsx +236 -0
  98. package/src/client/renderer/library/index.tsx +4 -0
  99. package/src/client/utils.ts +102 -0
  100. package/src/index.ts +1 -0
  101. package/src/server/__tests__/api.test.ts +105 -0
  102. package/src/server/__tests__/formatter.test.ts +49 -0
  103. package/src/server/__tests__/query.test.ts +220 -0
  104. package/src/server/actions/formatter.ts +49 -0
  105. package/src/server/actions/query.ts +285 -0
  106. package/src/server/collections/.gitkeep +0 -0
  107. package/src/server/index.ts +1 -0
  108. package/src/server/plugin.ts +37 -0
@@ -0,0 +1,450 @@
1
+ import { RightSquareOutlined } from '@ant-design/icons';
2
+ import { ArrayItems, Editable, Form, FormCollapse, FormItem, Switch } from '@formily/antd';
3
+ import { createForm, Form as FormType, ObjectField, onFieldChange, onFormInit } from '@formily/core';
4
+ import { FormConsumer, ISchema, Schema } from '@formily/react';
5
+ import {
6
+ AutoComplete,
7
+ Cascader,
8
+ DatePicker,
9
+ Filter,
10
+ gridRowColWrap,
11
+ Input,
12
+ InputNumber,
13
+ Radio,
14
+ SchemaComponent,
15
+ Select,
16
+ useCollectionFieldsOptions,
17
+ useCollectionFilterOptions,
18
+ useDesignable,
19
+ } from '@nocobase/client';
20
+ import { Alert, Button, Card, Col, Modal, Row, Space, Table, Tabs, Typography } from 'antd';
21
+ import { cloneDeep, isEqual } from 'lodash';
22
+ import React, { createContext, useContext, useMemo, useRef } from 'react';
23
+ import { useTranslation } from 'react-i18next';
24
+ import {
25
+ useChartFields,
26
+ useCollectionOptions,
27
+ useFieldsWithAssociation,
28
+ useFieldTypes,
29
+ useFormatters,
30
+ useOrderFieldsOptions,
31
+ useOrderReaction,
32
+ useTransformers,
33
+ } from '../hooks';
34
+ import { useChartsTranslation } from '../locale';
35
+ import { ChartRenderer, ChartRendererProvider, useCharts, useChartTypes } from '../renderer';
36
+ import { createRendererSchema, getField, getSelectedFields } from '../utils';
37
+ import { getConfigSchema, querySchema, transformSchema } from './schemas/configure';
38
+ const { Paragraph, Text } = Typography;
39
+
40
+ export type ChartConfigCurrent = {
41
+ schema: ISchema;
42
+ field: any;
43
+ collection: string;
44
+ };
45
+
46
+ export type SelectedField = {
47
+ field: string | string[];
48
+ alias?: string;
49
+ };
50
+
51
+ export const ChartConfigContext = createContext<{
52
+ visible: boolean;
53
+ setVisible?: (visible: boolean) => void;
54
+ current?: ChartConfigCurrent;
55
+ setCurrent?: (current: ChartConfigCurrent) => void;
56
+ data?: any[] | string;
57
+ setData?: (data: any[] | string) => void;
58
+ }>({
59
+ visible: true,
60
+ });
61
+
62
+ export const ChartConfigure: React.FC<{
63
+ insert: (
64
+ s: ISchema,
65
+ options: {
66
+ onSuccess: () => void;
67
+ wrap?: (schema: ISchema) => ISchema;
68
+ },
69
+ ) => void;
70
+ }> & {
71
+ Renderer: React.FC<{
72
+ runQuery?: any;
73
+ }>;
74
+ Config: React.FC;
75
+ Query: React.FC;
76
+ Transform: React.FC;
77
+ Data: React.FC;
78
+ } = (props) => {
79
+ const { t } = useChartsTranslation();
80
+ const { visible, setVisible, current } = useContext(ChartConfigContext);
81
+ const { schema, field, collection } = current || {};
82
+ const { dn } = useDesignable();
83
+ const { insert } = props;
84
+
85
+ const charts = useCharts();
86
+ const fields = useFieldsWithAssociation(collection);
87
+ const initChart = (overwrite = false) => {
88
+ if (!form.modified) {
89
+ return;
90
+ }
91
+ const chartType = form.values.config?.chartType;
92
+ if (!chartType) {
93
+ return;
94
+ }
95
+ const chart = charts[chartType];
96
+ const init = chart?.init;
97
+ if (!init) {
98
+ if (overwrite) {
99
+ form.values.config.general = {};
100
+ form.values.config.advanced = {};
101
+ }
102
+ return;
103
+ }
104
+ const query = form.values.query;
105
+ const selectedFields = getSelectedFields(fields, query);
106
+ const { general, advanced } = init(selectedFields, query);
107
+ if (general || overwrite) {
108
+ form.values.config.general = general;
109
+ }
110
+ if (advanced || overwrite) {
111
+ form.values.config.advanced = advanced || {};
112
+ }
113
+ };
114
+
115
+ const [measures, setMeasures] = React.useState([]);
116
+ const [dimensions, setDimensions] = React.useState([]);
117
+ const queryReact = (form: FormType, reaction?: () => void) => {
118
+ const currentMeasures = form.values.query?.measures.filter((item) => item.field) || [];
119
+ const currentDimensions = form.values.query?.dimensions.filter((item) => item.field) || [];
120
+ if (isEqual(currentMeasures, measures) && isEqual(currentDimensions, dimensions)) {
121
+ return;
122
+ }
123
+ reaction?.();
124
+ setMeasures(cloneDeep(currentMeasures));
125
+ setDimensions(cloneDeep(currentDimensions));
126
+ };
127
+ const chartTypes = useChartTypes();
128
+ const form = useMemo(
129
+ () => {
130
+ const chartType = chartTypes[0]?.children?.[0]?.value;
131
+ return createForm({
132
+ values: { config: { chartType }, ...schema?.['x-decorator-props'], collection, data: '' },
133
+ effects: (form) => {
134
+ onFieldChange('config.chartType', () => initChart(true));
135
+ onFormInit(() => queryReact(form));
136
+ },
137
+ });
138
+ },
139
+ // visible, collection added here to re-initialize form when visible, collection change
140
+ // eslint-disable-next-line react-hooks/exhaustive-deps
141
+ [schema, visible, collection],
142
+ );
143
+
144
+ const runQuery = useRef(null);
145
+ const RunButton: React.FC = () => {
146
+ const [loading, setLoading] = React.useState(false);
147
+ return (
148
+ <Button
149
+ type="link"
150
+ loading={loading}
151
+ icon={<RightSquareOutlined />}
152
+ onClick={async () => {
153
+ const queryField = form.query('query').take() as ObjectField;
154
+ try {
155
+ await queryField?.validate();
156
+ } catch (e) {
157
+ return;
158
+ }
159
+
160
+ setLoading(true);
161
+ try {
162
+ await runQuery?.current(form.values.query);
163
+ } catch (e) {
164
+ console.log(e);
165
+ }
166
+ queryReact(form, initChart);
167
+ setLoading(false);
168
+ }}
169
+ >
170
+ {t('Run query')}
171
+ </Button>
172
+ );
173
+ };
174
+
175
+ const queryRef = useRef(null);
176
+ const configRef = useRef(null);
177
+ return (
178
+ <Modal
179
+ title={t('Configure chart')}
180
+ open={visible}
181
+ onOk={() => {
182
+ const { query, config, transform, mode } = form.values;
183
+ const rendererProps = {
184
+ query,
185
+ config,
186
+ collection,
187
+ transform,
188
+ mode: mode || 'builder',
189
+ };
190
+ if (schema && schema['x-uid']) {
191
+ schema['x-decorator-props'] = rendererProps;
192
+ field.decoratorProps = rendererProps;
193
+ field['x-acl-action'] = `${collection}:list`;
194
+ dn.emit('patch', {
195
+ schema,
196
+ });
197
+ setVisible(false);
198
+ queryRef.current.scrollTop = 0;
199
+ configRef.current.scrollTop = 0;
200
+ return;
201
+ }
202
+ insert(createRendererSchema(rendererProps), {
203
+ onSuccess: () => setVisible(false),
204
+ wrap: gridRowColWrap,
205
+ });
206
+ }}
207
+ onCancel={() => {
208
+ Modal.confirm({
209
+ title: t('Are you sure to cancel?'),
210
+ content: t('You changes are not saved. If you click OK, your changes will be lost.'),
211
+ okButtonProps: {
212
+ danger: true,
213
+ },
214
+ onOk: () => {
215
+ setVisible(false);
216
+ queryRef.current.scrollTop = 0;
217
+ configRef.current.scrollTop = 0;
218
+ },
219
+ });
220
+ }}
221
+ width={'95%'}
222
+ bodyStyle={{
223
+ background: 'rgba(128, 128, 128, 0.08)',
224
+ }}
225
+ >
226
+ <Form layout="vertical" form={form}>
227
+ <Row gutter={8}>
228
+ <Col span={7}>
229
+ <Card
230
+ style={{
231
+ height: 'calc(100vh - 300px)',
232
+ overflow: 'scroll',
233
+ }}
234
+ ref={queryRef}
235
+ >
236
+ <Tabs
237
+ tabBarExtraContent={<RunButton />}
238
+ items={[
239
+ {
240
+ label: t('Query'),
241
+ key: 'query',
242
+ children: <ChartConfigure.Query />,
243
+ },
244
+ {
245
+ label: t('Data'),
246
+ key: 'data',
247
+ children: <ChartConfigure.Data />,
248
+ },
249
+ ]}
250
+ />
251
+ </Card>
252
+ </Col>
253
+ <Col span={6}>
254
+ <Card
255
+ style={{
256
+ height: 'calc(100vh - 300px)',
257
+ overflow: 'scroll',
258
+ }}
259
+ ref={configRef}
260
+ >
261
+ <Tabs
262
+ items={[
263
+ {
264
+ label: t('Chart'),
265
+ key: 'chart',
266
+ children: <ChartConfigure.Config />,
267
+ },
268
+ {
269
+ label: t('Transform'),
270
+ key: 'transform',
271
+ children: <ChartConfigure.Transform />,
272
+ },
273
+ ]}
274
+ />
275
+ </Card>
276
+ </Col>
277
+ <Col span={11}>
278
+ <Card>
279
+ <ChartConfigure.Renderer runQuery={runQuery} />
280
+ </Card>
281
+ </Col>
282
+ </Row>
283
+ </Form>
284
+ </Modal>
285
+ );
286
+ };
287
+
288
+ ChartConfigure.Renderer = function Renderer(props) {
289
+ const { current } = useContext(ChartConfigContext);
290
+ const { collection } = current || {};
291
+ return (
292
+ <FormConsumer>
293
+ {(form) => {
294
+ // Any change of config and transform will trigger rerender
295
+ // Change of query only trigger rerender when "Run query" button is clicked
296
+ const config = cloneDeep(form.values.config);
297
+ const transform = cloneDeep(form.values.transform);
298
+ return (
299
+ <ChartRendererProvider
300
+ collection={collection}
301
+ query={form.values.query}
302
+ config={config}
303
+ transform={transform}
304
+ mode={form.values.mode}
305
+ >
306
+ <ChartRenderer configuring={true} {...props} />
307
+ </ChartRendererProvider>
308
+ );
309
+ }}
310
+ </FormConsumer>
311
+ );
312
+ };
313
+
314
+ ChartConfigure.Query = function Query() {
315
+ const { t } = useChartsTranslation();
316
+ const { t: lang } = useTranslation();
317
+ const fields = useFieldsWithAssociation();
318
+ const useFormatterOptions = useFormatters(fields);
319
+ const collectionOptions = useCollectionOptions();
320
+ const { current, setCurrent } = useContext(ChartConfigContext);
321
+ const { collection } = current || {};
322
+ const fieldOptions = useCollectionFieldsOptions(collection, 1);
323
+ const compiledFieldOptions = Schema.compile(fieldOptions, { t: lang });
324
+ const filterOptions = useCollectionFilterOptions(collection);
325
+ const formCollapse = FormCollapse.createFormCollapse(['measures', 'dimensions', 'filter', 'sort']);
326
+ const onCollectionChange = (value: string) => {
327
+ const { schema, field } = current;
328
+ const newSchema = { ...schema };
329
+ newSchema['x-decorator-props'] = { collection: value };
330
+ newSchema['x-acl-action'] = `${value}:list`;
331
+ setCurrent({
332
+ schema: newSchema,
333
+ field,
334
+ collection: value,
335
+ });
336
+ };
337
+ const FromSql = () => (
338
+ <Text code>
339
+ From <span style={{ color: '#1890ff' }}>{current?.collection}</span>
340
+ </Text>
341
+ );
342
+ return (
343
+ <SchemaComponent
344
+ schema={querySchema}
345
+ scope={{
346
+ t,
347
+ formCollapse,
348
+ fieldOptions: compiledFieldOptions,
349
+ filterOptions,
350
+ useOrderOptions: useOrderFieldsOptions(compiledFieldOptions, fields),
351
+ collectionOptions,
352
+ useFormatterOptions,
353
+ onCollectionChange,
354
+ collection: current?.collection,
355
+ useOrderReaction: useOrderReaction(compiledFieldOptions, fields),
356
+ }}
357
+ components={{
358
+ ArrayItems,
359
+ Editable,
360
+ FormCollapse,
361
+ Card,
362
+ Switch,
363
+ Select,
364
+ Input,
365
+ InputNumber,
366
+ FormItem,
367
+ Radio,
368
+ Space,
369
+ Filter,
370
+ DatePicker,
371
+ Text,
372
+ FromSql,
373
+ Cascader,
374
+ }}
375
+ />
376
+ );
377
+ };
378
+
379
+ ChartConfigure.Config = function Config() {
380
+ const { t } = useChartsTranslation();
381
+ const chartTypes = useChartTypes();
382
+ const fields = useFieldsWithAssociation();
383
+ const charts = useCharts();
384
+ const getChartFields = useChartFields(fields);
385
+ const getReference = (chartType: string) => {
386
+ const reference = charts[chartType]?.reference;
387
+ if (!reference) return '';
388
+ const { title, link } = reference;
389
+ return (
390
+ <span>
391
+ {t('Config reference: ')}
392
+ <a href={link} target="_blank" rel="noreferrer">
393
+ {t(title)}
394
+ </a>
395
+ </span>
396
+ );
397
+ };
398
+
399
+ return (
400
+ <FormConsumer>
401
+ {(form) => {
402
+ const chartType = form.values.config?.chartType;
403
+ const chart = charts[chartType];
404
+ const schema = chart?.schema || {};
405
+ return (
406
+ <SchemaComponent
407
+ schema={getConfigSchema(schema)}
408
+ scope={{ t, chartTypes, useChartFields: getChartFields, getReference }}
409
+ components={{ Card, Select, Input, FormItem, ArrayItems, Space, AutoComplete }}
410
+ />
411
+ );
412
+ }}
413
+ </FormConsumer>
414
+ );
415
+ };
416
+
417
+ ChartConfigure.Transform = function Transform() {
418
+ const { t } = useChartsTranslation();
419
+ const fields = useFieldsWithAssociation();
420
+ const useFieldTypeOptions = useFieldTypes(fields);
421
+ const getChartFields = useChartFields(fields);
422
+ return (
423
+ <SchemaComponent
424
+ schema={transformSchema}
425
+ components={{ Select, FormItem, ArrayItems, Space }}
426
+ scope={{ useChartFields: getChartFields, useFieldTypeOptions, useTransformers, t }}
427
+ />
428
+ );
429
+ };
430
+
431
+ ChartConfigure.Data = function Data() {
432
+ const { data } = useContext(ChartConfigContext);
433
+ const fields = useFieldsWithAssociation();
434
+ return Array.isArray(data) ? (
435
+ <Table
436
+ dataSource={data}
437
+ columns={Object.keys(data[0] || {}).map((col) => {
438
+ const field = getField(fields, col.split('.'));
439
+ return {
440
+ title: field?.label || col,
441
+ dataIndex: col,
442
+ key: col,
443
+ };
444
+ })}
445
+ size="small"
446
+ />
447
+ ) : (
448
+ <Alert message="Error" type="error" description={data} showIcon />
449
+ );
450
+ };
@@ -0,0 +1,70 @@
1
+ import { lang } from '../locale';
2
+
3
+ export default {
4
+ datetime: [
5
+ {
6
+ label: lang('YYYY'),
7
+ value: 'YYYY',
8
+ },
9
+ {
10
+ label: lang('MM'),
11
+ value: 'MM',
12
+ },
13
+ {
14
+ label: lang('DD'),
15
+ value: 'DD',
16
+ },
17
+ {
18
+ label: lang('YYYY-MM'),
19
+ value: 'YYYY-MM',
20
+ },
21
+ {
22
+ label: lang('YYYY-MM-DD'),
23
+ value: 'YYYY-MM-DD',
24
+ },
25
+ {
26
+ label: lang('YYYY-MM-DD hh:mm'),
27
+ value: 'YYYY-MM-DD hh:mm',
28
+ },
29
+ {
30
+ label: lang('YYYY-MM-DD hh:mm:ss'),
31
+ value: 'YYYY-MM-DD hh:mm:ss',
32
+ },
33
+ ],
34
+ date: [
35
+ {
36
+ label: lang('YYYY'),
37
+ value: 'YYYY',
38
+ },
39
+ {
40
+ label: lang('MM'),
41
+ value: 'MM',
42
+ },
43
+ {
44
+ label: lang('DD'),
45
+ value: 'DD',
46
+ },
47
+ {
48
+ label: lang('YYYY-MM'),
49
+ value: 'YYYY-MM',
50
+ },
51
+ {
52
+ label: lang('YYYY-MM-DD'),
53
+ value: 'YYYY-MM-DD',
54
+ },
55
+ ],
56
+ time: [
57
+ {
58
+ label: lang('hh:mm:ss'),
59
+ value: 'hh:mm:ss',
60
+ },
61
+ {
62
+ label: lang('hh:mm'),
63
+ value: 'hh:mm',
64
+ },
65
+ {
66
+ label: lang('hh'),
67
+ value: 'hh',
68
+ },
69
+ ],
70
+ };
@@ -0,0 +1,4 @@
1
+ export * from './ChartBlock';
2
+ export * from './ChartBlockDesigner';
3
+ export * from './ChartBlockInitializer';
4
+ export * from './ChartConfigure';