This is an automated email from the ASF dual-hosted git repository.
kgabryje pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 86c8fa5cd7b fix: Badge count in folders editor (#38100)
86c8fa5cd7b is described below
commit 86c8fa5cd7b6946400d5ea7efc18fe3f0d52f064
Author: Kamil Gabryjelski <[email protected]>
AuthorDate: Thu Feb 19 18:45:04 2026 +0100
fix: Badge count in folders editor (#38100)
---
.../Datasource/FoldersEditor/constants.ts | 3 ++
.../Datasource/FoldersEditor/treeUtils.test.ts | 59 ++++++++++++++++++++++
.../Datasource/FoldersEditor/treeUtils.ts | 19 +++++++
.../DatasourceEditor/DatasourceEditor.tsx | 17 +++++--
4 files changed, 95 insertions(+), 3 deletions(-)
diff --git
a/superset-frontend/src/components/Datasource/FoldersEditor/constants.ts
b/superset-frontend/src/components/Datasource/FoldersEditor/constants.ts
index fc9f4d189f8..913ee905fb6 100644
--- a/superset-frontend/src/components/Datasource/FoldersEditor/constants.ts
+++ b/superset-frontend/src/components/Datasource/FoldersEditor/constants.ts
@@ -29,6 +29,9 @@ export const DEFAULT_METRICS_FOLDER_UUID =
export const DEFAULT_COLUMNS_FOLDER_UUID =
'83a7ae8f-2e8a-4f2b-a8cb-ebaebef95b9b';
+// Number of default folders (Metrics, Columns)
+export const DEFAULT_FOLDERS_COUNT = 2;
+
// Drag & drop constants
export const DRAG_INDENTATION_WIDTH = 64;
export const MAX_DEPTH = 3;
diff --git
a/superset-frontend/src/components/Datasource/FoldersEditor/treeUtils.test.ts
b/superset-frontend/src/components/Datasource/FoldersEditor/treeUtils.test.ts
index c6fdfa444b7..b17b7b727fe 100644
---
a/superset-frontend/src/components/Datasource/FoldersEditor/treeUtils.test.ts
+++
b/superset-frontend/src/components/Datasource/FoldersEditor/treeUtils.test.ts
@@ -28,8 +28,10 @@ import {
removeChildrenOf,
serializeForAPI,
getProjection,
+ countAllFolders,
} from './treeUtils';
import { FoldersEditorItemType } from '../types';
+import { DatasourceFolder } from
'src/explore/components/DatasourcePanel/types';
const createMetricItem = (uuid: string, name: string): TreeItem => ({
uuid,
@@ -667,3 +669,60 @@ test('getProjection nests item under folder when dragging
down with offset', ()
expect(projection!.depth).toBe(1);
expect(projection!.parentId).toBe('folder1');
});
+
+test('countAllFolders returns 0 for empty array', () => {
+ expect(countAllFolders([])).toBe(0);
+});
+
+test('countAllFolders counts flat folders', () => {
+ const folders: DatasourceFolder[] = [
+ createFolderItem('f1', 'Metrics', [createMetricItem('m1', 'Metric 1')]),
+ createFolderItem('f2', 'Columns', [createColumnItem('c1', 'Column 1')]),
+ ] as DatasourceFolder[];
+
+ expect(countAllFolders(folders)).toBe(2);
+});
+
+test('countAllFolders counts nested folders recursively', () => {
+ const folders: DatasourceFolder[] = [
+ createFolderItem('metrics', 'Metrics', [
+ createMetricItem('m1', 'Metric 1'),
+ ]),
+ createFolderItem('columns', 'Columns', [
+ createColumnItem('c1', 'Column 1'),
+ ]),
+ createFolderItem('custom', 'Custom Folder', [
+ createFolderItem('nested', 'Nested Folder', [
+ createMetricItem('m2', 'Metric 2'),
+ ]),
+ ]),
+ ] as DatasourceFolder[];
+
+ expect(countAllFolders(folders)).toBe(4);
+});
+
+test('countAllFolders counts deeply nested folders', () => {
+ const folders: DatasourceFolder[] = [
+ createFolderItem('level0', 'Level 0', [
+ createFolderItem('level1', 'Level 1', [
+ createFolderItem('level2', 'Level 2', [
+ createMetricItem('m1', 'Metric 1'),
+ ]),
+ ]),
+ ]),
+ ] as DatasourceFolder[];
+
+ expect(countAllFolders(folders)).toBe(3);
+});
+
+test('countAllFolders ignores non-folder children', () => {
+ const folders: DatasourceFolder[] = [
+ createFolderItem('f1', 'Folder', [
+ createMetricItem('m1', 'Metric 1'),
+ createColumnItem('c1', 'Column 1'),
+ createMetricItem('m2', 'Metric 2'),
+ ]),
+ ] as DatasourceFolder[];
+
+ expect(countAllFolders(folders)).toBe(1);
+});
diff --git
a/superset-frontend/src/components/Datasource/FoldersEditor/treeUtils.ts
b/superset-frontend/src/components/Datasource/FoldersEditor/treeUtils.ts
index 10dec0d28ab..c35817606d9 100644
--- a/superset-frontend/src/components/Datasource/FoldersEditor/treeUtils.ts
+++ b/superset-frontend/src/components/Datasource/FoldersEditor/treeUtils.ts
@@ -330,3 +330,22 @@ export function serializeForAPI(items: TreeItem[]):
DatasourceFolder[] {
})
.filter((folder): folder is DatasourceFolder => folder !== null);
}
+
+/**
+ * Recursively counts all folders in a DatasourceFolder array,
+ * including nested sub-folders within children.
+ */
+export function countAllFolders(folders: DatasourceFolder[]): number {
+ let count = 0;
+ for (const folder of folders) {
+ count += 1;
+ if (folder.children) {
+ for (const child of folder.children) {
+ if ('children' in child) {
+ count += countAllFolders([child as DatasourceFolder]);
+ }
+ }
+ }
+ }
+ return count;
+}
diff --git
a/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx
b/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx
index c7819929074..fae377266a6 100644
---
a/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx
+++
b/superset-frontend/src/components/Datasource/components/DatasourceEditor/DatasourceEditor.tsx
@@ -91,9 +91,11 @@ import { fetchSyncedColumns, updateColumns } from
'../../utils';
import DatasetUsageTab from './components/DatasetUsageTab';
import {
DEFAULT_COLUMNS_FOLDER_UUID,
+ DEFAULT_FOLDERS_COUNT,
DEFAULT_METRICS_FOLDER_UUID,
} from '../../FoldersEditor/constants';
import { validateFolders } from '../../FoldersEditor/folderValidation';
+import { countAllFolders } from '../../FoldersEditor/treeUtils';
import FoldersEditor from '../../FoldersEditor';
import { DatasourceFolder } from
'src/explore/components/DatasourcePanel/types';
@@ -266,6 +268,7 @@ interface DatasourceEditorState {
databaseColumns: Column[];
calculatedColumns: Column[];
folders: DatasourceFolder[];
+ folderCount: number;
metadataLoading: boolean;
activeTabKey: string;
datasourceType: string;
@@ -284,6 +287,7 @@ interface AbortControllers {
interface CollectionTabTitleProps {
title: string;
collection?: unknown[] | { length: number };
+ count?: number;
}
interface ColumnCollectionTableProps {
@@ -458,6 +462,7 @@ DATASOURCE_TYPES_ARR.forEach(o => {
function CollectionTabTitle({
title,
collection,
+ count,
}: CollectionTabTitleProps): JSX.Element {
return (
<div
@@ -465,7 +470,10 @@ function CollectionTabTitle({
data-test={`collection-tab-${title}`}
>
{title}{' '}
- <StyledBadge count={collection ? collection.length : 0} showZero />
+ <StyledBadge
+ count={count ?? (collection ? collection.length : 0)}
+ showZero
+ />
</div>
);
}
@@ -905,6 +913,8 @@ class DatasourceEditor extends PureComponent<
col => !!col.expression,
),
folders: props.datasource.folders || [],
+ folderCount:
+ countAllFolders(props.datasource.folders || []) +
DEFAULT_FOLDERS_COUNT,
metadataLoading: false,
activeTabKey: TABS_KEYS.SOURCE,
datasourceType: props.datasource.sql
@@ -988,13 +998,14 @@ class DatasourceEditor extends PureComponent<
}
handleFoldersChange(folders: DatasourceFolder[]) {
+ const folderCount = countAllFolders(folders);
const userMadeFolders = folders.filter(
f =>
f.uuid !== DEFAULT_METRICS_FOLDER_UUID &&
f.uuid !== DEFAULT_COLUMNS_FOLDER_UUID &&
(f.children?.length ?? 0) > 0,
);
- this.setState({ folders: userMadeFolders }, () => {
+ this.setState({ folders: userMadeFolders, folderCount }, () => {
this.onDatasourceChange({
...this.state.datasource,
folders: userMadeFolders,
@@ -2405,7 +2416,7 @@ class DatasourceEditor extends PureComponent<
key: TABS_KEYS.FOLDERS,
label: (
<CollectionTabTitle
- collection={this.state.folders}
+ count={this.state.folderCount}
title={t('Folders')}
/>
),