Copilot commented on code in PR #36332:
URL: https://github.com/apache/superset/pull/36332#discussion_r2578259744


##########
superset-frontend/src/explore/components/SaveModal.tsx:
##########
@@ -331,6 +453,64 @@ class SaveModal extends Component<SaveModalProps, 
SaveModalState> {
       totalCount: count,
     };
   };
+  loadTabs = async (dashboardId: number) => {
+    try {
+      const response = await SupersetClient.get({
+        endpoint: `/api/v1/dashboard/${dashboardId}/tabs`,
+      });
+
+      const { result } = response.json;
+      const tabTree = result.tab_tree || [];
+
+      const convertToTreeData = (nodes: TabNode[]): TreeDataNode[] =>
+        nodes.map(node => ({
+          value: node.value,
+          title: node.title,
+          key: node.value,
+          children:
+            node.children && node.children.length > 0
+              ? convertToTreeData(node.children)
+              : undefined,
+        }));
+
+      const treeData = convertToTreeData(tabTree);
+      this.setState({ tabsData: treeData });
+      return treeData;
+    } catch (error) {
+      logging.error('Error loading tabs:', error);
+      this.setState({ tabsData: [] });
+      return [];
+    }
+  };
+
+  onTabChange = (value: string) => {
+    if (value) {
+      const findTabInTree = (data: TabNode[]): TabNode | null => {
+        for (const item of data) {
+          if (item.value === value) {
+            return item;
+          }
+          if (item.children) {
+            const found = findTabInTree(item.children);
+            if (found) return found;
+          }
+        }
+        return null;
+      };
+
+      const selectedTab = findTabInTree(this.state.tabsData);

Review Comment:
   Type mismatch in `findTabInTree`: the function is defined to accept 
`TabNode[]` but is being called with `this.state.tabsData` which is typed as 
`Array<{ value: string; title: string; key: string }>` (and should actually be 
`TreeDataNode[]`). The parameter type should match the actual state type: 
`(data: TreeDataNode[])` or change state type to use `TabNode[]`.



##########
superset-frontend/src/explore/components/SaveModal.tsx:
##########
@@ -289,6 +326,91 @@ class SaveModal extends Component<SaveModalProps, 
SaveModalState> {
       this.setState({ isLoading: false });
     }
   }
+  addChartToDashboardTab = async (
+    dashboardId: number,
+    chartId: number,
+    tabId: string,
+  ) => {
+    try {
+      const dashboardResponse = await SupersetClient.get({
+        endpoint: `/api/v1/dashboard/${dashboardId}`,
+      });
+
+      const dashboard = dashboardResponse.json.result;
+
+      let positionJson = dashboard.position_json;
+      if (typeof positionJson === 'string') {
+        positionJson = JSON.parse(positionJson);
+      }
+      positionJson = positionJson || {};
+
+      const updatedPositionJson = JSON.parse(JSON.stringify(positionJson));
+
+      const chartKey = `CHART-${chartId}`;
+      const rowIndex = this.findNextRowPosition(updatedPositionJson);
+      const rowKey = `ROW-${rowIndex}`;
+
+      updatedPositionJson[chartKey] = {
+        type: 'CHART',
+        id: chartKey,
+        children: [],
+        parents: ['ROOT_ID', 'GRID_ID', rowKey],
+        meta: {
+          width: 4,
+          height: 50,
+          chartId: chartId,
+          sliceName: `Chart ${chartId}`,
+        },
+      };
+
+      updatedPositionJson[rowKey] = {
+        type: 'ROW',
+        id: rowKey,
+        children: [chartKey],
+        parents: ['ROOT_ID', 'GRID_ID', tabId],
+        meta: {
+          background: 'BACKGROUND_TRANSPARENT',
+        },
+      };
+
+      if (updatedPositionJson[tabId]) {
+        if (!updatedPositionJson[tabId].children) {
+          updatedPositionJson[tabId].children = [];
+        }
+        updatedPositionJson[tabId].children.push(rowKey);
+      } else {
+        throw new Error(`Tab ${tabId} not found in positionJson`);
+      }
+
+      const response = await SupersetClient.put({
+        endpoint: `/api/v1/dashboard/${dashboardId}`,
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({
+          position_json: JSON.stringify(updatedPositionJson),
+        }),
+      });
+
+      return response;
+    } catch (error) {
+      throw new Error('Error adding chart to dashboard tab:', error);
+      throw error;

Review Comment:
   There are two consecutive throw statements where the second one is 
unreachable. The first `throw new Error(...)` will prevent execution from 
reaching the second `throw error`. Remove line 396 as it will never be executed.
   ```suggestion
   
   ```



##########
superset-frontend/src/explore/components/SaveModal.tsx:
##########
@@ -289,6 +326,91 @@ class SaveModal extends Component<SaveModalProps, 
SaveModalState> {
       this.setState({ isLoading: false });
     }
   }
+  addChartToDashboardTab = async (
+    dashboardId: number,
+    chartId: number,
+    tabId: string,
+  ) => {

Review Comment:
   Missing documentation for the `addChartToDashboardTab` method. This complex 
method modifies dashboard layout structure and should include JSDoc comments 
explaining:
   - The purpose of the method
   - Parameters and their meanings
   - The return value
   - The layout structure it creates (parents hierarchy, row/chart relationship)
   - Potential errors and when they might occur



##########
superset-frontend/src/explore/components/SaveModal.tsx:
##########
@@ -331,6 +453,64 @@ class SaveModal extends Component<SaveModalProps, 
SaveModalState> {
       totalCount: count,
     };
   };
+  loadTabs = async (dashboardId: number) => {
+    try {
+      const response = await SupersetClient.get({
+        endpoint: `/api/v1/dashboard/${dashboardId}/tabs`,
+      });
+
+      const { result } = response.json;
+      const tabTree = result.tab_tree || [];
+
+      const convertToTreeData = (nodes: TabNode[]): TreeDataNode[] =>
+        nodes.map(node => ({
+          value: node.value,
+          title: node.title,
+          key: node.value,
+          children:
+            node.children && node.children.length > 0
+              ? convertToTreeData(node.children)
+              : undefined,
+        }));
+
+      const treeData = convertToTreeData(tabTree);
+      this.setState({ tabsData: treeData });
+      return treeData;
+    } catch (error) {
+      logging.error('Error loading tabs:', error);
+      this.setState({ tabsData: [] });
+      return [];
+    }
+  };

Review Comment:
   Missing test coverage for the `loadTabs` method. This method handles API 
calls and data transformation. Tests should verify:
   - Successful loading and conversion of tab data
   - Error handling when API call fails
   - Proper conversion from TabNode to TreeDataNode structure
   - State updates with correct data



##########
superset-frontend/src/explore/components/SaveModal.tsx:
##########
@@ -289,6 +326,91 @@ class SaveModal extends Component<SaveModalProps, 
SaveModalState> {
       this.setState({ isLoading: false });
     }
   }
+  addChartToDashboardTab = async (
+    dashboardId: number,
+    chartId: number,
+    tabId: string,
+  ) => {
+    try {
+      const dashboardResponse = await SupersetClient.get({
+        endpoint: `/api/v1/dashboard/${dashboardId}`,
+      });
+
+      const dashboard = dashboardResponse.json.result;
+
+      let positionJson = dashboard.position_json;
+      if (typeof positionJson === 'string') {
+        positionJson = JSON.parse(positionJson);
+      }
+      positionJson = positionJson || {};
+
+      const updatedPositionJson = JSON.parse(JSON.stringify(positionJson));
+
+      const chartKey = `CHART-${chartId}`;
+      const rowIndex = this.findNextRowPosition(updatedPositionJson);
+      const rowKey = `ROW-${rowIndex}`;
+
+      updatedPositionJson[chartKey] = {
+        type: 'CHART',
+        id: chartKey,
+        children: [],
+        parents: ['ROOT_ID', 'GRID_ID', rowKey],
+        meta: {
+          width: 4,
+          height: 50,
+          chartId: chartId,
+          sliceName: `Chart ${chartId}`,
+        },
+      };
+
+      updatedPositionJson[rowKey] = {
+        type: 'ROW',
+        id: rowKey,
+        children: [chartKey],
+        parents: ['ROOT_ID', 'GRID_ID', tabId],
+        meta: {
+          background: 'BACKGROUND_TRANSPARENT',
+        },
+      };
+
+      if (updatedPositionJson[tabId]) {
+        if (!updatedPositionJson[tabId].children) {
+          updatedPositionJson[tabId].children = [];
+        }
+        updatedPositionJson[tabId].children.push(rowKey);
+      } else {
+        throw new Error(`Tab ${tabId} not found in positionJson`);
+      }
+
+      const response = await SupersetClient.put({
+        endpoint: `/api/v1/dashboard/${dashboardId}`,
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({
+          position_json: JSON.stringify(updatedPositionJson),
+        }),
+      });
+
+      return response;
+    } catch (error) {
+      throw new Error('Error adding chart to dashboard tab:', error);
+      throw error;
+    }
+  };

Review Comment:
   Missing test coverage for the `addChartToDashboardTab` method. This is a 
critical new method that modifies dashboard layout and makes API calls. Tests 
should verify:
   - Correct construction of position_json structure
   - Proper handling of parent relationships in the layout hierarchy
   - Error handling when the tab doesn't exist
   - API call with correct parameters



##########
superset-frontend/src/explore/types.ts:
##########
@@ -123,3 +123,16 @@ export interface ExplorePageState {
   };
   sliceEntities?: JsonObject; // propagated from Dashboard view
 }
