diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 236ce9698bb69..1cfbff4f4e4d0 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -122,6 +122,7 @@ "rison": "^0.1.1", "shortid": "^2.2.6", "urijs": "^1.19.4", + "use-immer": "^0.4.2", "use-query-params": "^1.1.9" }, "devDependencies": { @@ -54551,6 +54552,15 @@ "ts-essentials": "^2.0.3" } }, + "node_modules/use-immer": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/use-immer/-/use-immer-0.4.2.tgz", + "integrity": "sha512-ONfZHEv/gzt/jyYxrJD3ZFUllKJED8F1mds9Fr9CYj54LmsiuGDg3vkx+R7WHS72p7DbSS1QbM7xNYrVWX+KcA==", + "peerDependencies": { + "immer": ">=2.0.0", + "react": "^16.8.0 || ^17.0.1" + } + }, "node_modules/use-isomorphic-layout-effect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.0.0.tgz", @@ -104943,6 +104953,12 @@ "ts-essentials": "^2.0.3" } }, + "use-immer": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/use-immer/-/use-immer-0.4.2.tgz", + "integrity": "sha512-ONfZHEv/gzt/jyYxrJD3ZFUllKJED8F1mds9Fr9CYj54LmsiuGDg3vkx+R7WHS72p7DbSS1QbM7xNYrVWX+KcA==", + "requires": {} + }, "use-isomorphic-layout-effect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.0.0.tgz", diff --git a/superset-frontend/package.json b/superset-frontend/package.json index 33d0b16641b22..aaf0c048c2114 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -174,6 +174,7 @@ "rison": "^0.1.1", "shortid": "^2.2.6", "urijs": "^1.19.4", + "use-immer": "^0.4.2", "use-query-params": "^1.1.9" }, "devDependencies": { diff --git a/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/FilterBar_spec.tsx b/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/FilterBar_spec.tsx index 97959f3719cc0..ccd29e198a225 100644 --- a/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/FilterBar_spec.tsx +++ b/superset-frontend/spec/javascripts/dashboard/components/nativeFilters/FilterBar_spec.tsx @@ -42,9 +42,9 @@ describe('FilterBar', () => { expect(wrapper.find({ name: 'filter' })).toExist(); expect(wrapper.find({ name: 'collapse' })).toExist(); }); - it('has apply and reset all buttons', () => { + it('has apply and clear all buttons', () => { expect(wrapper.find(Button).length).toBe(2); - expect(wrapper.find(Button).at(0)).toHaveProp('buttonStyle', 'secondary'); + expect(wrapper.find(Button).at(0)).toHaveProp('buttonStyle', 'tertiary'); expect(wrapper.find(Button).at(1)).toHaveProp('buttonStyle', 'primary'); }); }); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx index c6dacec883490..ac5c7068c2726 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx @@ -16,6 +16,8 @@ * specific language governing permissions and limitations * under the License. */ + +/* eslint-disable no-param-reassign */ import { styled, t, tn } from '@superset-ui/core'; import React, { useState, useEffect, useMemo, ChangeEvent } from 'react'; import { useDispatch, useSelector } from 'react-redux'; @@ -33,8 +35,11 @@ import { DataMaskUnit, DataMaskState, } from 'src/dataMask/types'; +import { useImmer } from 'use-immer'; +import { getInitialMask } from 'src/dataMask/reducer'; +import { areObjectsEqual } from 'src/reduxUtils'; import FilterConfigurationLink from './FilterConfigurationLink'; -import { useFilters, useFilterSets } from './state'; +import { useFilterSets } from './state'; import { useFilterConfiguration } from '../state'; import { Filter } from '../types'; import { @@ -48,6 +53,7 @@ const barWidth = `250px`; const BarWrapper = styled.div` width: ${({ theme }) => theme.gridUnit * 8}px; + &.open { width: ${barWidth}; // arbitrary... } @@ -70,6 +76,7 @@ const Bar = styled.div` transition: transform ${({ theme }) => theme.transitionTiming}s; transition-delay: 0s; } */ + &.open { display: flex; /* &.animated { @@ -85,6 +92,7 @@ const StyledTitle = styled.h4` color: ${({ theme }) => theme.colors.grayscale.dark1}; margin: 0; overflow-wrap: break-word; + & > .ant-select { width: 100%; } @@ -105,6 +113,7 @@ const CollapsedBar = styled.div` transition: transform ${({ theme }) => theme.transitionTiming}s; transition-delay: 0s; } */ + &.open { display: flex; flex-direction: column; @@ -115,6 +124,7 @@ const CollapsedBar = styled.div` transition-delay: ${({ theme }) => theme.transitionTiming * 3}s; } */ } + svg { width: ${({ theme }) => theme.gridUnit * 4}px; height: ${({ theme }) => theme.gridUnit * 4}px; @@ -141,12 +151,15 @@ const TitleArea = styled.h4` flex-direction: row; justify-content: space-between; margin: 0; - padding: ${({ theme }) => theme.gridUnit * 4}px; + padding: ${({ theme }) => theme.gridUnit * 2}px; + & > span { flex-grow: 1; } + & :not(:first-child) { margin-left: ${({ theme }) => theme.gridUnit}px; + &:hover { cursor: pointer; } @@ -154,17 +167,22 @@ const TitleArea = styled.h4` `; const ActionButtons = styled.div` - display: flex; + display: grid; flex-direction: row; - justify-content: space-around; - padding: ${({ theme }) => theme.gridUnit * 4}px; - padding-top: 0; + 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 1 50%; + flex: 1; } `; +const Sets = styled(ActionButtons)` + grid-template-columns: 1fr; +`; + const FilterControls = styled.div` padding: ${({ theme }) => theme.gridUnit * 4}px; `; @@ -180,7 +198,11 @@ const FilterBar: React.FC = ({ toggleFiltersBar, directPathToChild, }) => { - const [filterData, setFilterData] = useState({}); + const [filterData, setFilterData] = useImmer({}); + const [ + lastAppliedFilterData, + setLastAppliedFilterData, + ] = useImmer({}); const dispatch = useDispatch(); const dataMaskState = useSelector( state => state.dataMask.nativeFilters ?? {}, @@ -190,7 +212,6 @@ const FilterBar: React.FC = ({ const filterSetsConfigs = useSelector( state => state.dashboardInfo?.metadata?.filter_sets_configuration || [], ); - const filters = useFilters(); const [filtersSetName, setFiltersSetName] = useState(''); const [selectedFiltersSetId, setSelectedFiltersSetId] = useState< string | null @@ -238,20 +259,16 @@ const FilterBar: React.FC = ({ filter: Pick & Partial, dataMask: Partial, ) => { - setFilterData(prevFilterData => { + setFilterData(draft => { const children = cascadeChildren[filter.id] || []; // force instant updating on initialization or for parent filters if (filter.isInstant || children.length > 0) { dispatch(updateDataMask(filter.id, dataMask)); } - if (!dataMask.nativeFilters) { - return { ...prevFilterData }; + if (dataMask.nativeFilters) { + draft[filter.id] = dataMask.nativeFilters; } - return { - ...prevFilterData, - [filter.id]: dataMask.nativeFilters, - }; }); }; @@ -283,6 +300,7 @@ const FilterBar: React.FC = ({ ); } }); + setLastAppliedFilterData(() => filterData); }; useEffect(() => { @@ -320,21 +338,20 @@ const FilterBar: React.FC = ({ setSelectedFiltersSetId(null); }; - const handleResetAll = () => { + const handleClearAll = () => { filterConfigs.forEach(filter => { - dispatch( - updateDataMask(filter.id, { - nativeFilters: { - currentState: { - ...filterData[filter.id]?.currentState, - value: filters[filter.id]?.defaultValue, - }, - }, - }), - ); + setFilterData(draft => { + draft[filter.id] = getInitialMask(filter.id); + }); }); }; + const isClearAllDisabled = !Object.values(dataMaskState).every( + filter => + filterData[filter.id]?.currentState?.value === null || + (!filterData[filter.id] && filter.currentState?.value === null), + ); + return ( = ({ {isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET) && ( - +
{t('Choose filters set')}
@@ -429,7 +451,7 @@ const FilterBar: React.FC = ({ {t('Save Filters Set')}
-
+ )} {cascadeFilters.map(filter => (