You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by vi...@apache.org on 2021/03/10 09:11:47 UTC
[superset] branch master updated: feat(filter-set): Filter set
history (#13529)
This is an automated email from the ASF dual-hosted git repository.
villebro pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 1d1a1cd feat(filter-set): Filter set history (#13529)
1d1a1cd is described below
commit 1d1a1cdc208a7df6e45bffaac3e99675a61e0a20
Author: simcha90 <56...@users.noreply.github.com>
AuthorDate: Wed Mar 10 11:10:47 2021 +0200
feat(filter-set): Filter set history (#13529)
* refactor(native-filters): move data mask to root reducer
* refactor: update rest stuff for dataMask
* refactor: add ownCrrentState to explore
* fix: fix immer reducer
* fix: merge with master
* refactor: support explore dataMask
* refactor: support explore dataMask
* docs: add comment
* refactor: remove json stringify
* fix: fix failed cases
* feat: filter bat buttons start
* fix: fix CR notes
* fix: fix cascade filters
* fix: fix CR notes
* refactor: add clear all
* fix: fix CR notes
* fix: fix CR notes
* fix: fix CR notes
* feat: buttons in filter bar
* lint: update imports
* feat: add tabs for filter sets
* feat: add buttons to filter set
* feat: first phase add filter sets
* fix: undo FF
* refactor: continue filter sets
* fix: fix CR notes
* refactor: header
* fix: fix CR notes
* fix: fix CR notes
* refactor: continue filter sets
* lint: fix lint
* refactor: continue filter sets
* fix: fix filter bar opening
* refactor: continue filter sets
* refactor: continue filter sets
* refactor: continue filter sets
* feat: filters sets history
* feat: filters sets history
* fix: filter set name
* refactor: fix expand filters case
* fix: fix CR notes
* refactor: filter sets
* fix: fix CR notes
* refactor: filter sets
---
.../src/dashboard/actions/nativeFilters.ts | 10 +-
.../nativeFilters/FilterBar/FilterBar.tsx | 76 ++++-----
.../FilterBar/FilterSets/FilterSetUnit.tsx | 113 +++++++++++++
.../FilterBar/FilterSets/FilterSets.tsx | 188 ++++++++++++---------
.../FilterBar/FilterSets/FiltersHeader.tsx | 14 +-
.../nativeFilters/FilterBar/FilterSets/Footer.tsx | 6 +-
.../src/dashboard/reducers/nativeFilters.ts | 4 +-
superset-frontend/src/dashboard/reducers/types.ts | 4 +-
8 files changed, 287 insertions(+), 128 deletions(-)
diff --git a/superset-frontend/src/dashboard/actions/nativeFilters.ts b/superset-frontend/src/dashboard/actions/nativeFilters.ts
index a1571fe..8a18d52 100644
--- a/superset-frontend/src/dashboard/actions/nativeFilters.ts
+++ b/superset-frontend/src/dashboard/actions/nativeFilters.ts
@@ -26,7 +26,7 @@ import {
SET_DATA_MASK_FOR_FILTER_CONFIG_FAIL,
} from 'src/dataMask/actions';
import { dashboardInfoChanged } from './dashboardInfo';
-import { FiltersSet } from '../reducers/types';
+import { FilterSet } from '../reducers/types';
export const SET_FILTER_CONFIG_BEGIN = 'SET_FILTER_CONFIG_BEGIN';
export interface SetFilterConfigBegin {
@@ -46,18 +46,18 @@ export interface SetFilterConfigFail {
export const SET_FILTER_SETS_CONFIG_BEGIN = 'SET_FILTER_SETS_CONFIG_BEGIN';
export interface SetFilterSetsConfigBegin {
type: typeof SET_FILTER_SETS_CONFIG_BEGIN;
- filterSetsConfig: FiltersSet[];
+ filterSetsConfig: FilterSet[];
}
export const SET_FILTER_SETS_CONFIG_COMPLETE =
'SET_FILTER_SETS_CONFIG_COMPLETE';
export interface SetFilterSetsConfigComplete {
type: typeof SET_FILTER_SETS_CONFIG_COMPLETE;
- filterSetsConfig: FiltersSet[];
+ filterSetsConfig: FilterSet[];
}
export const SET_FILTER_SETS_CONFIG_FAIL = 'SET_FILTER_SETS_CONFIG_FAIL';
export interface SetFilterSetsConfigFail {
type: typeof SET_FILTER_SETS_CONFIG_FAIL;
- filterSetsConfig: FiltersSet[];
+ filterSetsConfig: FilterSet[];
}
interface DashboardInfo {
@@ -110,7 +110,7 @@ export const setFilterConfiguration = (
};
export const setFilterSetsConfiguration = (
- filterSetsConfig: FiltersSet[],
+ filterSetsConfig: FilterSet[],
) => async (dispatch: Dispatch, getState: () => any) => {
dispatch({
type: SET_FILTER_SETS_CONFIG_BEGIN,
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx
index f2964a1..b5f05e4 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterBar.tsx
@@ -27,11 +27,7 @@ import Icon from 'src/components/Icon';
import { Tabs } from 'src/common/components';
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
import { updateDataMask } from 'src/dataMask/actions';
-import {
- DataMaskUnitWithId,
- DataMaskUnit,
- DataMaskState,
-} from 'src/dataMask/types';
+import { DataMaskUnit, DataMaskState } from 'src/dataMask/types';
import { useImmer } from 'use-immer';
import { getInitialMask } from 'src/dataMask/reducer';
import { areObjectsEqual } from 'src/reduxUtils';
@@ -40,13 +36,15 @@ import { Filter } from '../types';
import { buildCascadeFiltersTree, mapParentFiltersToChildren } from './utils';
import CascadePopover from './CascadePopover';
import FilterSets from './FilterSets/FilterSets';
-import { useFilters, useFilterSets } from './state';
+import { useDataMask, useFilters, useFilterSets } from './state';
const barWidth = `250px`;
const BarWrapper = styled.div`
width: ${({ theme }) => theme.gridUnit * 8}px;
-
+ & .ant-tabs-top > .ant-tabs-nav {
+ margin: 0;
+ }
&.open {
width: ${barWidth}; // arbitrary...
}
@@ -158,7 +156,7 @@ const ActionButtons = styled.div`
`;
const FilterControls = styled.div`
- padding: 0 ${({ theme }) => theme.gridUnit * 4}px;
+ padding: ${({ theme }) => theme.gridUnit * 4}px;
&:hover {
cursor: pointer;
}
@@ -175,19 +173,17 @@ const FilterBar: React.FC<FiltersBarProps> = ({
toggleFiltersBar,
directPathToChild,
}) => {
- const [filterData, setFilterData] = useImmer<DataMaskUnit>({});
+ const [dataMaskSelected, setDataMaskSelected] = useImmer<DataMaskUnit>({});
const [
lastAppliedFilterData,
setLastAppliedFilterData,
] = useImmer<DataMaskUnit>({});
const dispatch = useDispatch();
const filterSets = useFilterSets();
- const filterSetsArray = Object.values(filterSets);
+ const filterSetFilterValues = Object.values(filterSets);
const filters = useFilters();
- const filtersArray = Object.values(filters);
- const dataMaskState = useSelector<any, DataMaskUnitWithId>(
- state => state.dataMask.nativeFilters ?? {},
- );
+ const filterValues = Object.values(filters);
+ const dataMaskApplied = useDataMask();
const canEdit = useSelector<any, boolean>(
({ dashboardInfo }) => dashboardInfo.dash_edit_perm,
);
@@ -195,59 +191,59 @@ const FilterBar: React.FC<FiltersBarProps> = ({
const [isInitialized, setIsInitialized] = useState<boolean>(false);
const handleApply = () => {
- const filterIds = Object.keys(filterData);
+ const filterIds = Object.keys(dataMaskSelected);
filterIds.forEach(filterId => {
- if (filterData[filterId]) {
+ if (dataMaskSelected[filterId]) {
dispatch(
updateDataMask(filterId, {
- nativeFilters: filterData[filterId],
+ nativeFilters: dataMaskSelected[filterId],
}),
);
}
});
- setLastAppliedFilterData(() => filterData);
+ setLastAppliedFilterData(() => dataMaskSelected);
};
useEffect(() => {
if (isInitialized) {
return;
}
- const areFiltersInitialized = filtersArray.every(filterConfig =>
+ const areFiltersInitialized = filterValues.every(filterValue =>
areObjectsEqual(
- filterConfig.defaultValue,
- filterData[filterConfig.id]?.currentState?.value,
+ filterValue.defaultValue,
+ dataMaskSelected[filterValue.id]?.currentState?.value,
),
);
if (areFiltersInitialized) {
handleApply();
setIsInitialized(true);
}
- }, [filtersArray, filterData, isInitialized]);
+ }, [filterValues, dataMaskSelected, isInitialized]);
useEffect(() => {
- if (filtersArray.length === 0 && filtersOpen) {
+ if (filterValues.length === 0 && filtersOpen) {
toggleFiltersBar(false);
}
- }, [filtersArray.length]);
+ }, [filterValues.length]);
const cascadeChildren = useMemo(
- () => mapParentFiltersToChildren(filtersArray),
- [filtersArray],
+ () => mapParentFiltersToChildren(filterValues),
+ [filterValues],
);
const cascadeFilters = useMemo(() => {
- const filtersWithValue = filtersArray.map(filter => ({
+ const filtersWithValue = filterValues.map(filter => ({
...filter,
- currentValue: filterData[filter.id]?.currentState?.value,
+ currentValue: dataMaskSelected[filter.id]?.currentState?.value,
}));
return buildCascadeFiltersTree(filtersWithValue);
- }, [filtersArray, filterData]);
+ }, [filterValues, dataMaskSelected]);
const handleFilterSelectionChange = (
filter: Pick<Filter, 'id'> & Partial<Filter>,
dataMask: Partial<DataMaskState>,
) => {
- setFilterData(draft => {
+ setDataMaskSelected(draft => {
const children = cascadeChildren[filter.id] || [];
// force instant updating on initialization or for parent filters
if (filter.isInstant || children.length > 0) {
@@ -261,17 +257,17 @@ const FilterBar: React.FC<FiltersBarProps> = ({
};
const handleClearAll = () => {
- filtersArray.forEach(filter => {
- setFilterData(draft => {
+ filterValues.forEach(filter => {
+ setDataMaskSelected(draft => {
draft[filter.id] = getInitialMask(filter.id);
});
});
};
- const isClearAllDisabled = Object.values(dataMaskState).every(
+ const isClearAllDisabled = Object.values(dataMaskApplied).every(
filter =>
- filterData[filter.id]?.currentState?.value === null ||
- (!filterData[filter.id] && filter.currentState?.value === null),
+ dataMaskSelected[filter.id]?.currentState?.value === null ||
+ (!dataMaskSelected[filter.id] && filter.currentState?.value === null),
);
const getFilterControls = () => (
@@ -293,7 +289,7 @@ const FilterBar: React.FC<FiltersBarProps> = ({
);
const isApplyDisabled =
- !isInitialized || areObjectsEqual(filterData, lastAppliedFilterData);
+ !isInitialized || areObjectsEqual(dataMaskSelected, lastAppliedFilterData);
return (
<BarWrapper data-test="filter-bar" className={cx({ open: filtersOpen })}>
@@ -309,7 +305,7 @@ const FilterBar: React.FC<FiltersBarProps> = ({
<span>{t('Filters')}</span>
{canEdit && (
<FilterConfigurationLink
- createNewOnOpen={filtersArray.length === 0}
+ createNewOnOpen={filterValues.length === 0}
>
<Icon name="edit" data-test="create-filter" />
</FilterConfigurationLink>
@@ -344,18 +340,18 @@ const FilterBar: React.FC<FiltersBarProps> = ({
onChange={() => {}}
>
<Tabs.TabPane
- tab={t(`All Filters (${filtersArray.length})`)}
+ tab={t(`All Filters (${filterValues.length})`)}
key="allFilters"
>
{getFilterControls()}
</Tabs.TabPane>
<Tabs.TabPane
- tab={t(`Filter Sets (${filterSetsArray.length})`)}
+ tab={t(`Filter Sets (${filterSetFilterValues.length})`)}
key="filterSets"
>
<FilterSets
disabled={!isApplyDisabled}
- dataMaskState={dataMaskState}
+ dataMaskSelected={dataMaskSelected}
onFilterSelectionChange={handleFilterSelectionChange}
/>
</Tabs.TabPane>
diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx
new file mode 100644
index 0000000..ee9ea25
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSetUnit.tsx
@@ -0,0 +1,113 @@
+/**
+ * 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 { 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 { CheckOutlined, EllipsisOutlined } from '@ant-design/icons';
+import { HandlerFunction, styled, supersetTheme, t } from '@superset-ui/core';
+import FiltersHeader from './FiltersHeader';
+import { Filter } from '../../types';
+
+const TitleText = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+`;
+
+const IconsBlock = styled.div`
+ display: flex;
+ justify-content: flex-end;
+ align-items: flex-start;
+ & > * {
+ ${({ theme }) => `padding-left: ${theme.gridUnit * 2}px`};
+ }
+`;
+
+type FilterSetUnitProps = {
+ filters: Filter[];
+ editMode?: boolean;
+ isApplied?: boolean;
+ filterSet?: FilterSet;
+ filterSetName?: string;
+ dataMaskApplied: DataMaskUnitWithId;
+ setFilterSetName?: (name: string) => void;
+ onDelete?: HandlerFunction;
+};
+
+const FilterSetUnit: FC<FilterSetUnitProps> = ({
+ filters,
+ editMode,
+ setFilterSetName,
+ onDelete,
+ filterSetName,
+ dataMaskApplied,
+ filterSet,
+ isApplied,
+}) => {
+ const menu = (
+ <Menu>
+ <Menu.Item onClick={onDelete}>{t('Delete')}</Menu.Item>
+ </Menu>
+ );
+ return (
+ <>
+ <TitleText>
+ <Typography.Text
+ strong
+ editable={{
+ editing: editMode,
+ icon: <span />,
+ onChange: setFilterSetName,
+ }}
+ >
+ {filterSet?.name ?? filterSetName}
+ </Typography.Text>
+ <IconsBlock>
+ {isApplied && (
+ <CheckOutlined
+ style={{ color: supersetTheme.colors.success.base }}
+ />
+ )}
+ {onDelete && (
+ <Dropdown
+ overlay={menu}
+ placement="bottomRight"
+ trigger={['click']}
+ >
+ <EllipsisOutlined
+ onClick={e => {
+ e.stopPropagation();
+ e.preventDefault();
+ }}
+ />
+ </Dropdown>
+ )}
+ </IconsBlock>
+ </TitleText>
+ <FiltersHeader
+ expanded={!filterSet}
+ dataMask={filterSet?.dataMask?.nativeFilters ?? dataMaskApplied}
+ filters={filters}
+ />
+ </>
+ );
+};
+
+export default FilterSetUnit;
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 8252677..608c2e7 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSets.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FilterSets.tsx
@@ -16,32 +16,25 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Select, Typography } from 'src/common/components';
-import Button from 'src/components/Button';
-import React, { useState } from 'react';
-import { styled, t, tn } from '@superset-ui/core';
+
+import React, { useEffect, useState, MouseEvent } from 'react';
+import { HandlerFunction, styled, t } from '@superset-ui/core';
import { useDispatch } from 'react-redux';
-import {
- DataMaskState,
- DataMaskUnitWithId,
- MaskWithId,
-} from 'src/dataMask/types';
+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 { Filter } from '../../types';
import { useFilters, useDataMask, useFilterSets } from '../state';
import Footer from './Footer';
-import FiltersHeader from './FiltersHeader';
+import FilterSetUnit from './FilterSetUnit';
-const FilterSet = styled.div`
+const FilterSetsWrapper = styled.div`
display: grid;
align-items: center;
justify-content: center;
grid-template-columns: 1fr;
- grid-gap: ${({ theme }) => theme.gridUnit}px;
- ${({ theme }) =>
- `padding: 0 ${theme.gridUnit * 4}px ${theme.gridUnit * 4}px`};
- border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
& button.superset-button {
margin-left: 0;
}
@@ -54,9 +47,27 @@ const FilterSet = styled.div`
}
`;
+const FilterSetUnitWrapper = styled.div<{
+ onClick?: HandlerFunction;
+ selected?: boolean;
+}>`
+ display: grid;
+ align-items: center;
+ justify-content: center;
+ grid-template-columns: 1fr;
+ grid-gap: ${({ theme }) => theme.gridUnit}px;
+ ${({ theme }) =>
+ `padding: 0 ${theme.gridUnit * 4}px ${theme.gridUnit * 4}px`};
+ border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2};
+ padding: ${({ theme }) => `${theme.gridUnit * 3}px ${theme.gridUnit * 2}px`};
+ cursor: ${({ onClick }) => (!onClick ? 'auto' : 'pointer')};
+ ${({ theme, selected }) =>
+ `background: ${selected ? theme.colors.primary.light5 : 'transparent'}`};
+`;
+
type FilterSetsProps = {
disabled: boolean;
- dataMaskState: DataMaskUnitWithId;
+ dataMaskSelected: DataMaskUnit;
onFilterSelectionChange: (
filter: Pick<Filter, 'id'> & Partial<Filter>,
dataMask: Partial<DataMaskState>,
@@ -66,27 +77,59 @@ type FilterSetsProps = {
const DEFAULT_FILTER_SET_NAME = t('New filter set');
const FilterSets: React.FC<FilterSetsProps> = ({
+ dataMaskSelected,
disabled,
onFilterSelectionChange,
- dataMaskState,
}) => {
const dispatch = useDispatch();
const [filterSetName, setFilterSetName] = useState(DEFAULT_FILTER_SET_NAME);
const [editMode, setEditMode] = useState(false);
+ const dataMaskApplied = useDataMask();
const filterSets = useFilterSets();
- const filterSetsArray = Object.values(filterSets);
- const dataMask = useDataMask();
+ const filterSetFilterValues = Object.values(filterSets);
const filters = Object.values(useFilters());
const [selectedFiltersSetId, setSelectedFiltersSetId] = useState<
string | null
>(null);
- const takeFilterSet = (value: string) => {
- setSelectedFiltersSetId(value);
- if (!value) {
+ 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;
+ });
+ setSelectedFiltersSetId(foundFilterSet?.id ?? null);
+ }, [dataMaskApplied, dataMaskSelected, filterSetFilterValues]);
+
+ const takeFilterSet = (target: HTMLElement, id: string) => {
+ const ignoreSelector = 'ant-collapse-header';
+ if (
+ target.classList.contains(ignoreSelector) ||
+ target.parentElement?.classList.contains(ignoreSelector) ||
+ target.parentElement?.parentElement?.classList.contains(ignoreSelector)
+ ) {
+ // We don't want select filter set when user expand filters
return;
}
- const filtersSet = filterSets[value];
+ setSelectedFiltersSetId(id);
+ if (!id) {
+ return;
+ }
+ const filtersSet = filterSets[id];
Object.values(filtersSet.dataMask?.nativeFilters ?? []).forEach(
dataMask => {
const { extraFormData, currentState, id } = dataMask as MaskWithId;
@@ -101,7 +144,7 @@ const FilterSets: React.FC<FilterSetsProps> = ({
const handleDeleteFilterSets = () => {
dispatch(
setFilterSetsConfiguration(
- filterSetsArray.filter(
+ filterSetFilterValues.filter(
filtersSet => filtersSet.id !== selectedFiltersSetId,
),
),
@@ -116,65 +159,58 @@ const FilterSets: React.FC<FilterSetsProps> = ({
};
const handleCreateFilterSet = () => {
+ const newFilterSet: FilterSet = {
+ name: filterSetName.trim(),
+ id: generateFiltersSetId(),
+ dataMask: {
+ nativeFilters: dataMaskApplied,
+ },
+ };
dispatch(
- setFilterSetsConfiguration(
- filterSetsArray.concat([
- {
- name: filterSetName.trim(),
- id: generateFiltersSetId(),
- dataMask: {
- nativeFilters: dataMaskState,
- },
- },
- ]),
- ),
+ setFilterSetsConfiguration([newFilterSet].concat(filterSetFilterValues)),
);
setEditMode(false);
setFilterSetName(DEFAULT_FILTER_SET_NAME);
};
return (
- <FilterSet>
- <Typography.Text
- strong
- editable={{
- editing: editMode,
- icon: <span />,
- onChange: setFilterSetName,
- }}
- >
- {filterSetName}
- </Typography.Text>
- <FiltersHeader dataMask={dataMask} filters={filters} />
- <Footer
- isApplyDisabled={!filterSetName.trim()}
- disabled={disabled}
- onCancel={handleCancel}
- editMode={editMode}
- onEdit={() => setEditMode(true)}
- onCreate={handleCreateFilterSet}
- />
- <Select
- size="small"
- allowClear
- value={selectedFiltersSetId as string}
- placeholder={tn('Available %d sets', filterSetsArray.length)}
- onChange={takeFilterSet}
- >
- {filterSetsArray.map(({ name, id }) => (
- <Select.Option value={id}>{name}</Select.Option>
- ))}
- </Select>
- <Button
- buttonStyle="warning"
- buttonSize="small"
- disabled={!selectedFiltersSetId}
- onClick={handleDeleteFilterSets}
- data-test="filter-save-filters-set-button"
- >
- {t('Delete Filters Set')}
- </Button>
- </FilterSet>
+ <FilterSetsWrapper>
+ {!selectedFiltersSetId && (
+ <FilterSetUnitWrapper>
+ <FilterSetUnit
+ filters={filters}
+ editMode={editMode}
+ setFilterSetName={setFilterSetName}
+ filterSetName={filterSetName}
+ dataMaskApplied={dataMaskApplied}
+ />
+ <Footer
+ isApplyDisabled={!filterSetName.trim()}
+ disabled={disabled}
+ onCancel={handleCancel}
+ editMode={editMode}
+ onEdit={() => setEditMode(true)}
+ onCreate={handleCreateFilterSet}
+ />
+ </FilterSetUnitWrapper>
+ )}
+ {filterSetFilterValues.map(filterSet => (
+ <FilterSetUnitWrapper
+ selected={filterSet.id === selectedFiltersSetId}
+ onClick={(e: MouseEvent<HTMLElement>) =>
+ takeFilterSet(e.target as HTMLElement, filterSet.id)
+ }
+ >
+ <FilterSetUnit
+ isApplied={filterSet.id === selectedFiltersSetId && !disabled}
+ onDelete={handleDeleteFilterSets}
+ filters={filters}
+ dataMaskApplied={dataMaskApplied}
+ filterSet={filterSet}
+ />
+ </FilterSetUnitWrapper>
+ ))}
+ </FilterSetsWrapper>
);
};
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 d8faa4b..8982a21 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/FiltersHeader.tsx
@@ -20,6 +20,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 { CaretDownOutlined } from '@ant-design/icons';
import { getFilterValueForDisplay } from './utils';
import { Filter } from '../../types';
@@ -43,6 +44,7 @@ const StyledCollapse = styled(Collapse)`
align-items: center;
flex-direction: row-reverse;
justify-content: flex-end;
+ max-width: max-content;
& .ant-collapse-arrow {
position: static;
padding-left: ${({ theme }) => theme.gridUnit}px;
@@ -53,9 +55,14 @@ const StyledCollapse = styled(Collapse)`
type FiltersHeaderProps = {
filters: Filter[];
dataMask: DataMaskUnitWithId;
+ expanded: boolean;
};
-const FiltersHeader: FC<FiltersHeaderProps> = ({ filters, dataMask }) => {
+const FiltersHeader: FC<FiltersHeaderProps> = ({
+ filters,
+ dataMask,
+ expanded,
+}) => {
const getFiltersHeader = () => (
<FilterHeader>
<Typography.Text type="secondary">
@@ -67,7 +74,10 @@ const FiltersHeader: FC<FiltersHeaderProps> = ({ filters, dataMask }) => {
<StyledCollapse
ghost
expandIconPosition="right"
- defaultActiveKey={['filters']}
+ defaultActiveKey={expanded ? ['filters'] : undefined}
+ expandIcon={({ isActive }: { isActive: boolean }) => (
+ <CaretDownOutlined rotate={isActive ? 0 : 180} />
+ )}
>
<Collapse.Panel header={getFiltersHeader()} key="filters">
{filters.map(({ id, name }) => (
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 a19be00..5e53ed8 100644
--- a/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx
+++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/Footer.tsx
@@ -38,6 +38,7 @@ const ActionButton = styled.div<{ disabled: boolean }>`
flex: 1;
}
`;
+
const ActionButtons = styled.div`
display: grid;
flex-direction: row;
@@ -70,7 +71,10 @@ const Footer: FC<FooterProps> = ({
</Button>
<Tooltip
placement="bottom"
- title={(isApplyDisabled || disabled) && APPLY_FILTERS}
+ title={
+ (isApplyDisabled && t('Please filter set name')) ||
+ (disabled && APPLY_FILTERS)
+ }
>
<ActionButton disabled={disabled}>
<Button
diff --git a/superset-frontend/src/dashboard/reducers/nativeFilters.ts b/superset-frontend/src/dashboard/reducers/nativeFilters.ts
index 92d0708..d860cbe 100644
--- a/superset-frontend/src/dashboard/reducers/nativeFilters.ts
+++ b/superset-frontend/src/dashboard/reducers/nativeFilters.ts
@@ -22,7 +22,7 @@ import {
SET_FILTER_CONFIG_COMPLETE,
SET_FILTER_SETS_CONFIG_COMPLETE,
} from 'src/dashboard/actions/nativeFilters';
-import { FiltersSet, NativeFiltersState } from './types';
+import { FilterSet, NativeFiltersState } from './types';
import { FilterConfiguration } from '../components/nativeFilters/types';
export function getInitialState({
@@ -30,7 +30,7 @@ export function getInitialState({
filterConfig,
state: prevState,
}: {
- filterSetsConfig?: FiltersSet[];
+ filterSetsConfig?: FilterSet[];
filterConfig?: FilterConfiguration;
state?: NativeFiltersState;
}): NativeFiltersState {
diff --git a/superset-frontend/src/dashboard/reducers/types.ts b/superset-frontend/src/dashboard/reducers/types.ts
index 78b3ea7..a3cd633 100644
--- a/superset-frontend/src/dashboard/reducers/types.ts
+++ b/superset-frontend/src/dashboard/reducers/types.ts
@@ -67,14 +67,14 @@ export type LayoutItem = {
};
};
-export type FiltersSet = {
+export type FilterSet = {
id: string;
name: string;
dataMask: Partial<DataMaskStateWithId>;
};
export type FilterSets = {
- [filtersSetId: string]: FiltersSet;
+ [filtersSetId: string]: FilterSet;
};
export type Filters = {