diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx index ac5c7068c2726..92ea57ba5243a 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx @@ -18,20 +18,17 @@ */ /* eslint-disable no-param-reassign */ -import { styled, t, tn } from '@superset-ui/core'; -import React, { useState, useEffect, useMemo, ChangeEvent } from 'react'; +import { styled, t } from '@superset-ui/core'; +import React, { useState, useEffect, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import cx from 'classnames'; import Button from 'src/components/Button'; import Icon from 'src/components/Icon'; -import { FiltersSet } from 'src/dashboard/reducers/types'; -import { Input, Select } from 'src/common/components'; +import { Tabs } from 'src/common/components'; import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; -import { setFilterSetsConfiguration } from 'src/dashboard/actions/nativeFilters'; import { updateDataMask } from 'src/dataMask/actions'; import { DataMaskUnitWithId, - MaskWithId, DataMaskUnit, DataMaskState, } from 'src/dataMask/types'; @@ -39,15 +36,11 @@ import { useImmer } from 'use-immer'; import { getInitialMask } from 'src/dataMask/reducer'; import { areObjectsEqual } from 'src/reduxUtils'; import FilterConfigurationLink from './FilterConfigurationLink'; -import { useFilterSets } from './state'; import { useFilterConfiguration } from '../state'; import { Filter } from '../types'; -import { - buildCascadeFiltersTree, - generateFiltersSetId, - mapParentFiltersToChildren, -} from './utils'; +import { buildCascadeFiltersTree, mapParentFiltersToChildren } from './utils'; import CascadePopover from './CascadePopover'; +import FilterSets from './FilterSets'; const barWidth = `250px`; @@ -86,18 +79,6 @@ const Bar = styled.div` } `; -const StyledTitle = styled.h4` - width: 100%; - font-size: ${({ theme }) => theme.typography.sizes.s}px; - color: ${({ theme }) => theme.colors.grayscale.dark1}; - margin: 0; - overflow-wrap: break-word; - - & > .ant-select { - width: 100%; - } -`; - const CollapsedBar = styled.div` position: absolute; top: 0; @@ -137,15 +118,6 @@ const StyledCollapseIcon = styled(Icon)` margin-bottom: ${({ theme }) => theme.gridUnit * 3}px; `; -const FilterSet = styled.div` - display: grid; - align-items: center; - justify-content: center; - grid-template-columns: 1fr; - grid-gap: 10px; - padding-top: 10px; -`; - const TitleArea = styled.h4` display: flex; flex-direction: row; @@ -166,25 +138,32 @@ const TitleArea = styled.h4` } `; +const StyledTabs = styled(Tabs)` + & .ant-tabs-nav-list { + width: 100%; + } + & .ant-tabs-tab { + display: flex; + justify-content: center; + margin: 0; + flex: 1; + } +`; + const ActionButtons = styled.div` display: grid; flex-direction: row; grid-template-columns: 1fr 1fr; ${({ theme }) => `padding: 0 ${theme.gridUnit * 2}px ${theme.gridUnit * 2}px`}; - border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; .btn { flex: 1; } `; -const Sets = styled(ActionButtons)` - grid-template-columns: 1fr; -`; - const FilterControls = styled.div` - padding: ${({ theme }) => theme.gridUnit * 4}px; + padding: 0 ${({ theme }) => theme.gridUnit * 4}px; `; interface FiltersBarProps { @@ -207,15 +186,7 @@ const FilterBar: React.FC = ({ const dataMaskState = useSelector( state => state.dataMask.nativeFilters ?? {}, ); - const filterSets = useFilterSets(); const filterConfigs = useFilterConfiguration(); - const filterSetsConfigs = useSelector( - state => state.dashboardInfo?.metadata?.filter_sets_configuration || [], - ); - const [filtersSetName, setFiltersSetName] = useState(''); - const [selectedFiltersSetId, setSelectedFiltersSetId] = useState< - string | null - >(null); const canEdit = useSelector( ({ dashboardInfo }) => dashboardInfo.dash_edit_perm, ); @@ -272,23 +243,6 @@ const FilterBar: React.FC = ({ }); }; - const takeFiltersSet = (value: string) => { - setSelectedFiltersSetId(value); - if (!value) { - return; - } - const filtersSet = filterSets[value]; - Object.values(filtersSet.dataMask?.nativeFilters ?? []).forEach( - dataMask => { - const { extraFormData, currentState, id } = dataMask as MaskWithId; - handleFilterSelectionChange( - { id }, - { nativeFilters: { extraFormData, currentState } }, - ); - }, - ); - }; - const handleApply = () => { const filterIds = Object.keys(filterData); filterIds.forEach(filterId => { @@ -309,35 +263,6 @@ const FilterBar: React.FC = ({ } }, [isInitialized]); - const handleSaveFilterSets = () => { - dispatch( - setFilterSetsConfiguration( - filterSetsConfigs.concat([ - { - name: filtersSetName.trim(), - id: generateFiltersSetId(), - dataMask: { - nativeFilters: dataMaskState, - }, - }, - ]), - ), - ); - setFiltersSetName(''); - }; - - const handleDeleteFilterSets = () => { - dispatch( - setFilterSetsConfiguration( - filterSetsConfigs.filter( - filtersSet => filtersSet.id !== selectedFiltersSetId, - ), - ), - ); - setFiltersSetName(''); - setSelectedFiltersSetId(null); - }; - const handleClearAll = () => { filterConfigs.forEach(filter => { setFilterData(draft => { @@ -352,6 +277,24 @@ const FilterBar: React.FC = ({ (!filterData[filter.id] && filter.currentState?.value === null), ); + const getFilterControls = () => ( + + {cascadeFilters.map(filter => ( + + setVisiblePopoverId(visible ? filter.id : null) + } + filter={filter} + onFilterSelectionChange={handleFilterSelectionChange} + directPathToChild={directPathToChild} + /> + ))} + + ); + return ( = ({ - - {t('Filters')} ({filterConfigs.length}) - + {t('Filters')} {canEdit && ( = ({ {t('Apply')} - {isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET) && ( - - - -
{t('Choose filters set')}
- -
- - -
{t('Name')}
- ) => { - setFiltersSetName(value); - }} - /> -
- -
-
+ {isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET) ? ( + {}} + > + + {getFilterControls()} + + + + + + ) : ( + getFilterControls() )} - - {cascadeFilters.map(filter => ( - - setVisiblePopoverId(visible ? filter.id : null) - } - filter={filter} - onFilterSelectionChange={handleFilterSelectionChange} - directPathToChild={directPathToChild} - /> - ))} -
); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets.tsx new file mode 100644 index 0000000000000..0d3cbfaac661b --- /dev/null +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets.tsx @@ -0,0 +1,198 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { Input, Select } from 'src/common/components'; +import Button from 'src/components/Button'; +import React, { ChangeEvent, useState } from 'react'; +import { styled, t, tn } from '@superset-ui/core'; +import { useDispatch, useSelector } from 'react-redux'; +import { + DataMaskState, + DataMaskUnitWithId, + MaskWithId, +} from 'src/dataMask/types'; +import { setFilterSetsConfiguration } from 'src/dashboard/actions/nativeFilters'; +import { FiltersSet, FilterSets } from 'src/dashboard/reducers/types'; +import { generateFiltersSetId } from './utils'; +import { Filter } from '../types'; + +const FilterSet = styled.div` + display: grid; + align-items: center; + justify-content: center; + grid-template-columns: 1fr; + grid-gap: 10px; + padding-top: 10px; +`; + +const StyledTitle = styled.h4` + width: 100%; + font-size: ${({ theme }) => theme.typography.sizes.s}px; + color: ${({ theme }) => theme.colors.grayscale.dark1}; + margin: 0; + overflow-wrap: break-word; + + & > .ant-select { + width: 100%; + } +`; + +const ActionButtons = styled.div` + display: grid; + flex-direction: row; + grid-template-columns: 1fr 1fr; + ${({ theme }) => + `padding: 0 ${theme.gridUnit * 2}px ${theme.gridUnit * 2}px`}; + border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; + + .btn { + flex: 1; + } +`; + +const Sets = styled(ActionButtons)` + grid-template-columns: 1fr; +`; + +type FilterSetsProps = { + dataMaskState: DataMaskUnitWithId; + onFilterSelectionChange: ( + filter: Pick & Partial, + dataMask: Partial, + ) => void; +}; + +const FilterSets: React.FC = ({ + onFilterSelectionChange, + dataMaskState, +}) => { + const dispatch = useDispatch(); + const [filtersSetName, setFiltersSetName] = useState(''); + const filterSets = useSelector( + state => state.nativeFilters.filterSets ?? {}, + ); + const filterSetsConfigs = useSelector( + state => state.dashboardInfo?.metadata?.filter_sets_configuration || [], + ); + const [selectedFiltersSetId, setSelectedFiltersSetId] = useState< + string | null + >(null); + + const takeFiltersSet = (value: string) => { + setSelectedFiltersSetId(value); + if (!value) { + return; + } + const filtersSet = filterSets[value]; + Object.values(filtersSet.dataMask?.nativeFilters ?? []).forEach( + dataMask => { + const { extraFormData, currentState, id } = dataMask as MaskWithId; + onFilterSelectionChange( + { id }, + { nativeFilters: { extraFormData, currentState } }, + ); + }, + ); + }; + + const handleSaveFilterSets = () => { + dispatch( + setFilterSetsConfiguration( + filterSetsConfigs.concat([ + { + name: filtersSetName.trim(), + id: generateFiltersSetId(), + dataMask: { + nativeFilters: dataMaskState, + }, + }, + ]), + ), + ); + setFiltersSetName(''); + }; + + const handleDeleteFilterSets = () => { + dispatch( + setFilterSetsConfiguration( + filterSetsConfigs.filter( + filtersSet => filtersSet.id !== selectedFiltersSetId, + ), + ), + ); + setFiltersSetName(''); + setSelectedFiltersSetId(null); + }; + + return ( + + + +
{t('Choose filters set')}
+ +
+ + +
{t('Name')}
+ ) => { + setFiltersSetName(value); + }} + /> +
+ +
+
+ ); +}; + +export default FilterSets; diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/state.ts b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/state.ts index cb5fe248b4c2e..afa64826d4303 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/state.ts +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/state.ts @@ -17,7 +17,7 @@ * under the License. */ import { useSelector } from 'react-redux'; -import { NativeFiltersState, FilterSets } from 'src/dashboard/reducers/types'; +import { NativeFiltersState } from 'src/dashboard/reducers/types'; import { DataMaskStateWithId } from 'src/dataMask/types'; import { mergeExtraFormData } from '../utils'; import { Filter } from '../types'; @@ -26,12 +26,6 @@ export function useFilters() { return useSelector(state => state.nativeFilters.filters); } -export function useFilterSets() { - return useSelector( - state => state.nativeFilters.filterSets ?? {}, - ); -} - export function useCascadingFilters(id: string) { const { filters } = useSelector( state => state.nativeFilters,