feat(filter-set): Filter set edge cases (#13576)

* 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

* refactor: filter sets

* refactor: filter sets

* refactor: filter sets

* refactor: update sets

* feat: edit filter set

* refactor: add warning icon

* fix: fix CR notes

* Update superset-frontend/src/dashboard/components/nativeFilters/FilterBar/FilterSets/EditSection.tsx

Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com>

* fix: fix CR notes

* feat: filter set edge cases

* lint: fix lint

* lint: fix TS

* refactor: fix CR notes

* fix: fix CR notes

* fix: fix CR notes

Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com>
This commit is contained in:
simcha90 2021-03-15 14:29:22 +02:00 committed by GitHub
parent ae66f5fa78
commit a35825de7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 317 additions and 149 deletions

View File

@ -18,7 +18,7 @@
*/
/* eslint-disable no-param-reassign */
import { styled, t } from '@superset-ui/core';
import { HandlerFunction, styled, t } from '@superset-ui/core';
import React, { useState, useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import cx from 'classnames';
@ -52,6 +52,11 @@ const BarWrapper = styled.div`
`;
const Bar = styled.div`
& .ant-typography-edit-content {
left: 0;
margin-top: 0;
width: 100%;
}
position: absolute;
top: 0;
left: 0;
@ -169,6 +174,11 @@ interface FiltersBarProps {
directPathToChild?: string[];
}
enum TabIds {
AllFilters = 'allFilters',
FilterSets = 'filterSets',
}
const FilterBar: React.FC<FiltersBarProps> = ({
filtersOpen,
toggleFiltersBar,
@ -183,8 +193,10 @@ const FilterBar: React.FC<FiltersBarProps> = ({
const dispatch = useDispatch();
const filterSets = useFilterSets();
const filterSetFilterValues = Object.values(filterSets);
const [isFilterSetChanged, setIsFilterSetChanged] = useState(false);
const [tab, setTab] = useState(TabIds.AllFilters);
const filters = useFilters();
const filterValues = Object.values(filters);
const filterValues = Object.values<Filter>(filters);
const dataMaskApplied = useDataMask();
const canEdit = useSelector<any, boolean>(
({ dashboardInfo }) => dashboardInfo.dash_edit_perm,
@ -212,8 +224,8 @@ const FilterBar: React.FC<FiltersBarProps> = ({
}
const areFiltersInitialized = filterValues.every(filterValue =>
areObjectsEqual(
filterValue.defaultValue,
dataMaskSelected[filterValue.id]?.currentState?.value,
filterValue?.defaultValue,
dataMaskSelected[filterValue?.id]?.currentState?.value,
),
);
if (areFiltersInitialized) {
@ -245,6 +257,7 @@ const FilterBar: React.FC<FiltersBarProps> = ({
filter: Pick<Filter, 'id'> & Partial<Filter>,
dataMask: Partial<DataMaskState>,
) => {
setIsFilterSetChanged(tab !== TabIds.AllFilters);
setDataMaskSelected(draft => {
const children = cascadeChildren[filter.id] || [];
// force instant updating on initialization or for parent filters
@ -338,12 +351,13 @@ const FilterBar: React.FC<FiltersBarProps> = ({
{isFeatureEnabled(FeatureFlag.DASHBOARD_NATIVE_FILTERS_SET) ? (
<StyledTabs
centered
defaultActiveKey="allFilters"
activeKey={editFilterSetId ? 'allFilters' : undefined}
onChange={setTab as HandlerFunction}
defaultActiveKey={TabIds.AllFilters}
activeKey={editFilterSetId ? TabIds.AllFilters : undefined}
>
<Tabs.TabPane
tab={t(`All Filters (${filterValues.length})`)}
key="allFilters"
key={TabIds.AllFilters}
>
{editFilterSetId && (
<EditSection
@ -358,12 +372,13 @@ const FilterBar: React.FC<FiltersBarProps> = ({
<Tabs.TabPane
disabled={!!editFilterSetId}
tab={t(`Filter Sets (${filterSetFilterValues.length})`)}
key="filterSets"
key={TabIds.FilterSets}
>
<FilterSets
onEditFilterSet={setEditFilterSetId}
disabled={!isApplyDisabled}
dataMaskSelected={dataMaskSelected}
isFilterSetChanged={isFilterSetChanged}
onFilterSelectionChange={handleFilterSelectionChange}
/>
</Tabs.TabPane>

View File

@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { FC, useMemo } from 'react';
import React, { FC, useMemo, useState } from 'react';
import { HandlerFunction, styled, t } from '@superset-ui/core';
import { Typography, Tooltip } from 'src/common/components';
import { useDispatch } from 'react-redux';
@ -25,8 +25,9 @@ import { setFilterSetsConfiguration } from 'src/dashboard/actions/nativeFilters'
import { DataMaskUnit } from 'src/dataMask/types';
import { WarningOutlined } from '@ant-design/icons';
import { ActionButtons } from './Footer';
import { useDataMask, useFilterSets } from '../state';
import { useDataMask, useFilters, useFilterSets } from '../state';
import { APPLY_FILTERS_HINT, findExistingFilterSet } from './utils';
import { useFilterSetNameDuplicated } from './state';
const Wrapper = styled.div`
display: grid;
@ -73,13 +74,26 @@ const EditSection: FC<EditSectionProps> = ({
const dataMaskApplied = useDataMask();
const dispatch = useDispatch();
const filterSets = useFilterSets();
const filters = useFilters();
const filterSetFilterValues = Object.values(filterSets);
const [filterSetName, setFilterSetName] = useState(
filterSets[filterSetId].name,
);
const isFilterSetNameDuplicated = useFilterSetNameDuplicated(
filterSetName,
filterSets[filterSetId].name,
);
const handleSave = () => {
dispatch(
setFilterSetsConfiguration(
filterSetFilterValues.map(filterSet => {
const newFilterSet = {
...filterSet,
name: filterSetName,
nativeFilters: filters,
dataMask: { nativeFilters: { ...dataMaskApplied } },
};
return filterSetId === filterSet.id ? newFilterSet : filterSet;
@ -92,20 +106,30 @@ const EditSection: FC<EditSectionProps> = ({
const foundFilterSet = useMemo(
() =>
findExistingFilterSet({
dataMaskApplied,
dataMaskSelected,
filterSetFilterValues,
}),
[dataMaskApplied, dataMaskSelected, filterSetFilterValues],
[dataMaskSelected, filterSetFilterValues],
);
const isDuplicateFilterSet =
foundFilterSet && foundFilterSet.id !== filterSetId;
const resultDisabled =
disabled || isDuplicateFilterSet || isFilterSetNameDuplicated;
return (
<Wrapper>
<Title strong>{t('Editing filter set:')}</Title>
<Title>{filterSets[filterSetId].name}</Title>
<Title
editable={{
editing: true,
icon: <span />,
onChange: setFilterSetName,
}}
>
{filterSetName}
</Title>
<ActionButtons>
<Button
ghost
@ -117,15 +141,17 @@ const EditSection: FC<EditSectionProps> = ({
{t('Cancel')}
</Button>
<Tooltip
placement="top"
placement="right"
title={
(isFilterSetNameDuplicated &&
t('Filter set with this name already exists')) ||
(isDuplicateFilterSet && t('Filter set already exists')) ||
(disabled && APPLY_FILTERS_HINT)
}
>
<ActionButton disabled={disabled || isDuplicateFilterSet}>
<ActionButton disabled={resultDisabled}>
<Button
disabled={disabled || isDuplicateFilterSet}
disabled={resultDisabled}
buttonStyle="primary"
htmlType="submit"
buttonSize="small"

View File

@ -22,8 +22,8 @@ import { FilterSet } from 'src/dashboard/reducers/types';
import { DataMaskUnit } from 'src/dataMask/types';
import { CheckOutlined, EllipsisOutlined } from '@ant-design/icons';
import { HandlerFunction, styled, supersetTheme, t } from '@superset-ui/core';
import { Tooltip } from 'src/common/components/Tooltip';
import FiltersHeader from './FiltersHeader';
import { Filter } from '../../types';
const TitleText = styled.div`
display: flex;
@ -41,36 +41,42 @@ const IconsBlock = styled.div`
`;
type FilterSetUnitProps = {
filters: Filter[];
editMode?: boolean;
isApplied?: boolean;
filterSet?: FilterSet;
filterSetName?: string;
dataMaskApplied?: DataMaskUnit;
dataMaskSelected?: DataMaskUnit;
setFilterSetName?: (name: string) => void;
onDelete?: HandlerFunction;
onEdit?: HandlerFunction;
onRebuild?: HandlerFunction;
};
const FilterSetUnit: FC<FilterSetUnitProps> = ({
filters,
editMode,
setFilterSetName,
onDelete,
onEdit,
filterSetName,
dataMaskApplied,
dataMaskSelected,
filterSet,
isApplied,
onRebuild,
}) => {
const menu = (
<Menu>
<Menu.Item onClick={onEdit}>{t('Edit')}</Menu.Item>
<Menu.Item onClick={onRebuild}>
<Tooltip placement="right" title={t('Remove invalid filters')}>
{t('Rebuild')}
</Tooltip>
</Menu.Item>
<Menu.Item onClick={onDelete} danger>
{t('Delete')}
</Menu.Item>
</Menu>
);
return (
<>
<TitleText>
@ -107,9 +113,8 @@ const FilterSetUnit: FC<FilterSetUnitProps> = ({
</IconsBlock>
</TitleText>
<FiltersHeader
expanded={!filterSet}
dataMask={filterSet?.dataMask?.nativeFilters ?? dataMaskApplied}
filters={filters}
filterSet={filterSet}
dataMask={filterSet?.dataMask?.nativeFilters ?? dataMaskSelected}
/>
</>
);

View File

@ -22,7 +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 { FilterSet } from 'src/dashboard/reducers/types';
import { Filters, FilterSet, FilterSets } from 'src/dashboard/reducers/types';
import { areObjectsEqual } from 'src/reduxUtils';
import { findExistingFilterSet, generateFiltersSetId } from './utils';
import { Filter } from '../../types';
import { useFilters, useDataMask, useFilterSets } from '../state';
@ -40,10 +41,6 @@ const FilterSetsWrapper = styled.div`
& input {
width: 100%;
}
& .ant-typography-edit-content {
left: 0;
margin-top: 0;
}
`;
const FilterSetUnitWrapper = styled.div<{
@ -66,6 +63,7 @@ const FilterSetUnitWrapper = styled.div<{
type FilterSetsProps = {
disabled: boolean;
isFilterSetChanged: boolean;
dataMaskSelected: DataMaskUnit;
onEditFilterSet: (id: string) => void;
onFilterSelectionChange: (
@ -81,6 +79,7 @@ const FilterSets: React.FC<FilterSetsProps> = ({
onEditFilterSet,
disabled,
onFilterSelectionChange,
isFilterSetChanged,
}) => {
const dispatch = useDispatch();
const [filterSetName, setFilterSetName] = useState(DEFAULT_FILTER_SET_NAME);
@ -88,30 +87,42 @@ const FilterSets: React.FC<FilterSetsProps> = ({
const dataMaskApplied = useDataMask();
const filterSets = useFilterSets();
const filterSetFilterValues = Object.values(filterSets);
const filters = Object.values(useFilters());
const filters = useFilters();
const filterValues = Object.values(filters) as Filter[];
const [selectedFiltersSetId, setSelectedFiltersSetId] = useState<
string | null
>(null);
useEffect(() => {
if (isFilterSetChanged) {
return;
}
const foundFilterSet = findExistingFilterSet({
dataMaskApplied,
dataMaskSelected,
filterSetFilterValues,
});
setSelectedFiltersSetId(foundFilterSet?.id ?? null);
}, [dataMaskApplied, dataMaskSelected, filterSetFilterValues]);
}, [isFilterSetChanged, dataMaskSelected, filterSetFilterValues]);
const isFilterMissingOrContainsInvalidMetadata = (
id: string,
filterSet?: FilterSet,
) =>
!filterValues.find(filter => filter?.id === id) ||
!areObjectsEqual(filters[id], filterSet?.nativeFilters?.[id]);
const takeFilterSet = (id: string, target?: HTMLElement) => {
const ignoreSelector = 'ant-collapse-header';
const ignoreSelectorHeader = 'ant-collapse-header';
const ignoreSelectorDropdown = 'ant-dropdown-menu-item';
if (
target?.classList.contains(ignoreSelector) ||
target?.parentElement?.classList.contains(ignoreSelector) ||
target?.classList.contains(ignoreSelectorHeader) ||
target?.classList.contains(ignoreSelectorDropdown) ||
target?.parentElement?.classList.contains(ignoreSelectorHeader) ||
target?.parentElement?.parentElement?.classList.contains(
ignoreSelector,
ignoreSelectorHeader,
) ||
target?.parentElement?.parentElement?.parentElement?.classList.contains(
ignoreSelector,
ignoreSelectorHeader,
)
) {
// We don't want select filter set when user expand filters
@ -121,10 +132,15 @@ const FilterSets: React.FC<FilterSetsProps> = ({
if (!id) {
return;
}
const filtersSet = filterSets[id];
Object.values(filtersSet.dataMask?.nativeFilters ?? []).forEach(
const filterSet = filterSets[id];
Object.values(filterSet?.dataMask?.nativeFilters ?? []).forEach(
dataMask => {
const { extraFormData, currentState, id } = dataMask as MaskWithId;
if (isFilterMissingOrContainsInvalidMetadata(id, filterSet)) {
return;
}
onFilterSelectionChange(
{ id },
{ nativeFilters: { extraFormData, currentState } },
@ -133,21 +149,55 @@ const FilterSets: React.FC<FilterSetsProps> = ({
);
};
const handleRebuild = (id: string) => {
const filterSet = filterSets[id];
// We need remove invalid filters from filter set
const newFilters = Object.values(filterSet?.dataMask?.nativeFilters ?? [])
.filter(dataMask => {
const { id } = dataMask as MaskWithId;
return !isFilterMissingOrContainsInvalidMetadata(id, filterSet);
})
.reduce((prev, next) => ({ ...prev, [next.id]: filters[next.id] }), {});
const updatedFilterSet: FilterSet = {
...filterSet,
nativeFilters: newFilters as Filters,
dataMask: {
nativeFilters: Object.keys(newFilters).reduce(
(prev, nextFilterId) => ({
...prev,
[nextFilterId]: filterSet.dataMask?.nativeFilters?.[nextFilterId],
}),
{},
),
},
};
dispatch(
setFilterSetsConfiguration(
filterSetFilterValues.map(filterSetIt => {
const isEquals = filterSetIt.id === updatedFilterSet.id;
return isEquals ? updatedFilterSet : filterSetIt;
}),
),
);
};
const handleEdit = (id: string) => {
takeFilterSet(id);
onEditFilterSet(id);
};
const handleDeleteFilterSets = () => {
const handleDeleteFilterSet = (filterSetId: string) => {
dispatch(
setFilterSetsConfiguration(
filterSetFilterValues.filter(
filtersSet => filtersSet.id !== selectedFiltersSetId,
filtersSet => filtersSet.id !== filterSetId,
),
),
);
setFilterSetName(DEFAULT_FILTER_SET_NAME);
setSelectedFiltersSetId(null);
if (filterSetId === selectedFiltersSetId) {
setSelectedFiltersSetId(null);
}
};
const handleCancel = () => {
@ -159,8 +209,15 @@ const FilterSets: React.FC<FilterSetsProps> = ({
const newFilterSet: FilterSet = {
name: filterSetName.trim(),
id: generateFiltersSetId(),
nativeFilters: filters,
dataMask: {
nativeFilters: dataMaskApplied,
nativeFilters: Object.keys(filters).reduce(
(prev, nextFilterId) => ({
...prev,
[nextFilterId]: dataMaskApplied[nextFilterId],
}),
{},
),
},
};
dispatch(
@ -175,14 +232,13 @@ const FilterSets: React.FC<FilterSetsProps> = ({
{!selectedFiltersSetId && (
<FilterSetUnitWrapper>
<FilterSetUnit
filters={filters}
dataMaskSelected={dataMaskSelected}
editMode={editMode}
setFilterSetName={setFilterSetName}
filterSetName={filterSetName}
dataMaskApplied={dataMaskApplied}
/>
<Footer
isApplyDisabled={!filterSetName.trim()}
filterSetName={filterSetName.trim()}
disabled={disabled}
onCancel={handleCancel}
editMode={editMode}
@ -200,9 +256,10 @@ const FilterSets: React.FC<FilterSetsProps> = ({
>
<FilterSetUnit
isApplied={filterSet.id === selectedFiltersSetId && !disabled}
onDelete={handleDeleteFilterSets}
onDelete={() => handleDeleteFilterSet(filterSet.id)}
onEdit={() => handleEdit(filterSet.id)}
filters={filters}
onRebuild={() => handleRebuild(filterSet.id)}
dataMaskSelected={dataMaskSelected}
filterSet={filterSet}
/>
</FilterSetUnitWrapper>

View File

@ -18,11 +18,13 @@
*/
import React, { FC } from 'react';
import { styled, t } from '@superset-ui/core';
import { Collapse, Typography } from 'src/common/components';
import { Collapse, Typography, Tooltip } from 'src/common/components';
import { DataMaskUnit } from 'src/dataMask/types';
import { CaretDownOutlined } from '@ant-design/icons';
import { areObjectsEqual } from 'src/reduxUtils';
import { FilterSet } from 'src/dashboard/reducers/types';
import { getFilterValueForDisplay } from './utils';
import { Filter } from '../../types';
import { useFilters } from '../state';
const FilterHeader = styled.div`
display: flex;
@ -53,45 +55,70 @@ const StyledCollapse = styled(Collapse)`
`;
type FiltersHeaderProps = {
filters: Filter[];
dataMask?: DataMaskUnit;
expanded: boolean;
filterSet?: FilterSet;
};
const FiltersHeader: FC<FiltersHeaderProps> = ({
filters,
dataMask,
expanded,
}) => {
const FiltersHeader: FC<FiltersHeaderProps> = ({ dataMask, filterSet }) => {
const filters = useFilters();
const filterValues = Object.values(filters);
let resultFilters = filterValues ?? [];
if (filterSet?.nativeFilters) {
resultFilters = Object.values(filterSet?.nativeFilters);
}
const getFiltersHeader = () => (
<FilterHeader>
<Typography.Text type="secondary">
{t('Filters (%d)', filters.length)}
{t('Filters (%d)', resultFilters.length)}
</Typography.Text>
</FilterHeader>
);
const getFilterRow = ({ id, name }: { id: string; name: string }) => {
const changedFilter =
filterSet &&
!areObjectsEqual(filters[id], filterSet?.nativeFilters?.[id]);
const removedFilter = !Object.keys(filters).includes(id);
return (
<Tooltip
title={
(removedFilter &&
t(
"This filter doesn't exist in dashboard. It will not be applied.",
)) ||
(changedFilter &&
t('Filter metadata changed in dashboard. It will not be applied.'))
}
placement="bottomLeft"
>
<div>
<Typography.Text strong delete={removedFilter} mark={changedFilter}>
{name}:&nbsp;
</Typography.Text>
<Typography.Text delete={removedFilter} mark={changedFilter}>
{getFilterValueForDisplay(dataMask?.[id]?.currentState?.value) || (
<Typography.Text type="secondary">{t('None')}</Typography.Text>
)}
</Typography.Text>
</div>
</Tooltip>
);
};
return (
<StyledCollapse
ghost
expandIconPosition="right"
defaultActiveKey={expanded ? ['filters'] : undefined}
defaultActiveKey={!filterSet ? ['filters'] : undefined}
expandIcon={({ isActive }: { isActive: boolean }) => (
<CaretDownOutlined rotate={isActive ? 0 : 180} />
)}
>
<Collapse.Panel header={getFiltersHeader()} key="filters">
{filters.map(({ id, name }) => (
<div>
<Typography.Text strong>{name}:&nbsp;</Typography.Text>
<Typography.Text>
{getFilterValueForDisplay(
dataMask?.[id]?.currentState?.value,
) || (
<Typography.Text type="secondary">{t('None')}</Typography.Text>
)}
</Typography.Text>
</div>
))}
{resultFilters.map(getFilterRow)}
</Collapse.Panel>
</StyledCollapse>
);

View File

@ -21,9 +21,10 @@ import React, { FC } from 'react';
import Button from 'src/components/Button';
import { Tooltip } from 'src/common/components/Tooltip';
import { APPLY_FILTERS_HINT } from './utils';
import { useFilterSetNameDuplicated } from './state';
type FooterProps = {
isApplyDisabled: boolean;
filterSetName: string;
disabled: boolean;
editMode: boolean;
onCancel: () => void;
@ -54,56 +55,65 @@ const Footer: FC<FooterProps> = ({
onEdit,
onCreate,
disabled,
isApplyDisabled,
}) => (
<>
{editMode ? (
<ActionButtons>
<Button
buttonStyle="tertiary"
buttonSize="small"
onClick={onCancel}
data-test="filter-set-cancel-button"
>
{t('Cancel')}
</Button>
<Tooltip
placement="bottom"
title={
(isApplyDisabled && t('Please filter set name')) ||
(disabled && APPLY_FILTERS_HINT)
}
>
filterSetName,
}) => {
const isFilterSetNameDuplicated = useFilterSetNameDuplicated(filterSetName);
const isCreateDisabled =
!filterSetName || isFilterSetNameDuplicated || disabled;
return (
<>
{editMode ? (
<ActionButtons>
<Button
buttonStyle="tertiary"
buttonSize="small"
onClick={onCancel}
data-test="filter-set-cancel-button"
>
{t('Cancel')}
</Button>
<Tooltip
placement="right"
title={
(!filterSetName && t('Please filter set name')) ||
(isFilterSetNameDuplicated &&
t('Filter set with this name already exists')) ||
(disabled && APPLY_FILTERS_HINT)
}
>
<ActionButton disabled={isCreateDisabled}>
<Button
disabled={isCreateDisabled}
buttonStyle="primary"
htmlType="submit"
buttonSize="small"
onClick={onCreate}
data-test="filter-set-create-button"
>
{t('Create')}
</Button>
</ActionButton>
</Tooltip>
</ActionButtons>
) : (
<Tooltip placement="bottom" title={disabled && APPLY_FILTERS_HINT}>
<ActionButton disabled={disabled}>
<Button
disabled={isApplyDisabled || disabled}
buttonStyle="primary"
htmlType="submit"
disabled={disabled}
buttonStyle="tertiary"
buttonSize="small"
onClick={onCreate}
data-test="filter-set-create-button"
data-test="filter-set-create-new-button"
onClick={onEdit}
>
{t('Create')}
{t('Create new filter set')}
</Button>
</ActionButton>
</Tooltip>
</ActionButtons>
) : (
<Tooltip placement="bottom" title={disabled && APPLY_FILTERS_HINT}>
<ActionButton disabled={disabled}>
<Button
disabled={disabled}
buttonStyle="tertiary"
buttonSize="small"
data-test="filter-set-create-new-button"
onClick={onEdit}
>
{t('Create new filter set')}
</Button>
</ActionButton>
</Tooltip>
)}
</>
);
)}
</>
);
};
export default Footer;

View File

@ -0,0 +1,37 @@
/**
* 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 { useMemo } from 'react';
import { useFilterSets } from '../state';
// eslint-disable-next-line import/prefer-default-export
export const useFilterSetNameDuplicated = (
filterSetName: string,
ignoreName?: string,
) => {
const filterSets = useFilterSets();
const filterSetFilterValues = Object.values(filterSets);
const isFilterSetNameDuplicated = useMemo(
() => !!filterSetFilterValues.find(({ name }) => name === filterSetName),
[filterSetFilterValues, filterSetName],
);
if (ignoreName === filterSetName) {
return false;
}
return isFilterSetNameDuplicated;
};

View File

@ -47,28 +47,22 @@ export const getFilterValueForDisplay = (
export const findExistingFilterSet = ({
filterSetFilterValues,
dataMaskApplied,
dataMaskSelected,
}: {
filterSetFilterValues: FilterSet[];
dataMaskApplied: DataMaskUnit;
dataMaskSelected: DataMaskUnit;
}) =>
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,
);
},
filterSetFilterValues.find(({ dataMask: dataMaskFromFilterSet }) => {
if (dataMaskFromFilterSet?.nativeFilters) {
const dataMaskSelectedEntries = Object.entries(dataMaskSelected);
return dataMaskSelectedEntries.every(
([id, filterFromSelectedFilters]) =>
areObjectsEqual(
filterFromSelectedFilters.currentState,
dataMaskFromFilterSet?.nativeFilters?.[id]?.currentState,
) &&
dataMaskSelectedEntries.length ===
Object.keys(dataMaskFromFilterSet?.nativeFilters ?? {}).length,
);
}
return false;

View File

@ -70,7 +70,7 @@ const FilterValue: React.FC<FilterProps> = ({
groupby,
inputRef,
});
if (!areObjectsEqual(formData || {}, newFormData)) {
if (!areObjectsEqual(formData, newFormData)) {
setFormData(newFormData);
if (!hasDataSource) {
return;

View File

@ -18,12 +18,12 @@
*/
import { useSelector } from 'react-redux';
import {
Filters,
FilterSets as FilterSetsType,
NativeFiltersState,
} from 'src/dashboard/reducers/types';
import { DataMaskUnitWithId } from 'src/dataMask/types';
import { mergeExtraFormData } from '../utils';
import { Filter } from '../types';
export const useFilterSets = () =>
useSelector<any, FilterSetsType>(
@ -31,7 +31,7 @@ export const useFilterSets = () =>
);
export const useFilters = () =>
useSelector<any, Filter>(state => state.nativeFilters.filters);
useSelector<any, Filters>(state => state.nativeFilters.filters);
export const useDataMask = () =>
useSelector<any, DataMaskUnitWithId>(state => state.dataMask.nativeFilters);

View File

@ -267,7 +267,7 @@ export const FiltersConfigForm: React.FC<FiltersConfigFormProps> = ({
/>
<StyledCheckboxFormItem
name={['filters', filterId, 'isInstant']}
initialValue={filterToEdit?.isInstant}
initialValue={filterToEdit?.isInstant || false}
valuePropName="checked"
colon={false}
>

View File

@ -70,6 +70,7 @@ export type LayoutItem = {
export type FilterSet = {
id: string;
name: string;
nativeFilters: Filters;
dataMask: Partial<DataMaskStateWithId>;
};

View File

@ -16,16 +16,16 @@
* specific language governing permissions and limitations
* under the License.
*/
import { MaskWithId } from './types';
import { DataMaskType, MaskWithId } from './types';
import { FilterConfiguration } from '../dashboard/components/nativeFilters/types';
export const UPDATE_DATA_MASK = 'UPDATE_DATA_MASK';
export interface UpdateDataMask {
type: typeof UPDATE_DATA_MASK;
filterId: string;
nativeFilters?: Omit<MaskWithId, 'id'>;
crossFilters?: Omit<MaskWithId, 'id'>;
ownFilters?: Omit<MaskWithId, 'id'>;
[DataMaskType.NativeFilters]?: Omit<MaskWithId, 'id'>;
[DataMaskType.CrossFilters]?: Omit<MaskWithId, 'id'>;
[DataMaskType.OwnFilters]?: Omit<MaskWithId, 'id'>;
}
export const SET_DATA_MASK_FOR_FILTER_CONFIG_COMPLETE =

View File

@ -20,7 +20,7 @@
/* eslint-disable no-param-reassign */
// <- When we work with Immer, we need reassign, so disabling lint
import produce from 'immer';
import { MaskWithId, DataMaskType, DataMaskStateWithId } from './types';
import { MaskWithId, DataMaskType, DataMaskStateWithId, Mask } from './types';
import {
AnyDataMaskAction,
SET_DATA_MASK_FOR_FILTER_CONFIG_COMPLETE,
@ -43,8 +43,7 @@ const setUnitDataMask = (
) => {
if (action[unitName]) {
dataMaskState[unitName][action.filterId] = {
...dataMaskState[unitName][action.filterId],
...action[unitName],
...(action[unitName] as Mask),
id: action.filterId,
};
}

View File

@ -169,9 +169,6 @@ export function areArraysShallowEqual(arr1: unknown[], arr2: unknown[]) {
return true;
}
export function areObjectsEqual(
obj1: Record<string, any>,
obj2: Record<string, any>,
) {
export function areObjectsEqual(obj1: any, obj2: any) {
return isEqual(obj1, obj2);
}