fix(bi): g2plot render wrong when fields contain .
(#2363)
* fix: g2plot render wrong when fields contains `.` * fix: build * fix: test * fix: dual axes bug * chore: add comment * fix: code style
This commit is contained in:
parent
07f1f16ea0
commit
f82b6a9d38
@ -5,7 +5,7 @@ import { Button, Card } from 'antd';
|
||||
import cls from 'classnames';
|
||||
import React, { useContext } from 'react';
|
||||
import { useChartsTranslation } from './locale';
|
||||
import { ChartLibraryContext, useToggleChartLibrary } from './renderer';
|
||||
import { ChartLibraryContext, useToggleChartLibrary } from './chart/library';
|
||||
|
||||
export const Settings = () => {
|
||||
const { t } = useChartsTranslation();
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Chart } from '../chart/chart';
|
||||
import { FieldOption } from '../hooks';
|
||||
import { infer } from '../renderer';
|
||||
const chart = new Chart('test', 'Test', null);
|
||||
|
||||
describe('library', () => {
|
||||
describe('auto infer', () => {
|
||||
@ -37,7 +38,7 @@ describe('library', () => {
|
||||
] as FieldOption[];
|
||||
|
||||
test('1 measure, 1 dimension', () => {
|
||||
const { xField, yField } = infer(fields, {
|
||||
const { xField, yField } = chart.infer(fields, {
|
||||
measures: [{ field: ['price'] }],
|
||||
dimensions: [{ field: ['title'] }],
|
||||
});
|
||||
@ -46,7 +47,7 @@ describe('library', () => {
|
||||
});
|
||||
|
||||
test('1 measure, 2 dimensions with date', () => {
|
||||
const { xField, yField, seriesField } = infer(fields, {
|
||||
const { xField, yField, seriesField } = chart.infer(fields, {
|
||||
measures: [{ field: ['price'] }],
|
||||
dimensions: [{ field: ['title'] }, { field: ['createdAt'] }],
|
||||
});
|
||||
@ -56,7 +57,7 @@ describe('library', () => {
|
||||
});
|
||||
|
||||
test('1 measure, 2 dimensions without date', () => {
|
||||
const { xField, yField, seriesField } = infer(fields, {
|
||||
const { xField, yField, seriesField } = chart.infer(fields, {
|
||||
measures: [{ field: ['price'] }],
|
||||
dimensions: [{ field: ['title'] }, { field: ['name'] }],
|
||||
});
|
||||
@ -66,7 +67,7 @@ describe('library', () => {
|
||||
});
|
||||
|
||||
test('2 measures, 1 dimension', () => {
|
||||
const { xField, yField, yFields } = infer(fields, {
|
||||
const { xField, yField, yFields } = chart.infer(fields, {
|
||||
measures: [{ field: ['price'] }, { field: ['count'] }],
|
||||
dimensions: [{ field: ['title'] }],
|
||||
});
|
||||
|
@ -26,9 +26,10 @@ import {
|
||||
useTransformers,
|
||||
} from '../hooks';
|
||||
import { useChartsTranslation } from '../locale';
|
||||
import { ChartRenderer, ChartRendererContext, useChartTypes, useCharts, useDefaultChartType } from '../renderer';
|
||||
import { ChartRenderer, ChartRendererContext } from '../renderer';
|
||||
import { createRendererSchema, getField, getSelectedFields } from '../utils';
|
||||
import { getConfigSchema, querySchema, transformSchema } from './schemas/configure';
|
||||
import { useChartTypes, useCharts, useDefaultChartType } from '../chart/library';
|
||||
const { Paragraph, Text } = Typography;
|
||||
|
||||
export type ChartConfigCurrent = {
|
||||
@ -98,7 +99,7 @@ export const ChartConfigure: React.FC<{
|
||||
}
|
||||
const query = form.values.query;
|
||||
const selectedFields = getSelectedFields(fields, query);
|
||||
const { general, advanced } = init(selectedFields, query);
|
||||
const { general, advanced } = chart.init(selectedFields, query);
|
||||
if (general || overwrite) {
|
||||
form.values.config.general = general;
|
||||
}
|
||||
@ -364,7 +365,7 @@ ChartConfigure.Config = function Config() {
|
||||
const charts = useCharts();
|
||||
const getChartFields = useChartFields(fields);
|
||||
const getReference = (chartType: string) => {
|
||||
const reference = charts[chartType]?.reference;
|
||||
const reference = charts[chartType]?.getReference?.();
|
||||
if (!reference) return '';
|
||||
const { title, link } = reference;
|
||||
return (
|
||||
|
@ -0,0 +1,10 @@
|
||||
import { Chart } from '../chart';
|
||||
|
||||
export class AntdChart extends Chart {
|
||||
getReference() {
|
||||
return {
|
||||
title: this.title,
|
||||
link: `https://ant.design/components/${this.name}`,
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
import { Statistic } from './statistic';
|
||||
import { Table } from './table';
|
||||
|
||||
export default [new Statistic(), new Table()];
|
@ -0,0 +1,64 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import { AntdChart } from './antd';
|
||||
import { Statistic as AntdStatistic } from 'antd';
|
||||
import { lang } from '../../locale';
|
||||
import { FieldOption } from '../../hooks';
|
||||
import { QueryProps } from '../../renderer';
|
||||
import { RenderProps } from '../chart';
|
||||
|
||||
export class Statistic extends AntdChart {
|
||||
schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
field: {
|
||||
title: lang('Field'),
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
title: lang('Title'),
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super('statistic', 'Statistic', AntdStatistic);
|
||||
}
|
||||
|
||||
init(
|
||||
fields: FieldOption[],
|
||||
{
|
||||
measures,
|
||||
dimensions,
|
||||
}: {
|
||||
measures?: QueryProps['measures'];
|
||||
dimensions?: QueryProps['dimensions'];
|
||||
},
|
||||
) {
|
||||
const { yField } = this.infer(fields, { measures, dimensions });
|
||||
return {
|
||||
general: {
|
||||
field: yField?.value,
|
||||
title: yField?.label,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getProps({ data, fieldProps, general, advanced }: RenderProps) {
|
||||
const record = data[0] || {};
|
||||
const field = general?.field;
|
||||
const props = fieldProps[field];
|
||||
return {
|
||||
value: record[field],
|
||||
formatter: props?.transformer,
|
||||
...general,
|
||||
...advanced,
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
import { RenderProps } from '../chart';
|
||||
import { AntdChart } from './antd';
|
||||
import { Table as AntdTable } from 'antd';
|
||||
|
||||
export class Table extends AntdChart {
|
||||
constructor() {
|
||||
super('table', 'Table', AntdTable);
|
||||
}
|
||||
|
||||
getProps({ data, fieldProps, general, advanced }: RenderProps) {
|
||||
const columns = data.length
|
||||
? Object.keys(data[0]).map((item) => ({
|
||||
title: fieldProps[item]?.label || item,
|
||||
dataIndex: item,
|
||||
key: item,
|
||||
}))
|
||||
: [];
|
||||
const dataSource = data.map((item: any) => {
|
||||
Object.keys(item).map((key: string) => {
|
||||
const props = fieldProps[key];
|
||||
if (props?.transformer) {
|
||||
item[key] = props.transformer(item[key]);
|
||||
}
|
||||
});
|
||||
return item;
|
||||
});
|
||||
const pageSize = advanced?.pagination?.pageSize || 10;
|
||||
return {
|
||||
bordered: true,
|
||||
size: 'middle',
|
||||
pagination:
|
||||
dataSource.length < pageSize
|
||||
? false
|
||||
: {
|
||||
pageSize,
|
||||
},
|
||||
dataSource,
|
||||
columns,
|
||||
...general,
|
||||
...advanced,
|
||||
};
|
||||
}
|
||||
}
|
134
packages/plugins/data-visualization/src/client/chart/chart.ts
Normal file
134
packages/plugins/data-visualization/src/client/chart/chart.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import React from 'react';
|
||||
import { FieldOption } from '../hooks';
|
||||
import { QueryProps } from '../renderer';
|
||||
import { parseField } from '../utils';
|
||||
import { ISchema } from '@formily/react';
|
||||
|
||||
export type RenderProps = {
|
||||
data: any[];
|
||||
general: any;
|
||||
advanced: any;
|
||||
fieldProps: {
|
||||
[field: string]: FieldOption & {
|
||||
transformer: (val: any) => string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export interface ChartType {
|
||||
name: string;
|
||||
title: string;
|
||||
component: React.FC<any>;
|
||||
schema: ISchema;
|
||||
infer: (
|
||||
fields: FieldOption[],
|
||||
{
|
||||
measures,
|
||||
dimensions,
|
||||
}: {
|
||||
measures?: QueryProps['measures'];
|
||||
dimensions?: QueryProps['dimensions'];
|
||||
},
|
||||
) => {
|
||||
xField: FieldOption;
|
||||
yField: FieldOption;
|
||||
seriesField: FieldOption;
|
||||
yFields: FieldOption[];
|
||||
};
|
||||
init?: (
|
||||
fields: FieldOption[],
|
||||
query: {
|
||||
measures?: QueryProps['measures'];
|
||||
dimensions?: QueryProps['dimensions'];
|
||||
},
|
||||
) => {
|
||||
general?: any;
|
||||
advanced?: any;
|
||||
};
|
||||
/**
|
||||
* getProps
|
||||
* Accept the information that the chart component needs to render,
|
||||
* process it and return the props of the chart component.
|
||||
*/
|
||||
getProps: (props: RenderProps) => any;
|
||||
getReference?: () => {
|
||||
title: string;
|
||||
link: string;
|
||||
};
|
||||
render: (props: RenderProps) => React.FC<any>;
|
||||
}
|
||||
|
||||
export class Chart implements ChartType {
|
||||
name: string;
|
||||
title: string;
|
||||
component: React.FC<any>;
|
||||
schema = {};
|
||||
|
||||
constructor(name: string, title: string, component: React.FC<any>) {
|
||||
this.name = name;
|
||||
this.title = title;
|
||||
this.component = component;
|
||||
}
|
||||
|
||||
infer(
|
||||
fields: FieldOption[],
|
||||
{
|
||||
measures,
|
||||
dimensions,
|
||||
}: {
|
||||
measures?: QueryProps['measures'];
|
||||
dimensions?: QueryProps['dimensions'];
|
||||
},
|
||||
) {
|
||||
let xField: FieldOption;
|
||||
let yField: FieldOption;
|
||||
let seriesField: FieldOption;
|
||||
let yFields: FieldOption[];
|
||||
const getField = (fields: FieldOption[], selected: { field: string | string[]; alias?: string }) => {
|
||||
if (selected.alias) {
|
||||
return fields.find((f) => f.value === selected.alias);
|
||||
}
|
||||
const { alias } = parseField(selected.field);
|
||||
return fields.find((f) => f.value === alias);
|
||||
};
|
||||
if (measures?.length) {
|
||||
yField = getField(fields, measures[0]);
|
||||
yFields = measures.map((m) => getField(fields, m));
|
||||
}
|
||||
if (dimensions) {
|
||||
if (dimensions.length === 1) {
|
||||
xField = getField(fields, dimensions[0]);
|
||||
} else if (dimensions.length > 1) {
|
||||
// If there is a time field, it is used as the x-axis field by default.
|
||||
let xIndex: number;
|
||||
dimensions.forEach((d, i) => {
|
||||
const field = getField(fields, d);
|
||||
if (['date', 'time', 'datetime'].includes(field?.type)) {
|
||||
xField = field;
|
||||
xIndex = i;
|
||||
}
|
||||
});
|
||||
if (xIndex) {
|
||||
// If there is a time field, the other field is used as the series field by default.
|
||||
const index = xIndex === 0 ? 1 : 0;
|
||||
seriesField = getField(fields, dimensions[index]);
|
||||
} else {
|
||||
xField = getField(fields, dimensions[0]);
|
||||
seriesField = getField(fields, dimensions[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { xField, yField, seriesField, yFields };
|
||||
}
|
||||
|
||||
getProps(props: RenderProps) {
|
||||
return props;
|
||||
}
|
||||
|
||||
render({ data, general, advanced, fieldProps }: RenderProps) {
|
||||
return () =>
|
||||
React.createElement(this.component, {
|
||||
...this.getProps({ data, general, advanced, fieldProps }),
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
import { FieldOption } from '../../hooks';
|
||||
import { QueryProps } from '../../renderer';
|
||||
import { Bar as G2PlotBar } from '@ant-design/plots';
|
||||
import { G2PlotChart } from './g2plot';
|
||||
|
||||
export class Bar extends G2PlotChart {
|
||||
constructor() {
|
||||
super('bar', 'Bar Chart', G2PlotBar);
|
||||
}
|
||||
|
||||
init(
|
||||
fields: FieldOption[],
|
||||
{
|
||||
measures,
|
||||
dimensions,
|
||||
}: {
|
||||
measures?: QueryProps['measures'];
|
||||
dimensions?: QueryProps['dimensions'];
|
||||
},
|
||||
) {
|
||||
const { xField, yField, seriesField } = this.infer(fields, { measures, dimensions });
|
||||
return {
|
||||
general: {
|
||||
xField: yField?.value,
|
||||
yField: xField?.value,
|
||||
seriesField: seriesField?.value,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import { G2PlotChart } from './g2plot';
|
||||
import { RenderProps } from '../chart';
|
||||
import React from 'react';
|
||||
import { DualAxes as G2DualAxes } from '@ant-design/plots';
|
||||
import { FieldOption } from '../../hooks';
|
||||
import { QueryProps } from '../../renderer';
|
||||
|
||||
export class DualAxes extends G2PlotChart {
|
||||
schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
xField: {
|
||||
title: '{{t("xField")}}',
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
required: true,
|
||||
},
|
||||
yField: {
|
||||
title: '{{t("yField")}}',
|
||||
type: 'array',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayItems',
|
||||
items: {
|
||||
type: 'void',
|
||||
'x-component': 'Space',
|
||||
properties: {
|
||||
sort: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayItems.SortHandle',
|
||||
},
|
||||
input: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
minWidth: '200px',
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
remove: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayItems.Remove',
|
||||
},
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
add: {
|
||||
type: 'void',
|
||||
title: '{{t("Add")}}',
|
||||
'x-component': 'ArrayItems.Addition',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super('dualAxes', 'Dual Axes Chart', G2DualAxes);
|
||||
}
|
||||
|
||||
init(
|
||||
fields: FieldOption[],
|
||||
{
|
||||
measures,
|
||||
dimensions,
|
||||
}: {
|
||||
measures?: QueryProps['measures'];
|
||||
dimensions?: QueryProps['dimensions'];
|
||||
},
|
||||
) {
|
||||
const { xField, yFields } = this.infer(fields, { measures, dimensions });
|
||||
return {
|
||||
general: {
|
||||
xField: xField?.value,
|
||||
yField: yFields?.map((f) => f.value).slice(0, 2) || [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
render({ data, general, advanced, fieldProps }: RenderProps) {
|
||||
const props = this.getProps({ data, general, advanced, fieldProps });
|
||||
const { data: _data } = props;
|
||||
return () =>
|
||||
React.createElement(this.component, {
|
||||
...props,
|
||||
data: [_data, _data],
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
import { Chart, RenderProps } from '../chart';
|
||||
import { FieldOption } from '../../hooks';
|
||||
import { QueryProps } from '../../renderer';
|
||||
import { ISchema } from '@formily/react';
|
||||
|
||||
export class G2PlotChart extends Chart {
|
||||
schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
xField: {
|
||||
title: '{{t("xField")}}',
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
required: true,
|
||||
},
|
||||
yField: {
|
||||
title: '{{t("yField")}}',
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
required: true,
|
||||
},
|
||||
seriesField: {
|
||||
title: '{{t("seriesField")}}',
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
init(
|
||||
fields: FieldOption[],
|
||||
{
|
||||
measures,
|
||||
dimensions,
|
||||
}: {
|
||||
measures?: QueryProps['measures'];
|
||||
dimensions?: QueryProps['dimensions'];
|
||||
},
|
||||
): {
|
||||
general?: any;
|
||||
advanced?: any;
|
||||
} {
|
||||
const { xField, yField, seriesField } = this.infer(fields, { measures, dimensions });
|
||||
return {
|
||||
general: {
|
||||
xField: xField?.value,
|
||||
yField: yField?.value,
|
||||
seriesField: seriesField?.value,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getProps({ data, general, advanced, fieldProps }: RenderProps) {
|
||||
const meta = {};
|
||||
// Some charts render wrong when the field name contains a dot in G2Plot
|
||||
const replace = (key: string) => key.replace(/\./g, '_');
|
||||
Object.entries(fieldProps).forEach(([key, props]) => {
|
||||
if (key.includes('.')) {
|
||||
key = replace(key);
|
||||
}
|
||||
meta[key] = {
|
||||
formatter: props.transformer,
|
||||
alias: props.label,
|
||||
};
|
||||
});
|
||||
general = Object.entries(general).reduce((obj, [key, value]) => {
|
||||
obj[key] = value;
|
||||
if (key.includes('Field')) {
|
||||
if (Array.isArray(value)) {
|
||||
obj[key] = value.map((v) => (v.includes('.') ? replace(v) : v));
|
||||
} else if (typeof value === 'string' && value.includes('.')) {
|
||||
obj[key] = replace(value);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
return {
|
||||
data: data.map((item) => {
|
||||
const obj = {};
|
||||
Object.entries(item).forEach(([key, value]) => {
|
||||
if (key.includes('.')) {
|
||||
key = replace(key);
|
||||
}
|
||||
obj[key] = value;
|
||||
});
|
||||
return obj;
|
||||
}),
|
||||
meta,
|
||||
animation: false,
|
||||
...general,
|
||||
...advanced,
|
||||
};
|
||||
}
|
||||
|
||||
getReference() {
|
||||
return {
|
||||
title: this.title,
|
||||
link: `https://g2plot.antv.antgroup.com/api/plots/${this.name}`,
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import { Area, Column, Line, Scatter } from '@ant-design/plots';
|
||||
import { Chart } from '../chart';
|
||||
import { Bar } from './bar';
|
||||
import { Pie } from './pie';
|
||||
import { DualAxes } from './dualAxes';
|
||||
import { G2PlotChart } from './g2plot';
|
||||
|
||||
export default [
|
||||
new G2PlotChart('line', 'Line Chart', Line),
|
||||
new G2PlotChart('area', 'Area Chart', Area),
|
||||
new G2PlotChart('column', 'Column Chart', Column),
|
||||
new Bar(),
|
||||
new Pie(),
|
||||
new DualAxes(),
|
||||
new G2PlotChart('scatter', 'Scatter Chart', Scatter),
|
||||
];
|
@ -0,0 +1,52 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import { G2PlotChart } from './g2plot';
|
||||
import { Pie as G2Pie } from '@ant-design/plots';
|
||||
import { FieldOption } from '../../hooks';
|
||||
import { QueryProps } from '../../renderer';
|
||||
|
||||
export class Pie extends G2PlotChart {
|
||||
schema: ISchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
angleField: {
|
||||
title: '{{t("angleField")}}',
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
required: true,
|
||||
},
|
||||
colorField: {
|
||||
title: '{{t("colorField")}}',
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super('pie', 'Pie Chart', G2Pie);
|
||||
}
|
||||
|
||||
init(
|
||||
fields: FieldOption[],
|
||||
{
|
||||
measures,
|
||||
dimensions,
|
||||
}: {
|
||||
measures?: QueryProps['measures'];
|
||||
dimensions?: QueryProps['dimensions'];
|
||||
},
|
||||
) {
|
||||
const { xField, yField } = this.infer(fields, { measures, dimensions });
|
||||
return {
|
||||
general: {
|
||||
colorField: xField?.value,
|
||||
angleField: yField?.value,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { lang } from '../locale';
|
||||
import { ChartType } from './chart';
|
||||
|
||||
export type ChartLibraries = {
|
||||
[library: string]: {
|
||||
enabled: boolean;
|
||||
charts: ChartType[];
|
||||
};
|
||||
};
|
||||
|
||||
export const ChartLibraryContext = createContext<ChartLibraries>({});
|
||||
|
||||
export const useCharts = (): {
|
||||
[name: string]: ChartType;
|
||||
} => {
|
||||
const library = useContext(ChartLibraryContext);
|
||||
return Object.values(library)
|
||||
.filter((l) => l.enabled)
|
||||
.reduce((allCharts, l) => {
|
||||
const charts = Object.values(l.charts);
|
||||
return {
|
||||
...allCharts,
|
||||
...charts.reduce((all, chart) => {
|
||||
return {
|
||||
...all,
|
||||
[chart.name]: chart,
|
||||
};
|
||||
}, {}),
|
||||
};
|
||||
}, {});
|
||||
};
|
||||
|
||||
export const useChartTypes = (): {
|
||||
label: string;
|
||||
children: {
|
||||
key: string;
|
||||
label: string;
|
||||
value: string;
|
||||
}[];
|
||||
}[] => {
|
||||
const library = useContext(ChartLibraryContext);
|
||||
return Object.entries(library)
|
||||
.filter(([_, l]) => l.enabled)
|
||||
.reduce((charts, [name, l]) => {
|
||||
const children = Object.values(l.charts).map((chart) => ({
|
||||
key: chart.name,
|
||||
label: lang(chart.title),
|
||||
value: chart.name,
|
||||
}));
|
||||
return [
|
||||
...charts,
|
||||
{
|
||||
label: lang(name),
|
||||
children,
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
};
|
||||
|
||||
export const useDefaultChartType = () => {
|
||||
const chartTypes = useChartTypes();
|
||||
return chartTypes[0]?.children?.[0]?.value;
|
||||
};
|
||||
|
||||
export const useToggleChartLibrary = () => {
|
||||
const ctx = useContext(ChartLibraryContext);
|
||||
return {
|
||||
toggle: (library: string) => {
|
||||
ctx[library].enabled = !ctx[library].enabled;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const ChartLibraryProvider: React.FC<{
|
||||
name: string;
|
||||
charts: ChartType[];
|
||||
}> = (props) => {
|
||||
const { children, charts, name } = props;
|
||||
const ctx = useContext(ChartLibraryContext);
|
||||
const library = {
|
||||
...ctx,
|
||||
[name]: {
|
||||
charts,
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
return <ChartLibraryContext.Provider value={library}>{children}</ChartLibraryContext.Provider>;
|
||||
};
|
@ -2,10 +2,12 @@ import { Plugin, SchemaComponentOptions, SchemaInitializerContext, SchemaInitial
|
||||
import React, { useContext } from 'react';
|
||||
import { ChartInitializers, ChartV2Block, ChartV2BlockDesigner, ChartV2BlockInitializer } from './block';
|
||||
import { useChartsTranslation } from './locale';
|
||||
import { ChartRenderer, ChartRendererProvider, InternalLibrary } from './renderer';
|
||||
import { ChartLibraryProvider } from './renderer/ChartLibrary';
|
||||
import { ChartRenderer, ChartRendererProvider } from './renderer';
|
||||
import { ChartLibraryProvider } from './chart/library';
|
||||
import g2plot from './chart/g2plot';
|
||||
import antd from './chart/antd';
|
||||
|
||||
const Chart: React.FC = (props) => {
|
||||
const DataVisualization: React.FC = (props) => {
|
||||
const { t } = useChartsTranslation();
|
||||
const initializers = useContext<any>(SchemaInitializerContext);
|
||||
const children = initializers.BlockInitializers.items[0].children;
|
||||
@ -29,7 +31,7 @@ const Chart: React.FC = (props) => {
|
||||
}}
|
||||
>
|
||||
<SchemaInitializerProvider initializers={{ ...initializers, ChartInitializers }}>
|
||||
<ChartLibraryProvider name="Built-in" charts={InternalLibrary}>
|
||||
<ChartLibraryProvider name="Built-in" charts={[...g2plot, ...antd]}>
|
||||
{props.children}
|
||||
</ChartLibraryProvider>
|
||||
</SchemaInitializerProvider>
|
||||
@ -39,9 +41,11 @@ const Chart: React.FC = (props) => {
|
||||
|
||||
class DataVisualizationPlugin extends Plugin {
|
||||
async load() {
|
||||
this.app.addProvider(Chart);
|
||||
this.app.addProvider(DataVisualization);
|
||||
}
|
||||
}
|
||||
|
||||
export default DataVisualizationPlugin;
|
||||
export { ChartLibraryProvider };
|
||||
export { Chart } from './chart/chart';
|
||||
export type { ChartType } from './chart/chart';
|
||||
|
@ -1,183 +0,0 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { FieldOption } from '../hooks';
|
||||
import { lang } from '../locale';
|
||||
import { parseField } from '../utils';
|
||||
import { QueryProps } from './ChartRendererProvider';
|
||||
|
||||
/**
|
||||
* @params {usePropsFunc} useProps - Accept the information that the chart component needs to render,
|
||||
* process it and return the props of the chart component.
|
||||
*/
|
||||
export type usePropsFunc = (props: {
|
||||
data: any[];
|
||||
fieldProps: {
|
||||
[field: string]: FieldOption & {
|
||||
transformer: (val: any) => string;
|
||||
};
|
||||
};
|
||||
general: any;
|
||||
advanced: any;
|
||||
}) => any;
|
||||
|
||||
export type ChartProps = {
|
||||
name: string;
|
||||
component: React.FC<any>;
|
||||
schema?: ISchema;
|
||||
useProps?: usePropsFunc;
|
||||
// The init function is used to initialize the configuration of the chart component from the query configuration.
|
||||
init?: (
|
||||
fields: FieldOption[],
|
||||
query: {
|
||||
measures?: QueryProps['measures'];
|
||||
dimensions?: QueryProps['dimensions'];
|
||||
},
|
||||
) => {
|
||||
general?: any;
|
||||
advanced?: any;
|
||||
};
|
||||
reference?: {
|
||||
title: string;
|
||||
link: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type Charts = {
|
||||
[type: string]: ChartProps;
|
||||
};
|
||||
|
||||
export type ChartLibraries = {
|
||||
[library: string]: {
|
||||
enabled: boolean;
|
||||
charts: Charts;
|
||||
};
|
||||
};
|
||||
|
||||
export const ChartLibraryContext = createContext<ChartLibraries>({});
|
||||
|
||||
export const useCharts = (): Charts => {
|
||||
const library = useContext(ChartLibraryContext);
|
||||
return Object.values(library)
|
||||
.filter((l) => l.enabled)
|
||||
.reduce((charts, l) => ({ ...charts, ...l.charts }), {});
|
||||
};
|
||||
|
||||
export const useChartTypes = (): {
|
||||
label: string;
|
||||
children: (ChartProps & {
|
||||
key: string;
|
||||
label: string;
|
||||
value: string;
|
||||
})[];
|
||||
}[] => {
|
||||
const library = useContext(ChartLibraryContext);
|
||||
return Object.entries(library)
|
||||
.filter(([_, l]) => l.enabled)
|
||||
.reduce((charts, [name, l]) => {
|
||||
const children = Object.entries(l.charts).map(([type, chart]) => ({
|
||||
...chart,
|
||||
key: type,
|
||||
label: lang(chart.name),
|
||||
value: type,
|
||||
}));
|
||||
return [
|
||||
...charts,
|
||||
{
|
||||
label: lang(name),
|
||||
children,
|
||||
},
|
||||
];
|
||||
}, []);
|
||||
};
|
||||
|
||||
export const useDefaultChartType = () => {
|
||||
const chartTypes = useChartTypes();
|
||||
return chartTypes[0]?.children?.[0]?.value;
|
||||
};
|
||||
|
||||
export const useToggleChartLibrary = () => {
|
||||
const ctx = useContext(ChartLibraryContext);
|
||||
return {
|
||||
toggle: (library: string) => {
|
||||
ctx[library].enabled = !ctx[library].enabled;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const ChartLibraryProvider: React.FC<{
|
||||
name: string;
|
||||
charts: Charts;
|
||||
}> = (props) => {
|
||||
const { children, charts, name } = props;
|
||||
const ctx = useContext(ChartLibraryContext);
|
||||
const library = {
|
||||
...ctx,
|
||||
[name]: {
|
||||
charts,
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
return <ChartLibraryContext.Provider value={library}>{children}</ChartLibraryContext.Provider>;
|
||||
};
|
||||
|
||||
export const infer = (
|
||||
fields: FieldOption[],
|
||||
{
|
||||
measures,
|
||||
dimensions,
|
||||
}: {
|
||||
measures?: QueryProps['measures'];
|
||||
dimensions?: QueryProps['dimensions'];
|
||||
},
|
||||
) => {
|
||||
let xField: FieldOption;
|
||||
let yField: FieldOption;
|
||||
let seriesField: FieldOption;
|
||||
let yFields: FieldOption[];
|
||||
const getField = (fields: FieldOption[], selected: { field: string | string[]; alias?: string }) => {
|
||||
if (selected.alias) {
|
||||
return fields.find((f) => f.value === selected.alias);
|
||||
}
|
||||
const { alias } = parseField(selected.field);
|
||||
return fields.find((f) => f.value === alias);
|
||||
};
|
||||
if (measures?.length) {
|
||||
yField = getField(fields, measures[0]);
|
||||
yFields = measures.map((m) => getField(fields, m));
|
||||
}
|
||||
if (dimensions) {
|
||||
if (dimensions.length === 1) {
|
||||
xField = getField(fields, dimensions[0]);
|
||||
} else if (dimensions.length > 1) {
|
||||
// If there is a time field, it is used as the x-axis field by default.
|
||||
let xIndex: number;
|
||||
dimensions.forEach((d, i) => {
|
||||
const field = getField(fields, d);
|
||||
if (['date', 'time', 'datetime'].includes(field?.type)) {
|
||||
xField = field;
|
||||
xIndex = i;
|
||||
}
|
||||
});
|
||||
if (xIndex) {
|
||||
// If there is a time field, the other field is used as the series field by default.
|
||||
const index = xIndex === 0 ? 1 : 0;
|
||||
seriesField = getField(fields, dimensions[index]);
|
||||
} else {
|
||||
xField = getField(fields, dimensions[0]);
|
||||
seriesField = getField(fields, dimensions[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { xField, yField, seriesField, yFields };
|
||||
};
|
||||
|
||||
export const commonInit: ChartProps['init'] = (fields, { measures, dimensions }) => {
|
||||
const { xField, yField, seriesField } = infer(fields, { measures, dimensions });
|
||||
return {
|
||||
general: {
|
||||
xField: xField?.value,
|
||||
yField: yField?.value,
|
||||
seriesField: seriesField?.value,
|
||||
},
|
||||
};
|
||||
};
|
@ -14,7 +14,7 @@ import { ChartConfigContext } from '../block';
|
||||
import { useData, useFieldTransformer, useFieldsWithAssociation } from '../hooks';
|
||||
import { useChartsTranslation } from '../locale';
|
||||
import { createRendererSchema, getField } from '../utils';
|
||||
import { useCharts } from './ChartLibrary';
|
||||
import { useCharts } from '../chart/library';
|
||||
import { ChartRendererContext } from './ChartRendererProvider';
|
||||
const { Paragraph, Text } = Typography;
|
||||
|
||||
@ -32,10 +32,9 @@ export const ChartRenderer: React.FC & {
|
||||
|
||||
const charts = useCharts();
|
||||
const chart = charts[config?.chartType];
|
||||
const Component = chart?.component;
|
||||
const locale = api.auth.getLocale();
|
||||
const transformers = useFieldTransformer(transform, locale);
|
||||
const info = {
|
||||
const Component = chart?.render({
|
||||
data,
|
||||
general,
|
||||
advanced,
|
||||
@ -47,18 +46,17 @@ export const ChartRenderer: React.FC & {
|
||||
}
|
||||
return props;
|
||||
}, {}),
|
||||
locale,
|
||||
};
|
||||
const componentProps = chart?.useProps?.(info) || info;
|
||||
});
|
||||
|
||||
const C = () =>
|
||||
Component ? (
|
||||
chart ? (
|
||||
<ErrorBoundary
|
||||
onError={(error) => {
|
||||
console.error(error);
|
||||
}}
|
||||
FallbackComponent={ErrorFallback}
|
||||
>
|
||||
<Component {...componentProps} />
|
||||
<Component />
|
||||
</ErrorBoundary>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('Please configure chart')} />
|
||||
|
@ -1,4 +1,2 @@
|
||||
export * from './ChartLibrary';
|
||||
export * from './ChartRenderer';
|
||||
export * from './ChartRendererProvider';
|
||||
export * from './library';
|
||||
|
@ -1,94 +0,0 @@
|
||||
import { Statistic, Table } from 'antd';
|
||||
import { lang } from '../../locale';
|
||||
import { Charts, infer } from '../ChartLibrary';
|
||||
|
||||
export const AntdLibrary: Charts = {
|
||||
statistic: {
|
||||
name: lang('Statistic'),
|
||||
component: Statistic,
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
field: {
|
||||
title: lang('Field'),
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
title: lang('Title'),
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
},
|
||||
},
|
||||
init: (fields, { measures, dimensions }) => {
|
||||
const { yField } = infer(fields, { measures, dimensions });
|
||||
return {
|
||||
general: {
|
||||
field: yField?.value,
|
||||
title: yField?.label,
|
||||
},
|
||||
};
|
||||
},
|
||||
useProps: ({ data, fieldProps, general, advanced }) => {
|
||||
const record = data[0] || {};
|
||||
const field = general?.field;
|
||||
const props = fieldProps[field];
|
||||
return {
|
||||
value: record[field],
|
||||
formatter: props?.transformer,
|
||||
...general,
|
||||
...advanced,
|
||||
};
|
||||
},
|
||||
reference: {
|
||||
title: lang('Statistic'),
|
||||
link: 'https://ant.design/components/statistic/',
|
||||
},
|
||||
},
|
||||
table: {
|
||||
name: lang('Table'),
|
||||
component: Table,
|
||||
useProps: ({ data, fieldProps, general, advanced }) => {
|
||||
const columns = data.length
|
||||
? Object.keys(data[0]).map((item) => ({
|
||||
title: fieldProps[item]?.label || item,
|
||||
dataIndex: item,
|
||||
key: item,
|
||||
}))
|
||||
: [];
|
||||
const dataSource = data.map((item: any) => {
|
||||
Object.keys(item).map((key: string) => {
|
||||
const props = fieldProps[key];
|
||||
if (props?.transformer) {
|
||||
item[key] = props.transformer(item[key]);
|
||||
}
|
||||
});
|
||||
return item;
|
||||
});
|
||||
const pageSize = advanced?.pagination?.pageSize || 10;
|
||||
return {
|
||||
bordered: true,
|
||||
size: 'middle',
|
||||
pagination:
|
||||
dataSource.length < pageSize
|
||||
? false
|
||||
: {
|
||||
pageSize,
|
||||
},
|
||||
dataSource,
|
||||
columns,
|
||||
...general,
|
||||
...advanced,
|
||||
};
|
||||
},
|
||||
reference: {
|
||||
title: lang('Table'),
|
||||
link: 'https://ant.design/components/table/',
|
||||
},
|
||||
},
|
||||
};
|
@ -1,236 +0,0 @@
|
||||
import { Area, Bar, Column, DualAxes, Line, Pie, Scatter } from '@ant-design/plots';
|
||||
import { Charts, commonInit, infer, usePropsFunc } from '../ChartLibrary';
|
||||
const init = commonInit;
|
||||
|
||||
const basicSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
xField: {
|
||||
title: '{{t("xField")}}',
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
required: true,
|
||||
},
|
||||
yField: {
|
||||
title: '{{t("yField")}}',
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
required: true,
|
||||
},
|
||||
seriesField: {
|
||||
title: '{{t("seriesField")}}',
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const useProps: usePropsFunc = ({ data, fieldProps, general, advanced }) => {
|
||||
const meta = {};
|
||||
Object.entries(fieldProps).forEach(([key, props]) => {
|
||||
meta[key] = {
|
||||
formatter: props.transformer,
|
||||
alias: props.label,
|
||||
};
|
||||
});
|
||||
return {
|
||||
data,
|
||||
meta,
|
||||
animation: false,
|
||||
...general,
|
||||
...advanced,
|
||||
};
|
||||
};
|
||||
|
||||
export const G2PlotLibrary: Charts = {
|
||||
line: {
|
||||
name: 'Line Chart',
|
||||
component: Line,
|
||||
schema: basicSchema,
|
||||
init,
|
||||
useProps,
|
||||
reference: {
|
||||
title: 'Line Chart',
|
||||
link: 'https://g2plot.antv.antgroup.com/api/plots/line',
|
||||
},
|
||||
},
|
||||
area: {
|
||||
name: 'Area Chart',
|
||||
component: Area,
|
||||
schema: basicSchema,
|
||||
init,
|
||||
useProps,
|
||||
reference: {
|
||||
title: 'Area Chart',
|
||||
link: 'https://g2plot.antv.antgroup.com/api/plots/area',
|
||||
},
|
||||
},
|
||||
column: {
|
||||
name: 'Column Chart',
|
||||
component: Column,
|
||||
schema: basicSchema,
|
||||
init,
|
||||
useProps,
|
||||
reference: {
|
||||
title: 'Column Chart',
|
||||
link: 'https://g2plot.antv.antgroup.com/api/plots/column',
|
||||
},
|
||||
},
|
||||
bar: {
|
||||
name: 'Bar Chart',
|
||||
component: Bar,
|
||||
schema: basicSchema,
|
||||
init: (fields, { measures, dimensions }) => {
|
||||
const { xField, yField, seriesField } = infer(fields, { measures, dimensions });
|
||||
return {
|
||||
general: {
|
||||
xField: yField?.value,
|
||||
yField: xField?.value,
|
||||
seriesField: seriesField?.value,
|
||||
},
|
||||
};
|
||||
},
|
||||
useProps,
|
||||
reference: {
|
||||
title: 'Bar Chart',
|
||||
link: 'https://g2plot.antv.antgroup.com/api/plots/bar',
|
||||
},
|
||||
},
|
||||
pie: {
|
||||
name: 'Pie Chart',
|
||||
component: Pie,
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
angleField: {
|
||||
title: '{{t("angleField")}}',
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
required: true,
|
||||
},
|
||||
colorField: {
|
||||
title: '{{t("colorField")}}',
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
init: (fields, { measures, dimensions }) => {
|
||||
const { xField, yField } = infer(fields, { measures, dimensions });
|
||||
return {
|
||||
general: {
|
||||
colorField: xField?.value,
|
||||
angleField: yField?.value,
|
||||
},
|
||||
};
|
||||
},
|
||||
useProps,
|
||||
reference: {
|
||||
title: 'Pie Chart',
|
||||
link: 'https://g2plot.antv.antgroup.com/api/plots/pie',
|
||||
},
|
||||
},
|
||||
dualAxes: {
|
||||
name: 'Dual Axes Chart',
|
||||
component: DualAxes,
|
||||
useProps: ({ data, fieldProps, general, advanced }) => {
|
||||
return {
|
||||
...useProps({ data, fieldProps, general, advanced }),
|
||||
data: [data, data],
|
||||
};
|
||||
},
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
xField: {
|
||||
title: '{{t("xField")}}',
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
required: true,
|
||||
},
|
||||
yField: {
|
||||
title: '{{t("yField")}}',
|
||||
type: 'array',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayItems',
|
||||
items: {
|
||||
type: 'void',
|
||||
'x-component': 'Space',
|
||||
properties: {
|
||||
sort: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayItems.SortHandle',
|
||||
},
|
||||
input: {
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Select',
|
||||
'x-reactions': '{{ useChartFields }}',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
minWidth: '200px',
|
||||
},
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
remove: {
|
||||
type: 'void',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'ArrayItems.Remove',
|
||||
},
|
||||
},
|
||||
},
|
||||
properties: {
|
||||
add: {
|
||||
type: 'void',
|
||||
title: '{{t("Add")}}',
|
||||
'x-component': 'ArrayItems.Addition',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
init: (fields, { measures, dimensions }) => {
|
||||
const { xField, yFields } = infer(fields, { measures, dimensions });
|
||||
return {
|
||||
general: {
|
||||
xField: xField?.value,
|
||||
yField: yFields?.map((f) => f.value).slice(0, 2) || [],
|
||||
},
|
||||
};
|
||||
},
|
||||
reference: {
|
||||
title: 'Dual Axes Chart',
|
||||
link: 'https://g2plot.antv.antgroup.com/api/plots/dual-axes',
|
||||
},
|
||||
},
|
||||
// gauge: {
|
||||
// name: 'Gauge Chart',
|
||||
// component: Gauge,
|
||||
// },
|
||||
scatter: {
|
||||
name: 'Scatter Chart',
|
||||
component: Scatter,
|
||||
schema: basicSchema,
|
||||
init,
|
||||
useProps,
|
||||
reference: {
|
||||
title: 'Scatter Chart',
|
||||
link: 'https://g2plot.antv.antgroup.com/api/plots/scatter',
|
||||
},
|
||||
},
|
||||
};
|
@ -1,4 +0,0 @@
|
||||
import { AntdLibrary } from './AntdLibrary';
|
||||
import { G2PlotLibrary } from './G2PlotLibrary';
|
||||
|
||||
export const InternalLibrary = { ...G2PlotLibrary, ...AntdLibrary };
|
@ -251,8 +251,18 @@ export const cacheWrap = async (
|
||||
};
|
||||
|
||||
export const query = async (ctx: Context, next: Next) => {
|
||||
const { uid, collection, measures, dimensions, orders, filter, limit, sql, cache: cacheConfig, refresh } = ctx.action
|
||||
.params.values as QueryParams;
|
||||
const {
|
||||
uid,
|
||||
collection,
|
||||
measures,
|
||||
dimensions,
|
||||
orders,
|
||||
filter,
|
||||
limit,
|
||||
sql,
|
||||
cache: cacheConfig,
|
||||
refresh,
|
||||
} = ctx.action.params.values as QueryParams;
|
||||
const roleName = ctx.state.currentRole || 'anonymous';
|
||||
const can = ctx.app.acl.can({ role: roleName, resource: collection, action: 'list' });
|
||||
if (!can && roleName !== 'root') {
|
||||
|
@ -20581,14 +20581,9 @@ prettier@2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.npmmirror.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5"
|
||||
|
||||
prettier@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz#e7b19f691245a21d618c68bc54dc06122f6105ae"
|
||||
|
||||
prettier@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmmirror.com/prettier/-/prettier-3.0.0.tgz#e7b19f691245a21d618c68bc54dc06122f6105ae"
|
||||
integrity sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==
|
||||
|
||||
pretty-error@^4.0.0:
|
||||
version "4.0.0"
|
||||
|
Loading…
Reference in New Issue
Block a user