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


##########
superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.jsx:
##########
@@ -470,6 +644,15 @@ export class TableRenderer extends Component {
               namesMapping,
               allowRenderHtml,
             )}
+            <span
+              role="columnheader button"
+              tabIndex={0}
+              onClick={e => {
+                e.stopPropagation();
+              }}

Review Comment:
   ### Sort icon wrapper blocks sorting functionality <sub>![category 
Functionality](https://img.shields.io/badge/Functionality-0284c7)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The sort icon span has an onClick handler that only calls stopPropagation() 
but doesn't trigger any sorting functionality. The actual sorting is handled by 
the getSortIcon function's onClick, but this outer handler prevents event 
bubbling.
   
   
   ###### Why this matters
   This creates a dead click zone where users can click on the sort icon area 
but nothing happens, leading to a confusing user experience where sorting 
appears broken.
   
   ###### Suggested change āˆ™ *Feature Preview*
   Remove the outer onClick handler or make it call the sort function:
   
   ```jsx
   <span
     role="columnheader button"
     tabIndex={0}
   >
     {visibleSortIcon && getSortIcon(i)}
   </span>
   ```
   
   
   ###### 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/a44b0113-66dd-47b0-b737-e611b8fdcc4b/upvote)
 
[![Incorrect](https://img.shields.io/badge/šŸ‘Ž%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/a44b0113-66dd-47b0-b737-e611b8fdcc4b?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/a44b0113-66dd-47b0-b737-e611b8fdcc4b?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/a44b0113-66dd-47b0-b737-e611b8fdcc4b?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/šŸ‘Ž%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/a44b0113-66dd-47b0-b737-e611b8fdcc4b)
   </details>
   
   <sub>
   
   šŸ’¬ Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:59d9dfdf-cb17-46d3-866f-e95f7b8971ed -->
   
   
   [](59d9dfdf-cb17-46d3-866f-e95f7b8971ed)



##########
superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.jsx:
##########
@@ -348,6 +418,86 @@ export class TableRenderer extends Component {
     return spans;
   }
 
+  calculateGroups(pivotData, visibleColKey, columnIndex) {
+    const groups = {};
+    const rows = pivotData.rowKeys;
+    rows.forEach(rowKey => {
+      let current = groups;
+      let sumGroup = 0;
+      rowKey.forEach(key => {
+        if (!current[key]) {
+          current[key] = { currentVal: 0 };
+        }
+        sumGroup += pivotData
+          .getAggregator(rowKey, visibleColKey[columnIndex])
+          .value();
+        current[key].currentVal = sumGroup;
+        current = current[key];
+      });
+    });
+    return groups;
+  }
+
+  sortAndCacheData(groups, sortOrder, subtotals, maxRowIndex) {
+    const { rowEnabled, rowPartialOnTop } = subtotals;
+    const sortedGroups = sortHierarchicalObject(
+      groups,
+      sortOrder,
+      rowPartialOnTop,
+    );
+    return convertToArray(
+      sortedGroups,
+      rowEnabled,
+      rowPartialOnTop,
+      maxRowIndex,
+    );
+  }
+
+  sortData(columnIndex, visibleColKeys, pivotData, maxRowIndex) {
+    this.setState(state => {
+      const { sortingOrder, activeSortColumn } = state;
+
+      const newSortingOrder = [];
+      let newDirection = 'asc';
+
+      if (activeSortColumn === columnIndex) {
+        newDirection = sortingOrder[columnIndex] === 'asc' ? 'desc' : 'asc';
+      }
+
+      newSortingOrder[columnIndex] = newDirection;
+
+      const cacheKey = `${columnIndex}-${newDirection}`;
+      let newRowKeys;
+      if (this.sortCache.has(cacheKey)) {
+        const cachedRowKeys = this.sortCache.get(cacheKey);
+        newRowKeys = cachedRowKeys;
+      } else {
+        const groups = this.calculateGroups(
+          pivotData,
+          visibleColKeys,
+          columnIndex,
+        );
+        const sortedRowKeys = this.sortAndCacheData(
+          groups,
+          newDirection,
+          pivotData.subtotals,
+          maxRowIndex,
+        );
+        this.sortCache.set(cacheKey, sortedRowKeys);
+        newRowKeys = sortedRowKeys;
+      }
+      this.cachedBasePivotSettings = {
+        ...this.cachedBasePivotSettings,
+        rowKeys: newRowKeys,
+      };

Review Comment:
   ### Direct mutation of cached pivot settings <sub>![category 
Functionality](https://img.shields.io/badge/Functionality-0284c7)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The sortData method modifies cachedBasePivotSettings directly, but this 
cached object is used throughout the component for rendering. This mutation can 
cause inconsistent state between the original pivot data and the sorted data.
   
   
   ###### Why this matters
   This direct mutation can lead to rendering issues where the component 
displays sorted data even when it shouldn't, or fails to revert to original 
data when sorting is cleared. It breaks the separation between original and 
sorted data states.
   
   ###### Suggested change āˆ™ *Feature Preview*
   Store sorted row keys in component state instead of mutating the cached 
settings:
   
   ```jsx
   return {
     sortingOrder: newSortingOrder,
     activeSortColumn: columnIndex,
     sortedRowKeys: newRowKeys,
   };
   ```
   
   Then use `this.state.sortedRowKeys || this.cachedBasePivotSettings.rowKeys` 
in the render method.
   
   
   ###### 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/1093021e-884f-4219-8e49-83c6b0e42559/upvote)
 
[![Incorrect](https://img.shields.io/badge/šŸ‘Ž%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/1093021e-884f-4219-8e49-83c6b0e42559?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/1093021e-884f-4219-8e49-83c6b0e42559?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/1093021e-884f-4219-8e49-83c6b0e42559?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/šŸ‘Ž%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/1093021e-884f-4219-8e49-83c6b0e42559)
   </details>
   
   <sub>
   
   šŸ’¬ Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:eb17698a-5dd1-4815-b086-e161837e94b1 -->
   
   
   [](eb17698a-5dd1-4815-b086-e161837e94b1)



##########
superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.jsx:
##########
@@ -348,6 +418,86 @@ export class TableRenderer extends Component {
     return spans;
   }
 
+  calculateGroups(pivotData, visibleColKey, columnIndex) {
+    const groups = {};
+    const rows = pivotData.rowKeys;
+    rows.forEach(rowKey => {
+      let current = groups;
+      let sumGroup = 0;
+      rowKey.forEach(key => {
+        if (!current[key]) {
+          current[key] = { currentVal: 0 };
+        }
+        sumGroup += pivotData
+          .getAggregator(rowKey, visibleColKey[columnIndex])
+          .value();
+        current[key].currentVal = sumGroup;
+        current = current[key];
+      });
+    });
+    return groups;
+  }
+
+  sortAndCacheData(groups, sortOrder, subtotals, maxRowIndex) {
+    const { rowEnabled, rowPartialOnTop } = subtotals;
+    const sortedGroups = sortHierarchicalObject(
+      groups,
+      sortOrder,
+      rowPartialOnTop,
+    );
+    return convertToArray(
+      sortedGroups,
+      rowEnabled,
+      rowPartialOnTop,
+      maxRowIndex,
+    );
+  }
+
+  sortData(columnIndex, visibleColKeys, pivotData, maxRowIndex) {
+    this.setState(state => {
+      const { sortingOrder, activeSortColumn } = state;
+
+      const newSortingOrder = [];
+      let newDirection = 'asc';
+
+      if (activeSortColumn === columnIndex) {
+        newDirection = sortingOrder[columnIndex] === 'asc' ? 'desc' : 'asc';
+      }
+
+      newSortingOrder[columnIndex] = newDirection;

Review Comment:
   ### Sorting state reset loses previous column sorts <sub>![category 
Functionality](https://img.shields.io/badge/Functionality-0284c7)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The sortingOrder array is completely reset to an empty array on each sort 
operation, which loses all previous sorting states for other columns. Only the 
current column's sort direction is preserved.
   
   
   ###### Why this matters
   Users will lose their sorting configuration for other columns when they sort 
a new column, making multi-column sorting impossible and creating a poor user 
experience.
   
   ###### Suggested change āˆ™ *Feature Preview*
   Preserve existing sorting order and only update the current column:
   
   ```jsx
   const newSortingOrder = [...sortingOrder];
   let newDirection = 'asc';
   
   if (activeSortColumn === columnIndex) {
     newDirection = sortingOrder[columnIndex] === 'asc' ? 'desc' : 'asc';
   }
   
   newSortingOrder[columnIndex] = newDirection;
   ```
   
   
   ###### 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/bc9a9186-427a-4f4c-8031-1e86c2d3bc98/upvote)
 
[![Incorrect](https://img.shields.io/badge/šŸ‘Ž%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/bc9a9186-427a-4f4c-8031-1e86c2d3bc98?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/bc9a9186-427a-4f4c-8031-1e86c2d3bc98?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/bc9a9186-427a-4f4c-8031-1e86c2d3bc98?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/šŸ‘Ž%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/bc9a9186-427a-4f4c-8031-1e86c2d3bc98)
   </details>
   
   <sub>
   
   šŸ’¬ Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:4806b583-f114-4635-b723-49aaed61acb3 -->
   
   
   [](4806b583-f114-4635-b723-49aaed61acb3)



##########
superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.jsx:
##########
@@ -71,15 +74,82 @@ function displayHeaderCell(
   );
 }
 
+function sortHierarchicalObject(obj, objSort, rowPartialOnTop) {
+  const sortedKeys = Object.keys(obj).sort((a, b) => {
+    const valA = obj[a].currentVal || 0;
+    const valB = obj[b].currentVal || 0;
+    if (rowPartialOnTop) {
+      if (obj[a].currentVal !== undefined && obj[b].currentVal === undefined) {
+        return -1;
+      }
+      if (obj[b].currentVal !== undefined && obj[a].currentVal === undefined) {
+        return 1;
+      }
+    }
+    return objSort === 'asc' ? valA - valB : valB - valA;
+  });
+  return sortedKeys.reduce((acc, key) => {
+    acc[key] =
+      typeof obj[key] === 'object' && !Array.isArray(obj[key])
+        ? sortHierarchicalObject(obj[key], objSort, rowPartialOnTop)
+        : obj[key];
+    return acc;
+  }, {});
+}
+
+function convertToArray(
+  obj,
+  rowEnabled,
+  rowPartialOnTop,
+  maxRowIndex,
+  parentKeys = [],
+  result = [],
+  flag = false,
+) {

Review Comment:
   ### Complex Function Signature with Mutable Defaults <sub>![category 
Design](https://img.shields.io/badge/Design-0d9488)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The function has too many parameters and uses mutable default parameters 
(result array), making it hard to understand and potentially causing side 
effects.
   
   
   ###### Why this matters
   Functions with many parameters are difficult to use correctly and maintain. 
Mutable default parameters can lead to unexpected behavior across multiple 
function calls.
   
   ###### Suggested change āˆ™ *Feature Preview*
   Refactor to use an options object and avoid mutable defaults:
   ```javascript
   function convertToArray(obj, options) {
     const {
       rowEnabled,
       rowPartialOnTop,
       maxRowIndex,
       parentKeys = [],
     } = options;
     const result = [];
     // ... implementation
     return result;
   }
   ```
   
   
   ###### 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/a7c33942-6902-4b7d-bf3d-7a3128f4acae/upvote)
 
[![Incorrect](https://img.shields.io/badge/šŸ‘Ž%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/a7c33942-6902-4b7d-bf3d-7a3128f4acae?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/a7c33942-6902-4b7d-bf3d-7a3128f4acae?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/a7c33942-6902-4b7d-bf3d-7a3128f4acae?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/šŸ‘Ž%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/a7c33942-6902-4b7d-bf3d-7a3128f4acae)
   </details>
   
   <sub>
   
   šŸ’¬ Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:095355bd-6f47-4ed6-be6b-ae2c66c68f13 -->
   
   
   [](095355bd-6f47-4ed6-be6b-ae2c66c68f13)



##########
superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.jsx:
##########
@@ -71,15 +74,82 @@ function displayHeaderCell(
   );
 }
 
+function sortHierarchicalObject(obj, objSort, rowPartialOnTop) {
+  const sortedKeys = Object.keys(obj).sort((a, b) => {
+    const valA = obj[a].currentVal || 0;
+    const valB = obj[b].currentVal || 0;
+    if (rowPartialOnTop) {
+      if (obj[a].currentVal !== undefined && obj[b].currentVal === undefined) {
+        return -1;
+      }
+      if (obj[b].currentVal !== undefined && obj[a].currentVal === undefined) {
+        return 1;
+      }
+    }
+    return objSort === 'asc' ? valA - valB : valB - valA;
+  });
+  return sortedKeys.reduce((acc, key) => {
+    acc[key] =
+      typeof obj[key] === 'object' && !Array.isArray(obj[key])
+        ? sortHierarchicalObject(obj[key], objSort, rowPartialOnTop)
+        : obj[key];
+    return acc;
+  }, {});
+}
+
+function convertToArray(
+  obj,
+  rowEnabled,
+  rowPartialOnTop,
+  maxRowIndex,
+  parentKeys = [],
+  result = [],
+  flag = false,
+) {
+  let updatedFlag = flag;
+  Object.keys(obj).forEach(key => {
+    if (key === 'currentVal') {
+      return;
+    }
+    if (rowEnabled && rowPartialOnTop && parentKeys.length < maxRowIndex - 1) {
+      result.push(parentKeys.length > 0 ? [...parentKeys, key] : [key]);
+      updatedFlag = true;
+    }
+    if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
+      convertToArray(
+        obj[key],
+        rowEnabled,
+        rowPartialOnTop,
+        maxRowIndex,
+        [...parentKeys, key],
+        result,
+      );
+    }
+    if (
+      parentKeys.length >= maxRowIndex - 1 ||
+      (rowEnabled && !rowPartialOnTop)
+    ) {
+      if (!updatedFlag) {
+        result.push(parentKeys.length > 0 ? [...parentKeys, key] : [key]);
+        return;
+      }
+    }
+    if (parentKeys.length === 0 && maxRowIndex === 1) {
+      result.push([key]);
+    }
+  });
+  return result;
+}
+
 export class TableRenderer extends Component {
   constructor(props) {
     super(props);
 
     // We need state to record which entries are collapsed and which aren't.
     // This is an object with flat-keys indicating if the corresponding rows
     // should be collapsed.
-    this.state = { collapsedRows: {}, collapsedCols: {} };
-
+    this.state = { collapsedRows: {}, collapsedCols: {}, sortingOrder: [] };
+    this.sortCache = new Map();

Review Comment:
   ### Unbounded cache growth in sortCache Map <sub>![category 
Performance](https://img.shields.io/badge/Performance-4f46e5)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The sortCache Map is initialized in the constructor but never cleared, 
leading to unbounded memory growth as different sort operations accumulate 
cached results.
   
   
   ###### Why this matters
   Over time, as users perform various sorting operations, the cache will 
continue to grow without any cleanup mechanism, potentially causing memory 
leaks in long-running applications.
   
   ###### Suggested change āˆ™ *Feature Preview*
   Implement cache size limits and/or cleanup mechanisms. Consider using a LRU 
cache with a maximum size, or clear the cache when component unmounts or when 
props change significantly:
   
   ```javascript
   componentWillUnmount() {
     this.sortCache.clear();
   }
   
   // Or implement LRU with size limit
   if (this.sortCache.size > MAX_CACHE_SIZE) {
     const firstKey = this.sortCache.keys().next().value;
     this.sortCache.delete(firstKey);
   }
   ```
   
   
   ###### 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/83e387df-02e2-4d18-86d7-d3da65da588d/upvote)
 
[![Incorrect](https://img.shields.io/badge/šŸ‘Ž%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/83e387df-02e2-4d18-86d7-d3da65da588d?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/83e387df-02e2-4d18-86d7-d3da65da588d?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/83e387df-02e2-4d18-86d7-d3da65da588d?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/šŸ‘Ž%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/83e387df-02e2-4d18-86d7-d3da65da588d)
   </details>
   
   <sub>
   
   šŸ’¬ Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:a6782fd7-3a31-4496-9a01-d0607378c2d3 -->
   
   
   [](a6782fd7-3a31-4496-9a01-d0607378c2d3)



##########
superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.jsx:
##########
@@ -348,6 +418,86 @@ export class TableRenderer extends Component {
     return spans;
   }
 
+  calculateGroups(pivotData, visibleColKey, columnIndex) {
+    const groups = {};
+    const rows = pivotData.rowKeys;
+    rows.forEach(rowKey => {
+      let current = groups;
+      let sumGroup = 0;
+      rowKey.forEach(key => {
+        if (!current[key]) {
+          current[key] = { currentVal: 0 };
+        }
+        sumGroup += pivotData
+          .getAggregator(rowKey, visibleColKey[columnIndex])
+          .value();
+        current[key].currentVal = sumGroup;
+        current = current[key];
+      });
+    });
+    return groups;
+  }
+
+  sortAndCacheData(groups, sortOrder, subtotals, maxRowIndex) {
+    const { rowEnabled, rowPartialOnTop } = subtotals;
+    const sortedGroups = sortHierarchicalObject(
+      groups,
+      sortOrder,
+      rowPartialOnTop,
+    );
+    return convertToArray(
+      sortedGroups,
+      rowEnabled,
+      rowPartialOnTop,
+      maxRowIndex,
+    );
+  }
+
+  sortData(columnIndex, visibleColKeys, pivotData, maxRowIndex) {
+    this.setState(state => {
+      const { sortingOrder, activeSortColumn } = state;
+
+      const newSortingOrder = [];
+      let newDirection = 'asc';
+
+      if (activeSortColumn === columnIndex) {
+        newDirection = sortingOrder[columnIndex] === 'asc' ? 'desc' : 'asc';
+      }
+
+      newSortingOrder[columnIndex] = newDirection;
+
+      const cacheKey = `${columnIndex}-${newDirection}`;
+      let newRowKeys;
+      if (this.sortCache.has(cacheKey)) {
+        const cachedRowKeys = this.sortCache.get(cacheKey);
+        newRowKeys = cachedRowKeys;
+      } else {
+        const groups = this.calculateGroups(
+          pivotData,
+          visibleColKeys,
+          columnIndex,
+        );

Review Comment:
   ### Parameter name mismatch in calculateGroups call <sub>![category 
Functionality](https://img.shields.io/badge/Functionality-0284c7)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The calculateGroups method is called with visibleColKeys but uses 
visibleColKey[columnIndex] internally, which assumes visibleColKeys is the 
parameter name. However, the parameter is actually visibleColKey (singular), 
creating a potential undefined access.
   
   
   ###### Why this matters
   This mismatch between parameter names could cause runtime errors when trying 
to access the column data for sorting calculations, leading to incorrect 
sorting or application crashes.
   
   ###### Suggested change āˆ™ *Feature Preview*
   Fix the parameter name consistency:
   
   ```jsx
   calculateGroups(pivotData, visibleColKeys, columnIndex) {
     // Method body uses visibleColKeys[columnIndex] correctly
   }
   ```
   
   Or update the method call to match the parameter name in the method 
definition.
   
   
   ###### 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/3084a096-ee53-4076-abf1-22dc53fec9df/upvote)
 
[![Incorrect](https://img.shields.io/badge/šŸ‘Ž%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/3084a096-ee53-4076-abf1-22dc53fec9df?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/3084a096-ee53-4076-abf1-22dc53fec9df?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/3084a096-ee53-4076-abf1-22dc53fec9df?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/šŸ‘Ž%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/3084a096-ee53-4076-abf1-22dc53fec9df)
   </details>
   
   <sub>
   
   šŸ’¬ Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:a03b25d0-7a2b-4e14-8275-bfa39576745a -->
   
   
   [](a03b25d0-7a2b-4e14-8275-bfa39576745a)



##########
superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.jsx:
##########
@@ -71,15 +74,82 @@ function displayHeaderCell(
   );
 }
 
+function sortHierarchicalObject(obj, objSort, rowPartialOnTop) {
+  const sortedKeys = Object.keys(obj).sort((a, b) => {
+    const valA = obj[a].currentVal || 0;
+    const valB = obj[b].currentVal || 0;
+    if (rowPartialOnTop) {
+      if (obj[a].currentVal !== undefined && obj[b].currentVal === undefined) {
+        return -1;
+      }
+      if (obj[b].currentVal !== undefined && obj[a].currentVal === undefined) {
+        return 1;
+      }
+    }
+    return objSort === 'asc' ? valA - valB : valB - valA;
+  });
+  return sortedKeys.reduce((acc, key) => {
+    acc[key] =
+      typeof obj[key] === 'object' && !Array.isArray(obj[key])
+        ? sortHierarchicalObject(obj[key], objSort, rowPartialOnTop)
+        : obj[key];
+    return acc;
+  }, {});
+}

Review Comment:
   ### Violation of Single Responsibility Principle in Sorting Function 
<sub>![category Design](https://img.shields.io/badge/Design-0d9488)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The sorting function handles both sorting logic and data transformation in a 
single function, violating the Single Responsibility Principle.
   
   
   ###### Why this matters
   Mixing sorting and transformation logic makes the code harder to maintain, 
test, and reuse. Future modifications to either sorting or transformation logic 
will require changing the same function.
   
   ###### Suggested change āˆ™ *Feature Preview*
   Split into two separate functions:
   ```javascript
   function sortKeys(obj, objSort, rowPartialOnTop) {
     return Object.keys(obj).sort((a, b) => {
       // sorting logic here
     });
   }
   
   function transformHierarchicalObject(obj, sortedKeys, objSort, 
rowPartialOnTop) {
     // transformation logic here
   }
   ```
   
   
   ###### 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/a09af85f-4925-4a11-9670-7858fa0cc289/upvote)
 
[![Incorrect](https://img.shields.io/badge/šŸ‘Ž%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/a09af85f-4925-4a11-9670-7858fa0cc289?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/a09af85f-4925-4a11-9670-7858fa0cc289?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/a09af85f-4925-4a11-9670-7858fa0cc289?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/šŸ‘Ž%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/a09af85f-4925-4a11-9670-7858fa0cc289)
   </details>
   
   <sub>
   
   šŸ’¬ Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:cc0e138a-1147-427a-9c13-c55749d34afe -->
   
   
   [](cc0e138a-1147-427a-9c13-c55749d34afe)



##########
superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.jsx:
##########
@@ -348,6 +418,86 @@ export class TableRenderer extends Component {
     return spans;
   }
 
+  calculateGroups(pivotData, visibleColKey, columnIndex) {
+    const groups = {};
+    const rows = pivotData.rowKeys;
+    rows.forEach(rowKey => {
+      let current = groups;
+      let sumGroup = 0;
+      rowKey.forEach(key => {
+        if (!current[key]) {
+          current[key] = { currentVal: 0 };
+        }
+        sumGroup += pivotData
+          .getAggregator(rowKey, visibleColKey[columnIndex])
+          .value();
+        current[key].currentVal = sumGroup;
+        current = current[key];
+      });
+    });
+    return groups;
+  }
+
+  sortAndCacheData(groups, sortOrder, subtotals, maxRowIndex) {
+    const { rowEnabled, rowPartialOnTop } = subtotals;
+    const sortedGroups = sortHierarchicalObject(
+      groups,
+      sortOrder,
+      rowPartialOnTop,
+    );
+    return convertToArray(
+      sortedGroups,
+      rowEnabled,
+      rowPartialOnTop,
+      maxRowIndex,
+    );
+  }
+
+  sortData(columnIndex, visibleColKeys, pivotData, maxRowIndex) {
+    this.setState(state => {
+      const { sortingOrder, activeSortColumn } = state;
+
+      const newSortingOrder = [];
+      let newDirection = 'asc';
+
+      if (activeSortColumn === columnIndex) {
+        newDirection = sortingOrder[columnIndex] === 'asc' ? 'desc' : 'asc';
+      }
+
+      newSortingOrder[columnIndex] = newDirection;

Review Comment:
   ### Inefficient sorting state management loses previous column sorts 
<sub>![category 
Performance](https://img.shields.io/badge/Performance-4f46e5)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The sortData method creates a new empty array for newSortingOrder and only 
sets one index, losing all previous sorting state for other columns 
unnecessarily.
   
   
   ###### Why this matters
   This approach discards sorting information for other columns, forcing 
recalculation of sorts that were already performed, and wastes the cached sort 
results.
   
   ###### Suggested change āˆ™ *Feature Preview*
   Preserve existing sorting state and only update the specific column:
   
   ```javascript
   const newSortingOrder = [...sortingOrder];
   let newDirection = 'asc';
   
   if (activeSortColumn === columnIndex) {
     newDirection = sortingOrder[columnIndex] === 'asc' ? 'desc' : 'asc';
   }
   
   newSortingOrder[columnIndex] = newDirection;
   ```
   
   
   ###### 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/c51cd50b-f430-4c90-81cc-bbd7587d0a9a/upvote)
 
[![Incorrect](https://img.shields.io/badge/šŸ‘Ž%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/c51cd50b-f430-4c90-81cc-bbd7587d0a9a?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/c51cd50b-f430-4c90-81cc-bbd7587d0a9a?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/c51cd50b-f430-4c90-81cc-bbd7587d0a9a?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/šŸ‘Ž%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/c51cd50b-f430-4c90-81cc-bbd7587d0a9a)
   </details>
   
   <sub>
   
   šŸ’¬ Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:f7158643-7a93-4c18-a038-7d692dde9dfd -->
   
   
   [](f7158643-7a93-4c18-a038-7d692dde9dfd)



##########
superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.jsx:
##########
@@ -348,6 +418,86 @@ export class TableRenderer extends Component {
     return spans;
   }
 
+  calculateGroups(pivotData, visibleColKey, columnIndex) {
+    const groups = {};
+    const rows = pivotData.rowKeys;
+    rows.forEach(rowKey => {
+      let current = groups;
+      let sumGroup = 0;
+      rowKey.forEach(key => {
+        if (!current[key]) {
+          current[key] = { currentVal: 0 };
+        }
+        sumGroup += pivotData
+          .getAggregator(rowKey, visibleColKey[columnIndex])
+          .value();
+        current[key].currentVal = sumGroup;
+        current = current[key];
+      });
+    });

Review Comment:
   ### Inefficient O(n²) aggregator calculations in calculateGroups 
<sub>![category 
Performance](https://img.shields.io/badge/Performance-4f46e5)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The calculateGroups method has O(n²) time complexity due to calling 
getAggregator for every row-key combination in nested loops, causing redundant 
aggregator value calculations.
   
   
   ###### Why this matters
   This nested iteration with expensive aggregator calls will cause significant 
performance degradation as the dataset size increases, especially with deep 
hierarchical row structures.
   
   ###### Suggested change āˆ™ *Feature Preview*
   Cache aggregator values or restructure to avoid redundant calculations. 
Calculate the aggregator value once per rowKey and reuse it:
   
   ```javascript
   rows.forEach(rowKey => {
     const aggValue = pivotData.getAggregator(rowKey, 
visibleColKey[columnIndex]).value();
     let current = groups;
     let sumGroup = 0;
     rowKey.forEach(key => {
       if (!current[key]) {
         current[key] = { currentVal: 0 };
       }
       sumGroup += aggValue;
       current[key].currentVal = sumGroup;
       current = current[key];
     });
   });
   ```
   
   
   ###### 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/4c7e8c3c-9ab9-4883-88f5-3afa7dd8d08d/upvote)
 
[![Incorrect](https://img.shields.io/badge/šŸ‘Ž%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/4c7e8c3c-9ab9-4883-88f5-3afa7dd8d08d?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/4c7e8c3c-9ab9-4883-88f5-3afa7dd8d08d?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/4c7e8c3c-9ab9-4883-88f5-3afa7dd8d08d?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/šŸ‘Ž%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/4c7e8c3c-9ab9-4883-88f5-3afa7dd8d08d)
   </details>
   
   <sub>
   
   šŸ’¬ Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:38e454c7-87eb-4fa5-8def-7ab68ec6ae1b -->
   
   
   [](38e454c7-87eb-4fa5-8def-7ab68ec6ae1b)



##########
superset-frontend/plugins/plugin-chart-pivot-table/src/react-pivottable/TableRenderers.jsx:
##########
@@ -71,15 +74,82 @@ function displayHeaderCell(
   );
 }
 
+function sortHierarchicalObject(obj, objSort, rowPartialOnTop) {
+  const sortedKeys = Object.keys(obj).sort((a, b) => {
+    const valA = obj[a].currentVal || 0;
+    const valB = obj[b].currentVal || 0;
+    if (rowPartialOnTop) {
+      if (obj[a].currentVal !== undefined && obj[b].currentVal === undefined) {
+        return -1;
+      }
+      if (obj[b].currentVal !== undefined && obj[a].currentVal === undefined) {
+        return 1;
+      }
+    }
+    return objSort === 'asc' ? valA - valB : valB - valA;
+  });
+  return sortedKeys.reduce((acc, key) => {
+    acc[key] =
+      typeof obj[key] === 'object' && !Array.isArray(obj[key])
+        ? sortHierarchicalObject(obj[key], objSort, rowPartialOnTop)
+        : obj[key];
+    return acc;
+  }, {});
+}
+
+function convertToArray(
+  obj,
+  rowEnabled,
+  rowPartialOnTop,
+  maxRowIndex,
+  parentKeys = [],
+  result = [],
+  flag = false,
+) {
+  let updatedFlag = flag;
+  Object.keys(obj).forEach(key => {
+    if (key === 'currentVal') {
+      return;
+    }
+    if (rowEnabled && rowPartialOnTop && parentKeys.length < maxRowIndex - 1) {
+      result.push(parentKeys.length > 0 ? [...parentKeys, key] : [key]);
+      updatedFlag = true;
+    }
+    if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
+      convertToArray(
+        obj[key],
+        rowEnabled,
+        rowPartialOnTop,
+        maxRowIndex,
+        [...parentKeys, key],
+        result,
+      );
+    }

Review Comment:
   ### Excessive array copying in convertToArray recursion <sub>![category 
Performance](https://img.shields.io/badge/Performance-4f46e5)</sub>
   
   <details>
     <summary>Tell me more</summary>
   
   ###### What is the issue?
   The convertToArray function creates new arrays with spread operator 
[...parentKeys, key] on every recursive call, leading to excessive memory 
allocations and O(n²) space complexity.
   
   
   ###### Why this matters
   Each recursive call creates new array copies, resulting in significant 
memory overhead and garbage collection pressure for deep hierarchical 
structures.
   
   ###### Suggested change āˆ™ *Feature Preview*
   Use array mutation instead of creating new arrays, and restore state after 
recursion:
   
   ```javascript
   parentKeys.push(key);
   convertToArray(
     obj[key],
     rowEnabled,
     rowPartialOnTop,
     maxRowIndex,
     parentKeys,
     result,
   );
   parentKeys.pop();
   ```
   
   
   ###### 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/621c072f-0c82-4a2c-99ad-e4a1c95d3df6/upvote)
 
[![Incorrect](https://img.shields.io/badge/šŸ‘Ž%20Incorrect-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/621c072f-0c82-4a2c-99ad-e4a1c95d3df6?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/621c072f-0c82-4a2c-99ad-e4a1c95d3df6?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/621c072f-0c82-4a2c-99ad-e4a1c95d3df6?what_not_in_standard=true)
 
[![Other](https://img.shields.io/badge/šŸ‘Ž%20Other-white)](https://app.korbit.ai/feedback/aa91ff46-6083-4491-9416-b83dd1994b51/621c072f-0c82-4a2c-99ad-e4a1c95d3df6)
   </details>
   
   <sub>
   
   šŸ’¬ Looking for more details? Reply to this comment to chat with Korbit.
   </sub>
   
   <!--- korbi internal id:066fd9ff-7731-4612-bfd4-5f4ad019d8e8 -->
   
   
   [](066fd9ff-7731-4612-bfd4-5f4ad019d8e8)



-- 
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