You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by vi...@apache.org on 2021/02/02 09:28:47 UTC
[superset] branch master updated: feat(native-filters): Add
defaultValue for Native filters modal (#12199)
This is an automated email from the ASF dual-hosted git repository.
villebro pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 465d986 feat(native-filters): Add defaultValue for Native filters modal (#12199)
465d986 is described below
commit 465d986617233ab401c99f9eab0f55488e0dae09
Author: simcha90 <56...@users.noreply.github.com>
AuthorDate: Tue Feb 2 11:28:12 2021 +0200
feat(native-filters): Add defaultValue for Native filters modal (#12199)
* refactor: sync Scoping tree with Forms data
* refactor: update scoping tree
* refactor: update scope tree logic to be more UX friendly
* test: fix tests
* lint: fix lin CR notes
* chore: temp
* fix: fix jsx
* feat: Init value
* refactor: move effect to utils
* chore: add comments
* feat: updates for default value in native filters
* refactor: move multi values management to Modal
* feat: added currentState to filterState
fix: Reset all fixed
* style: update filter styles
* fix: process selection of same filter
* fix: fix double choose select
* fix: fix order of cascading filters
* fix: fix CR comments
* fix: fix CR comments
---
.../spec/fixtures/mockNativeFilters.ts | 9 +-
.../src/dashboard/actions/nativeFilters.ts | 5 +
.../components/nativeFilters/CascadePopover.tsx | 51 +++---
.../components/nativeFilters/FilterBar.tsx | 184 ++++++++++-----------
.../components/nativeFilters/FilterConfigForm.tsx | 129 +++++++++++++--
.../components/nativeFilters/FilterConfigModal.tsx | 4 +-
.../dashboard/components/nativeFilters/state.ts | 108 ++++++++++--
.../dashboard/components/nativeFilters/types.ts | 24 ++-
.../dashboard/components/nativeFilters/utils.ts | 66 +++++++-
.../src/dashboard/reducers/nativeFilters.ts | 1 +
.../filters/components/Select/AntdSelectFilter.tsx | 54 ++++--
.../src/filters/components/Select/types.ts | 6 +-
superset-frontend/src/filters/utils.ts | 2 +-
13 files changed, 461 insertions(+), 182 deletions(-)
diff --git a/superset-frontend/spec/fixtures/mockNativeFilters.ts b/superset-frontend/spec/fixtures/mockNativeFilters.ts
index 9aa6e10..24872d8 100644
--- a/superset-frontend/spec/fixtures/mockNativeFilters.ts
+++ b/superset-frontend/spec/fixtures/mockNativeFilters.ts
@@ -16,14 +16,17 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { NativeFiltersState } from 'src/dashboard/components/nativeFilters/types';
+import {
+ FilterType,
+ NativeFiltersState,
+} from 'src/dashboard/components/nativeFilters/types';
export const nativeFilters: NativeFiltersState = {
filters: {
'NATIVE_FILTER-e7Q8zKixx': {
id: 'NATIVE_FILTER-e7Q8zKixx',
name: 'region',
- type: 'text',
+ filterType: FilterType.filter_select,
targets: [
{
datasetId: 2,
@@ -46,7 +49,7 @@ export const nativeFilters: NativeFiltersState = {
'NATIVE_FILTER-x9QPw0so1': {
id: 'NATIVE_FILTER-x9QPw0so1',
name: 'country_code',
- type: 'text',
+ filterType: FilterType.filter_select,
targets: [
{
datasetId: 2,
diff --git a/superset-frontend/src/dashboard/actions/nativeFilters.ts b/superset-frontend/src/dashboard/actions/nativeFilters.ts
index 2bafb73..a327eba 100644
--- a/superset-frontend/src/dashboard/actions/nativeFilters.ts
+++ b/superset-frontend/src/dashboard/actions/nativeFilters.ts
@@ -20,6 +20,7 @@
import { ExtraFormData, makeApi } from '@superset-ui/core';
import { Dispatch } from 'redux';
import {
+ CurrentFilterState,
Filter,
FilterConfiguration,
SelectedValues,
@@ -99,6 +100,7 @@ export interface SetExtraFormData {
type: typeof SET_EXTRA_FORM_DATA;
filterId: string;
extraFormData: ExtraFormData;
+ currentState: CurrentFilterState;
}
export function setFilterState(
@@ -117,15 +119,18 @@ export function setFilterState(
* Sets the selected option(s) for a given filter
* @param filterId the id of the native filter
* @param extraFormData the selection translated into extra form data
+ * @param currentState
*/
export function setExtraFormData(
filterId: string,
extraFormData: ExtraFormData,
+ currentState: CurrentFilterState,
): SetExtraFormData {
return {
type: SET_EXTRA_FORM_DATA,
filterId,
extraFormData,
+ currentState,
};
}
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/CascadePopover.tsx b/superset-frontend/src/dashboard/components/nativeFilters/CascadePopover.tsx
index e12c933..b131ba6 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/CascadePopover.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/CascadePopover.tsx
@@ -22,14 +22,19 @@ import Popover from 'src/common/components/Popover';
import Icon from 'src/components/Icon';
import { Pill } from 'src/dashboard/components/FiltersBadge/Styles';
import { CascadeFilterControl, FilterControl } from './FilterBar';
-import { Filter, CascadeFilter } from './types';
+import { Filter, CascadeFilter, CurrentFilterState } from './types';
+import { useFilterState } from './state';
interface CascadePopoverProps {
filter: CascadeFilter;
visible: boolean;
directPathToChild?: string[];
onVisibleChange: (visible: boolean) => void;
- onExtraFormDataChange: (filter: Filter, extraFormData: ExtraFormData) => void;
+ onFilterSelectionChange: (
+ filter: Filter,
+ extraFormData: ExtraFormData,
+ currentState: CurrentFilterState,
+ ) => void;
}
const StyledTitleBox = styled.div`
@@ -73,10 +78,11 @@ const CascadePopover: React.FC<CascadePopoverProps> = ({
filter,
visible,
onVisibleChange,
- onExtraFormDataChange,
+ onFilterSelectionChange,
directPathToChild,
}) => {
const [currentPathToChild, setCurrentPathToChild] = useState<string[]>();
+ const filterState = useFilterState(filter.id);
useEffect(() => {
setCurrentPathToChild(directPathToChild);
@@ -86,26 +92,27 @@ const CascadePopover: React.FC<CascadePopoverProps> = ({
return () => clearTimeout(timeout);
}, [directPathToChild, setCurrentPathToChild]);
- const getActiveChildren = useCallback((filter: CascadeFilter):
- | CascadeFilter[]
- | null => {
- const children = filter.cascadeChildren || [];
- const currentValue = filter.currentValue || [];
+ const getActiveChildren = useCallback(
+ (filter: CascadeFilter): CascadeFilter[] | null => {
+ const children = filter.cascadeChildren || [];
+ const currentValue = filterState.currentState?.value;
- const activeChildren = children.flatMap(
- childFilter => getActiveChildren(childFilter) || [],
- );
+ const activeChildren = children.flatMap(
+ childFilter => getActiveChildren(childFilter) || [],
+ );
- if (activeChildren.length > 0) {
- return activeChildren;
- }
+ if (activeChildren.length > 0) {
+ return activeChildren;
+ }
- if (currentValue.length > 0) {
- return [filter];
- }
+ if (currentValue) {
+ return [filter];
+ }
- return null;
- }, []);
+ return null;
+ },
+ [filterState],
+ );
const getAllFilters = (filter: CascadeFilter): CascadeFilter[] => {
const children = filter.cascadeChildren || [];
@@ -139,7 +146,7 @@ const CascadePopover: React.FC<CascadePopoverProps> = ({
<FilterControl
filter={filter}
directPathToChild={directPathToChild}
- onExtraFormDataChange={onExtraFormDataChange}
+ onFilterSelectionChange={onFilterSelectionChange}
/>
);
}
@@ -160,7 +167,7 @@ const CascadePopover: React.FC<CascadePopoverProps> = ({
key={filter.id}
filter={filter}
directPathToChild={visible ? currentPathToChild : undefined}
- onExtraFormDataChange={onExtraFormDataChange}
+ onFilterSelectionChange={onFilterSelectionChange}
/>
);
@@ -180,7 +187,7 @@ const CascadePopover: React.FC<CascadePopoverProps> = ({
<FilterControl
key={activeFilter.id}
filter={activeFilter}
- onExtraFormDataChange={onExtraFormDataChange}
+ onFilterSelectionChange={onFilterSelectionChange}
directPathToChild={currentPathToChild}
icon={
<>
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar.tsx
index 617928c..e7ad83f 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar.tsx
@@ -23,16 +23,9 @@ import {
t,
ExtraFormData,
} from '@superset-ui/core';
-import React, {
- useState,
- useEffect,
- useMemo,
- useCallback,
- useRef,
-} from 'react';
+import React, { useState, useEffect, useMemo, useRef } from 'react';
import { useSelector } from 'react-redux';
import cx from 'classnames';
-import { Form } from 'src/common/components';
import Button from 'src/components/Button';
import Icon from 'src/components/Icon';
import { getChartDataRequest } from 'src/chart/chartAction';
@@ -40,15 +33,19 @@ import { areObjectsEqual } from 'src/reduxUtils';
import Loading from 'src/components/Loading';
import BasicErrorAlert from 'src/components/ErrorMessage/BasicErrorAlert';
import FilterConfigurationLink from './FilterConfigurationLink';
-// import FilterScopeModal from 'src/dashboard/components/filterscope/FilterScopeModal';
-
import {
useCascadingFilters,
useFilterConfiguration,
+ useFilters,
+ useFilterState,
useSetExtraFormData,
} from './state';
-import { Filter, CascadeFilter } from './types';
-import { buildCascadeFiltersTree, mapParentFiltersToChildren } from './utils';
+import { Filter, CascadeFilter, CurrentFilterState } from './types';
+import {
+ buildCascadeFiltersTree,
+ getFormData,
+ mapParentFiltersToChildren,
+} from './utils';
import CascadePopover from './CascadePopover';
const barWidth = `250px`;
@@ -60,6 +57,10 @@ const BarWrapper = styled.div`
}
`;
+const FilterItem = styled.div`
+ padding-bottom: 10px;
+`;
+
const Bar = styled.div`
position: absolute;
top: 0;
@@ -201,7 +202,11 @@ interface FilterProps {
filter: Filter;
icon?: React.ReactElement;
directPathToChild?: string[];
- onExtraFormDataChange: (filter: Filter, extraFormData: ExtraFormData) => void;
+ onFilterSelectionChange: (
+ filter: Filter,
+ extraFormData: ExtraFormData,
+ currentState: CurrentFilterState,
+ ) => void;
}
interface FiltersBarProps {
@@ -213,17 +218,18 @@ interface FiltersBarProps {
const FilterValue: React.FC<FilterProps> = ({
filter,
directPathToChild,
- onExtraFormDataChange,
+ onFilterSelectionChange,
}) => {
const {
id,
allowsMultipleValues,
inverseSelection,
targets,
- currentValue,
defaultValue,
+ filterType,
} = filter;
const cascadingFilters = useCascadingFilters(id);
+ const filterState = useFilterState(id);
const [loading, setLoading] = useState<boolean>(true);
const [state, setState] = useState([]);
const [error, setError] = useState<boolean>(false);
@@ -232,29 +238,17 @@ const FilterValue: React.FC<FilterProps> = ({
const [target] = targets;
const { datasetId = 18, column } = target;
const { name: groupby } = column;
-
- const getFormData = (): Partial<QueryFormData> => ({
- adhoc_filters: [],
- datasource: `${datasetId}__table`,
- extra_filters: [],
- extra_form_data: cascadingFilters,
- granularity_sqla: 'ds',
- groupby: [groupby],
- inverseSelection,
- metrics: ['count'],
- multiSelect: allowsMultipleValues,
- row_limit: 10000,
- showSearch: true,
- time_range: 'No filter',
- time_range_endpoints: ['inclusive', 'exclusive'],
- url_params: {},
- viz_type: 'filter_select',
- defaultValues: currentValue || defaultValue || [],
- inputRef,
- });
-
+ const currentValue = filterState.currentState?.value;
useEffect(() => {
- const newFormData = getFormData();
+ const newFormData = getFormData({
+ datasetId,
+ cascadingFilters,
+ groupby,
+ allowsMultipleValues,
+ defaultValue,
+ currentValue,
+ inverseSelection,
+ });
if (!areObjectsEqual(formData || {}, newFormData)) {
setFormData(newFormData);
getChartDataRequest({
@@ -272,7 +266,7 @@ const FilterValue: React.FC<FilterProps> = ({
setLoading(false);
});
}
- }, [cascadingFilters, datasetId, groupby]);
+ }, [cascadingFilters, datasetId, groupby, defaultValue, currentValue]);
useEffect(() => {
if (directPathToChild?.[0] === filter.id) {
@@ -285,8 +279,13 @@ const FilterValue: React.FC<FilterProps> = ({
return undefined;
}, [inputRef, directPathToChild, filter.id]);
- const setExtraFormData = (extraFormData: ExtraFormData) =>
- onExtraFormDataChange(filter, extraFormData);
+ const setExtraFormData = ({
+ extraFormData,
+ currentState,
+ }: {
+ extraFormData: ExtraFormData;
+ currentState: CurrentFilterState;
+ }) => onFilterSelectionChange(filter, extraFormData, currentState);
if (loading) {
return (
@@ -307,29 +306,24 @@ const FilterValue: React.FC<FilterProps> = ({
}
return (
- <Form
- onFinish={values => {
- setExtraFormData(values.value);
- }}
- >
- <Form.Item name="value">
- <SuperChart
- height={20}
- width={220}
- formData={getFormData()}
- queriesData={state}
- chartType="filter_select"
- hooks={{ setExtraFormData }}
- />
- </Form.Item>
- </Form>
+ <FilterItem>
+ <SuperChart
+ height={20}
+ width={220}
+ formData={formData}
+ queriesData={state}
+ chartType={filterType}
+ // @ts-ignore (update superset-ui)
+ hooks={{ setExtraFormData }}
+ />
+ </FilterItem>
);
};
export const FilterControl: React.FC<FilterProps> = ({
filter,
icon,
- onExtraFormDataChange,
+ onFilterSelectionChange,
directPathToChild,
}) => {
const { name = '<undefined>' } = filter;
@@ -342,7 +336,7 @@ export const FilterControl: React.FC<FilterProps> = ({
<FilterValue
filter={filter}
directPathToChild={directPathToChild}
- onExtraFormDataChange={onExtraFormDataChange}
+ onFilterSelectionChange={onFilterSelectionChange}
/>
</StyledFilterControlContainer>
);
@@ -351,13 +345,17 @@ export const FilterControl: React.FC<FilterProps> = ({
interface CascadeFilterControlProps {
filter: CascadeFilter;
directPathToChild?: string[];
- onExtraFormDataChange: (filter: Filter, extraFormData: ExtraFormData) => void;
+ onFilterSelectionChange: (
+ filter: Filter,
+ extraFormData: ExtraFormData,
+ currentState: CurrentFilterState,
+ ) => void;
}
export const CascadeFilterControl: React.FC<CascadeFilterControlProps> = ({
filter,
directPathToChild,
- onExtraFormDataChange,
+ onFilterSelectionChange,
}) => (
<>
<StyledFilterControlBox>
@@ -365,7 +363,7 @@ export const CascadeFilterControl: React.FC<CascadeFilterControlProps> = ({
<FilterControl
filter={filter}
directPathToChild={directPathToChild}
- onExtraFormDataChange={onExtraFormDataChange}
+ onFilterSelectionChange={onFilterSelectionChange}
/>
</StyledFilterControlBox>
@@ -375,7 +373,7 @@ export const CascadeFilterControl: React.FC<CascadeFilterControlProps> = ({
<CascadeFilterControl
filter={childFilter}
directPathToChild={directPathToChild}
- onExtraFormDataChange={onExtraFormDataChange}
+ onFilterSelectionChange={onFilterSelectionChange}
/>
</li>
))}
@@ -388,11 +386,15 @@ const FilterBar: React.FC<FiltersBarProps> = ({
toggleFiltersBar,
directPathToChild,
}) => {
- const [filterData, setFilterData] = useState<{ [id: string]: ExtraFormData }>(
- {},
- );
+ const [filterData, setFilterData] = useState<{
+ [id: string]: {
+ extraFormData: ExtraFormData;
+ currentState: CurrentFilterState;
+ };
+ }>({});
const setExtraFormData = useSetExtraFormData();
const filterConfigs = useFilterConfiguration();
+ const filters = useFilters();
const canEdit = useSelector<any, boolean>(
({ dashboardInfo }) => dashboardInfo.dash_edit_perm,
);
@@ -404,25 +406,6 @@ const FilterBar: React.FC<FiltersBarProps> = ({
}
}, [filterConfigs]);
- const getFilterValue = useCallback(
- (filter: Filter): (string | number | boolean)[] | null => {
- const filters = filterData[filter.id]?.append_form_data?.filters;
- if (filters?.length) {
- const filter = filters[0];
- if ('val' in filter) {
- // need to nest these if statements to get a reference to val to appease TS
- const { val } = filter;
- if (Array.isArray(val)) {
- return val;
- }
- return [val];
- }
- }
- return null;
- },
- [filterData],
- );
-
const cascadeChildren = useMemo(
() => mapParentFiltersToChildren(filterConfigs),
[filterConfigs],
@@ -431,24 +414,28 @@ const FilterBar: React.FC<FiltersBarProps> = ({
const cascadeFilters = useMemo(() => {
const filtersWithValue = filterConfigs.map(filter => ({
...filter,
- currentValue: getFilterValue(filter),
+ currentValue: filterData[filter.id]?.currentState?.value,
}));
return buildCascadeFiltersTree(filtersWithValue);
- }, [filterConfigs, getFilterValue]);
+ }, [filterConfigs]);
- const handleExtraFormDataChange = (
+ const handleFilterSelectionChange = (
filter: Filter,
extraFormData: ExtraFormData,
+ currentState: CurrentFilterState,
) => {
setFilterData(prevFilterData => ({
...prevFilterData,
- [filter.id]: extraFormData,
+ [filter.id]: {
+ extraFormData,
+ currentState,
+ },
}));
const children = cascadeChildren[filter.id] || [];
// force instant updating for parent filters
if (filter.isInstant || children.length > 0) {
- setExtraFormData(filter.id, extraFormData);
+ setExtraFormData(filter.id, extraFormData, currentState);
}
};
@@ -456,18 +443,21 @@ const FilterBar: React.FC<FiltersBarProps> = ({
const filterIds = Object.keys(filterData);
filterIds.forEach(filterId => {
if (filterData[filterId]) {
- setExtraFormData(filterId, filterData[filterId]);
+ setExtraFormData(
+ filterId,
+ filterData[filterId]?.extraFormData,
+ filterData[filterId]?.currentState,
+ );
}
});
};
const handleResetAll = () => {
- setFilterData({});
- const filterIds = Object.keys(filterData);
- filterIds.forEach(filterId => {
- if (filterData[filterId]) {
- setExtraFormData(filterId, {});
- }
+ filterConfigs.forEach(filter => {
+ setExtraFormData(filter.id, filterData[filter.id]?.extraFormData, {
+ ...filterData[filter.id]?.currentState,
+ value: filters[filter.id]?.defaultValue,
+ });
});
};
@@ -521,7 +511,7 @@ const FilterBar: React.FC<FiltersBarProps> = ({
setVisiblePopoverId(visible ? filter.id : null)
}
filter={filter}
- onExtraFormDataChange={handleExtraFormDataChange}
+ onFilterSelectionChange={handleFilterSelectionChange}
directPathToChild={directPathToChild}
/>
))}
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigForm.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigForm.tsx
index 9489209..82269d0 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigForm.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigForm.tsx
@@ -16,9 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { styled, t } from '@superset-ui/core';
+import { styled, SuperChart, t } from '@superset-ui/core';
import { FormInstance } from 'antd/lib/form';
-import React, { useCallback, useState } from 'react';
+import React, { useCallback } from 'react';
import {
Button,
Checkbox,
@@ -27,14 +27,19 @@ import {
Typography,
} from 'src/common/components';
import { Select } from 'src/components/Select/SupersetStyledSelect';
-import SupersetResourceSelect, {
- Value,
-} from 'src/components/SupersetResourceSelect';
+import SupersetResourceSelect from 'src/components/SupersetResourceSelect';
import { addDangerToast } from 'src/messageToasts/actions';
import { ClientErrorObject } from 'src/utils/getClientErrorObject';
import { ColumnSelect } from './ColumnSelect';
-import { Filter, NativeFiltersForm } from './types';
+import { Filter, FilterType, NativeFiltersForm } from './types';
import FilterScope from './FilterScope';
+import {
+ FilterTypeNames,
+ getFormData,
+ setFilterFieldValues,
+ useForceUpdate,
+} from './utils';
+import { useBackendFormUpdate } from './state';
type DatasetSelectValue = {
value: number;
@@ -77,6 +82,10 @@ const StyledLabel = styled.span`
text-transform: uppercase;
`;
+const CleanFormItem = styled(Form.Item)`
+ margin-bottom: 0;
+`;
+
export interface FilterConfigFormProps {
filterId: string;
filterToEdit?: Filter;
@@ -98,11 +107,19 @@ export const FilterConfigForm: React.FC<FilterConfigFormProps> = ({
form,
parentFilters,
}) => {
- const [dataset, setDataset] = useState<Value<number> | undefined>(
- filterToEdit?.targets[0].datasetId
- ? { label: '', value: filterToEdit?.targets[0].datasetId }
- : undefined,
- );
+ const forceUpdate = useForceUpdate();
+ const formFilter = (form.getFieldValue('filters') || {})[filterId];
+ useBackendFormUpdate(form, filterId, filterToEdit);
+
+ const initDatasetId = filterToEdit?.targets[0].datasetId;
+ const initColumn = filterToEdit?.targets[0]?.column?.name;
+ const newFormData = getFormData({
+ datasetId: formFilter?.dataset?.value,
+ groupby: formFilter?.column,
+ allowsMultipleValues: formFilter?.allowsMultipleValues,
+ defaultValue: formFilter?.defaultValue,
+ inverseSelection: formFilter?.inverseSelection,
+ });
const onDatasetSelectError = useCallback(
({ error, message }: ClientErrorObject) => {
@@ -146,21 +163,30 @@ export const FilterConfigForm: React.FC<FilterConfigFormProps> = ({
>
<Input />
</StyledFormItem>
-
<StyledFormItem
name={['filters', filterId, 'dataset']}
+ initialValue={{ value: initDatasetId }}
label={<StyledLabel>{t('Datasource')}</StyledLabel>}
rules={[{ required: !removed, message: t('Datasource is required') }]}
data-test="datasource-input"
>
<SupersetResourceSelect
- initialId={filterToEdit?.targets[0].datasetId}
+ initialId={initDatasetId}
resource="dataset"
searchColumn="table_name"
transformItem={datasetToSelectOption}
isMulti={false}
- onChange={setDataset}
onError={onDatasetSelectError}
+ onChange={e => {
+ // We need reset column when dataset changed
+ const datasetId = formFilter?.dataset?.value;
+ if (datasetId && e?.value !== datasetId) {
+ setFilterFieldValues(form, filterId, {
+ column: null,
+ });
+ }
+ forceUpdate();
+ }}
/>
</StyledFormItem>
</StyledContainer>
@@ -168,7 +194,7 @@ export const FilterConfigForm: React.FC<FilterConfigFormProps> = ({
// don't show the column select unless we have a dataset
// style={{ display: datasetId == null ? undefined : 'none' }}
name={['filters', filterId, 'column']}
- initialValue={filterToEdit?.targets[0]?.column?.name}
+ initialValue={initColumn}
label={<StyledLabel>{t('Field')}</StyledLabel>}
rules={[{ required: !removed, message: t('Field is required') }]}
data-test="field-input"
@@ -176,10 +202,69 @@ export const FilterConfigForm: React.FC<FilterConfigFormProps> = ({
<ColumnSelect
form={form}
filterId={filterId}
- datasetId={dataset?.value}
+ datasetId={formFilter?.dataset?.value}
+ onChange={forceUpdate}
/>
</StyledFormItem>
<StyledFormItem
+ name={['filters', filterId, 'filterType']}
+ rules={[{ required: !removed, message: t('Name is required') }]}
+ initialValue={filterToEdit?.filterType || FilterType.filter_select}
+ label={<StyledLabel>{t('Filter Type')}</StyledLabel>}
+ >
+ <Select
+ options={Object.values(FilterType).map(filterType => ({
+ value: filterType,
+ label: FilterTypeNames[filterType],
+ }))}
+ onChange={({ value }: { value: FilterType }) => {
+ setFilterFieldValues(form, filterId, {
+ filterType: value,
+ defaultValue: null,
+ });
+ forceUpdate();
+ }}
+ />
+ </StyledFormItem>
+ {formFilter?.dataset && formFilter?.column && (
+ <CleanFormItem
+ name={['filters', filterId, 'defaultValueFormData']}
+ hidden
+ initialValue={newFormData}
+ />
+ )}
+ <CleanFormItem
+ name={['filters', filterId, 'defaultValueQueriesData']}
+ hidden
+ initialValue={null}
+ />
+ <StyledFormItem
+ name={['filters', filterId, 'defaultValue']}
+ initialValue={filterToEdit?.defaultValue}
+ label={<StyledLabel>{t('Default Value')}</StyledLabel>}
+ >
+ {formFilter?.dataset &&
+ formFilter?.column &&
+ formFilter?.defaultValueQueriesData && (
+ <SuperChart
+ height={20}
+ width={220}
+ formData={newFormData}
+ queriesData={formFilter?.defaultValueQueriesData}
+ chartType={formFilter?.filterType}
+ hooks={{
+ // @ts-ignore
+ setExtraFormData: ({ currentState }) => {
+ setFilterFieldValues(form, filterId, {
+ defaultValue: currentState?.value,
+ });
+ forceUpdate();
+ },
+ }}
+ />
+ )}
+ </StyledFormItem>
+ <StyledFormItem
name={['filters', filterId, 'parentFilter']}
label={<StyledLabel>{t('Parent filter')}</StyledLabel>}
initialValue={parentFilterOptions.find(
@@ -192,7 +277,6 @@ export const FilterConfigForm: React.FC<FilterConfigFormProps> = ({
isClearable
/>
</StyledFormItem>
-
<StyledCheckboxFormItem
name={['filters', filterId, 'isInstant']}
initialValue={filterToEdit?.isInstant}
@@ -207,7 +291,16 @@ export const FilterConfigForm: React.FC<FilterConfigFormProps> = ({
valuePropName="checked"
colon={false}
>
- <Checkbox>{t('Allow multiple selections')}</Checkbox>
+ <Checkbox
+ onChange={() => {
+ setFilterFieldValues(form, filterId, {
+ defaultValue: null,
+ });
+ forceUpdate();
+ }}
+ >
+ {t('Allow multiple selections')}
+ </Checkbox>
</StyledCheckboxFormItem>
<StyledCheckboxFormItem
name={['filters', filterId, 'inverseSelection']}
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal.tsx
index 7980d79..0017bae 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterConfigModal.tsx
@@ -402,7 +402,7 @@ export function FilterConfigModal({
return {
id,
name: formInputs.name,
- type: 'text',
+ filterType: formInputs.filterType,
// for now there will only ever be one target
targets: [
{
@@ -497,6 +497,7 @@ export function FilterConfigModal({
visible={isOpen}
title={t('Filter configuration and scoping')}
width="55%"
+ destroyOnClose
onCancel={handleCancel}
onOk={onOk}
centered
@@ -506,6 +507,7 @@ export function FilterConfigModal({
<ErrorBoundary>
<StyledModalBody>
<StyledForm
+ preserve={false}
form={form}
onValuesChange={(changes, values: NativeFiltersForm) => {
if (
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/state.ts b/superset-frontend/src/dashboard/components/nativeFilters/state.ts
index 282c75b..16ffa62 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/state.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/state.ts
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { useCallback, useMemo } from 'react';
+import { useCallback, useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { setExtraFormData } from 'src/dashboard/actions/nativeFilters';
import { getInitialFilterState } from 'src/dashboard/reducers/nativeFilters';
@@ -27,14 +27,24 @@ import {
CHART_TYPE,
DASHBOARD_ROOT_TYPE,
} from 'src/dashboard/util/componentTypes';
+import { FormInstance } from 'antd/lib/form';
+import { getChartDataRequest } from 'src/chart/chartAction';
import {
+ CurrentFilterState,
Filter,
FilterConfiguration,
FilterState,
+ NativeFiltersForm,
NativeFiltersState,
TreeItem,
} from './types';
-import { buildTree, mergeExtraFormData } from './utils';
+import {
+ buildTree,
+ getFormData,
+ mergeExtraFormData,
+ setFilterFieldValues,
+ useForceUpdate,
+} from './utils';
const defaultFilterConfiguration: Filter[] = [];
@@ -68,11 +78,24 @@ export function useFilterState(id: string) {
);
}
+export function useFiltersState() {
+ return useSelector<any, FilterState>(
+ state => state.nativeFilters.filtersState,
+ );
+}
+
+export function useFilters() {
+ return useSelector<any, FilterState>(state => state.nativeFilters.filters);
+}
+
export function useSetExtraFormData() {
const dispatch = useDispatch();
return useCallback(
- (id: string, extraFormData: ExtraFormData) =>
- dispatch(setExtraFormData(id, extraFormData)),
+ (
+ id: string,
+ extraFormData: ExtraFormData,
+ currentState: CurrentFilterState,
+ ) => dispatch(setExtraFormData(id, extraFormData, currentState)),
[dispatch],
);
}
@@ -113,17 +136,70 @@ export function useFilterScopeTree(): {
}
export function useCascadingFilters(id: string) {
- return useSelector<any, ExtraFormData>(state => {
- const { nativeFilters }: { nativeFilters: NativeFiltersState } = state;
- const { filters, filtersState } = nativeFilters;
- const filter = filters[id];
- const cascadeParentIds = filter?.cascadeParentIds ?? [];
- let cascadedFilters = {};
- cascadeParentIds.forEach(parentId => {
- const parentState = filtersState[parentId] || {};
- const { extraFormData: parentExtra = {} } = parentState;
- cascadedFilters = mergeExtraFormData(cascadedFilters, parentExtra);
- });
- return cascadedFilters;
+ const nativeFilters = useSelector<any, NativeFiltersState>(
+ state => state.nativeFilters,
+ );
+ const { filters, filtersState } = nativeFilters;
+ const filter = filters[id];
+ const cascadeParentIds = filter?.cascadeParentIds ?? [];
+ let cascadedFilters = {};
+ cascadeParentIds.forEach(parentId => {
+ const parentState = filtersState[parentId] || {};
+ const { extraFormData: parentExtra = {} } = parentState;
+ cascadedFilters = mergeExtraFormData(cascadedFilters, parentExtra);
});
+ return cascadedFilters;
}
+
+// When some fields in form changed we need re-fetch data for Filter defaultValue
+export const useBackendFormUpdate = (
+ form: FormInstance<NativeFiltersForm>,
+ filterId: string,
+ filterToEdit?: Filter,
+) => {
+ const forceUpdate = useForceUpdate();
+ const formFilter = (form.getFieldValue('filters') || {})[filterId];
+ useEffect(() => {
+ let resolvedDefaultValue: any = null;
+ // No need to check data set change because it cascading update column
+ // So check that column exists is enough
+ if (!formFilter?.column) {
+ setFilterFieldValues(form, filterId, {
+ defaultValueQueriesData: [],
+ defaultValue: resolvedDefaultValue,
+ });
+ return;
+ }
+ const formData = getFormData({
+ datasetId: formFilter?.dataset?.value,
+ groupby: formFilter?.column,
+ allowsMultipleValues: formFilter?.allowsMultipleValues,
+ defaultValue: formFilter?.defaultValue,
+ inverseSelection: formFilter?.inverseSelection,
+ });
+ getChartDataRequest({
+ formData,
+ force: false,
+ requestParams: { dashboardId: 0 },
+ }).then(response => {
+ if (
+ filterToEdit?.filterType === formFilter?.filterType &&
+ filterToEdit?.targets[0].datasetId === formFilter?.dataset?.value &&
+ formFilter?.column === filterToEdit?.targets[0]?.column?.name &&
+ filterToEdit?.allowsMultipleValues === formFilter?.allowsMultipleValues
+ ) {
+ resolvedDefaultValue = filterToEdit?.defaultValue;
+ }
+ setFilterFieldValues(form, filterId, {
+ defaultValueQueriesData: response.result,
+ defaultValue: resolvedDefaultValue,
+ });
+ forceUpdate();
+ });
+ }, [
+ formFilter?.filterType,
+ formFilter?.column,
+ formFilter?.dataset?.value,
+ filterId,
+ ]);
+};
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/types.ts b/superset-frontend/src/dashboard/components/nativeFilters/types.ts
index 311c308..ac90309 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/types.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/types.ts
@@ -16,7 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { ExtraFormData, QueryObjectFilterClause } from '@superset-ui/core';
+import {
+ ExtraFormData,
+ JsonObject,
+ QueryObjectFilterClause,
+} from '@superset-ui/core';
export enum Scoping {
all,
@@ -29,12 +33,13 @@ export type AntCallback = (value1?: any, value2?: any) => void;
interface NativeFiltersFormItem {
scope: Scope;
name: string;
+ filterType: FilterType;
dataset: {
value: number;
label: string;
};
column: string;
- defaultValue: string;
+ defaultValue: any;
parentFilter: {
value: string;
label: string;
@@ -69,7 +74,10 @@ export interface Target {
// clarityColumns?: Column[];
}
-export type FilterType = 'text' | 'date';
+export enum FilterType {
+ filter_select = 'filter_select',
+ filter_range = 'filter_range',
+}
/**
* This is a filter configuration object, stored in the dashboard's json metadata.
@@ -78,15 +86,15 @@ export type FilterType = 'text' | 'date';
export interface Filter {
allowsMultipleValues: boolean;
cascadeParentIds: string[];
- defaultValue: string | null;
- currentValue?: (string | number | boolean)[] | null;
+ defaultValue: any;
+ currentValue?: any;
inverseSelection: boolean;
isInstant: boolean;
isRequired: boolean;
id: string; // randomly generated at filter creation
name: string;
scope: Scope;
- type: FilterType;
+ filterType: FilterType;
// for now there will only ever be one target
// when multiple targets are supported, change this to Target[]
targets: [Target];
@@ -99,11 +107,15 @@ export interface CascadeFilter extends Filter {
export type FilterConfiguration = Filter[];
export type SelectedValues = string[] | null;
+export type CurrentFilterState = JsonObject & {
+ value: any;
+};
/** Current state of the filter, stored in `nativeFilters` in redux */
export type FilterState = {
id: string; // ties this filter state to the config object
extraFormData?: ExtraFormData;
+ currentState?: CurrentFilterState;
};
export type AllFilterState = {
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/utils.ts b/superset-frontend/src/dashboard/components/nativeFilters/utils.ts
index 7f89aee..3257ef0 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/utils.ts
+++ b/superset-frontend/src/dashboard/components/nativeFilters/utils.ts
@@ -16,7 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { ExtraFormData, QueryObject } from '@superset-ui/core';
+import {
+ ExtraFormData,
+ QueryFormData,
+ QueryObject,
+ t,
+} from '@superset-ui/core';
import { Charts, Layout, LayoutItem } from 'src/dashboard/types';
import {
CHART_TYPE,
@@ -24,10 +29,11 @@ import {
TAB_TYPE,
} from 'src/dashboard/util/componentTypes';
import { FormInstance } from 'antd/lib/form';
-import React from 'react';
+import React, { RefObject } from 'react';
import {
CascadeFilter,
Filter,
+ FilterType,
NativeFiltersState,
Scope,
TreeItem,
@@ -238,6 +244,11 @@ export function buildCascadeFiltersTree(filters: Filter[]): CascadeFilter[] {
.map(getCascadeFilter);
}
+export const FilterTypeNames = {
+ [FilterType.filter_select]: t('Select'),
+ [FilterType.filter_range]: t('Range'),
+};
+
export const setFilterFieldValues = (
form: FormInstance,
filterId: string,
@@ -257,3 +268,54 @@ export const setFilterFieldValues = (
export const isScopingAll = (scope: Scope) =>
!scope || (scope.rootPath[0] === DASHBOARD_ROOT_ID && !scope.excluded.length);
+
+export const getFormData = ({
+ datasetId = 18,
+ cascadingFilters = {},
+ groupby,
+ allowsMultipleValues = false,
+ defaultValue,
+ currentValue,
+ inverseSelection,
+ inputRef,
+}: Partial<Filter> & {
+ datasetId?: number;
+ inputRef?: RefObject<HTMLInputElement>;
+ cascadingFilters?: object;
+ groupby: string;
+}): Partial<QueryFormData> => ({
+ adhoc_filters: [],
+ datasource: `${datasetId}__table`,
+ extra_filters: [],
+ extra_form_data: cascadingFilters,
+ granularity_sqla: 'ds',
+ groupby: [groupby],
+ inverseSelection,
+ metrics: ['count'],
+ multiSelect: allowsMultipleValues,
+ row_limit: 10000,
+ showSearch: true,
+ currentValue,
+ time_range: 'No filter',
+ time_range_endpoints: ['inclusive', 'exclusive'],
+ url_params: {},
+ viz_type: 'filter_select',
+ // TODO: need process per filter type after will be decided approach
+ defaultValue,
+ inputRef,
+});
+
+type AppendFormData = {
+ filters: {
+ val?: number | string | null;
+ }[];
+};
+
+export const extractDefaultValue = {
+ [FilterType.filter_select]: (appendFormData: AppendFormData) =>
+ appendFormData.filters?.[0]?.val,
+ [FilterType.filter_range]: (appendFormData: AppendFormData) => ({
+ min: appendFormData.filters?.[0].val,
+ max: appendFormData.filters?.[1].val,
+ }),
+};
diff --git a/superset-frontend/src/dashboard/reducers/nativeFilters.ts b/superset-frontend/src/dashboard/reducers/nativeFilters.ts
index b69ccd5..56e34b9 100644
--- a/superset-frontend/src/dashboard/reducers/nativeFilters.ts
+++ b/superset-frontend/src/dashboard/reducers/nativeFilters.ts
@@ -63,6 +63,7 @@ export default function nativeFilterReducer(
[action.filterId]: {
...filtersState[action.filterId],
extraFormData: action.extraFormData,
+ currentState: action.currentState,
},
},
};
diff --git a/superset-frontend/src/filters/components/Select/AntdSelectFilter.tsx b/superset-frontend/src/filters/components/Select/AntdSelectFilter.tsx
index b97312c..d9f89c9 100644
--- a/superset-frontend/src/filters/components/Select/AntdSelectFilter.tsx
+++ b/superset-frontend/src/filters/components/Select/AntdSelectFilter.tsx
@@ -33,13 +33,13 @@ const { Option } = Select;
export default function AntdPluginFilterSelect(
props: AntdPluginFilterSelectProps,
) {
- const [values, setValues] = useState<(string | number)[]>([]);
const { data, formData, height, width, setExtraFormData } = props;
const {
- defaultValues,
+ defaultValue,
enableEmptyFilter,
multiSelect,
showSearch,
+ currentValue,
inverseSelection,
inputRef,
} = {
@@ -47,24 +47,50 @@ export default function AntdPluginFilterSelect(
...formData,
};
- useEffect(() => {
- setValues(defaultValues || []);
- }, [defaultValues]);
+ const [values, setValues] = useState<(string | number)[]>(defaultValue ?? []);
let { groupby = [] } = formData;
groupby = Array.isArray(groupby) ? groupby : [groupby];
- function handleChange(value?: number[] | string[] | null) {
- setValues(value || []);
+ const handleChange = (
+ value?: (number | string)[] | number | string | null,
+ ) => {
+ let resultValue: (number | string)[];
+ // Works only with arrays even for single select
+ if (!Array.isArray(value)) {
+ resultValue = value ? [value] : [];
+ } else {
+ resultValue = value;
+ }
+ setValues(resultValue);
const [col] = groupby;
const emptyFilter =
- enableEmptyFilter &&
- !inverseSelection &&
- (value === undefined || value === null || value.length === 0);
- setExtraFormData(
- getSelectExtraFormData(col, value, emptyFilter, inverseSelection),
- );
- }
+ enableEmptyFilter && !inverseSelection && resultValue?.length === 0;
+ setExtraFormData({
+ // @ts-ignore
+ extraFormData: getSelectExtraFormData(
+ col,
+ resultValue,
+ emptyFilter,
+ inverseSelection,
+ ),
+ // @ts-ignore (add to superset-ui/core)
+ currentState: {
+ value: resultValue,
+ },
+ });
+ };
+
+ useEffect(() => {
+ handleChange(currentValue ?? []);
+ }, [JSON.stringify(currentValue)]);
+
+ useEffect(() => {
+ handleChange(defaultValue ?? []);
+ // I think after Config Modal update some filter it re-creates default value for all other filters
+ // so we can process it like this `JSON.stringify` or start to use `Immer`
+ }, [JSON.stringify(defaultValue)]);
+
const placeholderText =
(data || []).length === 0
? 'No data'
diff --git a/superset-frontend/src/filters/components/Select/types.ts b/superset-frontend/src/filters/components/Select/types.ts
index d8f554b..5720978 100644
--- a/superset-frontend/src/filters/components/Select/types.ts
+++ b/superset-frontend/src/filters/components/Select/types.ts
@@ -25,7 +25,8 @@ import { RefObject } from 'react';
import { AntdPluginFilterStylesProps } from '../types';
interface AntdPluginFilterSelectCustomizeProps {
- defaultValues?: (string | number)[];
+ defaultValue?: (string | number)[] | null;
+ currentValue?: (string | number)[] | null;
enableEmptyFilter: boolean;
fetchPredicate?: string;
inverseSelection: boolean;
@@ -45,7 +46,8 @@ export type AntdPluginFilterSelectProps = AntdPluginFilterStylesProps & {
};
export const DEFAULT_FORM_DATA: AntdPluginFilterSelectCustomizeProps = {
- defaultValues: [],
+ defaultValue: null,
+ currentValue: null,
enableEmptyFilter: false,
fetchPredicate: '',
inverseSelection: false,
diff --git a/superset-frontend/src/filters/utils.ts b/superset-frontend/src/filters/utils.ts
index 1a5d11c..364cd9c 100644
--- a/superset-frontend/src/filters/utils.ts
+++ b/superset-frontend/src/filters/utils.ts
@@ -20,7 +20,7 @@ import { QueryObjectFilterClause } from '@superset-ui/core';
export const getSelectExtraFormData = (
col: string,
- value?: undefined | null | string[] | number[],
+ value?: null | (string | number)[],
emptyFilter = false,
inverseSelection = false,
) => ({