Skip to content

Commit

Permalink
feat(filter-set): Update existing filter set (#13545)
Browse files Browse the repository at this point in the history
* 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

Co-authored-by: Ville Brofeldt <33317356+villebro@users.noreply.github.com>
  • Loading branch information
simcha90 and villebro authored Mar 10, 2021
1 parent 1d1a1cd commit d509b15
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 41 deletions.
1 change: 1 addition & 0 deletions superset-frontend/src/components/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface ButtonProps {
id?: string;
className?: string;
tooltip?: string;
ghost?: boolean;
placement?:
| 'bottom'
| 'left'
Expand Down
5 changes: 3 additions & 2 deletions superset-frontend/src/dashboard/actions/nativeFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`;

Expand Down Expand Up @@ -173,6 +174,7 @@ const FilterBar: React.FC<FiltersBarProps> = ({
toggleFiltersBar,
directPathToChild,
}) => {
const [editFilterSetId, setEditFilterSetId] = useState<string | null>(null);
const [dataMaskSelected, setDataMaskSelected] = useImmer<DataMaskUnit>({});
const [
lastAppliedFilterData,
Expand Down Expand Up @@ -337,19 +339,29 @@ const FilterBar: React.FC<FiltersBarProps> = ({
<StyledTabs
centered
defaultActiveKey="allFilters"
onChange={() => {}}
activeKey={editFilterSetId ? 'allFilters' : undefined}
>
<Tabs.TabPane
tab={t(`All Filters (${filterValues.length})`)}
key="allFilters"
>
{editFilterSetId && (
<EditSection
dataMaskSelected={dataMaskSelected}
disabled={!isApplyDisabled}
onCancel={() => setEditFilterSetId(null)}
filterSetId={editFilterSetId}
/>
)}
{getFilterControls()}
</Tabs.TabPane>
<Tabs.TabPane
disabled={!!editFilterSetId}
tab={t(`Filter Sets (${filterSetFilterValues.length})`)}
key="filterSets"
>
<FilterSets
onEditFilterSet={setEditFilterSetId}
disabled={!isApplyDisabled}
dataMaskSelected={dataMaskSelected}
onFilterSelectionChange={handleFilterSelectionChange}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/**
* 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 React, { FC, useMemo } from 'react';
import { HandlerFunction, styled, t } from '@superset-ui/core';
import { Typography, Tooltip } from 'src/common/components';
import { useDispatch } from 'react-redux';
import Button from 'src/components/Button';
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 { APPLY_FILTERS_HINT, findExistingFilterSet } from './utils';

const Wrapper = styled.div`
display: grid;
grid-template-columns: 1fr;
align-items: flex-start;
justify-content: flex-start;
grid-gap: ${({ theme }) => 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<EditSectionProps> = ({
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 (
<Wrapper>
<Title strong>{t('Editing filter set:')}</Title>
<Title>{filterSets[filterSetId].name}</Title>
<ActionButtons>
<Button
ghost
buttonStyle="tertiary"
buttonSize="small"
onClick={onCancel}
data-test="filter-set-edit-cancel"
>
{t('Cancel')}
</Button>
<Tooltip
placement="top"
title={
(isDuplicateFilterSet && t('Filter set already exists')) ||
(disabled && APPLY_FILTERS_HINT)
}
>
<ActionButton disabled={disabled || isDuplicateFilterSet}>
<Button
disabled={disabled || isDuplicateFilterSet}
buttonStyle="primary"
htmlType="submit"
buttonSize="small"
onClick={handleSave}
data-test="filter-set-edit-save"
>
{t('Save')}
</Button>
</ActionButton>
</Tooltip>
</ActionButtons>
{isDuplicateFilterSet && (
<Warning mark>
<WarningOutlined />
{t('This filter set is identical to: "%s"', foundFilterSet?.name)}
</Warning>
)}
</Wrapper>
);
};

export default EditSection;
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -46,24 +46,29 @@ type FilterSetUnitProps = {
isApplied?: boolean;
filterSet?: FilterSet;
filterSetName?: string;
dataMaskApplied: DataMaskUnitWithId;
dataMaskApplied?: DataMaskUnit;
setFilterSetName?: (name: string) => void;
onDelete?: HandlerFunction;
onEdit?: HandlerFunction;
};

const FilterSetUnit: FC<FilterSetUnitProps> = ({
filters,
editMode,
setFilterSetName,
onDelete,
onEdit,
filterSetName,
dataMaskApplied,
filterSet,
isApplied,
}) => {
const menu = (
<Menu>
<Menu.Item onClick={onDelete}>{t('Delete')}</Menu.Item>
<Menu.Item onClick={onEdit}>{t('Edit')}</Menu.Item>
<Menu.Item onClick={onDelete} danger>
{t('Delete')}
</Menu.Item>
</Menu>
);
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -68,6 +67,7 @@ const FilterSetUnitWrapper = styled.div<{
type FilterSetsProps = {
disabled: boolean;
dataMaskSelected: DataMaskUnit;
onEditFilterSet: (id: string) => void;
onFilterSelectionChange: (
filter: Pick<Filter, 'id'> & Partial<Filter>,
dataMask: Partial<DataMaskState>,
Expand All @@ -78,6 +78,7 @@ const DEFAULT_FILTER_SET_NAME = t('New filter set');

const FilterSets: React.FC<FilterSetsProps> = ({
dataMaskSelected,
onEditFilterSet,
disabled,
onFilterSelectionChange,
}) => {
Expand All @@ -93,34 +94,25 @@ const FilterSets: React.FC<FilterSetsProps> = ({
>(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;
Expand All @@ -141,6 +133,11 @@ const FilterSets: React.FC<FilterSetsProps> = ({
);
};

const handleEdit = (id: string) => {
takeFilterSet(id);
onEditFilterSet(id);
};

const handleDeleteFilterSets = () => {
dispatch(
setFilterSetsConfiguration(
Expand Down Expand Up @@ -198,14 +195,14 @@ const FilterSets: React.FC<FilterSetsProps> = ({
<FilterSetUnitWrapper
selected={filterSet.id === selectedFiltersSetId}
onClick={(e: MouseEvent<HTMLElement>) =>
takeFilterSet(e.target as HTMLElement, filterSet.id)
takeFilterSet(filterSet.id, e.target as HTMLElement)
}
>
<FilterSetUnit
isApplied={filterSet.id === selectedFiltersSetId && !disabled}
onDelete={handleDeleteFilterSets}
onEdit={() => handleEdit(filterSet.id)}
filters={filters}
dataMaskApplied={dataMaskApplied}
filterSet={filterSet}
/>
</FilterSetUnitWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -54,7 +54,7 @@ const StyledCollapse = styled(Collapse)`

type FiltersHeaderProps = {
filters: Filter[];
dataMask: DataMaskUnitWithId;
dataMask?: DataMaskUnit;
expanded: boolean;
};

Expand Down Expand Up @@ -84,7 +84,9 @@ const FiltersHeader: FC<FiltersHeaderProps> = ({
<div>
<Typography.Text strong>{name}:&nbsp;</Typography.Text>
<Typography.Text>
{getFilterValueForDisplay(dataMask[id]?.currentState?.value) || (
{getFilterValueForDisplay(
dataMask?.[id]?.currentState?.value,
) || (
<Typography.Text type="secondary">{t('None')}</Typography.Text>
)}
</Typography.Text>
Expand Down
Loading

0 comments on commit d509b15

Please sign in to comment.