Skip to content

Commit

Permalink
feat(filter-sets): Saving filter sets in metadata (#13205)
Browse files Browse the repository at this point in the history
* feat: POC adding filters set feature

* lint: fix TS

* fix: fix FF name

* refactor: fix CR notes

* fix: fix update values in filter bar

* refactor: save filter sets in meta

* feat(filter-sets): save filters sets in metadata
  • Loading branch information
simcha90 authored Feb 18, 2021
1 parent f85497e commit 2ff8741
Show file tree
Hide file tree
Showing 10 changed files with 215 additions and 55 deletions.
1 change: 1 addition & 0 deletions superset-frontend/spec/fixtures/mockNativeFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export const nativeFilters: NativeFiltersState = {
'NATIVE_FILTER-x9QPw0so1': {
id: 'NATIVE_FILTER-x9QPw0so1',
extraFormData: {},
currentState: {},
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ describe('getFormDataWithExtraFilters', () => {
[filterId]: {
id: filterId,
extraFormData: {},
currentState: {},
},
},
},
Expand Down
64 changes: 63 additions & 1 deletion superset-frontend/src/dashboard/actions/nativeFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ import {
FilterConfiguration,
} from 'src/dashboard/components/nativeFilters/types';
import { dashboardInfoChanged } from './dashboardInfo';
import { CurrentFilterState, NativeFilterState } from '../reducers/types';
import {
CurrentFilterState,
FiltersSet,
NativeFilterState,
} from '../reducers/types';
import { SelectedValues } from '../components/nativeFilters/FilterConfigModal/types';

export const SET_FILTER_CONFIG_BEGIN = 'SET_FILTER_CONFIG_BEGIN';
Expand All @@ -42,6 +46,22 @@ export interface SetFilterConfigFail {
type: typeof SET_FILTER_CONFIG_FAIL;
filterConfig: FilterConfiguration;
}
export const SET_FILTER_SETS_CONFIG_BEGIN = 'SET_FILTER_SETS_CONFIG_BEGIN';
export interface SetFilterSetsConfigBegin {
type: typeof SET_FILTER_SETS_CONFIG_BEGIN;
filterSetsConfig: FiltersSet[];
}
export const SET_FILTER_SETS_CONFIG_COMPLETE =
'SET_FILTER_SETS_CONFIG_COMPLETE';
export interface SetFilterSetsConfigComplete {
type: typeof SET_FILTER_SETS_CONFIG_COMPLETE;
filterSetsConfig: FiltersSet[];
}
export const SET_FILTER_SETS_CONFIG_FAIL = 'SET_FILTER_SETS_CONFIG_FAIL';
export interface SetFilterSetsConfigFail {
type: typeof SET_FILTER_SETS_CONFIG_FAIL;
filterSetsConfig: FiltersSet[];
}

export const SET_FILTER_STATE = 'SET_FILTER_STATE';
export interface SetFilterState {
Expand Down Expand Up @@ -95,6 +115,45 @@ export const setFilterConfiguration = (
}
};

export const setFilterSetsConfiguration = (
filterSetsConfig: FiltersSet[],
) => async (dispatch: Dispatch, getState: () => any) => {
dispatch({
type: SET_FILTER_SETS_CONFIG_BEGIN,
filterSetsConfig,
});
const { id, metadata } = getState().dashboardInfo;

// TODO extract this out when makeApi supports url parameters
const updateDashboard = makeApi<
Partial<DashboardInfo>,
{ result: DashboardInfo }
>({
method: 'PUT',
endpoint: `/api/v1/dashboard/${id}`,
});

try {
const response = await updateDashboard({
json_metadata: JSON.stringify({
...metadata,
filter_sets_configuration: filterSetsConfig,
}),
});
dispatch(
dashboardInfoChanged({
metadata: JSON.parse(response.result.json_metadata),
}),
);
dispatch({
type: SET_FILTER_SETS_CONFIG_COMPLETE,
filterSetsConfig,
});
} catch (err) {
dispatch({ type: SET_FILTER_SETS_CONFIG_FAIL, filterSetsConfig });
}
};

export const SET_EXTRA_FORM_DATA = 'SET_EXTRA_FORM_DATA';
export interface SetExtraFormData {
type: typeof SET_EXTRA_FORM_DATA;
Expand Down Expand Up @@ -174,6 +233,9 @@ export type AnyFilterAction =
| SetFilterConfigBegin
| SetFilterConfigComplete
| SetFilterConfigFail
| SetFilterSetsConfigBegin
| SetFilterSetsConfigComplete
| SetFilterSetsConfigFail
| SetFiltersState
| SetExtraFormData
| SaveFilterSets
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ import { useDispatch, useSelector } from 'react-redux';
import cx from 'classnames';
import Button from 'src/components/Button';
import Icon from 'src/components/Icon';
import { CurrentFilterState } from 'src/dashboard/reducers/types';
import {
CurrentFilterState,
FiltersSet,
NativeFilterState,
} from 'src/dashboard/reducers/types';
import { Input, Select } from 'src/common/components';
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
import {
saveFilterSets,
setFiltersState,
} from 'src/dashboard/actions/nativeFilters';
import { SelectValue } from 'antd/lib/select';
import { setFilterSetsConfiguration } from 'src/dashboard/actions/nativeFilters';
import FilterConfigurationLink from './FilterConfigurationLink';
import {
useFilters,
Expand Down Expand Up @@ -193,12 +193,33 @@ const FilterBar: React.FC<FiltersBarProps> = ({
const filtersState = useFiltersState();
const filterSets = useFilterSets();
const filterConfigs = useFilterConfiguration();
const filterSetsConfigs = useSelector<any, FiltersSet[]>(
state => state.dashboardInfo?.metadata?.filter_sets_configuration || [],
);
const filters = useFilters();
const [filtersSetName, setFiltersSetName] = useState('');
const [selectedFiltersSetId, setSelectedFiltersSetId] = useState<
string | null
>(null);
const canEdit = useSelector<any, boolean>(
({ dashboardInfo }) => dashboardInfo.dash_edit_perm,
);
const [visiblePopoverId, setVisiblePopoverId] = useState<string | null>(null);
const [isInitialized, setIsInitialized] = useState<boolean>(false);

useEffect(() => {
if (isInitialized) {
return;
}
const areFiltersInitialized = filterConfigs.every(
filterConfig =>
filterConfig.defaultValue ===
filterData[filterConfig.id]?.currentState?.value,
);
if (areFiltersInitialized) {
setIsInitialized(true);
}
}, [filterConfigs, filterData, isInitialized]);

useEffect(() => {
if (filterConfigs.length === 0 && filtersOpen) {
Expand All @@ -217,18 +238,20 @@ const FilterBar: React.FC<FiltersBarProps> = ({
currentValue: filterData[filter.id]?.currentState?.value,
}));
return buildCascadeFiltersTree(filtersWithValue);
}, [filterConfigs]);
}, [filterConfigs, filterData]);

const handleFilterSelectionChange = (
filter: Filter,
filter: Pick<Filter, 'id'> & Partial<Filter>,
extraFormData: ExtraFormData,
currentState: CurrentFilterState,
) => {
let isInitialized = false;
setFilterData(prevFilterData => {
if (filter.id in prevFilterData) {
isInitialized = true;
const children = cascadeChildren[filter.id] || [];
// force instant updating on initialization or for parent filters
if (filter.isInstant || children.length > 0) {
setExtraFormData(filter.id, extraFormData, currentState);
}

return {
...prevFilterData,
[filter.id]: {
Expand All @@ -237,12 +260,22 @@ const FilterBar: React.FC<FiltersBarProps> = ({
},
};
});
};

const children = cascadeChildren[filter.id] || [];
// force instant updating on initialization or for parent filters
if (!isInitialized || filter.isInstant || children.length > 0) {
setExtraFormData(filter.id, extraFormData, currentState);
const takeFiltersSet = (value: string) => {
setSelectedFiltersSetId(value);
if (!value) {
return;
}
const filtersSet = filterSets[value];
Object.values(filtersSet.filtersState).forEach(filterState => {
const {
extraFormData,
currentState,
id,
} = filterState as NativeFilterState;
handleFilterSelectionChange({ id }, extraFormData, currentState);
});
};

const handleApply = () => {
Expand All @@ -258,15 +291,38 @@ const FilterBar: React.FC<FiltersBarProps> = ({
});
};

useEffect(() => {
if (isInitialized) {
handleApply();
}
}, [isInitialized]);

const handleSaveFilterSets = () => {
dispatch(
saveFilterSets(
filtersSetName.trim(),
generateFiltersSetId(),
filtersState,
setFilterSetsConfiguration(
filterSetsConfigs.concat([
{
name: filtersSetName.trim(),
id: generateFiltersSetId(),
// TODO: After merge https://github.com/apache/superset/pull/13137, compare if data changed (meantime save only clicking `apply`)
filtersState,
},
]),
),
);
setFiltersSetName('');
};

const handleDeleteFilterSets = () => {
dispatch(
setFilterSetsConfiguration(
filterSetsConfigs.filter(
filtersSet => filtersSet.id !== selectedFiltersSetId,
),
),
);
setFiltersSetName('');
setSelectedFiltersSetId(null);
};

const handleResetAll = () => {
Expand All @@ -278,10 +334,6 @@ const FilterBar: React.FC<FiltersBarProps> = ({
});
};

const takeFiltersSet = (value: SelectValue) => {
dispatch(setFiltersState(filterSets[String(value)]?.filtersState));
};

return (
<BarWrapper data-test="filter-bar" className={cx({ open: filtersOpen })}>
<CollapsedBar
Expand Down Expand Up @@ -332,6 +384,7 @@ const FilterBar: React.FC<FiltersBarProps> = ({
<Select
size="small"
allowClear
value={selectedFiltersSetId as string}
placeholder={tn(
'Available %d sets',
Object.keys(filterSets).length,
Expand All @@ -343,6 +396,15 @@ const FilterBar: React.FC<FiltersBarProps> = ({
))}
</Select>
</StyledTitle>
<Button
buttonStyle="warning"
buttonSize="small"
disabled={!selectedFiltersSetId}
onClick={handleDeleteFilterSets}
data-test="filter-save-filters-set-button"
>
{t('Delete Filters Set')}
</Button>
<StyledTitle>
<div>{t('Name')}</div>
<Input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import BasicErrorAlert from 'src/components/ErrorMessage/BasicErrorAlert';
import { CurrentFilterState } from 'src/dashboard/reducers/types';
import { FilterProps } from './types';
import { getFormData } from '../utils';
import { useCascadingFilters, useFilterState } from './state';
import { useCascadingFilters } from './state';

const StyledLoadingBox = styled.div`
position: relative;
Expand All @@ -50,7 +50,6 @@ const FilterValue: React.FC<FilterProps> = ({
}) => {
const { id, targets, filterType } = filter;
const cascadingFilters = useCascadingFilters(id);
const filterState = useFilterState(id);
const [state, setState] = useState([]);
const [error, setError] = useState<boolean>(false);
const [formData, setFormData] = useState<Partial<QueryFormData>>({});
Expand All @@ -61,7 +60,6 @@ const FilterValue: React.FC<FilterProps> = ({
column = {},
}: Partial<{ datasetId: number; column: { name?: string } }> = target;
const { name: groupby } = column;
const currentValue = filterState.currentState?.value;
const hasDataSource = !!(datasetId && groupby);
const [loading, setLoading] = useState<boolean>(hasDataSource);
useEffect(() => {
Expand All @@ -70,7 +68,6 @@ const FilterValue: React.FC<FilterProps> = ({
datasetId,
cascadingFilters,
groupby,
currentValue,
inputRef,
});
if (!areObjectsEqual(formData || {}, newFormData)) {
Expand All @@ -93,7 +90,13 @@ const FilterValue: React.FC<FilterProps> = ({
setLoading(false);
});
}
}, [cascadingFilters, datasetId, groupby, filter.defaultValue, currentValue]);
}, [
cascadingFilters,
datasetId,
groupby,
JSON.stringify(filter),
hasDataSource,
]);

useEffect(() => {
if (directPathToChild?.[0] === filter.id) {
Expand Down
7 changes: 4 additions & 3 deletions superset-frontend/src/dashboard/reducers/getInitialState.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,9 +258,10 @@ export default function getInitialState(bootstrapData) {
directPathToChild.push(directLinkComponentId);
}

const nativeFilters = getInitialNativeFilterState(
dashboard.metadata.filter_configuration || [],
);
const nativeFilters = getInitialNativeFilterState({
filterConfig: dashboard.metadata.filter_configuration || [],
filterSetsConfig: dashboard.metadata.filter_sets_configuration || [],
});

return {
datasources,
Expand Down
Loading

0 comments on commit 2ff8741

Please sign in to comment.