Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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: [],
});
});
});
});
126 changes: 126 additions & 0 deletions superset/assets/src/dashboard/util/getFilterScopeFromNodesTree.js
Original file line number Diff line number Diff line change
@@ -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 {};
}