fix(bi): eliminate redundancy queries (#2268)
* fix(bi): eliminate redundancy queries * fix: reset data after close configure pane * fix: enum transform bug * fix: reset form after clicking cancel button * fix: reset form when changing collection
This commit is contained in:
parent
2d8451f2dd
commit
d80213be5e
@ -1,21 +1,23 @@
|
|||||||
import { SchemaInitializerButtonContext, useDesignable } from '@nocobase/client';
|
import { SchemaInitializerButtonContext, useDesignable } from '@nocobase/client';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import { ChartRendererProvider } from '../renderer';
|
||||||
import { ChartConfigContext, ChartConfigCurrent, ChartConfigure } from './ChartConfigure';
|
import { ChartConfigContext, ChartConfigCurrent, ChartConfigure } from './ChartConfigure';
|
||||||
|
|
||||||
export const ChartV2Block: React.FC = (props) => {
|
export const ChartV2Block: React.FC = (props) => {
|
||||||
const { insertAdjacent } = useDesignable();
|
const { insertAdjacent } = useDesignable();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [current, setCurrent] = useState<ChartConfigCurrent>({} as any);
|
const [current, setCurrent] = useState<ChartConfigCurrent>({} as any);
|
||||||
const [data, setData] = useState<string | any[]>([]);
|
|
||||||
const [initialVisible, setInitialVisible] = useState(false);
|
const [initialVisible, setInitialVisible] = useState(false);
|
||||||
const [searchValue, setSearchValue] = useState('');
|
const [searchValue, setSearchValue] = useState('');
|
||||||
return (
|
return (
|
||||||
<SchemaInitializerButtonContext.Provider
|
<SchemaInitializerButtonContext.Provider
|
||||||
value={{ visible: initialVisible, setVisible: setInitialVisible, searchValue, setSearchValue }}
|
value={{ visible: initialVisible, setVisible: setInitialVisible, searchValue, setSearchValue }}
|
||||||
>
|
>
|
||||||
<ChartConfigContext.Provider value={{ visible, setVisible, current, setCurrent, data, setData }}>
|
<ChartConfigContext.Provider value={{ visible, setVisible, current, setCurrent }}>
|
||||||
{props.children}
|
{props.children}
|
||||||
<ChartConfigure insert={(schema, options) => insertAdjacent('beforeEnd', schema, options)} />
|
<ChartRendererProvider {...current.field?.decoratorProps}>
|
||||||
|
<ChartConfigure insert={(schema, options) => insertAdjacent('beforeEnd', schema, options)} />
|
||||||
|
</ChartRendererProvider>
|
||||||
</ChartConfigContext.Provider>
|
</ChartConfigContext.Provider>
|
||||||
</SchemaInitializerButtonContext.Provider>
|
</SchemaInitializerButtonContext.Provider>
|
||||||
);
|
);
|
||||||
|
@ -8,13 +8,12 @@ import { ChartConfigContext } from './ChartConfigure';
|
|||||||
|
|
||||||
const itemWrap = SchemaInitializer.itemWrap;
|
const itemWrap = SchemaInitializer.itemWrap;
|
||||||
const ConfigureButton = itemWrap((props) => {
|
const ConfigureButton = itemWrap((props) => {
|
||||||
const { setVisible, setCurrent, setData } = useContext(ChartConfigContext);
|
const { setVisible, setCurrent } = useContext(ChartConfigContext);
|
||||||
return (
|
return (
|
||||||
<SchemaInitializer.Item
|
<SchemaInitializer.Item
|
||||||
{...props}
|
{...props}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrent({ schema: {}, field: null, collection: props.item?.name });
|
setCurrent({ schema: {}, field: null, collection: props.item?.name, service: null, data: undefined });
|
||||||
setData([]);
|
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -20,7 +20,6 @@ import {
|
|||||||
import { Alert, App, Button, Card, Col, Modal, Row, Space, Table, Tabs, Typography } from 'antd';
|
import { Alert, App, Button, Card, Col, Modal, Row, Space, Table, Tabs, Typography } from 'antd';
|
||||||
import { cloneDeep, isEqual } from 'lodash';
|
import { cloneDeep, isEqual } from 'lodash';
|
||||||
import React, { createContext, useContext, useMemo, useRef } from 'react';
|
import React, { createContext, useContext, useMemo, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import {
|
import {
|
||||||
useChartFields,
|
useChartFields,
|
||||||
useCollectionOptions,
|
useCollectionOptions,
|
||||||
@ -32,8 +31,8 @@ import {
|
|||||||
useTransformers,
|
useTransformers,
|
||||||
} from '../hooks';
|
} from '../hooks';
|
||||||
import { useChartsTranslation } from '../locale';
|
import { useChartsTranslation } from '../locale';
|
||||||
import { ChartRenderer, ChartRendererProvider, useChartTypes, useCharts } from '../renderer';
|
import { ChartRenderer, ChartRendererContext, useChartTypes, useCharts, useDefaultChartType } from '../renderer';
|
||||||
import { createRendererSchema, getField, getSelectedFields } from '../utils';
|
import { createRendererSchema, getField, getSelectedFields, processData } from '../utils';
|
||||||
import { getConfigSchema, querySchema, transformSchema } from './schemas/configure';
|
import { getConfigSchema, querySchema, transformSchema } from './schemas/configure';
|
||||||
const { Paragraph, Text } = Typography;
|
const { Paragraph, Text } = Typography;
|
||||||
|
|
||||||
@ -41,6 +40,9 @@ export type ChartConfigCurrent = {
|
|||||||
schema: ISchema;
|
schema: ISchema;
|
||||||
field: any;
|
field: any;
|
||||||
collection: string;
|
collection: string;
|
||||||
|
service: any;
|
||||||
|
initialValues?: any;
|
||||||
|
data: any[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SelectedField = {
|
export type SelectedField = {
|
||||||
@ -53,8 +55,6 @@ export const ChartConfigContext = createContext<{
|
|||||||
setVisible?: (visible: boolean) => void;
|
setVisible?: (visible: boolean) => void;
|
||||||
current?: ChartConfigCurrent;
|
current?: ChartConfigCurrent;
|
||||||
setCurrent?: (current: ChartConfigCurrent) => void;
|
setCurrent?: (current: ChartConfigCurrent) => void;
|
||||||
data?: any[] | string;
|
|
||||||
setData?: (data: any[] | string) => void;
|
|
||||||
}>({
|
}>({
|
||||||
visible: true,
|
visible: true,
|
||||||
});
|
});
|
||||||
@ -68,17 +68,16 @@ export const ChartConfigure: React.FC<{
|
|||||||
},
|
},
|
||||||
) => void;
|
) => void;
|
||||||
}> & {
|
}> & {
|
||||||
Renderer: React.FC<{
|
Renderer: React.FC;
|
||||||
runQuery?: any;
|
|
||||||
}>;
|
|
||||||
Config: React.FC;
|
Config: React.FC;
|
||||||
Query: React.FC;
|
Query: React.FC;
|
||||||
Transform: React.FC;
|
Transform: React.FC;
|
||||||
Data: React.FC;
|
Data: React.FC;
|
||||||
} = (props) => {
|
} = (props) => {
|
||||||
const { t } = useChartsTranslation();
|
const { t } = useChartsTranslation();
|
||||||
|
const { service } = useContext(ChartRendererContext);
|
||||||
const { visible, setVisible, current } = useContext(ChartConfigContext);
|
const { visible, setVisible, current } = useContext(ChartConfigContext);
|
||||||
const { schema, field, collection } = current || {};
|
const { schema, field, collection, initialValues } = current || {};
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const { modal } = App.useApp();
|
const { modal } = App.useApp();
|
||||||
const { insert } = props;
|
const { insert } = props;
|
||||||
@ -125,53 +124,48 @@ export const ChartConfigure: React.FC<{
|
|||||||
setMeasures(cloneDeep(currentMeasures));
|
setMeasures(cloneDeep(currentMeasures));
|
||||||
setDimensions(cloneDeep(currentDimensions));
|
setDimensions(cloneDeep(currentDimensions));
|
||||||
};
|
};
|
||||||
const chartTypes = useChartTypes();
|
const chartType = useDefaultChartType();
|
||||||
const form = useMemo(
|
const form = useMemo(
|
||||||
() => {
|
() => {
|
||||||
const chartType = chartTypes[0]?.children?.[0]?.value;
|
|
||||||
return createForm({
|
return createForm({
|
||||||
values: { config: { chartType }, ...schema?.['x-decorator-props'], collection, data: '' },
|
values: { config: { chartType }, ...(initialValues || field?.decoratorProps), collection },
|
||||||
effects: (form) => {
|
effects: (form) => {
|
||||||
onFieldChange('config.chartType', () => initChart(true));
|
onFieldChange('config.chartType', () => initChart(true));
|
||||||
onFormInit(() => queryReact(form));
|
onFormInit(() => {
|
||||||
|
queryReact(form);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// visible, collection added here to re-initialize form when visible, collection change
|
// visible, collection added here to re-initialize form when visible, collection change
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[schema, visible, collection],
|
[field, visible, collection],
|
||||||
);
|
);
|
||||||
|
|
||||||
const runQuery = useRef(null);
|
const RunButton: React.FC = () => (
|
||||||
const RunButton: React.FC = () => {
|
<Button
|
||||||
const [loading, setLoading] = React.useState(false);
|
type="link"
|
||||||
return (
|
loading={service?.loading}
|
||||||
<Button
|
icon={<RightSquareOutlined />}
|
||||||
type="link"
|
onClick={async () => {
|
||||||
loading={loading}
|
const queryField = form.query('query').take() as ObjectField;
|
||||||
icon={<RightSquareOutlined />}
|
try {
|
||||||
onClick={async () => {
|
await queryField?.validate();
|
||||||
const queryField = form.query('query').take() as ObjectField;
|
} catch (e) {
|
||||||
try {
|
return;
|
||||||
await queryField?.validate();
|
}
|
||||||
} catch (e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(true);
|
try {
|
||||||
try {
|
await service.runAsync(collection, form.values.query);
|
||||||
await runQuery?.current(form.values.query);
|
} catch (e) {
|
||||||
} catch (e) {
|
console.log(e);
|
||||||
console.log(e);
|
}
|
||||||
}
|
queryReact(form, initChart);
|
||||||
queryReact(form, initChart);
|
}}
|
||||||
setLoading(false);
|
>
|
||||||
}}
|
{t('Run query')}
|
||||||
>
|
</Button>
|
||||||
{t('Run query')}
|
);
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const queryRef = useRef(null);
|
const queryRef = useRef(null);
|
||||||
const configRef = useRef(null);
|
const configRef = useRef(null);
|
||||||
@ -181,6 +175,13 @@ export const ChartConfigure: React.FC<{
|
|||||||
open={visible}
|
open={visible}
|
||||||
onOk={() => {
|
onOk={() => {
|
||||||
const { query, config, transform, mode } = form.values;
|
const { query, config, transform, mode } = form.values;
|
||||||
|
const afterSave = () => {
|
||||||
|
setVisible(false);
|
||||||
|
current.service?.run(collection, query);
|
||||||
|
queryRef.current.scrollTop = 0;
|
||||||
|
configRef.current.scrollTop = 0;
|
||||||
|
service.mutate(undefined);
|
||||||
|
};
|
||||||
const rendererProps = {
|
const rendererProps = {
|
||||||
query,
|
query,
|
||||||
config,
|
config,
|
||||||
@ -195,13 +196,11 @@ export const ChartConfigure: React.FC<{
|
|||||||
dn.emit('patch', {
|
dn.emit('patch', {
|
||||||
schema,
|
schema,
|
||||||
});
|
});
|
||||||
setVisible(false);
|
afterSave();
|
||||||
queryRef.current.scrollTop = 0;
|
|
||||||
configRef.current.scrollTop = 0;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
insert(createRendererSchema(rendererProps), {
|
insert(createRendererSchema(rendererProps), {
|
||||||
onSuccess: () => setVisible(false),
|
onSuccess: afterSave,
|
||||||
wrap: gridRowColWrap,
|
wrap: gridRowColWrap,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -216,6 +215,7 @@ export const ChartConfigure: React.FC<{
|
|||||||
setVisible(false);
|
setVisible(false);
|
||||||
queryRef.current.scrollTop = 0;
|
queryRef.current.scrollTop = 0;
|
||||||
configRef.current.scrollTop = 0;
|
configRef.current.scrollTop = 0;
|
||||||
|
service.mutate(undefined);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -231,6 +231,7 @@ export const ChartConfigure: React.FC<{
|
|||||||
style={{
|
style={{
|
||||||
height: 'calc(100vh - 300px)',
|
height: 'calc(100vh - 300px)',
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
|
margin: '12px 0 12px 12px',
|
||||||
}}
|
}}
|
||||||
ref={queryRef}
|
ref={queryRef}
|
||||||
>
|
>
|
||||||
@ -256,6 +257,7 @@ export const ChartConfigure: React.FC<{
|
|||||||
style={{
|
style={{
|
||||||
height: 'calc(100vh - 300px)',
|
height: 'calc(100vh - 300px)',
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
|
margin: '12px 3px 12px 3px',
|
||||||
}}
|
}}
|
||||||
ref={configRef}
|
ref={configRef}
|
||||||
>
|
>
|
||||||
@ -276,8 +278,12 @@ export const ChartConfigure: React.FC<{
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={11}>
|
<Col span={11}>
|
||||||
<Card>
|
<Card
|
||||||
<ChartConfigure.Renderer runQuery={runQuery} />
|
style={{
|
||||||
|
margin: '12px 12px 12px 0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ChartConfigure.Renderer />
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -288,7 +294,8 @@ export const ChartConfigure: React.FC<{
|
|||||||
|
|
||||||
ChartConfigure.Renderer = function Renderer(props) {
|
ChartConfigure.Renderer = function Renderer(props) {
|
||||||
const { current } = useContext(ChartConfigContext);
|
const { current } = useContext(ChartConfigContext);
|
||||||
const { collection } = current || {};
|
const { collection, data } = current || {};
|
||||||
|
const { service } = useContext(ChartRendererContext);
|
||||||
return (
|
return (
|
||||||
<FormConsumer>
|
<FormConsumer>
|
||||||
{(form) => {
|
{(form) => {
|
||||||
@ -297,15 +304,9 @@ ChartConfigure.Renderer = function Renderer(props) {
|
|||||||
const config = cloneDeep(form.values.config);
|
const config = cloneDeep(form.values.config);
|
||||||
const transform = cloneDeep(form.values.transform);
|
const transform = cloneDeep(form.values.transform);
|
||||||
return (
|
return (
|
||||||
<ChartRendererProvider
|
<ChartRendererContext.Provider value={{ collection, config, transform, service, data }}>
|
||||||
collection={collection}
|
<ChartRenderer {...props} />
|
||||||
query={form.values.query}
|
</ChartRendererContext.Provider>
|
||||||
config={config}
|
|
||||||
transform={transform}
|
|
||||||
mode={form.values.mode}
|
|
||||||
>
|
|
||||||
<ChartRenderer configuring={true} {...props} />
|
|
||||||
</ChartRendererProvider>
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</FormConsumer>
|
</FormConsumer>
|
||||||
@ -314,27 +315,30 @@ ChartConfigure.Renderer = function Renderer(props) {
|
|||||||
|
|
||||||
ChartConfigure.Query = function Query() {
|
ChartConfigure.Query = function Query() {
|
||||||
const { t } = useChartsTranslation();
|
const { t } = useChartsTranslation();
|
||||||
const { t: lang } = useTranslation();
|
|
||||||
const fields = useFieldsWithAssociation();
|
const fields = useFieldsWithAssociation();
|
||||||
const useFormatterOptions = useFormatters(fields);
|
const useFormatterOptions = useFormatters(fields);
|
||||||
const collectionOptions = useCollectionOptions();
|
const collectionOptions = useCollectionOptions();
|
||||||
const { current, setCurrent } = useContext(ChartConfigContext);
|
const { current, setCurrent } = useContext(ChartConfigContext);
|
||||||
const { collection } = current || {};
|
const { collection } = current || {};
|
||||||
const fieldOptions = useCollectionFieldsOptions(collection, 1);
|
const fieldOptions = useCollectionFieldsOptions(collection, 1);
|
||||||
const compiledFieldOptions = Schema.compile(fieldOptions, { t: lang });
|
const compiledFieldOptions = Schema.compile(fieldOptions, { t });
|
||||||
const filterOptions = useCollectionFilterOptions(collection);
|
const filterOptions = useCollectionFilterOptions(collection);
|
||||||
const formCollapse = FormCollapse.createFormCollapse(['measures', 'dimensions', 'filter', 'sort']);
|
|
||||||
|
const { service } = useContext(ChartRendererContext);
|
||||||
const onCollectionChange = (value: string) => {
|
const onCollectionChange = (value: string) => {
|
||||||
const { schema, field } = current;
|
const { schema, field } = current;
|
||||||
const newSchema = { ...schema };
|
|
||||||
newSchema['x-decorator-props'] = { collection: value };
|
|
||||||
newSchema['x-acl-action'] = `${value}:list`;
|
|
||||||
setCurrent({
|
setCurrent({
|
||||||
schema: newSchema,
|
schema,
|
||||||
field,
|
field,
|
||||||
collection: value,
|
collection: value,
|
||||||
|
service: current.service,
|
||||||
|
initialValues: {},
|
||||||
|
data: undefined,
|
||||||
});
|
});
|
||||||
|
service.mutate(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formCollapse = FormCollapse.createFormCollapse(['measures', 'dimensions', 'filter', 'sort']);
|
||||||
const FromSql = () => (
|
const FromSql = () => (
|
||||||
<Text code>
|
<Text code>
|
||||||
From <span style={{ color: '#1890ff' }}>{current?.collection}</span>
|
From <span style={{ color: '#1890ff' }}>{current?.collection}</span>
|
||||||
@ -430,22 +434,38 @@ ChartConfigure.Transform = function Transform() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ChartConfigure.Data = function Data() {
|
ChartConfigure.Data = function Data() {
|
||||||
const { data } = useContext(ChartConfigContext);
|
const { t } = useChartsTranslation();
|
||||||
|
const { current } = useContext(ChartConfigContext);
|
||||||
|
const { service } = useContext(ChartRendererContext);
|
||||||
const fields = useFieldsWithAssociation();
|
const fields = useFieldsWithAssociation();
|
||||||
return Array.isArray(data) ? (
|
const data = processData(fields, service?.data || current?.data || [], { t });
|
||||||
<Table
|
const error = service?.error;
|
||||||
dataSource={data}
|
return !error ? (
|
||||||
columns={Object.keys(data[0] || {}).map((col) => {
|
<div
|
||||||
const field = getField(fields, col.split('.'));
|
style={{
|
||||||
return {
|
overflowX: 'auto',
|
||||||
title: field?.label || col,
|
overflowY: 'hidden',
|
||||||
dataIndex: col,
|
}}
|
||||||
key: col,
|
>
|
||||||
};
|
<Table
|
||||||
})}
|
dataSource={data}
|
||||||
size="small"
|
columns={Object.keys(data[0] || {}).map((col) => {
|
||||||
/>
|
const field = getField(fields, col.split('.'));
|
||||||
|
return {
|
||||||
|
title: field?.label || col,
|
||||||
|
dataIndex: col,
|
||||||
|
key: col,
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Alert message="Error" type="error" description={data} showIcon />
|
<Alert
|
||||||
|
message="Error"
|
||||||
|
type="error"
|
||||||
|
description={error?.response?.data?.errors?.map?.((error: any) => error.message).join('\n') || error.message}
|
||||||
|
showIcon
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -90,6 +90,11 @@ export const useChartTypes = (): {
|
|||||||
}, []);
|
}, []);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useDefaultChartType = () => {
|
||||||
|
const chartTypes = useChartTypes();
|
||||||
|
return chartTypes[0]?.children?.[0]?.value;
|
||||||
|
};
|
||||||
|
|
||||||
export const useToggleChartLibrary = () => {
|
export const useToggleChartLibrary = () => {
|
||||||
const ctx = useContext(ChartLibraryContext);
|
const ctx = useContext(ChartLibraryContext);
|
||||||
return {
|
return {
|
||||||
|
@ -1,107 +1,34 @@
|
|||||||
import { useField, useFieldSchema } from '@formily/react';
|
import { useField, useFieldSchema } from '@formily/react';
|
||||||
import {
|
import {
|
||||||
GeneralSchemaDesigner,
|
GeneralSchemaDesigner,
|
||||||
gridRowColWrap,
|
|
||||||
SchemaSettings,
|
SchemaSettings,
|
||||||
|
gridRowColWrap,
|
||||||
useAPIClient,
|
useAPIClient,
|
||||||
useCollection,
|
useCollection,
|
||||||
useDesignable,
|
useDesignable,
|
||||||
useRequest,
|
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { Empty, Result, Typography } from 'antd';
|
import { Empty, Result, Spin, Typography } from 'antd';
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { ChartConfigContext } from '../block';
|
import { ChartConfigContext } from '../block';
|
||||||
import { useFieldsWithAssociation, useFieldTransformer } from '../hooks';
|
import { useFieldTransformer, useFieldsWithAssociation } from '../hooks';
|
||||||
import { useChartsTranslation } from '../locale';
|
import { useChartsTranslation } from '../locale';
|
||||||
import { createRendererSchema, getField, parseField, processData } from '../utils';
|
import { createRendererSchema, getField, processData } from '../utils';
|
||||||
import { useCharts } from './ChartLibrary';
|
import { useCharts } from './ChartLibrary';
|
||||||
import { ChartRendererContext, DimensionProps, MeasureProps, QueryProps } from './ChartRendererProvider';
|
import { ChartRendererContext } from './ChartRendererProvider';
|
||||||
const { Paragraph, Text } = Typography;
|
const { Paragraph, Text } = Typography;
|
||||||
|
|
||||||
export const ChartRenderer: React.FC<{
|
export const ChartRenderer: React.FC & {
|
||||||
configuring?: boolean;
|
|
||||||
runQuery?: any;
|
|
||||||
}> & {
|
|
||||||
Designer: React.FC;
|
Designer: React.FC;
|
||||||
} = (props) => {
|
} = (props) => {
|
||||||
const { t } = useChartsTranslation();
|
const { t } = useChartsTranslation();
|
||||||
const { setData: setQueryData, current } = useContext(ChartConfigContext);
|
const ctx = useContext(ChartRendererContext);
|
||||||
const { query, config, collection, transform } = useContext(ChartRendererContext);
|
const { config, transform, collection, service, data: _data } = ctx;
|
||||||
const { configuring, runQuery } = props;
|
const fields = useFieldsWithAssociation(collection);
|
||||||
|
const data = processData(fields, service?.data || _data || [], { t });
|
||||||
const general = config?.general || {};
|
const general = config?.general || {};
|
||||||
const advanced = config?.advanced || {};
|
const advanced = config?.advanced || {};
|
||||||
const schema = useFieldSchema();
|
|
||||||
const currentSchema = schema || current?.schema;
|
|
||||||
const fields = useFieldsWithAssociation(collection);
|
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
const [data, setData] = useState<any[]>([]);
|
|
||||||
const { runAsync } = useRequest(
|
|
||||||
(query) =>
|
|
||||||
api
|
|
||||||
.request({
|
|
||||||
url: 'charts:query',
|
|
||||||
method: 'POST',
|
|
||||||
data: {
|
|
||||||
uid: currentSchema?.['x-uid'],
|
|
||||||
collection,
|
|
||||||
...query,
|
|
||||||
dimensions: (query?.dimensions || []).map((item: DimensionProps) => {
|
|
||||||
const dimension = { ...item };
|
|
||||||
if (item.format && !item.alias) {
|
|
||||||
const { alias } = parseField(item.field);
|
|
||||||
dimension.alias = alias;
|
|
||||||
}
|
|
||||||
return dimension;
|
|
||||||
}),
|
|
||||||
measures: (query?.measures || []).map((item: MeasureProps) => {
|
|
||||||
const measure = { ...item };
|
|
||||||
if (item.aggregation && !item.alias) {
|
|
||||||
const { alias } = parseField(item.field);
|
|
||||||
measure.alias = alias;
|
|
||||||
}
|
|
||||||
return measure;
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
const data = res?.data?.data || [];
|
|
||||||
return processData(fields, data, { t });
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
manual: true,
|
|
||||||
onSuccess: (data) => {
|
|
||||||
setData(data);
|
|
||||||
},
|
|
||||||
onFinally(params, data, error: any) {
|
|
||||||
if (!configuring) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (error) {
|
|
||||||
const message = error?.response?.data?.errors?.map?.((error: any) => error.message).join('\n');
|
|
||||||
setQueryData(message || error.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setQueryData(data);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setData([]);
|
|
||||||
const run = async (query: QueryProps) => {
|
|
||||||
if (
|
|
||||||
query?.measures?.length
|
|
||||||
// || (query?.sql?.fields && query?.sql?.clauses)
|
|
||||||
) {
|
|
||||||
await runAsync(query);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (runQuery) {
|
|
||||||
runQuery.current = run;
|
|
||||||
}
|
|
||||||
run(query);
|
|
||||||
}, [query, runAsync, runQuery]);
|
|
||||||
|
|
||||||
const charts = useCharts();
|
const charts = useCharts();
|
||||||
const chart = charts[config?.chartType];
|
const chart = charts[config?.chartType];
|
||||||
@ -137,16 +64,21 @@ export const ChartRenderer: React.FC<{
|
|||||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('Please configure chart')} />
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('Please configure chart')} />
|
||||||
);
|
);
|
||||||
|
|
||||||
return data && data.length ? (
|
if (service.loading) {
|
||||||
<C />
|
return <Spin />;
|
||||||
) : (
|
}
|
||||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('Please configure and run query')} />
|
|
||||||
);
|
if (!(data && data.length)) {
|
||||||
|
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('Please configure and run query')} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <C />;
|
||||||
};
|
};
|
||||||
|
|
||||||
ChartRenderer.Designer = function Designer() {
|
ChartRenderer.Designer = function Designer() {
|
||||||
const { t } = useChartsTranslation();
|
const { t } = useChartsTranslation();
|
||||||
const { setVisible, setCurrent } = useContext(ChartConfigContext);
|
const { setVisible, setCurrent } = useContext(ChartConfigContext);
|
||||||
|
const { service } = useContext(ChartRendererContext);
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const schema = useFieldSchema();
|
const schema = useFieldSchema();
|
||||||
const { insertAdjacent } = useDesignable();
|
const { insertAdjacent } = useDesignable();
|
||||||
@ -156,7 +88,7 @@ ChartRenderer.Designer = function Designer() {
|
|||||||
<SchemaSettings.Item
|
<SchemaSettings.Item
|
||||||
key="configure"
|
key="configure"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrent({ schema, field, collection: name });
|
setCurrent({ schema, field, collection: name, service, data: service?.data });
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { MaybeCollectionProvider } from '@nocobase/client';
|
import { useFieldSchema } from '@formily/react';
|
||||||
|
import { MaybeCollectionProvider, useAPIClient, useRequest } from '@nocobase/client';
|
||||||
import React, { createContext } from 'react';
|
import React, { createContext } from 'react';
|
||||||
|
import { parseField } from '../utils';
|
||||||
|
|
||||||
export type MeasureProps = {
|
export type MeasureProps = {
|
||||||
field: string | string[];
|
field: string | string[];
|
||||||
@ -47,10 +49,62 @@ export type ChartRendererProps = {
|
|||||||
mode?: 'builder' | 'sql';
|
mode?: 'builder' | 'sql';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChartRendererContext = createContext<ChartRendererProps>({} as any);
|
export const ChartRendererContext = createContext<{
|
||||||
|
collection: string;
|
||||||
|
config?: {
|
||||||
|
chartType: string;
|
||||||
|
general: any;
|
||||||
|
advanced: any;
|
||||||
|
};
|
||||||
|
transform?: TransformProps[];
|
||||||
|
service: any;
|
||||||
|
data?: any[];
|
||||||
|
}>({} as any);
|
||||||
|
|
||||||
export const ChartRendererProvider: React.FC<ChartRendererProps> = (props) => {
|
export const ChartRendererProvider: React.FC<ChartRendererProps> = (props) => {
|
||||||
const { collection } = props;
|
const { query, config, collection, transform } = props;
|
||||||
|
const schema = useFieldSchema();
|
||||||
|
const api = useAPIClient();
|
||||||
|
const service = useRequest(
|
||||||
|
(collection, query) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
if (!(collection && query?.measures?.length)) return resolve(undefined);
|
||||||
|
api
|
||||||
|
.request({
|
||||||
|
url: 'charts:query',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
uid: schema?.['x-uid'],
|
||||||
|
collection,
|
||||||
|
...query,
|
||||||
|
dimensions: (query?.dimensions || []).map((item: DimensionProps) => {
|
||||||
|
const dimension = { ...item };
|
||||||
|
if (item.format && !item.alias) {
|
||||||
|
const { alias } = parseField(item.field);
|
||||||
|
dimension.alias = alias;
|
||||||
|
}
|
||||||
|
return dimension;
|
||||||
|
}),
|
||||||
|
measures: (query?.measures || []).map((item: MeasureProps) => {
|
||||||
|
const measure = { ...item };
|
||||||
|
if (item.aggregation && !item.alias) {
|
||||||
|
const { alias } = parseField(item.field);
|
||||||
|
measure.alias = alias;
|
||||||
|
}
|
||||||
|
return measure;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
resolve(res?.data?.data);
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
defaultParams: [collection, query],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MaybeCollectionProvider collection={collection}>
|
<MaybeCollectionProvider collection={collection}>
|
||||||
<div
|
<div
|
||||||
@ -58,12 +112,11 @@ export const ChartRendererProvider: React.FC<ChartRendererProps> = (props) => {
|
|||||||
.ant-card {
|
.ant-card {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
.ant-card-body {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<ChartRendererContext.Provider value={{ ...props }}>{props.children}</ChartRendererContext.Provider>
|
<ChartRendererContext.Provider value={{ collection, config, transform, service }}>
|
||||||
|
{props.children}
|
||||||
|
</ChartRendererContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
</MaybeCollectionProvider>
|
</MaybeCollectionProvider>
|
||||||
);
|
);
|
||||||
|
@ -181,7 +181,7 @@ export const G2PlotLibrary: Charts = {
|
|||||||
'x-reactions': '{{ useChartFields }}',
|
'x-reactions': '{{ useChartFields }}',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
style: {
|
style: {
|
||||||
'min-width': '200px',
|
minWidth: '200px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user