mirror of
https://github.com/apache/superset.git
synced 2024-09-12 00:29:39 -04:00
fix: Removing parent filter causes incorrect state of child filter (#16876)
* fix: Removing parent filter causes incorrect state of child filter * removed to isRemoved * Shows (deleted) when the parent is removed * Removes unnecessary code
This commit is contained in:
parent
3272d1c086
commit
4da4fe4136
@ -37,6 +37,7 @@ describe('FilterScope', () => {
|
|||||||
restoreFilter: jest.fn(),
|
restoreFilter: jest.fn(),
|
||||||
parentFilters: [],
|
parentFilters: [],
|
||||||
save,
|
save,
|
||||||
|
removedFilters: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const MockModal = ({ scope }: { scope?: object }) => {
|
const MockModal = ({ scope }: { scope?: object }) => {
|
||||||
|
@ -44,7 +44,7 @@ import React, {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual, isEmpty } from 'lodash';
|
||||||
import { FormItem } from 'src/components/Form';
|
import { FormItem } from 'src/components/Form';
|
||||||
import { Input } from 'src/common/components';
|
import { Input } from 'src/common/components';
|
||||||
import { Select } from 'src/components';
|
import { Select } from 'src/components';
|
||||||
@ -70,7 +70,7 @@ import {
|
|||||||
} from 'src/dashboard/types';
|
} from 'src/dashboard/types';
|
||||||
import Loading from 'src/components/Loading';
|
import Loading from 'src/components/Loading';
|
||||||
import { ColumnSelect } from './ColumnSelect';
|
import { ColumnSelect } from './ColumnSelect';
|
||||||
import { NativeFiltersForm } from '../types';
|
import { NativeFiltersForm, FilterRemoval } from '../types';
|
||||||
import {
|
import {
|
||||||
FILTER_SUPPORTED_TYPES,
|
FILTER_SUPPORTED_TYPES,
|
||||||
hasTemporalColumns,
|
hasTemporalColumns,
|
||||||
@ -264,7 +264,7 @@ const FilterPanels = {
|
|||||||
export interface FiltersConfigFormProps {
|
export interface FiltersConfigFormProps {
|
||||||
filterId: string;
|
filterId: string;
|
||||||
filterToEdit?: Filter;
|
filterToEdit?: Filter;
|
||||||
removed?: boolean;
|
removedFilters: Record<string, FilterRemoval>;
|
||||||
restoreFilter: (filterId: string) => void;
|
restoreFilter: (filterId: string) => void;
|
||||||
form: FormInstance<NativeFiltersForm>;
|
form: FormInstance<NativeFiltersForm>;
|
||||||
parentFilters: { id: string; title: string }[];
|
parentFilters: { id: string; title: string }[];
|
||||||
@ -300,21 +300,22 @@ const FiltersConfigForm = (
|
|||||||
{
|
{
|
||||||
filterId,
|
filterId,
|
||||||
filterToEdit,
|
filterToEdit,
|
||||||
removed,
|
removedFilters,
|
||||||
restoreFilter,
|
restoreFilter,
|
||||||
form,
|
form,
|
||||||
parentFilters,
|
parentFilters,
|
||||||
}: FiltersConfigFormProps,
|
}: FiltersConfigFormProps,
|
||||||
ref: React.RefObject<any>,
|
ref: React.RefObject<any>,
|
||||||
) => {
|
) => {
|
||||||
|
const isRemoved = !!removedFilters[filterId];
|
||||||
const [error, setError] = useState<string>('');
|
const [error, setError] = useState<string>('');
|
||||||
const [metrics, setMetrics] = useState<Metric[]>([]);
|
const [metrics, setMetrics] = useState<Metric[]>([]);
|
||||||
const [activeTabKey, setActiveTabKey] = useState<string>(
|
const [activeTabKey, setActiveTabKey] = useState<string>(
|
||||||
FilterTabs.configuration.key,
|
FilterTabs.configuration.key,
|
||||||
);
|
);
|
||||||
const [activeFilterPanelKey, setActiveFilterPanelKey] = useState<
|
const [activeFilterPanelKey, setActiveFilterPanelKey] = useState<
|
||||||
string | string[]
|
string | string[] | undefined
|
||||||
>(FilterPanels.basic.key);
|
>();
|
||||||
const [undoFormValues, setUndoFormValues] = useState<Record<
|
const [undoFormValues, setUndoFormValues] = useState<Record<
|
||||||
string,
|
string,
|
||||||
any
|
any
|
||||||
@ -325,6 +326,24 @@ const FiltersConfigForm = (
|
|||||||
const formValues = form.getFieldValue('filters')?.[filterId];
|
const formValues = form.getFieldValue('filters')?.[filterId];
|
||||||
const formFilter = formValues || undoFormValues || defaultFormFilter;
|
const formFilter = formValues || undoFormValues || defaultFormFilter;
|
||||||
|
|
||||||
|
const parentFilterOptions = useMemo(
|
||||||
|
() =>
|
||||||
|
parentFilters.map(filter => ({
|
||||||
|
value: filter.id,
|
||||||
|
label: filter.title,
|
||||||
|
})),
|
||||||
|
[parentFilters],
|
||||||
|
);
|
||||||
|
|
||||||
|
const parentId =
|
||||||
|
formFilter?.parentFilter?.value || filterToEdit?.cascadeParentIds?.[0];
|
||||||
|
|
||||||
|
const parentFilter = parentFilterOptions.find(
|
||||||
|
({ value }) => value === parentId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasParentFilter = !!parentFilter;
|
||||||
|
|
||||||
const nativeFilterItems = getChartMetadataRegistry().items;
|
const nativeFilterItems = getChartMetadataRegistry().items;
|
||||||
const nativeFilterVizTypes = Object.entries(nativeFilterItems)
|
const nativeFilterVizTypes = Object.entries(nativeFilterItems)
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -374,7 +393,7 @@ const FiltersConfigForm = (
|
|||||||
filterType: formFilter?.filterType,
|
filterType: formFilter?.filterType,
|
||||||
filterToEdit,
|
filterToEdit,
|
||||||
formFilter,
|
formFilter,
|
||||||
removed,
|
removed: isRemoved,
|
||||||
})
|
})
|
||||||
: {};
|
: {};
|
||||||
const hasColumn = !!mainControlItems.groupby;
|
const hasColumn = !!mainControlItems.groupby;
|
||||||
@ -506,19 +525,6 @@ const FiltersConfigForm = (
|
|||||||
[filterId, form, formChanged],
|
[filterId, form, formChanged],
|
||||||
);
|
);
|
||||||
|
|
||||||
const parentFilterOptions = parentFilters.map(filter => ({
|
|
||||||
value: filter.id,
|
|
||||||
label: filter.title,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const parentFilter = parentFilterOptions.find(
|
|
||||||
({ value }) =>
|
|
||||||
value === formFilter?.parentFilter?.value ||
|
|
||||||
value === filterToEdit?.cascadeParentIds?.[0],
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasParentFilter = !!parentFilter;
|
|
||||||
|
|
||||||
const hasPreFilter =
|
const hasPreFilter =
|
||||||
!!formFilter?.adhoc_filters ||
|
!!formFilter?.adhoc_filters ||
|
||||||
!!formFilter?.time_range ||
|
!!formFilter?.time_range ||
|
||||||
@ -583,28 +589,32 @@ const FiltersConfigForm = (
|
|||||||
return Promise.reject(new Error(t('Pre-filter is required')));
|
return Promise.reject(new Error(t('Pre-filter is required')));
|
||||||
};
|
};
|
||||||
|
|
||||||
let hasCheckedAdvancedControl = hasParentFilter || hasPreFilter || hasSorting;
|
|
||||||
if (!hasCheckedAdvancedControl) {
|
|
||||||
hasCheckedAdvancedControl = Object.keys(controlItems)
|
|
||||||
.filter(key => !BASIC_CONTROL_ITEMS.includes(key))
|
|
||||||
.some(key => controlItems[key].checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ParentSelect = ({
|
const ParentSelect = ({
|
||||||
value,
|
value,
|
||||||
...rest
|
...rest
|
||||||
}: {
|
}: {
|
||||||
value?: { value: string | number };
|
value?: { value: string | number };
|
||||||
}) => (
|
}) => {
|
||||||
<Select
|
const parentId = value?.value;
|
||||||
ariaLabel={t('Parent filter')}
|
const isParentRemoved = parentId && removedFilters[parentId];
|
||||||
placeholder={t('None')}
|
let options = parentFilterOptions;
|
||||||
options={parentFilterOptions}
|
if (isParentRemoved) {
|
||||||
allowClear
|
options = [
|
||||||
value={value?.value}
|
{ label: t('(deleted)'), value: parentId as string },
|
||||||
{...rest}
|
...parentFilterOptions,
|
||||||
/>
|
];
|
||||||
);
|
}
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
ariaLabel={t('Parent filter')}
|
||||||
|
placeholder={t('None')}
|
||||||
|
options={options}
|
||||||
|
allowClear
|
||||||
|
value={parentId}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (datasetId) {
|
if (datasetId) {
|
||||||
@ -647,12 +657,28 @@ const FiltersConfigForm = (
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const activeFilterPanelKey = [FilterPanels.basic.key];
|
// Run only once when the control items are available
|
||||||
if (hasCheckedAdvancedControl) {
|
if (!activeFilterPanelKey && !isEmpty(controlItems)) {
|
||||||
activeFilterPanelKey.push(FilterPanels.advanced.key);
|
const hasCheckedAdvancedControl =
|
||||||
|
hasParentFilter ||
|
||||||
|
hasPreFilter ||
|
||||||
|
hasSorting ||
|
||||||
|
Object.keys(controlItems)
|
||||||
|
.filter(key => !BASIC_CONTROL_ITEMS.includes(key))
|
||||||
|
.some(key => controlItems[key].checked);
|
||||||
|
setActiveFilterPanelKey(
|
||||||
|
hasCheckedAdvancedControl
|
||||||
|
? [FilterPanels.basic.key, FilterPanels.advanced.key]
|
||||||
|
: FilterPanels.basic.key,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
setActiveFilterPanelKey(activeFilterPanelKey);
|
}, [
|
||||||
}, [hasCheckedAdvancedControl]);
|
activeFilterPanelKey,
|
||||||
|
hasParentFilter,
|
||||||
|
hasPreFilter,
|
||||||
|
hasSorting,
|
||||||
|
controlItems,
|
||||||
|
]);
|
||||||
|
|
||||||
const initiallyExcludedCharts = useMemo(() => {
|
const initiallyExcludedCharts = useMemo(() => {
|
||||||
const excluded: number[] = [];
|
const excluded: number[] = [];
|
||||||
@ -678,20 +704,20 @@ const FiltersConfigForm = (
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// just removed, saving current form items for eventual undo
|
// just removed, saving current form items for eventual undo
|
||||||
if (removed) {
|
if (isRemoved) {
|
||||||
setUndoFormValues(formValues);
|
setUndoFormValues(formValues);
|
||||||
}
|
}
|
||||||
}, [removed]);
|
}, [isRemoved]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// the filter was just restored after undo
|
// the filter was just restored after undo
|
||||||
if (undoFormValues && !removed) {
|
if (undoFormValues && !isRemoved) {
|
||||||
setNativeFilterFieldValues(form, filterId, undoFormValues);
|
setNativeFilterFieldValues(form, filterId, undoFormValues);
|
||||||
setUndoFormValues(null);
|
setUndoFormValues(null);
|
||||||
}
|
}
|
||||||
}, [formValues, filterId, form, removed, undoFormValues]);
|
}, [formValues, filterId, form, isRemoved, undoFormValues]);
|
||||||
|
|
||||||
if (removed) {
|
if (isRemoved) {
|
||||||
return <RemovedFilter onClick={() => restoreFilter(filterId)} />;
|
return <RemovedFilter onClick={() => restoreFilter(filterId)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -718,13 +744,13 @@ const FiltersConfigForm = (
|
|||||||
name={['filters', filterId, 'name']}
|
name={['filters', filterId, 'name']}
|
||||||
label={<StyledLabel>{t('Filter name')}</StyledLabel>}
|
label={<StyledLabel>{t('Filter name')}</StyledLabel>}
|
||||||
initialValue={filterToEdit?.name}
|
initialValue={filterToEdit?.name}
|
||||||
rules={[{ required: !removed, message: t('Name is required') }]}
|
rules={[{ required: !isRemoved, message: t('Name is required') }]}
|
||||||
>
|
>
|
||||||
<Input {...getFiltersConfigModalTestId('name-input')} />
|
<Input {...getFiltersConfigModalTestId('name-input')} />
|
||||||
</StyledFormItem>
|
</StyledFormItem>
|
||||||
<StyledFormItem
|
<StyledFormItem
|
||||||
name={['filters', filterId, 'filterType']}
|
name={['filters', filterId, 'filterType']}
|
||||||
rules={[{ required: !removed, message: t('Name is required') }]}
|
rules={[{ required: !isRemoved, message: t('Name is required') }]}
|
||||||
initialValue={filterToEdit?.filterType || 'filter_select'}
|
initialValue={filterToEdit?.filterType || 'filter_select'}
|
||||||
label={<StyledLabel>{t('Filter Type')}</StyledLabel>}
|
label={<StyledLabel>{t('Filter Type')}</StyledLabel>}
|
||||||
{...getFiltersConfigModalTestId('filter-type')}
|
{...getFiltersConfigModalTestId('filter-type')}
|
||||||
@ -782,7 +808,7 @@ const FiltersConfigForm = (
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
rules={[
|
rules={[
|
||||||
{ required: !removed, message: t('Dataset is required') },
|
{ required: !isRemoved, message: t('Dataset is required') },
|
||||||
]}
|
]}
|
||||||
{...getFiltersConfigModalTestId('datasource-input')}
|
{...getFiltersConfigModalTestId('datasource-input')}
|
||||||
>
|
>
|
||||||
@ -837,7 +863,7 @@ const FiltersConfigForm = (
|
|||||||
formChanged();
|
formChanged();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!removed && (
|
{!isRemoved && (
|
||||||
<StyledRowSubFormItem
|
<StyledRowSubFormItem
|
||||||
name={['filters', filterId, 'defaultDataMask']}
|
name={['filters', filterId, 'defaultDataMask']}
|
||||||
initialValue={initialDefaultValue}
|
initialValue={initialDefaultValue}
|
||||||
|
@ -196,6 +196,27 @@ export function FiltersConfigModal({
|
|||||||
title: getFilterTitle(id),
|
title: getFilterTitle(id),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const cleanDeletedParents = (values: NativeFiltersForm | null) => {
|
||||||
|
Object.keys(filterConfigMap).forEach(key => {
|
||||||
|
const filter = filterConfigMap[key];
|
||||||
|
const parentId = filter.cascadeParentIds?.[0];
|
||||||
|
if (parentId && removedFilters[parentId]) {
|
||||||
|
filter.cascadeParentIds = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const filters = values?.filters;
|
||||||
|
if (filters) {
|
||||||
|
Object.keys(filters).forEach(key => {
|
||||||
|
const filter = filters[key];
|
||||||
|
const parentId = filter.parentFilter?.value;
|
||||||
|
if (parentId && removedFilters[parentId]) {
|
||||||
|
filter.parentFilter = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
const values: NativeFiltersForm | null = await validateForm(
|
const values: NativeFiltersForm | null = await validateForm(
|
||||||
form,
|
form,
|
||||||
@ -207,6 +228,7 @@ export function FiltersConfigModal({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (values) {
|
if (values) {
|
||||||
|
cleanDeletedParents(values);
|
||||||
createHandleSave(
|
createHandleSave(
|
||||||
filterConfigMap,
|
filterConfigMap,
|
||||||
filterIds,
|
filterIds,
|
||||||
@ -295,7 +317,7 @@ export function FiltersConfigModal({
|
|||||||
form={form}
|
form={form}
|
||||||
filterId={id}
|
filterId={id}
|
||||||
filterToEdit={filterConfigMap[id]}
|
filterToEdit={filterConfigMap[id]}
|
||||||
removed={!!removedFilters[id]}
|
removedFilters={removedFilters}
|
||||||
restoreFilter={restoreFilter}
|
restoreFilter={restoreFilter}
|
||||||
parentFilters={getParentFilters(id)}
|
parentFilters={getParentFilters(id)}
|
||||||
/>
|
/>
|
||||||
|
@ -36,7 +36,7 @@ export interface NativeFiltersFormItem {
|
|||||||
};
|
};
|
||||||
defaultValue: any;
|
defaultValue: any;
|
||||||
defaultDataMask: DataMask;
|
defaultDataMask: DataMask;
|
||||||
parentFilter: {
|
parentFilter?: {
|
||||||
value: string;
|
value: string;
|
||||||
label: string;
|
label: string;
|
||||||
};
|
};
|
||||||
|
@ -65,8 +65,8 @@ export const validateForm = async (
|
|||||||
'Cannot create cyclic hierarchy',
|
'Cannot create cyclic hierarchy',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const parentId = formValues.filters[filterId]
|
const parentId = formValues.filters?.[filterId]
|
||||||
? formValues.filters[filterId].parentFilter?.value
|
? formValues.filters[filterId]?.parentFilter?.value
|
||||||
: filterConfigMap[filterId]?.cascadeParentIds?.[0];
|
: filterConfigMap[filterId]?.cascadeParentIds?.[0];
|
||||||
if (parentId) {
|
if (parentId) {
|
||||||
validateCycles(parentId, [...trace, filterId]);
|
validateCycles(parentId, [...trace, filterId]);
|
||||||
@ -111,7 +111,7 @@ export const createHandleSave = (
|
|||||||
.filter(id => !removedFilters[id])
|
.filter(id => !removedFilters[id])
|
||||||
.map(id => {
|
.map(id => {
|
||||||
// create a filter config object from the form inputs
|
// create a filter config object from the form inputs
|
||||||
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];
|
||||||
const target: Partial<Target> = {};
|
const target: Partial<Target> = {};
|
||||||
|
Loading…
Reference in New Issue
Block a user