+
+export interface TabNode {
+  value: string;
+  title: string;
+  children?: TabNode[];
+}
+
+export interface TreeDataNode {
+  value: string;
+  title: string;
+  key: string;
+  children?: TreeDataNode[];

Review Comment:
   [nitpick] Inconsistent naming: The type is named `TreeDataNode` but 
`TreeSelect` component from Ant Design typically uses `treeData` prop with 
nodes that have standard properties. Consider renaming to `TabTreeNode` to 
better reflect that this represents tab data in tree structure, making it more 
domain-specific and clearer.
   ```suggestion
   export interface TabTreeNode {
     value: string;
     title: string;
     key: string;
     children?: TabTreeNode[];
   ```



##########
superset-frontend/src/explore/components/SaveModal.tsx:
##########
@@ -331,6 +453,64 @@ class SaveModal extends Component<SaveModalProps, 
SaveModalState> {
       totalCount: count,
     };
   };
+  loadTabs = async (dashboardId: number) => {
+    try {
+      const response = await SupersetClient.get({
+        endpoint: `/api/v1/dashboard/${dashboardId}/tabs`,
+      });
+
+      const { result } = response.json;
+      const tabTree = result.tab_tree || [];

Review Comment:
   Missing error handling: If the API endpoint 
`/api/v1/dashboard/${dashboardId}/tabs` doesn't exist or returns an unexpected 
response structure, the code assumes `result.tab_tree` will be available. While 
there's a catch block, it would be safer to validate the response structure 
before accessing `result.tab_tree`:
   ```typescript
   const { result } = response.json;
   if (!result || !Array.isArray(result.tab_tree)) {
     logging.warn('Invalid tabs response format');
     this.setState({ tabsData: [] });
     return [];
   }
   const tabTree = result.tab_tree;
   ```
   ```suggestion
         if (!result || !Array.isArray(result.tab_tree)) {
           logging.warn('Invalid tabs response format');
           this.setState({ tabsData: [] });
           return [];
         }
         const tabTree = result.tab_tree;
   ```



##########
superset-frontend/src/explore/components/SaveModal.tsx:
##########
@@ -289,6 +326,91 @@ class SaveModal extends Component<SaveModalProps, 
SaveModalState> {
       this.setState({ isLoading: false });
     }
   }
+  addChartToDashboardTab = async (
+    dashboardId: number,
+    chartId: number,
+    tabId: string,
+  ) => {
+    try {
+      const dashboardResponse = await SupersetClient.get({
+        endpoint: `/api/v1/dashboard/${dashboardId}`,
+      });
+
+      const dashboard = dashboardResponse.json.result;
+
+      let positionJson = dashboard.position_json;
+      if (typeof positionJson === 'string') {
+        positionJson = JSON.parse(positionJson);
+      }
+      positionJson = positionJson || {};
+
+      const updatedPositionJson = JSON.parse(JSON.stringify(positionJson));

Review Comment:
   [nitpick] Unnecessary deep clone: `JSON.parse(JSON.stringify(positionJson))` 
creates a deep copy even though you're directly mutating the cloned object 
afterward. Consider directly working with the `positionJson` object or use 
structured clone if available. This improves performance and readability.



##########
superset-frontend/src/explore/components/SaveModal.tsx:
##########
@@ -289,6 +326,91 @@ class SaveModal extends Component<SaveModalProps, 
SaveModalState> {
       this.setState({ isLoading: false });
     }
   }
+  addChartToDashboardTab = async (
+    dashboardId: number,
+    chartId: number,
+    tabId: string,
+  ) => {
+    try {
+      const dashboardResponse = await SupersetClient.get({
+        endpoint: `/api/v1/dashboard/${dashboardId}`,
+      });
+
+      const dashboard = dashboardResponse.json.result;
+
+      let positionJson = dashboard.position_json;
+      if (typeof positionJson === 'string') {
+        positionJson = JSON.parse(positionJson);
+      }
+      positionJson = positionJson || {};
+
+      const updatedPositionJson = JSON.parse(JSON.stringify(positionJson));
+
+      const chartKey = `CHART-${chartId}`;
+      const rowIndex = this.findNextRowPosition(updatedPositionJson);
+      const rowKey = `ROW-${rowIndex}`;
+
+      updatedPositionJson[chartKey] = {
+        type: 'CHART',
+        id: chartKey,
+        children: [],
+        parents: ['ROOT_ID', 'GRID_ID', rowKey],
+        meta: {
+          width: 4,
+          height: 50,

Review Comment:
   [nitpick] The hardcoded chart dimensions (width: 4, height: 50) may not be 
appropriate for all chart types or dashboard layouts. Consider making these 
configurable or deriving them from the dashboard's grid settings or chart 
metadata.



##########
superset-frontend/src/explore/components/SaveModal.tsx:
##########
@@ -262,6 +282,20 @@ class SaveModal extends Component<SaveModalProps, 
SaveModalState> {
               }
             : null,
         );
+        if (dashboard && selectedTabId) {
+          try {
+            await this.addChartToDashboardTab(
+              dashboard.id,
+              value.id,
+              selectedTabId,
+            );
+          } catch (error) {
+            logging.error('Error adding chart to dashboard tab:', error);
+            this.props.addDangerToast(
+              t('Chart was saved but could not be added to the selected tab.'),
+            );
+          }
+        }

Review Comment:
   Missing test coverage for integration between save action and tab placement. 
Tests should verify:
   - Chart is added to selected tab when saving to a dashboard
   - URL includes tab hash when navigating to dashboard
   - Error toast is shown when tab placement fails but save succeeds
   - selectedTabId is correctly extracted and passed to addChartToDashboardTab



##########
superset-frontend/src/explore/components/SaveModal.tsx:
##########
@@ -152,10 +158,20 @@ class SaveModal extends Component<SaveModalProps, 
SaveModalState> {
     this.setState({ newSliceName: event.target.value });
   }
 
-  onDashboardChange(dashboard: { label: string; value: string | number }) {
-    this.setState({ dashboard });
-  }
+  onDashboardChange = async (dashboard: {

Review Comment:
   [nitpick] Inconsistent method binding: The class uses arrow functions for 
`onDashboardChange` (line 161) and `onTabChange` (line 486), but uses explicit 
binding in the constructor for older methods (lines 104-107). For consistency, 
either use arrow functions for all event handlers or bind them all in the 
constructor. Arrow functions are the modern React pattern and eliminate the 
need for explicit binding.



##########
superset-frontend/src/explore/components/SaveModal.tsx:
##########
@@ -289,6 +326,91 @@ class SaveModal extends Component<SaveModalProps, 
SaveModalState> {
       this.setState({ isLoading: false });
     }
   }
+  addChartToDashboardTab = async (
+    dashboardId: number,
+    chartId: number,
+    tabId: string,
+  ) => {
+    try {
+      const dashboardResponse = await SupersetClient.get({
+        endpoint: `/api/v1/dashboard/${dashboardId}`,
+      });
+
+      const dashboard = dashboardResponse.json.result;
+
+      let positionJson = dashboard.position_json;
+      if (typeof positionJson === 'string') {
+        positionJson = JSON.parse(positionJson);
+      }
+      positionJson = positionJson || {};
+
+      const updatedPositionJson = JSON.parse(JSON.stringify(positionJson));
+
+      const chartKey = `CHART-${chartId}`;
+      const rowIndex = this.findNextRowPosition(updatedPositionJson);
+      const rowKey = `ROW-${rowIndex}`;
+
+      updatedPositionJson[chartKey] = {
+        type: 'CHART',
+        id: chartKey,
+        children: [],
+        parents: ['ROOT_ID', 'GRID_ID', rowKey],
+        meta: {
+          width: 4,
+          height: 50,
+          chartId: chartId,
+          sliceName: `Chart ${chartId}`,
+        },
+      };
+
+      updatedPositionJson[rowKey] = {
+        type: 'ROW',
+        id: rowKey,
+        children: [chartKey],
+        parents: ['ROOT_ID', 'GRID_ID', tabId],
+        meta: {
+          background: 'BACKGROUND_TRANSPARENT',
+        },
+      };
+
+      if (updatedPositionJson[tabId]) {
+        if (!updatedPositionJson[tabId].children) {
+          updatedPositionJson[tabId].children = [];
+        }
+        updatedPositionJson[tabId].children.push(rowKey);
+      } else {
+        throw new Error(`Tab ${tabId} not found in positionJson`);
+      }
+
+      const response = await SupersetClient.put({
+        endpoint: `/api/v1/dashboard/${dashboardId}`,
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({
+          position_json: JSON.stringify(updatedPositionJson),
+        }),
+      });
+
+      return response;
+    } catch (error) {
+      throw new Error('Error adding chart to dashboard tab:', error);
+      throw error;

Review Comment:
   The error message construction is incorrect. The `Error` constructor expects 
a string message as the first parameter, but you're passing the error object as 
a second parameter which will be ignored. This should be:
   ```typescript
   throw new Error(`Error adding chart to dashboard tab: ${error}`);
   ```
   or simply re-throw the original error:
   ```typescript
   throw error;
   ```
   ```suggestion
         throw new Error(`Error adding chart to dashboard tab: ${error}`);
   ```



##########
superset-frontend/src/explore/components/SaveModal.tsx:
##########
@@ -152,10 +158,20 @@ class SaveModal extends Component<SaveModalProps, 
SaveModalState> {
     this.setState({ newSliceName: event.target.value });
   }
 
-  onDashboardChange(dashboard: { label: string; value: string | number }) {
-    this.setState({ dashboard });
-  }
+  onDashboardChange = async (dashboard: {
+    label: string;
+    value: string | number;
+  }) => {
+    this.setState({
+      dashboard,
+      tabsData: [],
+      selectedTab: undefined,
+    });
 
+    if (typeof dashboard.value === 'number') {
+      await this.loadTabs(dashboard.value);
+    }
+  };

Review Comment:
   Potential race condition: If a user quickly changes the dashboard selection 
multiple times, multiple `loadTabs` calls could be in flight simultaneously. 
The last completed call will set the state, but it may not correspond to the 
currently selected dashboard. Consider using an AbortController or tracking the 
latest request to ignore stale responses:
   ```typescript
   private currentLoadTabsRequest = 0;
   
   onDashboardChange = async (dashboard) => {
     const requestId = ++this.currentLoadTabsRequest;
     // ... existing code ...
     if (typeof dashboard.value === 'number') {
       const tabs = await this.loadTabs(dashboard.value);
       if (requestId === this.currentLoadTabsRequest) {
         this.setState({ tabsData: tabs });
       }
     }
   };
   ```



##########
superset-frontend/src/explore/components/SaveModal.test.jsx:
##########
@@ -345,3 +357,62 @@ test('removes form_data_key from URL parameters after 
save', () => {
   expect(result.get('slice_id')).toEqual('1');
   expect(result.get('save_action')).toEqual('overwrite');
 });
+
+test('disables tab selector when no dashboard selected', () => {
+  const { getByRole, getByTestId } = setup();
+  fireEvent.click(getByRole('radio', { name: 'Save as...' }));
+  const tabSelector = getByTestId('mock-tree-select');
+  expect(tabSelector).toBeInTheDocument();
+  expect(tabSelector).toBeDisabled();
+});
+
+test('renders tab selector when saving as ', async () => {
+  const { getByRole, getByTestId } = setup();
+  fireEvent.click(getByRole('radio', { name: 'Save as...' }));
+  const selection = getByTestId('mock-async-select');
+  fireEvent.change(selection, { target: { value: '1' } });
+  const tabSelector = getByTestId('mock-tree-select');
+  expect(tabSelector).toBeInTheDocument();
+  expect(tabSelector).toBeDisabled();
+});
+
+test('onDashboardChange triggers tabs load for existing dashboard', async () 
=> {
+  const dashboardId = mockEvent.value;
+
+  fetchMock.get(`glob:*/api/v1/dashboard/${dashboardId}/tabs`, {
+    json: {
+      result: {
+        tab_tree: [{ value: 'tab1', title: 'Main Tab' }],
+      },
+    },
+  });
+  const component = new PureSaveModal(defaultProps);
+  const loadTabsMock = jest
+    .fn()
+    .mockResolvedValue([{ value: 'tab1', title: 'Main Tab' }]);
+  component.loadTabs = loadTabsMock;
+  await component.onDashboardChange({
+    value: dashboardId,
+    label: 'Test Dashboard',
+  });
+  expect(loadTabsMock).toHaveBeenCalledWith(dashboardId);
+});
+
+test('onTabChange updates selectedTab state', () => {
+  const component = new PureSaveModal(defaultProps);
+
+  const setStateMock = jest.fn();
+  component.setState = setStateMock;
+
+  component.state.tabsData = [
+    { value: 'tab1', title: 'Main Tab', key: 'tab1' },
+    { value: 'tab2', title: 'Analytics Tab', key: 'tab2' },
+  ];

Review Comment:
   Use `setState` instead of directly modifying component state.
   ```suggestion
     component.setState({
       tabsData: [
         { value: 'tab1', title: 'Main Tab', key: 'tab1' },
         { value: 'tab2', title: 'Analytics Tab', key: 'tab2' },
       ],
     });
   ```



##########
superset-frontend/src/explore/components/SaveModal.test.jsx:
##########
@@ -345,3 +357,62 @@ test('removes form_data_key from URL parameters after 
save', () => {
   expect(result.get('slice_id')).toEqual('1');
   expect(result.get('save_action')).toEqual('overwrite');
 });
+
+test('disables tab selector when no dashboard selected', () => {
+  const { getByRole, getByTestId } = setup();
+  fireEvent.click(getByRole('radio', { name: 'Save as...' }));
+  const tabSelector = getByTestId('mock-tree-select');
+  expect(tabSelector).toBeInTheDocument();
+  expect(tabSelector).toBeDisabled();
+});
+
+test('renders tab selector when saving as ', async () => {

Review Comment:
   Typo in test name: "saving as " has a trailing space. Should be "saving as" 
or "saving as new chart".
   ```suggestion
   test('renders tab selector when saving as', async () => {
   ```



##########
superset-frontend/src/explore/components/SaveModal.tsx:
##########
@@ -231,6 +248,9 @@ class SaveModal extends Component<SaveModalProps, 
SaveModalState> {
             ? sliceDashboards
             : [...sliceDashboards, dashboard.id];
           formData.dashboards = sliceDashboards;
+          if (this.state.action === 'saveas') {
+            selectedTabId = this.state.selectedTab?.value as string;

Review Comment:
   The condition on line 251 only adds the chart to the selected tab when 
`action === 'saveas'`, but line 586 shows that the tab selector is only 
displayed for 'saveas' action. This means charts saved with the 'overwrite' 
action will be added to the dashboard but not to any specific tab. Consider if 
this is the intended behavior, or if the condition should check 
`this.state.selectedTab` instead.
   ```suggestion
             if (this.state.selectedTab) {
               selectedTabId = this.state.selectedTab.value as string;
   ```



##########
superset-frontend/src/explore/components/SaveModal.tsx:
##########
@@ -72,6 +75,8 @@ type SaveModalState = {
   isLoading: boolean;
   saveStatus?: string | null;
   dashboard?: { label: string; value: string | number };
+  selectedTab?: { label: string; value: string | number };
+  tabsData: Array<{ value: string; title: string; key: string }>;

Review Comment:
   Type inconsistency: `tabsData` state is typed as `Array<{ value: string; 
title: string; key: string }>` (line 79), but it's actually populated with 
`TreeDataNode[]` which has a `children` property (lines 465-474). The state 
type should be `TreeDataNode[]` to match the actual data structure.
   ```suggestion
     tabsData: TreeDataNode[];
   ```



##########
superset-frontend/src/explore/components/SaveModal.tsx:
##########
@@ -289,6 +326,91 @@ class SaveModal extends Component<SaveModalProps, 
SaveModalState> {
       this.setState({ isLoading: false });
     }
   }
+  addChartToDashboardTab = async (
+    dashboardId: number,
+    chartId: number,
+    tabId: string,
+  ) => {
+    try {
+      const dashboardResponse = await SupersetClient.get({
+        endpoint: `/api/v1/dashboard/${dashboardId}`,
+      });
+
+      const dashboard = dashboardResponse.json.result;
+
+      let positionJson = dashboard.position_json;
+      if (typeof positionJson === 'string') {
+        positionJson = JSON.parse(positionJson);
+      }
+      positionJson = positionJson || {};
+
+      const updatedPositionJson = JSON.parse(JSON.stringify(positionJson));
+
+      const chartKey = `CHART-${chartId}`;
+      const rowIndex = this.findNextRowPosition(updatedPositionJson);
+      const rowKey = `ROW-${rowIndex}`;
+
+      updatedPositionJson[chartKey] = {
+        type: 'CHART',
+        id: chartKey,
+        children: [],
+        parents: ['ROOT_ID', 'GRID_ID', rowKey],

Review Comment:
   The parents array is inconsistent with the actual hierarchy. If this chart 
is being added to a specific tab, the parents should be `['ROOT_ID', tabId]`, 
not `['ROOT_ID', 'GRID_ID', rowKey]`. The current structure suggests GRID_ID is 
a parent, which may not align with the tab-based layout structure.
   ```suggestion
           parents: ['ROOT_ID', tabId],
   ```



##########
superset-frontend/src/explore/components/SaveModal.tsx:
##########
@@ -289,6 +326,91 @@ class SaveModal extends Component<SaveModalProps, 
SaveModalState> {
       this.setState({ isLoading: false });
     }
   }
+  addChartToDashboardTab = async (
+    dashboardId: number,
+    chartId: number,
+    tabId: string,
+  ) => {
+    try {
+      const dashboardResponse = await SupersetClient.get({
+        endpoint: `/api/v1/dashboard/${dashboardId}`,
+      });
+
+      const dashboard = dashboardResponse.json.result;
+
+      let positionJson = dashboard.position_json;
+      if (typeof positionJson === 'string') {
+        positionJson = JSON.parse(positionJson);
+      }
+      positionJson = positionJson || {};
+
+      const updatedPositionJson = JSON.parse(JSON.stringify(positionJson));
+
+      const chartKey = `CHART-${chartId}`;
+      const rowIndex = this.findNextRowPosition(updatedPositionJson);
+      const rowKey = `ROW-${rowIndex}`;
+
+      updatedPositionJson[chartKey] = {
+        type: 'CHART',
+        id: chartKey,
+        children: [],
+        parents: ['ROOT_ID', 'GRID_ID', rowKey],
+        meta: {
+          width: 4,
+          height: 50,
+          chartId: chartId,
+          sliceName: `Chart ${chartId}`,

Review Comment:
   The `meta` object is missing the `uuid` property which is required according 
to the `LayoutItemMeta` type definition (line 217 in dashboard/types.ts shows 
uuid is required). This will cause type inconsistencies and potential runtime 
errors. Add a uuid property:
   ```typescript
   meta: {
     width: 4,
     height: 50,
     chartId: chartId,
     sliceName: `Chart ${chartId}`,
     uuid: `${chartKey}-${Date.now()}`, // or use a proper UUID generator
   },
   ```
   ```suggestion
             sliceName: `Chart ${chartId}`,
             uuid: `${chartKey}-${Date.now()}`,
   ```



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