korbit-ai[bot] commented on code in PR #32548:
URL: https://github.com/apache/superset/pull/32548#discussion_r2040019022


##########
superset-frontend/src/explore/components/DatasourcePanel/index.tsx:
##########
@@ -137,7 +143,7 @@ export default function DataSourcePanel({
   width,
 }: Props) {
   const [dropzones] = useContext(DropzoneContext);

Review Comment:
   ### Missing Context Provider Error Handling <sub>![category Error 
Handling](https://img.shields.io/badge/Error%20Handling-ea580c)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   No error handling or fallback for when DropzoneContext is undefined.
   
   ###### Why this matters
   If the component is rendered outside of a DropzoneContext provider, it will 
crash when trying to destructure undefined.
   
   ###### Suggested change ∙ *Feature Preview*
   Add a check for context availability:
   ```typescript
   const dropzoneContext = useContext(DropzoneContext);
   if (!dropzoneContext) {
     return <div>Error: DropzoneContext not found</div>;
   }
   const [dropzones] = dropzoneContext;
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/00715585-6a11-436c-accc-f0639ff34a59/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/00715585-6a11-436c-accc-f0639ff34a59?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/00715585-6a11-436c-accc-f0639ff34a59?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/00715585-6a11-436c-accc-f0639ff34a59?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/00715585-6a11-436c-accc-f0639ff34a59)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:73b5bd94-a7fd-4c0d-8dc5-6ebc8cf40d64 -->
   
   
   [](73b5bd94-a7fd-4c0d-8dc5-6ebc8cf40d64)



##########
superset-frontend/packages/superset-ui-core/src/query/types/Datasource.ts:
##########
@@ -58,6 +59,7 @@ export const DEFAULT_METRICS: Metric[] = [
   {
     metric_name: 'COUNT(*)',
     expression: 'COUNT(*)',
+    uuid: nanoid(),

Review Comment:
   ### Dynamic UUID Generation in Static Constants <sub>![category 
Performance](https://img.shields.io/badge/Performance-4f46e5)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   Generating UUIDs in DEFAULT_METRICS using nanoid() will create new IDs on 
every import of the module
   
   ###### Why this matters
   This causes unnecessary memory allocation and computation on every module 
import, even when the DEFAULT_METRICS array is not used. For static default 
values, the UUID should be a constant.
   
   ###### Suggested change ∙ *Feature Preview*
   Use a hardcoded UUID string instead of generating it dynamically:
   ```typescript
   uuid: 'DEFAULT_COUNT_METRIC_UUID', // or any other constant UUID string
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/b49d7f80-0ebb-4563-ac0f-2b47fbfc1949/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/b49d7f80-0ebb-4563-ac0f-2b47fbfc1949?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/b49d7f80-0ebb-4563-ac0f-2b47fbfc1949?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/b49d7f80-0ebb-4563-ac0f-2b47fbfc1949?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/b49d7f80-0ebb-4563-ac0f-2b47fbfc1949)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:db6be6f1-9c30-4670-99ba-f8bbe2c2445d -->
   
   
   [](db6be6f1-9c30-4670-99ba-f8bbe2c2445d)



