feat(native-filters): enable filter indicator and make datasource optional (#13148)

* refactor: continue decouple native filters

* refactor(native-filters): decouple native filter parms from config modal

* lint: fix TS errors

* lint: fix TS errors

* refactor: update filter badge

* refactor: rename ant filters

* test: fix tests

* test: fix tests

* refactor: remove 18 from dataset
This commit is contained in:
simcha90 2021-02-16 16:12:35 +02:00 committed by GitHub
parent 5ab613d89e
commit 5aa38ef929
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 292 additions and 288 deletions

View File

@ -19,7 +19,16 @@
export default { export default {
id: 1234, id: 1234,
slug: 'dashboardSlug', slug: 'dashboardSlug',
metadata: {}, metadata: {
filter_configuration: [
{
id: 'DefaultsID',
filterType: 'filter_select',
targets: [{}],
cascadeParentIds: [],
},
],
},
userId: 'mock_user_id', userId: 'mock_user_id',
dash_edit_perm: true, dash_edit_perm: true,
dash_save_perm: true, dash_save_perm: true,

View File

@ -16,7 +16,6 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { FilterType } from 'src/dashboard/components/nativeFilters/types';
import { NativeFiltersState } from 'src/dashboard/reducers/types'; import { NativeFiltersState } from 'src/dashboard/reducers/types';
export const nativeFilters: NativeFiltersState = { export const nativeFilters: NativeFiltersState = {
@ -24,7 +23,7 @@ export const nativeFilters: NativeFiltersState = {
'NATIVE_FILTER-e7Q8zKixx': { 'NATIVE_FILTER-e7Q8zKixx': {
id: 'NATIVE_FILTER-e7Q8zKixx', id: 'NATIVE_FILTER-e7Q8zKixx',
name: 'region', name: 'region',
filterType: FilterType.filter_select, filterType: 'filter_select',
targets: [ targets: [
{ {
datasetId: 2, datasetId: 2,
@ -49,7 +48,7 @@ export const nativeFilters: NativeFiltersState = {
'NATIVE_FILTER-x9QPw0so1': { 'NATIVE_FILTER-x9QPw0so1': {
id: 'NATIVE_FILTER-x9QPw0so1', id: 'NATIVE_FILTER-x9QPw0so1',
name: 'country_code', name: 'country_code',
filterType: FilterType.filter_select, filterType: 'filter_select',
targets: [ targets: [
{ {
datasetId: 2, datasetId: 2,
@ -75,6 +74,9 @@ export const nativeFilters: NativeFiltersState = {
filtersState: { filtersState: {
'NATIVE_FILTER-e7Q8zKixx': { 'NATIVE_FILTER-e7Q8zKixx': {
id: 'NATIVE_FILTER-e7Q8zKixx', id: 'NATIVE_FILTER-e7Q8zKixx',
currentState: {
value: ['East Asia & Pacific'],
},
extraFormData: { extraFormData: {
append_form_data: { append_form_data: {
filters: [ filters: [
@ -128,6 +130,9 @@ export const singleNativeFiltersState = {
[NATIVE_FILTER_ID]: { [NATIVE_FILTER_ID]: {
id: NATIVE_FILTER_ID, id: NATIVE_FILTER_ID,
extraFormData, extraFormData,
currentState: {
value: ['No, not an ethnic minority'],
},
}, },
}, },
}; };

View File

@ -40,10 +40,25 @@ Object.defineProperty(window, 'matchMedia', {
})), })),
}); });
jest.mock('@superset-ui/core', () => ({
// @ts-ignore
...jest.requireActual('@superset-ui/core'),
getChartMetadataRegistry: () => ({
items: {
filter_select: {
value: {
datasourceCount: 1,
behaviors: ['NATIVE_FILTER'],
},
},
},
}),
}));
describe('FiltersConfigModal', () => { describe('FiltersConfigModal', () => {
const mockedProps = { const mockedProps = {
isOpen: true, isOpen: true,
initialFilterId: 'DefaultFilterId', initialFilterId: 'DefaultsID',
createNewOnOpen: true, createNewOnOpen: true,
onCancel: jest.fn(), onCancel: jest.fn(),
save: jest.fn(), save: jest.fn(),

View File

@ -18,10 +18,10 @@
*/ */
export const nativeFiltersInfo = { export const nativeFiltersInfo = {
filters: { filters: {
DefaultID1: { DefaultsID: {
id: 'DefaultID1', id: 'DefaultsID',
name: 'test', name: 'test',
type: 'text', type: 'filter_select',
targets: [ targets: [
{ {
datasetId: 0, datasetId: 0,

View File

@ -18,10 +18,9 @@
*/ */
import { TIME_FILTER_MAP } from 'src/explore/constants'; import { TIME_FILTER_MAP } from 'src/explore/constants';
import { getChartIdsInFilterScope } from 'src/dashboard/util/activeDashboardFilters'; import { getChartIdsInFilterScope } from 'src/dashboard/util/activeDashboardFilters';
import { import { NativeFiltersState } from 'src/dashboard/reducers/types';
NativeFiltersState, import { getTreeCheckedItems } from '../nativeFilters/FilterConfigModal/utils';
NativeFilterState, import { Layout } from '../../types';
} from 'src/dashboard/reducers/types';
export enum IndicatorStatus { export enum IndicatorStatus {
Unset = 'UNSET', Unset = 'UNSET',
@ -130,7 +129,7 @@ const getRejectedColumns = (chart: any): Set<string> =>
); );
export type Indicator = { export type Indicator = {
column: string; column?: string;
name: string; name: string;
value: string[]; value: string[];
status: IndicatorStatus; status: IndicatorStatus;
@ -172,50 +171,52 @@ export const selectIndicatorsForChart = (
return indicators; return indicators;
}; };
const selectNativeIndicatorValue = (
filterState: NativeFilterState,
): string[] => {
const filters = filterState?.extraFormData?.append_form_data?.filters;
if (filters?.length) {
const filter = filters[0];
if ('val' in filter) {
const val = filter.val as string | string[];
if (Array.isArray(val)) {
return val;
}
return [val];
}
}
return [];
};
export const selectNativeIndicatorsForChart = ( export const selectNativeIndicatorsForChart = (
nativeFilters: NativeFiltersState, nativeFilters: NativeFiltersState,
chartId: number, chartId: number,
charts: any, charts: any,
dashboardLayout: Layout,
): Indicator[] => { ): Indicator[] => {
const chart = charts[chartId]; const chart = charts[chartId];
const appliedColumns = getAppliedColumns(chart); const appliedColumns = getAppliedColumns(chart);
const rejectedColumns = getRejectedColumns(chart); const rejectedColumns = getRejectedColumns(chart);
const getStatus = (column: string, value: string[]): IndicatorStatus => { const getStatus = (
if (rejectedColumns.has(column)) return IndicatorStatus.Incompatible; value: string[],
if (appliedColumns.has(column) && value.length > 0) { isAffectedByScope: boolean,
column?: string,
): IndicatorStatus => {
if (!column && isAffectedByScope) {
// Filter without datasource
return IndicatorStatus.Applied;
}
if (column && rejectedColumns.has(column))
return IndicatorStatus.Incompatible;
if (column && appliedColumns.has(column) && value.length > 0) {
return IndicatorStatus.Applied; return IndicatorStatus.Applied;
} }
return IndicatorStatus.Unset; return IndicatorStatus.Unset;
}; };
const indicators = Object.values(nativeFilters.filters).map(nativeFilter => { const indicators = Object.values(nativeFilters.filters).map(nativeFilter => {
const column = nativeFilter.targets[0].column.name; const isAffectedByScope = getTreeCheckedItems(
nativeFilter.scope,
dashboardLayout,
).some(
layoutItem => dashboardLayout[layoutItem]?.meta?.chartId === chartId,
);
const column = nativeFilter.targets[0]?.column?.name;
const filterState = nativeFilters.filtersState[nativeFilter.id]; const filterState = nativeFilters.filtersState[nativeFilter.id];
const value = selectNativeIndicatorValue(filterState); let value = filterState?.currentState?.value ?? [];
if (!Array.isArray(value)) {
value = [value];
}
return { return {
column, column,
name: nativeFilter.name, name: nativeFilter.name,
path: [nativeFilter.id], path: [nativeFilter.id],
status: getStatus(column, value), status: getStatus(value, isAffectedByScope, column),
value, value,
}; };
}); });

View File

@ -51,15 +51,19 @@ const FilterValue: React.FC<FilterProps> = ({
const { id, targets, filterType } = filter; const { id, targets, filterType } = filter;
const cascadingFilters = useCascadingFilters(id); const cascadingFilters = useCascadingFilters(id);
const filterState = useFilterState(id); const filterState = useFilterState(id);
const [loading, setLoading] = useState<boolean>(true);
const [state, setState] = useState([]); const [state, setState] = useState([]);
const [error, setError] = useState<boolean>(false); const [error, setError] = useState<boolean>(false);
const [formData, setFormData] = useState<Partial<QueryFormData>>({}); const [formData, setFormData] = useState<Partial<QueryFormData>>({});
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const [target] = targets; const [target] = targets;
const { datasetId = 18, column } = target; const {
datasetId,
column = {},
}: Partial<{ datasetId: number; column: { name?: string } }> = target;
const { name: groupby } = column; const { name: groupby } = column;
const currentValue = filterState.currentState?.value; const currentValue = filterState.currentState?.value;
const hasDataSource = !!(datasetId && groupby);
const [loading, setLoading] = useState<boolean>(hasDataSource);
useEffect(() => { useEffect(() => {
const newFormData = getFormData({ const newFormData = getFormData({
datasetId, datasetId,
@ -71,6 +75,9 @@ const FilterValue: React.FC<FilterProps> = ({
}); });
if (!areObjectsEqual(formData || {}, newFormData)) { if (!areObjectsEqual(formData || {}, newFormData)) {
setFormData(newFormData); setFormData(newFormData);
if (!hasDataSource) {
return;
}
getChartDataRequest({ getChartDataRequest({
formData: newFormData, formData: newFormData,
force: false, force: false,
@ -131,7 +138,8 @@ const FilterValue: React.FC<FilterProps> = ({
height={20} height={20}
width={220} width={220}
formData={formData} formData={formData}
queriesData={state} // For charts that don't have datasource we need workaround for empty placeholder
queriesData={hasDataSource ? state : [{ data: [null] }]}
chartType={filterType} chartType={filterType}
// @ts-ignore (update superset-ui) // @ts-ignore (update superset-ui)
hooks={{ setExtraFormData }} hooks={{ setExtraFormData }}

View File

@ -21,6 +21,8 @@ import {
SuperChart, SuperChart,
t, t,
getChartControlPanelRegistry, getChartControlPanelRegistry,
getChartMetadataRegistry,
Behavior,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { FormInstance } from 'antd/lib/form'; import { FormInstance } from 'antd/lib/form';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
@ -39,15 +41,10 @@ import { CustomControlItem } from '@superset-ui/chart-controls';
import { ColumnSelect } from './ColumnSelect'; import { ColumnSelect } from './ColumnSelect';
import { NativeFiltersForm } from './types'; import { NativeFiltersForm } from './types';
import FilterScope from './FilterScope'; import FilterScope from './FilterScope';
import { import { getControlItems, setFilterFieldValues, useForceUpdate } from './utils';
FilterTypeNames,
getControlItems,
setFilterFieldValues,
useForceUpdate,
} from './utils';
import { useBackendFormUpdate } from './state'; import { useBackendFormUpdate } from './state';
import { getFormData } from '../utils'; import { getFormData } from '../utils';
import { Filter, FilterType } from '../types'; import { Filter } from '../types';
type DatasetSelectValue = { type DatasetSelectValue = {
value: number; value: number;
@ -121,9 +118,25 @@ export const FilterConfigForm: React.FC<FilterConfigFormProps> = ({
const controlItems = getControlItems( const controlItems = getControlItems(
controlPanelRegistry.get(formFilter?.filterType), controlPanelRegistry.get(formFilter?.filterType),
); );
useBackendFormUpdate(form, filterId, filterToEdit);
const initDatasetId = filterToEdit?.targets[0].datasetId; const nativeFilterItems = getChartMetadataRegistry().items;
const nativeFilterVizTypes = Object.entries(nativeFilterItems)
// @ts-ignore
.filter(([, { value }]) =>
value.behaviors?.includes(Behavior.NATIVE_FILTER),
)
.map(([key]) => key);
// @ts-ignore
const hasDatasource = !!nativeFilterItems[formFilter?.filterType]?.value
?.datasourceCount;
const hasFilledDatasource =
(formFilter?.dataset && formFilter?.column) || !hasDatasource;
useBackendFormUpdate(form, filterId, filterToEdit, hasDatasource);
const initDatasetId = filterToEdit?.targets[0]?.datasetId;
const initColumn = filterToEdit?.targets[0]?.column?.name; const initColumn = filterToEdit?.targets[0]?.column?.name;
const newFormData = getFormData({ const newFormData = getFormData({
datasetId: formFilter?.dataset?.value, datasetId: formFilter?.dataset?.value,
@ -179,69 +192,76 @@ export const FilterConfigForm: React.FC<FilterConfigFormProps> = ({
<Input /> <Input />
</StyledFormItem> </StyledFormItem>
<StyledFormItem <StyledFormItem
name={['filters', filterId, 'dataset']} name={['filters', filterId, 'filterType']}
initialValue={{ value: initDatasetId }} rules={[{ required: !removed, message: t('Name is required') }]}
label={<StyledLabel>{t('Datasource')}</StyledLabel>} initialValue={filterToEdit?.filterType || 'filter_select'}
rules={[{ required: !removed, message: t('Datasource is required') }]} label={<StyledLabel>{t('Filter Type')}</StyledLabel>}
data-test="datasource-input"
> >
<SupersetResourceSelect <Select
initialId={initDatasetId} options={nativeFilterVizTypes.map(filterType => ({
resource="dataset" value: filterType,
searchColumn="table_name" // @ts-ignore
transformItem={datasetToSelectOption} label: nativeFilterItems[filterType]?.value.name,
isMulti={false} }))}
onError={onDatasetSelectError} onChange={({ value }: { value: string }) => {
onChange={e => { setFilterFieldValues(form, filterId, {
// We need reset column when dataset changed filterType: value,
const datasetId = formFilter?.dataset?.value; defaultValue: null,
if (datasetId && e?.value !== datasetId) { });
setFilterFieldValues(form, filterId, {
column: null,
});
}
forceUpdate(); forceUpdate();
}} }}
/> />
</StyledFormItem> </StyledFormItem>
</StyledContainer> </StyledContainer>
<StyledFormItem {hasDatasource && (
// don't show the column select unless we have a dataset <>
// style={{ display: datasetId == null ? undefined : 'none' }} <StyledFormItem
name={['filters', filterId, 'column']} name={['filters', filterId, 'dataset']}
initialValue={initColumn} initialValue={{ value: initDatasetId }}
label={<StyledLabel>{t('Field')}</StyledLabel>} label={<StyledLabel>{t('Datasource')}</StyledLabel>}
rules={[{ required: !removed, message: t('Field is required') }]} rules={[
data-test="field-input" { required: !removed, message: t('Datasource is required') },
> ]}
<ColumnSelect data-test="datasource-input"
form={form} >
filterId={filterId} <SupersetResourceSelect
datasetId={formFilter?.dataset?.value} initialId={initDatasetId}
onChange={forceUpdate} resource="dataset"
/> searchColumn="table_name"
</StyledFormItem> transformItem={datasetToSelectOption}
<StyledFormItem isMulti={false}
name={['filters', filterId, 'filterType']} onError={onDatasetSelectError}
rules={[{ required: !removed, message: t('Name is required') }]} onChange={e => {
initialValue={filterToEdit?.filterType || FilterType.filter_select} // We need reset column when dataset changed
label={<StyledLabel>{t('Filter Type')}</StyledLabel>} const datasetId = formFilter?.dataset?.value;
> if (datasetId && e?.value !== datasetId) {
<Select setFilterFieldValues(form, filterId, {
options={Object.values(FilterType).map(filterType => ({ column: null,
value: filterType, });
label: FilterTypeNames[filterType], }
}))} forceUpdate();
onChange={({ value }: { value: FilterType }) => { }}
setFilterFieldValues(form, filterId, { />
filterType: value, </StyledFormItem>
defaultValue: null, <StyledFormItem
}); // don't show the column select unless we have a dataset
forceUpdate(); // style={{ display: datasetId == null ? undefined : 'none' }}
}} name={['filters', filterId, 'column']}
/> initialValue={initColumn}
</StyledFormItem> label={<StyledLabel>{t('Field')}</StyledLabel>}
{formFilter?.dataset && formFilter?.column && ( rules={[{ required: !removed, message: t('Field is required') }]}
data-test="field-input"
>
<ColumnSelect
form={form}
filterId={filterId}
datasetId={formFilter?.dataset?.value}
onChange={forceUpdate}
/>
</StyledFormItem>
</>
)}
{hasFilledDatasource && (
<CleanFormItem <CleanFormItem
name={['filters', filterId, 'defaultValueFormData']} name={['filters', filterId, 'defaultValueFormData']}
hidden hidden
@ -259,25 +279,29 @@ export const FilterConfigForm: React.FC<FilterConfigFormProps> = ({
data-test="default-input" data-test="default-input"
label={<StyledLabel>{t('Default Value')}</StyledLabel>} label={<StyledLabel>{t('Default Value')}</StyledLabel>}
> >
{formFilter?.dataset && {((hasFilledDatasource && formFilter?.defaultValueQueriesData) ||
formFilter?.column && !hasDatasource) && (
formFilter?.defaultValueQueriesData && ( <SuperChart
<SuperChart height={25}
height={25} width={250}
width={250} formData={newFormData}
formData={newFormData} // For charts that don't have datasource we need workaround for empty placeholder
queriesData={formFilter?.defaultValueQueriesData} queriesData={
chartType={formFilter?.filterType} hasDatasource
hooks={{ ? formFilter?.defaultValueQueriesData
setExtraFormData: ({ currentState }) => { : [{ data: [null] }]
setFilterFieldValues(form, filterId, { }
defaultValue: currentState?.value, chartType={formFilter?.filterType}
}); hooks={{
forceUpdate(); setExtraFormData: ({ currentState }) => {
}, setFilterFieldValues(form, filterId, {
}} defaultValue: currentState?.value,
/> });
)} forceUpdate();
},
}}
/>
)}
</StyledFormItem> </StyledFormItem>
<StyledFormItem <StyledFormItem
name={['filters', filterId, 'parentFilter']} name={['filters', filterId, 'parentFilter']}

View File

@ -400,20 +400,22 @@ export function FilterConfigModal({
const formInputs = values.filters[id]; const formInputs = values.filters[id];
// if user didn't open a filter, return the original config // if user didn't open a filter, return the original config
if (!formInputs) return filterConfigMap[id]; if (!formInputs) return filterConfigMap[id];
let target = {};
if (formInputs.dataset && formInputs.column) {
target = {
datasetId: formInputs.dataset.value,
column: {
name: formInputs.column,
},
};
}
return { return {
id, id,
controlValues: formInputs.controlValues, controlValues: formInputs.controlValues,
name: formInputs.name, name: formInputs.name,
filterType: formInputs.filterType, filterType: formInputs.filterType,
// for now there will only ever be one target // for now there will only ever be one target
targets: [ targets: [target],
{
datasetId: formInputs.dataset.value,
column: {
name: formInputs.column,
},
},
],
defaultValue: formInputs.defaultValue || null, defaultValue: formInputs.defaultValue || null,
cascadeParentIds: formInputs.parentFilter cascadeParentIds: formInputs.parentFilter
? [formInputs.parentFilter.value] ? [formInputs.parentFilter.value]

View File

@ -1,67 +0,0 @@
/**
* 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 React from 'react';
import { styled } from '@superset-ui/core';
import { Button } from 'src/common/components';
import Icon from 'src/components/Icon';
import { useFilterConfiguration } from '../state';
interface Args {
filter: any;
index: number;
}
interface FiltersListProps {
setEditFilter: (arg0: Args) => void;
setDataset: (arg0: any) => void;
}
const FiltersStyle = styled.div`
display: flex;
flex-direction: row;
`;
const FiltersList = ({ setEditFilter, setDataset }: FiltersListProps) => {
const filterConfigs = useFilterConfiguration();
<>
{filterConfigs.map((filter, i: number) => (
<FiltersStyle>
<Button
type="link"
key={filter.name}
onClick={() => {
setEditFilter({ filter, index: i });
setDataset(filter.targets[0].datasetId);
}}
>
{filter.name}
</Button>
<span
role="button"
title="Edit dashboard"
tabIndex={0}
className="action-button"
>
<Icon name="trash" />
</span>
</FiltersStyle>
))}
</>;
};
export default FiltersList;

View File

@ -79,11 +79,16 @@ export const useBackendFormUpdate = (
form: FormInstance<NativeFiltersForm>, form: FormInstance<NativeFiltersForm>,
filterId: string, filterId: string,
filterToEdit?: Filter, filterToEdit?: Filter,
hasDatasource?: boolean,
) => { ) => {
const forceUpdate = useForceUpdate(); const forceUpdate = useForceUpdate();
const formFilter = (form.getFieldValue('filters') || {})[filterId]; const formFilter = (form.getFieldValue('filters') || {})[filterId];
useEffect(() => { useEffect(() => {
let resolvedDefaultValue: any = null; let resolvedDefaultValue: any = null;
if (!hasDatasource) {
forceUpdate();
return;
}
// No need to check data set change because it cascading update column // No need to check data set change because it cascading update column
// So check that column exists is enough // So check that column exists is enough
if (!formFilter?.column) { if (!formFilter?.column) {
@ -107,7 +112,7 @@ export const useBackendFormUpdate = (
if ( if (
filterToEdit?.filterType === formFilter?.filterType && filterToEdit?.filterType === formFilter?.filterType &&
filterToEdit?.targets[0].datasetId === formFilter?.dataset?.value && filterToEdit?.targets[0].datasetId === formFilter?.dataset?.value &&
formFilter?.column === filterToEdit?.targets[0]?.column?.name formFilter?.column === filterToEdit?.targets[0].column?.name
) { ) {
resolvedDefaultValue = filterToEdit?.defaultValue; resolvedDefaultValue = filterToEdit?.defaultValue;
} }

View File

@ -17,7 +17,7 @@
* under the License. * under the License.
*/ */
import { QueryObjectFilterClause } from '@superset-ui/core'; import { QueryObjectFilterClause } from '@superset-ui/core';
import { Column, FilterType, Scope } from '../types'; import { Column, Scope } from '../types';
export enum Scoping { export enum Scoping {
all, all,
@ -30,7 +30,7 @@ export type AntCallback = (value1?: any, value2?: any) => void;
export interface NativeFiltersFormItem { export interface NativeFiltersFormItem {
scope: Scope; scope: Scope;
name: string; name: string;
filterType: FilterType; filterType: string;
dataset: { dataset: {
value: number; value: number;
label: string; label: string;

View File

@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
import { t } from '@superset-ui/core';
import { flatMapDeep } from 'lodash'; import { flatMapDeep } from 'lodash';
import { Charts, Layout, LayoutItem } from 'src/dashboard/types'; import { Charts, Layout, LayoutItem } from 'src/dashboard/types';
import { import {
@ -29,7 +29,7 @@ import React from 'react';
import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants'; import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants';
import { CustomControlItem } from '@superset-ui/chart-controls'; import { CustomControlItem } from '@superset-ui/chart-controls';
import { TreeItem } from './types'; import { TreeItem } from './types';
import { FilterType, Scope } from '../types'; import { Scope } from '../types';
export const useForceUpdate = () => { export const useForceUpdate = () => {
const [, updateState] = React.useState({}); const [, updateState] = React.useState({});
@ -155,12 +155,6 @@ export const findFilterScope = (
}; };
}; };
export const FilterTypeNames = {
[FilterType.filter_select]: t('Select'),
[FilterType.filter_range]: t('Range'),
[FilterType.filter_time]: t('Time'),
};
export const setFilterFieldValues = ( export const setFilterFieldValues = (
form: FormInstance, form: FormInstance,
filterId: string, filterId: string,

View File

@ -27,12 +27,6 @@ export interface Scope {
excluded: number[]; excluded: number[];
} }
export enum FilterType {
filter_select = 'filter_select',
filter_range = 'filter_range',
filter_time = 'filter_time',
}
/** The target of a filter is the datasource/column being filtered */ /** The target of a filter is the datasource/column being filtered */
export interface Target { export interface Target {
datasetId: number; datasetId: number;
@ -51,10 +45,10 @@ export interface Filter {
id: string; // randomly generated at filter creation id: string; // randomly generated at filter creation
name: string; name: string;
scope: Scope; scope: Scope;
filterType: FilterType; filterType: string;
// for now there will only ever be one target // for now there will only ever be one target
// when multiple targets are supported, change this to Target[] // when multiple targets are supported, change this to Target[]
targets: [Target]; targets: [Partial<Target>];
controlValues: { controlValues: {
[key: string]: any; [key: string]: any;
}; };

View File

@ -41,26 +41,34 @@ export const getFormData = ({
datasetId?: number; datasetId?: number;
inputRef?: RefObject<HTMLInputElement>; inputRef?: RefObject<HTMLInputElement>;
cascadingFilters?: object; cascadingFilters?: object;
groupby: string; groupby?: string;
}): Partial<QueryFormData> => ({ }): Partial<QueryFormData> => {
adhoc_filters: [], let otherProps: { datasource?: string; groupby?: string[] } = {};
datasource: `${datasetId}__table`, if (datasetId && groupby) {
extra_filters: [], otherProps = {
extra_form_data: cascadingFilters, datasource: `${datasetId}__table`,
granularity_sqla: 'ds', groupby: [groupby],
groupby: [groupby], };
metrics: ['count'], }
row_limit: 10000, return {
showSearch: true, adhoc_filters: [],
currentValue, extra_filters: [],
defaultValue, extra_form_data: cascadingFilters,
time_range: 'No filter', granularity_sqla: 'ds',
time_range_endpoints: ['inclusive', 'exclusive'], metrics: ['count'],
url_params: {}, row_limit: 10000,
viz_type: 'filter_select', showSearch: true,
inputRef, currentValue,
...controlValues, defaultValue,
}); time_range: 'No filter',
time_range_endpoints: ['inclusive', 'exclusive'],
url_params: {},
viz_type: 'filter_select',
inputRef,
...controlValues,
...otherProps,
};
};
export function mergeExtraFormData( export function mergeExtraFormData(
originalExtra: ExtraFormData, originalExtra: ExtraFormData,

View File

@ -52,7 +52,13 @@ const sortByStatus = (indicators: Indicator[]): Indicator[] => {
}; };
const mapStateToProps = ( const mapStateToProps = (
{ datasources, dashboardFilters, nativeFilters, charts }: any, {
datasources,
dashboardFilters,
nativeFilters,
charts,
dashboardLayout: { present },
}: any,
{ chartId }: FiltersBadgeProps, { chartId }: FiltersBadgeProps,
) => { ) => {
const dashboardIndicators = selectIndicatorsForChart( const dashboardIndicators = selectIndicatorsForChart(
@ -66,6 +72,7 @@ const mapStateToProps = (
nativeFilters, nativeFilters,
chartId, chartId,
charts, charts,
present,
); );
const indicators = uniqWith( const indicators = uniqWith(

View File

@ -19,16 +19,16 @@
import { styled } from '@superset-ui/core'; import { styled } from '@superset-ui/core';
import React from 'react'; import React from 'react';
import { Slider } from 'src/common/components'; import { Slider } from 'src/common/components';
import { AntdPluginFilterRangeProps } from './types'; import { PluginFilterRangeProps } from './types';
import { AntdPluginFilterStylesProps } from '../types'; import { PluginFilterStylesProps } from '../types';
import { getRangeExtraFormData } from '../../utils'; import { getRangeExtraFormData } from '../../utils';
const Styles = styled.div<AntdPluginFilterStylesProps>` const Styles = styled.div<PluginFilterStylesProps>`
height: ${({ height }) => height}; height: ${({ height }) => height};
width: ${({ width }) => width}; width: ${({ width }) => width};
`; `;
export default function AntdRangeFilter(props: AntdPluginFilterRangeProps) { export default function RangeFilterPlugin(props: PluginFilterRangeProps) {
const { data, formData, height, width, setExtraFormData, inputRef } = props; const { data, formData, height, width, setExtraFormData, inputRef } = props;
const [row] = data; const [row] = data;
// @ts-ignore // @ts-ignore

View File

@ -22,10 +22,10 @@ import controlPanel from './controlPanel';
import transformProps from './transformProps'; import transformProps from './transformProps';
import thumbnail from './images/thumbnail.png'; import thumbnail from './images/thumbnail.png';
export default class AntdRangeFilterPlugin extends ChartPlugin { export default class RangeFilterPlugin extends ChartPlugin {
constructor() { constructor() {
const metadata = new ChartMetadata({ const metadata = new ChartMetadata({
name: t('Range filter plugin'), name: t('Range filter'),
description: 'Range filter plugin using AntD', description: 'Range filter plugin using AntD',
behaviors: [Behavior.CROSS_FILTER, Behavior.NATIVE_FILTER], behaviors: [Behavior.CROSS_FILTER, Behavior.NATIVE_FILTER],
thumbnail, thumbnail,
@ -34,7 +34,7 @@ export default class AntdRangeFilterPlugin extends ChartPlugin {
super({ super({
buildQuery, buildQuery,
controlPanel, controlPanel,
loadChart: () => import('./AntdRangeFilter'), loadChart: () => import('./RangeFilterPlugin'),
metadata, metadata,
transformProps, transformProps,
}); });

View File

@ -22,18 +22,18 @@ import {
SetExtraFormDataHook, SetExtraFormDataHook,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { RefObject } from 'react'; import { RefObject } from 'react';
import { AntdPluginFilterStylesProps } from '../types'; import { PluginFilterStylesProps } from '../types';
interface AntdPluginFilterSelectCustomizeProps { interface PluginFilterSelectCustomizeProps {
max?: number; max?: number;
min?: number; min?: number;
} }
export type PluginFilterRangeQueryFormData = QueryFormData & export type PluginFilterRangeQueryFormData = QueryFormData &
AntdPluginFilterStylesProps & PluginFilterStylesProps &
AntdPluginFilterSelectCustomizeProps; PluginFilterSelectCustomizeProps;
export type AntdPluginFilterRangeProps = AntdPluginFilterStylesProps & { export type PluginFilterRangeProps = PluginFilterStylesProps & {
data: DataRecord[]; data: DataRecord[];
formData: PluginFilterRangeQueryFormData; formData: PluginFilterRangeQueryFormData;
setExtraFormData: SetExtraFormDataHook; setExtraFormData: SetExtraFormDataHook;

View File

@ -21,10 +21,10 @@ import { action } from '@storybook/addon-actions';
import { boolean, withKnobs } from '@storybook/addon-knobs'; import { boolean, withKnobs } from '@storybook/addon-knobs';
import { SuperChart, getChartTransformPropsRegistry } from '@superset-ui/core'; import { SuperChart, getChartTransformPropsRegistry } from '@superset-ui/core';
import { mockQueryDataForCountries } from 'spec/fixtures/mockNativeFilters'; import { mockQueryDataForCountries } from 'spec/fixtures/mockNativeFilters';
import AntdSelectFilterPlugin from './index'; import SelectFilterPlugin from './index';
import transformProps from './transformProps'; import transformProps from './transformProps';
new AntdSelectFilterPlugin().configure({ key: 'filter_select' }).register(); new SelectFilterPlugin().configure({ key: 'filter_select' }).register();
getChartTransformPropsRegistry().registerValue('filter_select', transformProps); getChartTransformPropsRegistry().registerValue('filter_select', transformProps);

View File

@ -19,20 +19,18 @@
import { styled } from '@superset-ui/core'; import { styled } from '@superset-ui/core';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Select } from 'src/common/components'; import { Select } from 'src/common/components';
import { AntdPluginFilterSelectProps } from './types'; import { PluginFilterSelectProps } from './types';
import { AntdPluginFilterStylesProps } from '../types'; import { PluginFilterStylesProps } from '../types';
import { getSelectExtraFormData } from '../../utils'; import { getSelectExtraFormData } from '../../utils';
const Styles = styled.div<AntdPluginFilterStylesProps>` const Styles = styled.div<PluginFilterStylesProps>`
height: ${({ height }) => height}; height: ${({ height }) => height};
width: ${({ width }) => width}; width: ${({ width }) => width};
`; `;
const { Option } = Select; const { Option } = Select;
export default function AntdPluginFilterSelect( export default function PluginFilterSelect(props: PluginFilterSelectProps) {
props: AntdPluginFilterSelectProps,
) {
const { data, formData, height, width, setExtraFormData } = props; const { data, formData, height, width, setExtraFormData } = props;
const { const {
defaultValue, defaultValue,

View File

@ -22,10 +22,10 @@ import controlPanel from './controlPanel';
import transformProps from './transformProps'; import transformProps from './transformProps';
import thumbnail from './images/thumbnail.png'; import thumbnail from './images/thumbnail.png';
export default class AntdFilterSelectPlugin extends ChartPlugin { export default class FilterSelectPlugin extends ChartPlugin {
constructor() { constructor() {
const metadata = new ChartMetadata({ const metadata = new ChartMetadata({
name: t('Select filter plugin'), name: t('Select filter'),
description: 'Select filter plugin using AntD', description: 'Select filter plugin using AntD',
behaviors: [Behavior.CROSS_FILTER, Behavior.NATIVE_FILTER], behaviors: [Behavior.CROSS_FILTER, Behavior.NATIVE_FILTER],
thumbnail, thumbnail,
@ -34,7 +34,7 @@ export default class AntdFilterSelectPlugin extends ChartPlugin {
super({ super({
buildQuery, buildQuery,
controlPanel, controlPanel,
loadChart: () => import('./AntdSelectFilter'), loadChart: () => import('./SelectFilterPlugin'),
metadata, metadata,
transformProps, transformProps,
}); });

View File

@ -22,9 +22,9 @@ import {
SetExtraFormDataHook, SetExtraFormDataHook,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { RefObject } from 'react'; import { RefObject } from 'react';
import { AntdPluginFilterStylesProps } from '../types'; import { PluginFilterStylesProps } from '../types';
interface AntdPluginFilterSelectCustomizeProps { interface PluginFilterSelectCustomizeProps {
defaultValue?: (string | number)[] | null; defaultValue?: (string | number)[] | null;
currentValue?: (string | number)[] | null; currentValue?: (string | number)[] | null;
enableEmptyFilter: boolean; enableEmptyFilter: boolean;
@ -34,17 +34,17 @@ interface AntdPluginFilterSelectCustomizeProps {
inputRef?: RefObject<HTMLInputElement>; inputRef?: RefObject<HTMLInputElement>;
} }
export type AntdPluginFilterSelectQueryFormData = QueryFormData & export type PluginFilterSelectQueryFormData = QueryFormData &
AntdPluginFilterStylesProps & PluginFilterStylesProps &
AntdPluginFilterSelectCustomizeProps; PluginFilterSelectCustomizeProps;
export type AntdPluginFilterSelectProps = AntdPluginFilterStylesProps & { export type PluginFilterSelectProps = PluginFilterStylesProps & {
data: DataRecord[]; data: DataRecord[];
setExtraFormData: SetExtraFormDataHook; setExtraFormData: SetExtraFormDataHook;
formData: AntdPluginFilterSelectQueryFormData; formData: PluginFilterSelectQueryFormData;
}; };
export const DEFAULT_FORM_DATA: AntdPluginFilterSelectCustomizeProps = { export const DEFAULT_FORM_DATA: PluginFilterSelectCustomizeProps = {
defaultValue: null, defaultValue: null,
currentValue: null, currentValue: null,
enableEmptyFilter: false, enableEmptyFilter: false,

View File

@ -19,18 +19,18 @@
import { styled } from '@superset-ui/core'; import { styled } from '@superset-ui/core';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import DateFilterControl from 'src/explore/components/controls/DateFilterControl/DateFilterControl'; import DateFilterControl from 'src/explore/components/controls/DateFilterControl/DateFilterControl';
import { AntdPluginFilterStylesProps } from '../types'; import { PluginFilterStylesProps } from '../types';
import { AntdPluginFilterTimeProps } from './types'; import { PluginFilterTimeProps } from './types';
const DEFAULT_VALUE = 'Last week'; const DEFAULT_VALUE = 'Last week';
const Styles = styled.div<AntdPluginFilterStylesProps>` const Styles = styled.div<PluginFilterStylesProps>`
height: ${({ height }) => height}px; height: ${({ height }) => height}px;
width: ${({ width }) => width}px; width: ${({ width }) => width}px;
overflow-x: scroll; overflow-x: scroll;
`; `;
export default function AntdTimeFilter(props: AntdPluginFilterTimeProps) { export default function TimeFilterPlugin(props: PluginFilterTimeProps) {
const { formData, setExtraFormData, width } = props; const { formData, setExtraFormData, width } = props;
const { defaultValue, currentValue } = formData; const { defaultValue, currentValue } = formData;

View File

@ -24,15 +24,16 @@ import thumbnail from './images/thumbnail.png';
export default class TimeFilterPlugin extends ChartPlugin { export default class TimeFilterPlugin extends ChartPlugin {
constructor() { constructor() {
const metadata = new ChartMetadata({ const metadata = new ChartMetadata({
name: t('Time range filter plugin'), name: t('Time filter'),
description: 'Custom time filter plugin', description: 'Custom time filter plugin',
behaviors: [Behavior.CROSS_FILTER, Behavior.NATIVE_FILTER], behaviors: [Behavior.CROSS_FILTER, Behavior.NATIVE_FILTER],
thumbnail, thumbnail,
datasourceCount: 0,
}); });
super({ super({
controlPanel, controlPanel,
loadChart: () => import('./AntdTimeFilter'), loadChart: () => import('./TimeFilterPlugin'),
metadata, metadata,
transformProps, transformProps,
}); });

View File

@ -21,21 +21,21 @@ import {
DataRecord, DataRecord,
SetExtraFormDataHook, SetExtraFormDataHook,
} from '@superset-ui/core'; } from '@superset-ui/core';
import { AntdPluginFilterStylesProps } from '../types'; import { PluginFilterStylesProps } from '../types';
interface PluginFilterTimeCustomizeProps { interface PluginFilterTimeCustomizeProps {
defaultValue?: string | null; defaultValue?: string | null;
currentValue?: string | null; currentValue?: string | null;
} }
export type AntdPluginFilterSelectQueryFormData = QueryFormData & export type PluginFilterSelectQueryFormData = QueryFormData &
AntdPluginFilterStylesProps & PluginFilterStylesProps &
PluginFilterTimeCustomizeProps; PluginFilterTimeCustomizeProps;
export type AntdPluginFilterTimeProps = AntdPluginFilterStylesProps & { export type PluginFilterTimeProps = PluginFilterStylesProps & {
data: DataRecord[]; data: DataRecord[];
setExtraFormData: SetExtraFormDataHook; setExtraFormData: SetExtraFormDataHook;
formData: AntdPluginFilterSelectQueryFormData; formData: PluginFilterSelectQueryFormData;
}; };
export const DEFAULT_FORM_DATA: PluginFilterTimeCustomizeProps = { export const DEFAULT_FORM_DATA: PluginFilterTimeCustomizeProps = {

View File

@ -16,6 +16,6 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
export { default as AntdSelectFilterPlugin } from './Select'; export { default as SelectFilterPlugin } from './Select';
export { default as AntdRangeFilterPlugin } from './Range'; export { default as RangeFilterPlugin } from './Range';
export { default as TimeFilterPlugin } from './Time'; export { default as TimeFilterPlugin } from './Time';

View File

@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
export interface AntdPluginFilterStylesProps { export interface PluginFilterStylesProps {
height: number; height: number;
width: number; width: number;
} }

View File

@ -60,8 +60,8 @@ import {
EchartsTimeseriesChartPlugin, EchartsTimeseriesChartPlugin,
} from '@superset-ui/plugin-chart-echarts'; } from '@superset-ui/plugin-chart-echarts';
import { import {
AntdSelectFilterPlugin, SelectFilterPlugin,
AntdRangeFilterPlugin, RangeFilterPlugin,
TimeFilterPlugin, TimeFilterPlugin,
} from 'src/filters/components/'; } from 'src/filters/components/';
import FilterBoxChartPlugin from '../FilterBox/FilterBoxChartPlugin'; import FilterBoxChartPlugin from '../FilterBox/FilterBoxChartPlugin';
@ -112,8 +112,8 @@ export default class MainPreset extends Preset {
new EchartsTimeseriesChartPlugin().configure({ new EchartsTimeseriesChartPlugin().configure({
key: 'echarts_timeseries', key: 'echarts_timeseries',
}), }),
new AntdSelectFilterPlugin().configure({ key: 'filter_select' }), new SelectFilterPlugin().configure({ key: 'filter_select' }),
new AntdRangeFilterPlugin().configure({ key: 'filter_range' }), new RangeFilterPlugin().configure({ key: 'filter_range' }),
new TimeFilterPlugin().configure({ key: 'filter_time' }), new TimeFilterPlugin().configure({ key: 'filter_time' }),
], ],
}); });