diff --git a/superset-frontend/src/components/Button/index.tsx b/superset-frontend/src/components/Button/index.tsx index 168f1767e0..347c6b8dac 100644 --- a/superset-frontend/src/components/Button/index.tsx +++ b/superset-frontend/src/components/Button/index.tsx @@ -30,6 +30,7 @@ export interface ButtonProps { id?: string; className?: string; tooltip?: string; + ghost?: boolean; placement?: | 'bottom' | 'left' diff --git a/superset-frontend/src/dashboard/actions/nativeFilters.ts b/superset-frontend/src/dashboard/actions/nativeFilters.ts index 8a18d523da..2e1c7e732d 100644 --- a/superset-frontend/src/dashboard/actions/nativeFilters.ts +++ b/superset-frontend/src/dashboard/actions/nativeFilters.ts @@ -134,14 +134,15 @@ export const setFilterSetsConfiguration = ( filter_sets_configuration: filterSetsConfig, }), }); + const newMetadata = JSON.parse(response.result.json_metadata); dispatch( dashboardInfoChanged({ - metadata: JSON.parse(response.result.json_metadata), + metadata: newMetadata, }), ); dispatch({ type: SET_FILTER_SETS_CONFIG_COMPLETE, - filterSetsConfig, + filterSetsConfig: newMetadata?.filter_sets_configuration, }); } catch (err) { dispatch({ type: SET_FILTER_SETS_CONFIG_FAIL, filterSetsConfig }); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx index b5f05e4645..03779ab960 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx @@ -37,6 +37,7 @@ import { buildCascadeFiltersTree, mapParentFiltersToChildren } from './utils'; import CascadePopover from './CascadePopover'; import FilterSets from './FilterSets/FilterSets'; import { useDataMask, useFilters, useFilterSets } from './state'; +import EditSection from './FilterSets/EditSection'; const barWidth = `250px`; @@ -173,6 +174,7 @@ const FilterBar: React.FC = ({ toggleFiltersBar, directPathToChild, }) => { + const [editFilterSetId, setEditFilterSetId] = useState(null); const [dataMaskSelected, setDataMaskSelected] = useImmer({}); const [ lastAppliedFilterData, @@ -337,19 +339,29 @@ const FilterBar: React.FC = ({ {}} + activeKey={editFilterSetId ? 'allFilters' : undefined} > + {editFilterSetId && ( + setEditFilterSetId(null)} + filterSetId={editFilterSetId} + /> + )} {getFilterControls()} theme.gridUnit}px; + background: ${({ theme }) => theme.colors.primary.light4}; + padding: ${({ theme }) => theme.gridUnit * 2}px; +`; + +const Title = styled(Typography.Text)` + color: ${({ theme }) => theme.colors.primary.dark2}; +`; + +const Warning = styled(Typography.Text)` + font-size: ${({ theme }) => theme.typography.sizes.s}px; + & .anticon { + padding: ${({ theme }) => theme.gridUnit}px; + } +`; + +const ActionButton = styled.div<{ disabled?: boolean }>` + display: flex; + & button { + ${({ disabled }) => `pointer-events: ${disabled ? 'none' : 'all'}`}; + flex: 1; + } +`; + +type EditSectionProps = { + filterSetId: string; + dataMaskSelected: DataMaskUnit; + onCancel: HandlerFunction; + disabled: boolean; +}; + +const EditSection: FC = ({ + filterSetId, + onCancel, + dataMaskSelected, + disabled, +}) => { + const dataMaskApplied = useDataMask(); + const dispatch = useDispatch(); + const filterSets = useFilterSets(); + const filterSetFilterValues = Object.values(filterSets); + const handleSave = () => { + dispatch( + setFilterSetsConfiguration( + filterSetFilterValues.map(filterSet => { + const newFilterSet = { + ...filterSet, + dataMask: { nativeFilters: { ...dataMaskApplied } }, + }; + return filterSetId === filterSet.id ? newFilterSet : filterSet; + }), + ), + ); + onCancel(); + }; + + const foundFilterSet = useMemo( + () => + findExistingFilterSet({ + dataMaskApplied, + dataMaskSelected, + filterSetFilterValues, + }), + [dataMaskApplied, dataMaskSelected, filterSetFilterValues], + ); + + const isDuplicateFilterSet = + foundFilterSet && foundFilterSet.id !== filterSetId; + + return ( + + {t('Editing filter set:')} + {filterSets[filterSetId].name} + + + + + + + + + {isDuplicateFilterSet && ( + + + {t('This filter set is identical to: "%s"', foundFilterSet?.name)} + + )} + + ); +}; + +export default EditSection; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx index ee9ea254f4..6a83a8b1c6 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx @@ -19,7 +19,7 @@ import { Typography, Dropdown, Menu } from 'src/common/components'; import React, { FC } from 'react'; import { FilterSet } from 'src/dashboard/reducers/types'; -import { DataMaskUnitWithId } from 'src/dataMask/types'; +import { DataMaskUnit } from 'src/dataMask/types'; import { CheckOutlined, EllipsisOutlined } from '@ant-design/icons'; import { HandlerFunction, styled, supersetTheme, t } from '@superset-ui/core'; import FiltersHeader from './FiltersHeader'; @@ -46,9 +46,10 @@ type FilterSetUnitProps = { isApplied?: boolean; filterSet?: FilterSet; filterSetName?: string; - dataMaskApplied: DataMaskUnitWithId; + dataMaskApplied?: DataMaskUnit; setFilterSetName?: (name: string) => void; onDelete?: HandlerFunction; + onEdit?: HandlerFunction; }; const FilterSetUnit: FC = ({ @@ -56,6 +57,7 @@ const FilterSetUnit: FC = ({ editMode, setFilterSetName, onDelete, + onEdit, filterSetName, dataMaskApplied, filterSet, @@ -63,7 +65,10 @@ const FilterSetUnit: FC = ({ }) => { const menu = ( - {t('Delete')} + {t('Edit')} + + {t('Delete')} + ); return ( diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSets.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSets.tsx index 608c2e7094..47c99fd922 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSets.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSets.tsx @@ -22,9 +22,8 @@ import { HandlerFunction, styled, t } from '@superset-ui/core'; import { useDispatch } from 'react-redux'; import { DataMaskState, DataMaskUnit, MaskWithId } from 'src/dataMask/types'; import { setFilterSetsConfiguration } from 'src/dashboard/actions/nativeFilters'; -import { areObjectsEqual } from 'src/reduxUtils'; import { FilterSet } from 'src/dashboard/reducers/types'; -import { generateFiltersSetId } from './utils'; +import { findExistingFilterSet, generateFiltersSetId } from './utils'; import { Filter } from '../../types'; import { useFilters, useDataMask, useFilterSets } from '../state'; import Footer from './Footer'; @@ -68,6 +67,7 @@ const FilterSetUnitWrapper = styled.div<{ type FilterSetsProps = { disabled: boolean; dataMaskSelected: DataMaskUnit; + onEditFilterSet: (id: string) => void; onFilterSelectionChange: ( filter: Pick & Partial, dataMask: Partial, @@ -78,6 +78,7 @@ const DEFAULT_FILTER_SET_NAME = t('New filter set'); const FilterSets: React.FC = ({ dataMaskSelected, + onEditFilterSet, disabled, onFilterSelectionChange, }) => { @@ -93,34 +94,25 @@ const FilterSets: React.FC = ({ >(null); useEffect(() => { - const foundFilterSet = filterSetFilterValues.find(({ dataMask }) => { - if (dataMask?.nativeFilters) { - return Object.values(dataMask?.nativeFilters).every( - filterFromFilterSet => { - let currentValueFromFiltersTab = - dataMaskApplied[filterFromFilterSet.id]?.currentState ?? {}; - if (dataMaskSelected[filterFromFilterSet.id]) { - currentValueFromFiltersTab = - dataMaskSelected[filterFromFilterSet.id]?.currentState; - } - return areObjectsEqual( - filterFromFilterSet.currentState ?? {}, - currentValueFromFiltersTab, - ); - }, - ); - } - return false; + const foundFilterSet = findExistingFilterSet({ + dataMaskApplied, + dataMaskSelected, + filterSetFilterValues, }); setSelectedFiltersSetId(foundFilterSet?.id ?? null); }, [dataMaskApplied, dataMaskSelected, filterSetFilterValues]); - const takeFilterSet = (target: HTMLElement, id: string) => { + const takeFilterSet = (id: string, target?: HTMLElement) => { const ignoreSelector = 'ant-collapse-header'; if ( - target.classList.contains(ignoreSelector) || - target.parentElement?.classList.contains(ignoreSelector) || - target.parentElement?.parentElement?.classList.contains(ignoreSelector) + target?.classList.contains(ignoreSelector) || + target?.parentElement?.classList.contains(ignoreSelector) || + target?.parentElement?.parentElement?.classList.contains( + ignoreSelector, + ) || + target?.parentElement?.parentElement?.parentElement?.classList.contains( + ignoreSelector, + ) ) { // We don't want select filter set when user expand filters return; @@ -141,6 +133,11 @@ const FilterSets: React.FC = ({ ); }; + const handleEdit = (id: string) => { + takeFilterSet(id); + onEditFilterSet(id); + }; + const handleDeleteFilterSets = () => { dispatch( setFilterSetsConfiguration( @@ -198,14 +195,14 @@ const FilterSets: React.FC = ({ ) => - takeFilterSet(e.target as HTMLElement, filterSet.id) + takeFilterSet(filterSet.id, e.target as HTMLElement) } > handleEdit(filterSet.id)} filters={filters} - dataMaskApplied={dataMaskApplied} filterSet={filterSet} /> diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx index 8982a212b7..eb43eb930c 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx @@ -19,7 +19,7 @@ import React, { FC } from 'react'; import { styled, t } from '@superset-ui/core'; import { Collapse, Typography } from 'src/common/components'; -import { DataMaskUnitWithId } from 'src/dataMask/types'; +import { DataMaskUnit } from 'src/dataMask/types'; import { CaretDownOutlined } from '@ant-design/icons'; import { getFilterValueForDisplay } from './utils'; import { Filter } from '../../types'; @@ -54,7 +54,7 @@ const StyledCollapse = styled(Collapse)` type FiltersHeaderProps = { filters: Filter[]; - dataMask: DataMaskUnitWithId; + dataMask?: DataMaskUnit; expanded: boolean; }; @@ -84,7 +84,9 @@ const FiltersHeader: FC = ({
{name}:  - {getFilterValueForDisplay(dataMask[id]?.currentState?.value) || ( + {getFilterValueForDisplay( + dataMask?.[id]?.currentState?.value, + ) || ( {t('None')} )} diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx index 5e53ed8622..60fbef4107 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx @@ -20,6 +20,7 @@ import { t, styled } from '@superset-ui/core'; import React, { FC } from 'react'; import Button from 'src/components/Button'; import { Tooltip } from 'src/common/components/Tooltip'; +import { APPLY_FILTERS_HINT } from './utils'; type FooterProps = { isApplyDisabled: boolean; @@ -32,14 +33,13 @@ type FooterProps = { const ActionButton = styled.div<{ disabled: boolean }>` display: flex; - padding: 1px; & button { ${({ disabled }) => `pointer-events: ${disabled ? 'none' : 'all'}`}; flex: 1; } `; -const ActionButtons = styled.div` +export const ActionButtons = styled.div` display: grid; flex-direction: row; justify-content: center; @@ -48,8 +48,6 @@ const ActionButtons = styled.div` grid-template-columns: 1fr 1fr; `; -const APPLY_FILTERS = t('Please apply filter changes'); - const Footer: FC = ({ onCancel, editMode, @@ -73,7 +71,7 @@ const Footer: FC = ({ placement="bottom" title={ (isApplyDisabled && t('Please filter set name')) || - (disabled && APPLY_FILTERS) + (disabled && APPLY_FILTERS_HINT) } > @@ -91,7 +89,7 @@ const Footer: FC = ({ ) : ( - +