##########
superset-frontend/src/explore/components/DatasourcePanel/DatasourceItems.tsx:
##########
@@ -0,0 +1,151 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { useCallback, useEffect, useMemo, useState } from 'react';
+import { VariableSizeList as List } from 'react-window';
+import { cloneDeep } from 'lodash';
+import { FlattenedItem, Folder } from './types';
+import DatasourcePanelItem from './DatasourcePanelItem';
+
+const BORDER_WIDTH = 2;
+const HEADER_ITEM_HEIGHT = 50;
+const METRIC_OR_COLUMN_ITEM_HEIGHT = 32;
+const DIVIDER_ITEM_HEIGHT = 16;
+
+const flattenFolderStructure = (
+  folders: Folder[],
+  depth = 0,
+  folderMap: Map<string, Folder> = new Map(),
+): { flattenedItems: FlattenedItem[]; folderMap: Map<string, Folder> } => {
+  const flattenedItems: FlattenedItem[] = [];
+
+  folders.forEach((folder, idx) => {
+    folderMap.set(folder.id, folder);
+
+    flattenedItems.push({
+      type: 'header',
+      folderId: folder.id,
+      depth,
+      height: HEADER_ITEM_HEIGHT,
+    });
+
+    if (!folder.isCollapsed) {
+      folder.items.forEach(item => {
+        flattenedItems.push({
+          type: 'item',
+          folderId: folder.id,
+          depth,
+          item,
+          height: METRIC_OR_COLUMN_ITEM_HEIGHT,
+        });
+      });
+
+      if (folder.subFolders && folder.subFolders.length > 0) {
+        const { flattenedItems: subItems } = flattenFolderStructure(
+          folder.subFolders,
+          depth + 1,
+          folderMap,
+        );
+
+        flattenedItems.push(...subItems);
+      }
+    }
+    if (depth === 0 && idx !== folders.length - 1) {
+      flattenedItems.push({
+        type: 'divider',
+        folderId: folder.id,
+        depth,
+        height: DIVIDER_ITEM_HEIGHT,
+      });
+    }
+  });
+
+  return { flattenedItems, folderMap };
+};
+
+interface DatasourceItemsProps {
+  width: number;
+  height: number;
+  folders: Folder[];
+}
+export const DatasourceItems = ({
+  width,
+  height,
+  folders,
+}: DatasourceItemsProps) => {
+  const [folderStructure, setFolderStructure] = useState<Folder[]>(folders);
+
+  useEffect(() => {
+    setFolderStructure(prev => (prev !== folders ? folders : prev));
+  }, [folders]);
+
+  const { flattenedItems, folderMap } = useMemo(
+    () => flattenFolderStructure(folderStructure),
+    [folderStructure],
+  );
+
+  const handleToggleCollapse = useCallback((folderId: string) => {
+    setFolderStructure(prevFolders => {
+      const updatedFolders = cloneDeep(prevFolders);

Review Comment:
   ### Inefficient Deep Cloning in State Updates <sub>![category 
Performance](https://img.shields.io/badge/Performance-4f46e5)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   Using lodash's cloneDeep for folder state updates creates unnecessary deep 
cloning of the entire folder structure.
   
   ###### Why this matters
   Deep cloning large data structures is computationally expensive and can 
impact performance, especially when the folder structure is large or when 
frequent collapse/expand operations occur.
   
   ###### Suggested change ∙ *Feature Preview*
   Implement selective immutable updates using spread operators or immer to 
only clone the path to the modified folder. Example:
   ```typescript
   const updateFolder = (folders: Folder[]): Folder[] => {
     return folders.map(folder => {
       if (folder.id === folderId) {
         return { ...folder, isCollapsed: !folder.isCollapsed };
       }
       if (folder.subFolders) {
         return { ...folder, subFolders: updateFolder(folder.subFolders) };
       }
       return folder;
     });
   };
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/293a74f6-cd27-40b4-9043-824576cf09e8/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/293a74f6-cd27-40b4-9043-824576cf09e8?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/293a74f6-cd27-40b4-9043-824576cf09e8?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/293a74f6-cd27-40b4-9043-824576cf09e8?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/293a74f6-cd27-40b4-9043-824576cf09e8)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:7d3e0c5a-6e6d-475d-aa7c-a5cf8a69f4f8 -->
   
   
   [](7d3e0c5a-6e6d-475d-aa7c-a5cf8a69f4f8)



##########
superset-frontend/src/explore/components/DatasourcePanel/transformDatasourceFolders.ts:
##########
@@ -0,0 +1,155 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { Metric, t } from '@superset-ui/core';
+import {
+  ColumnItem,
+  DatasourceFolder,
+  DatasourcePanelColumn,
+  Folder,
+  MetricItem,
+} from './types';
+
+const transformToFolderStructure = (
+  metrics: MetricItem[],
+  columns: ColumnItem[],
+  folderConfig: DatasourceFolder[] | undefined,
+): Folder[] => {
+  const metricsMap = new Map<string, MetricItem>();
+  const columnsMap = new Map<string, ColumnItem>();
+
+  const assignedMetricUuids = new Set<string>();
+  const assignedColumnUuids = new Set<string>();
+
+  metrics.forEach(metric => {
+    metricsMap.set(metric.uuid, metric);
+  });
+
+  columns.forEach(column => {
+    columnsMap.set(column.uuid, column);
+  });
+
+  const processFolder = (
+    datasourceFolder: DatasourceFolder,
+    parentId?: string,
+  ): Folder => {
+    const folder: Folder = {
+      id: datasourceFolder.uuid,
+      name: datasourceFolder.name,
+      description: datasourceFolder.description,
+      isCollapsed: false,
+      items: [],
+      parentId,
+    };
+
+    if (datasourceFolder.children && datasourceFolder.children.length > 0) {
+      if (!folder.subFolders) {
+        folder.subFolders = [];
+      }
+
+      datasourceFolder.children.forEach(child => {
+        if (child.type === 'folder') {
+          folder.subFolders!.push(
+            processFolder(child as DatasourceFolder, folder.id),
+          );
+        } else if (child.type === 'metric') {
+          const metric = metricsMap.get(child.uuid);
+          if (metric) {
+            folder.items.push(metric);
+            assignedMetricUuids.add(metric.uuid);
+          }
+        } else if (child.type === 'column') {
+          const column = columnsMap.get(child.uuid);
+          if (column) {
+            folder.items.push(column);
+            assignedColumnUuids.add(column.uuid);
+          }
+        }

Review Comment:
   ### Simplify child type handling logic <sub>![category 
Readability](https://img.shields.io/badge/Readability-0284c7)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The nested if-else chain handling different child types has repetitive 
patterns and could be simplified using a map or switch statement.
   
   ###### Why this matters
   The current structure makes it harder to add new types and increases 
cognitive load when reading the code. Similar operations are repeated for 
metrics and columns.
   
   ###### Suggested change ∙ *Feature Preview*
   ```typescript
   const typeHandlers = {
     folder: (child: DatasourceFolder) => {
       folder.subFolders!.push(processFolder(child, folder.id));
     },
     metric: (child: { uuid: string }) => {
       const item = metricsMap.get(child.uuid);
       if (item) {
         folder.items.push(item);
         assignedMetricUuids.add(item.uuid);
       }
     },
     column: (child: { uuid: string }) => {
       const item = columnsMap.get(child.uuid);
       if (item) {
         folder.items.push(item);
         assignedColumnUuids.add(item.uuid);
       }
     },
   };
   
   datasourceFolder.children.forEach(child => {
     const handler = typeHandlers[child.type];
     if (handler) {
       handler(child as any);
     }
   });
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/ab36e2a9-3948-44ff-90c5-6e36c50f13d3/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/ab36e2a9-3948-44ff-90c5-6e36c50f13d3?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/ab36e2a9-3948-44ff-90c5-6e36c50f13d3?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/ab36e2a9-3948-44ff-90c5-6e36c50f13d3?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/ab36e2a9-3948-44ff-90c5-6e36c50f13d3)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:cad26b98-73c2-4e12-adca-b12a769370f2 -->
   
   
   [](cad26b98-73c2-4e12-adca-b12a769370f2)



##########
superset-frontend/src/explore/components/DatasourcePanel/transformDatasourceFolders.ts:
##########
@@ -0,0 +1,155 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { Metric, t } from '@superset-ui/core';
+import {
+  ColumnItem,
+  DatasourceFolder,
+  DatasourcePanelColumn,
+  Folder,
+  MetricItem,
+} from './types';
+
+const transformToFolderStructure = (
+  metrics: MetricItem[],
+  columns: ColumnItem[],
+  folderConfig: DatasourceFolder[] | undefined,
+): Folder[] => {
+  const metricsMap = new Map<string, MetricItem>();
+  const columnsMap = new Map<string, ColumnItem>();
+
+  const assignedMetricUuids = new Set<string>();
+  const assignedColumnUuids = new Set<string>();
+
+  metrics.forEach(metric => {
+    metricsMap.set(metric.uuid, metric);
+  });
+
+  columns.forEach(column => {
+    columnsMap.set(column.uuid, column);
+  });
+
+  const processFolder = (
+    datasourceFolder: DatasourceFolder,
+    parentId?: string,
+  ): Folder => {
+    const folder: Folder = {
+      id: datasourceFolder.uuid,
+      name: datasourceFolder.name,
+      description: datasourceFolder.description,
+      isCollapsed: false,
+      items: [],
+      parentId,
+    };
+
+    if (datasourceFolder.children && datasourceFolder.children.length > 0) {
+      if (!folder.subFolders) {
+        folder.subFolders = [];
+      }
+
+      datasourceFolder.children.forEach(child => {
+        if (child.type === 'folder') {
+          folder.subFolders!.push(
+            processFolder(child as DatasourceFolder, folder.id),
+          );
+        } else if (child.type === 'metric') {
+          const metric = metricsMap.get(child.uuid);
+          if (metric) {
+            folder.items.push(metric);
+            assignedMetricUuids.add(metric.uuid);
+          }

Review Comment:
   ### Silent failure on missing metric reference <sub>![category Error 
Handling](https://img.shields.io/badge/Error%20Handling-ea580c)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   Silent handling of missing metrics in the metricsMap when processing folder 
structure.
   
   ###### Why this matters
   If a metric UUID referenced in the folder structure doesn't exist in the 
metricsMap, the code silently skips it without logging or error reporting, 
making it difficult to debug misconfigurations or data inconsistencies.
   
   ###### Suggested change ∙ *Feature Preview*
   Add logging or error collection for missing metric references:
   ```typescript
   const metric = metricsMap.get(child.uuid);
   if (metric) {
     folder.items.push(metric);
     assignedMetricUuids.add(metric.uuid);
   } else {
     console.warn(`Referenced metric ${child.uuid} not found in metrics map`);
   }
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/12a108f9-3ef6-462b-9406-c1220cb3e130/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/12a108f9-3ef6-462b-9406-c1220cb3e130?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/12a108f9-3ef6-462b-9406-c1220cb3e130?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/12a108f9-3ef6-462b-9406-c1220cb3e130?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/12a108f9-3ef6-462b-9406-c1220cb3e130)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:542ed542-fc55-44be-8c3f-8ea5a379edc2 -->
   
   
   [](542ed542-fc55-44be-8c3f-8ea5a379edc2)



##########
superset-frontend/src/explore/components/DatasourcePanel/DatasourceItems.tsx:
##########
@@ -0,0 +1,151 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { useCallback, useEffect, useMemo, useState } from 'react';
+import { VariableSizeList as List } from 'react-window';
+import { cloneDeep } from 'lodash';
+import { FlattenedItem, Folder } from './types';
+import DatasourcePanelItem from './DatasourcePanelItem';
+
+const BORDER_WIDTH = 2;
+const HEADER_ITEM_HEIGHT = 50;
+const METRIC_OR_COLUMN_ITEM_HEIGHT = 32;
+const DIVIDER_ITEM_HEIGHT = 16;
+
+const flattenFolderStructure = (
+  folders: Folder[],
+  depth = 0,
+  folderMap: Map<string, Folder> = new Map(),
+): { flattenedItems: FlattenedItem[]; folderMap: Map<string, Folder> } => {
+  const flattenedItems: FlattenedItem[] = [];
+
+  folders.forEach((folder, idx) => {
+    folderMap.set(folder.id, folder);
+
+    flattenedItems.push({
+      type: 'header',
+      folderId: folder.id,
+      depth,
+      height: HEADER_ITEM_HEIGHT,
+    });
+
+    if (!folder.isCollapsed) {
+      folder.items.forEach(item => {
+        flattenedItems.push({
+          type: 'item',
+          folderId: folder.id,
+          depth,
+          item,
+          height: METRIC_OR_COLUMN_ITEM_HEIGHT,
+        });
+      });
+
+      if (folder.subFolders && folder.subFolders.length > 0) {
+        const { flattenedItems: subItems } = flattenFolderStructure(
+          folder.subFolders,
+          depth + 1,
+          folderMap,
+        );
+
+        flattenedItems.push(...subItems);
+      }
+    }
+    if (depth === 0 && idx !== folders.length - 1) {
+      flattenedItems.push({
+        type: 'divider',
+        folderId: folder.id,
+        depth,
+        height: DIVIDER_ITEM_HEIGHT,
+      });
+    }
+  });
+
+  return { flattenedItems, folderMap };
+};
+
+interface DatasourceItemsProps {
+  width: number;
+  height: number;
+  folders: Folder[];
+}
+export const DatasourceItems = ({
+  width,
+  height,
+  folders,
+}: DatasourceItemsProps) => {
+  const [folderStructure, setFolderStructure] = useState<Folder[]>(folders);
+
+  useEffect(() => {
+    setFolderStructure(prev => (prev !== folders ? folders : prev));
+  }, [folders]);
+
+  const { flattenedItems, folderMap } = useMemo(
+    () => flattenFolderStructure(folderStructure),
+    [folderStructure],
+  );
+
+  const handleToggleCollapse = useCallback((folderId: string) => {
+    setFolderStructure(prevFolders => {
+      const updatedFolders = cloneDeep(prevFolders);
+
+      const updateFolder = (folders: Folder[] | undefined): boolean => {
+        if (!folders) {
+          return false;
+        }
+        for (let i = 0; i < folders.length; i += 1) {
+          if (folders[i].id === folderId) {
+            // eslint-disable-next-line no-param-reassign
+            folders[i].isCollapsed = !folders[i].isCollapsed;
+            return true;
+          }
+
+          if (folders[i].subFolders && updateFolder(folders[i].subFolders)) {
+            return true;
+          }
+        }
+        return false;
+      };

Review Comment:
   ### Complex imperative folder update logic <sub>![category 
Readability](https://img.shields.io/badge/Readability-0284c7)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The updateFolder function is using a nested imperative approach with 
mutation, making it harder to follow the logic flow.
   
   ###### Why this matters
   Complex nested loops with mutations are harder to debug and reason about. A 
recursive functional approach would be clearer.
   
   ###### Suggested change ∙ *Feature Preview*
   ```typescript
   const updateFolder = (folders: Folder[] | undefined): Folder[] | undefined 
=> {
     if (!folders) return undefined;
     
     return folders.map(folder => ({
       ...folder,
       isCollapsed: folder.id === folderId ? !folder.isCollapsed : 
folder.isCollapsed,
       subFolders: updateFolder(folder.subFolders)
     }));
   };
   ```
   
   
   ###### Provide feedback to improve future suggestions
   [![Nice 
Catch](https://img.shields.io/badge/👍%20Nice%20Catch-71BC78)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/dcfcd9cc-2e66-415b-8669-d9334ae2a49a/upvote)
 
[![Incorrect](https://img.shields.io/badge/👎%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/dcfcd9cc-2e66-415b-8669-d9334ae2a49a?what_not_true=true)
  [![Not in 
Scope](https://img.shields.io/badge/👎%20Out%20of%20PR%20scope-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/dcfcd9cc-2e66-415b-8669-d9334ae2a49a?what_out_of_scope=true)
 [![Not in coding 
standard](https://img.shields.io/badge/👎%20Not%20in%20our%20standards-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/dcfcd9cc-2e66-415b-8669-d9334ae2a49a?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/👎%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/dcfcd9cc-2e66-415b-8669-d9334ae2a49a)
   </details>
   
   <sub>
   
   💬 Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:4e3a431c-b1d9-4628-a2b6-c98bbdfaee93 -->
   
   
   [](4e3a431c-b1d9-4628-a2b6-c98bbdfaee93)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to