Skip to content

Commit

Permalink
feat(filter-set): Filter set history (#13529)
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

* fix: fix CR notes

* refactor: filter sets
  • Loading branch information
simcha90 committed Mar 10, 2021
1 parent 226dd4b commit 1d1a1cd
Show file tree
Hide file tree
Showing 8 changed files with 287 additions and 128 deletions.
10 changes: 5 additions & 5 deletions superset-frontend/src/dashboard/actions/nativeFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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...
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -175,79 +173,77 @@ 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,
);
const [visiblePopoverId, setVisiblePopoverId] = useState<string | null>(null);
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) {
Expand All @@ -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 = () => (
Expand All @@ -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 })}>
Expand All @@ -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>
Expand Down Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Loading

0 comments on commit 1d1a1cd

Please sign in to comment.