From 3192ce54fbfd7bb52c484dc77a7963852d2ab78b Mon Sep 17 00:00:00 2001 From: Ajay Mancheery Date: Tue, 9 Nov 2021 12:18:36 -0800 Subject: [PATCH 1/8] Tests are working, type errors are fixed --- .../nativeFilters/NativeFiltersModal_spec.tsx | 17 +- .../DashboardBuilder/DashboardContainer.tsx | 9 + .../components/FiltersBadge/selectors.ts | 13 +- .../FilterControls/FilterControls.tsx | 48 ++++-- .../FilterBar/FilterControls/utils.ts | 12 +- .../nativeFilters/FilterBar/utils.ts | 7 +- .../FilterConfigPane.test.tsx | 15 +- .../FilterConfigurePane.tsx | 11 +- .../FiltersConfigModal/FilterTitlePane.tsx | 74 ++++---- .../FiltersConfigModal/FiltersConfigModal.tsx | 160 +++++++++++------- .../FiltersConfigModal/SectionConfigForm.tsx | 44 +++++ .../nativeFilters/FiltersConfigModal/state.ts | 3 +- .../nativeFilters/FiltersConfigModal/types.ts | 8 +- .../nativeFilters/FiltersConfigModal/utils.ts | 109 +++++++----- .../components/nativeFilters/state.ts | 47 +++-- .../components/nativeFilters/types.ts | 8 +- 16 files changed, 385 insertions(+), 200 deletions(-) create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/SectionConfigForm.tsx diff --git a/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/NativeFiltersModal_spec.tsx b/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/NativeFiltersModal_spec.tsx index b153d32cd5288..3becc59dd7bbe 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/NativeFiltersModal_spec.tsx +++ b/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/NativeFiltersModal_spec.tsx @@ -25,6 +25,7 @@ import { Provider } from 'react-redux'; import { mockStore } from 'spec/fixtures/mockStore'; import { styledMount as mount } from 'spec/helpers/theming'; import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint'; +import { Dropdown, Menu } from 'src/common/components'; import Alert from 'src/components/Alert'; import { FiltersConfigModal } from 'src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal'; @@ -60,7 +61,7 @@ jest.mock('@superset-ui/core', () => ({ describe('FiltersConfigModal', () => { const mockedProps = { isOpen: true, - initialFilterId: 'DefaultsID', + initialFilterId: 'NATIVE_FILTER-1', createNewOnOpen: true, onCancel: jest.fn(), onSave: jest.fn(), @@ -81,7 +82,7 @@ describe('FiltersConfigModal', () => { ); }); - it('the form validates required fields', async () => { + it('the form validates required fields', () => { const onSave = jest.fn(); const wrapper = setup({ save: onSave }); act(() => { @@ -92,7 +93,7 @@ describe('FiltersConfigModal', () => { wrapper.find('.ant-modal-footer button').at(1).simulate('click'); }); - await waitForComponentToPaint(wrapper); + // await waitForComponentToPaint(wrapper); expect(onSave.mock.calls).toHaveLength(0); }); @@ -112,9 +113,13 @@ describe('FiltersConfigModal', () => { await waitForComponentToPaint(wrapper); } - function addFilter() { + async function addFilter() { act(() => { - wrapper.find('[aria-label="Add filter"]').at(0).simulate('click'); + wrapper.find(Dropdown).at(0).simulate('mouseEnter'); + }); + await waitForComponentToPaint(wrapper, 300); + act(() => { + wrapper.find(Menu.Item).at(0).simulate('click'); }); } @@ -124,7 +129,7 @@ describe('FiltersConfigModal', () => { }); it('shows correct alert message for unsaved filters', async () => { - addFilter(); + await addFilter(); await clickCancel(); expect(onCancel.mock.calls).toHaveLength(0); expect(wrapper.find(Alert).text()).toContain( diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx index 511b643d9d8dd..32035fcf1d82f 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx +++ b/superset-frontend/src/dashboard/components/DashboardBuilder/DashboardContainer.tsx @@ -36,6 +36,7 @@ import { getChartIdsInFilterScope } from '../../util/activeDashboardFilters'; import findTabIndexByComponentId from '../../util/findTabIndexByComponentId'; import { findTabsWithChartsInScope } from '../nativeFilters/utils'; import { setInScopeStatusOfFilters } from '../../actions/nativeFilters'; +import { NATIVE_FILTER_SECTION_PREFIX } from '../nativeFilters/FiltersConfigModal/utils'; type DashboardContainerProps = { topLevelTabs?: LayoutItem; @@ -71,6 +72,7 @@ const DashboardContainer: FC = ({ topLevelTabs }) => { const filterScopes = Object.values(nativeFilters ?? {}).map(filter => ({ id: filter.id, scope: filter.scope, + type: filter.type, })); useEffect(() => { if ( @@ -80,6 +82,13 @@ const DashboardContainer: FC = ({ topLevelTabs }) => { return; } const scopes = filterScopes.map(filterScope => { + if (filterScope.id.startsWith(NATIVE_FILTER_SECTION_PREFIX)) { + return { + filterId: filterScope.id, + tabsInScope: [], + chartsInScope: [], + }; + } const { scope } = filterScope; const chartsInScope: number[] = getChartIdsInFilterScope({ filterScope: { diff --git a/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts b/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts index ea850ae2146b7..695e2f94bc790 100644 --- a/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts +++ b/superset-frontend/src/dashboard/components/FiltersBadge/selectors.ts @@ -29,6 +29,7 @@ import { DataMaskStateWithId, DataMaskType } from 'src/dataMask/types'; import { areObjectsEqual } from 'src/reduxUtils'; import { Layout } from '../../types'; import { getTreeCheckedItems } from '../nativeFilters/FiltersConfigModal/FiltersConfigForm/FilterScope/utils'; +import { NativeFilterType } from '../nativeFilters/types'; export enum IndicatorStatus { Unset = 'UNSET', @@ -268,11 +269,13 @@ export const selectNativeIndicatorsForChart = ( nativeFilterIndicators = nativeFilters && Object.values(nativeFilters) - .filter(nativeFilter => - getTreeCheckedItems(nativeFilter.scope, dashboardLayout).some( - layoutItem => - dashboardLayout[layoutItem]?.meta?.chartId === chartId, - ), + .filter( + nativeFilter => + nativeFilter.type === NativeFilterType.NATIVE_FILTER && + getTreeCheckedItems(nativeFilter.scope, dashboardLayout).some( + layoutItem => + dashboardLayout[layoutItem]?.meta?.chartId === chartId, + ), ) .map(nativeFilter => { const column = nativeFilter.targets[0]?.column?.name; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx index ab7855f591ab0..86a474fc96640 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/FilterControls.tsx @@ -30,10 +30,15 @@ import { useDashboardHasTabs, useSelectFiltersInScope, } from 'src/dashboard/components/nativeFilters/state'; -import { Filter } from 'src/dashboard/components/nativeFilters/types'; +import { + Filter, + NativeFilterType, + Section, +} from 'src/dashboard/components/nativeFilters/types'; import CascadePopover from '../CascadeFilters/CascadePopover'; import { useFilters } from '../state'; import { buildCascadeFiltersTree } from './utils'; +import { CascadeFilter } from '../CascadeFilters/types'; const Wrapper = styled.div` padding: ${({ theme }) => theme.gridUnit * 4}px; @@ -80,21 +85,32 @@ const FilterControls: FC = ({ const showCollapsePanel = dashboardHasTabs && cascadeFilters.length > 0; const cascadePopoverFactory = useCallback( - index => ( - - setVisiblePopoverId(visible ? cascadeFilters[index].id : null) - } - filter={cascadeFilters[index]} - onFilterSelectionChange={onFilterSelectionChange} - directPathToChild={directPathToChild} - inView={false} - /> - ), + index => { + const filter = cascadeFilters[index]; + if (filter.type === NativeFilterType.SECTION) { + return ( +
+

