You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by gr...@apache.org on 2019/11/06 22:26:12 UTC
[incubator-superset] branch scoped-filter-redux-v2 created (now
af17619)
This is an automated email from the ASF dual-hosted git repository.
graceguo pushed a change to branch scoped-filter-redux-v2
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git.
at af17619 merge filter scope settings into dashboard redux state
This branch includes the following new commits:
new af17619 merge filter scope settings into dashboard redux state
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
[incubator-superset] 01/01: merge filter scope settings into
dashboard redux state
Posted by gr...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
graceguo pushed a commit to branch scoped-filter-redux-v2
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
commit af17619e18893ba8e2860c59877056e0eb105c53
Author: Grace <gr...@airbnb.com>
AuthorDate: Wed Nov 6 13:35:32 2019 -0800
merge filter scope settings into dashboard redux state
---
.../src/dashboard/actions/dashboardFilters.js | 16 +++
.../src/dashboard/actions/dashboardLayout.js | 4 +
.../assets/src/dashboard/components/Dashboard.jsx | 72 +++++-------
.../components/FilterIndicatorsContainer.jsx | 30 ++---
.../assets/src/dashboard/components/Header.jsx | 5 +
.../dashboard/components/HeaderActionsDropdown.jsx | 3 +
.../assets/src/dashboard/components/SaveModal.jsx | 3 +
.../src/dashboard/components/SliceHeader.jsx | 2 -
.../dashboard/components/SliceHeaderControls.jsx | 5 +-
.../components/filterscope/FilterScopeSelector.jsx | 62 +++++-----
.../dashboard/components/gridComponents/Chart.jsx | 16 ++-
superset/assets/src/dashboard/containers/Chart.jsx | 11 +-
.../assets/src/dashboard/containers/Dashboard.jsx | 8 +-
.../src/dashboard/containers/DashboardHeader.jsx | 10 +-
.../src/dashboard/containers/FilterIndicators.jsx | 6 +-
.../src/dashboard/containers/FilterScope.jsx | 11 +-
.../src/dashboard/reducers/dashboardFilters.js | 43 ++++++-
.../src/dashboard/reducers/getInitialState.js | 51 ++++++--
.../src/dashboard/util/activeDashboardFilters.js | 130 ++++++++++++++++-----
.../util/charts/getEffectiveExtraFilters.js | 43 ++-----
.../util/charts/getFormDataWithExtraFilters.js | 10 +-
.../src/dashboard/util/getCurrentScopeChartIds.js | 62 ----------
.../assets/src/dashboard/util/getDashboardUrl.js | 20 +++-
...ashboardUrl.js => getFilterValuesByFilterId.js} | 23 +++-
superset/assets/src/dashboard/util/propShapes.jsx | 26 ++---
...hboardUrl.js => serializeActiveFilterValues.js} | 21 +++-
...getDashboardUrl.js => serializeFilterScopes.js} | 19 ++-
superset/views/core.py | 14 ++-
28 files changed, 420 insertions(+), 306 deletions(-)
diff --git a/superset/assets/src/dashboard/actions/dashboardFilters.js b/superset/assets/src/dashboard/actions/dashboardFilters.js
index c2b8b9c..b8f92b8 100644
--- a/superset/assets/src/dashboard/actions/dashboardFilters.js
+++ b/superset/assets/src/dashboard/actions/dashboardFilters.js
@@ -46,11 +46,13 @@ export const CHANGE_FILTER = 'CHANGE_FILTER';
export function changeFilter(chartId, newSelectedValues, merge) {
return (dispatch, getState) => {
if (isValidFilter(getState, chartId)) {
+ const components = getState().dashboardLayout.present;
return dispatch({
type: CHANGE_FILTER,
chartId,
newSelectedValues,
merge,
+ components,
});
}
return getState().dashboardFilters;
@@ -66,3 +68,17 @@ export function updateDirectPathToFilter(chartId, path) {
return getState().dashboardFilters;
};
}
+
+export const UPDATE_LAYOUT_COMPONENTS = 'UPDATE_LAYOUT_COMPONENTS';
+export function updateLayoutComponents(components) {
+ return dispatch => {
+ dispatch({ type: UPDATE_LAYOUT_COMPONENTS, components });
+ };
+}
+
+export const UPDATE_DASHBOARD_FILTERS_SCOPE = 'UPDATE_DASHBOARD_FILTERS_SCOPE';
+export function updateDashboardFiltersScope(scopes) {
+ return dispatch => {
+ dispatch({ type: UPDATE_DASHBOARD_FILTERS_SCOPE, scopes });
+ };
+}
diff --git a/superset/assets/src/dashboard/actions/dashboardLayout.js b/superset/assets/src/dashboard/actions/dashboardLayout.js
index b276e37..8e517d5 100644
--- a/superset/assets/src/dashboard/actions/dashboardLayout.js
+++ b/superset/assets/src/dashboard/actions/dashboardLayout.js
@@ -20,6 +20,7 @@ import { ActionCreators as UndoActionCreators } from 'redux-undo';
import { t } from '@superset-ui/translation';
import { addWarningToast } from '../../messageToasts/actions';
+import { updateLayoutComponents } from './dashboardFilters';
import { setUnsavedChanges } from './dashboardState';
import { TABS_TYPE, ROW_TYPE } from '../util/componentTypes';
import {
@@ -42,6 +43,9 @@ function setUnsavedChangesAfterAction(action) {
dispatch(result);
}
+ const components = getState().dashboardLayout.present;
+ dispatch(updateLayoutComponents(components));
+
if (!getState().dashboardState.hasUnsavedChanges) {
dispatch(setUnsavedChanges(true));
}
diff --git a/superset/assets/src/dashboard/components/Dashboard.jsx b/superset/assets/src/dashboard/components/Dashboard.jsx
index 334704e..85c670c 100644
--- a/superset/assets/src/dashboard/components/Dashboard.jsx
+++ b/superset/assets/src/dashboard/components/Dashboard.jsx
@@ -28,9 +28,7 @@ import {
slicePropShape,
dashboardInfoPropShape,
dashboardStatePropShape,
- loadStatsPropShape,
} from '../util/propShapes';
-import { areObjectsEqual } from '../../reduxUtils';
import { LOG_ACTIONS_MOUNT_DASHBOARD } from '../../logger/LogUtils';
import OmniContainer from '../../components/OmniContainer';
import { safeStringify } from '../../utils/safeStringify';
@@ -50,7 +48,6 @@ const propTypes = {
slices: PropTypes.objectOf(slicePropShape).isRequired,
filters: PropTypes.object.isRequired,
datasources: PropTypes.object.isRequired,
- loadStats: loadStatsPropShape.isRequired,
layout: PropTypes.object.isRequired,
impressionId: PropTypes.string.isRequired,
initMessages: PropTypes.array,
@@ -122,27 +119,36 @@ class Dashboard extends React.PureComponent {
// do not apply filter when dashboard in edit mode
if (!editMode && safeStringify(appliedFilters) !== safeStringify(filters)) {
// refresh charts if a filter was removed, added, or changed
- let changedFilterKey = null;
const currFilterKeys = Object.keys(filters);
const appliedFilterKeys = Object.keys(appliedFilters);
- currFilterKeys.forEach(key => {
- if (
- // filter was added or changed
- typeof appliedFilters[key] === 'undefined' ||
- !areObjectsEqual(appliedFilters[key], filters[key])
+ const allKeys = new Set(currFilterKeys.concat(appliedFilterKeys));
+ [...allKeys].forEach(filterKey => {
+ const affectedChartIds = [];
+ if (!currFilterKeys.includes(filterKey)) {
+ // removed filter?
+ [].push.apply(affectedChartIds, appliedFilters[filterKey].scope);
+ } else if (!appliedFilterKeys.includes(filterKey)) {
+ // added filter?
+ [].push.apply(affectedChartIds, filters[filterKey].scope);
+ } else if (
+ safeStringify(filters[filterKey].values) !==
+ safeStringify(appliedFilters[filterKey].values) ||
+ safeStringify(filters[filterKey].scope) !==
+ safeStringify(appliedFilters[filterKey].scope)
) {
- changedFilterKey = key;
+ // changed filter field value?
+ const affectedScope = filters[filterKey].scope.concat(
+ appliedFilters[filterKey].scope,
+ );
+ [].push.apply(affectedChartIds, affectedScope);
}
+
+ const idSet = new Set(affectedChartIds);
+ this.refreshCharts([...idSet]);
});
- if (
- !!changedFilterKey ||
- currFilterKeys.length !== appliedFilterKeys.length // remove 1 or more filters
- ) {
- this.refreshExcept(changedFilterKey);
- this.appliedFilters = filters;
- }
+ this.appliedFilters = filters;
}
if (hasUnsavedChanges) {
@@ -157,35 +163,9 @@ class Dashboard extends React.PureComponent {
return Object.values(this.props.charts);
}
- refreshExcept(filterKey) {
- const { filters } = this.props;
- const currentFilteredNames =
- filterKey && filters[filterKey] ? Object.keys(filters[filterKey]) : [];
- const filterImmuneSlices = this.props.dashboardInfo.metadata
- .filterImmuneSlices;
- const filterImmuneSliceFields = this.props.dashboardInfo.metadata
- .filterImmuneSliceFields;
-
- this.getAllCharts().forEach(chart => {
- // filterKey is a string, filter_immune_slices array contains numbers
- if (
- String(chart.id) === filterKey ||
- filterImmuneSlices.includes(chart.id)
- ) {
- return;
- }
-
- const filterImmuneSliceFieldsNames =
- filterImmuneSliceFields[chart.id] || [];
- // has filter-able field names
- if (
- currentFilteredNames.length === 0 ||
- currentFilteredNames.some(
- name => !filterImmuneSliceFieldsNames.includes(name),
- )
- ) {
- this.props.actions.triggerQuery(true, chart.id);
- }
+ refreshCharts(ids) {
+ ids.forEach(id => {
+ this.props.actions.triggerQuery(true, id);
});
}
diff --git a/superset/assets/src/dashboard/components/FilterIndicatorsContainer.jsx b/superset/assets/src/dashboard/components/FilterIndicatorsContainer.jsx
index f287108..bfc5658 100644
--- a/superset/assets/src/dashboard/components/FilterIndicatorsContainer.jsx
+++ b/superset/assets/src/dashboard/components/FilterIndicatorsContainer.jsx
@@ -23,6 +23,7 @@ import { isEmpty } from 'lodash';
import FilterIndicator from './FilterIndicator';
import FilterIndicatorGroup from './FilterIndicatorGroup';
import { FILTER_INDICATORS_DISPLAY_LENGTH } from '../util/constants';
+import { getChartIdsInFilterScope } from '../util/activeDashboardFilters';
import { getDashboardFilterKey } from '../util/getDashboardFilterKey';
import { getFilterColorMap } from '../util/dashboardFiltersColorMap';
@@ -33,8 +34,6 @@ const propTypes = {
chartStatus: PropTypes.string,
// from redux
- filterImmuneSlices: PropTypes.arrayOf(PropTypes.number).isRequired,
- filterImmuneSliceFields: PropTypes.object.isRequired,
setDirectPathToChild: PropTypes.func.isRequired,
filterFieldOnFocus: PropTypes.object.isRequired,
};
@@ -59,8 +58,6 @@ export default class FilterIndicatorsContainer extends React.PureComponent {
const {
dashboardFilters,
chartId: currentChartId,
- filterImmuneSlices,
- filterImmuneSliceFields,
filterFieldOnFocus,
} = this.props;
@@ -76,20 +73,22 @@ export default class FilterIndicatorsContainer extends React.PureComponent {
chartId,
componentId,
directPathToFilter,
- scope,
isDateFilter,
isInstantFilter,
columns,
labels,
+ scopes,
} = dashboardFilter;
- // do not apply filter on filter_box itself
- // do not apply filter on filterImmuneSlices list
- if (
- currentChartId !== chartId &&
- !filterImmuneSlices.includes(currentChartId)
- ) {
+ if (currentChartId !== chartId) {
Object.keys(columns).forEach(name => {
+ const chartIdsInFilterScope = getChartIdsInFilterScope({
+ filterScope: scopes[name],
+ });
+
+ if (!chartIdsInFilterScope.includes(currentChartId)) {
+ return;
+ }
const colorMapKey = getDashboardFilterKey({
chartId,
column: name,
@@ -101,7 +100,6 @@ export default class FilterIndicatorsContainer extends React.PureComponent {
colorCode: dashboardFiltersColorMap[colorMapKey],
componentId,
directPathToFilter: directPathToLabel,
- scope,
isDateFilter,
isInstantFilter,
name,
@@ -116,14 +114,6 @@ export default class FilterIndicatorsContainer extends React.PureComponent {
name === filterFieldOnFocus.column,
};
- // do not apply filter on fields in the filterImmuneSliceFields map
- if (
- filterImmuneSliceFields[currentChartId] &&
- filterImmuneSliceFields[currentChartId].includes(name)
- ) {
- return;
- }
-
if (isEmpty(indicator.values)) {
indicators[1].push(indicator);
} else {
diff --git a/superset/assets/src/dashboard/components/Header.jsx b/superset/assets/src/dashboard/components/Header.jsx
index 569dac9..1d56986 100644
--- a/superset/assets/src/dashboard/components/Header.jsx
+++ b/superset/assets/src/dashboard/components/Header.jsx
@@ -54,6 +54,7 @@ const propTypes = {
charts: PropTypes.objectOf(chartPropShape).isRequired,
layout: PropTypes.object.isRequired,
filters: PropTypes.object.isRequired,
+ filterScopes: PropTypes.object.isRequired,
expandedSlices: PropTypes.object.isRequired,
css: PropTypes.string.isRequired,
colorNamespace: PropTypes.string,
@@ -218,6 +219,7 @@ class Header extends React.PureComponent {
colorNamespace,
colorScheme,
filters,
+ filterScopes,
dashboardInfo,
refreshFrequency,
} = this.props;
@@ -236,6 +238,7 @@ class Header extends React.PureComponent {
label_colors: labelColors,
dashboard_title: dashboardTitle,
default_filters: safeStringify(filters),
+ filter_scopes: safeStringify(filterScopes),
refresh_frequency: refreshFrequency,
};
@@ -264,6 +267,7 @@ class Header extends React.PureComponent {
dashboardTitle,
layout,
filters,
+ filterScopes,
expandedSlices,
css,
colorNamespace,
@@ -416,6 +420,7 @@ class Header extends React.PureComponent {
dashboardTitle={dashboardTitle}
layout={layout}
filters={filters}
+ filterScopes={filterScopes}
expandedSlices={expandedSlices}
css={css}
colorNamespace={colorNamespace}
diff --git a/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx b/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx
index 6fc0182..6038b4e 100644
--- a/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx
+++ b/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx
@@ -51,6 +51,7 @@ const propTypes = {
isLoading: PropTypes.bool.isRequired,
layout: PropTypes.object.isRequired,
filters: PropTypes.object.isRequired,
+ filterScopes: PropTypes.object.isRequired,
expandedSlices: PropTypes.object.isRequired,
onSave: PropTypes.func.isRequired,
};
@@ -121,6 +122,7 @@ class HeaderActionsDropdown extends React.PureComponent {
hasUnsavedChanges,
layout,
filters,
+ filterScopes,
expandedSlices,
onSave,
userCanEdit,
@@ -149,6 +151,7 @@ class HeaderActionsDropdown extends React.PureComponent {
saveType={SAVE_TYPE_NEWDASHBOARD}
layout={layout}
filters={filters}
+ filterScopes={filterScopes}
expandedSlices={expandedSlices}
refreshFrequency={refreshFrequency}
css={css}
diff --git a/superset/assets/src/dashboard/components/SaveModal.jsx b/superset/assets/src/dashboard/components/SaveModal.jsx
index d926bc8..5a978b5 100644
--- a/superset/assets/src/dashboard/components/SaveModal.jsx
+++ b/superset/assets/src/dashboard/components/SaveModal.jsx
@@ -38,6 +38,7 @@ const propTypes = {
saveType: PropTypes.oneOf([SAVE_TYPE_OVERWRITE, SAVE_TYPE_NEWDASHBOARD]),
triggerNode: PropTypes.node.isRequired,
filters: PropTypes.object.isRequired,
+ filterScopes: PropTypes.object.isRequired,
css: PropTypes.string.isRequired,
colorNamespace: PropTypes.string,
colorScheme: PropTypes.string,
@@ -102,6 +103,7 @@ class SaveModal extends React.PureComponent {
colorScheme,
expandedSlices,
filters,
+ filterScopes,
dashboardId,
refreshFrequency,
} = this.props;
@@ -121,6 +123,7 @@ class SaveModal extends React.PureComponent {
dashboard_title:
saveType === SAVE_TYPE_NEWDASHBOARD ? newDashName : dashboardTitle,
default_filters: safeStringify(filters),
+ filter_scopes: safeStringify(filterScopes),
duplicate_slices: this.state.duplicateSlices,
refresh_frequency: refreshFrequency,
};
diff --git a/superset/assets/src/dashboard/components/SliceHeader.jsx b/superset/assets/src/dashboard/components/SliceHeader.jsx
index 100a070..3d36a51 100644
--- a/superset/assets/src/dashboard/components/SliceHeader.jsx
+++ b/superset/assets/src/dashboard/components/SliceHeader.jsx
@@ -94,7 +94,6 @@ class SliceHeader extends React.PureComponent {
annotationQuery,
annotationError,
componentId,
- filters,
addDangerToast,
} = this.props;
@@ -146,7 +145,6 @@ class SliceHeader extends React.PureComponent {
supersetCanCSV={supersetCanCSV}
sliceCanEdit={sliceCanEdit}
componentId={componentId}
- filters={filters}
addDangerToast={addDangerToast}
/>
)}
diff --git a/superset/assets/src/dashboard/components/SliceHeaderControls.jsx b/superset/assets/src/dashboard/components/SliceHeaderControls.jsx
index 65565f0..779609d 100644
--- a/superset/assets/src/dashboard/components/SliceHeaderControls.jsx
+++ b/superset/assets/src/dashboard/components/SliceHeaderControls.jsx
@@ -23,11 +23,11 @@ import { Dropdown, MenuItem } from 'react-bootstrap';
import { t } from '@superset-ui/translation';
import URLShortLinkModal from '../../components/URLShortLinkModal';
import getDashboardUrl from '../util/getDashboardUrl';
+import { getActiveFilters } from '../util/activeDashboardFilters';
const propTypes = {
slice: PropTypes.object.isRequired,
componentId: PropTypes.string.isRequired,
- filters: PropTypes.object.isRequired,
addDangerToast: PropTypes.func.isRequired,
isCached: PropTypes.bool,
isExpanded: PropTypes.bool,
@@ -107,7 +107,6 @@ class SliceHeaderControls extends React.PureComponent {
isCached,
cachedDttm,
updatedDttm,
- filters,
componentId,
addDangerToast,
} = this.props;
@@ -162,7 +161,7 @@ class SliceHeaderControls extends React.PureComponent {
<URLShortLinkModal
url={getDashboardUrl(
window.location.pathname,
- filters,
+ getActiveFilters(),
componentId,
)}
addDangerToast={addDangerToast}
diff --git a/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx b/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx
index a469906..fd70499 100644
--- a/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx
+++ b/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx
@@ -26,24 +26,26 @@ import buildFilterScopeTreeEntry from '../../util/buildFilterScopeTreeEntry';
import getFilterScopeNodesTree from '../../util/getFilterScopeNodesTree';
import getFilterFieldNodesTree from '../../util/getFilterFieldNodesTree';
import getFilterScopeParentNodes from '../../util/getFilterScopeParentNodes';
-import getCurrentScopeChartIds from '../../util/getCurrentScopeChartIds';
import getKeyForFilterScopeTree from '../../util/getKeyForFilterScopeTree';
import getSelectedChartIdForFilterScopeTree from '../../util/getSelectedChartIdForFilterScopeTree';
+import getFilterScopeFromNodesTree from '../../util/getFilterScopeFromNodesTree';
import getRevertedFilterScope from '../../util/getRevertedFilterScope';
import FilterScopeTree from './FilterScopeTree';
import FilterFieldTree from './FilterFieldTree';
+import { getChartIdsInFilterScope } from '../../util/activeDashboardFilters';
import {
getChartIdAndColumnFromFilterKey,
getDashboardFilterKey,
} from '../../util/getDashboardFilterKey';
import { ALL_FILTERS } from '../../util/constants';
+import { dashboardFilterPropShape } from '../../util/propShapes';
const propTypes = {
- dashboardFilters: PropTypes.object.isRequired,
+ dashboardFilters: dashboardFilterPropShape.isRequired,
layout: PropTypes.object.isRequired,
- filterImmuneSlices: PropTypes.arrayOf(PropTypes.number).isRequired,
- filterImmuneSliceFields: PropTypes.object.isRequired,
- setDirectPathToChild: PropTypes.func.isRequired,
+
+ updateDashboardFiltersScope: PropTypes.func.isRequired,
+ setUnsavedChanges: PropTypes.func.isRequired,
onCloseModal: PropTypes.func.isRequired,
};
@@ -51,12 +53,7 @@ export default class FilterScopeSelector extends React.PureComponent {
constructor(props) {
super(props);
- const {
- dashboardFilters,
- filterImmuneSlices,
- filterImmuneSliceFields,
- layout,
- } = props;
+ const { dashboardFilters, layout } = props;
if (Object.keys(dashboardFilters).length > 0) {
// display filter fields in tree structure
@@ -88,14 +85,12 @@ export default class FilterScopeSelector extends React.PureComponent {
filterFields: [filterKey],
selectedChartId: filterId,
});
- const checked = getCurrentScopeChartIds({
- scopeComponentIds: ['ROOT_ID'], // dashboardFilters[chartId].scopes[columnName],
- filterField: columnName,
- filterImmuneSlices,
- filterImmuneSliceFields,
- components: layout,
- });
const expanded = getFilterScopeParentNodes(nodes, 1);
+ // display filter_box chart as checked, but do not show checkbox
+ const chartIdsInFilterScope = getChartIdsInFilterScope({
+ filterScope: dashboardFilters[filterId].scopes[columnName],
+ });
+
return {
...mapByChartId,
[filterKey]: {
@@ -103,7 +98,7 @@ export default class FilterScopeSelector extends React.PureComponent {
nodes,
// filtered nodes in display if searchText is not empty
nodesFiltered: [...nodes],
- checked,
+ checked: chartIdsInFilterScope,
expanded,
},
};
@@ -304,17 +299,28 @@ export default class FilterScopeSelector extends React.PureComponent {
onSave() {
const { filterScopeMap } = this.state;
- console.log(
- 'i am current state',
- this.allfilterFields.reduce(
- (map, key) => ({
+ const allFilterFieldScopes = this.allfilterFields.reduce(
+ (map, filterKey) => {
+ const { chartId } = getChartIdAndColumnFromFilterKey(filterKey);
+ const nodes = filterScopeMap[filterKey].nodes;
+ // remove filter's chart id from its own scope
+ const checkedChartIds =
+ filterScopeMap[filterKey].checked.filter(id => id !== chartId) || [];
+
+ return {
...map,
- [key]: filterScopeMap[key].checked,
- }),
- {},
- ),
+ [filterKey]: getFilterScopeFromNodesTree({
+ filterKey,
+ nodes,
+ checkedChartIds,
+ }),
+ };
+ },
+ {},
);
+ this.props.updateDashboardFiltersScope(allFilterFieldScopes);
+ this.props.setUnsavedChanges(true);
// save do not close modal
}
@@ -503,7 +509,7 @@ export default class FilterScopeSelector extends React.PureComponent {
)}
</div>
<div className="dashboard-modal-actions-container">
- <Button onClick={this.onClose}>{t('Cancel')}</Button>
+ <Button onClick={this.onClose}>{t('Close')}</Button>
{showSelector && (
<Button bsStyle="primary" onClick={this.onSave}>
{t('Save')}
diff --git a/superset/assets/src/dashboard/components/gridComponents/Chart.jsx b/superset/assets/src/dashboard/components/gridComponents/Chart.jsx
index 4d34cc6..d50c24c 100644
--- a/superset/assets/src/dashboard/components/gridComponents/Chart.jsx
+++ b/superset/assets/src/dashboard/components/gridComponents/Chart.jsx
@@ -30,6 +30,9 @@ import {
LOG_ACTIONS_EXPORT_CSV_DASHBOARD_CHART,
LOG_ACTIONS_FORCE_REFRESH_CHART,
} from '../../../logger/LogUtils';
+import { safeStringify } from '../../../utils/safeStringify';
+import { isFilterBox } from '../../util/activeDashboardFilters';
+import getFilterValuesByFilterId from '../../util/getFilterValuesByFilterId';
const propTypes = {
id: PropTypes.number.isRequired,
@@ -46,6 +49,7 @@ const propTypes = {
slice: slicePropShape.isRequired,
sliceName: PropTypes.string.isRequired,
timeout: PropTypes.number.isRequired,
+ // all active filter fields in dashboard
filters: PropTypes.object.isRequired,
refreshChart: PropTypes.func.isRequired,
logEvent: PropTypes.func.isRequired,
@@ -115,7 +119,9 @@ class Chart extends React.Component {
for (let i = 0; i < SHOULD_UPDATE_ON_PROP_CHANGES.length; i += 1) {
const prop = SHOULD_UPDATE_ON_PROP_CHANGES[i];
- if (nextProps[prop] !== this.props[prop]) {
+ if (
+ safeStringify(nextProps[prop]) !== safeStringify(this.props[prop])
+ ) {
return true;
}
}
@@ -238,6 +244,12 @@ class Chart extends React.Component {
const isCached = queryResponse && queryResponse.is_cached;
const cachedDttm = queryResponse && queryResponse.cached_dttm;
const isOverflowable = OVERFLOWABLE_VIZ_TYPES.has(slice.viz_type);
+ const initialValues = isFilterBox(id)
+ ? getFilterValuesByFilterId({
+ activeFilters: filters,
+ filterId: id,
+ })
+ : {};
return (
<div>
@@ -297,7 +309,7 @@ class Chart extends React.Component {
chartId={id}
chartStatus={chart.chartStatus}
datasource={datasource}
- initialValues={filters[id]}
+ initialValues={initialValues}
formData={formData}
queryResponse={chart.queryResponse}
timeout={timeout}
diff --git a/superset/assets/src/dashboard/containers/Chart.jsx b/superset/assets/src/dashboard/containers/Chart.jsx
index 5f0b107..361fe47 100644
--- a/superset/assets/src/dashboard/containers/Chart.jsx
+++ b/superset/assets/src/dashboard/containers/Chart.jsx
@@ -29,7 +29,10 @@ import { changeFilter } from '../actions/dashboardFilters';
import { addDangerToast } from '../../messageToasts/actions';
import { refreshChart } from '../../chart/chartAction';
import { logEvent } from '../../logger/actions';
-import { getActiveFilters } from '../util/activeDashboardFilters';
+import {
+ getActiveFilters,
+ getAppliedFilterValues,
+} from '../util/activeDashboardFilters';
import getFormDataWithExtraFilters from '../util/charts/getFormDataWithExtraFilters';
import Chart from '../components/gridComponents/Chart';
@@ -48,7 +51,6 @@ function mapStateToProps(
const { id } = ownProps;
const chart = chartQueries[id] || {};
const { colorScheme, colorNamespace } = dashboardState;
- const filters = getActiveFilters();
return {
chart,
@@ -57,12 +59,11 @@ function mapStateToProps(
{},
slice: sliceEntities.slices[id],
timeout: dashboardInfo.common.conf.SUPERSET_WEBSERVER_TIMEOUT,
- filters: filters || EMPTY_FILTERS,
+ filters: getActiveFilters() || EMPTY_FILTERS,
// note: this method caches filters if possible to prevent render cascades
formData: getFormDataWithExtraFilters({
chart,
- dashboardMetadata: dashboardInfo.metadata,
- filters,
+ filters: getAppliedFilterValues(id),
colorScheme,
colorNamespace,
sliceId: id,
diff --git a/superset/assets/src/dashboard/containers/Dashboard.jsx b/superset/assets/src/dashboard/containers/Dashboard.jsx
index c602cd3..91cc70f 100644
--- a/superset/assets/src/dashboard/containers/Dashboard.jsx
+++ b/superset/assets/src/dashboard/containers/Dashboard.jsx
@@ -27,7 +27,6 @@ import {
} from '../actions/dashboardState';
import { triggerQuery } from '../../chart/chartAction';
import { logEvent } from '../../logger/actions';
-import getLoadStatsPerTopLevelComponent from '../util/logging/getLoadStatsPerTopLevelComponent';
import { getActiveFilters } from '../util/activeDashboardFilters';
function mapStateToProps(state) {
@@ -49,7 +48,8 @@ function mapStateToProps(state) {
dashboardState,
charts,
datasources,
- // filters prop: All the filter_box's state in this dashboard
+ // filters prop:
+ // All the active filter_box's values and scope in this dashboard, for each filter field.
// When dashboard is first loaded into browser,
// its value is from preselect_filters that dashboard owner saved in dashboard's meta data
// When user start interacting with dashboard, it will be user picked values from all filter_box
@@ -57,10 +57,6 @@ function mapStateToProps(state) {
slices: sliceEntities.slices,
layout: dashboardLayout.present,
impressionId,
- loadStats: getLoadStatsPerTopLevelComponent({
- layout: dashboardLayout.present,
- chartQueries: charts,
- }),
};
}
diff --git a/superset/assets/src/dashboard/containers/DashboardHeader.jsx b/superset/assets/src/dashboard/containers/DashboardHeader.jsx
index 9d11998..b9bfff2 100644
--- a/superset/assets/src/dashboard/containers/DashboardHeader.jsx
+++ b/superset/assets/src/dashboard/containers/DashboardHeader.jsx
@@ -50,6 +50,8 @@ import {
addWarningToast,
} from '../../messageToasts/actions';
+import serializeFilterScopes from '../util/serializeFilterScopes';
+import serializeActiveFilterValues from '../util/serializeActiveFilterValues';
import { logEvent } from '../../logger/actions';
import { DASHBOARD_HEADER_ID } from '../util/constants';
import { getActiveFilters } from '../util/activeDashboardFilters';
@@ -58,14 +60,20 @@ function mapStateToProps({
dashboardLayout: undoableLayout,
dashboardState,
dashboardInfo,
+ dashboardFilters,
charts,
}) {
+ // persist selected values for each filter field, grouped by filter id
+ const filters = serializeActiveFilterValues(getActiveFilters());
+ // persist filter scope for each filter field, grouped by filter id
+ const filterScopes = serializeFilterScopes(dashboardFilters);
return {
dashboardInfo,
undoLength: undoableLayout.past.length,
redoLength: undoableLayout.future.length,
layout: undoableLayout.present,
- filters: getActiveFilters(),
+ filters,
+ filterScopes,
dashboardTitle: (
(undoableLayout.present[DASHBOARD_HEADER_ID] || {}).meta || {}
).text,
diff --git a/superset/assets/src/dashboard/containers/FilterIndicators.jsx b/superset/assets/src/dashboard/containers/FilterIndicators.jsx
index 9ac8d62..80e3f77 100644
--- a/superset/assets/src/dashboard/containers/FilterIndicators.jsx
+++ b/superset/assets/src/dashboard/containers/FilterIndicators.jsx
@@ -23,7 +23,7 @@ import FilterIndicatorsContainer from '../components/FilterIndicatorsContainer';
import { setDirectPathToChild } from '../actions/dashboardState';
function mapStateToProps(
- { dashboardState, dashboardFilters, dashboardInfo, charts },
+ { dashboardState, dashboardFilters, dashboardLayout, charts },
ownProps,
) {
const chartId = ownProps.chartId;
@@ -33,9 +33,7 @@ function mapStateToProps(
dashboardFilters,
chartId,
chartStatus,
- filterImmuneSlices: dashboardInfo.metadata.filterImmuneSlices || [],
- filterImmuneSliceFields:
- dashboardInfo.metadata.filterImmuneSliceFields || {},
+ layout: dashboardLayout.present,
filterFieldOnFocus:
dashboardState.focusedFilterField.length === 0
? {}
diff --git a/superset/assets/src/dashboard/containers/FilterScope.jsx b/superset/assets/src/dashboard/containers/FilterScope.jsx
index f88d665..d816df9 100644
--- a/superset/assets/src/dashboard/containers/FilterScope.jsx
+++ b/superset/assets/src/dashboard/containers/FilterScope.jsx
@@ -19,15 +19,13 @@
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
-import { setDirectPathToChild } from '../actions/dashboardState';
+import { updateDashboardFiltersScope } from '../actions/dashboardFilters';
+import { setUnsavedChanges } from '../actions/dashboardState';
import FilterScopeSelector from '../components/filterscope/FilterScopeSelector';
-function mapStateToProps({ dashboardLayout, dashboardFilters, dashboardInfo }) {
+function mapStateToProps({ dashboardLayout, dashboardFilters }) {
return {
dashboardFilters,
- filterImmuneSlices: dashboardInfo.metadata.filterImmuneSlices || [],
- filterImmuneSliceFields:
- dashboardInfo.metadata.filterImmuneSliceFields || {},
layout: dashboardLayout.present,
};
}
@@ -35,7 +33,8 @@ function mapStateToProps({ dashboardLayout, dashboardFilters, dashboardInfo }) {
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
- setDirectPathToChild,
+ updateDashboardFiltersScope,
+ setUnsavedChanges,
},
dispatch,
);
diff --git a/superset/assets/src/dashboard/reducers/dashboardFilters.js b/superset/assets/src/dashboard/reducers/dashboardFilters.js
index 224b50b..75cb243 100644
--- a/superset/assets/src/dashboard/reducers/dashboardFilters.js
+++ b/superset/assets/src/dashboard/reducers/dashboardFilters.js
@@ -22,12 +22,15 @@ import {
REMOVE_FILTER,
CHANGE_FILTER,
UPDATE_DIRECT_PATH_TO_FILTER,
+ UPDATE_LAYOUT_COMPONENTS,
+ UPDATE_DASHBOARD_FILTERS_SCOPE,
} from '../actions/dashboardFilters';
import { TIME_RANGE } from '../../visualizations/FilterBox/FilterBox';
+import { DASHBOARD_ROOT_ID } from '../util/constants';
import getFilterConfigsFromFormdata from '../util/getFilterConfigsFromFormdata';
import { buildFilterColorMap } from '../util/dashboardFiltersColorMap';
import { buildActiveFilters } from '../util/activeDashboardFilters';
-import { DASHBOARD_ROOT_ID } from '../util/constants';
+import { getChartIdAndColumnFromFilterKey } from '../util/getDashboardFilterKey';
export const DASHBOARD_FILTER_SCOPE_GLOBAL = {
scope: [DASHBOARD_ROOT_ID],
@@ -113,10 +116,42 @@ export default function dashboardFiltersReducer(dashboardFilters = {}, action) {
},
};
- if (action.type === REMOVE_FILTER) {
+ if (action.type === UPDATE_LAYOUT_COMPONENTS) {
+ buildActiveFilters({
+ dashboardFilters,
+ components: action.components,
+ });
+ return dashboardFilters;
+ } else if (action.type === UPDATE_DASHBOARD_FILTERS_SCOPE) {
+ const allDashboardFiltersScope = action.scopes;
+ // update filter scope for each filter field
+ const updatedFilters = Object.entries(allDashboardFiltersScope).reduce(
+ (map, entry) => {
+ const [filterKey, { scope, immune }] = entry;
+ const { chartId, column } = getChartIdAndColumnFromFilterKey(filterKey);
+ const scopes = {
+ ...map[chartId].scopes,
+ [column]: {
+ scope,
+ immune,
+ },
+ };
+ return {
+ ...map,
+ [chartId]: {
+ ...map[chartId],
+ scopes,
+ },
+ };
+ },
+ dashboardFilters,
+ );
+
+ return updatedFilters;
+ } else if (action.type === REMOVE_FILTER) {
const { chartId } = action;
const { [chartId]: deletedFilter, ...updatedFilters } = dashboardFilters;
- buildActiveFilters(updatedFilters);
+ buildActiveFilters({ dashboardFilters: updatedFilters });
buildFilterColorMap(updatedFilters);
return updatedFilters;
@@ -127,7 +162,7 @@ export default function dashboardFiltersReducer(dashboardFilters = {}, action) {
dashboardFilters[action.chartId],
),
};
- buildActiveFilters(updatedFilters);
+ buildActiveFilters({ dashboardFilters: updatedFilters });
buildFilterColorMap(updatedFilters);
return updatedFilters;
diff --git a/superset/assets/src/dashboard/reducers/getInitialState.js b/superset/assets/src/dashboard/reducers/getInitialState.js
index 185a8b0..1cefe92 100644
--- a/superset/assets/src/dashboard/reducers/getInitialState.js
+++ b/superset/assets/src/dashboard/reducers/getInitialState.js
@@ -17,12 +17,15 @@
* under the License.
*/
/* eslint-disable camelcase */
-import { isString } from 'lodash';
+import { isString, isEmpty } from 'lodash';
import shortid from 'shortid';
import { CategoricalColorNamespace } from '@superset-ui/color';
import { chart } from '../../chart/chartReducer';
-import { dashboardFilter } from './dashboardFilters';
+import {
+ DASHBOARD_FILTER_SCOPE_GLOBAL,
+ dashboardFilter,
+} from './dashboardFilters';
import { initSliceEntities } from './sliceEntities';
import { getParam } from '../../modules/utils';
import { applyDefaultFormData } from '../../explore/store';
@@ -98,6 +101,11 @@ export default function(bootstrapData) {
let newSlicesContainer;
let newSlicesContainerWidth = 0;
+ const filterImmuneSliceFields =
+ dashboard.metadata.filter_immune_slice_fields || {};
+ const filterImmuneSlices = dashboard.metadata.filter_immune_slices || [];
+ const filterScopes = dashboard.metadata.filter_scopes || {};
+
const chartQueries = {};
const dashboardFilters = {};
const slices = {};
@@ -173,6 +181,34 @@ export default function(bootstrapData) {
});
}
+ // backward compatible:
+ // merge scoped filter settings with old global immune settings
+ const scopesByChartId = Object.keys(columns).reduce((map, column) => {
+ const scopeSettings = {
+ ...filterScopes[key],
+ };
+ const { scope, immune } = {
+ ...DASHBOARD_FILTER_SCOPE_GLOBAL,
+ ...scopeSettings[column],
+ };
+ const immuneChartIds = new Set(filterImmuneSlices.slice());
+ if (!isEmpty(filterImmuneSliceFields)) {
+ Object.keys(filterImmuneSliceFields).forEach(strChartId => {
+ if (filterImmuneSliceFields[strChartId].includes(column)) {
+ immuneChartIds.add(parseInt(strChartId, 10));
+ }
+ });
+ }
+
+ return {
+ ...map,
+ [column]: {
+ scope,
+ immune: [...immuneChartIds].concat(immune),
+ },
+ };
+ }, {});
+
const componentId = chartIdToLayoutId[key];
const directPathToFilter = (layout[componentId].parents || []).slice();
directPathToFilter.push(componentId);
@@ -184,6 +220,7 @@ export default function(bootstrapData) {
directPathToFilter,
columns,
labels,
+ scopes: scopesByChartId,
isInstantFilter: !!slice.form_data.instant_filtering,
isDateFilter: Object.keys(columns).includes(TIME_RANGE),
};
@@ -198,8 +235,11 @@ export default function(bootstrapData) {
layout[layoutId].meta.sliceName = slice.slice_name;
}
});
- buildActiveFilters(dashboardFilters);
- buildFilterColorMap(dashboardFilters);
+ buildActiveFilters({
+ dashboardFilters,
+ components: layout,
+ });
+ buildFilterColorMap(dashboardFilters, layout);
// store the header as a layout component so we can undo/redo changes
layout[DASHBOARD_HEADER_ID] = {
@@ -233,9 +273,6 @@ export default function(bootstrapData) {
id: dashboard.id,
slug: dashboard.slug,
metadata: {
- filterImmuneSliceFields:
- dashboard.metadata.filter_immune_slice_fields || {},
- filterImmuneSlices: dashboard.metadata.filter_immune_slices || [],
timed_refresh_immune_slices:
dashboard.metadata.timed_refresh_immune_slices,
},
diff --git a/superset/assets/src/dashboard/util/activeDashboardFilters.js b/superset/assets/src/dashboard/util/activeDashboardFilters.js
index 8fa00d0..4a6a5af 100644
--- a/superset/assets/src/dashboard/util/activeDashboardFilters.js
+++ b/superset/assets/src/dashboard/util/activeDashboardFilters.js
@@ -16,52 +16,120 @@
* specific language governing permissions and limitations
* under the License.
*/
-let activeFilters = {};
+import { isEmpty } from 'lodash';
+
+import {
+ getChartIdAndColumnFromFilterKey,
+ getDashboardFilterKey,
+} from './getDashboardFilterKey';
+import { CHART_TYPE } from '../util/componentTypes';
+
let allFilterBoxChartIds = [];
+let activeFilters = {};
+let appliedFilterValuesByChart = {};
+let allComponents = {};
+// output: { [id_column]: { values, scope } }
export function getActiveFilters() {
return activeFilters;
}
-// currently filterbox is a chart,
-// when define filter scopes, they have to be out pulled out in a few places.
-// after we make filterbox a dashboard build-in component,
-// will not need this check anymore
+// currently filter_box is a chart,
+// when selecting filter scopes, they have to be out pulled out in a few places.
+// after we make filter_box a dashboard build-in component, will not need this check anymore.
export function isFilterBox(chartId) {
return allFilterBoxChartIds.includes(chartId);
}
-export function getAllFilterBoxChartIds() {
- return allFilterBoxChartIds;
+// output: { [column]: values }
+export function getAppliedFilterValues(chartId) {
+ if (!(chartId in appliedFilterValuesByChart)) {
+ appliedFilterValuesByChart[chartId] = Object.entries(activeFilters).reduce(
+ (map, entry) => {
+ const [filterKey, { scope: chartIds, values }] = entry;
+ if (chartIds.includes(chartId)) {
+ const { column } = getChartIdAndColumnFromFilterKey(filterKey);
+ return {
+ ...map,
+ [column]: values,
+ };
+ }
+ return map;
+ },
+ {},
+ );
+ }
+ return appliedFilterValuesByChart[chartId];
+}
+
+export function getChartIdsInFilterScope({ filterScope }) {
+ function traverse(chartIds, component, immuneChartIds) {
+ if (!component) {
+ return;
+ }
+
+ if (
+ component.type === CHART_TYPE &&
+ component.meta &&
+ component.meta.chartId &&
+ !immuneChartIds.includes(component.meta.chartId)
+ ) {
+ chartIds.push(component.meta.chartId);
+ } else if (component.children) {
+ component.children.forEach(child =>
+ traverse(chartIds, allComponents[child], immuneChartIds),
+ );
+ }
+ }
+
+ const chartIds = [];
+ const { scope: scopeComponentIds, immune: immuneChartIds } = filterScope;
+ scopeComponentIds.forEach(componentId =>
+ traverse(chartIds, allComponents[componentId], immuneChartIds),
+ );
+
+ return chartIds;
}
-// non-empty filters from dashboardFilters,
-// this function does not take into account: filter immune or filter scope settings
-export function buildActiveFilters(allDashboardFilters = {}) {
- allFilterBoxChartIds = Object.values(allDashboardFilters).map(
+// non-empty filter fields in dashboardFilters,
+// activeFilters map contains selected values and filter scope.
+// values: array of selected values
+// scope: array of chartIds that applicable by the filter field.
+export function buildActiveFilters({ dashboardFilters = {}, components = {} }) {
+ allFilterBoxChartIds = Object.values(dashboardFilters).map(
filter => filter.chartId,
);
- activeFilters = Object.values(allDashboardFilters).reduce(
- (result, filter) => {
- const { chartId, columns } = filter;
+ // clear cache
+ if (!isEmpty(components)) {
+ allComponents = components;
+ }
+ appliedFilterValuesByChart = {};
+ activeFilters = Object.values(dashboardFilters).reduce((result, filter) => {
+ const { chartId, columns, scopes } = filter;
+ const nonEmptyFilters = {};
- Object.keys(columns).forEach(key => {
- if (
- Array.isArray(columns[key])
- ? columns[key].length
- : columns[key] !== undefined
- ) {
- /* eslint-disable no-param-reassign */
- result[chartId] = {
- ...result[chartId],
- [key]: columns[key],
- };
- }
- });
+ Object.keys(columns).forEach(column => {
+ if (
+ Array.isArray(columns[column])
+ ? columns[column].length
+ : columns[column] !== undefined
+ ) {
+ // remove filter itself
+ const scope = getChartIdsInFilterScope({
+ filterScope: scopes[column],
+ }).filter(id => chartId !== id);
- return result;
- },
- {},
- );
+ nonEmptyFilters[getDashboardFilterKey({ chartId, column })] = {
+ values: columns[column],
+ scope,
+ };
+ }
+ });
+
+ return {
+ ...result,
+ ...nonEmptyFilters,
+ };
+ }, {});
}
diff --git a/superset/assets/src/dashboard/util/charts/getEffectiveExtraFilters.js b/superset/assets/src/dashboard/util/charts/getEffectiveExtraFilters.js
index fa6ad2a..53ead91 100644
--- a/superset/assets/src/dashboard/util/charts/getEffectiveExtraFilters.js
+++ b/superset/assets/src/dashboard/util/charts/getEffectiveExtraFilters.js
@@ -16,43 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
-export default function getEffectiveExtraFilters({
- dashboardMetadata,
- filters,
- sliceId,
-}) {
- const immuneSlices = dashboardMetadata.filterImmuneSlices || [];
-
- if (sliceId && immuneSlices.includes(sliceId)) {
- // The slice is immune to dashboard filters
- return [];
- }
-
- // Build a list of fields the slice is immune to filters on
+export default function getEffectiveExtraFilters(filters) {
const effectiveFilters = [];
- let immuneToFields = [];
- if (
- sliceId &&
- dashboardMetadata.filterImmuneSliceFields &&
- dashboardMetadata.filterImmuneSliceFields[sliceId]
- ) {
- immuneToFields = dashboardMetadata.filterImmuneSliceFields[sliceId];
- }
-
- Object.keys(filters).forEach(filteringSliceId => {
- if (filteringSliceId === sliceId.toString()) {
- // Filters applied by the slice don't apply to itself
- return;
- }
- const filtersFromSlice = filters[filteringSliceId];
- Object.keys(filtersFromSlice).forEach(field => {
- if (!immuneToFields.includes(field)) {
- effectiveFilters.push({
- col: field,
- op: 'in',
- val: filtersFromSlice[field],
- });
- }
+ Object.entries(filters).forEach(entry => {
+ const [column, values] = entry;
+ effectiveFilters.push({
+ col: column,
+ op: 'in',
+ val: values,
});
});
diff --git a/superset/assets/src/dashboard/util/charts/getFormDataWithExtraFilters.js b/superset/assets/src/dashboard/util/charts/getFormDataWithExtraFilters.js
index 5869a09..5aa8d0f 100644
--- a/superset/assets/src/dashboard/util/charts/getFormDataWithExtraFilters.js
+++ b/superset/assets/src/dashboard/util/charts/getFormDataWithExtraFilters.js
@@ -22,13 +22,11 @@ import getEffectiveExtraFilters from './getEffectiveExtraFilters';
// We cache formData objects so that our connected container components don't always trigger
// render cascades. we cannot leverage the reselect library because our cache size is >1
-const cachedDashboardMetadataByChart = {};
const cachedFiltersByChart = {};
const cachedFormdataByChart = {};
export default function getFormDataWithExtraFilters({
chart = {},
- dashboardMetadata,
filters,
colorScheme,
colorNamespace,
@@ -40,7 +38,6 @@ export default function getFormDataWithExtraFilters({
// if dashboard metadata + filters have not changed, use cache if possible
if (
- (cachedDashboardMetadataByChart[sliceId] || {}) === dashboardMetadata &&
(cachedFiltersByChart[sliceId] || {}) === filters &&
(colorScheme == null ||
cachedFormdataByChart[sliceId].color_scheme === colorScheme) &&
@@ -55,14 +52,9 @@ export default function getFormDataWithExtraFilters({
...chart.formData,
...(colorScheme && { color_scheme: colorScheme }),
label_colors: labelColors,
- extra_filters: getEffectiveExtraFilters({
- dashboardMetadata,
- filters,
- sliceId,
- }),
+ extra_filters: getEffectiveExtraFilters(filters),
};
- cachedDashboardMetadataByChart[sliceId] = dashboardMetadata;
cachedFiltersByChart[sliceId] = filters;
cachedFormdataByChart[sliceId] = formData;
diff --git a/superset/assets/src/dashboard/util/getCurrentScopeChartIds.js b/superset/assets/src/dashboard/util/getCurrentScopeChartIds.js
deleted file mode 100644
index 60d86b5..0000000
--- a/superset/assets/src/dashboard/util/getCurrentScopeChartIds.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * 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 { CHART_TYPE } from '../util/componentTypes';
-
-export default function getCurrentScopeChartIds({
- scopeComponentIds,
- filterField,
- filterImmuneSlices,
- filterImmuneSliceFields,
- components,
-}) {
- let chartIds = [];
-
- function traverse(component) {
- if (!component) {
- return;
- }
-
- if (
- component.type === CHART_TYPE &&
- component.meta &&
- component.meta.chartId
- ) {
- chartIds.push(component.meta.chartId);
- } else if (component.children) {
- component.children.forEach(child => traverse(components[child]));
- }
- }
-
- scopeComponentIds.forEach(componentId => traverse(components[componentId]));
-
- if (filterImmuneSlices && filterImmuneSlices.length) {
- chartIds = chartIds.filter(id => !filterImmuneSlices.includes(id));
- }
-
- if (filterImmuneSliceFields) {
- chartIds = chartIds.filter(
- id =>
- !(id.toString() in filterImmuneSliceFields) ||
- !filterImmuneSliceFields[id].includes(filterField),
- );
- }
-
- return chartIds;
-}
diff --git a/superset/assets/src/dashboard/util/getDashboardUrl.js b/superset/assets/src/dashboard/util/getDashboardUrl.js
index 243c8cb..4e2c636 100644
--- a/superset/assets/src/dashboard/util/getDashboardUrl.js
+++ b/superset/assets/src/dashboard/util/getDashboardUrl.js
@@ -18,8 +18,26 @@
*/
/* eslint camelcase: 0 */
+import { getChartIdAndColumnFromFilterKey } from './getDashboardFilterKey';
+
+// filters: { [id_column]: values }
export default function getDashboardUrl(pathname, filters = {}, hash = '') {
- const preselect_filters = encodeURIComponent(JSON.stringify(filters));
+ // convert flattened { [id_column]: values } object
+ // to nested filter object
+ const obj = Object.entries(filters).reduce((map, entry) => {
+ const [filterKey, { values }] = entry;
+ const { chartId, column } = getChartIdAndColumnFromFilterKey(filterKey);
+
+ return {
+ ...map,
+ [chartId]: {
+ ...map[chartId],
+ [column]: values,
+ },
+ };
+ }, {});
+
+ const preselect_filters = encodeURIComponent(JSON.stringify(obj));
const hashSection = hash ? `#${hash}` : '';
return `${pathname}?preselect_filters=${preselect_filters}${hashSection}`;
}
diff --git a/superset/assets/src/dashboard/util/getDashboardUrl.js b/superset/assets/src/dashboard/util/getFilterValuesByFilterId.js
similarity index 58%
copy from superset/assets/src/dashboard/util/getDashboardUrl.js
copy to superset/assets/src/dashboard/util/getFilterValuesByFilterId.js
index 243c8cb..75f9c70 100644
--- a/superset/assets/src/dashboard/util/getDashboardUrl.js
+++ b/superset/assets/src/dashboard/util/getFilterValuesByFilterId.js
@@ -16,10 +16,23 @@
* specific language governing permissions and limitations
* under the License.
*/
-/* eslint camelcase: 0 */
+import { getChartIdAndColumnFromFilterKey } from './getDashboardFilterKey';
-export default function getDashboardUrl(pathname, filters = {}, hash = '') {
- const preselect_filters = encodeURIComponent(JSON.stringify(filters));
- const hashSection = hash ? `#${hash}` : '';
- return `${pathname}?preselect_filters=${preselect_filters}${hashSection}`;
+// input: { [id_column1]: values, [id_column2]: values }
+// output: { column1: values, column2: values }
+export default function getFilterValuesByFilterId({
+ activeFilters = {},
+ filterId,
+}) {
+ return Object.entries(activeFilters).reduce((map, entry) => {
+ const [filterKey, { values }] = entry;
+ const { chartId, column } = getChartIdAndColumnFromFilterKey(filterKey);
+ if (chartId === filterId) {
+ return {
+ ...map,
+ [column]: values,
+ };
+ }
+ return map;
+ }, {});
}
diff --git a/superset/assets/src/dashboard/util/propShapes.jsx b/superset/assets/src/dashboard/util/propShapes.jsx
index b59f41e..96e2e7f 100644
--- a/superset/assets/src/dashboard/util/propShapes.jsx
+++ b/superset/assets/src/dashboard/util/propShapes.jsx
@@ -24,6 +24,7 @@ import headerStyleOptions from './headerStyleOptions';
export const componentShape = PropTypes.shape({
id: PropTypes.string.isRequired,
type: PropTypes.oneOf(Object.values(componentTypes)).isRequired,
+ parents: PropTypes.arrayOf(PropTypes.string),
children: PropTypes.arrayOf(PropTypes.string),
meta: PropTypes.shape({
// Dimensions
@@ -79,10 +80,20 @@ export const filterIndicatorPropShape = PropTypes.shape({
isInstantFilter: PropTypes.bool.isRequired,
label: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
- scope: PropTypes.arrayOf(PropTypes.string),
values: PropTypes.array.isRequired,
});
+export const dashboardFilterPropShape = PropTypes.shape({
+ chartId: PropTypes.number.isRequired,
+ componentId: PropTypes.string.isRequired,
+ directPathToFilter: PropTypes.arrayOf(PropTypes.string).isRequired,
+ isDateFilter: PropTypes.bool.isRequired,
+ isInstantFilter: PropTypes.bool.isRequired,
+ columns: PropTypes.object,
+ labels: PropTypes.object,
+ scopes: PropTypes.object,
+});
+
export const dashboardStatePropShape = PropTypes.shape({
sliceIds: PropTypes.arrayOf(PropTypes.number).isRequired,
expandedSlices: PropTypes.object,
@@ -128,16 +139,3 @@ export const filterScopeSelectorTreeNodePropShape = PropTypes.oneOfType([
PropTypes.shape(parentShape),
leafType,
]);
-
-export const loadStatsPropShape = PropTypes.objectOf(
- PropTypes.shape({
- didLoad: PropTypes.bool.isRequired,
- minQueryStartTime: PropTypes.number.isRequired,
- id: PropTypes.string.isRequired,
- type: PropTypes.string.isRequired,
- parent_id: PropTypes.string,
- parent_type: PropTypes.string,
- index: PropTypes.number.isRequired,
- slice_ids: PropTypes.arrayOf(PropTypes.number).isRequired,
- }),
-);
diff --git a/superset/assets/src/dashboard/util/getDashboardUrl.js b/superset/assets/src/dashboard/util/serializeActiveFilterValues.js
similarity index 57%
copy from superset/assets/src/dashboard/util/getDashboardUrl.js
copy to superset/assets/src/dashboard/util/serializeActiveFilterValues.js
index 243c8cb..6497b10 100644
--- a/superset/assets/src/dashboard/util/getDashboardUrl.js
+++ b/superset/assets/src/dashboard/util/serializeActiveFilterValues.js
@@ -16,10 +16,21 @@
* specific language governing permissions and limitations
* under the License.
*/
-/* eslint camelcase: 0 */
+import { getChartIdAndColumnFromFilterKey } from './getDashboardFilterKey';
-export default function getDashboardUrl(pathname, filters = {}, hash = '') {
- const preselect_filters = encodeURIComponent(JSON.stringify(filters));
- const hashSection = hash ? `#${hash}` : '';
- return `${pathname}?preselect_filters=${preselect_filters}${hashSection}`;
+// input: { [id_column1]: values, [id_column2]: values }
+// output: { id: { column1: values, column2: values } }
+export default function serializeActiveFilterValues(activeFilters) {
+ return Object.entries(activeFilters).reduce((map, entry) => {
+ const [filterKey, { values }] = entry;
+ const { chartId, column } = getChartIdAndColumnFromFilterKey(filterKey);
+ const entryByChartId = {
+ ...map[chartId],
+ [column]: values,
+ };
+ return {
+ ...map,
+ [chartId]: entryByChartId,
+ };
+ }, {});
}
diff --git a/superset/assets/src/dashboard/util/getDashboardUrl.js b/superset/assets/src/dashboard/util/serializeFilterScopes.js
similarity index 67%
copy from superset/assets/src/dashboard/util/getDashboardUrl.js
copy to superset/assets/src/dashboard/util/serializeFilterScopes.js
index 243c8cb..303171f 100644
--- a/superset/assets/src/dashboard/util/getDashboardUrl.js
+++ b/superset/assets/src/dashboard/util/serializeFilterScopes.js
@@ -16,10 +16,19 @@
* specific language governing permissions and limitations
* under the License.
*/
-/* eslint camelcase: 0 */
+export default function serializeFilterScopes(dashboardFilters) {
+ return Object.values(dashboardFilters).reduce((map, { chartId, scopes }) => {
+ const scopesById = Object.keys(scopes).reduce(
+ (scopesByColumn, column) => ({
+ ...scopesByColumn,
+ [column]: scopes[column],
+ }),
+ {},
+ );
-export default function getDashboardUrl(pathname, filters = {}, hash = '') {
- const preselect_filters = encodeURIComponent(JSON.stringify(filters));
- const hashSection = hash ? `#${hash}` : '';
- return `${pathname}?preselect_filters=${preselect_filters}${hashSection}`;
+ return {
+ ...map,
+ [chartId]: scopesById,
+ };
+ }, {});
}
diff --git a/superset/views/core.py b/superset/views/core.py
index 52f488d..706d053 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -1634,12 +1634,18 @@ class Superset(BaseSupersetView):
dashboard.css = data.get("css")
dashboard.dashboard_title = data["dashboard_title"]
- if "filter_immune_slices" not in md:
- md["filter_immune_slices"] = []
if "timed_refresh_immune_slices" not in md:
md["timed_refresh_immune_slices"] = []
- if "filter_immune_slice_fields" not in md:
- md["filter_immune_slice_fields"] = {}
+
+ if data["filter_scopes"]:
+ md.pop("filter_immune_slices", None)
+ md.pop("filter_immune_slice_fields", None)
+ md["filter_scopes"] = json.loads(data.get("filter_scopes", "{}"))
+ else:
+ if "filter_immune_slices" not in md:
+ md["filter_immune_slices"] = []
+ if "filter_immune_slice_fields" not in md:
+ md["filter_immune_slice_fields"] = {}
md["expanded_slices"] = data["expanded_slices"]
md["refresh_frequency"] = data.get("refresh_frequency", 0)
default_filters_data = json.loads(data.get("default_filters", "{}"))