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/10/15 01:00:42 UTC
[incubator-superset] 01/01: another PR
This is an automated email from the ASF dual-hosted git repository.
graceguo pushed a commit to branch update-dashboard-redux-state
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
commit cc6bc04682848a646f4ba68ac8d8aca169827106
Author: Grace <gr...@airbnb.com>
AuthorDate: Mon Oct 14 17:57:34 2019 -0700
another PR
---
.../util/getFilterScopeFromNodesTree_spec.js | 67 +++++++++++++
.../src/dashboard/actions/dashboardFilters.js | 16 +++
.../src/dashboard/actions/dashboardLayout.js | 4 +
.../assets/src/dashboard/components/Dashboard.jsx | 68 +++++--------
.../components/FilterIndicatorsContainer.jsx | 31 +++---
.../dashboard/components/HeaderActionsDropdown.jsx | 1 +
.../src/dashboard/components/SliceHeader.jsx | 2 -
.../dashboard/components/SliceHeaderControls.jsx | 5 +-
.../components/filterscope/FilterScopeSelector.jsx | 65 ++++++------
.../dashboard/components/gridComponents/Chart.jsx | 16 ++-
superset/assets/src/dashboard/containers/Chart.jsx | 11 ++-
.../assets/src/dashboard/containers/Dashboard.jsx | 7 +-
.../src/dashboard/containers/FilterIndicators.jsx | 6 +-
.../src/dashboard/containers/FilterScope.jsx | 12 +--
.../src/dashboard/reducers/dashboardFilters.js | 57 ++++++++++-
.../src/dashboard/reducers/getInitialState.js | 55 +++++++++--
.../src/dashboard/util/activeDashboardFilters.js | 110 +++++++++++++++++----
.../util/charts/getEffectiveExtraFilters.js | 43 ++------
.../util/charts/getFormDataWithExtraFilters.js | 10 +-
.../src/dashboard/util/getCurrentScopeChartIds.js | 60 -----------
.../src/dashboard/util/getDashboardFilterKey.js | 4 +-
.../assets/src/dashboard/util/getDashboardUrl.js | 20 +++-
.../dashboard/util/getFilterScopeFromNodesTree.js | 77 +++++++++++++++
...rdFilterKey.js => getFilterValuesByFilterId.js} | 25 +++--
superset/assets/src/dashboard/util/propShapes.jsx | 34 +++----
25 files changed, 515 insertions(+), 291 deletions(-)
diff --git a/superset/assets/spec/javascripts/dashboard/util/getFilterScopeFromNodesTree_spec.js b/superset/assets/spec/javascripts/dashboard/util/getFilterScopeFromNodesTree_spec.js
new file mode 100644
index 0000000..85c9b74
--- /dev/null
+++ b/superset/assets/spec/javascripts/dashboard/util/getFilterScopeFromNodesTree_spec.js
@@ -0,0 +1,67 @@
+/**
+ * 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 getFilterScopeFromNodesTree from '../../../../src/dashboard/util/getFilterScopeFromNodesTree';
+
+describe('getFilterScopeFromNodesTree', () => {
+ it('should return empty dashboard', () => {
+ const nodes = [];
+ expect(
+ getFilterScopeFromNodesTree({
+ nodes,
+ checkedChartIds: [],
+ }),
+ ).toEqual({
+ scope: [],
+ immune: [],
+ });
+ });
+
+ it('should return scope for simple grid', () => {
+ const nodes = [
+ {
+ label: 'All dashboard',
+ type: 'ROOT',
+ value: 'ROOT_ID',
+ children: [
+ {
+ value: 104,
+ label: 'Life Expectancy VS Rural %',
+ type: 'CHART',
+ },
+ { value: 105, label: 'Rural Breakdown', type: 'CHART' },
+ {
+ value: 106,
+ label: "World's Pop Growth",
+ type: 'CHART',
+ },
+ ],
+ },
+ ];
+ const checkedChartIds = [104, 106];
+ expect(
+ getFilterScopeFromNodesTree({
+ nodes,
+ checkedChartIds,
+ }),
+ ).toEqual({
+ scope: ['ROOT_ID'],
+ immune: [105],
+ });
+ });
+});
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..0e789ff 100644
--- a/superset/assets/src/dashboard/components/Dashboard.jsx
+++ b/superset/assets/src/dashboard/components/Dashboard.jsx
@@ -28,14 +28,13 @@ 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';
import '../stylesheets/index.less';
+import { getDashboardFilterByKey } from '../util/getDashboardFilterKey';
const propTypes = {
actions: PropTypes.shape({
@@ -50,7 +49,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 +120,31 @@ 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)
) {
- changedFilterKey = key;
+ // changed filter field value?
+ [].push.apply(affectedChartIds, filters[filterKey].scope);
}
+
+ const idSet = new Set(affectedChartIds);
+ this.refreshExcept([...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 +159,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);
- }
+ refreshExcept(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 6c8b6cf..f4aa83c 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,23 @@ 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, name);
const directPathToLabel = directPathToFilter.slice();
directPathToLabel.push(`LABEL-${name}`);
@@ -98,7 +98,6 @@ export default class FilterIndicatorsContainer extends React.PureComponent {
colorCode: dashboardFiltersColorMap[colorMapKey],
componentId,
directPathToFilter: directPathToLabel,
- scope,
isDateFilter,
isInstantFilter,
name,
@@ -113,14 +112,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/HeaderActionsDropdown.jsx b/superset/assets/src/dashboard/components/HeaderActionsDropdown.jsx
index 6fc0182..c407443 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,
};
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 8028271..126fa39 100644
--- a/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx
+++ b/superset/assets/src/dashboard/components/filterscope/FilterScopeSelector.jsx
@@ -25,19 +25,20 @@ import { t } from '@superset-ui/translation';
import getFilterScopeNodesTree from '../../util/getFilterScopeNodesTree';
import getFilterFieldNodesTree from '../../util/getFilterFieldNodesTree';
import getFilterScopeParentNodes from '../../util/getFilterScopeParentNodes';
-import getCurrentScopeChartIds from '../../util/getCurrentScopeChartIds';
+import getFilterScopeFromNodesTree from '../../util/getFilterScopeFromNodesTree';
import getRevertedFilterScope from '../../util/getRevertedFilterScope';
import FilterScopeTree from './FilterScopeTree';
import FilterFieldTree from './FilterFieldTree';
+import { getChartIdsInFilterScope } from '../../util/activeDashboardFilters';
import { getDashboardFilterKey } from '../../util/getDashboardFilterKey';
+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,
};
@@ -45,12 +46,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
@@ -72,7 +68,7 @@ export default class FilterScopeSelector extends React.PureComponent {
// do not expand filter box
const expandedFilterIds = [];
- // display checkbox tree of whole dashboard layout
+ // display whole dashboard layout in tree structure
const nodes = getFilterScopeNodesTree({
components: layout,
isSingleEditMode: true,
@@ -84,6 +80,14 @@ export default class FilterScopeSelector extends React.PureComponent {
const filterScopeByChartId = Object.keys(columns).reduce(
(mapByChartId, columnName) => {
const filterKey = getDashboardFilterKey(chartId, columnName);
+ const chartIdsInFilterScope = getChartIdsInFilterScope({
+ filterScope: dashboardFilters[chartId].scopes[columnName],
+ });
+ // remove filter's id from its scope
+ chartIdsInFilterScope.splice(
+ chartIdsInFilterScope.indexOf(chartId),
+ 1,
+ );
return {
...mapByChartId,
[filterKey]: {
@@ -91,13 +95,7 @@ export default class FilterScopeSelector extends React.PureComponent {
nodes,
// filtered nodes in display if searchText is not empty
nodesFiltered: nodes.slice(),
- checked: getCurrentScopeChartIds({
- scopeComponentIds: ['ROOT_ID'], //dashboardFilters[chartId].scopes[columnName],
- filterField: columnName,
- filterImmuneSlices,
- filterImmuneSliceFields,
- components: layout,
- }),
+ checked: chartIdsInFilterScope.slice(),
expanded,
},
};
@@ -251,11 +249,7 @@ export default class FilterScopeSelector extends React.PureComponent {
}
onToggleEditMode() {
- const {
- activeKey,
- isSingleEditMode,
- checkedFilterFields,
- } = this.state;
+ const { activeKey, isSingleEditMode, checkedFilterFields } = this.state;
const { dashboardFilters } = this.props;
if (isSingleEditMode) {
// single edit => multi edit
@@ -296,16 +290,21 @@ export default class FilterScopeSelector extends React.PureComponent {
onSave() {
const { filterScopeMap } = this.state;
- console.log(
- 'i am current state',
- this.allfilterFields.reduce(
- (map, key) => ({
- ...map,
- [key]: filterScopeMap[key].checked,
- }),
- {},
- ),
+ const currentFiltersState = this.allfilterFields.reduce(
+ (map, key) => ({
+ ...map,
+ [key]: {
+ nodes: filterScopeMap[key].nodes,
+ checked: filterScopeMap[key].checked,
+ },
+ }),
+ {},
+ );
+ console.log('i am current state', currentFiltersState);
+ this.props.updateDashboardFiltersScope(
+ getFilterScopeFromNodesTree(currentFiltersState),
);
+ this.props.setUnsavedChanges(true);
this.props.onCloseModal();
}
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..02472ac 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,7 @@ 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,
// 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 +56,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/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 38a5a08..d816df9 100644
--- a/superset/assets/src/dashboard/containers/FilterScope.jsx
+++ b/superset/assets/src/dashboard/containers/FilterScope.jsx
@@ -19,24 +19,22 @@
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 }, ownProps) {
+function mapStateToProps({ dashboardLayout, dashboardFilters }) {
return {
dashboardFilters,
- filterImmuneSlices: dashboardInfo.metadata.filterImmuneSlices || [],
- filterImmuneSliceFields:
- dashboardInfo.metadata.filterImmuneSliceFields || {},
layout: dashboardLayout.present,
- // closeModal: ownProps.onCloseModal,
};
}
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 1525462..74033b1 100644
--- a/superset/assets/src/dashboard/reducers/dashboardFilters.js
+++ b/superset/assets/src/dashboard/reducers/dashboardFilters.js
@@ -22,11 +22,20 @@ 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 {getDashboardFilterByKey} from "../util/getDashboardFilterKey";
+
+export const DASHBOARD_FILTER_SCOPE_GLOBAL = {
+ scope: [DASHBOARD_ROOT_ID],
+ immune: [],
+};
export const dashboardFilter = {
chartId: 0,
@@ -40,6 +49,8 @@ export const dashboardFilter = {
scopes: {},
};
+let dashboardLayoutComponents = {};
+
export default function dashboardFiltersReducer(dashboardFilters = {}, action) {
const actionHandlers = {
[ADD_FILTER]() {
@@ -57,6 +68,13 @@ export default function dashboardFiltersReducer(dashboardFilters = {}, action) {
directPathToFilter,
columns,
labels,
+ scopes: Object.keys(columns).reduce(
+ (map, column) => ({
+ ...map,
+ [column]: DASHBOARD_FILTER_SCOPE_GLOBAL,
+ }),
+ {},
+ ),
isInstantFilter: !!form_data.instant_filtering,
isDateFilter: Object.keys(columns).includes(TIME_RANGE),
};
@@ -99,11 +117,40 @@ export default function dashboardFiltersReducer(dashboardFilters = {}, action) {
},
};
- if (action.type === REMOVE_FILTER) {
+ if (action.type === UPDATE_LAYOUT_COMPONENTS) {
+ dashboardLayoutComponents = action.components;
+ buildActiveFilters(dashboardFilters, dashboardLayoutComponents);
+ return dashboardFilters;
+ } else if (action.type === UPDATE_DASHBOARD_FILTERS_SCOPE) {
+ const allDashboardFiltersScope = action.scopes;
+ // fulfill filter scope by each filter field
+ const updatedDashboardFilters = Object.entries(
+ allDashboardFiltersScope,
+ ).reduce(({ map, entry }) => {
+ const [filterKey, { scope, immune }] = entry;
+ const [chartId, column] = getDashboardFilterByKey(filterKey);
+ const scopes = {
+ ...map[chartId].scopes,
+ [column]: {
+ scope,
+ immune,
+ },
+ };
+ return {
+ ...map,
+ [chartId]: {
+ ...map[chartId],
+ scopes,
+ },
+ };
+ }, dashboardFilters);
+
+ return updatedDashboardFilters;
+ } else if (action.type === REMOVE_FILTER) {
const { chartId } = action;
const { [chartId]: deletedFilter, ...updatedFilters } = dashboardFilters;
- buildActiveFilters(updatedFilters);
- buildFilterColorMap(updatedFilters);
+ buildActiveFilters(updatedFilters, dashboardLayoutComponents);
+ buildFilterColorMap(updatedFilters, dashboardLayoutComponents);
return updatedFilters;
} else if (action.type in actionHandlers) {
@@ -113,8 +160,8 @@ export default function dashboardFiltersReducer(dashboardFilters = {}, action) {
dashboardFilters[action.chartId],
),
};
- buildActiveFilters(updatedFilters);
- buildFilterColorMap(updatedFilters);
+ buildActiveFilters(updatedFilters, dashboardLayoutComponents);
+ buildFilterColorMap(updatedFilters, dashboardLayoutComponents);
return updatedFilters;
}
diff --git a/superset/assets/src/dashboard/reducers/getInitialState.js b/superset/assets/src/dashboard/reducers/getInitialState.js
index 185a8b0..9939f15 100644
--- a/superset/assets/src/dashboard/reducers/getInitialState.js
+++ b/superset/assets/src/dashboard/reducers/getInitialState.js
@@ -22,7 +22,10 @@ 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,20 @@ export default function(bootstrapData) {
let newSlicesContainer;
let newSlicesContainerWidth = 0;
+ const filterImmuneSliceFields = dashboard.metadata.filter_immune_slice_fields;
+ // {
+ // '105': ['country_name'],
+ // };
+ const filterImmuneSlices = dashboard.metadata.filter_immune_slices;
+ // [104];
+ const filterScopes = dashboard.metadata.filter_scopes || {};
+ // '108': {
+ // __time_range: {
+ // scope: ['TAB-oMbymVL_s'],
+ // immune: [105],
+ // },
+ // }
+
const chartQueries = {};
const dashboardFilters = {};
const slices = {};
@@ -173,6 +190,34 @@ export default function(bootstrapData) {
});
}
+ // backward compatible:
+ // merge scoped filter settings with global immune settings
+ const scopesByColumnName = Object.keys(columns).reduce(
+ (map, column) => {
+ const { scope, immune } = {
+ ...DASHBOARD_FILTER_SCOPE_GLOBAL,
+ ...filterScopes[key],
+ };
+ const immuneChartIds = new Set(filterImmuneSlices.slice());
+ if (Object.keys(filterImmuneSliceFields).length) {
+ 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 +229,7 @@ export default function(bootstrapData) {
directPathToFilter,
columns,
labels,
+ scopes: scopesByColumnName,
isInstantFilter: !!slice.form_data.instant_filtering,
isDateFilter: Object.keys(columns).includes(TIME_RANGE),
};
@@ -198,8 +244,8 @@ export default function(bootstrapData) {
layout[layoutId].meta.sliceName = slice.slice_name;
}
});
- buildActiveFilters(dashboardFilters);
- buildFilterColorMap(dashboardFilters);
+ buildActiveFilters(dashboardFilters, layout);
+ buildFilterColorMap(dashboardFilters, layout);
// store the header as a layout component so we can undo/redo changes
layout[DASHBOARD_HEADER_ID] = {
@@ -233,9 +279,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 ea8e4e5..d8fc364 100644
--- a/superset/assets/src/dashboard/util/activeDashboardFilters.js
+++ b/superset/assets/src/dashboard/util/activeDashboardFilters.js
@@ -16,49 +16,121 @@
* specific language governing permissions and limitations
* under the License.
*/
-let activeFilters = {};
+import {
+ getDashboardFilterByKey,
+ getDashboardFilterKey,
+} from './getDashboardFilterKey';
+import { CHART_TYPE } from '../util/componentTypes';
+
let allFilterIds = [];
+let activeFilters = {};
+let appliedFilterValuesByChart = {};
+let allComponents = {};
+// output: { [id_column]: { values, scope } }
export function getActiveFilters() {
return activeFilters;
}
-// currently filterbox is a chart,
+// currently filter_box 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,
+// after we make filter_box a dashboard build-in component,
// will not need this check anymore
export function isFilterBox(chartId) {
return allFilterIds.includes(chartId);
}
-export function getAllFilterIds() {
- return allFilterIds;
+// 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] = getDashboardFilterByKey(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 = {}) {
- allFilterIds = Object.values(allDashboardFilters).map(filter => filter.chartId);
+// non-empty filters list in dashboardFilters,
+// it contains selected values and filter scope
+// values: array of selected values
+// scope: array of chartIds
+export function buildActiveFilters(allDashboardFilters = {}, components = {}) {
+ allFilterIds = Object.values(allDashboardFilters).map(
+ filter => filter.chartId,
+ );
+ // clear cache
+ allComponents = components;
+ appliedFilterValuesByChart = {};
activeFilters = Object.values(allDashboardFilters).reduce(
(result, filter) => {
- const { chartId, columns } = filter;
+ const { chartId, columns, scopes } = filter;
+ const nonEmptyFilters = {};
- Object.keys(columns).forEach(key => {
+ Object.keys(columns).forEach(column => {
if (
- Array.isArray(columns[key])
- ? columns[key].length
- : columns[key] !== undefined
+ Array.isArray(columns[column])
+ ? columns[column].length
+ : columns[column] !== undefined
) {
- /* eslint-disable no-param-reassign */
- result[chartId] = {
- ...result[chartId],
- [key]: columns[key],
+ const scope = getChartIdsInFilterScope({
+ filterScope: scopes[column],
+ });
+ // remove filter itself
+ if (scope.length) {
+ scope.splice(scope.indexOf(chartId), 1);
+ }
+ nonEmptyFilters[getDashboardFilterKey(chartId, column)] = {
+ values: columns[column],
+ scope,
};
}
});
- return result;
+ 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 ca73256..0000000
--- a/superset/assets/src/dashboard/util/getCurrentScopeChartIds.js
+++ /dev/null
@@ -1,60 +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,
-}) {
- 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]));
- }
- }
-
- let chartIds = [];
- 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/getDashboardFilterKey.js b/superset/assets/src/dashboard/util/getDashboardFilterKey.js
index ffb4ab3..b1e560f 100644
--- a/superset/assets/src/dashboard/util/getDashboardFilterKey.js
+++ b/superset/assets/src/dashboard/util/getDashboardFilterKey.js
@@ -22,6 +22,6 @@ export function getDashboardFilterKey(chartId, column) {
export function getDashboardFilterByKey(key) {
const [chartId, ...parts] = key.split('_');
- const columnName = parts.slice().join('_');
- return [chartId, columnName];
+ const column = parts.slice().join('_');
+ return [parseInt(chartId, 10), column];
}
diff --git a/superset/assets/src/dashboard/util/getDashboardUrl.js b/superset/assets/src/dashboard/util/getDashboardUrl.js
index 243c8cb..0d9a406 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 { getDashboardFilterByKey } 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] = getDashboardFilterByKey(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/getFilterScopeFromNodesTree.js b/superset/assets/src/dashboard/util/getFilterScopeFromNodesTree.js
new file mode 100644
index 0000000..b9a03fc
--- /dev/null
+++ b/superset/assets/src/dashboard/util/getFilterScopeFromNodesTree.js
@@ -0,0 +1,77 @@
+/**
+ * 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, TAB_TYPE } from './componentTypes';
+
+// input [{value, label, children: []}],
+// output {
+// filterKey1: { scope: [tab1, tab2], immune: [chart1, chart2] }
+// filterKey2: { scope: [tab1, tab2], immune: [chart1, chart2] }
+// }
+export default function getFilterScopeFromNodesTree({
+ nodes = [],
+ checkedChartIds = [],
+}) {
+ function traverse({ currentNode, parent = '', scope, immune }) {
+ if (!currentNode) {
+ return;
+ }
+
+ const { value: nodeValue, children } = currentNode;
+ // any chart type child is checked?
+ const chartChildren = children.filter(({ type }) => type === CHART_TYPE);
+ if (chartChildren.some(({ value }) => checkedChartIds.includes(value))) {
+ if (!scope.has(parent)) {
+ scope.add(nodeValue);
+ }
+ children.forEach(({ value }) => {
+ if (!checkedChartIds.includes(value)) {
+ immune.push(value);
+ }
+ });
+ }
+
+ const tabChildren = children.filter(({ type }) => type === TAB_TYPE);
+ tabChildren.forEach(child => {
+ traverse({
+ currentNode: child,
+ parent: nodeValue,
+ scope,
+ immune,
+ });
+ });
+ }
+
+ const scope = new Set();
+ const immune = [];
+ if (nodes && nodes.length) {
+ nodes.forEach(node => {
+ traverse({
+ currentNode: node,
+ parent: '',
+ scope,
+ immune,
+ });
+ });
+ }
+
+ return {
+ scope: [...scope],
+ immune,
+ };
+}
diff --git a/superset/assets/src/dashboard/util/getDashboardFilterKey.js b/superset/assets/src/dashboard/util/getFilterValuesByFilterId.js
similarity index 58%
copy from superset/assets/src/dashboard/util/getDashboardFilterKey.js
copy to superset/assets/src/dashboard/util/getFilterValuesByFilterId.js
index ffb4ab3..66ba225 100644
--- a/superset/assets/src/dashboard/util/getDashboardFilterKey.js
+++ b/superset/assets/src/dashboard/util/getFilterValuesByFilterId.js
@@ -16,12 +16,23 @@
* specific language governing permissions and limitations
* under the License.
*/
-export function getDashboardFilterKey(chartId, column) {
- return `${chartId}_${column}`;
-}
+import { getDashboardFilterByKey } from './getDashboardFilterKey';
-export function getDashboardFilterByKey(key) {
- const [chartId, ...parts] = key.split('_');
- const columnName = parts.slice().join('_');
- return [chartId, columnName];
+// 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] = getDashboardFilterByKey(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 be04b81..2c3aa11 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,14 +80,24 @@ 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 filterScopeNodePropShape = PropTypes.shape({
- children: PropTypes.arrayOf(filterScopeNodePropShape),
- label: PropTypes.string.isRequired,
- value: PropTypes.string.isRequired,
+// export const activeFilterPropShape = PropTypes.shape({
+// filterKey: PropTypes.string.isRequired,
+// values: PropTypes.array.isRequired,
+// scope: 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({
@@ -110,16 +121,3 @@ export const dashboardInfoPropShape = PropTypes.shape({
common: PropTypes.object,
userId: PropTypes.string.isRequired,
});
-
-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,
- }),
-);