Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(filter-sets): Saving filter sets in metadata #13205

Merged
merged 12 commits into from
Feb 18, 2021
Merged
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