This is an automated email from the ASF dual-hosted git repository.
graceguo pushed a commit to branch feature--dashboard-scoped-filter
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
The following commit(s) were added to
refs/heads/feature--dashboard-scoped-filter by this push:
new b1aaa43 [WIP][dashboard scoped filter] part 2: add algorithm to
convert checked ids to scope object (#8564)
b1aaa43 is described below
commit b1aaa43a94be9638e202080204f6305f351040ce
Author: Grace Guo <[email protected]>
AuthorDate: Wed Nov 13 14:17:57 2019 -0800
[WIP][dashboard scoped filter] part 2: add algorithm to convert checked ids
to scope object (#8564)
* convert ids to scope object
* use lodash helpers to make code readable
---
.../util/getFilterScopeFromNodesTree_spec.js | 216 +++++++++++++++++++++
.../dashboard/util/getFilterScopeFromNodesTree.js | 126 ++++++++++++
2 files changed, 342 insertions(+)
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..067e193
--- /dev/null
+++
b/superset/assets/spec/javascripts/dashboard/util/getFilterScopeFromNodesTree_spec.js
@@ -0,0 +1,216 @@
+/**
+ * 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 scope', () => {
+ const nodes = [];
+ expect(
+ getFilterScopeFromNodesTree({
+ filterKey: '107_region',
+ nodes,
+ checkedChartIds: [],
+ }),
+ ).toEqual({});
+ });
+
+ 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',
+ },
+ {
+ label: 'Time Filter',
+ showCheckbox: false,
+ type: 'CHART',
+ value: 108,
+ },
+ ],
+ },
+ ];
+ const checkedChartIds = [104, 106];
+ expect(
+ getFilterScopeFromNodesTree({
+ filterKey: '108___time_range',
+ nodes,
+ checkedChartIds,
+ }),
+ ).toEqual({
+ scope: ['ROOT_ID'],
+ immune: [105],
+ });
+ });
+
+ describe('should return scope for tabbed dashboard', () => {
+ const nodes = [
+ {
+ label: 'All dashboard',
+ type: 'ROOT',
+ value: 'ROOT_ID',
+ children: [
+ {
+ label: 'Tab 1',
+ type: 'TAB',
+ value: 'TAB-Rb5aaqKWgG',
+ children: [
+ {
+ label: 'Geo Filters',
+ showCheckbox: false,
+ type: 'CHART',
+ value: 107,
+ },
+ {
+ label: "World's Pop Growth",
+ showCheckbox: true,
+ type: 'CHART',
+ value: 106,
+ },
+ ],
+ },
+ {
+ label: 'Tab 2',
+ type: 'TAB',
+ value: 'TAB-w5Fp904Rs',
+ children: [
+ {
+ label: 'Time Filter',
+ showCheckbox: true,
+ type: 'CHART',
+ value: 108,
+ },
+ {
+ label: 'Life Expectancy VS Rural %',
+ showCheckbox: true,
+ type: 'CHART',
+ value: 104,
+ },
+ {
+ label: 'Row Tab 1',
+ type: 'TAB',
+ value: 'TAB-E4mJaZ-uQM',
+ children: [
+ {
+ value: 105,
+ label: 'Rural Breakdown',
+ type: 'CHART',
+ showCheckbox: true,
+ },
+ {
+ value: 103,
+ label: '% Rural',
+ type: 'CHART',
+ showCheckbox: true,
+ },
+ ],
+ },
+ {
+ value: 'TAB-rLYu-Cryu',
+ label: 'New Tab',
+ type: 'TAB',
+ children: [
+ {
+ value: 102,
+ label: 'Most Populated Countries',
+ type: 'CHART',
+ showCheckbox: true,
+ },
+ {
+ value: 101,
+ label: "World's Population",
+ type: 'CHART',
+ showCheckbox: true,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ];
+
+ it('root level tab scope', () => {
+ const checkedChartIds = [106];
+ expect(
+ getFilterScopeFromNodesTree({
+ filterKey: '107_region',
+ nodes,
+ checkedChartIds,
+ }),
+ ).toEqual({
+ scope: ['TAB-Rb5aaqKWgG'],
+ immune: [],
+ });
+ });
+
+ it('global scope', () => {
+ const checkedChartIds = [106, 104, 101, 102, 103, 105];
+ expect(
+ getFilterScopeFromNodesTree({
+ filterKey: '107_country_name',
+ nodes,
+ checkedChartIds,
+ }),
+ ).toEqual({
+ scope: ['ROOT_ID'],
+ immune: [108],
+ });
+ });
+
+ it('row level tab scope', () => {
+ const checkedChartIds = [103, 105];
+ expect(
+ getFilterScopeFromNodesTree({
+ filterKey: '108___time_range',
+ nodes,
+ checkedChartIds,
+ }),
+ ).toEqual({
+ scope: ['TAB-E4mJaZ-uQM'],
+ immune: [],
+ });
+ });
+
+ it('mixed row level and root level scope', () => {
+ const checkedChartIds = [103, 105, 106];
+ expect(
+ getFilterScopeFromNodesTree({
+ filterKey: '107_region',
+ nodes,
+ checkedChartIds,
+ }),
+ ).toEqual({
+ scope: ['TAB-Rb5aaqKWgG', 'TAB-E4mJaZ-uQM'],
+ immune: [],
+ });
+ });
+ });
+});
diff --git a/superset/assets/src/dashboard/util/getFilterScopeFromNodesTree.js
b/superset/assets/src/dashboard/util/getFilterScopeFromNodesTree.js
new file mode 100644
index 0000000..2eb97a4
--- /dev/null
+++ b/superset/assets/src/dashboard/util/getFilterScopeFromNodesTree.js
@@ -0,0 +1,126 @@
+/**
+ * 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 { flow, keyBy, mapValues } from 'lodash/fp';
+import { flatMap, isEmpty } from 'lodash';
+
+import { CHART_TYPE, TAB_TYPE } from './componentTypes';
+import { getChartIdAndColumnFromFilterKey } from './getDashboardFilterKey';
+
+function getTabChildrenScope({
+ tabScopes,
+ parentNodeValue,
+ forceAggregate = false,
+}) {
+ // if all sub-tabs are in scope, or forceAggregate = true
+ // aggregate scope to parentNodeValue
+ if (
+ forceAggregate ||
+ Object.entries(tabScopes).every(
+ ([key, { scope }]) => scope && scope.length && key === scope[0],
+ )
+ ) {
+ return {
+ scope: [parentNodeValue],
+ immune: flatMap(Object.values(tabScopes), ({ immune }) => immune),
+ };
+ }
+
+ const componentsInScope = Object.values(tabScopes).filter(
+ ({ scope }) => scope && scope.length,
+ );
+ return {
+ scope: flatMap(componentsInScope, ({ scope }) => scope),
+ immune: flatMap(componentsInScope, ({ immune }) => immune),
+ };
+}
+
+function traverse({ currentNode = {}, filterId, checkedChartIds = [] }) {
+ if (!currentNode) {
+ return {};
+ }
+
+ const { value: currentValue, children } = currentNode;
+ const chartChildren = children.filter(({ type }) => type === CHART_TYPE);
+ const tabChildren = children.filter(({ type }) => type === TAB_TYPE);
+
+ const chartsImmune = chartChildren
+ .filter(
+ ({ value }) => filterId !== value && !checkedChartIds.includes(value),
+ )
+ .map(({ value }) => value);
+ const tabScopes = flow(
+ keyBy(child => child.value),
+ mapValues(child =>
+ traverse({
+ currentNode: child,
+ filterId,
+ checkedChartIds,
+ }),
+ ),
+ )(tabChildren);
+
+ // if any chart type child is in scope,
+ // no matter has tab children or not, current node should be scope
+ if (
+ !isEmpty(chartChildren) &&
+ chartChildren.some(({ value }) => checkedChartIds.includes(value))
+ ) {
+ if (isEmpty(tabChildren)) {
+ return { scope: [currentValue], immune: chartsImmune };
+ }
+
+ const { scope, immune } = getTabChildrenScope({
+ tabScopes,
+ parentNodeValue: currentValue,
+ forceAggregate: true,
+ });
+ return {
+ scope,
+ immune: chartsImmune.concat(immune),
+ };
+ }
+
+ // has tab children but only some sub-tab in scope
+ if (tabChildren.length) {
+ return getTabChildrenScope({ tabScopes, parentNodeValue: currentValue });
+ }
+
+ // no tab children and no chart children in scope
+ return {
+ scope: [],
+ immune: chartsImmune,
+ };
+}
+
+export default function getFilterScopeFromNodesTree({
+ filterKey,
+ nodes = [],
+ checkedChartIds = [],
+}) {
+ if (nodes.length) {
+ const { chartId } = getChartIdAndColumnFromFilterKey(filterKey);
+ return traverse({
+ currentNode: nodes[0],
+ filterId: chartId,
+ checkedChartIds,
+ });
+ }
+
+ return {};
+}