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 11dfda11d31 fix(folders): expand collapsed folders on Select All and
add selection counter (#38270)
11dfda11d31 is described below
commit 11dfda11d31d461ed1bede7cb42b8ab100a03047
Author: Kamil Gabryjelski <[email protected]>
AuthorDate: Fri Feb 27 07:28:07 2026 +0100
fix(folders): expand collapsed folders on Select All and add selection
counter (#38270)
Co-authored-by: Claude Opus 4.6 <[email protected]>
---
.../FoldersEditor/FoldersEditor.test.tsx | 64 +++++++++++
.../components/FoldersToolbarComponent.tsx | 118 ++++++++++++++++-----
.../components/Datasource/FoldersEditor/index.tsx | 36 ++++++-
.../components/Datasource/FoldersEditor/styles.tsx | 14 +++
4 files changed, 204 insertions(+), 28 deletions(-)
diff --git
a/superset-frontend/src/components/Datasource/FoldersEditor/FoldersEditor.test.tsx
b/superset-frontend/src/components/Datasource/FoldersEditor/FoldersEditor.test.tsx
index fbf3080cd14..4808f03a6dc 100644
---
a/superset-frontend/src/components/Datasource/FoldersEditor/FoldersEditor.test.tsx
+++
b/superset-frontend/src/components/Datasource/FoldersEditor/FoldersEditor.test.tsx
@@ -112,6 +112,11 @@ const mockFolders: DatasourceFolder[] = [
name: 'Metrics',
children: [
{ type: FoldersEditorItemType.Metric, uuid: 'metric1', name: 'Count' },
+ {
+ type: FoldersEditorItemType.Metric,
+ uuid: 'metric2',
+ name: 'Sum Revenue',
+ },
],
},
{
@@ -120,6 +125,7 @@ const mockFolders: DatasourceFolder[] = [
name: 'Columns',
children: [
{ type: FoldersEditorItemType.Column, uuid: 'col1', name: 'ID' },
+ { type: FoldersEditorItemType.Column, uuid: 'col2', name: 'name' },
],
},
];
@@ -200,6 +206,31 @@ test('selects all items when Select all is clicked', async
() => {
});
});
+test('shows item count and updates to selection count', async () => {
+ renderEditor(<FoldersEditor {...defaultProps} />);
+
+ // With nothing selected, counter shows total items (2 metrics + 2 columns =
4)
+ expect(screen.getByText('4 items')).toBeInTheDocument();
+
+ // Click "Select all"
+ const selectAllButton = screen.getByText('Select all');
+ fireEvent.click(selectAllButton);
+
+ // Counter should show total selected out of total items
+ await waitFor(() => {
+ expect(screen.getByText('4 out of 4 selected')).toBeInTheDocument();
+ });
+
+ // Deselect all
+ const deselectAllButton = screen.getByText('Deselect all');
+ fireEvent.click(deselectAllButton);
+
+ // Counter should revert to item count
+ await waitFor(() => {
+ expect(screen.getByText('4 items')).toBeInTheDocument();
+ });
+});
+
test('expands and collapses folders', async () => {
renderEditor(<FoldersEditor {...defaultProps} />);
@@ -496,6 +527,39 @@ test('drag functionality integrates properly with
selection state', () => {
expect(checkboxes.length).toBeGreaterThan(0);
});
+test('select all expands collapsed folders', async () => {
+ renderEditor(<FoldersEditor {...defaultProps} />);
+
+ // Folder should be expanded by default, so Count should be visible
+ expect(screen.getByText('Count')).toBeInTheDocument();
+
+ // Collapse the Metrics folder
+ const downIcons = screen.getAllByRole('img', { name: 'down' });
+ fireEvent.click(downIcons[0]);
+
+ await waitFor(() => {
+ expect(screen.queryByText('Count')).not.toBeInTheDocument();
+ });
+
+ // Click "Select all"
+ const selectAllButton = screen.getByText('Select all');
+ fireEvent.click(selectAllButton);
+
+ // The collapsed folder should be expanded and items should be visible
+ await waitFor(() => {
+ expect(screen.getByText('Count')).toBeInTheDocument();
+ });
+
+ // All checkboxes should be checked
+ const checkboxes = screen.getAllByRole('checkbox');
+ const nonButtonCheckboxes = checkboxes.filter(
+ checkbox => !checkbox.closest('button'),
+ );
+ nonButtonCheckboxes.forEach(checkbox => {
+ expect(checkbox).toBeChecked();
+ });
+});
+
test('nested folders with items remain visible after drag is cancelled', async
() => {
const onChange = jest.fn();
const nestedFolders: DatasourceFolder[] = [
diff --git
a/superset-frontend/src/components/Datasource/FoldersEditor/components/FoldersToolbarComponent.tsx
b/superset-frontend/src/components/Datasource/FoldersEditor/components/FoldersToolbarComponent.tsx
index 8fe6718c254..e62cff18444 100644
---
a/superset-frontend/src/components/Datasource/FoldersEditor/components/FoldersToolbarComponent.tsx
+++
b/superset-frontend/src/components/Datasource/FoldersEditor/components/FoldersToolbarComponent.tsx
@@ -17,11 +17,17 @@
* under the License.
*/
-import { memo } from 'react';
-import { t } from '@apache-superset/core';
-import { Button, Input } from '@superset-ui/core/components';
+import { memo, useMemo } from 'react';
+import { t, tn } from '@apache-superset/core';
+import { Button, Input, Tooltip } from '@superset-ui/core/components';
import { Icons } from '@superset-ui/core/components/Icons';
-import { FoldersToolbar, FoldersSearch, FoldersActions } from '../styles';
+import {
+ FoldersToolbar,
+ FoldersSearch,
+ FoldersActions,
+ FoldersActionsRow,
+ SelectionCount,
+} from '../styles';
interface FoldersToolbarComponentProps {
onSearch: (e: React.ChangeEvent<HTMLInputElement>) => void;
@@ -29,6 +35,10 @@ interface FoldersToolbarComponentProps {
onSelectAll: () => void;
onResetToDefault: () => void;
allVisibleSelected: boolean;
+ selectedColumnsCount: number;
+ selectedMetricsCount: number;
+ totalColumnsCount: number;
+ totalMetricsCount: number;
}
function FoldersToolbarComponentInner({
@@ -37,7 +47,56 @@ function FoldersToolbarComponentInner({
onSelectAll,
onResetToDefault,
allVisibleSelected,
+ selectedColumnsCount,
+ selectedMetricsCount,
+ totalColumnsCount,
+ totalMetricsCount,
}: FoldersToolbarComponentProps) {
+ const selectedCount = selectedColumnsCount + selectedMetricsCount;
+ const totalCount = totalColumnsCount + totalMetricsCount;
+
+ const tooltipTitle = useMemo(() => {
+ if (selectedCount > 0) {
+ return (
+ <>
+ {tn(
+ '%s out of %s column',
+ '%s out of %s columns',
+ totalColumnsCount,
+ selectedColumnsCount,
+ totalColumnsCount,
+ )}
+ <br />
+ {tn(
+ '%s out of %s metric',
+ '%s out of %s metrics',
+ totalMetricsCount,
+ selectedMetricsCount,
+ totalMetricsCount,
+ )}
+ </>
+ );
+ }
+ return (
+ <>
+ {tn('%s column', '%s columns', totalColumnsCount, totalColumnsCount)}
+ <br />
+ {tn('%s metric', '%s metrics', totalMetricsCount, totalMetricsCount)}
+ </>
+ );
+ }, [
+ selectedCount,
+ selectedColumnsCount,
+ selectedMetricsCount,
+ totalColumnsCount,
+ totalMetricsCount,
+ ]);
+
+ const counterText =
+ selectedCount > 0
+ ? t('%s out of %s selected', selectedCount, totalCount)
+ : tn('%s item', '%s items', totalCount, totalCount);
+
return (
<FoldersToolbar>
<FoldersSearch>
@@ -48,29 +107,34 @@ function FoldersToolbarComponentInner({
prefix={<Icons.SearchOutlined />}
/>
</FoldersSearch>
- <FoldersActions>
- <Button
- buttonStyle="link"
- onClick={onAddFolder}
- icon={<Icons.PlusOutlined />}
- >
- {t('Add folder')}
- </Button>
- <Button
- buttonStyle="link"
- onClick={onSelectAll}
- icon={<Icons.CheckOutlined />}
- >
- {allVisibleSelected ? t('Deselect all') : t('Select all')}
- </Button>
- <Button
- buttonStyle="link"
- onClick={onResetToDefault}
- icon={<Icons.HistoryOutlined />}
- >
- {t('Reset all folders to default')}
- </Button>
- </FoldersActions>
+ <FoldersActionsRow>
+ <FoldersActions>
+ <Button
+ buttonStyle="link"
+ onClick={onAddFolder}
+ icon={<Icons.PlusOutlined />}
+ >
+ {t('Add folder')}
+ </Button>
+ <Button
+ buttonStyle="link"
+ onClick={onSelectAll}
+ icon={<Icons.CheckOutlined />}
+ >
+ {allVisibleSelected ? t('Deselect all') : t('Select all')}
+ </Button>
+ <Button
+ buttonStyle="link"
+ onClick={onResetToDefault}
+ icon={<Icons.HistoryOutlined />}
+ >
+ {t('Reset all folders to default')}
+ </Button>
+ </FoldersActions>
+ <Tooltip title={tooltipTitle}>
+ <SelectionCount>{counterText}</SelectionCount>
+ </Tooltip>
+ </FoldersActionsRow>
</FoldersToolbar>
);
}
diff --git
a/superset-frontend/src/components/Datasource/FoldersEditor/index.tsx
b/superset-frontend/src/components/Datasource/FoldersEditor/index.tsx
index 688adb3faee..4733dc91726 100644
--- a/superset-frontend/src/components/Datasource/FoldersEditor/index.tsx
+++ b/superset-frontend/src/components/Datasource/FoldersEditor/index.tsx
@@ -205,8 +205,28 @@ export default function FoldersEditor({
setSelectedItemIds(new Set());
} else {
setSelectedItemIds(itemsToSelect);
+ // Expand ancestor folders of selected items
+ const parentMap = new Map<string, string | null>();
+ for (const item of fullFlattenedItems) {
+ parentMap.set(item.uuid, item.parentId);
+ }
+ const foldersToExpand = new Set<string>();
+ for (const id of itemsToSelect) {
+ let parentId = parentMap.get(id) ?? null;
+ while (parentId) {
+ foldersToExpand.add(parentId);
+ parentId = parentMap.get(parentId) ?? null;
+ }
+ }
+ setCollapsedIds(prev => {
+ const newSet = new Set(prev);
+ for (const folderId of foldersToExpand) {
+ newSet.delete(folderId);
+ }
+ return newSet;
+ });
}
- }, [visibleItemIds, fullItemsByUuid, allVisibleSelected]);
+ }, [visibleItemIds, fullItemsByUuid, fullFlattenedItems,
allVisibleSelected]);
const handleResetToDefault = () => {
setShowResetConfirm(true);
@@ -380,6 +400,16 @@ export default function FoldersEditor({
[flattenedItems],
);
+ const selectedMetricsCount = useMemo(() => {
+ let count = 0;
+ for (const id of selectedItemIds) {
+ if (metricsMap.has(id)) {
+ count += 1;
+ }
+ }
+ return count;
+ }, [selectedItemIds, metricsMap]);
+
const folderChildCounts = useMemo(() => {
const counts = new Map<string, number>();
// Initialize all folders with 0
@@ -410,6 +440,10 @@ export default function FoldersEditor({
onSelectAll={handleSelectAll}
onResetToDefault={handleResetToDefault}
allVisibleSelected={allVisibleSelected}
+ selectedMetricsCount={selectedMetricsCount}
+ selectedColumnsCount={selectedItemIds.size - selectedMetricsCount}
+ totalMetricsCount={metrics.length}
+ totalColumnsCount={columns.length}
/>
<FoldersContent ref={contentRef}>
<DndContext
diff --git
a/superset-frontend/src/components/Datasource/FoldersEditor/styles.tsx
b/superset-frontend/src/components/Datasource/FoldersEditor/styles.tsx
index 1c62cfa2802..7bd04b7a577 100644
--- a/superset-frontend/src/components/Datasource/FoldersEditor/styles.tsx
+++ b/superset-frontend/src/components/Datasource/FoldersEditor/styles.tsx
@@ -54,6 +54,20 @@ export const FoldersActions = styled.div`
`}
`;
+export const FoldersActionsRow = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: ${({ theme }) => theme.paddingXS}px;
+`;
+
+export const SelectionCount = styled.div`
+ ${({ theme }) => `
+ align-self: flex-end;
+ font-size: ${theme.fontSizeSM}px;
+ color: ${theme.colorTextSecondary};
+ `}
+`;
+
export const FoldersContent = styled.div`
flex: 1;
min-height: 0;