{(filter as Section).title}

+

{(filter as Section).description}

+
+ ); + } + return ( + + setVisiblePopoverId(visible ? filter.id : null) + } + filter={filter as CascadeFilter} + onFilterSelectionChange={onFilterSelectionChange} + directPathToChild={directPathToChild} + inView={false} + /> + ); + }, [ cascadeFilters, JSON.stringify(dataMaskSelected), diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/utils.ts b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/utils.ts index 0b6d9a78f3ef5..0edd5b9083f85 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/utils.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterControls/utils.ts @@ -22,12 +22,14 @@ import { setFocusedNativeFilter, unsetFocusedNativeFilter, } from 'src/dashboard/actions/nativeFilters'; -import { Filter } from '../../types'; +import { Filter, NativeFilterType, Section } from '../../types'; import { CascadeFilter } from '../CascadeFilters/types'; import { mapParentFiltersToChildren } from '../utils'; // eslint-disable-next-line import/prefer-default-export -export function buildCascadeFiltersTree(filters: Filter[]): CascadeFilter[] { +export function buildCascadeFiltersTree( + filters: Array
, +): Array { const cascadeChildren = mapParentFiltersToChildren(filters); const getCascadeFilter = (filter: Filter): CascadeFilter => { @@ -39,7 +41,11 @@ export function buildCascadeFiltersTree(filters: Filter[]): CascadeFilter[] { }; return filters - .filter(filter => !filter.cascadeParentIds?.length) + .filter( + filter => + filter.type === NativeFilterType.SECTION || + !(filter as Filter).cascadeParentIds?.length, + ) .map(getCascadeFilter); } diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts index af358a09fe214..2add9e5239660 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/utils.ts @@ -20,7 +20,7 @@ import { DataMaskStateWithId } from 'src/dataMask/types'; import { areObjectsEqual } from 'src/reduxUtils'; import { FilterState } from '@superset-ui/core'; -import { Filter } from '../types'; +import { Filter, Section } from '../types'; export enum TabIds { AllFilters = 'allFilters', @@ -28,11 +28,12 @@ export enum TabIds { } export function mapParentFiltersToChildren( - filters: Filter[], + filters: (Filter | Section)[], ): { [id: string]: Filter[] } { const cascadeChildren = {}; filters.forEach(filter => { - const [parentId] = filter.cascadeParentIds || []; + const [parentId] = + ('cascadeParentIds' in filter && filter.cascadeParentIds) || []; if (parentId) { if (!cascadeChildren[parentId]) { cascadeChildren[parentId] = []; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterConfigPane.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterConfigPane.test.tsx index 8785e7bd330b4..d7c5b10192ab9 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterConfigPane.test.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterConfigPane.test.tsx @@ -26,7 +26,8 @@ const defaultProps = { children: jest.fn(), getFilterTitle: (id: string) => id, onChange: jest.fn(), - onEdit: jest.fn(), + onAdd: jest.fn(), + onRemove: jest.fn(), onRearrange: jest.fn(), restoreFilter: jest.fn(), currentFilterId: 'NATIVE_FILTER-1', @@ -93,22 +94,24 @@ test('remove filter', async () => { }), ); }); - expect(defaultProps.onEdit).toHaveBeenCalledWith('NATIVE_FILTER-2', 'remove'); + expect(defaultProps.onRemove).toHaveBeenCalledWith('NATIVE_FILTER-2'); }); test('add filter', async () => { defaultRender(); // First trash icon - const removeFilterIcon = screen.getByText('Add filter')!; + const addButton = screen.getByText('Add')!; + fireEvent.mouseOver(addButton); + const addFilterButton = await screen.findByText('Filter'); + await act(async () => { fireEvent( - removeFilterIcon, + addFilterButton, new MouseEvent('click', { bubbles: true, cancelable: true, }), ); }); - - expect(defaultProps.onEdit).toHaveBeenCalledWith('', 'add'); + expect(defaultProps.onAdd).toHaveBeenCalledWith('NATIVE_FILTER'); }); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterConfigurePane.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterConfigurePane.tsx index 4d5174094ab0e..e77a29bad46b9 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterConfigurePane.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterConfigurePane.tsx @@ -18,6 +18,7 @@ */ import { styled } from '@superset-ui/core'; import React from 'react'; +import { NativeFilterType } from '../types'; import FilterTitlePane from './FilterTitlePane'; import { FilterRemoval } from './types'; @@ -25,7 +26,8 @@ interface Props { children: (filterId: string) => React.ReactNode; getFilterTitle: (filterId: string) => string; onChange: (activeKey: string) => void; - onEdit: (filterId: string, action: 'add' | 'remove') => void; + onAdd: (type: NativeFilterType) => void; + onRemove: (id: string) => void; onRearrange: (dragIndex: number, targetIndex: number) => void; erroredFilters: string[]; restoreFilter: (id: string) => void; @@ -52,9 +54,10 @@ const TitlesContainer = styled.div` const FiltureConfigurePane: React.FC = ({ getFilterTitle, onChange, - onEdit, + onRemove, onRearrange, restoreFilter, + onAdd, erroredFilters, children, currentFilterId, @@ -72,9 +75,9 @@ const FiltureConfigurePane: React.FC = ({ erroredFilters={erroredFilters} getFilterTitle={getFilterTitle} onChange={onChange} - onEdit={onEdit} + onAdd={(type: NativeFilterType) => onAdd(type)} onRearrage={onRearrange} - onRemove={(id: string) => onEdit(id, 'remove')} + onRemove={(id: string) => onRemove(id)} restoreFilter={restoreFilter} /> diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTitlePane.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTitlePane.tsx index 5da0b1031a9a5..914eb478aa90a 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTitlePane.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FilterTitlePane.tsx @@ -16,9 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { PlusOutlined } from '@ant-design/icons'; import { styled, t, useTheme } from '@superset-ui/core'; import React from 'react'; +import { Dropdown, MainNav as Menu } from 'src/common/components'; +import { NativeFilterType } from '../types'; import FilterTitleContainer from './FilterTitleContainer'; import { FilterRemoval } from './types'; @@ -28,13 +29,17 @@ interface Props { onRearrage: (dragIndex: number, targetIndex: number) => void; onRemove: (id: string) => void; onChange: (id: string) => void; - onEdit: (filterId: string, action: 'add' | 'remove') => void; + onAdd: (type: NativeFilterType) => void; removedFilters: Record; currentFilterId: string; filterGroups: string[][]; erroredFilters: string[]; } +const StyledI = styled.div` + color: ${({ theme }) => theme.colors.primary.dark1}; +`; + const StyledHeader = styled.div` ${({ theme }) => ` color: ${theme.colors.grayscale.dark1}; @@ -52,21 +57,10 @@ const TabsContainer = styled.div` flex-direction: column; `; -const StyledAddFilterBox = styled.div` - color: ${({ theme }) => theme.colors.primary.dark1}; - padding: ${({ theme }) => theme.gridUnit * 2}px; - border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; - cursor: pointer; - margin-top: auto; - &:hover { - color: ${({ theme }) => theme.colors.primary.base}; - } -`; - const FilterTitlePane: React.FC = ({ getFilterTitle, onChange, - onEdit, + onAdd, onRemove, onRearrage, restoreFilter, @@ -76,6 +70,29 @@ const FilterTitlePane: React.FC = ({ erroredFilters, }) => { const theme = useTheme(); + const options = [ + { label: 'Filter', type: NativeFilterType.NATIVE_FILTER }, + { label: 'Section', type: NativeFilterType.SECTION }, + ]; + const handleOnAdd = (type: NativeFilterType) => { + onAdd(type); + setTimeout(() => { + const element = document.getElementById('native-filters-tabs'); + if (element) { + const navList = element.getElementsByClassName('ant-tabs-nav-list')[0]; + navList.scrollTop = navList.scrollHeight; + } + }, 0); + }; + const menu = ( + + {options.map(item => ( + handleOnAdd(item.type)}> + {item.label} + + ))} + + ); return ( Filters @@ -98,29 +115,12 @@ const FilterTitlePane: React.FC = ({ restoreFilter={restoreFilter} /> - { - onEdit('', 'add'); - setTimeout(() => { - const element = document.getElementById('native-filters-tabs'); - if (element) { - const navList = element.getElementsByClassName( - 'ant-tabs-nav-list', - )[0]; - navList.scrollTop = navList.scrollHeight; - } - }, 0); - }} - > - {' '} - - {t('Add filter')} - - + +
+ {' '} + {t('Add')} +
+
); }; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.tsx index 04cae20bce3df..5d97891fdf531 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.tsx @@ -30,7 +30,12 @@ import ErrorBoundary from 'src/components/ErrorBoundary'; import { StyledModal } from 'src/components/Modal'; import { testWithId } from 'src/utils/testUtils'; import { useFilterConfigMap, useFilterConfiguration } from '../state'; -import { FilterConfiguration } from '../types'; +import { + Filter, + FilterConfiguration, + NativeFilterType, + Section, +} from '../types'; import FiltureConfigurePane from './FilterConfigurePane'; import FiltersConfigForm, { FilterPanels, @@ -40,12 +45,14 @@ import { useOpenModal, useRemoveCurrentFilter } from './state'; import { FilterRemoval, NativeFiltersForm, FilterHierarchy } from './types'; import { createHandleSave, - createHandleTabEdit, + createHandleRemoveItem, generateFilterId, getFilterIds, buildFilterGroup, validateForm, + NATIVE_FILTER_SECTION_PREFIX, } from './utils'; +import SectionConfigForm from './SectionConfigForm'; const StyledModalWrapper = styled(StyledModal)` min-width: 700px; @@ -153,7 +160,10 @@ export function FiltersConfigModal({ const getInitialFilterHierarchy = () => filterConfig.map(filter => ({ id: filter.id, - parentId: filter.cascadeParentIds[0] || null, + parentId: + filter.type === NativeFilterType.NATIVE_FILTER + ? (filter as Filter).cascadeParentIds[0] || null + : null, })); const [filterHierarchy, setFilterHierarchy] = useState(() => @@ -175,24 +185,28 @@ export function FiltersConfigModal({ }; // generates a new filter id and appends it to the newFilterIds - const addFilter = useCallback(() => { - const newFilterId = generateFilterId(); - setNewFilterIds([...newFilterIds, newFilterId]); - setCurrentFilterId(newFilterId); - setSaveAlertVisible(false); - setFilterHierarchy(previousState => [ - ...previousState, - { id: newFilterId, parentId: null }, - ]); - setOrderedFilters([...orderedFilters, [newFilterId]]); - setActiveFilterPanelKey(`${newFilterId}-${FilterPanels.basic.key}`); - }, [ - newFilterIds, - orderedFilters, - setCurrentFilterId, - setFilterHierarchy, - setOrderedFilters, - ]); + const addFilter = useCallback( + (type: NativeFilterType) => { + const newFilterId = generateFilterId(type); + setNewFilterIds([...newFilterIds, newFilterId]); + setCurrentFilterId(newFilterId); + setSaveAlertVisible(false); + setFilterHierarchy(previousState => [ + ...previousState, + { id: newFilterId, parentId: null }, + ]); + setOrderedFilters([...orderedFilters, [newFilterId]]); + setActiveFilterPanelKey(`${newFilterId}-${FilterPanels.basic.key}`); + }, + [ + newFilterIds, + orderedFilters, + setCurrentFilterId, + setFilterHierarchy, + setOrderedFilters, + setNewFilterIds, + ], + ); useOpenModal(isOpen, addFilter, createNewOnOpen); @@ -203,12 +217,11 @@ export function FiltersConfigModal({ setCurrentFilterId, ); - const handleTabEdit = createHandleTabEdit( + const handleRemoveItem = createHandleRemoveItem( setRemovedFilters, setSaveAlertVisible, setOrderedFilters, setFilterHierarchy, - addFilter, filterHierarchy, ); @@ -230,21 +243,29 @@ export function FiltersConfigModal({ form.setFieldsValue({ changed: false }); }; - const getFilterTitle = (id: string) => - formValues.filters[id]?.name || - filterConfigMap[id]?.name || - t('[untitled]'); - + const getFilterTitle = (id: string) => { + const formValue = formValues.filters[id]; + const config = filterConfigMap[id]; + return ( + (formValue && 'name' in formValue && formValue.name) || + (formValue && 'title' in formValue && formValue.title) || + (config && 'name' in config && config.name) || + (config && 'title' in config && config.title) || + '[untitled]' + ); + }; const getParentFilters = (id: string) => filterIds .filter(filterId => filterId !== id && !removedFilters[filterId]) - .filter(filterId => - CASCADING_FILTERS.includes( - formValues.filters[filterId] - ? formValues.filters[filterId].filterType - : filterConfigMap[filterId]?.filterType, - ), - ) + .filter(filterId => { + const component = + formValues.filters[filterId] || filterConfigMap[filterId]; + return ( + component && + 'filterType' in component && + CASCADING_FILTERS.includes(component.filterType) + ); + }) .map(id => ({ id, title: getFilterTitle(id), @@ -253,6 +274,9 @@ export function FiltersConfigModal({ const cleanDeletedParents = (values: NativeFiltersForm | null) => { Object.keys(filterConfigMap).forEach(key => { const filter = filterConfigMap[key]; + if (!('cascadeParentIds' in filter)) { + return; + } const parentId = filter.cascadeParentIds?.[0]; if (parentId && removedFilters[parentId]) { filter.cascadeParentIds = []; @@ -263,6 +287,9 @@ export function FiltersConfigModal({ if (filters) { Object.keys(filters).forEach(key => { const filter = filters[key]; + if (!('parentFilter' in filter)) { + return; + } const parentId = filter.parentFilter?.value; if (parentId && removedFilters[parentId]) { filter.parentFilter = undefined; @@ -369,13 +396,18 @@ export function FiltersConfigModal({ const onValuesChange = useMemo( () => debounce((changes: any, values: NativeFiltersForm) => { - if ( + const didChangeFilterName = + changes.filters && + Object.values(changes.filters).some( + (filter: any) => filter.name !== null, + ); + const didChangeSectionTitle = changes.filters && Object.values(changes.filters).some( - (filter: any) => filter.name != null, - ) - ) { - // we only need to set this if a name changed + (filter: any) => filter.title !== null, + ); + if (didChangeFilterName || didChangeSectionTitle) { + // we only need to set this if a name/title changed setFormValues(values); } const changedFilterHierarchies = Object.keys(changes.filters) @@ -402,6 +434,31 @@ export function FiltersConfigModal({ prevErroredFilters.filter(f => !removedFilters[f]), ); }, [removedFilters]); + const getForm = (id: string) => { + const isSection = id.startsWith(NATIVE_FILTER_SECTION_PREFIX); + return isSection ? ( + + ) : ( + setActiveFilterPanelKey(key)} + isActive={currentFilterId === id} + setErroredFilters={setErroredFilters} + /> + ); + }; return ( - {(id: string) => ( - - setActiveFilterPanelKey(key) - } - isActive={currentFilterId === id} - setErroredFilters={setErroredFilters} - /> - )} + {(id: string) => getForm(id)} diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/SectionConfigForm.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/SectionConfigForm.tsx new file mode 100644 index 0000000000000..6faddf5f6c98c --- /dev/null +++ b/superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/SectionConfigForm.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { FormItem } from 'src/components/Form'; +import { Input, TextArea } from 'src/common/components'; +import { styled } from '@superset-ui/core'; +import { NativeFilterType } from '../types'; + +interface Props { + componentId: string; + section?: { + title: string; + description: string; + }; +} +const Container = styled.div` + ${({ theme }) => ` + padding: ${theme.gridUnit * 4}px; + `} +`; + +const SectionConfigForm: React.FC = ({ componentId, section }) => ( + + + + + +