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


##########
superset-embedded-sdk/src/index.ts:
##########
@@ -93,6 +93,7 @@ export type EmbeddedDashboard = {
   ) => void;
   getDataMask: () => Promise<Record<string, any>>;
   getChartStates: () => Promise<Record<string, any>>;
+  getChartDataPayloads: (chartId?: number) => Promise<Record<string, any>>;

Review Comment:
   The type definition should match the intended API design. If the 
implementation expects `{ chartId?: number }` as a parameter object, the type 
should be:
   ```typescript
   getChartDataPayloads: (params?: { chartId?: number }) => 
Promise<Record<string, any>>;
   ```
   This would be more consistent with `getDashboardPermalink` which accepts a 
structured parameter `(anchor: string)` where `anchor` is actually destructured 
from an object parameter in the implementation.
   ```suggestion
     getChartDataPayloads: (params?: { chartId?: number }) => 
Promise<Record<string, any>>;
   ```



##########
superset-embedded-sdk/src/index.ts:
##########
@@ -249,6 +250,8 @@ export async function embedDashboard({
   const getActiveTabs = () => ourPort.get<string[]>('getActiveTabs');
   const getDataMask = () => ourPort.get<Record<string, any>>('getDataMask');
   const getChartStates = () => ourPort.get<Record<string, 
any>>('getChartStates');
+  const getChartDataPayloads = (chartId?: number) =>
+    ourPort.get<Record<string, any>>('getChartDataPayloads', { chartId });

Review Comment:
   The SDK function signature is inconsistent with the implementation. The SDK 
signature accepts `chartId?: number` as a simple parameter, but the 
implementation in `api.tsx` expects an object parameter `{ chartId?: number }`. 
This mismatch will cause the function to not work correctly. 
   
   The SDK should either:
   1. Accept an object: `getChartDataPayloads: (params?: { chartId?: number }) 
=> Promise<Record<string, any>>`
   2. Or change line 254 to: `ourPort.get<Record<string, 
any>>('getChartDataPayloads', chartId ? { chartId } : undefined)`
   
   Option 1 is more consistent with other methods like `getDashboardPermalink` 
which takes a structured parameter.
   ```suggestion
     const getChartDataPayloads = (params?: { chartId?: number }) =>
       ourPort.get<Record<string, any>>('getChartDataPayloads', params);
   ```



##########
superset-frontend/src/embedded/api.tsx:
##########
@@ -80,10 +92,103 @@ const getDataMask = () => store?.getState()?.dataMask || 
{};
 const getChartStates = () =>
   store?.getState()?.dashboardState?.chartStates || {};
 
+/**
+ * Get query context payloads for stateful charts (e.g., AG Grid tables).
+ * These payloads include dashboard filters and chart state (sorting, column 
order, etc.)
+ * and can be POSTed directly to /api/v1/chart/data for CSV export.
+ *
+ * @param chartId - Optional chart ID to get payload for a specific chart only
+ * @returns Record of chart IDs to their query context payloads
+ */
+const getChartDataPayloads = async ({
+  chartId,
+}: {
+  chartId?: number;
+}): Promise<Record<string, JsonObject>> => {
+  const state = store?.getState();
+  if (!state) return {};
+
+  const charts = state.charts || {};
+  const sliceEntities = state.sliceEntities?.slices || {};
+  const dataMask = state.dataMask || {};
+  const chartStates = state.dashboardState?.chartStates || {};
+  const chartConfiguration =
+    state.dashboardInfo?.metadata?.chart_configuration || {};
+  const nativeFilters = state.nativeFilters?.filters || {};
+  const allSliceIds = state.dashboardState?.sliceIds || [];
+  const colorScheme = state.dashboardState?.colorScheme;
+  const colorNamespace = state.dashboardState?.colorNamespace;
+
+  const payloads: Record<string, JsonObject> = {};
+
+  // Build payloads for each eligible chart
+  const chartEntries = Object.entries(charts).filter(([id]) => {
+    const numericId = Number(id);
+    const slice = sliceEntities[id];
+
+    // Filter: only stateful charts (e.g., AG Grid)
+    if (!slice || !hasChartStateConverter(slice.viz_type)) {
+      return false;
+    }
+
+    // Filter: specific chartId if provided
+    if (chartId !== undefined && numericId !== chartId) {
+      return false;
+    }
+
+    return true;
+  });
+
+  // Process charts sequentially to avoid race conditions
+  for (const [id, chart] of chartEntries) {
+    const numericId = Number(id);
+    const slice = sliceEntities[id];
+

Review Comment:
   [nitpick] The type assertion `(chart as JsonObject).form_data` is 
potentially unsafe. If `chart` doesn't have a `form_data` property or if 
`form_data` is not the expected type, this could lead to runtime errors. 
Consider adding a type guard or at least a check to ensure `chart.form_data` 
exists before accessing it:
   
   ```typescript
   const formData = chart && typeof chart === 'object' && 'form_data' in chart
     ? getFormDataWithExtraFilters({
         chart: { id: numericId, form_data: chart.form_data },
         // ... rest of params
       })
     : null;
   
   if (!formData) {
     continue; // or handle the error appropriately
   }
   ```
   ```suggestion
   
       // Type guard: ensure chart has form_data property
       if (
         !chart ||
         typeof chart !== 'object' ||
         !('form_data' in chart)
       ) {
         continue;
       }
   ```



##########
superset-frontend/src/embedded/api.tsx:
##########
@@ -80,10 +92,103 @@ const getDataMask = () => store?.getState()?.dataMask || 
{};
 const getChartStates = () =>
   store?.getState()?.dashboardState?.chartStates || {};
 
+/**
+ * Get query context payloads for stateful charts (e.g., AG Grid tables).
+ * These payloads include dashboard filters and chart state (sorting, column 
order, etc.)
+ * and can be POSTed directly to /api/v1/chart/data for CSV export.
+ *
+ * @param chartId - Optional chart ID to get payload for a specific chart only
+ * @returns Record of chart IDs to their query context payloads
+ */
+const getChartDataPayloads = async ({
+  chartId,
+}: {
+  chartId?: number;
+}): Promise<Record<string, JsonObject>> => {
+  const state = store?.getState();
+  if (!state) return {};
+
+  const charts = state.charts || {};
+  const sliceEntities = state.sliceEntities?.slices || {};
+  const dataMask = state.dataMask || {};
+  const chartStates = state.dashboardState?.chartStates || {};
+  const chartConfiguration =
+    state.dashboardInfo?.metadata?.chart_configuration || {};
+  const nativeFilters = state.nativeFilters?.filters || {};
+  const allSliceIds = state.dashboardState?.sliceIds || [];
+  const colorScheme = state.dashboardState?.colorScheme;
+  const colorNamespace = state.dashboardState?.colorNamespace;
+
+  const payloads: Record<string, JsonObject> = {};
+
+  // Build payloads for each eligible chart
+  const chartEntries = Object.entries(charts).filter(([id]) => {
+    const numericId = Number(id);
+    const slice = sliceEntities[id];
+
+    // Filter: only stateful charts (e.g., AG Grid)
+    if (!slice || !hasChartStateConverter(slice.viz_type)) {
+      return false;
+    }
+
+    // Filter: specific chartId if provided
+    if (chartId !== undefined && numericId !== chartId) {
+      return false;
+    }
+
+    return true;
+  });
+
+  // Process charts sequentially to avoid race conditions
+  for (const [id, chart] of chartEntries) {
+    const numericId = Number(id);
+    const slice = sliceEntities[id];
+
+    // Build enriched form_data with dashboard filters applied
+    const formData = getFormDataWithExtraFilters({
+      chart: { id: numericId, form_data: (chart as JsonObject).form_data },
+      chartConfiguration,
+      filters: getAppliedFilterValues(numericId),
+      colorScheme,
+      colorNamespace,
+      sliceId: numericId,
+      nativeFilters,
+      allSliceIds,
+      dataMask,
+      extraControls: {},
+    });
+
+    const chartState = chartStates[id]?.state;
+    const baseOwnState = dataMask[id]?.ownState || {};
+    const convertedState = chartState
+      ? convertChartStateToOwnState(slice.viz_type, chartState)
+      : {};
+
+    const ownState = {
+      ...baseOwnState,
+      ...convertedState,
+    };
+
+    const payload = await buildV1ChartDataPayload({
+      formData,
+      resultFormat: 'json',
+      resultType: 'results',
+      ownState,
+      setDataMask: null,
+      force: false,
+    });
+
+    payloads[id] = payload;
+  }

Review Comment:
   [nitpick] Processing charts sequentially in a for loop could be slow when 
there are many stateful charts. The comment mentions "avoid race conditions" 
but it's unclear what race condition is being avoided here since each iteration 
is independent. Consider using `Promise.all()` to process charts in parallel 
for better performance:
   
   ```typescript
   const payloadPromises = chartEntries.map(async ([id, chart]) => {
     // ... existing logic ...
     const payload = await buildV1ChartDataPayload({...});
     return [id, payload];
   });
   
   const results = await Promise.all(payloadPromises);
   results.forEach(([id, payload]) => {
     payloads[id] = payload;
   });
   ```
   
   This would significantly improve performance when multiple charts need their 
payloads generated.



##########
superset-frontend/src/embedded/api.tsx:
##########
@@ -80,10 +92,103 @@ const getDataMask = () => store?.getState()?.dataMask || 
{};
 const getChartStates = () =>
   store?.getState()?.dashboardState?.chartStates || {};
 
+/**
+ * Get query context payloads for stateful charts (e.g., AG Grid tables).
+ * These payloads include dashboard filters and chart state (sorting, column 
order, etc.)
+ * and can be POSTed directly to /api/v1/chart/data for CSV export.
+ *
+ * @param chartId - Optional chart ID to get payload for a specific chart only
+ * @returns Record of chart IDs to their query context payloads
+ */
+const getChartDataPayloads = async ({
+  chartId,
+}: {
+  chartId?: number;
+}): Promise<Record<string, JsonObject>> => {
+  const state = store?.getState();
+  if (!state) return {};
+
+  const charts = state.charts || {};
+  const sliceEntities = state.sliceEntities?.slices || {};
+  const dataMask = state.dataMask || {};
+  const chartStates = state.dashboardState?.chartStates || {};
+  const chartConfiguration =
+    state.dashboardInfo?.metadata?.chart_configuration || {};
+  const nativeFilters = state.nativeFilters?.filters || {};
+  const allSliceIds = state.dashboardState?.sliceIds || [];
+  const colorScheme = state.dashboardState?.colorScheme;
+  const colorNamespace = state.dashboardState?.colorNamespace;
+
+  const payloads: Record<string, JsonObject> = {};
+
+  // Build payloads for each eligible chart
+  const chartEntries = Object.entries(charts).filter(([id]) => {
+    const numericId = Number(id);
+    const slice = sliceEntities[id];
+
+    // Filter: only stateful charts (e.g., AG Grid)
+    if (!slice || !hasChartStateConverter(slice.viz_type)) {
+      return false;
+    }
+
+    // Filter: specific chartId if provided
+    if (chartId !== undefined && numericId !== chartId) {
+      return false;
+    }
+
+    return true;
+  });
+
+  // Process charts sequentially to avoid race conditions
+  for (const [id, chart] of chartEntries) {
+    const numericId = Number(id);
+    const slice = sliceEntities[id];
+
+    // Build enriched form_data with dashboard filters applied
+    const formData = getFormDataWithExtraFilters({
+      chart: { id: numericId, form_data: (chart as JsonObject).form_data },
+      chartConfiguration,
+      filters: getAppliedFilterValues(numericId),
+      colorScheme,
+      colorNamespace,
+      sliceId: numericId,
+      nativeFilters,
+      allSliceIds,
+      dataMask,
+      extraControls: {},
+    });
+
+    const chartState = chartStates[id]?.state;
+    const baseOwnState = dataMask[id]?.ownState || {};
+    const convertedState = chartState
+      ? convertChartStateToOwnState(slice.viz_type, chartState)
+      : {};
+
+    const ownState = {
+      ...baseOwnState,
+      ...convertedState,
+    };
+
+    const payload = await buildV1ChartDataPayload({
+      formData,
+      resultFormat: 'json',
+      resultType: 'results',
+      ownState,
+      setDataMask: null,
+      force: false,
+    });
+
+    payloads[id] = payload;

Review Comment:
   The function silently catches and swallows any errors that might occur 
during `buildV1ChartDataPayload` execution. If payload building fails for one 
chart, the function will continue processing other charts without any 
indication that an error occurred. Consider adding try-catch blocks around the 
payload building logic to handle errors gracefully and either:
   1. Log errors (if debug mode is available)
   2. Include error information in the returned object
   3. At minimum, skip the failed chart and continue with others while tracking 
which charts failed
   
   This would make debugging issues much easier for SDK users.
   ```suggestion
       try {
         const payload = await buildV1ChartDataPayload({
           formData,
           resultFormat: 'json',
           resultType: 'results',
           ownState,
           setDataMask: null,
           force: false,
         });
         payloads[id] = payload;
       } catch (error) {
         // Optionally log the error for debugging
         // You could check for a debug flag here if available
         console.error(`Error building payload for chart ${id}:`, error);
         payloads[id] = {
           error: true,
           message: error instanceof Error ? error.message : String(error),
         };
       }
   ```



##########
superset-frontend/src/embedded/api.tsx:
##########
@@ -80,10 +92,103 @@ const getDataMask = () => store?.getState()?.dataMask || 
{};
 const getChartStates = () =>
   store?.getState()?.dashboardState?.chartStates || {};
 
+/**
+ * Get query context payloads for stateful charts (e.g., AG Grid tables).
+ * These payloads include dashboard filters and chart state (sorting, column 
order, etc.)
+ * and can be POSTed directly to /api/v1/chart/data for CSV export.
+ *
+ * @param chartId - Optional chart ID to get payload for a specific chart only
+ * @returns Record of chart IDs to their query context payloads
+ */
+const getChartDataPayloads = async ({
+  chartId,
+}: {
+  chartId?: number;
+}): Promise<Record<string, JsonObject>> => {
+  const state = store?.getState();
+  if (!state) return {};
+
+  const charts = state.charts || {};
+  const sliceEntities = state.sliceEntities?.slices || {};
+  const dataMask = state.dataMask || {};
+  const chartStates = state.dashboardState?.chartStates || {};
+  const chartConfiguration =
+    state.dashboardInfo?.metadata?.chart_configuration || {};
+  const nativeFilters = state.nativeFilters?.filters || {};
+  const allSliceIds = state.dashboardState?.sliceIds || [];
+  const colorScheme = state.dashboardState?.colorScheme;
+  const colorNamespace = state.dashboardState?.colorNamespace;
+
+  const payloads: Record<string, JsonObject> = {};
+
+  // Build payloads for each eligible chart
+  const chartEntries = Object.entries(charts).filter(([id]) => {
+    const numericId = Number(id);
+    const slice = sliceEntities[id];
+
+    // Filter: only stateful charts (e.g., AG Grid)
+    if (!slice || !hasChartStateConverter(slice.viz_type)) {
+      return false;
+    }
+
+    // Filter: specific chartId if provided
+    if (chartId !== undefined && numericId !== chartId) {
+      return false;
+    }
+
+    return true;
+  });
+
+  // Process charts sequentially to avoid race conditions
+  for (const [id, chart] of chartEntries) {
+    const numericId = Number(id);
+    const slice = sliceEntities[id];
+
+    // Build enriched form_data with dashboard filters applied
+    const formData = getFormDataWithExtraFilters({
+      chart: { id: numericId, form_data: (chart as JsonObject).form_data },
+      chartConfiguration,
+      filters: getAppliedFilterValues(numericId),
+      colorScheme,
+      colorNamespace,
+      sliceId: numericId,
+      nativeFilters,
+      allSliceIds,
+      dataMask,
+      extraControls: {},
+    });
+
+    const chartState = chartStates[id]?.state;
+    const baseOwnState = dataMask[id]?.ownState || {};
+    const convertedState = chartState
+      ? convertChartStateToOwnState(slice.viz_type, chartState)
+      : {};
+
+    const ownState = {
+      ...baseOwnState,
+      ...convertedState,
+    };
+
+    const payload = await buildV1ChartDataPayload({
+      formData,
+      resultFormat: 'json',
+      resultType: 'results',
+      ownState,
+      setDataMask: null,
+      force: false,
+    });
+
+    payloads[id] = payload;
+  }
+

Review Comment:
   The function should handle the case when a specific `chartId` is provided 
but it doesn't exist in the state. Currently, if a user provides a `chartId` 
that doesn't exist, the function will return an empty object without any 
indication that the requested chart was not found. Consider adding a check 
after the loop to verify that at least one payload was generated when a 
specific `chartId` was requested, or return a more informative result that 
indicates whether the requested chart was found.
   ```suggestion
   
     // If a specific chartId was requested but no payload was generated, 
return an error object
     if (chartId !== undefined && Object.keys(payloads).length === 0) {
       return { error: 'Chart not found', chartId };
     }
   ```



##########
superset-frontend/src/embedded/api.tsx:
##########
@@ -80,10 +92,103 @@ const getDataMask = () => store?.getState()?.dataMask || 
{};
 const getChartStates = () =>
   store?.getState()?.dashboardState?.chartStates || {};
 
+/**
+ * Get query context payloads for stateful charts (e.g., AG Grid tables).
+ * These payloads include dashboard filters and chart state (sorting, column 
order, etc.)
+ * and can be POSTed directly to /api/v1/chart/data for CSV export.
+ *
+ * @param chartId - Optional chart ID to get payload for a specific chart only
+ * @returns Record of chart IDs to their query context payloads
+ */

Review Comment:
   The documentation says "These payloads include dashboard filters and chart 
state (sorting, column order, etc.)" but it only processes stateful charts 
(charts with registered state converters). This creates an inconsistency - the 
documentation implies all charts are included, but the implementation filters 
to only stateful charts. Consider clarifying this in the documentation, e.g.: 
"Get query context payloads for stateful charts only (e.g., AG Grid tables). 
Returns payloads only for charts that have registered state converters. 
Non-stateful charts will not be included in the result."



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