mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
fix(native-filters): prevent infinite recursion in filter scope tree traversal (#39355)
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
import { Layout, LayoutItem, Charts } from 'src/dashboard/types';
|
||||
import { VizType } from '@superset-ui/core';
|
||||
import { buildTree } from './utils';
|
||||
import { buildTree, getTreeCheckedItems } from './utils';
|
||||
import type { TreeItem } from './types';
|
||||
|
||||
// The types defined for Layout and sub elements is not compatible with the data we get back fro a real dashboard layout
|
||||
@@ -18048,6 +18048,80 @@ describe('Ensure buildTree does not throw runtime errors when encountering an in
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test('Does not infinitely recurse when layout contains a cycle (nested tabs circular reference)', () => {
|
||||
// Simulate a corrupted layout where TAB-A → TAB-B → TAB-A (cycle)
|
||||
const circularLayout = {
|
||||
ROOT_ID: {
|
||||
id: 'ROOT_ID',
|
||||
type: 'ROOT',
|
||||
children: ['TAB-A'],
|
||||
parents: [],
|
||||
},
|
||||
'TAB-A': {
|
||||
id: 'TAB-A',
|
||||
type: 'TAB',
|
||||
children: ['TAB-B'],
|
||||
parents: ['ROOT_ID'],
|
||||
meta: { text: 'Tab A' },
|
||||
},
|
||||
'TAB-B': {
|
||||
id: 'TAB-B',
|
||||
type: 'TAB',
|
||||
// Points back to TAB-A, creating a cycle
|
||||
children: ['TAB-A'],
|
||||
parents: ['ROOT_ID', 'TAB-A'],
|
||||
meta: { text: 'Tab B' },
|
||||
},
|
||||
};
|
||||
|
||||
const rootNode = circularLayout.ROOT_ID;
|
||||
const rootTreeItem: TreeItem = { key: 'ROOT_ID', title: 'Root', children: [], nodeType: 'TAB' };
|
||||
|
||||
expect(() => {
|
||||
buildTree(
|
||||
rootNode as unknown as LayoutItem,
|
||||
rootTreeItem,
|
||||
circularLayout as unknown as Layout,
|
||||
{} as Charts,
|
||||
['ROOT_ID', 'TAB-A', 'TAB-B'],
|
||||
[],
|
||||
() => 'title',
|
||||
);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test('getTreeCheckedItems does not infinitely recurse when scope rootPath creates a cycle', () => {
|
||||
// Simulate a corrupted layout where ROW-A → ROW-B → ROW-A (cycle via children)
|
||||
const circularLayout = {
|
||||
ROOT_ID: {
|
||||
id: 'ROOT_ID',
|
||||
type: 'ROOT',
|
||||
children: ['ROW-A'],
|
||||
parents: [],
|
||||
},
|
||||
'ROW-A': {
|
||||
id: 'ROW-A',
|
||||
type: 'ROW',
|
||||
children: ['ROW-B'],
|
||||
parents: ['ROOT_ID'],
|
||||
},
|
||||
'ROW-B': {
|
||||
id: 'ROW-B',
|
||||
type: 'ROW',
|
||||
// Points back to ROW-A, creating a cycle
|
||||
children: ['ROW-A'],
|
||||
parents: ['ROOT_ID', 'ROW-A'],
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
getTreeCheckedItems(
|
||||
{ rootPath: ['ROOT_ID'], excluded: [] },
|
||||
circularLayout as unknown as Layout,
|
||||
);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test('Avoids runtime error with invalid inputs', () => {
|
||||
expect(() => {
|
||||
buildTree(
|
||||
|
||||
@@ -100,6 +100,7 @@ export const buildTree = (
|
||||
initiallyExcludedCharts: number[],
|
||||
buildTreeLeafTitle: BuildTreeLeafTitle,
|
||||
sliceEntities?: Record<number, Slice>,
|
||||
visited: Set<string> = new Set(),
|
||||
) => {
|
||||
if (!node) {
|
||||
return;
|
||||
@@ -154,6 +155,10 @@ export const buildTree = (
|
||||
|
||||
if (node.type !== CHART_TYPE) {
|
||||
node?.children?.forEach?.(child => {
|
||||
if (visited.has(child)) {
|
||||
return;
|
||||
}
|
||||
visited.add(child);
|
||||
const childNode = layout?.[child];
|
||||
if (childNode) {
|
||||
buildTree(
|
||||
@@ -165,6 +170,7 @@ export const buildTree = (
|
||||
initiallyExcludedCharts,
|
||||
buildTreeLeafTitle,
|
||||
sliceEntities,
|
||||
visited,
|
||||
);
|
||||
} else {
|
||||
logging.warn(
|
||||
@@ -192,13 +198,19 @@ const checkTreeItem = (
|
||||
layout: Layout,
|
||||
items: string[],
|
||||
excluded: number[],
|
||||
visited: Set<string> = new Set(),
|
||||
) => {
|
||||
items.forEach(item => {
|
||||
if (visited.has(item)) {
|
||||
return;
|
||||
}
|
||||
visited.add(item);
|
||||
checkTreeItem(
|
||||
checkedItems,
|
||||
layout,
|
||||
addInvisibleParents(layout, item),
|
||||
excluded,
|
||||
visited,
|
||||
);
|
||||
if (
|
||||
layout[item]?.type === CHART_TYPE &&
|
||||
|
||||
Reference in New Issue
Block a user