@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,78 @@
1
+ import { FieldOption } from '../hooks';
2
+ import { infer } from '../renderer';
3
+
4
+ describe('library', () => {
5
+ describe('auto infer', () => {
6
+ const fields = [
7
+ {
8
+ name: 'price',
9
+ value: 'price',
10
+ type: 'number',
11
+ label: 'Price',
12
+ },
13
+ {
14
+ name: 'count',
15
+ value: 'count',
16
+ type: 'number',
17
+ label: 'Count',
18
+ },
19
+ {
20
+ name: 'title',
21
+ value: 'title',
22
+ type: 'string',
23
+ label: 'Title',
24
+ },
25
+ {
26
+ name: 'name',
27
+ value: 'name',
28
+ type: 'string',
29
+ label: 'Name',
30
+ },
31
+ {
32
+ name: 'createdAt',
33
+ value: 'createdAt',
34
+ type: 'date',
35
+ label: 'Created At',
36
+ },
37
+ ] as FieldOption[];
38
+
39
+ test('1 measure, 1 dimension', () => {
40
+ const { xField, yField } = infer(fields, {
41
+ measures: [{ field: ['price'] }],
42
+ dimensions: [{ field: ['title'] }],
43
+ });
44
+ expect(yField.value).toEqual('price');
45
+ expect(xField.value).toEqual('title');
46
+ });
47
+
48
+ test('1 measure, 2 dimensions with date', () => {
49
+ const { xField, yField, seriesField } = infer(fields, {
50
+ measures: [{ field: ['price'] }],
51
+ dimensions: [{ field: ['title'] }, { field: ['createdAt'] }],
52
+ });
53
+ expect(yField.value).toEqual('price');
54
+ expect(xField.value).toEqual('createdAt');
55
+ expect(seriesField.value).toEqual('title');
56
+ });
57
+
58
+ test('1 measure, 2 dimensions without date', () => {
59
+ const { xField, yField, seriesField } = infer(fields, {
60
+ measures: [{ field: ['price'] }],
61
+ dimensions: [{ field: ['title'] }, { field: ['name'] }],
62
+ });
63
+ expect(yField.value).toEqual('price');
64
+ expect(xField.value).toEqual('title');
65
+ expect(seriesField.value).toEqual('name');
66
+ });
67
+
68
+ test('2 measures, 1 dimension', () => {
69
+ const { xField, yField, yFields } = infer(fields, {
70
+ measures: [{ field: ['price'] }, { field: ['count'] }],
71
+ dimensions: [{ field: ['title'] }],
72
+ });
73
+ expect(yField.value).toEqual('price');
74
+ expect(xField.value).toEqual('title');
75
+ expect(yFields.length).toEqual(2);
76
+ });
77
+ });
78
+ });
@@ -0,0 +1,30 @@
1
+ describe('ChartRenderer', () => {
2
+ it('should render correctly', () => {
3
+ // render(
4
+ // <ChartLibraryProvider
5
+ // name="Test"
6
+ // charts={{
7
+ // chart: {
8
+ // name: 'Chart',
9
+ // component: () => <div role="chart">Chart</div>,
10
+ // useProps: (info) => info,
11
+ // },
12
+ // }}
13
+ // >
14
+ // <ChartRendererProvider
15
+ // query={{}}
16
+ // config={{
17
+ // chartType: 'chart',
18
+ // general: {},
19
+ // advanced: {},
20
+ // }}
21
+ // collection=""
22
+ // transform={[]}
23
+ // >
24
+ // <ChartRenderer />
25
+ // </ChartRendererProvider>
26
+ // </ChartLibraryProvider>,
27
+ // );
28
+ // expect(screen.getByText('Please configure and run query')).toBeInTheDocument();
29
+ });
30
+ });
@@ -0,0 +1,261 @@
1
+ import * as client from '@nocobase/client';
2
+ import { renderHook } from '@testing-library/react';
3
+ import { vi } from 'vitest';
4
+ import formatters from '../block/formatters';
5
+ import transformers from '../block/transformers';
6
+ import {
7
+ useChartFields,
8
+ useFieldsWithAssociation,
9
+ useFieldTransformer,
10
+ useFieldTypes,
11
+ useFormatters,
12
+ useOrderFieldsOptions,
13
+ useTransformers,
14
+ } from '../hooks';
15
+
16
+ describe('hooks', () => {
17
+ beforeEach(() => {
18
+ vi.spyOn(client, 'useCollectionManager').mockReturnValue({
19
+ getCollectionFields: (name: string) =>
20
+ ({
21
+ orders: [
22
+ {
23
+ interface: 'string',
24
+ name: 'name',
25
+ uiSchema: {
26
+ title: '{{t("Name")}}',
27
+ },
28
+ type: 'string',
29
+ },
30
+ {
31
+ interface: 'number',
32
+ name: 'price',
33
+ uiSchema: {
34
+ title: '{{t("Price")}}',
35
+ },
36
+ type: 'double',
37
+ },
38
+ {
39
+ interface: 'createdAt',
40
+ name: 'createdAt',
41
+ uiSchema: {
42
+ title: '{{t("Created At")}}',
43
+ },
44
+ type: 'date',
45
+ },
46
+ {
47
+ interface: 'm2o',
48
+ name: 'user',
49
+ uiSchema: {
50
+ title: '{{t("User")}}',
51
+ },
52
+ target: 'users',
53
+ type: 'belongsTo',
54
+ },
55
+ ],
56
+ users: [
57
+ {
58
+ interface: 'string',
59
+ name: 'name',
60
+ uiSchema: {
61
+ title: '{{t("Name")}}',
62
+ },
63
+ type: 'string',
64
+ },
65
+ ],
66
+ }[name]),
67
+ } as any);
68
+ });
69
+
70
+ afterEach(() => {
71
+ vi.restoreAllMocks();
72
+ });
73
+
74
+ test('useFieldsWithAssociation', () => {
75
+ const { result } = renderHook(() => useFieldsWithAssociation('orders'));
76
+ expect(result.current).toMatchObject([
77
+ {
78
+ key: 'name',
79
+ label: 'Name',
80
+ value: 'name',
81
+ },
82
+ {
83
+ key: 'price',
84
+ label: 'Price',
85
+ value: 'price',
86
+ },
87
+ {
88
+ key: 'createdAt',
89
+ label: 'Created At',
90
+ value: 'createdAt',
91
+ },
92
+ {
93
+ key: 'user',
94
+ label: 'User',
95
+ value: 'user',
96
+ target: 'users',
97
+ targetFields: [
98
+ {
99
+ key: 'user.name',
100
+ label: 'User / Name',
101
+ value: 'user.name',
102
+ },
103
+ ],
104
+ },
105
+ ]);
106
+ });
107
+
108
+ test('useChartFields', () => {
109
+ const fields = renderHook(() => useFieldsWithAssociation('orders')).result.current;
110
+ const { result } = renderHook(() => useChartFields(fields));
111
+ const func = result.current;
112
+ const field = {
113
+ query: () => ({
114
+ get: () => ({
115
+ measures: [
116
+ {
117
+ field: ['price'],
118
+ alias: 'Price Alias',
119
+ },
120
+ ],
121
+ dimensions: [
122
+ {
123
+ field: ['user', 'name'],
124
+ },
125
+ ],
126
+ }),
127
+ }),
128
+ dataSource: [],
129
+ };
130
+ func(field);
131
+ expect(field.dataSource).toMatchObject([
132
+ {
133
+ key: 'Price Alias',
134
+ label: 'Price Alias',
135
+ value: 'Price Alias',
136
+ },
137
+ {
138
+ key: 'user.name',
139
+ label: 'User / Name',
140
+ value: 'user.name',
141
+ },
142
+ ]);
143
+ });
144
+
145
+ test('useFormatters', () => {
146
+ const fields = renderHook(() => useFieldsWithAssociation('orders')).result.current;
147
+ const { result } = renderHook(() => useFormatters(fields));
148
+ const func = result.current;
149
+ const field = {
150
+ query: () => ({
151
+ get: () => 'createdAt',
152
+ }),
153
+ dataSource: [],
154
+ };
155
+ func(field);
156
+ expect(field.dataSource).toEqual(formatters.datetime);
157
+ });
158
+
159
+ test('useFieldTypes', () => {
160
+ const fields = renderHook(() => useFieldsWithAssociation('orders')).result.current;
161
+ const { result } = renderHook(() => useFieldTypes(fields));
162
+ const func = result.current;
163
+ let state1 = {};
164
+ let state2 = {};
165
+ const field = {
166
+ dataSource: [],
167
+ state: {},
168
+ };
169
+ const query = (path: string, val: string) => ({
170
+ get: () => {
171
+ if (path === 'query') {
172
+ return { measures: [{ field: ['price'] }, { field: ['name'] }] };
173
+ }
174
+ return val;
175
+ },
176
+ });
177
+ const field1 = {
178
+ query: (path: string) => query(path, 'price'),
179
+ setState: (state) => (state1 = state),
180
+ ...field,
181
+ };
182
+ const field2 = {
183
+ query: (path: string) => query(path, 'name'),
184
+ setState: (state) => (state2 = state),
185
+ ...field,
186
+ };
187
+ func(field1);
188
+ func(field2);
189
+ expect(field1.dataSource.map((item) => item.value)).toEqual(Object.keys(transformers));
190
+ expect(state1).toEqual({ value: 'number', disabled: true });
191
+ expect(state2).toEqual({ value: null, disabled: false });
192
+ });
193
+
194
+ test('useTransformers', () => {
195
+ const field = {
196
+ query: () => ({
197
+ get: () => 'datetime',
198
+ }),
199
+ dataSource: [],
200
+ };
201
+ renderHook(() => useTransformers(field));
202
+ expect(field.dataSource.map((item) => item.value)).toEqual(Object.keys(transformers['datetime']));
203
+ });
204
+
205
+ test('useFieldTransformers', () => {
206
+ const { result } = renderHook(() =>
207
+ useFieldTransformer([
208
+ {
209
+ field: '1',
210
+ type: 'datetime',
211
+ format: 'YYYY',
212
+ },
213
+ {
214
+ field: '2',
215
+ type: 'number',
216
+ format: 'YYYY',
217
+ },
218
+ ]),
219
+ );
220
+ expect(result.current['1']).toBeDefined();
221
+ expect(result.current['2']).toBeUndefined();
222
+ });
223
+
224
+ test('useOrderFieldsOptions', () => {
225
+ const fields = renderHook(() => useFieldsWithAssociation('orders')).result.current;
226
+ const { result } = renderHook(() => useOrderFieldsOptions([], fields));
227
+ const func = result.current;
228
+ const field1 = {
229
+ query: () => ({
230
+ get: () => ({
231
+ measures: [{ field: ['price'] }],
232
+ }),
233
+ }),
234
+ dataSource: [],
235
+ componentProps: {
236
+ fieldNames: {},
237
+ },
238
+ };
239
+ const field2 = {
240
+ query: () => ({
241
+ get: () => ({
242
+ measures: [{ field: ['price'], aggregation: 'sum' }],
243
+ }),
244
+ }),
245
+ componentProps: {
246
+ fieldNames: {},
247
+ },
248
+ dataSource: [],
249
+ };
250
+ func(field1);
251
+ func(field2);
252
+ expect(field1.dataSource).toEqual([]);
253
+ expect(field1.componentProps.fieldNames).toEqual({
254
+ label: 'title',
255
+ value: 'name',
256
+ children: 'children',
257
+ });
258
+ expect(field2.dataSource).toMatchObject([{ key: 'price', value: 'price', label: 'Price' }]);
259
+ expect(field2.componentProps.fieldNames).toEqual({});
260
+ });
261
+ });
@@ -0,0 +1,22 @@
1
+ import { SchemaInitializerButtonContext, useDesignable } from '@nocobase/client';
2
+ import React, { useState } from 'react';
3
+ import { ChartConfigContext, ChartConfigCurrent, ChartConfigure } from './ChartConfigure';
4
+
5
+ export const ChartV2Block: React.FC = (props) => {
6
+ const { insertAdjacent } = useDesignable();
7
+ const [visible, setVisible] = useState(false);
8
+ const [current, setCurrent] = useState<ChartConfigCurrent>({} as any);
9
+ const [data, setData] = useState<string | any[]>([]);
10
+ const [initialVisible, setInitialVisible] = useState(false);
11
+ const [searchValue, setSearchValue] = useState('');
12
+ return (
13
+ <SchemaInitializerButtonContext.Provider
14
+ value={{ visible: initialVisible, setVisible: setInitialVisible, searchValue, setSearchValue }}
15
+ >
16
+ <ChartConfigContext.Provider value={{ visible, setVisible, current, setCurrent, data, setData }}>
17
+ {props.children}
18
+ <ChartConfigure insert={(schema, options) => insertAdjacent('beforeEnd', schema, options)} />
19
+ </ChartConfigContext.Provider>
20
+ </SchemaInitializerButtonContext.Provider>
21
+ );
22
+ };
@@ -0,0 +1,19 @@
1
+ import { GeneralSchemaDesigner, SchemaSettings } from '@nocobase/client';
2
+ import React from 'react';
3
+ import { useChartsTranslation } from '../locale';
4
+
5
+ export const ChartV2BlockDesigner: React.FC = () => {
6
+ const { t } = useChartsTranslation();
7
+ return (
8
+ <GeneralSchemaDesigner title={t('Charts')}>
9
+ <SchemaSettings.BlockTitleItem />
10
+ <SchemaSettings.Divider />
11
+ <SchemaSettings.Remove
12
+ removeParentsIfNoChildren
13
+ breakRemoveOn={{
14
+ 'x-component': 'Grid',
15
+ }}
16
+ />
17
+ </GeneralSchemaDesigner>
18
+ );
19
+ };
@@ -0,0 +1,83 @@
1
+ import { LineChartOutlined } from '@ant-design/icons';
2
+ import { ISchema } from '@formily/react';
3
+ import { uid } from '@formily/shared';
4
+ import { SchemaInitializer, useACLRoleContext, useCollectionDataSourceItems } from '@nocobase/client';
5
+ import React, { useContext } from 'react';
6
+ import { useChartsTranslation } from '../locale';
7
+ import { ChartConfigContext } from './ChartConfigure';
8
+
9
+ const itemWrap = SchemaInitializer.itemWrap;
10
+ const ConfigureButton = itemWrap((props) => {
11
+ const { setVisible, setCurrent, setData } = useContext(ChartConfigContext);
12
+ return (
13
+ <SchemaInitializer.Item
14
+ {...props}
15
+ onClick={() => {
16
+ setCurrent({ schema: {}, field: null, collection: props.item?.name });
17
+ setData([]);
18
+ setVisible(true);
19
+ }}
20
+ />
21
+ );
22
+ });
23
+
24
+ export const ChartInitializers = () => {
25
+ const { t } = useChartsTranslation();
26
+ const collections = useCollectionDataSourceItems('Chart');
27
+ const { allowAll, parseAction } = useACLRoleContext();
28
+ const children = collections[0].children
29
+ .filter((item) => {
30
+ if (allowAll) {
31
+ return true;
32
+ }
33
+ const params = parseAction(`${item.name}:list`);
34
+ return params;
35
+ })
36
+ .map((item) => ({
37
+ ...item,
38
+ component: ConfigureButton,
39
+ }));
40
+ if (!children.length) {
41
+ // Leave a blank item to show the filter component
42
+ children.push({} as any);
43
+ }
44
+ collections[0].children = children;
45
+ return (
46
+ <SchemaInitializer.Button
47
+ icon={'PlusOutlined'}
48
+ items={collections as any}
49
+ dropdown={{
50
+ placement: 'bottomLeft',
51
+ }}
52
+ >
53
+ {t('Add chart')}
54
+ </SchemaInitializer.Button>
55
+ );
56
+ };
57
+
58
+ export const ChartV2BlockInitializer: React.FC<{
59
+ insert: (s: ISchema) => void;
60
+ }> = (props) => {
61
+ const { insert } = props;
62
+ return (
63
+ <SchemaInitializer.Item
64
+ {...props}
65
+ icon={<LineChartOutlined />}
66
+ onClick={() => {
67
+ insert({
68
+ type: 'void',
69
+ 'x-component': 'CardItem',
70
+ 'x-designer': 'ChartV2BlockDesigner',
71
+ properties: {
72
+ [uid()]: {
73
+ type: 'void',
74
+ 'x-component': 'Grid',
75
+ 'x-decorator': 'ChartV2Block',
76
+ 'x-initializer': 'ChartInitializers',
77
+ },
78
+ },
79
+ });
80
+ }}
81
+ />
82
+ );
83
+ };