mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
feat: move filters from superset-ui to incubator (#12154)
* feat: move filters from superset-ui to incubator * refactor: add safety check * refactor: move extraForm data utils * refactor: move extraForm data utils * lint: fix lint * chore: add license * chore: undo changes to file * refactor: fix CR notes / add tests * test: update tests * fix: fix range logic Co-authored-by: Amit Miran <47772523+amitmiran137@users.noreply.github.com>
This commit is contained in:
156
superset-frontend/spec/javascripts/filters/utils_spec.ts
Normal file
156
superset-frontend/spec/javascripts/filters/utils_spec.ts
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
getRangeExtraFormData,
|
||||||
|
getSelectExtraFormData,
|
||||||
|
} from '../../../src/filters/utils';
|
||||||
|
|
||||||
|
describe('Filter utils', () => {
|
||||||
|
describe('getRangeExtraFormData', () => {
|
||||||
|
it('getRangeExtraFormData - col: "testCol", lower: 1, upper: 2', () => {
|
||||||
|
expect(getRangeExtraFormData('testCol', 1, 2)).toEqual({
|
||||||
|
append_form_data: {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
col: 'testCol',
|
||||||
|
op: '>=',
|
||||||
|
val: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
col: 'testCol',
|
||||||
|
op: '<=',
|
||||||
|
val: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('getRangeExtraFormData - col: "testCol", lower: 0, upper: 0', () => {
|
||||||
|
expect(getRangeExtraFormData('testCol', 0, 0)).toEqual({
|
||||||
|
append_form_data: {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
col: 'testCol',
|
||||||
|
op: '>=',
|
||||||
|
val: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
col: 'testCol',
|
||||||
|
op: '<=',
|
||||||
|
val: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('getRangeExtraFormData - col: "testCol", lower: null, upper: 2', () => {
|
||||||
|
expect(getRangeExtraFormData('testCol', null, 2)).toEqual({
|
||||||
|
append_form_data: {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
col: 'testCol',
|
||||||
|
op: '<=',
|
||||||
|
val: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('getRangeExtraFormData - col: "testCol", lower: 1, upper: undefined', () => {
|
||||||
|
expect(getRangeExtraFormData('testCol', 1, undefined)).toEqual({
|
||||||
|
append_form_data: {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
col: 'testCol',
|
||||||
|
op: '>=',
|
||||||
|
val: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('getSelectExtraFormData', () => {
|
||||||
|
it('getSelectExtraFormData - col: "testCol", value: ["value"], emptyFilter: false, inverseSelection: false', () => {
|
||||||
|
expect(
|
||||||
|
getSelectExtraFormData('testCol', ['value'], false, false),
|
||||||
|
).toEqual({
|
||||||
|
append_form_data: {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
col: 'testCol',
|
||||||
|
op: 'IN',
|
||||||
|
val: ['value'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('getSelectExtraFormData - col: "testCol", value: ["value"], emptyFilter: true, inverseSelection: false', () => {
|
||||||
|
expect(getSelectExtraFormData('testCol', ['value'], true, false)).toEqual(
|
||||||
|
{
|
||||||
|
append_form_data: {
|
||||||
|
extras: {
|
||||||
|
where: '1 = 0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('getSelectExtraFormData - col: "testCol", value: ["value"], emptyFilter: false, inverseSelection: true', () => {
|
||||||
|
expect(getSelectExtraFormData('testCol', ['value'], false, true)).toEqual(
|
||||||
|
{
|
||||||
|
append_form_data: {
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
col: 'testCol',
|
||||||
|
op: 'NOT IN',
|
||||||
|
val: ['value'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('getSelectExtraFormData - col: "testCol", value: [], emptyFilter: false, inverseSelection: false', () => {
|
||||||
|
expect(getSelectExtraFormData('testCol', [], false, false)).toEqual({
|
||||||
|
append_form_data: {
|
||||||
|
filters: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('getSelectExtraFormData - col: "testCol", value: undefined, emptyFilter: false, inverseSelection: false', () => {
|
||||||
|
expect(
|
||||||
|
getSelectExtraFormData('testCol', undefined, false, false),
|
||||||
|
).toEqual({
|
||||||
|
append_form_data: {
|
||||||
|
filters: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('getSelectExtraFormData - col: "testCol", value: null, emptyFilter: false, inverseSelection: false', () => {
|
||||||
|
expect(getSelectExtraFormData('testCol', null, false, false)).toEqual({
|
||||||
|
append_form_data: {
|
||||||
|
filters: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -44,6 +44,7 @@ export {
|
|||||||
Typography,
|
Typography,
|
||||||
Tree,
|
Tree,
|
||||||
Popover,
|
Popover,
|
||||||
|
Slider,
|
||||||
Radio,
|
Radio,
|
||||||
Row,
|
Row,
|
||||||
Select,
|
Select,
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ const FilterValue: React.FC<FilterProps> = ({
|
|||||||
} = filter;
|
} = filter;
|
||||||
const cascadingFilters = useCascadingFilters(id);
|
const cascadingFilters = useCascadingFilters(id);
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
const [state, setState] = useState({ data: undefined });
|
const [state, setState] = useState([]);
|
||||||
const [formData, setFormData] = useState<Partial<QueryFormData>>({});
|
const [formData, setFormData] = useState<Partial<QueryFormData>>({});
|
||||||
const [target] = targets;
|
const [target] = targets;
|
||||||
const { datasetId = 18, column } = target;
|
const { datasetId = 18, column } = target;
|
||||||
@@ -256,7 +256,7 @@ const FilterValue: React.FC<FilterProps> = ({
|
|||||||
force: false,
|
force: false,
|
||||||
requestParams: { dashboardId: 0 },
|
requestParams: { dashboardId: 0 },
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
setState({ data: response.result[0].data });
|
setState(response.result);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import { styled } from '@superset-ui/core';
|
||||||
|
import React from 'react';
|
||||||
|
import { Slider } from 'src/common/components';
|
||||||
|
import { AntdPluginFilterRangeProps } from './types';
|
||||||
|
import { AntdPluginFilterStylesProps } from '../types';
|
||||||
|
import { getRangeExtraFormData } from '../../utils';
|
||||||
|
|
||||||
|
const Styles = styled.div<AntdPluginFilterStylesProps>`
|
||||||
|
height: ${({ height }) => height};
|
||||||
|
width: ${({ width }) => width};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function AntdRangeFilter(props: AntdPluginFilterRangeProps) {
|
||||||
|
const { data, formData, height, width, setExtraFormData } = props;
|
||||||
|
const [row] = data;
|
||||||
|
// @ts-ignore
|
||||||
|
const { min, max }: { min: number; max: number } = row;
|
||||||
|
const { groupby } = formData;
|
||||||
|
const [col] = groupby || [];
|
||||||
|
|
||||||
|
const handleChange = (value: [number, number]) => {
|
||||||
|
const [lower, upper] = value;
|
||||||
|
|
||||||
|
setExtraFormData(getRangeExtraFormData(col, lower, upper));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Styles height={height} width={width}>
|
||||||
|
<Slider
|
||||||
|
range
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
defaultValue={[min, max]}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</Styles>
|
||||||
|
);
|
||||||
|
}
|
||||||
74
superset-frontend/src/filters/components/Range/buildQuery.ts
Normal file
74
superset-frontend/src/filters/components/Range/buildQuery.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
buildQueryContext,
|
||||||
|
ColumnType,
|
||||||
|
QueryFormData,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The buildQuery function is used to create an instance of QueryContext that's
|
||||||
|
* sent to the chart data endpoint. In addition to containing information of which
|
||||||
|
* datasource to use, it specifies the type (e.g. full payload, samples, query) and
|
||||||
|
* format (e.g. CSV or JSON) of the result and whether or not to force refresh the data from
|
||||||
|
* the datasource as opposed to using a cached copy of the data, if available.
|
||||||
|
*
|
||||||
|
* More importantly though, QueryContext contains a property `queries`, which is an array of
|
||||||
|
* QueryObjects specifying individual data requests to be made. A QueryObject specifies which
|
||||||
|
* columns, metrics and filters, among others, to use during the query. Usually it will be enough
|
||||||
|
* to specify just one query based on the baseQueryObject, but for some more advanced use cases
|
||||||
|
* it is possible to define post processing operations in the QueryObject, or multiple queries
|
||||||
|
* if a viz needs multiple different result sets.
|
||||||
|
*/
|
||||||
|
export default function buildQuery(formData: QueryFormData) {
|
||||||
|
const { groupby } = formData;
|
||||||
|
const [column] = groupby || [];
|
||||||
|
return buildQueryContext(formData, baseQueryObject => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
...baseQueryObject,
|
||||||
|
groupby: [],
|
||||||
|
metrics: [
|
||||||
|
{
|
||||||
|
aggregate: 'MIN',
|
||||||
|
column: {
|
||||||
|
columnName: column,
|
||||||
|
id: 1,
|
||||||
|
type: ColumnType.FLOAT,
|
||||||
|
},
|
||||||
|
expressionType: 'SIMPLE',
|
||||||
|
hasCustomLabel: true,
|
||||||
|
label: 'min',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
aggregate: 'MAX',
|
||||||
|
column: {
|
||||||
|
columnName: column,
|
||||||
|
id: 2,
|
||||||
|
type: ColumnType.FLOAT,
|
||||||
|
},
|
||||||
|
expressionType: 'SIMPLE',
|
||||||
|
hasCustomLabel: true,
|
||||||
|
label: 'max',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import { t, validateNonEmpty } from '@superset-ui/core';
|
||||||
|
import { ControlPanelConfig, sections } from '@superset-ui/chart-controls';
|
||||||
|
|
||||||
|
const config: ControlPanelConfig = {
|
||||||
|
// For control input types, see: superset-frontend/src/explore/components/controls/index.js
|
||||||
|
controlPanelSections: [
|
||||||
|
// @ts-ignore
|
||||||
|
sections.legacyRegularTime,
|
||||||
|
{
|
||||||
|
label: t('Query'),
|
||||||
|
expanded: true,
|
||||||
|
controlSetRows: [['groupby'], ['adhoc_filters']],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
controlOverrides: {
|
||||||
|
groupby: {
|
||||||
|
validators: [validateNonEmpty],
|
||||||
|
clearable: false,
|
||||||
|
},
|
||||||
|
row_limit: {
|
||||||
|
default: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
42
superset-frontend/src/filters/components/Range/index.ts
Normal file
42
superset-frontend/src/filters/components/Range/index.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
|
||||||
|
import buildQuery from './buildQuery';
|
||||||
|
import controlPanel from './controlPanel';
|
||||||
|
import transformProps from './transformProps';
|
||||||
|
import thumbnail from './images/thumbnail.png';
|
||||||
|
|
||||||
|
export default class AntdRangeFilterPlugin extends ChartPlugin {
|
||||||
|
constructor() {
|
||||||
|
const metadata = new ChartMetadata({
|
||||||
|
name: t('Range Filter Plugin'),
|
||||||
|
description: 'Range Filter Plugin using AntD',
|
||||||
|
isNativeFilter: true,
|
||||||
|
thumbnail,
|
||||||
|
});
|
||||||
|
|
||||||
|
super({
|
||||||
|
buildQuery,
|
||||||
|
controlPanel,
|
||||||
|
loadChart: () => import('./AntdRangeFilter'),
|
||||||
|
metadata,
|
||||||
|
transformProps,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import { ChartProps } from '@superset-ui/core';
|
||||||
|
|
||||||
|
export default function transformProps(chartProps: ChartProps) {
|
||||||
|
const { formData, height, hooks, queriesData, width } = chartProps;
|
||||||
|
const { setExtraFormData } = hooks;
|
||||||
|
const { data } = queriesData[0];
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
formData,
|
||||||
|
height,
|
||||||
|
setExtraFormData,
|
||||||
|
width,
|
||||||
|
};
|
||||||
|
}
|
||||||
39
superset-frontend/src/filters/components/Range/types.ts
Normal file
39
superset-frontend/src/filters/components/Range/types.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
DataRecord,
|
||||||
|
QueryFormData,
|
||||||
|
SetExtraFormDataHook,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
import { AntdPluginFilterStylesProps } from '../types';
|
||||||
|
|
||||||
|
interface AntdPluginFilterSelectCustomizeProps {
|
||||||
|
max?: number;
|
||||||
|
min?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PluginFilterRangeQueryFormData = QueryFormData &
|
||||||
|
AntdPluginFilterStylesProps &
|
||||||
|
AntdPluginFilterSelectCustomizeProps;
|
||||||
|
|
||||||
|
export type AntdPluginFilterRangeProps = AntdPluginFilterStylesProps & {
|
||||||
|
data: DataRecord[];
|
||||||
|
formData: PluginFilterRangeQueryFormData;
|
||||||
|
setExtraFormData: SetExtraFormDataHook;
|
||||||
|
};
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import { styled } from '@superset-ui/core';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Select } from 'src/common/components';
|
||||||
|
import { DEFAULT_FORM_DATA, AntdPluginFilterSelectProps } from './types';
|
||||||
|
import { AntdPluginFilterStylesProps } from '../types';
|
||||||
|
import { getSelectExtraFormData } from '../../utils';
|
||||||
|
|
||||||
|
const Styles = styled.div<AntdPluginFilterStylesProps>`
|
||||||
|
height: ${({ height }) => height};
|
||||||
|
width: ${({ width }) => width};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
export default function AntdPluginFilterSelect(
|
||||||
|
props: AntdPluginFilterSelectProps,
|
||||||
|
) {
|
||||||
|
const [values, setValues] = useState<(string | number)[]>([]);
|
||||||
|
const { data, formData, height, width, setExtraFormData } = props;
|
||||||
|
const {
|
||||||
|
defaultValues,
|
||||||
|
enableEmptyFilter,
|
||||||
|
multiSelect,
|
||||||
|
showSearch,
|
||||||
|
inverseSelection,
|
||||||
|
} = {
|
||||||
|
...DEFAULT_FORM_DATA,
|
||||||
|
...formData,
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValues(defaultValues || []);
|
||||||
|
}, [defaultValues]);
|
||||||
|
|
||||||
|
let { groupby = [] } = formData;
|
||||||
|
groupby = Array.isArray(groupby) ? groupby : [groupby];
|
||||||
|
|
||||||
|
function handleChange(value?: number[] | string[] | null) {
|
||||||
|
setValues(value || []);
|
||||||
|
const [col] = groupby;
|
||||||
|
const emptyFilter =
|
||||||
|
enableEmptyFilter &&
|
||||||
|
!inverseSelection &&
|
||||||
|
(value === undefined || value === null || value.length === 0);
|
||||||
|
setExtraFormData(
|
||||||
|
getSelectExtraFormData(col, value, emptyFilter, inverseSelection),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const placeholderText =
|
||||||
|
(data || []).length === 0
|
||||||
|
? 'No data'
|
||||||
|
: `${data.length} option${data.length > 1 ? 's' : 0}`;
|
||||||
|
return (
|
||||||
|
<Styles height={height} width={width}>
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
value={values}
|
||||||
|
showSearch={showSearch}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
mode={multiSelect ? 'multiple' : undefined}
|
||||||
|
placeholder={placeholderText}
|
||||||
|
// @ts-ignore
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{(data || []).map(row => {
|
||||||
|
const option = `${groupby.map(col => row[col])[0]}`;
|
||||||
|
return (
|
||||||
|
<Option key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</Option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</Styles>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import { buildQueryContext, QueryFormData } from '@superset-ui/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The buildQuery function is used to create an instance of QueryContext that's
|
||||||
|
* sent to the chart data endpoint. In addition to containing information of which
|
||||||
|
* datasource to use, it specifies the type (e.g. full payload, samples, query) and
|
||||||
|
* format (e.g. CSV or JSON) of the result and whether or not to force refresh the data from
|
||||||
|
* the datasource as opposed to using a cached copy of the data, if available.
|
||||||
|
*
|
||||||
|
* More importantly though, QueryContext contains a property `queries`, which is an array of
|
||||||
|
* QueryObjects specifying individual data requests to be made. A QueryObject specifies which
|
||||||
|
* columns, metrics and filters, among others, to use during the query. Usually it will be enough
|
||||||
|
* to specify just one query based on the baseQueryObject, but for some more advanced use cases
|
||||||
|
* it is possible to define post processing operations in the QueryObject, or multiple queries
|
||||||
|
* if a viz needs multiple different result sets.
|
||||||
|
*/
|
||||||
|
export default function buildQuery(formData: QueryFormData) {
|
||||||
|
return buildQueryContext(formData, baseQueryObject => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
...baseQueryObject,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
114
superset-frontend/src/filters/components/Select/controlPanel.ts
Normal file
114
superset-frontend/src/filters/components/Select/controlPanel.ts
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import { t, validateNonEmpty } from '@superset-ui/core';
|
||||||
|
import { ControlPanelConfig, sections } from '@superset-ui/chart-controls';
|
||||||
|
import { DEFAULT_FORM_DATA } from './types';
|
||||||
|
|
||||||
|
const {
|
||||||
|
enableEmptyFilter,
|
||||||
|
fetchPredicate,
|
||||||
|
inverseSelection,
|
||||||
|
multiSelect,
|
||||||
|
showSearch,
|
||||||
|
} = DEFAULT_FORM_DATA;
|
||||||
|
|
||||||
|
const config: ControlPanelConfig = {
|
||||||
|
controlPanelSections: [
|
||||||
|
// @ts-ignore
|
||||||
|
sections.legacyRegularTime,
|
||||||
|
{
|
||||||
|
label: t('Query'),
|
||||||
|
expanded: true,
|
||||||
|
controlSetRows: [
|
||||||
|
['groupby'],
|
||||||
|
['metrics'],
|
||||||
|
['adhoc_filters'],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'multiSelect',
|
||||||
|
config: {
|
||||||
|
type: 'CheckboxControl',
|
||||||
|
label: t('Multiple Select'),
|
||||||
|
default: multiSelect,
|
||||||
|
description: t('Allow selecting multiple values'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'enableEmptyFilter',
|
||||||
|
config: {
|
||||||
|
type: 'CheckboxControl',
|
||||||
|
label: t('Enable Empty Filter'),
|
||||||
|
default: enableEmptyFilter,
|
||||||
|
description: t(
|
||||||
|
'When selection is empty, should an always false filter event be emitted',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'inverseSelection',
|
||||||
|
config: {
|
||||||
|
type: 'CheckboxControl',
|
||||||
|
label: t('Inverse Selection'),
|
||||||
|
default: inverseSelection,
|
||||||
|
description: t('Exclude selected values'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'showSearch',
|
||||||
|
config: {
|
||||||
|
type: 'CheckboxControl',
|
||||||
|
label: t('Search Field'),
|
||||||
|
default: showSearch,
|
||||||
|
description: t('Allow typing search terms'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: 'fetchPredicate',
|
||||||
|
config: {
|
||||||
|
type: 'TextControl',
|
||||||
|
label: t('Fetch predicate'),
|
||||||
|
default: fetchPredicate,
|
||||||
|
description: t(
|
||||||
|
'Predicate applied when fetching distinct value to populate the filter control component.',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
],
|
||||||
|
['row_limit', null],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
controlOverrides: {
|
||||||
|
groupby: {
|
||||||
|
multi: false,
|
||||||
|
validators: [validateNonEmpty],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
42
superset-frontend/src/filters/components/Select/index.ts
Normal file
42
superset-frontend/src/filters/components/Select/index.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import { t, ChartMetadata, ChartPlugin } from '@superset-ui/core';
|
||||||
|
import buildQuery from './buildQuery';
|
||||||
|
import controlPanel from './controlPanel';
|
||||||
|
import transformProps from './transformProps';
|
||||||
|
import thumbnail from './images/thumbnail.png';
|
||||||
|
|
||||||
|
export default class AntdFilterSelectPlugin extends ChartPlugin {
|
||||||
|
constructor() {
|
||||||
|
const metadata = new ChartMetadata({
|
||||||
|
name: t('Select Filter Plugin'),
|
||||||
|
description: 'Select Filter Plugin using AntD',
|
||||||
|
isNativeFilter: true,
|
||||||
|
thumbnail,
|
||||||
|
});
|
||||||
|
|
||||||
|
super({
|
||||||
|
buildQuery,
|
||||||
|
controlPanel,
|
||||||
|
loadChart: () => import('./AntdSelectFilter'),
|
||||||
|
metadata,
|
||||||
|
transformProps,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import { ChartProps } from '@superset-ui/core';
|
||||||
|
import { DEFAULT_FORM_DATA } from './types';
|
||||||
|
|
||||||
|
export default function transformProps(chartProps: ChartProps) {
|
||||||
|
const { formData, height, hooks, queriesData, width } = chartProps;
|
||||||
|
const newFormData = { ...DEFAULT_FORM_DATA, ...formData };
|
||||||
|
const { setExtraFormData = () => {} } = hooks;
|
||||||
|
|
||||||
|
const { data } = queriesData[0];
|
||||||
|
|
||||||
|
return {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
data,
|
||||||
|
formData: newFormData,
|
||||||
|
setExtraFormData,
|
||||||
|
};
|
||||||
|
}
|
||||||
52
superset-frontend/src/filters/components/Select/types.ts
Normal file
52
superset-frontend/src/filters/components/Select/types.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
QueryFormData,
|
||||||
|
DataRecord,
|
||||||
|
SetExtraFormDataHook,
|
||||||
|
} from '@superset-ui/core';
|
||||||
|
import { AntdPluginFilterStylesProps } from '../types';
|
||||||
|
|
||||||
|
interface AntdPluginFilterSelectCustomizeProps {
|
||||||
|
defaultValues?: (string | number)[];
|
||||||
|
enableEmptyFilter: boolean;
|
||||||
|
fetchPredicate?: string;
|
||||||
|
inverseSelection: boolean;
|
||||||
|
multiSelect: boolean;
|
||||||
|
showSearch: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AntdPluginFilterSelectQueryFormData = QueryFormData &
|
||||||
|
AntdPluginFilterStylesProps &
|
||||||
|
AntdPluginFilterSelectCustomizeProps;
|
||||||
|
|
||||||
|
export type AntdPluginFilterSelectProps = AntdPluginFilterStylesProps & {
|
||||||
|
data: DataRecord[];
|
||||||
|
setExtraFormData: SetExtraFormDataHook;
|
||||||
|
formData: AntdPluginFilterSelectQueryFormData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_FORM_DATA: AntdPluginFilterSelectCustomizeProps = {
|
||||||
|
defaultValues: [],
|
||||||
|
enableEmptyFilter: false,
|
||||||
|
fetchPredicate: '',
|
||||||
|
inverseSelection: false,
|
||||||
|
multiSelect: true,
|
||||||
|
showSearch: true,
|
||||||
|
};
|
||||||
20
superset-frontend/src/filters/components/index.ts
Normal file
20
superset-frontend/src/filters/components/index.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
export { default as AntdSelectFilterPlugin } from './Select';
|
||||||
|
export { default as AntdRangeFilterPlugin } from './Range';
|
||||||
22
superset-frontend/src/filters/components/types.ts
Normal file
22
superset-frontend/src/filters/components/types.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
export interface AntdPluginFilterStylesProps {
|
||||||
|
height: number;
|
||||||
|
width: number;
|
||||||
|
}
|
||||||
65
superset-frontend/src/filters/utils.ts
Normal file
65
superset-frontend/src/filters/utils.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
import { QueryObjectFilterClause } from '@superset-ui/core';
|
||||||
|
|
||||||
|
export const getSelectExtraFormData = (
|
||||||
|
col: string,
|
||||||
|
value?: undefined | null | string[] | number[],
|
||||||
|
emptyFilter = false,
|
||||||
|
inverseSelection = false,
|
||||||
|
) => ({
|
||||||
|
append_form_data: emptyFilter
|
||||||
|
? {
|
||||||
|
extras: {
|
||||||
|
where: '1 = 0',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
filters:
|
||||||
|
value === undefined || value === null || value.length === 0
|
||||||
|
? []
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
col,
|
||||||
|
op: inverseSelection ? ('NOT IN' as const) : ('IN' as const),
|
||||||
|
val: value,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getRangeExtraFormData = (
|
||||||
|
col: string,
|
||||||
|
lower?: number | null,
|
||||||
|
upper?: number | null,
|
||||||
|
) => {
|
||||||
|
const filters: QueryObjectFilterClause[] = [];
|
||||||
|
if (lower !== undefined && lower !== null) {
|
||||||
|
filters.push({ col, op: '>=', val: lower });
|
||||||
|
}
|
||||||
|
if (upper !== undefined && upper !== null) {
|
||||||
|
filters.push({ col, op: '<=', val: upper });
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
append_form_data: {
|
||||||
|
filters,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -60,10 +60,9 @@ import {
|
|||||||
EchartsTimeseriesChartPlugin,
|
EchartsTimeseriesChartPlugin,
|
||||||
} from '@superset-ui/plugin-chart-echarts';
|
} from '@superset-ui/plugin-chart-echarts';
|
||||||
import {
|
import {
|
||||||
AntdRangeFilterPlugin,
|
|
||||||
AntdSelectFilterPlugin,
|
AntdSelectFilterPlugin,
|
||||||
} from '@superset-ui/plugin-filter-antd';
|
AntdRangeFilterPlugin,
|
||||||
|
} from 'src/filters/components/';
|
||||||
import FilterBoxChartPlugin from '../FilterBox/FilterBoxChartPlugin';
|
import FilterBoxChartPlugin from '../FilterBox/FilterBoxChartPlugin';
|
||||||
import TimeTableChartPlugin from '../TimeTable/TimeTableChartPlugin';
|
import TimeTableChartPlugin from '../TimeTable/TimeTableChartPlugin';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user