@nocobase/plugin-data-visualization 0.11.0-alpha.1 → 0.11.1-alpha.2
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.
- package/lib/client/block/ChartBlock.js +16 -15
- package/lib/client/block/ChartBlockInitializer.js +4 -4
- package/lib/client/block/ChartConfigure.d.ts +4 -5
- package/lib/client/block/ChartConfigure.js +109 -94
- package/lib/client/block/schemas/configure.js +7 -7
- package/lib/client/index.js +1 -1
- package/lib/client/locale/index.d.ts +2 -2
- package/lib/client/locale/index.js +3 -5
- package/lib/client/locale/zh-CN.d.ts +1 -0
- package/lib/client/locale/zh-CN.js +2 -1
- package/lib/client/renderer/ChartLibrary.d.ts +1 -0
- package/lib/client/renderer/ChartLibrary.js +8 -2
- package/lib/client/renderer/ChartRenderer.d.ts +1 -4
- package/lib/client/renderer/ChartRenderer.js +28 -103
- package/lib/client/renderer/ChartRendererProvider.d.ts +11 -1
- package/lib/client/renderer/ChartRendererProvider.js +62 -11
- package/lib/client/renderer/library/G2PlotLibrary.js +24 -25
- package/lib/server/actions/query.js +6 -1
- package/package.json +9 -9
- package/src/client/block/ChartBlock.tsx +5 -3
- package/src/client/block/ChartBlockInitializer.tsx +2 -3
- package/src/client/block/ChartConfigure.tsx +106 -85
- package/src/client/block/schemas/configure.ts +7 -7
- package/src/client/index.tsx +1 -1
- package/src/client/locale/index.ts +2 -3
- package/src/client/locale/zh-CN.ts +1 -0
- package/src/client/renderer/ChartLibrary.tsx +6 -1
- package/src/client/renderer/ChartRenderer.tsx +22 -90
- package/src/client/renderer/ChartRendererProvider.tsx +60 -7
- package/src/client/renderer/library/G2PlotLibrary.tsx +24 -25
- package/src/server/__tests__/api.test.ts +47 -50
- package/src/server/actions/query.ts +6 -1
|
@@ -17,10 +17,9 @@ import {
|
|
|
17
17
|
useCollectionFilterOptions,
|
|
18
18
|
useDesignable,
|
|
19
19
|
} from '@nocobase/client';
|
|
20
|
-
import { Alert, Button, Card, Col, Modal, Row, Space, Table, Tabs, Typography } from 'antd';
|
|
20
|
+
import { Alert, App, Button, Card, Col, Modal, Row, Space, Table, Tabs, Typography } from 'antd';
|
|
21
21
|
import { cloneDeep, isEqual } from 'lodash';
|
|
22
22
|
import React, { createContext, useContext, useMemo, useRef } from 'react';
|
|
23
|
-
import { useTranslation } from 'react-i18next';
|
|
24
23
|
import {
|
|
25
24
|
useChartFields,
|
|
26
25
|
useCollectionOptions,
|
|
@@ -32,8 +31,8 @@ import {
|
|
|
32
31
|
useTransformers,
|
|
33
32
|
} from '../hooks';
|
|
34
33
|
import { useChartsTranslation } from '../locale';
|
|
35
|
-
import { ChartRenderer,
|
|
36
|
-
import { createRendererSchema, getField, getSelectedFields } from '../utils';
|
|
34
|
+
import { ChartRenderer, ChartRendererContext, useChartTypes, useCharts, useDefaultChartType } from '../renderer';
|
|
35
|
+
import { createRendererSchema, getField, getSelectedFields, processData } from '../utils';
|
|
37
36
|
import { getConfigSchema, querySchema, transformSchema } from './schemas/configure';
|
|
38
37
|
const { Paragraph, Text } = Typography;
|
|
39
38
|
|
|
@@ -41,6 +40,9 @@ export type ChartConfigCurrent = {
|
|
|
41
40
|
schema: ISchema;
|
|
42
41
|
field: any;
|
|
43
42
|
collection: string;
|
|
43
|
+
service: any;
|
|
44
|
+
initialValues?: any;
|
|
45
|
+
data: any[];
|
|
44
46
|
};
|
|
45
47
|
|
|
46
48
|
export type SelectedField = {
|
|
@@ -53,8 +55,6 @@ export const ChartConfigContext = createContext<{
|
|
|
53
55
|
setVisible?: (visible: boolean) => void;
|
|
54
56
|
current?: ChartConfigCurrent;
|
|
55
57
|
setCurrent?: (current: ChartConfigCurrent) => void;
|
|
56
|
-
data?: any[] | string;
|
|
57
|
-
setData?: (data: any[] | string) => void;
|
|
58
58
|
}>({
|
|
59
59
|
visible: true,
|
|
60
60
|
});
|
|
@@ -68,18 +68,18 @@ export const ChartConfigure: React.FC<{
|
|
|
68
68
|
},
|
|
69
69
|
) => void;
|
|
70
70
|
}> & {
|
|
71
|
-
Renderer: React.FC
|
|
72
|
-
runQuery?: any;
|
|
73
|
-
}>;
|
|
71
|
+
Renderer: React.FC;
|
|
74
72
|
Config: React.FC;
|
|
75
73
|
Query: React.FC;
|
|
76
74
|
Transform: React.FC;
|
|
77
75
|
Data: React.FC;
|
|
78
76
|
} = (props) => {
|
|
79
77
|
const { t } = useChartsTranslation();
|
|
78
|
+
const { service } = useContext(ChartRendererContext);
|
|
80
79
|
const { visible, setVisible, current } = useContext(ChartConfigContext);
|
|
81
|
-
const { schema, field, collection } = current || {};
|
|
80
|
+
const { schema, field, collection, initialValues } = current || {};
|
|
82
81
|
const { dn } = useDesignable();
|
|
82
|
+
const { modal } = App.useApp();
|
|
83
83
|
const { insert } = props;
|
|
84
84
|
|
|
85
85
|
const charts = useCharts();
|
|
@@ -124,53 +124,48 @@ export const ChartConfigure: React.FC<{
|
|
|
124
124
|
setMeasures(cloneDeep(currentMeasures));
|
|
125
125
|
setDimensions(cloneDeep(currentDimensions));
|
|
126
126
|
};
|
|
127
|
-
const
|
|
127
|
+
const chartType = useDefaultChartType();
|
|
128
128
|
const form = useMemo(
|
|
129
129
|
() => {
|
|
130
|
-
const chartType = chartTypes[0]?.children?.[0]?.value;
|
|
131
130
|
return createForm({
|
|
132
|
-
values: { config: { chartType }, ...
|
|
131
|
+
values: { config: { chartType }, ...(initialValues || field?.decoratorProps), collection },
|
|
133
132
|
effects: (form) => {
|
|
134
133
|
onFieldChange('config.chartType', () => initChart(true));
|
|
135
|
-
onFormInit(() =>
|
|
134
|
+
onFormInit(() => {
|
|
135
|
+
queryReact(form);
|
|
136
|
+
});
|
|
136
137
|
},
|
|
137
138
|
});
|
|
138
139
|
},
|
|
139
140
|
// visible, collection added here to re-initialize form when visible, collection change
|
|
140
141
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
141
|
-
[
|
|
142
|
+
[field, visible, collection],
|
|
142
143
|
);
|
|
143
144
|
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
} catch (e) {
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
145
|
+
const RunButton: React.FC = () => (
|
|
146
|
+
<Button
|
|
147
|
+
type="link"
|
|
148
|
+
loading={service?.loading}
|
|
149
|
+
icon={<RightSquareOutlined />}
|
|
150
|
+
onClick={async () => {
|
|
151
|
+
const queryField = form.query('query').take() as ObjectField;
|
|
152
|
+
try {
|
|
153
|
+
await queryField?.validate();
|
|
154
|
+
} catch (e) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
159
157
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
</Button>
|
|
172
|
-
);
|
|
173
|
-
};
|
|
158
|
+
try {
|
|
159
|
+
await service.runAsync(collection, form.values.query);
|
|
160
|
+
} catch (e) {
|
|
161
|
+
console.log(e);
|
|
162
|
+
}
|
|
163
|
+
queryReact(form, initChart);
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
{t('Run query')}
|
|
167
|
+
</Button>
|
|
168
|
+
);
|
|
174
169
|
|
|
175
170
|
const queryRef = useRef(null);
|
|
176
171
|
const configRef = useRef(null);
|
|
@@ -180,6 +175,13 @@ export const ChartConfigure: React.FC<{
|
|
|
180
175
|
open={visible}
|
|
181
176
|
onOk={() => {
|
|
182
177
|
const { query, config, transform, mode } = form.values;
|
|
178
|
+
const afterSave = () => {
|
|
179
|
+
setVisible(false);
|
|
180
|
+
current.service?.run(collection, query);
|
|
181
|
+
queryRef.current.scrollTop = 0;
|
|
182
|
+
configRef.current.scrollTop = 0;
|
|
183
|
+
service.mutate(undefined);
|
|
184
|
+
};
|
|
183
185
|
const rendererProps = {
|
|
184
186
|
query,
|
|
185
187
|
config,
|
|
@@ -194,18 +196,16 @@ export const ChartConfigure: React.FC<{
|
|
|
194
196
|
dn.emit('patch', {
|
|
195
197
|
schema,
|
|
196
198
|
});
|
|
197
|
-
|
|
198
|
-
queryRef.current.scrollTop = 0;
|
|
199
|
-
configRef.current.scrollTop = 0;
|
|
199
|
+
afterSave();
|
|
200
200
|
return;
|
|
201
201
|
}
|
|
202
202
|
insert(createRendererSchema(rendererProps), {
|
|
203
|
-
onSuccess:
|
|
203
|
+
onSuccess: afterSave,
|
|
204
204
|
wrap: gridRowColWrap,
|
|
205
205
|
});
|
|
206
206
|
}}
|
|
207
207
|
onCancel={() => {
|
|
208
|
-
|
|
208
|
+
modal.confirm({
|
|
209
209
|
title: t('Are you sure to cancel?'),
|
|
210
210
|
content: t('You changes are not saved. If you click OK, your changes will be lost.'),
|
|
211
211
|
okButtonProps: {
|
|
@@ -215,6 +215,7 @@ export const ChartConfigure: React.FC<{
|
|
|
215
215
|
setVisible(false);
|
|
216
216
|
queryRef.current.scrollTop = 0;
|
|
217
217
|
configRef.current.scrollTop = 0;
|
|
218
|
+
service.mutate(undefined);
|
|
218
219
|
},
|
|
219
220
|
});
|
|
220
221
|
}}
|
|
@@ -229,7 +230,8 @@ export const ChartConfigure: React.FC<{
|
|
|
229
230
|
<Card
|
|
230
231
|
style={{
|
|
231
232
|
height: 'calc(100vh - 300px)',
|
|
232
|
-
overflow: '
|
|
233
|
+
overflow: 'auto',
|
|
234
|
+
margin: '12px 0 12px 12px',
|
|
233
235
|
}}
|
|
234
236
|
ref={queryRef}
|
|
235
237
|
>
|
|
@@ -254,7 +256,8 @@ export const ChartConfigure: React.FC<{
|
|
|
254
256
|
<Card
|
|
255
257
|
style={{
|
|
256
258
|
height: 'calc(100vh - 300px)',
|
|
257
|
-
overflow: '
|
|
259
|
+
overflow: 'auto',
|
|
260
|
+
margin: '12px 3px 12px 3px',
|
|
258
261
|
}}
|
|
259
262
|
ref={configRef}
|
|
260
263
|
>
|
|
@@ -275,8 +278,12 @@ export const ChartConfigure: React.FC<{
|
|
|
275
278
|
</Card>
|
|
276
279
|
</Col>
|
|
277
280
|
<Col span={11}>
|
|
278
|
-
<Card
|
|
279
|
-
|
|
281
|
+
<Card
|
|
282
|
+
style={{
|
|
283
|
+
margin: '12px 12px 12px 0',
|
|
284
|
+
}}
|
|
285
|
+
>
|
|
286
|
+
<ChartConfigure.Renderer />
|
|
280
287
|
</Card>
|
|
281
288
|
</Col>
|
|
282
289
|
</Row>
|
|
@@ -287,7 +294,8 @@ export const ChartConfigure: React.FC<{
|
|
|
287
294
|
|
|
288
295
|
ChartConfigure.Renderer = function Renderer(props) {
|
|
289
296
|
const { current } = useContext(ChartConfigContext);
|
|
290
|
-
const { collection } = current || {};
|
|
297
|
+
const { collection, data } = current || {};
|
|
298
|
+
const { service } = useContext(ChartRendererContext);
|
|
291
299
|
return (
|
|
292
300
|
<FormConsumer>
|
|
293
301
|
{(form) => {
|
|
@@ -296,15 +304,9 @@ ChartConfigure.Renderer = function Renderer(props) {
|
|
|
296
304
|
const config = cloneDeep(form.values.config);
|
|
297
305
|
const transform = cloneDeep(form.values.transform);
|
|
298
306
|
return (
|
|
299
|
-
<
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
config={config}
|
|
303
|
-
transform={transform}
|
|
304
|
-
mode={form.values.mode}
|
|
305
|
-
>
|
|
306
|
-
<ChartRenderer configuring={true} {...props} />
|
|
307
|
-
</ChartRendererProvider>
|
|
307
|
+
<ChartRendererContext.Provider value={{ collection, config, transform, service, data }}>
|
|
308
|
+
<ChartRenderer {...props} />
|
|
309
|
+
</ChartRendererContext.Provider>
|
|
308
310
|
);
|
|
309
311
|
}}
|
|
310
312
|
</FormConsumer>
|
|
@@ -313,27 +315,30 @@ ChartConfigure.Renderer = function Renderer(props) {
|
|
|
313
315
|
|
|
314
316
|
ChartConfigure.Query = function Query() {
|
|
315
317
|
const { t } = useChartsTranslation();
|
|
316
|
-
const { t: lang } = useTranslation();
|
|
317
318
|
const fields = useFieldsWithAssociation();
|
|
318
319
|
const useFormatterOptions = useFormatters(fields);
|
|
319
320
|
const collectionOptions = useCollectionOptions();
|
|
320
321
|
const { current, setCurrent } = useContext(ChartConfigContext);
|
|
321
322
|
const { collection } = current || {};
|
|
322
323
|
const fieldOptions = useCollectionFieldsOptions(collection, 1);
|
|
323
|
-
const compiledFieldOptions = Schema.compile(fieldOptions, { t
|
|
324
|
+
const compiledFieldOptions = Schema.compile(fieldOptions, { t });
|
|
324
325
|
const filterOptions = useCollectionFilterOptions(collection);
|
|
325
|
-
|
|
326
|
+
|
|
327
|
+
const { service } = useContext(ChartRendererContext);
|
|
326
328
|
const onCollectionChange = (value: string) => {
|
|
327
329
|
const { schema, field } = current;
|
|
328
|
-
const newSchema = { ...schema };
|
|
329
|
-
newSchema['x-decorator-props'] = { collection: value };
|
|
330
|
-
newSchema['x-acl-action'] = `${value}:list`;
|
|
331
330
|
setCurrent({
|
|
332
|
-
schema
|
|
331
|
+
schema,
|
|
333
332
|
field,
|
|
334
333
|
collection: value,
|
|
334
|
+
service: current.service,
|
|
335
|
+
initialValues: {},
|
|
336
|
+
data: undefined,
|
|
335
337
|
});
|
|
338
|
+
service.mutate(undefined);
|
|
336
339
|
};
|
|
340
|
+
|
|
341
|
+
const formCollapse = FormCollapse.createFormCollapse(['measures', 'dimensions', 'filter', 'sort']);
|
|
337
342
|
const FromSql = () => (
|
|
338
343
|
<Text code>
|
|
339
344
|
From <span style={{ color: '#1890ff' }}>{current?.collection}</span>
|
|
@@ -429,22 +434,38 @@ ChartConfigure.Transform = function Transform() {
|
|
|
429
434
|
};
|
|
430
435
|
|
|
431
436
|
ChartConfigure.Data = function Data() {
|
|
432
|
-
const {
|
|
437
|
+
const { t } = useChartsTranslation();
|
|
438
|
+
const { current } = useContext(ChartConfigContext);
|
|
439
|
+
const { service } = useContext(ChartRendererContext);
|
|
433
440
|
const fields = useFieldsWithAssociation();
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
441
|
+
const data = processData(fields, service?.data || current?.data || [], { t });
|
|
442
|
+
const error = service?.error;
|
|
443
|
+
return !error ? (
|
|
444
|
+
<div
|
|
445
|
+
style={{
|
|
446
|
+
overflowX: 'auto',
|
|
447
|
+
overflowY: 'hidden',
|
|
448
|
+
}}
|
|
449
|
+
>
|
|
450
|
+
<Table
|
|
451
|
+
dataSource={data}
|
|
452
|
+
columns={Object.keys(data[0] || {}).map((col) => {
|
|
453
|
+
const field = getField(fields, col.split('.'));
|
|
454
|
+
return {
|
|
455
|
+
title: field?.label || col,
|
|
456
|
+
dataIndex: col,
|
|
457
|
+
key: col,
|
|
458
|
+
};
|
|
459
|
+
})}
|
|
460
|
+
size="small"
|
|
461
|
+
/>
|
|
462
|
+
</div>
|
|
447
463
|
) : (
|
|
448
|
-
<Alert
|
|
464
|
+
<Alert
|
|
465
|
+
message="Error"
|
|
466
|
+
type="error"
|
|
467
|
+
description={error?.response?.data?.errors?.map?.((error: any) => error.message).join('\n') || error.message}
|
|
468
|
+
showIcon
|
|
469
|
+
/>
|
|
449
470
|
);
|
|
450
471
|
};
|
|
@@ -154,7 +154,7 @@ export const querySchema: ISchema = {
|
|
|
154
154
|
'x-component-props': {
|
|
155
155
|
options: '{{ collectionOptions }}',
|
|
156
156
|
onChange: '{{ onCollectionChange }}',
|
|
157
|
-
placeholder:
|
|
157
|
+
placeholder: '{{t("Collection")}}',
|
|
158
158
|
},
|
|
159
159
|
},
|
|
160
160
|
},
|
|
@@ -206,11 +206,11 @@ export const querySchema: ISchema = {
|
|
|
206
206
|
placeholder: '{{t("Aggregation")}}',
|
|
207
207
|
},
|
|
208
208
|
enum: [
|
|
209
|
-
{ label:
|
|
210
|
-
{ label:
|
|
211
|
-
{ label:
|
|
212
|
-
{ label:
|
|
213
|
-
{ label:
|
|
209
|
+
{ label: '{{t("Sum")}}', value: 'sum' },
|
|
210
|
+
{ label: '{{t("Count")}}', value: 'count' },
|
|
211
|
+
{ label: '{{t("Avg")}}', value: 'avg' },
|
|
212
|
+
{ label: '{{t("Max")}}', value: 'max' },
|
|
213
|
+
{ label: '{{t("Min")}}', value: 'min' },
|
|
214
214
|
],
|
|
215
215
|
},
|
|
216
216
|
alias: {
|
|
@@ -287,7 +287,7 @@ export const querySchema: ISchema = {
|
|
|
287
287
|
'x-decorator': 'FormItem',
|
|
288
288
|
'x-decorator-props': {
|
|
289
289
|
style: {
|
|
290
|
-
overflow: '
|
|
290
|
+
overflow: 'auto',
|
|
291
291
|
},
|
|
292
292
|
},
|
|
293
293
|
'x-component': 'Filter',
|
package/src/client/index.tsx
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { i18n } from '@nocobase/client';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import zhCN from './zh-CN';
|
|
4
3
|
|
|
5
|
-
export const NAMESPACE = '
|
|
4
|
+
export const NAMESPACE = 'data-visualization';
|
|
6
5
|
|
|
7
|
-
i18n.addResources('zh-CN', NAMESPACE, zhCN);
|
|
6
|
+
// i18n.addResources('zh-CN', NAMESPACE, zhCN);
|
|
8
7
|
// i18n.addResources('en-US', NAMESPACE, enUS);
|
|
9
8
|
// i18n.addResources('ja-JP', NAMESPACE, jaJP);
|
|
10
9
|
// i18n.addResources('ru-RU', NAMESPACE, ruRU);
|
|
@@ -77,7 +77,7 @@ export const useChartTypes = (): {
|
|
|
77
77
|
const children = Object.entries(l.charts).map(([type, chart]) => ({
|
|
78
78
|
...chart,
|
|
79
79
|
key: type,
|
|
80
|
-
label: chart.name,
|
|
80
|
+
label: lang(chart.name),
|
|
81
81
|
value: type,
|
|
82
82
|
}));
|
|
83
83
|
return [
|
|
@@ -90,6 +90,11 @@ export const useChartTypes = (): {
|
|
|
90
90
|
}, []);
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
+
export const useDefaultChartType = () => {
|
|
94
|
+
const chartTypes = useChartTypes();
|
|
95
|
+
return chartTypes[0]?.children?.[0]?.value;
|
|
96
|
+
};
|
|
97
|
+
|
|
93
98
|
export const useToggleChartLibrary = () => {
|
|
94
99
|
const ctx = useContext(ChartLibraryContext);
|
|
95
100
|
return {
|
|
@@ -1,107 +1,34 @@
|
|
|
1
1
|
import { useField, useFieldSchema } from '@formily/react';
|
|
2
2
|
import {
|
|
3
3
|
GeneralSchemaDesigner,
|
|
4
|
-
gridRowColWrap,
|
|
5
4
|
SchemaSettings,
|
|
5
|
+
gridRowColWrap,
|
|
6
6
|
useAPIClient,
|
|
7
7
|
useCollection,
|
|
8
8
|
useDesignable,
|
|
9
|
-
useRequest,
|
|
10
9
|
} from '@nocobase/client';
|
|
11
|
-
import { Empty, Result, Typography } from 'antd';
|
|
12
|
-
import React, { useContext
|
|
10
|
+
import { Empty, Result, Spin, Typography } from 'antd';
|
|
11
|
+
import React, { useContext } from 'react';
|
|
13
12
|
import { ErrorBoundary } from 'react-error-boundary';
|
|
14
13
|
import { ChartConfigContext } from '../block';
|
|
15
|
-
import {
|
|
14
|
+
import { useFieldTransformer, useFieldsWithAssociation } from '../hooks';
|
|
16
15
|
import { useChartsTranslation } from '../locale';
|
|
17
|
-
import { createRendererSchema, getField,
|
|
16
|
+
import { createRendererSchema, getField, processData } from '../utils';
|
|
18
17
|
import { useCharts } from './ChartLibrary';
|
|
19
|
-
import { ChartRendererContext
|
|
18
|
+
import { ChartRendererContext } from './ChartRendererProvider';
|
|
20
19
|
const { Paragraph, Text } = Typography;
|
|
21
20
|
|
|
22
|
-
export const ChartRenderer: React.FC
|
|
23
|
-
configuring?: boolean;
|
|
24
|
-
runQuery?: any;
|
|
25
|
-
}> & {
|
|
21
|
+
export const ChartRenderer: React.FC & {
|
|
26
22
|
Designer: React.FC;
|
|
27
23
|
} = (props) => {
|
|
28
24
|
const { t } = useChartsTranslation();
|
|
29
|
-
const
|
|
30
|
-
const {
|
|
31
|
-
const
|
|
25
|
+
const ctx = useContext(ChartRendererContext);
|
|
26
|
+
const { config, transform, collection, service, data: _data } = ctx;
|
|
27
|
+
const fields = useFieldsWithAssociation(collection);
|
|
28
|
+
const data = processData(fields, service?.data || _data || [], { t });
|
|
32
29
|
const general = config?.general || {};
|
|
33
30
|
const advanced = config?.advanced || {};
|
|
34
|
-
const schema = useFieldSchema();
|
|
35
|
-
const currentSchema = schema || current?.schema;
|
|
36
|
-
const fields = useFieldsWithAssociation(collection);
|
|
37
31
|
const api = useAPIClient();
|
|
38
|
-
const [data, setData] = useState<any[]>([]);
|
|
39
|
-
const { runAsync } = useRequest(
|
|
40
|
-
(query) =>
|
|
41
|
-
api
|
|
42
|
-
.request({
|
|
43
|
-
url: 'charts:query',
|
|
44
|
-
method: 'POST',
|
|
45
|
-
data: {
|
|
46
|
-
uid: currentSchema?.['x-uid'],
|
|
47
|
-
collection,
|
|
48
|
-
...query,
|
|
49
|
-
dimensions: (query?.dimensions || []).map((item: DimensionProps) => {
|
|
50
|
-
const dimension = { ...item };
|
|
51
|
-
if (item.format && !item.alias) {
|
|
52
|
-
const { alias } = parseField(item.field);
|
|
53
|
-
dimension.alias = alias;
|
|
54
|
-
}
|
|
55
|
-
return dimension;
|
|
56
|
-
}),
|
|
57
|
-
measures: (query?.measures || []).map((item: MeasureProps) => {
|
|
58
|
-
const measure = { ...item };
|
|
59
|
-
if (item.aggregation && !item.alias) {
|
|
60
|
-
const { alias } = parseField(item.field);
|
|
61
|
-
measure.alias = alias;
|
|
62
|
-
}
|
|
63
|
-
return measure;
|
|
64
|
-
}),
|
|
65
|
-
},
|
|
66
|
-
})
|
|
67
|
-
.then((res) => {
|
|
68
|
-
const data = res?.data?.data || [];
|
|
69
|
-
return processData(fields, data, { t });
|
|
70
|
-
}),
|
|
71
|
-
{
|
|
72
|
-
manual: true,
|
|
73
|
-
onSuccess: (data) => {
|
|
74
|
-
setData(data);
|
|
75
|
-
},
|
|
76
|
-
onFinally(params, data, error: any) {
|
|
77
|
-
if (!configuring) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
if (error) {
|
|
81
|
-
const message = error?.response?.data?.errors?.map?.((error: any) => error.message).join('\n');
|
|
82
|
-
setQueryData(message || error.message);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
setQueryData(data);
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
useEffect(() => {
|
|
91
|
-
setData([]);
|
|
92
|
-
const run = async (query: QueryProps) => {
|
|
93
|
-
if (
|
|
94
|
-
query?.measures?.length
|
|
95
|
-
// || (query?.sql?.fields && query?.sql?.clauses)
|
|
96
|
-
) {
|
|
97
|
-
await runAsync(query);
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
if (runQuery) {
|
|
101
|
-
runQuery.current = run;
|
|
102
|
-
}
|
|
103
|
-
run(query);
|
|
104
|
-
}, [query, runAsync, runQuery]);
|
|
105
32
|
|
|
106
33
|
const charts = useCharts();
|
|
107
34
|
const chart = charts[config?.chartType];
|
|
@@ -137,16 +64,21 @@ export const ChartRenderer: React.FC<{
|
|
|
137
64
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('Please configure chart')} />
|
|
138
65
|
);
|
|
139
66
|
|
|
140
|
-
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
)
|
|
67
|
+
if (service.loading) {
|
|
68
|
+
return <Spin />;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!(data && data.length)) {
|
|
72
|
+
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('Please configure and run query')} />;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return <C />;
|
|
145
76
|
};
|
|
146
77
|
|
|
147
78
|
ChartRenderer.Designer = function Designer() {
|
|
148
79
|
const { t } = useChartsTranslation();
|
|
149
80
|
const { setVisible, setCurrent } = useContext(ChartConfigContext);
|
|
81
|
+
const { service } = useContext(ChartRendererContext);
|
|
150
82
|
const field = useField();
|
|
151
83
|
const schema = useFieldSchema();
|
|
152
84
|
const { insertAdjacent } = useDesignable();
|
|
@@ -156,7 +88,7 @@ ChartRenderer.Designer = function Designer() {
|
|
|
156
88
|
<SchemaSettings.Item
|
|
157
89
|
key="configure"
|
|
158
90
|
onClick={() => {
|
|
159
|
-
setCurrent({ schema, field, collection: name });
|
|
91
|
+
setCurrent({ schema, field, collection: name, service, data: service?.data });
|
|
160
92
|
setVisible(true);
|
|
161
93
|
}}
|
|
162
94
|
>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { css } from '@emotion/css';
|
|
2
|
-
import {
|
|
2
|
+
import { useFieldSchema } from '@formily/react';
|
|
3
|
+
import { MaybeCollectionProvider, useAPIClient, useRequest } from '@nocobase/client';
|
|
3
4
|
import React, { createContext } from 'react';
|
|
5
|
+
import { parseField } from '../utils';
|
|
4
6
|
|
|
5
7
|
export type MeasureProps = {
|
|
6
8
|
field: string | string[];
|
|
@@ -47,10 +49,62 @@ export type ChartRendererProps = {
|
|
|
47
49
|
mode?: 'builder' | 'sql';
|
|
48
50
|
};
|
|
49
51
|
|
|
50
|
-
export const ChartRendererContext = createContext<
|
|
52
|
+
export const ChartRendererContext = createContext<{
|
|
53
|
+
collection: string;
|
|
54
|
+
config?: {
|
|
55
|
+
chartType: string;
|
|
56
|
+
general: any;
|
|
57
|
+
advanced: any;
|
|
58
|
+
};
|
|
59
|
+
transform?: TransformProps[];
|
|
60
|
+
service: any;
|
|
61
|
+
data?: any[];
|
|
62
|
+
}>({} as any);
|
|
51
63
|
|
|
52
64
|
export const ChartRendererProvider: React.FC<ChartRendererProps> = (props) => {
|
|
53
|
-
const { collection } = props;
|
|
65
|
+
const { query, config, collection, transform } = props;
|
|
66
|
+
const schema = useFieldSchema();
|
|
67
|
+
const api = useAPIClient();
|
|
68
|
+
const service = useRequest(
|
|
69
|
+
(collection, query) =>
|
|
70
|
+
new Promise((resolve, reject) => {
|
|
71
|
+
if (!(collection && query?.measures?.length)) return resolve(undefined);
|
|
72
|
+
api
|
|
73
|
+
.request({
|
|
74
|
+
url: 'charts:query',
|
|
75
|
+
method: 'POST',
|
|
76
|
+
data: {
|
|
77
|
+
uid: schema?.['x-uid'],
|
|
78
|
+
collection,
|
|
79
|
+
...query,
|
|
80
|
+
dimensions: (query?.dimensions || []).map((item: DimensionProps) => {
|
|
81
|
+
const dimension = { ...item };
|
|
82
|
+
if (item.format && !item.alias) {
|
|
83
|
+
const { alias } = parseField(item.field);
|
|
84
|
+
dimension.alias = alias;
|
|
85
|
+
}
|
|
86
|
+
return dimension;
|
|
87
|
+
}),
|
|
88
|
+
measures: (query?.measures || []).map((item: MeasureProps) => {
|
|
89
|
+
const measure = { ...item };
|
|
90
|
+
if (item.aggregation && !item.alias) {
|
|
91
|
+
const { alias } = parseField(item.field);
|
|
92
|
+
measure.alias = alias;
|
|
93
|
+
}
|
|
94
|
+
return measure;
|
|
95
|
+
}),
|
|
96
|
+
},
|
|
97
|
+
})
|
|
98
|
+
.then((res) => {
|
|
99
|
+
resolve(res?.data?.data);
|
|
100
|
+
})
|
|
101
|
+
.catch(reject);
|
|
102
|
+
}),
|
|
103
|
+
{
|
|
104
|
+
defaultParams: [collection, query],
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
|
|
54
108
|
return (
|
|
55
109
|
<MaybeCollectionProvider collection={collection}>
|
|
56
110
|
<div
|
|
@@ -58,12 +112,11 @@ export const ChartRendererProvider: React.FC<ChartRendererProps> = (props) => {
|
|
|
58
112
|
.ant-card {
|
|
59
113
|
box-shadow: none;
|
|
60
114
|
}
|
|
61
|
-
.ant-card-body {
|
|
62
|
-
padding: 0;
|
|
63
|
-
}
|
|
64
115
|
`}
|
|
65
116
|
>
|
|
66
|
-
<ChartRendererContext.Provider value={{
|
|
117
|
+
<ChartRendererContext.Provider value={{ collection, config, transform, service }}>
|
|
118
|
+
{props.children}
|
|
119
|
+
</ChartRendererContext.Provider>
|
|
67
120
|
</div>
|
|
68
121
|
</MaybeCollectionProvider>
|
|
69
122
|
);
|