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 0827ec38110 fix(dataset-modal): include nested folders when dragging 
all their children (#38275)
0827ec38110 is described below

commit 0827ec38110abf149e09400e9150d15b63f97cbd
Author: Kamil Gabryjelski <[email protected]>
AuthorDate: Fri Feb 27 07:27:37 2026 +0100

    fix(dataset-modal): include nested folders when dragging all their children 
(#38275)
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../FoldersEditor/hooks/useDragHandlers.ts         | 133 ++++++++++++++++++++-
 1 file changed, 129 insertions(+), 4 deletions(-)

diff --git 
a/superset-frontend/src/components/Datasource/FoldersEditor/hooks/useDragHandlers.ts
 
b/superset-frontend/src/components/Datasource/FoldersEditor/hooks/useDragHandlers.ts
index 5191c03b971..388da41cfcd 100644
--- 
a/superset-frontend/src/components/Datasource/FoldersEditor/hooks/useDragHandlers.ts
+++ 
b/superset-frontend/src/components/Datasource/FoldersEditor/hooks/useDragHandlers.ts
@@ -293,9 +293,38 @@ export function useDragHandlers({
       }
     }
 
-    // Single pass to gather info about dragged items
+    // Auto-include non-default folders when ALL their children are being
+    // dragged. This preserves nested folder hierarchy during "Select All"
+    // + drag operations. Process bottom-up (deepest first) so child folder
+    // promotions propagate up to parent folders.
+    const promotedFolderIds = new Set<string>();
+    const nonDefaultFolders = fullFlattenedItems.filter(
+      (item: FlattenedTreeItem) =>
+        item.type === FoldersEditorItemType.Folder &&
+        !isDefaultFolder(item.uuid) &&
+        !itemsBeingDraggedSet.has(item.uuid),
+    );
+    nonDefaultFolders.sort(
+      (a: FlattenedTreeItem, b: FlattenedTreeItem) => b.depth - a.depth,
+    );
+    for (const folder of nonDefaultFolders) {
+      const children = childrenByParentId.get(folder.uuid);
+      if (
+        children &&
+        children.length > 0 &&
+        children.every(
+          (child: FlattenedTreeItem) =>
+            itemsBeingDraggedSet.has(child.uuid) ||
+            promotedFolderIds.has(child.uuid),
+        )
+      ) {
+        promotedFolderIds.add(folder.uuid);
+      }
+    }
+
+    // Single pass to gather info about dragged items (including promoted 
folders)
     let hasNonFolderItems = false;
-    let hasDraggedFolder = false;
+    let hasDraggedFolder = promotedFolderIds.size > 0;
     let hasDraggedDefaultFolder = false;
     let hasDraggedColumn = false;
     let hasDraggedMetric = false;
@@ -333,6 +362,11 @@ export function useDragHandlers({
       const targetFolder = fullItemsByUuid.get(projectedPosition.parentId);
 
       if (targetFolder && isDefaultFolder(targetFolder.uuid)) {
+        if (promotedFolderIds.size > 0) {
+          addWarningToast(t('Cannot nest folders in default folders'));
+          return;
+        }
+
         const isDefaultMetricsFolder =
           targetFolder.uuid === DEFAULT_METRICS_FOLDER_UUID;
         const isDefaultColumnsFolder =
@@ -366,7 +400,7 @@ export function useDragHandlers({
       return;
     }
 
-    // Check max depth for folders
+    // Check max depth for folders (including promoted folders)
     if (hasDraggedFolder && projectedPosition) {
       for (const draggedItem of draggedItems) {
         if (draggedItem.type === FoldersEditorItemType.Folder) {
@@ -386,6 +420,22 @@ export function useDragHandlers({
           }
         }
       }
+      for (const folderId of promotedFolderIds) {
+        const folder = fullItemsByUuid.get(folderId);
+        if (folder) {
+          const maxFolderDescendantDepth = getMaxFolderDescendantDepth(
+            folderId,
+            folder.depth,
+          );
+          const descendantDepthOffset = maxFolderDescendantDepth - 
folder.depth;
+          const newMaxDescendantDepth =
+            projectedPosition.depth + descendantDepthOffset;
+          if (newMaxDescendantDepth >= MAX_DEPTH) {
+            addWarningToast(t('Maximum folder nesting depth reached'));
+            return;
+          }
+        }
+      }
     }
 
     let newItems = fullFlattenedItems;
@@ -398,8 +448,69 @@ export function useDragHandlers({
         { depth: number; parentId: string | null | undefined }
       >();
 
+      // Compute the depth change for topmost promoted folders.
+      // They become siblings of the dragged items in the target folder,
+      // so they move to projectedPosition.depth regardless of their
+      // original depth.
+      const topmostPromotedFolders: FlattenedTreeItem[] = [];
+      for (const folderId of promotedFolderIds) {
+        const folder = fullItemsByUuid.get(folderId);
+        if (!folder) continue;
+        const isTopmost =
+          !folder.parentId || !promotedFolderIds.has(folder.parentId);
+        if (isTopmost) {
+          topmostPromotedFolders.push(folder);
+        }
+      }
+
+      // For each topmost promoted folder, compute its depth change
+      // and apply it to the folder and all its descendants
+      const promotedDepthChange = new Map<string, number>();
+      for (const folder of topmostPromotedFolders) {
+        const folderDepthChange = projectedPosition.depth - folder.depth;
+        promotedDepthChange.set(folder.uuid, folderDepthChange);
+        itemsToUpdate.set(folder.uuid, {
+          depth: projectedPosition.depth,
+          parentId: projectedPosition.parentId,
+        });
+      }
+
+      // For nested promoted folders (not topmost), keep their original
+      // parentId and apply the topmost ancestor's depth change
+      for (const folderId of promotedFolderIds) {
+        if (itemsToUpdate.has(folderId)) continue;
+        const folder = fullItemsByUuid.get(folderId);
+        if (!folder) continue;
+        // Find the topmost promoted ancestor's depth change
+        let ancestor = folder;
+        while (
+          ancestor.parentId &&
+          promotedFolderIds.has(ancestor.parentId) &&
+          !promotedDepthChange.has(ancestor.parentId)
+        ) {
+          ancestor = fullItemsByUuid.get(ancestor.parentId)!;
+        }
+        const ancestorChange = ancestor.parentId
+          ? (promotedDepthChange.get(ancestor.parentId) ?? depthChange)
+          : depthChange;
+        promotedDepthChange.set(folderId, ancestorChange);
+        itemsToUpdate.set(folderId, {
+          depth: folder.depth + ancestorChange,
+          parentId: undefined,
+        });
+      }
+
       draggedItems.forEach((item: FlattenedTreeItem) => {
-        if (item.uuid === active.id) {
+        // Items whose parent folder was promoted should keep their
+        // original parentId and use the promoted folder's depth change
+        if (item.parentId && promotedFolderIds.has(item.parentId)) {
+          const folderChange =
+            promotedDepthChange.get(item.parentId) ?? depthChange;
+          itemsToUpdate.set(item.uuid, {
+            depth: item.depth + folderChange,
+            parentId: undefined,
+          });
+        } else if (item.uuid === active.id) {
           itemsToUpdate.set(item.uuid, {
             depth: projectedPosition.depth,
             parentId: projectedPosition.parentId,
@@ -431,11 +542,17 @@ export function useDragHandlers({
         }
       };
 
+      // Collect descendants for explicitly dragged folders
       draggedItems.forEach((item: FlattenedTreeItem) => {
         if (item.type === FoldersEditorItemType.Folder) {
           collectDescendants(item.uuid, depthChange);
         }
       });
+      // Collect descendants for promoted folders using their specific depth 
change
+      for (const folderId of promotedFolderIds) {
+        const folderChange = promotedDepthChange.get(folderId) ?? depthChange;
+        collectDescendants(folderId, folderChange);
+      }
 
       newItems = fullFlattenedItems.map((item: FlattenedTreeItem) => {
         const update = itemsToUpdate.get(item.uuid);
@@ -473,6 +590,14 @@ export function useDragHandlers({
       }
     });
 
+    // Include promoted folders and their descendants in the move set
+    if (projectedPosition) {
+      for (const folderId of promotedFolderIds) {
+        itemsToMoveIds.add(folderId);
+        collectDescendantIds(folderId);
+      }
+    }
+
     // Indices are already in ascending order since we iterate 
fullFlattenedItems sequentially
     const itemsToMoveIndices: number[] = [];
     fullFlattenedItems.forEach((item: FlattenedTreeItem, idx: number) => {

Reply via email to