feat(data-vi): support multiple data sources (#3743)
* feat(data-vi): support multiple data sources * chore: update * chore: new pr * chore: update * chore: merge * fix: bug * fix: isDBInstance * fix: fix T-3624 * fix: fix T-3625 * fix: test * fix: fix T-3659 * fix: fix T-3660 * fix: backend tests * fix: acl * fix: fix T-3680 * fix: build
This commit is contained in:
parent
d691e4c7e6
commit
088f3977a2
@ -11,6 +11,7 @@ export interface DataSourceOptions {
|
|||||||
collections?: CollectionOptions[];
|
collections?: CollectionOptions[];
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
status?: 'loaded' | 'loading-failed' | 'loading' | 'reloading';
|
status?: 'loaded' | 'loading-failed' | 'loading' | 'reloading';
|
||||||
|
isDBInstance?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DataSourceFactory = new (options: DataSourceOptions, dataSourceManager: DataSourceManager) => DataSource;
|
export type DataSourceFactory = new (options: DataSourceOptions, dataSourceManager: DataSourceManager) => DataSource;
|
||||||
|
@ -56,13 +56,22 @@ export interface GeneralSchemaDesignerProps {
|
|||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
draggable?: boolean;
|
draggable?: boolean;
|
||||||
|
showDataSource?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated use `SchemaToolbar` instead
|
* @deprecated use `SchemaToolbar` instead
|
||||||
*/
|
*/
|
||||||
export const GeneralSchemaDesigner: FC<GeneralSchemaDesignerProps> = (props: any) => {
|
export const GeneralSchemaDesigner: FC<GeneralSchemaDesignerProps> = (props: any) => {
|
||||||
const { disableInitializer, title, template, schemaSettings, contextValue, draggable = true } = props;
|
const {
|
||||||
|
disableInitializer,
|
||||||
|
title,
|
||||||
|
template,
|
||||||
|
schemaSettings,
|
||||||
|
contextValue,
|
||||||
|
draggable = true,
|
||||||
|
showDataSource = true,
|
||||||
|
} = props;
|
||||||
const { dn, designable } = useDesignable();
|
const { dn, designable } = useDesignable();
|
||||||
const field = useField();
|
const field = useField();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -112,7 +121,9 @@ export const GeneralSchemaDesigner: FC<GeneralSchemaDesignerProps> = (props: any
|
|||||||
<div className={classNames('general-schema-designer-title', titleCss)}>
|
<div className={classNames('general-schema-designer-title', titleCss)}>
|
||||||
<Space size={2}>
|
<Space size={2}>
|
||||||
<span className={'title-tag'}>
|
<span className={'title-tag'}>
|
||||||
{dataSource ? `${compile(dataSource?.displayName)} > ${compile(title)}` : compile(title)}
|
{showDataSource && dataSource
|
||||||
|
? `${compile(dataSource?.displayName)} > ${compile(title)}`
|
||||||
|
: compile(title)}
|
||||||
</span>
|
</span>
|
||||||
{template && (
|
{template && (
|
||||||
<span className={'title-tag'}>
|
<span className={'title-tag'}>
|
||||||
|
@ -15,7 +15,9 @@ import {
|
|||||||
|
|
||||||
describe('hooks', () => {
|
describe('hooks', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.spyOn(client, 'useCollectionManager_deprecated').mockReturnValue({
|
vi.spyOn(client, 'useDataSourceManager').mockReturnValue({
|
||||||
|
getDataSource: () => ({
|
||||||
|
collectionManager: {
|
||||||
getCollectionFields: (name: string) =>
|
getCollectionFields: (name: string) =>
|
||||||
({
|
({
|
||||||
orders: [
|
orders: [
|
||||||
@ -64,7 +66,10 @@ describe('hooks', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})[name],
|
})[name],
|
||||||
getInterface: (i: string) => {
|
},
|
||||||
|
}),
|
||||||
|
collectionFieldInterfaceManager: {
|
||||||
|
getFieldInterface: (i: string) => {
|
||||||
switch (i) {
|
switch (i) {
|
||||||
case 'm2o':
|
case 'm2o':
|
||||||
return {
|
return {
|
||||||
@ -76,6 +81,7 @@ describe('hooks', () => {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
},
|
||||||
} as any);
|
} as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -84,7 +90,7 @@ describe('hooks', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('useFieldsWithAssociation', () => {
|
test('useFieldsWithAssociation', () => {
|
||||||
const { result } = renderHook(() => useFieldsWithAssociation('orders'));
|
const { result } = renderHook(() => useFieldsWithAssociation('main', 'orders'));
|
||||||
expect(result.current).toMatchObject([
|
expect(result.current).toMatchObject([
|
||||||
{
|
{
|
||||||
key: 'name',
|
key: 'name',
|
||||||
@ -118,7 +124,7 @@ describe('hooks', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('useChartFields', () => {
|
test('useChartFields', () => {
|
||||||
const fields = renderHook(() => useFieldsWithAssociation('orders')).result.current;
|
const fields = renderHook(() => useFieldsWithAssociation('main', 'orders')).result.current;
|
||||||
const { result } = renderHook(() => useChartFields(fields));
|
const { result } = renderHook(() => useChartFields(fields));
|
||||||
const func = result.current;
|
const func = result.current;
|
||||||
const field = {
|
const field = {
|
||||||
@ -155,7 +161,7 @@ describe('hooks', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('useFormatters', () => {
|
test('useFormatters', () => {
|
||||||
const fields = renderHook(() => useFieldsWithAssociation('orders')).result.current;
|
const fields = renderHook(() => useFieldsWithAssociation('main', 'orders')).result.current;
|
||||||
const { result } = renderHook(() => useFormatters(fields));
|
const { result } = renderHook(() => useFormatters(fields));
|
||||||
const func = result.current;
|
const func = result.current;
|
||||||
const field = {
|
const field = {
|
||||||
@ -169,7 +175,7 @@ describe('hooks', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('useFieldTypes', () => {
|
test('useFieldTypes', () => {
|
||||||
const fields = renderHook(() => useFieldsWithAssociation('orders')).result.current;
|
const fields = renderHook(() => useFieldsWithAssociation('main', 'orders')).result.current;
|
||||||
const { result } = renderHook(() => useFieldTypes(fields));
|
const { result } = renderHook(() => useFieldTypes(fields));
|
||||||
const func = result.current;
|
const func = result.current;
|
||||||
let state1 = {};
|
let state1 = {};
|
||||||
@ -234,7 +240,7 @@ describe('hooks', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('useOrderFieldsOptions', () => {
|
test('useOrderFieldsOptions', () => {
|
||||||
const fields = renderHook(() => useFieldsWithAssociation('orders')).result.current;
|
const fields = renderHook(() => useFieldsWithAssociation('main', 'orders')).result.current;
|
||||||
const { result } = renderHook(() => useOrderFieldsOptions([], fields));
|
const { result } = renderHook(() => useOrderFieldsOptions([], fields));
|
||||||
const func = result.current;
|
const func = result.current;
|
||||||
const field1 = {
|
const field1 = {
|
||||||
|
@ -10,7 +10,7 @@ import { useChartsTranslation } from '../locale';
|
|||||||
export const ChartV2BlockDesigner: React.FC = () => {
|
export const ChartV2BlockDesigner: React.FC = () => {
|
||||||
const { t } = useChartsTranslation();
|
const { t } = useChartsTranslation();
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner title={t('Charts')}>
|
<GeneralSchemaDesigner title={t('Charts')} showDataSource={false}>
|
||||||
<SchemaSettingsBlockTitleItem />
|
<SchemaSettingsBlockTitleItem />
|
||||||
<SchemaSettingsDivider />
|
<SchemaSettingsDivider />
|
||||||
<SchemaSettingsRemove
|
<SchemaSettingsRemove
|
||||||
|
@ -2,6 +2,7 @@ import { BarChartOutlined, LineChartOutlined } from '@ant-design/icons';
|
|||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import {
|
import {
|
||||||
CompatibleSchemaInitializer,
|
CompatibleSchemaInitializer,
|
||||||
|
DEFAULT_DATA_SOURCE_KEY,
|
||||||
DataBlockInitializer,
|
DataBlockInitializer,
|
||||||
SchemaInitializerItem,
|
SchemaInitializerItem,
|
||||||
useACLRoleContext,
|
useACLRoleContext,
|
||||||
@ -15,20 +16,23 @@ import { lang } from '../locale';
|
|||||||
|
|
||||||
const ChartInitializer = () => {
|
const ChartInitializer = () => {
|
||||||
const { setVisible, setCurrent } = useContext(ChartConfigContext);
|
const { setVisible, setCurrent } = useContext(ChartConfigContext);
|
||||||
const { allowAll, parseAction } = useACLRoleContext();
|
const { parseAction } = useACLRoleContext();
|
||||||
const itemConfig = useSchemaInitializerItem();
|
const itemConfig = useSchemaInitializerItem();
|
||||||
const filter = useCallback(
|
const filter = useCallback(
|
||||||
(item) => {
|
(item) => {
|
||||||
const params = parseAction(`${item.name}:list`);
|
const params = parseAction(`${item.name}:list`);
|
||||||
return params;
|
return params;
|
||||||
},
|
},
|
||||||
[allowAll, parseAction],
|
[parseAction],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataBlockInitializer
|
<DataBlockInitializer
|
||||||
{...itemConfig}
|
{...itemConfig}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
|
filterDataSource={(ds) => {
|
||||||
|
return ds.key === DEFAULT_DATA_SOURCE_KEY || ds.getOptions().isDBInstance;
|
||||||
|
}}
|
||||||
icon={<BarChartOutlined />}
|
icon={<BarChartOutlined />}
|
||||||
componentType={'Chart'}
|
componentType={'Chart'}
|
||||||
onCreateBlockSchema={async ({ item }) => {
|
onCreateBlockSchema={async ({ item }) => {
|
||||||
|
@ -2,6 +2,7 @@ import React, { createContext, useState } from 'react';
|
|||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
|
|
||||||
type ChartData = {
|
type ChartData = {
|
||||||
|
dataSource: string;
|
||||||
collection: string;
|
collection: string;
|
||||||
service: any;
|
service: any;
|
||||||
query: any;
|
query: any;
|
||||||
@ -18,8 +19,8 @@ export const ChartDataProvider: React.FC = (props) => {
|
|||||||
const [charts, setCharts] = useState<{
|
const [charts, setCharts] = useState<{
|
||||||
[uid: string]: ChartData;
|
[uid: string]: ChartData;
|
||||||
}>({});
|
}>({});
|
||||||
const addChart = useMemoizedFn((uid: string, { collection, service, query }: ChartData) => {
|
const addChart = useMemoizedFn((uid: string, { dataSource, collection, service, query }: ChartData) => {
|
||||||
setCharts((charts) => ({ ...charts, [uid]: { collection, service, query } }));
|
setCharts((charts) => ({ ...charts, [uid]: { dataSource, collection, service, query } }));
|
||||||
});
|
});
|
||||||
const removeChart = useMemoizedFn((uid: string) => {
|
const removeChart = useMemoizedFn((uid: string) => {
|
||||||
setCharts((charts) => ({ ...charts, [uid]: undefined }));
|
setCharts((charts) => ({ ...charts, [uid]: undefined }));
|
||||||
|
@ -34,14 +34,14 @@ export class Table extends AntdChart {
|
|||||||
});
|
});
|
||||||
const pageSize = advanced?.pagination?.pageSize || 10;
|
const pageSize = advanced?.pagination?.pageSize || 10;
|
||||||
return {
|
return {
|
||||||
bordered: true,
|
// bordered: true,
|
||||||
size: 'middle',
|
size: 'middle',
|
||||||
// pagination:
|
pagination:
|
||||||
// dataSource.length < pageSize
|
dataSource.length < pageSize
|
||||||
// ? false
|
? false
|
||||||
// : {
|
: {
|
||||||
// pageSize,
|
pageSize,
|
||||||
// },
|
},
|
||||||
dataSource,
|
dataSource,
|
||||||
columns,
|
columns,
|
||||||
scroll: {
|
scroll: {
|
||||||
|
@ -35,11 +35,11 @@ export class G2PlotChart extends Chart {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
tooltip: (d, index: number, data, column: any) => {
|
tooltip: (d, index: number, data, column: any) => {
|
||||||
const field = column.y.field;
|
const field = column.y?.field;
|
||||||
const props = fieldProps[field];
|
const props = fieldProps[field];
|
||||||
const name = props?.label || field;
|
const name = props?.label || field;
|
||||||
const transformer = props?.transformer;
|
const transformer = props?.transformer;
|
||||||
const value = column.y.value[index];
|
const value = column.y?.value[index];
|
||||||
return { name, value: transformer ? transformer(value) : value };
|
return { name, value: transformer ? transformer(value) : value };
|
||||||
},
|
},
|
||||||
axis: {
|
axis: {
|
||||||
|
@ -2,21 +2,15 @@ import { RightSquareOutlined } from '@ant-design/icons';
|
|||||||
import { ArrayItems, Editable, FormCollapse, FormItem, FormLayout, Switch } from '@formily/antd-v5';
|
import { ArrayItems, Editable, FormCollapse, FormItem, FormLayout, Switch } from '@formily/antd-v5';
|
||||||
import { Form as FormType, ObjectField, createForm, onFieldChange, onFormInit } from '@formily/core';
|
import { Form as FormType, ObjectField, createForm, onFieldChange, onFormInit } from '@formily/core';
|
||||||
import { FormConsumer, ISchema, Schema } from '@formily/react';
|
import { FormConsumer, ISchema, Schema } from '@formily/react';
|
||||||
import {
|
import { AutoComplete, FormProvider, SchemaComponent, gridRowColWrap, useDesignable } from '@nocobase/client';
|
||||||
AutoComplete,
|
|
||||||
FormProvider,
|
|
||||||
SchemaComponent,
|
|
||||||
gridRowColWrap,
|
|
||||||
useCollectionFieldsOptions,
|
|
||||||
useCollectionFilterOptions,
|
|
||||||
useDesignable,
|
|
||||||
} from '@nocobase/client';
|
|
||||||
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, useEffect, useMemo, useRef } from 'react';
|
import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
useChartFields,
|
useChartFields,
|
||||||
useCollectionOptions,
|
useCollectionOptions,
|
||||||
|
useCollectionFieldsOptions,
|
||||||
|
useCollectionFilterOptions,
|
||||||
useData,
|
useData,
|
||||||
useFieldTypes,
|
useFieldTypes,
|
||||||
useFieldsWithAssociation,
|
useFieldsWithAssociation,
|
||||||
@ -57,13 +51,13 @@ export const ChartConfigure: React.FC<{
|
|||||||
const { t } = useChartsTranslation();
|
const { t } = useChartsTranslation();
|
||||||
const { service } = useContext(ChartRendererContext);
|
const { service } = useContext(ChartRendererContext);
|
||||||
const { visible, setVisible, current } = useContext(ChartConfigContext);
|
const { visible, setVisible, current } = useContext(ChartConfigContext);
|
||||||
const { schema, field, collection, initialValues } = current || {};
|
const { schema, field, dataSource, collection, initialValues } = current || {};
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const { modal } = App.useApp();
|
const { modal } = App.useApp();
|
||||||
const { insert } = props;
|
const { insert } = props;
|
||||||
|
|
||||||
const charts = useCharts();
|
const charts = useCharts();
|
||||||
const fields = useFieldsWithAssociation(collection);
|
const fields = useFieldsWithAssociation(dataSource, collection);
|
||||||
const initChart = (overwrite = false) => {
|
const initChart = (overwrite = false) => {
|
||||||
if (!form.modified) {
|
if (!form.modified) {
|
||||||
return;
|
return;
|
||||||
@ -108,15 +102,19 @@ export const ChartConfigure: React.FC<{
|
|||||||
const form = useMemo(
|
const form = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createForm({
|
createForm({
|
||||||
values: { config: { chartType }, ...(initialValues || field?.decoratorProps), collection },
|
values: {
|
||||||
|
config: { chartType },
|
||||||
|
...(initialValues || field?.decoratorProps),
|
||||||
|
collection: [dataSource, 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, dataSource, collection added here to re-initialize form when visible, dataSource, collection change
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[field, visible, collection],
|
[field, visible, dataSource, collection],
|
||||||
);
|
);
|
||||||
|
|
||||||
const RunButton: React.FC = () => (
|
const RunButton: React.FC = () => (
|
||||||
@ -133,7 +131,7 @@ export const ChartConfigure: React.FC<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await service.runAsync(collection, form.values.query, true);
|
await service.runAsync(dataSource, collection, form.values.query, true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
@ -163,7 +161,7 @@ export const ChartConfigure: React.FC<{
|
|||||||
const { query, config, transform, mode } = form.values;
|
const { query, config, transform, mode } = form.values;
|
||||||
const afterSave = () => {
|
const afterSave = () => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
current.service?.run(collection, query);
|
current.service?.run(dataSource, collection, query);
|
||||||
queryRef.current.scrollTop = 0;
|
queryRef.current.scrollTop = 0;
|
||||||
configRef.current.scrollTop = 0;
|
configRef.current.scrollTop = 0;
|
||||||
service.mutate(undefined);
|
service.mutate(undefined);
|
||||||
@ -171,6 +169,7 @@ export const ChartConfigure: React.FC<{
|
|||||||
const rendererProps = {
|
const rendererProps = {
|
||||||
query,
|
query,
|
||||||
config,
|
config,
|
||||||
|
dataSource,
|
||||||
collection,
|
collection,
|
||||||
transform,
|
transform,
|
||||||
mode: mode || 'builder',
|
mode: mode || 'builder',
|
||||||
@ -308,19 +307,20 @@ ChartConfigure.Query = function Query() {
|
|||||||
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 { dataSource, collection } = current || {};
|
||||||
const fieldOptions = useCollectionFieldsOptions(collection, 1);
|
const fieldOptions = useCollectionFieldsOptions(dataSource, collection, 1);
|
||||||
const compiledFieldOptions = Schema.compile(fieldOptions, { t });
|
const compiledFieldOptions = Schema.compile(fieldOptions, { t });
|
||||||
const filterOptions = useCollectionFilterOptions(collection);
|
const filterOptions = useCollectionFilterOptions(dataSource, collection);
|
||||||
|
|
||||||
const { service } = useContext(ChartRendererContext);
|
const { service } = useContext(ChartRendererContext);
|
||||||
const onCollectionChange = (value: string) => {
|
const onCollectionChange = (value: string[]) => {
|
||||||
const { schema, field } = current;
|
const { schema, field } = current;
|
||||||
|
const [dataSource, collection] = value;
|
||||||
setCurrent({
|
setCurrent({
|
||||||
schema,
|
schema,
|
||||||
field,
|
field,
|
||||||
collection: value,
|
collection,
|
||||||
dataSource: current.dataSource,
|
dataSource,
|
||||||
service: current.service,
|
service: current.service,
|
||||||
initialValues: {},
|
initialValues: {},
|
||||||
data: undefined,
|
data: undefined,
|
||||||
|
@ -150,9 +150,9 @@ export const querySchema: ISchema = {
|
|||||||
title: '{{t("Collection")}}',
|
title: '{{t("Collection")}}',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component': 'Select',
|
'x-component': 'Cascader',
|
||||||
|
enum: '{{ collectionOptions }}',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
options: '{{ collectionOptions }}',
|
|
||||||
onChange: '{{ onCollectionChange }}',
|
onChange: '{{ onCollectionChange }}',
|
||||||
placeholder: '{{t("Collection")}}',
|
placeholder: '{{t("Collection")}}',
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { InitializerWithSwitch, useSchemaInitializerItem } from '@nocobase/client';
|
||||||
|
import { ISchema } from '@formily/react';
|
||||||
|
|
||||||
|
export const CollectionFieldInitializer = () => {
|
||||||
|
const schema: ISchema = {};
|
||||||
|
const itemConfig = useSchemaInitializerItem();
|
||||||
|
return <InitializerWithSwitch {...itemConfig} item={itemConfig} schema={schema} type={'name'} />;
|
||||||
|
};
|
@ -5,7 +5,7 @@ import { useChartsTranslation } from '../locale';
|
|||||||
export const ChartFilterBlockDesigner: React.FC = () => {
|
export const ChartFilterBlockDesigner: React.FC = () => {
|
||||||
const { t } = useChartsTranslation();
|
const { t } = useChartsTranslation();
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner disableInitializer title={t('Filter')}>
|
<GeneralSchemaDesigner disableInitializer title={t('Filter')} showDataSource={false}>
|
||||||
{/* <SchemaSettings.BlockTitleItem /> */}
|
{/* <SchemaSettings.BlockTitleItem /> */}
|
||||||
{/* <SchemaSettings.Divider /> */}
|
{/* <SchemaSettings.Divider /> */}
|
||||||
<SchemaSettingsRemove
|
<SchemaSettingsRemove
|
||||||
|
@ -16,6 +16,7 @@ import { ChartFilterCheckbox } from './FilterCheckbox';
|
|||||||
import { ArrayItems } from '@formily/antd-v5';
|
import { ArrayItems } from '@formily/antd-v5';
|
||||||
import { ChartFilterFormItem } from './FilterItemInitializers';
|
import { ChartFilterFormItem } from './FilterItemInitializers';
|
||||||
import { ChartFilterForm } from './FilterForm';
|
import { ChartFilterForm } from './FilterForm';
|
||||||
|
import { CollectionFieldInitializer } from './CollectionFieldInitializer';
|
||||||
|
|
||||||
export const ChartFilterBlockProvider: React.FC = (props) => {
|
export const ChartFilterBlockProvider: React.FC = (props) => {
|
||||||
const { t } = useChartsTranslation();
|
const { t } = useChartsTranslation();
|
||||||
@ -43,6 +44,7 @@ export const ChartFilterBlockProvider: React.FC = (props) => {
|
|||||||
ArrayItems,
|
ArrayItems,
|
||||||
ChartFilterCollapseDesigner,
|
ChartFilterCollapseDesigner,
|
||||||
ChartFilterActionDesigner,
|
ChartFilterActionDesigner,
|
||||||
|
CollectionFieldInitializer,
|
||||||
}}
|
}}
|
||||||
scope={{ t, useChartFilterActionProps, useChartFilterResetProps, useChartFilterCollapseProps }}
|
scope={{ t, useChartFilterActionProps, useChartFilterResetProps, useChartFilterCollapseProps }}
|
||||||
>
|
>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { memo, useContext, useEffect, useMemo, useRef } from 'react';
|
import React, { memo, useContext, useEffect, useMemo, useRef } from 'react';
|
||||||
import { createForm, onFieldInit, onFieldMount, onFieldUnmount } from '@formily/core';
|
import { createForm, onFieldInit, onFieldMount, onFieldUnmount } from '@formily/core';
|
||||||
import { ChartFilterContext } from './FilterProvider';
|
import { ChartFilterContext } from './FilterProvider';
|
||||||
import { FormV2, VariablesContext } from '@nocobase/client';
|
import { DEFAULT_DATA_SOURCE_KEY, FormV2, VariablesContext } from '@nocobase/client';
|
||||||
import { setDefaultValue } from './utils';
|
import { setDefaultValue } from './utils';
|
||||||
import { useChartFilter } from '../hooks';
|
import { useChartFilter } from '../hooks';
|
||||||
|
|
||||||
@ -33,7 +33,10 @@ export const ChartFilterForm: React.FC = memo((props) => {
|
|||||||
if (!name) {
|
if (!name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setField(name, { title: field.title, operator: field.componentProps['filter-operator'] });
|
setField(name, {
|
||||||
|
title: field.title,
|
||||||
|
operator: field.componentProps['filter-operator'],
|
||||||
|
});
|
||||||
|
|
||||||
// parse field title
|
// parse field title
|
||||||
if (field.title.includes('/')) {
|
if (field.title.includes('/')) {
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
useDesignable,
|
useDesignable,
|
||||||
SchemaSettingsSelectItem,
|
SchemaSettingsSelectItem,
|
||||||
CollectionFieldOptions_deprecated,
|
CollectionFieldOptions_deprecated,
|
||||||
|
DEFAULT_DATA_SOURCE_KEY,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { useChartsTranslation } from '../locale';
|
import { useChartsTranslation } from '../locale';
|
||||||
import { Schema, useField, useFieldSchema } from '@formily/react';
|
import { Schema, useField, useFieldSchema } from '@formily/react';
|
||||||
@ -21,7 +22,7 @@ import _ from 'lodash';
|
|||||||
import { ChartFilterContext } from './FilterProvider';
|
import { ChartFilterContext } from './FilterProvider';
|
||||||
import { getPropsSchemaByComponent, setDefaultValue } from './utils';
|
import { getPropsSchemaByComponent, setDefaultValue } from './utils';
|
||||||
import { ChartFilterVariableInput } from './FilterVariableInput';
|
import { ChartFilterVariableInput } from './FilterVariableInput';
|
||||||
import { useChartFilter, useCollectionJoinFieldTitle } from '../hooks';
|
import { useChartDataSource, useChartFilter, useCollectionJoinFieldTitle } from '../hooks';
|
||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
import { getFormulaInterface } from '../utils';
|
import { getFormulaInterface } from '../utils';
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
@ -72,29 +73,33 @@ const EditTitle = () => {
|
|||||||
const EditOperator = () => {
|
const EditOperator = () => {
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const fieldName = fieldSchema.name as string;
|
|
||||||
const field = useField<Field>();
|
const field = useField<Field>();
|
||||||
const { t } = useChartsTranslation();
|
const { t } = useChartsTranslation();
|
||||||
const { dn } = useDesignable();
|
const { dn } = useDesignable();
|
||||||
const { setField } = useContext(ChartFilterContext);
|
const { setField } = useContext(ChartFilterContext);
|
||||||
const { getInterface, getCollectionJoinField } = useCollectionManager_deprecated();
|
const fieldName = fieldSchema['x-collection-field'];
|
||||||
|
const dataSource = fieldSchema['x-data-source'] || DEFAULT_DATA_SOURCE_KEY;
|
||||||
|
const { cm, fim } = useChartDataSource(dataSource);
|
||||||
|
if (!cm) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const getOperators = (props: CollectionFieldOptions_deprecated) => {
|
const getOperators = (props: CollectionFieldOptions_deprecated) => {
|
||||||
let fieldInterface = props?.interface;
|
let fieldInterface = props?.interface;
|
||||||
if (fieldInterface === 'formula') {
|
if (fieldInterface === 'formula') {
|
||||||
fieldInterface = getFormulaInterface(props.dataType) || props.dataType;
|
fieldInterface = getFormulaInterface(props.dataType) || props.dataType;
|
||||||
}
|
}
|
||||||
const interfaceConfig = getInterface(fieldInterface);
|
const interfaceConfig = fim.getFieldInterface(fieldInterface);
|
||||||
const operatorList = interfaceConfig?.filterable?.operators || [];
|
const operatorList = interfaceConfig?.filterable?.operators || [];
|
||||||
return { operatorList, interfaceConfig };
|
return { operatorList, interfaceConfig };
|
||||||
};
|
};
|
||||||
|
|
||||||
let props = getCollectionJoinField(fieldName);
|
let props = cm.getCollectionField(fieldName);
|
||||||
let { operatorList, interfaceConfig } = getOperators(props);
|
let { operatorList, interfaceConfig } = getOperators(props);
|
||||||
if (!operatorList.length) {
|
if (!operatorList.length) {
|
||||||
const names = fieldName.split('.');
|
const names = fieldName.split('.');
|
||||||
const name = names.pop();
|
const name = names.pop();
|
||||||
props = getCollectionJoinField(names.join('.'));
|
props = cm.getCollectionField(names.join('.'));
|
||||||
if (!props) {
|
if (!props) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -159,7 +164,7 @@ const EditOperator = () => {
|
|||||||
setOperatorComponent(operator, defaultComponent);
|
setOperatorComponent(operator, defaultComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
setField(fieldName, { operator });
|
setField(fieldSchema.name as string, { operator });
|
||||||
dn.refresh();
|
dn.refresh();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -300,10 +305,11 @@ export const ChartFilterItemDesigner: React.FC = () => {
|
|||||||
const { t } = useChartsTranslation();
|
const { t } = useChartsTranslation();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const fieldName = fieldSchema.name as string;
|
const fieldName = fieldSchema.name as string;
|
||||||
|
const dataSource = fieldSchema['x-data-source'] || DEFAULT_DATA_SOURCE_KEY;
|
||||||
const collectionField = getField(fieldName) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
const collectionField = getField(fieldName) || getCollectionJoinField(fieldSchema['x-collection-field']);
|
||||||
const isCustom = fieldName.startsWith('custom.');
|
const isCustom = fieldName.startsWith('custom.');
|
||||||
const hasProps = getPropsSchemaByComponent(fieldSchema['x-component']);
|
const hasProps = getPropsSchemaByComponent(fieldSchema['x-component']);
|
||||||
const originalTitle = useCollectionJoinFieldTitle(fieldName);
|
const originalTitle = useCollectionJoinFieldTitle(dataSource, fieldName);
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner disableInitializer>
|
<GeneralSchemaDesigner disableInitializer>
|
||||||
{!isCustom && (
|
{!isCustom && (
|
||||||
|
@ -7,7 +7,9 @@ import {
|
|||||||
ACLCollectionFieldProvider,
|
ACLCollectionFieldProvider,
|
||||||
BlockItem,
|
BlockItem,
|
||||||
CollectionFieldProvider,
|
CollectionFieldProvider,
|
||||||
|
CollectionManagerProvider,
|
||||||
CollectionProvider,
|
CollectionProvider,
|
||||||
|
DEFAULT_DATA_SOURCE_KEY,
|
||||||
CompatibleSchemaInitializer,
|
CompatibleSchemaInitializer,
|
||||||
FormDialog,
|
FormDialog,
|
||||||
HTMLEncode,
|
HTMLEncode,
|
||||||
@ -16,6 +18,7 @@ import {
|
|||||||
SchemaInitializerItem,
|
SchemaInitializerItem,
|
||||||
gridRowColWrap,
|
gridRowColWrap,
|
||||||
useCollectionManager_deprecated,
|
useCollectionManager_deprecated,
|
||||||
|
useDataSourceManager,
|
||||||
useDesignable,
|
useDesignable,
|
||||||
useGlobalTheme,
|
useGlobalTheme,
|
||||||
useSchemaInitializerItem,
|
useSchemaInitializerItem,
|
||||||
@ -50,6 +53,7 @@ const ErrorFallback = ({ error }) => {
|
|||||||
|
|
||||||
export const ChartFilterFormItem = observer(
|
export const ChartFilterFormItem = observer(
|
||||||
(props: any) => {
|
(props: any) => {
|
||||||
|
const { t } = useChartsTranslation();
|
||||||
const field = useField<Field>();
|
const field = useField<Field>();
|
||||||
const schema = useFieldSchema();
|
const schema = useFieldSchema();
|
||||||
const showTitle = schema['x-decorator-props']?.showTitle ?? true;
|
const showTitle = schema['x-decorator-props']?.showTitle ?? true;
|
||||||
@ -80,21 +84,31 @@ export const ChartFilterFormItem = observer(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}, [showTitle]);
|
}, [showTitle]);
|
||||||
|
const dataSource = schema?.['x-data-source'] || DEFAULT_DATA_SOURCE_KEY;
|
||||||
const collectionField = schema?.['x-collection-field'] || '';
|
const collectionField = schema?.['x-collection-field'] || '';
|
||||||
const [collection] = collectionField.split('.');
|
const [collection] = collectionField.split('.');
|
||||||
|
// const { getIsChartCollectionExists } = useChartData();
|
||||||
|
// const exists = (schema.name as string).startsWith('custom.') || getIsChartCollectionExists(dataSource, collection);
|
||||||
return (
|
return (
|
||||||
|
<BlockItem className={'nb-form-item'}>
|
||||||
|
<CollectionManagerProvider dataSource={dataSource}>
|
||||||
<CollectionProvider name={collection} allowNull={!collection}>
|
<CollectionProvider name={collection} allowNull={!collection}>
|
||||||
<CollectionFieldProvider name={schema.name} allowNull={!schema['x-collection-field']}>
|
<CollectionFieldProvider name={schema.name} allowNull={!schema['x-collection-field']}>
|
||||||
<ACLCollectionFieldProvider>
|
<ACLCollectionFieldProvider>
|
||||||
<BlockItem className={'nb-form-item'}>
|
{/* {exists ? ( */}
|
||||||
<ErrorBoundary onError={(err) => console.log(err)} FallbackComponent={ErrorFallback}>
|
<ErrorBoundary onError={(err) => console.log(err)} FallbackComponent={ErrorFallback}>
|
||||||
<FormItem className={className} {...props} extra={extra} />
|
<FormItem className={className} {...props} extra={extra} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</BlockItem>
|
{/* ) : ( */}
|
||||||
|
{/* <div style={{ color: '#ccc', marginBottom: '10px' }}> */}
|
||||||
|
{/* {t('The chart using the collection of this field have been deleted. Please remove this field.')} */}
|
||||||
|
{/* </div> */}
|
||||||
|
{/* )} */}
|
||||||
</ACLCollectionFieldProvider>
|
</ACLCollectionFieldProvider>
|
||||||
</CollectionFieldProvider>
|
</CollectionFieldProvider>
|
||||||
</CollectionProvider>
|
</CollectionProvider>
|
||||||
|
</CollectionManagerProvider>
|
||||||
|
</BlockItem>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
{ displayName: 'ChartFilterFormItem' },
|
{ displayName: 'ChartFilterFormItem' },
|
||||||
@ -110,7 +124,7 @@ export const ChartFilterCustomItemInitializer: React.FC<{
|
|||||||
const { theme } = useGlobalTheme();
|
const { theme } = useGlobalTheme();
|
||||||
const { insert } = props;
|
const { insert } = props;
|
||||||
const itemConfig = useSchemaInitializerItem();
|
const itemConfig = useSchemaInitializerItem();
|
||||||
const { getCollectionJoinField, getInterface } = useCollectionManager_deprecated();
|
const dm = useDataSourceManager();
|
||||||
const sourceFields = useChartFilterSourceFields();
|
const sourceFields = useChartFilterSourceFields();
|
||||||
const { options: fieldComponents, values: fieldComponentValues } = useFieldComponents();
|
const { options: fieldComponents, values: fieldComponentValues } = useFieldComponents();
|
||||||
const handleClick = useCallback(async () => {
|
const handleClick = useCallback(async () => {
|
||||||
@ -177,8 +191,17 @@ export const ChartFilterCustomItemInitializer: React.FC<{
|
|||||||
},
|
},
|
||||||
effects() {
|
effects() {
|
||||||
onFieldValueChange('source', (field) => {
|
onFieldValueChange('source', (field) => {
|
||||||
const name = field.value?.join('.');
|
if (!field.value) {
|
||||||
const props = getCollectionJoinField(name);
|
return;
|
||||||
|
}
|
||||||
|
const [dataSource, ...fields] = field.value;
|
||||||
|
const ds = dm.getDataSource(dataSource);
|
||||||
|
if (!ds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cm = ds.collectionManager;
|
||||||
|
const name = fields.join('.');
|
||||||
|
const props = cm.getCollectionField(name);
|
||||||
if (!props) {
|
if (!props) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -204,7 +227,8 @@ export const ChartFilterCustomItemInitializer: React.FC<{
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const { name, title, component, props } = values;
|
const { name, title, component, props } = values;
|
||||||
const defaultSchema = getInterface(component)?.default?.uiSchema || {};
|
const fim = dm.collectionFieldInterfaceManager;
|
||||||
|
const defaultSchema = fim.getFieldInterface(component)?.default?.uiSchema || {};
|
||||||
insert(
|
insert(
|
||||||
gridRowColWrap({
|
gridRowColWrap({
|
||||||
'x-component': component,
|
'x-component': component,
|
||||||
@ -227,58 +251,7 @@ export const ChartFilterCustomItemInitializer: React.FC<{
|
|||||||
});
|
});
|
||||||
ChartFilterCustomItemInitializer.displayName = 'ChartFilterCustomItemInitializer';
|
ChartFilterCustomItemInitializer.displayName = 'ChartFilterCustomItemInitializer';
|
||||||
|
|
||||||
/**
|
const filterItemInitializers = {
|
||||||
* @deprecated
|
|
||||||
*/
|
|
||||||
export const chartFilterItemInitializers_deprecated = new CompatibleSchemaInitializer({
|
|
||||||
name: 'ChartFilterItemInitializers',
|
|
||||||
'data-testid': 'configure-fields-button-of-chart-filter-item',
|
|
||||||
wrap: gridRowColWrap,
|
|
||||||
icon: 'SettingOutlined',
|
|
||||||
title: '{{ t("Configure fields") }}',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
type: 'itemGroup',
|
|
||||||
name: 'displayFields',
|
|
||||||
title: '{{ t("Display fields") }}',
|
|
||||||
useChildren: () => {
|
|
||||||
const { getCollection } = useCollectionManager_deprecated();
|
|
||||||
const { getChartCollections } = useChartData();
|
|
||||||
const { getChartFilterFields } = useChartFilter();
|
|
||||||
const collections = getChartCollections();
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
return collections.map((name: any) => {
|
|
||||||
const collection = getCollection(name);
|
|
||||||
const fields = getChartFilterFields(collection);
|
|
||||||
return {
|
|
||||||
name: collection.key,
|
|
||||||
type: 'subMenu',
|
|
||||||
title: collection.title,
|
|
||||||
children: fields,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, [collections]);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'divider',
|
|
||||||
type: 'divider',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'custom',
|
|
||||||
type: 'item',
|
|
||||||
title: lang('Custom'),
|
|
||||||
Component: () => {
|
|
||||||
const { insertAdjacent } = useDesignable();
|
|
||||||
return <ChartFilterCustomItemInitializer insert={(s: Schema) => insertAdjacent('beforeEnd', s)} />;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const chartFilterItemInitializers = new CompatibleSchemaInitializer(
|
|
||||||
{
|
|
||||||
name: 'chartFilterForm:configureFields',
|
name: 'chartFilterForm:configureFields',
|
||||||
'data-testid': 'configure-fields-button-of-chart-filter-item',
|
'data-testid': 'configure-fields-button-of-chart-filter-item',
|
||||||
wrap: gridRowColWrap,
|
wrap: gridRowColWrap,
|
||||||
@ -290,23 +263,34 @@ export const chartFilterItemInitializers = new CompatibleSchemaInitializer(
|
|||||||
name: 'displayFields',
|
name: 'displayFields',
|
||||||
title: '{{ t("Display fields") }}',
|
title: '{{ t("Display fields") }}',
|
||||||
useChildren: () => {
|
useChildren: () => {
|
||||||
const { getCollection } = useCollectionManager_deprecated();
|
const { t } = useChartsTranslation();
|
||||||
const { getChartCollections } = useChartData();
|
const { chartCollections, showDataSource } = useChartData();
|
||||||
const { getChartFilterFields } = useChartFilter();
|
const { getChartFilterFields } = useChartFilter();
|
||||||
const collections = getChartCollections();
|
const dm = useDataSourceManager();
|
||||||
|
const fim = dm.collectionFieldInterfaceManager;
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return collections.map((name: any) => {
|
const options = Object.entries(chartCollections).map(([dataSource, collections]) => {
|
||||||
const collection = getCollection(name);
|
const ds = dm.getDataSource(dataSource);
|
||||||
const fields = getChartFilterFields(collection);
|
return {
|
||||||
|
name: ds.key,
|
||||||
|
title: Schema.compile(ds.displayName, { t }),
|
||||||
|
type: 'subMenu',
|
||||||
|
children: collections.map((name) => {
|
||||||
|
const cm = ds.collectionManager;
|
||||||
|
const collection = cm.getCollection(name);
|
||||||
|
const fields = getChartFilterFields({ dataSource, collection, cm, fim });
|
||||||
return {
|
return {
|
||||||
name: collection.key,
|
name: collection.key,
|
||||||
|
title: Schema.compile(collection.title, { t }),
|
||||||
type: 'subMenu',
|
type: 'subMenu',
|
||||||
title: collection.title,
|
|
||||||
children: fields,
|
children: fields,
|
||||||
};
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}, [collections]);
|
return showDataSource ? options : options[0]?.children || [];
|
||||||
|
}, [chartCollections, showDataSource]);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -323,6 +307,17 @@ export const chartFilterItemInitializers = new CompatibleSchemaInitializer(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
export const chartFilterItemInitializers_deprecated = new CompatibleSchemaInitializer({
|
||||||
|
...filterItemInitializers,
|
||||||
|
name: 'ChartFilterItemInitializers',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const chartFilterItemInitializers = new CompatibleSchemaInitializer(
|
||||||
|
filterItemInitializers,
|
||||||
chartFilterItemInitializers_deprecated,
|
chartFilterItemInitializers_deprecated,
|
||||||
);
|
);
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
import React, { createContext, useEffect, useState } from 'react';
|
import React, { createContext, useEffect, useState } from 'react';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn } from 'ahooks';
|
||||||
|
|
||||||
|
type FilterField = {
|
||||||
|
title?: string;
|
||||||
|
operator?: {
|
||||||
|
value: string;
|
||||||
|
noValue?: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const ChartFilterContext = createContext<{
|
export const ChartFilterContext = createContext<{
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
setEnabled: (enabled: boolean) => void;
|
setEnabled: (enabled: boolean) => void;
|
||||||
fields: {
|
fields: {
|
||||||
[name: string]: {
|
[name: string]: FilterField;
|
||||||
title: string;
|
|
||||||
operator?: {
|
|
||||||
value: string;
|
|
||||||
noValue?: boolean;
|
|
||||||
};
|
};
|
||||||
};
|
setField: (name: string, field: FilterField) => void;
|
||||||
};
|
|
||||||
setField: (name: string, field: { title?: string; operator?: string }) => void;
|
|
||||||
removeField: (name: string) => void;
|
removeField: (name: string) => void;
|
||||||
collapse: {
|
collapse: {
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
@ -32,7 +34,7 @@ export const ChartFilterProvider: React.FC = (props) => {
|
|||||||
const [fields, setFields] = useState({});
|
const [fields, setFields] = useState({});
|
||||||
const [collapse, _setCollapse] = useState({ collapsed: false, row: 1 });
|
const [collapse, _setCollapse] = useState({ collapsed: false, row: 1 });
|
||||||
const [form, _setForm] = useState<any>();
|
const [form, _setForm] = useState<any>();
|
||||||
const setField = useMemoizedFn((name: string, props: { title?: string; operator?: string }) => {
|
const setField = useMemoizedFn((name: string, props: FilterField) => {
|
||||||
setFields((fields) => ({
|
setFields((fields) => ({
|
||||||
...fields,
|
...fields,
|
||||||
[name]: {
|
[name]: {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import { DEFAULT_DATA_SOURCE_KEY } from '@nocobase/client';
|
||||||
import { moment2str } from '@nocobase/utils/client';
|
import { moment2str } from '@nocobase/utils/client';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { Schema } from '@formily/react';
|
||||||
|
|
||||||
export const getOptionsSchema = () => {
|
export const getOptionsSchema = () => {
|
||||||
const options = {
|
const options = {
|
||||||
@ -175,3 +177,39 @@ export const setDefaultValue = async (field: any, variables: any) => {
|
|||||||
field.loading = false;
|
field.loading = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const FILTER_FIELD_PREFIX_SEPARATOR = '-';
|
||||||
|
|
||||||
|
export const getFilterFieldPrefix = (dataSource: string, fieldName: string) => {
|
||||||
|
return dataSource ? `${dataSource}${FILTER_FIELD_PREFIX_SEPARATOR}${fieldName}` : fieldName;
|
||||||
|
};
|
||||||
|
|
||||||
|
// [dataSource-]collection.fieldName.associateName
|
||||||
|
export const parseFilterFieldName = (name: string) => {
|
||||||
|
const [prefix, fieldName] = name.split(FILTER_FIELD_PREFIX_SEPARATOR);
|
||||||
|
if (fieldName) {
|
||||||
|
return { dataSource: prefix, fieldName };
|
||||||
|
}
|
||||||
|
return { dataSource: DEFAULT_DATA_SOURCE_KEY, fieldName: prefix };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findSchema = (schema: Schema, key: string, targetName: string) => {
|
||||||
|
if (!Schema.isSchemaInstance(schema)) return null;
|
||||||
|
return schema.reduceProperties((buf, s) => {
|
||||||
|
let fieldName = s[key];
|
||||||
|
if (!fieldName.includes(FILTER_FIELD_PREFIX_SEPARATOR)) {
|
||||||
|
fieldName = `${DEFAULT_DATA_SOURCE_KEY}${FILTER_FIELD_PREFIX_SEPARATOR}${fieldName}`;
|
||||||
|
}
|
||||||
|
if (fieldName === targetName) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
if (s['x-component'] !== 'Action.Container' && s['x-component'] !== 'AssociationField.Viewer') {
|
||||||
|
const c = findSchema(s, key, targetName);
|
||||||
|
if (c) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -1,7 +1,16 @@
|
|||||||
import { SchemaInitializerItemType, i18n, useActionContext, useCollectionManager_deprecated } from '@nocobase/client';
|
import {
|
||||||
import { useContext, useMemo } from 'react';
|
Collection,
|
||||||
|
CollectionFieldInterfaceManager,
|
||||||
|
CollectionFieldOptions,
|
||||||
|
CollectionManager,
|
||||||
|
SchemaInitializerItemType,
|
||||||
|
i18n,
|
||||||
|
useActionContext,
|
||||||
|
useCollectionManager_deprecated,
|
||||||
|
useDataSourceManager,
|
||||||
|
} from '@nocobase/client';
|
||||||
|
import { useCallback, useContext, useMemo } from 'react';
|
||||||
import { ChartDataContext } from '../block/ChartDataProvider';
|
import { ChartDataContext } from '../block/ChartDataProvider';
|
||||||
import { CollectionOptions } from '@nocobase/database';
|
|
||||||
import { Schema } from '@formily/react';
|
import { Schema } from '@formily/react';
|
||||||
import { useChartsTranslation } from '../locale';
|
import { useChartsTranslation } from '../locale';
|
||||||
import { ChartFilterContext } from '../filter/FilterProvider';
|
import { ChartFilterContext } from '../filter/FilterProvider';
|
||||||
@ -10,6 +19,7 @@ import { parse } from '@nocobase/utils/client';
|
|||||||
import lodash from 'lodash';
|
import lodash from 'lodash';
|
||||||
import { getFormulaComponent, getValuesByPath } from '../utils';
|
import { getFormulaComponent, getValuesByPath } from '../utils';
|
||||||
import deepmerge from 'deepmerge';
|
import deepmerge from 'deepmerge';
|
||||||
|
import { findSchema, getFilterFieldPrefix, parseFilterFieldName } from '../filter/utils';
|
||||||
|
|
||||||
export const useCustomFieldInterface = () => {
|
export const useCustomFieldInterface = () => {
|
||||||
const { getInterface } = useCollectionManager_deprecated();
|
const { getInterface } = useCollectionManager_deprecated();
|
||||||
@ -44,35 +54,63 @@ export const useCustomFieldInterface = () => {
|
|||||||
export const useChartData = () => {
|
export const useChartData = () => {
|
||||||
const { charts } = useContext(ChartDataContext);
|
const { charts } = useContext(ChartDataContext);
|
||||||
|
|
||||||
const getChartCollections = () =>
|
const chartCollections: {
|
||||||
Array.from(
|
[dataSource: string]: string[];
|
||||||
new Set(
|
} = useMemo(() => {
|
||||||
Object.values(charts)
|
return Object.values(charts)
|
||||||
.filter((chart) => chart)
|
.filter((chart) => chart)
|
||||||
.map((chart) => chart.collection),
|
.reduce((mp, chart) => {
|
||||||
),
|
const { dataSource, collection } = chart;
|
||||||
|
if (mp[dataSource]?.includes(collection)) {
|
||||||
|
return mp;
|
||||||
|
}
|
||||||
|
mp[dataSource] = [...(mp[dataSource] || []), collection];
|
||||||
|
return mp;
|
||||||
|
}, {});
|
||||||
|
}, [charts]);
|
||||||
|
|
||||||
|
const showDataSource = useMemo(() => {
|
||||||
|
return Object.keys(chartCollections).length > 1;
|
||||||
|
}, [chartCollections]);
|
||||||
|
|
||||||
|
const getIsChartCollectionExists = useCallback(
|
||||||
|
(dataSource: string, collection: string) => {
|
||||||
|
return chartCollections[dataSource]?.includes(collection) || false;
|
||||||
|
},
|
||||||
|
[chartCollections],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getChartCollections,
|
chartCollections,
|
||||||
|
showDataSource,
|
||||||
|
getIsChartCollectionExists,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useChartFilter = () => {
|
export const useChartFilter = () => {
|
||||||
|
const dm = useDataSourceManager();
|
||||||
const { charts } = useContext(ChartDataContext);
|
const { charts } = useContext(ChartDataContext);
|
||||||
const { fieldSchema } = useActionContext();
|
const { fieldSchema } = useActionContext();
|
||||||
const action = fieldSchema?.['x-action'];
|
const action = fieldSchema?.['x-action'];
|
||||||
const { getCollection, getInterface, getCollectionFields, getCollectionJoinField } =
|
|
||||||
useCollectionManager_deprecated();
|
|
||||||
const { fields: fieldProps, form } = useContext(ChartFilterContext);
|
const { fields: fieldProps, form } = useContext(ChartFilterContext);
|
||||||
|
|
||||||
const getChartFilterFields = (collection: CollectionOptions) => {
|
const getChartFilterFields = ({
|
||||||
const fields = getCollectionFields(collection);
|
dataSource,
|
||||||
const field2item = (field: any, title: string, name: string) => {
|
collection,
|
||||||
|
cm,
|
||||||
|
fim,
|
||||||
|
}: {
|
||||||
|
dataSource: string;
|
||||||
|
collection: Collection;
|
||||||
|
cm: CollectionManager;
|
||||||
|
fim: CollectionFieldInterfaceManager;
|
||||||
|
}) => {
|
||||||
|
const fields = cm.getCollectionFields(collection.name);
|
||||||
|
const field2item = (field: any, title: string, name: string, fieldName: string) => {
|
||||||
const fieldTitle = field.uiSchema?.title || field.name;
|
const fieldTitle = field.uiSchema?.title || field.name;
|
||||||
const interfaceConfig = getInterface(field.interface);
|
const interfaceConfig = fim.getFieldInterface(field.interface);
|
||||||
const defaultOperator = interfaceConfig?.filterable?.operators?.[0];
|
const defaultOperator = interfaceConfig?.filterable?.operators?.[0];
|
||||||
const targetCollection = getCollection(field.target);
|
const targetCollection = cm.getCollection(field.target);
|
||||||
title = title ? `${title} / ${fieldTitle}` : fieldTitle;
|
title = title ? `${title} / ${fieldTitle}` : fieldTitle;
|
||||||
let schema = {
|
let schema = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@ -82,7 +120,8 @@ export const useChartFilter = () => {
|
|||||||
'x-designer': 'ChartFilterItemDesigner',
|
'x-designer': 'ChartFilterItemDesigner',
|
||||||
'x-component': 'CollectionField',
|
'x-component': 'CollectionField',
|
||||||
'x-decorator': 'ChartFilterFormItem',
|
'x-decorator': 'ChartFilterFormItem',
|
||||||
'x-collection-field': `${name}.${field.name}`,
|
'x-data-source': dataSource,
|
||||||
|
'x-collection-field': `${fieldName}.${field.name}`,
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
...field.uiSchema?.['x-component-props'],
|
...field.uiSchema?.['x-component-props'],
|
||||||
'filter-operator': defaultOperator,
|
'filter-operator': defaultOperator,
|
||||||
@ -104,6 +143,7 @@ export const useChartFilter = () => {
|
|||||||
type: 'item',
|
type: 'item',
|
||||||
title: field?.uiSchema?.title || field.name,
|
title: field?.uiSchema?.title || field.name,
|
||||||
Component: 'CollectionFieldInitializer',
|
Component: 'CollectionFieldInitializer',
|
||||||
|
find: findSchema,
|
||||||
remove: (schema, cb) => {
|
remove: (schema, cb) => {
|
||||||
cb(schema, {
|
cb(schema, {
|
||||||
breakRemoveOn: {
|
breakRemoveOn: {
|
||||||
@ -126,7 +166,7 @@ export const useChartFilter = () => {
|
|||||||
return resultItem;
|
return resultItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
const children2item = (child: any, title: string, name: string) => {
|
const children2item = (child: any, title: string, name: string, fieldName: string) => {
|
||||||
const childTitle = child.uiSchema?.title || child.name;
|
const childTitle = child.uiSchema?.title || child.name;
|
||||||
title = title ? `${title} / ${childTitle}` : childTitle;
|
title = title ? `${title} / ${childTitle}` : childTitle;
|
||||||
const defaultOperator = child.operators[0];
|
const defaultOperator = child.operators[0];
|
||||||
@ -136,7 +176,8 @@ export const useChartFilter = () => {
|
|||||||
required: false,
|
required: false,
|
||||||
'x-designer': 'ChartFilterItemDesigner',
|
'x-designer': 'ChartFilterItemDesigner',
|
||||||
'x-decorator': 'ChartFilterFormItem',
|
'x-decorator': 'ChartFilterFormItem',
|
||||||
'x-collection-field': `${name}.${child.name}`,
|
'x-data-source': dataSource,
|
||||||
|
'x-collection-field': `${fieldName}.${child.name}`,
|
||||||
...child.schema,
|
...child.schema,
|
||||||
title,
|
title,
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
@ -159,6 +200,7 @@ export const useChartFilter = () => {
|
|||||||
type: 'item',
|
type: 'item',
|
||||||
title: child.title || child.name,
|
title: child.title || child.name,
|
||||||
Component: 'CollectionFieldInitializer',
|
Component: 'CollectionFieldInitializer',
|
||||||
|
find: findSchema,
|
||||||
remove: (schema, cb) => {
|
remove: (schema, cb) => {
|
||||||
cb(schema, {
|
cb(schema, {
|
||||||
breakRemoveOn: {
|
breakRemoveOn: {
|
||||||
@ -172,23 +214,31 @@ export const useChartFilter = () => {
|
|||||||
return resultItem;
|
return resultItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
const field2option = (field: any, depth: number, title: string, name: string): SchemaInitializerItemType => {
|
const field2option = (
|
||||||
|
field: any,
|
||||||
|
depth: number,
|
||||||
|
title: string,
|
||||||
|
name: string,
|
||||||
|
fieldName: string,
|
||||||
|
): SchemaInitializerItemType => {
|
||||||
if (!field.interface) {
|
if (!field.interface) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fieldInterface = getInterface(field.interface);
|
const fieldInterface = fim.getFieldInterface(field.interface);
|
||||||
if (!fieldInterface?.filterable) {
|
if (!fieldInterface?.filterable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { nested, children } = fieldInterface.filterable;
|
const { nested, children } = fieldInterface.filterable;
|
||||||
const fieldTitle = field.uiSchema?.title || field.name;
|
const fieldTitle = field.uiSchema?.title || field.name;
|
||||||
const item = field2item(field, title, name);
|
const item = field2item(field, title, name, fieldName);
|
||||||
if (field.target && depth > 2) {
|
if (field.target && depth > 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
title = title ? `${title} / ${fieldTitle}` : fieldTitle;
|
title = title ? `${title} / ${fieldTitle}` : fieldTitle;
|
||||||
if (children?.length && !['chinaRegion', 'createdBy', 'updatedBy'].includes(field.interface)) {
|
if (children?.length && !['chinaRegion', 'createdBy', 'updatedBy', 'attachment'].includes(field.interface)) {
|
||||||
const items = children.map((child: any) => children2item(child, title, `${name}.${field.name}`));
|
const items = children.map((child: any) =>
|
||||||
|
children2item(child, title, `${name}.${field.name}`, `${fieldName}.${field.name}`),
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
key: `${name}.${field.name}`,
|
key: `${name}.${field.name}`,
|
||||||
name: field.name,
|
name: field.name,
|
||||||
@ -201,9 +251,9 @@ export const useChartFilter = () => {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
if (nested) {
|
if (nested) {
|
||||||
const targetFields = getCollectionFields(field.target);
|
const targetFields = cm.getCollectionFields(field.target);
|
||||||
const items = targetFields.map((targetField) =>
|
const items = targetFields.map((targetField) =>
|
||||||
field2option(targetField, depth + 1, '', `${name}.${field.name}`),
|
field2option(targetField, depth + 1, '', `${name}.${field.name}`, `${fieldName}.${field.name}`),
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
key: `${name}.${field.name}`,
|
key: `${name}.${field.name}`,
|
||||||
@ -220,12 +270,12 @@ export const useChartFilter = () => {
|
|||||||
const associationOptions = [];
|
const associationOptions = [];
|
||||||
fields.forEach((field) => {
|
fields.forEach((field) => {
|
||||||
const fieldInterface = field.interface;
|
const fieldInterface = field.interface;
|
||||||
const option = field2option(field, 0, '', collection.name);
|
const option = field2option(field, 0, '', getFilterFieldPrefix(dataSource, collection.name), collection.name);
|
||||||
if (option) {
|
if (option) {
|
||||||
options.push(option);
|
options.push(option);
|
||||||
}
|
}
|
||||||
if (['m2o'].includes(fieldInterface)) {
|
if (['m2o'].includes(fieldInterface)) {
|
||||||
const option = field2option(field, 1, '', collection.name);
|
const option = field2option(field, 1, '', getFilterFieldPrefix(dataSource, collection.name), collection.name);
|
||||||
if (option) {
|
if (option) {
|
||||||
associationOptions.push(option);
|
associationOptions.push(option);
|
||||||
}
|
}
|
||||||
@ -251,20 +301,26 @@ export const useChartFilter = () => {
|
|||||||
const getFilter = () => {
|
const getFilter = () => {
|
||||||
const values = form?.values || {};
|
const values = form?.values || {};
|
||||||
const filter = {};
|
const filter = {};
|
||||||
Object.entries(fieldProps).forEach(([name, props]) => {
|
Object.entries(fieldProps)
|
||||||
|
.filter(([_, props]) => props)
|
||||||
|
.forEach(([name, props]) => {
|
||||||
const { operator } = props || {};
|
const { operator } = props || {};
|
||||||
const field = getCollectionJoinField(name);
|
const { dataSource, fieldName } = parseFilterFieldName(name);
|
||||||
|
const ds = dm.getDataSource(dataSource);
|
||||||
|
const cm = ds.collectionManager;
|
||||||
|
const field = cm.getCollectionField(fieldName);
|
||||||
if (field?.target) {
|
if (field?.target) {
|
||||||
name = `${name}.${field.targetKey || 'id'}`;
|
name = `${fieldName}.${field.targetKey || 'id'}`;
|
||||||
}
|
}
|
||||||
const [collection, ...fields] = name.split('.');
|
const [collection, ...fields] = fieldName.split('.');
|
||||||
const value = getValuesByPath(values, name);
|
const value = getValuesByPath(values, name);
|
||||||
const op = operator?.value || '$eq';
|
const op = operator?.value || '$eq';
|
||||||
if (collection !== 'custom') {
|
if (collection !== 'custom') {
|
||||||
filter[collection] = filter[collection] || { $and: [] };
|
const key = getFilterFieldPrefix(dataSource, collection);
|
||||||
|
filter[key] = filter[key] || { $and: [] };
|
||||||
const condition = {};
|
const condition = {};
|
||||||
lodash.set(condition, fields.join('.'), { [op]: value });
|
lodash.set(condition, fields.join('.'), { [op]: value });
|
||||||
filter[collection].$and.push(condition);
|
filter[key].$and.push(condition);
|
||||||
} else {
|
} else {
|
||||||
filter[collection] = filter[collection] || {};
|
filter[collection] = filter[collection] || {};
|
||||||
filter[collection][`$nFilter.${fields.join('.')}`] = value;
|
filter[collection][`$nFilter.${fields.join('.')}`] = value;
|
||||||
@ -273,18 +329,19 @@ export const useChartFilter = () => {
|
|||||||
return filter;
|
return filter;
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasFilter = (chart: { collection: string; query: any }, filterValues: any) => {
|
const hasFilter = (chart: { dataSource: string; collection: string; query: any }, filterValues: any) => {
|
||||||
const { collection, query } = chart;
|
const { dataSource, collection, query } = chart;
|
||||||
const { parameters } = parse(query.filter || '');
|
const { parameters } = parse(query.filter || '');
|
||||||
return (
|
return (
|
||||||
chart &&
|
chart &&
|
||||||
(filterValues[collection] ||
|
(filterValues[getFilterFieldPrefix(dataSource, collection)] ||
|
||||||
(filterValues['custom'] && parameters?.find((param: { key: string }) => filterValues['custom'][param.key])))
|
(filterValues['custom'] &&
|
||||||
|
parameters?.find(({ key }: { key: string }) => lodash.has(filterValues['custom'], key))))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const appendFilter = (chart: { collection: string; query: any }, filterValues: any) => {
|
const appendFilter = (chart: { dataSource: string; collection: string; query: any }, filterValues: any) => {
|
||||||
const { collection, query } = chart;
|
const { dataSource, collection, query } = chart;
|
||||||
let newQuery = { ...query };
|
let newQuery = { ...query };
|
||||||
const originFilter = { ...(newQuery.filter || {}) };
|
const originFilter = { ...(newQuery.filter || {}) };
|
||||||
let filter = {};
|
let filter = {};
|
||||||
@ -297,7 +354,7 @@ export const useChartFilter = () => {
|
|||||||
newQuery = {
|
newQuery = {
|
||||||
...newQuery,
|
...newQuery,
|
||||||
filter: {
|
filter: {
|
||||||
$and: [filter, filterValues[collection]],
|
$and: [filter, filterValues[getFilterFieldPrefix(dataSource, collection)]],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return newQuery;
|
return newQuery;
|
||||||
@ -308,8 +365,8 @@ export const useChartFilter = () => {
|
|||||||
const requests = Object.values(charts)
|
const requests = Object.values(charts)
|
||||||
.filter((chart) => hasFilter(chart, filterValues))
|
.filter((chart) => hasFilter(chart, filterValues))
|
||||||
.map((chart) => async () => {
|
.map((chart) => async () => {
|
||||||
const { service, collection } = chart;
|
const { dataSource, service, collection } = chart;
|
||||||
await service.runAsync(collection, appendFilter(chart, filterValues), true);
|
await service.runAsync(dataSource, collection, appendFilter(chart, filterValues), true);
|
||||||
});
|
});
|
||||||
await Promise.all(requests.map((request) => request()));
|
await Promise.all(requests.map((request) => request()));
|
||||||
};
|
};
|
||||||
@ -320,8 +377,8 @@ export const useChartFilter = () => {
|
|||||||
return chart;
|
return chart;
|
||||||
})
|
})
|
||||||
.map((chart) => async () => {
|
.map((chart) => async () => {
|
||||||
const { service, collection, query } = chart;
|
const { service, dataSource, collection, query } = chart;
|
||||||
await service.runAsync(collection, query, true);
|
await service.runAsync(dataSource, collection, query, true);
|
||||||
});
|
});
|
||||||
await Promise.all(requests.map((request) => request()));
|
await Promise.all(requests.map((request) => request()));
|
||||||
};
|
};
|
||||||
@ -375,14 +432,16 @@ export const useFilterVariable = () => {
|
|||||||
|
|
||||||
export const useChartFilterSourceFields = () => {
|
export const useChartFilterSourceFields = () => {
|
||||||
const { t } = useChartsTranslation();
|
const { t } = useChartsTranslation();
|
||||||
const { getChartCollections } = useChartData();
|
const { chartCollections } = useChartData();
|
||||||
const { getInterface, getCollectionFields, getCollection } = useCollectionManager_deprecated();
|
const dm = useDataSourceManager();
|
||||||
|
const fim = dm.collectionFieldInterfaceManager;
|
||||||
|
|
||||||
const { values } = useFieldComponents();
|
const { values } = useFieldComponents();
|
||||||
const field2option = (field: any, depth: number) => {
|
const field2option = (cm: CollectionManager, field: any, depth: number) => {
|
||||||
if (!field.interface) {
|
if (!field.interface) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fieldInterface = getInterface(field.interface);
|
const fieldInterface = fim.getFieldInterface(field.interface);
|
||||||
if (!fieldInterface?.filterable) {
|
if (!fieldInterface?.filterable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -398,8 +457,8 @@ export const useChartFilterSourceFields = () => {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
if (nested) {
|
if (nested) {
|
||||||
const targetFields = getCollectionFields(field.target);
|
const targetFields = cm.getCollectionFields(field.target);
|
||||||
const items = targetFields.map((targetField) => field2option(targetField, depth + 1));
|
const items = targetFields.map((targetField) => field2option(cm, targetField, depth + 1));
|
||||||
return {
|
return {
|
||||||
value: field.name,
|
value: field.name,
|
||||||
label: t(field?.uiSchema?.title || field.name),
|
label: t(field?.uiSchema?.title || field.name),
|
||||||
@ -412,29 +471,28 @@ export const useChartFilterSourceFields = () => {
|
|||||||
return item;
|
return item;
|
||||||
};
|
};
|
||||||
|
|
||||||
const collections = getChartCollections();
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const options = [];
|
const options = Object.entries(chartCollections).map(([dataSource, collections]) => {
|
||||||
collections.forEach((name) => {
|
const ds = dm.getDataSource(dataSource);
|
||||||
const collection = getCollection(name);
|
return {
|
||||||
const children = [];
|
value: dataSource,
|
||||||
const fields = getCollectionFields(collection);
|
label: Schema.compile(ds.displayName, { t }),
|
||||||
fields.forEach((field) => {
|
children: collections.map((name: string) => {
|
||||||
const option = field2option(field, 1);
|
const cm = ds.collectionManager;
|
||||||
if (option) {
|
const collection = cm.getCollection(name);
|
||||||
children.push(option);
|
const fields = cm.getCollectionFields(name);
|
||||||
}
|
const children = fields.map((field) => field2option(cm, field, 1)).filter((item) => item);
|
||||||
});
|
return {
|
||||||
if (children.length) {
|
|
||||||
options.push({
|
|
||||||
value: name,
|
value: name,
|
||||||
label: t(collection.title),
|
label: Schema.compile(collection.title, { t }),
|
||||||
children,
|
children,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
return options;
|
return options;
|
||||||
}, [collections]);
|
}, [chartCollections]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useFieldComponents = () => {
|
export const useFieldComponents = () => {
|
||||||
@ -457,26 +515,35 @@ export const useFieldComponents = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCollectionJoinFieldTitle = (name: string) => {
|
export const useCollectionJoinFieldTitle = (dataSource: string, name: string) => {
|
||||||
const { getCollection, getCollectionField } = useCollectionManager_deprecated();
|
const { t } = useChartsTranslation();
|
||||||
|
const dm = useDataSourceManager();
|
||||||
|
const { showDataSource } = useChartData();
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
|
const ds = dm.getDataSource(dataSource);
|
||||||
|
if (!ds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const cm = ds.collectionManager;
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const [collectionName, ...fieldNames] = name.split('.');
|
const { fieldName } = parseFilterFieldName(name);
|
||||||
|
const [collectionName, ...fieldNames] = fieldName.split('.');
|
||||||
if (!fieldNames?.length) {
|
if (!fieldNames?.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const collection = getCollection(collectionName);
|
const collection = cm.getCollection(collectionName);
|
||||||
let cName: any = collectionName;
|
let cName: any = collectionName;
|
||||||
let field: any;
|
let field: any;
|
||||||
let title = Schema.compile(collection?.title, { t: i18n.t });
|
let title = Schema.compile(collection?.title, { t });
|
||||||
while (cName && fieldNames.length > 0) {
|
while (cName && fieldNames.length > 0) {
|
||||||
const fileName = fieldNames.shift();
|
const fileName = fieldNames.shift();
|
||||||
field = getCollectionField(`${cName}.${fileName}`);
|
field = cm.getCollectionField(`${cName}.${fileName}`);
|
||||||
const fieldTitle = field?.uiSchema?.title || field?.name;
|
const fieldTitle = field?.uiSchema?.title || field?.name;
|
||||||
if (fieldTitle) {
|
if (fieldTitle) {
|
||||||
title += ` / ${Schema.compile(fieldTitle, { t: i18n.t })}`;
|
title += ` / ${Schema.compile(fieldTitle, { t })}`;
|
||||||
}
|
}
|
||||||
if (field?.target) {
|
if (field?.target) {
|
||||||
cName = field.target;
|
cName = field.target;
|
||||||
@ -484,6 +551,6 @@ export const useCollectionJoinFieldTitle = (name: string) => {
|
|||||||
cName = null;
|
cName = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return title;
|
return showDataSource ? `${Schema.compile(ds.displayName, { t })} > ${title}` : title;
|
||||||
}, [name]);
|
}, [name, dataSource, showDataSource]);
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
import { ArrayField } from '@formily/core';
|
import { ArrayField } from '@formily/core';
|
||||||
import { ISchema, Schema, useForm } from '@formily/react';
|
import { ISchema, Schema, useForm } from '@formily/react';
|
||||||
import {
|
import {
|
||||||
|
CollectionFieldOptions,
|
||||||
CollectionFieldOptions_deprecated,
|
CollectionFieldOptions_deprecated,
|
||||||
|
CollectionManager,
|
||||||
|
DEFAULT_DATA_SOURCE_KEY,
|
||||||
useACLRoleContext,
|
useACLRoleContext,
|
||||||
useCollectionManager_deprecated,
|
useCollectionManager_deprecated,
|
||||||
|
useDataSourceManager,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { useContext, useMemo } from 'react';
|
import { useContext, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -26,19 +30,24 @@ export type FieldOption = {
|
|||||||
targetFields?: FieldOption[];
|
targetFields?: FieldOption[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useChartDataSource = (dataSource?: string) => {
|
||||||
|
const { current } = useContext(ChartConfigContext);
|
||||||
|
const { dataSource: _dataSource = dataSource || DEFAULT_DATA_SOURCE_KEY, collection } = current || {};
|
||||||
|
const dm = useDataSourceManager();
|
||||||
|
const ds = dm.getDataSource(_dataSource);
|
||||||
|
const fim = dm.collectionFieldInterfaceManager;
|
||||||
|
const cm = ds?.collectionManager;
|
||||||
|
return { cm, fim, collection };
|
||||||
|
};
|
||||||
|
|
||||||
export const useFields = (
|
export const useFields = (
|
||||||
collection?: string,
|
collectionFields: CollectionFieldOptions[],
|
||||||
): (CollectionFieldOptions_deprecated & {
|
): (CollectionFieldOptions & {
|
||||||
key: string;
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
})[] => {
|
})[] => {
|
||||||
const { current } = useContext(ChartConfigContext);
|
const fields = (collectionFields || [])
|
||||||
if (!collection) {
|
|
||||||
collection = current?.collection || '';
|
|
||||||
}
|
|
||||||
const { getCollectionFields } = useCollectionManager_deprecated();
|
|
||||||
const fields = (getCollectionFields(collection) || [])
|
|
||||||
.filter((field) => {
|
.filter((field) => {
|
||||||
return field.interface;
|
return field.interface;
|
||||||
})
|
})
|
||||||
@ -51,21 +60,22 @@ export const useFields = (
|
|||||||
return fields;
|
return fields;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useFieldsWithAssociation = (collection?: string) => {
|
export const useFieldsWithAssociation = (dataSource?: string, collection?: string) => {
|
||||||
const { getCollectionFields, getInterface } = useCollectionManager_deprecated();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const fields = useFields(collection);
|
const { cm, fim, collection: _collection } = useChartDataSource(dataSource);
|
||||||
|
const collectionFields = cm.getCollectionFields(collection || _collection);
|
||||||
|
const fields = useFields(collectionFields);
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() =>
|
() =>
|
||||||
fields.map((field) => {
|
fields.map((field) => {
|
||||||
const filterable = getInterface(field.interface)?.filterable;
|
const filterable = fim.getFieldInterface(field.interface)?.filterable;
|
||||||
const label = Schema.compile(field.uiSchema?.title || field.name, { t });
|
const label = Schema.compile(field.uiSchema?.title || field.name, { t });
|
||||||
if (!(filterable && (filterable?.nested || filterable?.children?.length))) {
|
if (!(filterable && (filterable?.nested || filterable?.children?.length))) {
|
||||||
return { ...field, label };
|
return { ...field, label };
|
||||||
}
|
}
|
||||||
let targetFields = [];
|
let targetFields = [];
|
||||||
if (filterable?.nested) {
|
if (filterable?.nested) {
|
||||||
const nestedFields = (getCollectionFields(field.target) || [])
|
const nestedFields = (cm.getCollectionFields(field.target) || [])
|
||||||
.filter((targetField) => {
|
.filter((targetField) => {
|
||||||
return targetField.interface;
|
return targetField.interface;
|
||||||
})
|
})
|
||||||
@ -132,20 +142,23 @@ export const useFormatters = (fields: FieldOption[]) => (field: any) => {
|
|||||||
|
|
||||||
export const useCollectionOptions = () => {
|
export const useCollectionOptions = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { collections } = useCollectionManager_deprecated();
|
const dm = useDataSourceManager();
|
||||||
const { allowAll, parseAction } = useACLRoleContext();
|
const { parseAction } = useACLRoleContext();
|
||||||
const options = collections
|
const allCollections = dm.getAllCollections({
|
||||||
.filter((collection: { name: string }) => {
|
filterCollection: (collection) => {
|
||||||
if (allowAll) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const params = parseAction(`${collection.name}:list`);
|
const params = parseAction(`${collection.name}:list`);
|
||||||
return params;
|
return params;
|
||||||
})
|
},
|
||||||
.map((collection: { name: string; title: string }) => ({
|
});
|
||||||
label: collection.title,
|
const options = allCollections
|
||||||
|
.filter(({ key, isDBInstance }) => key === DEFAULT_DATA_SOURCE_KEY || isDBInstance)
|
||||||
|
.map(({ key, displayName, collections }) => ({
|
||||||
|
value: key,
|
||||||
|
label: displayName,
|
||||||
|
children: collections.map((collection) => ({
|
||||||
value: collection.name,
|
value: collection.name,
|
||||||
key: collection.name,
|
label: collection.title,
|
||||||
|
})),
|
||||||
}));
|
}));
|
||||||
return useMemo(() => Schema.compile(options, { t }), [options]);
|
return useMemo(() => Schema.compile(options, { t }), [options]);
|
||||||
};
|
};
|
||||||
@ -202,11 +215,124 @@ export const useOrderReaction = (defaultOptions: any[], fields: FieldOption[]) =
|
|||||||
field.setValue(newOrders);
|
field.setValue(newOrders);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useData = (data?: any[], collection?: string) => {
|
export const useData = (data?: any[], dataSource?: string, collection?: string) => {
|
||||||
const { t } = useChartsTranslation();
|
const { t } = useChartsTranslation();
|
||||||
const { service, query } = useContext(ChartRendererContext);
|
const { service, query } = useContext(ChartRendererContext);
|
||||||
const fields = useFieldsWithAssociation(collection);
|
const fields = useFieldsWithAssociation(dataSource, collection);
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
const selectedFields = getSelectedFields(fields, form?.values?.query || query);
|
const selectedFields = getSelectedFields(fields, form?.values?.query || query);
|
||||||
return processData(selectedFields, service?.data || data || [], { t });
|
return processData(selectedFields, service?.data || data || [], { t });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useCollectionFieldsOptions = (dataSource: string, collectionName: string, maxDepth = 2, excludes = []) => {
|
||||||
|
const { cm, fim, collection } = useChartDataSource(dataSource);
|
||||||
|
const collectionFields = cm.getCollectionFields(collectionName || collection);
|
||||||
|
const fields = collectionFields.filter((v) => !excludes.includes(v.interface));
|
||||||
|
|
||||||
|
const field2option = (field, depth, prefix?) => {
|
||||||
|
if (!field.interface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fieldInterface = fim.getFieldInterface(field.interface);
|
||||||
|
if (!fieldInterface?.filterable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { nested, children } = fieldInterface.filterable;
|
||||||
|
const value = prefix ? `${prefix}.${field.name}` : field.name;
|
||||||
|
const option = {
|
||||||
|
...field,
|
||||||
|
name: field.name,
|
||||||
|
title: field?.uiSchema?.title || field.name,
|
||||||
|
schema: field?.uiSchema,
|
||||||
|
key: value,
|
||||||
|
};
|
||||||
|
if (field.target && depth > maxDepth) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (depth > maxDepth) {
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
if (children?.length) {
|
||||||
|
option['children'] = children.map((v) => {
|
||||||
|
return {
|
||||||
|
...v,
|
||||||
|
key: `${field.name}.${v.name}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (nested) {
|
||||||
|
const targetFields = cm.getCollectionFields(field.target).filter((v) => !excludes.includes(v.interface));
|
||||||
|
const options = getOptions(targetFields, depth + 1, field.name).filter(Boolean);
|
||||||
|
option['children'] = option['children'] || [];
|
||||||
|
option['children'].push(...options);
|
||||||
|
}
|
||||||
|
return option;
|
||||||
|
};
|
||||||
|
const getOptions = (fields, depth, prefix?) => {
|
||||||
|
const options = [];
|
||||||
|
fields.forEach((field) => {
|
||||||
|
const option = field2option(field, depth, prefix);
|
||||||
|
if (option) {
|
||||||
|
options.push(option);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
const options = getOptions(fields, 1);
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCollectionFilterOptions = (dataSource: string, collection: string) => {
|
||||||
|
const { cm, fim, collection: _collection } = useChartDataSource(dataSource);
|
||||||
|
return useMemo(() => {
|
||||||
|
const fields = cm.getCollectionFields(collection || _collection);
|
||||||
|
const field2option = (field, depth) => {
|
||||||
|
if (!field.interface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fieldInterface = fim.getFieldInterface(field.interface);
|
||||||
|
if (!fieldInterface?.filterable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { nested, children, operators } = fieldInterface.filterable;
|
||||||
|
const option = {
|
||||||
|
name: field.name,
|
||||||
|
title: field?.uiSchema?.title || field.name,
|
||||||
|
schema: field?.uiSchema,
|
||||||
|
operators:
|
||||||
|
operators?.filter?.((operator) => {
|
||||||
|
return !operator?.visible || operator.visible(field);
|
||||||
|
}) || [],
|
||||||
|
interface: field.interface,
|
||||||
|
};
|
||||||
|
if (field.target && depth > 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (depth > 2) {
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
if (children?.length) {
|
||||||
|
option['children'] = children;
|
||||||
|
}
|
||||||
|
if (nested) {
|
||||||
|
const targetFields = cm.getCollectionFields(field.target);
|
||||||
|
const options = getOptions(targetFields, depth + 1).filter(Boolean);
|
||||||
|
option['children'] = option['children'] || [];
|
||||||
|
option['children'].push(...options);
|
||||||
|
}
|
||||||
|
return option;
|
||||||
|
};
|
||||||
|
const getOptions = (fields, depth) => {
|
||||||
|
const options = [];
|
||||||
|
fields.forEach((field) => {
|
||||||
|
const option = field2option(field, depth);
|
||||||
|
if (option) {
|
||||||
|
options.push(option);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
const options = getOptions(fields, 1);
|
||||||
|
return options;
|
||||||
|
}, [collection, cm, fim]);
|
||||||
|
};
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
gridRowColWrap,
|
gridRowColWrap,
|
||||||
useAPIClient,
|
useAPIClient,
|
||||||
useCollection_deprecated,
|
useCollection_deprecated,
|
||||||
|
useDataSource,
|
||||||
useDesignable,
|
useDesignable,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
import { Empty, Result, Spin, Typography } from 'antd';
|
import { Empty, Result, Spin, Typography } from 'antd';
|
||||||
@ -27,9 +28,9 @@ export const ChartRenderer: React.FC & {
|
|||||||
} = (props) => {
|
} = (props) => {
|
||||||
const { t } = useChartsTranslation();
|
const { t } = useChartsTranslation();
|
||||||
const ctx = useContext(ChartRendererContext);
|
const ctx = useContext(ChartRendererContext);
|
||||||
const { config, transform, collection, service, data: _data } = ctx;
|
const { config, transform, dataSource, collection, service, data: _data } = ctx;
|
||||||
const fields = useFieldsWithAssociation(collection);
|
const fields = useFieldsWithAssociation(dataSource, collection);
|
||||||
const data = useData(_data, collection);
|
const data = useData(_data, dataSource, collection);
|
||||||
const general = config?.general || {};
|
const general = config?.general || {};
|
||||||
const advanced = config?.advanced || {};
|
const advanced = config?.advanced || {};
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
@ -70,7 +71,7 @@ export const ChartRenderer: React.FC & {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!(data && data.length)) {
|
if (!(data && data.length)) {
|
||||||
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('Please configure and run query')} />;
|
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('No data')} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <C />;
|
return <C />;
|
||||||
@ -84,14 +85,15 @@ ChartRenderer.Designer = function Designer() {
|
|||||||
const field = useField();
|
const field = useField();
|
||||||
const schema = useFieldSchema();
|
const schema = useFieldSchema();
|
||||||
const { insertAdjacent } = useDesignable();
|
const { insertAdjacent } = useDesignable();
|
||||||
const { name, title, dataSource } = useCollection_deprecated();
|
const dataSource = useDataSource();
|
||||||
|
const { name, title } = useCollection_deprecated();
|
||||||
return (
|
return (
|
||||||
<GeneralSchemaDesigner disableInitializer title={title || name}>
|
<GeneralSchemaDesigner disableInitializer title={title || name}>
|
||||||
<SchemaSettingsItem
|
<SchemaSettingsItem
|
||||||
title="Configure"
|
title="Configure"
|
||||||
key="configure"
|
key="configure"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setCurrent({ schema, field, dataSource, collection: name, service, data: service.data });
|
setCurrent({ schema, field, dataSource: dataSource.key, collection: name, service, data: service.data });
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useFieldSchema } from '@formily/react';
|
import { useFieldSchema } from '@formily/react';
|
||||||
import {
|
import {
|
||||||
CollectionManagerProvider,
|
CollectionManagerProvider,
|
||||||
|
DEFAULT_DATA_SOURCE_KEY,
|
||||||
MaybeCollectionProvider,
|
MaybeCollectionProvider,
|
||||||
useAPIClient,
|
useAPIClient,
|
||||||
useDataSourceManager,
|
useDataSourceManager,
|
||||||
@ -68,14 +69,14 @@ export const ChartRendererContext = createContext<
|
|||||||
ChartRendererContext.displayName = 'ChartRendererContext';
|
ChartRendererContext.displayName = 'ChartRendererContext';
|
||||||
|
|
||||||
export const ChartRendererProvider: React.FC<ChartRendererProps> = (props) => {
|
export const ChartRendererProvider: React.FC<ChartRendererProps> = (props) => {
|
||||||
const { query, config, collection, transform, dataSource } = props;
|
const { query, config, collection, transform, dataSource = DEFAULT_DATA_SOURCE_KEY } = props;
|
||||||
const { addChart } = useContext(ChartDataContext);
|
const { addChart } = useContext(ChartDataContext);
|
||||||
const { ready, form, enabled } = useContext(ChartFilterContext);
|
const { ready, form, enabled } = useContext(ChartFilterContext);
|
||||||
const { getFilter, hasFilter, appendFilter } = useChartFilter();
|
const { getFilter, hasFilter, appendFilter } = useChartFilter();
|
||||||
const schema = useFieldSchema();
|
const schema = useFieldSchema();
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
const service = useRequest(
|
const service = useRequest(
|
||||||
(collection, query, manual) =>
|
(dataSource, collection, query, manual) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
// Check if the chart is configured
|
// Check if the chart is configured
|
||||||
if (!(collection && query?.measures?.length)) return resolve(undefined);
|
if (!(collection && query?.measures?.length)) return resolve(undefined);
|
||||||
@ -83,8 +84,8 @@ export const ChartRendererProvider: React.FC<ChartRendererProps> = (props) => {
|
|||||||
if (enabled && !form) return resolve(undefined);
|
if (enabled && !form) return resolve(undefined);
|
||||||
const filterValues = getFilter();
|
const filterValues = getFilter();
|
||||||
const queryWithFilter =
|
const queryWithFilter =
|
||||||
!manual && hasFilter({ collection, query }, filterValues)
|
!manual && hasFilter({ dataSource, collection, query }, filterValues)
|
||||||
? appendFilter({ collection, query }, filterValues)
|
? appendFilter({ dataSource, collection, query }, filterValues)
|
||||||
: query;
|
: query;
|
||||||
api
|
api
|
||||||
.request({
|
.request({
|
||||||
@ -92,6 +93,7 @@ export const ChartRendererProvider: React.FC<ChartRendererProps> = (props) => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: {
|
data: {
|
||||||
uid: schema?.['x-uid'],
|
uid: schema?.['x-uid'],
|
||||||
|
dataSource,
|
||||||
collection,
|
collection,
|
||||||
...queryWithFilter,
|
...queryWithFilter,
|
||||||
filter: removeUnparsableFilter(queryWithFilter.filter),
|
filter: removeUnparsableFilter(queryWithFilter.filter),
|
||||||
@ -115,14 +117,16 @@ export const ChartRendererProvider: React.FC<ChartRendererProps> = (props) => {
|
|||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
resolve(res?.data?.data);
|
resolve(res?.data?.data);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
if (!manual && schema?.['x-uid']) {
|
if (!manual && schema?.['x-uid']) {
|
||||||
addChart(schema?.['x-uid'], { collection, service, query });
|
addChart(schema?.['x-uid'], { dataSource, collection, service, query });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
defaultParams: [collection, query],
|
defaultParams: [dataSource, collection, query],
|
||||||
// Wait until ChartFilterProvider is rendered and check the status of the filter form
|
// Wait until ChartFilterProvider is rendered and check the status of the filter form
|
||||||
// since the filter parameters should be applied if the filter block is enabled
|
// since the filter parameters should be applied if the filter block is enabled
|
||||||
ready: ready && (!enabled || !!form),
|
ready: ready && (!enabled || !!form),
|
||||||
@ -131,9 +135,9 @@ export const ChartRendererProvider: React.FC<ChartRendererProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<CollectionManagerProvider dataSource={dataSource}>
|
<CollectionManagerProvider dataSource={dataSource}>
|
||||||
<MaybeCollectionProvider collection={collection} dataSource={dataSource}>
|
<MaybeCollectionProvider collection={collection}>
|
||||||
<ConfigProvider card={{ style: { boxShadow: 'none' } }}>
|
<ConfigProvider card={{ style: { boxShadow: 'none' } }}>
|
||||||
<ChartRendererContext.Provider value={{ collection, config, transform, service, query }}>
|
<ChartRendererContext.Provider value={{ dataSource, collection, config, transform, service, query }}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</ChartRendererContext.Provider>
|
</ChartRendererContext.Provider>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
|
@ -51,6 +51,7 @@ describe('api', () => {
|
|||||||
|
|
||||||
test('query', async () => {
|
test('query', async () => {
|
||||||
const ctx = {
|
const ctx = {
|
||||||
|
app,
|
||||||
db,
|
db,
|
||||||
action: {
|
action: {
|
||||||
params: {
|
params: {
|
||||||
@ -82,6 +83,7 @@ describe('api', () => {
|
|||||||
|
|
||||||
test('query with sort', async () => {
|
test('query with sort', async () => {
|
||||||
const ctx = {
|
const ctx = {
|
||||||
|
app,
|
||||||
db,
|
db,
|
||||||
action: {
|
action: {
|
||||||
params: {
|
params: {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { MockServer, mockServer } from '@nocobase/test';
|
import { MockServer, createMockServer } from '@nocobase/test';
|
||||||
import compose from 'koa-compose';
|
import compose from 'koa-compose';
|
||||||
import { vi } from 'vitest';
|
import { vi } from 'vitest';
|
||||||
import { cacheMiddleware, parseBuilder, parseFieldAndAssociations } from '../actions/query';
|
import { cacheMiddleware, parseBuilder, parseFieldAndAssociations } from '../actions/query';
|
||||||
const formatter = await import('../actions/formatter');
|
const formatter = await import('../actions/formatter');
|
||||||
|
|
||||||
describe('query', () => {
|
describe('query', () => {
|
||||||
describe('parseBuilder', () => {
|
describe('parseBuilder', () => {
|
||||||
const sequelize = {
|
const sequelize = {
|
||||||
@ -11,8 +12,10 @@ describe('query', () => {
|
|||||||
};
|
};
|
||||||
let ctx: any;
|
let ctx: any;
|
||||||
let app: MockServer;
|
let app: MockServer;
|
||||||
beforeAll(() => {
|
beforeAll(async () => {
|
||||||
app = mockServer();
|
app = await createMockServer({
|
||||||
|
plugins: ['data-source-manager'],
|
||||||
|
});
|
||||||
app.db.options.underscored = true;
|
app.db.options.underscored = true;
|
||||||
app.db.collection({
|
app.db.collection({
|
||||||
name: 'orders',
|
name: 'orders',
|
||||||
|
@ -27,6 +27,7 @@ type OrderProps = {
|
|||||||
|
|
||||||
type QueryParams = Partial<{
|
type QueryParams = Partial<{
|
||||||
uid: string;
|
uid: string;
|
||||||
|
dataSource: string;
|
||||||
collection: string;
|
collection: string;
|
||||||
measures: MeasureProps[];
|
measures: MeasureProps[];
|
||||||
dimensions: DimensionProps[];
|
dimensions: DimensionProps[];
|
||||||
@ -45,6 +46,11 @@ type QueryParams = Partial<{
|
|||||||
refresh: boolean;
|
refresh: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
const getDB = (ctx: Context, dataSource: string) => {
|
||||||
|
const ds = ctx.app.dataSourceManager.dataSources.get(dataSource);
|
||||||
|
return ds?.collectionManager.db;
|
||||||
|
};
|
||||||
|
|
||||||
export const postProcess = async (ctx: Context, next: Next) => {
|
export const postProcess = async (ctx: Context, next: Next) => {
|
||||||
const { data, fieldMap } = ctx.action.params.values as {
|
const { data, fieldMap } = ctx.action.params.values as {
|
||||||
data: any[];
|
data: any[];
|
||||||
@ -71,8 +77,9 @@ export const postProcess = async (ctx: Context, next: Next) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const queryData = async (ctx: Context, next: Next) => {
|
export const queryData = async (ctx: Context, next: Next) => {
|
||||||
const { collection, queryParams, fieldMap } = ctx.action.params.values;
|
const { dataSource, collection, queryParams, fieldMap } = ctx.action.params.values;
|
||||||
const model = ctx.db.getModel(collection);
|
const db = getDB(ctx, dataSource) || ctx.db;
|
||||||
|
const model = db.getModel(collection);
|
||||||
const data = await model.findAll(queryParams);
|
const data = await model.findAll(queryParams);
|
||||||
ctx.action.params.values = {
|
ctx.action.params.values = {
|
||||||
data,
|
data,
|
||||||
@ -89,8 +96,9 @@ export const queryData = async (ctx: Context, next: Next) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const parseBuilder = async (ctx: Context, next: Next) => {
|
export const parseBuilder = async (ctx: Context, next: Next) => {
|
||||||
const { sequelize } = ctx.db;
|
const { dataSource, measures, dimensions, orders, include, where, limit } = ctx.action.params.values;
|
||||||
const { measures, dimensions, orders, include, where, limit } = ctx.action.params.values;
|
const db = getDB(ctx, dataSource) || ctx.db;
|
||||||
|
const { sequelize } = db;
|
||||||
const attributes = [];
|
const attributes = [];
|
||||||
const group = [];
|
const group = [];
|
||||||
const order = [];
|
const order = [];
|
||||||
@ -157,8 +165,16 @@ export const parseBuilder = async (ctx: Context, next: Next) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const parseFieldAndAssociations = async (ctx: Context, next: Next) => {
|
export const parseFieldAndAssociations = async (ctx: Context, next: Next) => {
|
||||||
const { collection: collectionName, measures, dimensions, orders, filter } = ctx.action.params.values as QueryParams;
|
const {
|
||||||
const collection = ctx.db.getCollection(collectionName);
|
dataSource,
|
||||||
|
collection: collectionName,
|
||||||
|
measures,
|
||||||
|
dimensions,
|
||||||
|
orders,
|
||||||
|
filter,
|
||||||
|
} = ctx.action.params.values as QueryParams;
|
||||||
|
const db = getDB(ctx, dataSource) || ctx.db;
|
||||||
|
const collection = db.getCollection(collectionName);
|
||||||
const fields = collection.fields;
|
const fields = collection.fields;
|
||||||
const models: {
|
const models: {
|
||||||
[target: string]: {
|
[target: string]: {
|
||||||
@ -180,7 +196,7 @@ export const parseFieldAndAssociations = async (ctx: Context, next: Next) => {
|
|||||||
let fieldType = fields.get(name)?.type;
|
let fieldType = fields.get(name)?.type;
|
||||||
if (target) {
|
if (target) {
|
||||||
const targetField = fields.get(target) as Field;
|
const targetField = fields.get(target) as Field;
|
||||||
const targetCollection = ctx.db.getCollection(targetField.target);
|
const targetCollection = db.getCollection(targetField.target);
|
||||||
const targetFields = targetCollection.fields;
|
const targetFields = targetCollection.fields;
|
||||||
fieldType = targetFields.get(name)?.type;
|
fieldType = targetFields.get(name)?.type;
|
||||||
field = `${target}.${field}`;
|
field = `${target}.${field}`;
|
||||||
|
Loading…
Reference in New Issue
Block a user