You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ms...@apache.org on 2026/02/23 12:16:35 UTC
(superset) branch msyavuz/fix/dataset-folders-improved-search created (now b0b5cf23d0c)
This is an automated email from the ASF dual-hosted git repository.
msyavuz pushed a change to branch msyavuz/fix/dataset-folders-improved-search
in repository https://gitbox.apache.org/repos/asf/superset.git
at b0b5cf23d0c fix(Dataset Folders): improve search-collapse
This branch includes the following new commits:
new b0b5cf23d0c fix(Dataset Folders): improve search-collapse
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(superset) 01/01: fix(Dataset Folders): improve search-collapse
Posted by ms...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
msyavuz pushed a commit to branch msyavuz/fix/dataset-folders-improved-search
in repository https://gitbox.apache.org/repos/asf/superset.git
commit b0b5cf23d0cd52983f9ef575e2b7760761088095
Author: Mehmet Salih Yavuz <sa...@proton.me>
AuthorDate: Mon Feb 23 15:15:57 2026 +0300
fix(Dataset Folders): improve search-collapse
---
.../components/Datasource/FoldersEditor/index.tsx | 193 ++++++++++++++++++---
1 file changed, 169 insertions(+), 24 deletions(-)
diff --git a/superset-frontend/src/components/Datasource/FoldersEditor/index.tsx b/superset-frontend/src/components/Datasource/FoldersEditor/index.tsx
index 7325569c50c..01a9c271a75 100644
--- a/superset-frontend/src/components/Datasource/FoldersEditor/index.tsx
+++ b/superset-frontend/src/components/Datasource/FoldersEditor/index.tsx
@@ -86,48 +86,184 @@ export default function FoldersEditor({
const [searchTerm, setSearchTerm] = useState('');
const [collapsedIds, setCollapsedIds] = useState<Set<string>>(new Set());
const [editingFolderId, setEditingFolderId] = useState<string | null>(null);
+ const [preSearchCollapsedIds, setPreSearchCollapsedIds] =
+ useState<Set<string> | null>(null);
const [showResetConfirm, setShowResetConfirm] = useState(false);
const sensors = useSensors(useSensor(PointerSensor, pointerSensorOptions));
const fullFlattenedItems = useMemo(() => flattenTree(items), [items]);
+ // Track which folders contain matching items during search
+ const { visibleItemIds, searchExpandedFolderIds, foldersWithMatches } =
+ useMemo(() => {
+ if (!searchTerm) {
+ const allIds = new Set<string>();
+ metrics.forEach(m => allIds.add(m.uuid));
+ columns.forEach(c => allIds.add(c.uuid));
+ return {
+ visibleItemIds: allIds,
+ searchExpandedFolderIds: new Set<string>(),
+ foldersWithMatches: new Set<string>(),
+ };
+ }
+
+ const allItems = [...metrics, ...columns];
+ const matchingItemIds = filterItemsBySearch(searchTerm, allItems);
+ const expandedFolders = new Set<string>();
+ const matchingFolders = new Set<string>();
+ const lowerSearch = searchTerm.toLowerCase();
+
+ // Helper to check if folder title matches search
+ const folderMatches = (folder: TreeItemType): boolean =>
+ folder.type === FoldersEditorItemType.Folder &&
+ folder.name?.toLowerCase().includes(lowerSearch);
+
+ // Helper to recursively check if a folder contains matching items
+ const folderContainsMatches = (folder: TreeItemType): boolean => {
+ if (folder.type !== FoldersEditorItemType.Folder) return false;
+
+ // If folder name matches, it contains matches
+ if (folderMatches(folder)) {
+ return true;
+ }
+
+ // Check direct children
+ if (
+ folder.children?.some(child => {
+ if (child.type === FoldersEditorItemType.Folder) {
+ return folderContainsMatches(child);
+ }
+ return matchingItemIds.has(child.uuid);
+ })
+ ) {
+ return true;
+ }
+
+ return false;
+ };
+
+ // Helper to get all item IDs in a folder
+ const getAllItemsInFolder = (
+ folder: TreeItemType,
+ itemSet: Set<string>,
+ ) => {
+ if ('children' in folder && folder.children) {
+ folder.children.forEach((child: TreeItemType) => {
+ if (child.type === FoldersEditorItemType.Folder) {
+ getAllItemsInFolder(child, itemSet);
+ } else {
+ itemSet.add(child.uuid);
+ }
+ });
+ }
+ };
+
+ // Process each folder to determine which should expand and which items to show
+ const finalVisibleItems = new Set<string>(matchingItemIds);
+
+ items.forEach(item => {
+ if (item.type === FoldersEditorItemType.Folder) {
+ if (folderMatches(item)) {
+ // Folder title matches - expand and show all children
+ expandedFolders.add(item.uuid);
+ matchingFolders.add(item.uuid);
+ getAllItemsInFolder(item, finalVisibleItems);
+
+ // Recursively expand all subfolders
+ const expandAllSubfolders = (folder: TreeItemType) => {
+ if ('children' in folder && folder.children) {
+ folder.children.forEach((child: TreeItemType) => {
+ if (child.type === FoldersEditorItemType.Folder) {
+ expandedFolders.add(child.uuid);
+ matchingFolders.add(child.uuid);
+ expandAllSubfolders(child);
+ }
+ });
+ }
+ };
+ expandAllSubfolders(item);
+ } else if (folderContainsMatches(item)) {
+ // Folder contains matching items - expand it
+ expandedFolders.add(item.uuid);
+ matchingFolders.add(item.uuid);
+
+ // Recursively expand subfolders that contain matches
+ const expandMatchingSubfolders = (folder: TreeItemType) => {
+ if ('children' in folder && folder.children) {
+ folder.children.forEach((child: TreeItemType) => {
+ if (
+ child.type === FoldersEditorItemType.Folder &&
+ folderContainsMatches(child)
+ ) {
+ expandedFolders.add(child.uuid);
+ matchingFolders.add(child.uuid);
+ expandMatchingSubfolders(child);
+ }
+ });
+ }
+ };
+ expandMatchingSubfolders(item);
+ }
+ }
+ });
+
+ return {
+ visibleItemIds: finalVisibleItems,
+ searchExpandedFolderIds: expandedFolders,
+ foldersWithMatches: matchingFolders,
+ };
+ }, [searchTerm, metrics, columns, items]);
+
const collapsedFolderIds = useMemo(() => {
const result: UniqueIdentifier[] = [];
for (const { uuid, type, children } of fullFlattenedItems) {
- if (
- type === FoldersEditorItemType.Folder &&
- collapsedIds.has(uuid) &&
- children?.length
- ) {
- result.push(uuid);
+ if (type === FoldersEditorItemType.Folder && children?.length) {
+ // During search, use search-expanded folders
+ if (searchTerm) {
+ if (!searchExpandedFolderIds.has(uuid)) {
+ result.push(uuid);
+ }
+ } else {
+ // Normal collapse state when not searching
+ if (collapsedIds.has(uuid)) {
+ result.push(uuid);
+ }
+ }
}
}
return result;
- }, [fullFlattenedItems, collapsedIds]);
+ }, [fullFlattenedItems, collapsedIds, searchTerm, searchExpandedFolderIds]);
const computeFlattenedItems = useCallback(
- (activeId: UniqueIdentifier | null) =>
- removeChildrenOf(
- fullFlattenedItems,
+ (activeId: UniqueIdentifier | null) => {
+ // During search, filter out folders that don't match
+ let itemsToProcess = fullFlattenedItems;
+ if (searchTerm && foldersWithMatches) {
+ itemsToProcess = fullFlattenedItems.filter(item => {
+ if (item.type === FoldersEditorItemType.Folder) {
+ return foldersWithMatches.has(item.uuid);
+ }
+ return visibleItemIds.has(item.uuid);
+ });
+ }
+
+ return removeChildrenOf(
+ itemsToProcess,
activeId != null
? [activeId, ...collapsedFolderIds]
: collapsedFolderIds,
- ),
- [fullFlattenedItems, collapsedFolderIds],
+ );
+ },
+ [
+ fullFlattenedItems,
+ collapsedFolderIds,
+ searchTerm,
+ foldersWithMatches,
+ visibleItemIds,
+ ],
);
- const visibleItemIds = useMemo(() => {
- if (!searchTerm) {
- const allIds = new Set<string>();
- metrics.forEach(m => allIds.add(m.uuid));
- columns.forEach(c => allIds.add(c.uuid));
- return allIds;
- }
- const allItems = [...metrics, ...columns];
- return filterItemsBySearch(searchTerm, allItems);
- }, [searchTerm, metrics, columns]);
-
const metricsMap = useMemo(
() => new Map(metrics.map(m => [m.uuid, m])),
[metrics],
@@ -162,9 +298,18 @@ export default function FoldersEditor({
const debouncedSearch = useCallback(
debounce((term: string) => {
+ // Save collapsed state before search starts
+ if (!searchTerm && term) {
+ setPreSearchCollapsedIds(new Set(collapsedIds));
+ }
+ // Restore collapsed state when search is cleared
+ if (searchTerm && !term && preSearchCollapsedIds) {
+ setCollapsedIds(preSearchCollapsedIds);
+ setPreSearchCollapsedIds(null);
+ }
setSearchTerm(term);
}, 300),
- [],
+ [searchTerm, collapsedIds, preSearchCollapsedIds],
);
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {