This is an automated email from the ASF dual-hosted git repository.

rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 8d25777b995 test(plugin-chart-table): cover server-side sort, query 
mode controls, and sort ordering (#44845)
8d25777b995 is described below

commit 8d25777b99577fd3e5a9497f50e99bc9de653e85
Author: Joe Li <[email protected]>
AuthorDate: Fri Oct 2 10:25:11 2026 -0700

    test(plugin-chart-table): cover server-side sort, query mode controls, and 
sort ordering (#44845)
    
    Co-authored-by: Claude Opus 5.5 <[email protected]>
---
 .../plugin-chart-table/test/TableChart.test.tsx    | 88 ++++++++++++++++++++++
 .../plugin-chart-table/test/buildQuery.test.ts     | 42 +++++++++++
 .../plugin-chart-table/test/controlPanel.test.ts   | 55 ++++++++++++++
 3 files changed, 185 insertions(+)

diff --git 
a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx 
b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
index a22b0452ad5..fb21635a0bb 100644
--- a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
@@ -3572,3 +3572,91 @@ test('TableChart should NOT emit cross-filter when 
clicking a cell in a not-filt
   );
   expect(crossFilterCall).toBeUndefined();
 });
+
+test.each([
+  { orderDesc: true, firstClickDesc: true },
+  { orderDesc: false, firstClickDesc: false },
+])(
+  'pushes the clicked column sort to the server own state when server 
pagination is enabled (order_desc $orderDesc)',
+  ({ orderDesc, firstClickDesc }) => {
+    const setDataMask = jest.fn();
+    const props = transformProps({
+      ...testData.raw,
+      rawFormData: {
+        ...testData.raw.rawFormData,
+        server_pagination: true,
+        order_desc: orderDesc,
+      },
+      hooks: { setDataMask },
+      queriesData: [
+        {
+          ...testData.raw.queriesData[0],
+          colnames: ['name'],
+          coltypes: [GenericDataType.String],
+          data: [{ name: 'Michael' }, { name: 'John' }],
+        },
+      ],
+    });
+    render(
+      ProviderWrapper({
+        children: (
+          <TableChart {...props} setDataMask={setDataMask} sticky={false} />
+        ),
+      }),
+    );
+
+    const lastPushedSortBy = () =>
+      setDataMask.mock.calls
+        .map(([mask]) => mask?.ownState?.sortBy)
+        .filter(Array.isArray)
+        .at(-1);
+
+    expect(lastPushedSortBy()).toBeUndefined();
+
+    // The first click sorts in the configured default direction and the
+    // second click flips it; each change is pushed with the column key.
+    fireEvent.click(screen.getByText('name'));
+    expect(lastPushedSortBy()).toEqual([
+      expect.objectContaining({ key: 'name', desc: firstClickDesc }),
+    ]);
+
+    fireEvent.click(screen.getByText('name'));
+    expect(lastPushedSortBy()).toEqual([
+      expect.objectContaining({ key: 'name', desc: !firstClickDesc }),
+    ]);
+  },
+);
+
+test('does not push a column sort to the server own state when server 
pagination is disabled', () => {
+  const setDataMask = jest.fn();
+  const props = transformProps({
+    ...testData.raw,
+    rawFormData: {
+      ...testData.raw.rawFormData,
+      server_pagination: false,
+    },
+    hooks: { setDataMask },
+    queriesData: [
+      {
+        ...testData.raw.queriesData[0],
+        colnames: ['name'],
+        coltypes: [GenericDataType.String],
+        data: [{ name: 'Michael' }, { name: 'John' }],
+      },
+    ],
+  });
+  render(
+    ProviderWrapper({
+      children: (
+        <TableChart {...props} setDataMask={setDataMask} sticky={false} />
+      ),
+    }),
+  );
+
+  fireEvent.click(screen.getByText('name'));
+
+  const pushedSort = setDataMask.mock.calls.some(([mask]) =>
+    Object.prototype.hasOwnProperty.call(mask?.ownState ?? {}, 'sortBy'),
+  );
+  expect(pushedSort).toBe(false);
+});
diff --git 
a/superset-frontend/plugins/plugin-chart-table/test/buildQuery.test.ts 
b/superset-frontend/plugins/plugin-chart-table/test/buildQuery.test.ts
index 8f9e0481451..33d449ae09d 100644
--- a/superset-frontend/plugins/plugin-chart-table/test/buildQuery.test.ts
+++ b/superset-frontend/plugins/plugin-chart-table/test/buildQuery.test.ts
@@ -212,6 +212,48 @@ describe('plugin-chart-table', () => {
       expect(query.columns).toEqual(['rawcol']);
       expect(query.post_processing).toEqual([]);
     });
+
+    test.each([
+      { orderDesc: true, expectedAscending: false },
+      { orderDesc: false, expectedAscending: true },
+    ])(
+      'orders by the sort-by metric in aggregate mode when order_desc is 
$orderDesc',
+      ({ orderDesc, expectedAscending }) => {
+        const query = buildQueryCached({
+          ...basicFormData,
+          query_mode: QueryMode.Aggregate,
+          groupby: ['col1'],
+          metrics: ['first_metric', 'sort_metric'],
+          timeseries_limit_metric: 'sort_metric',
+          order_desc: orderDesc,
+        }).queries[0];
+        expect(query.orderby).toEqual([['sort_metric', expectedAscending]]);
+      },
+    );
+
+    test('orders by the first metric descending in aggregate mode without a 
sort-by metric', () => {
+      const query = buildQueryCached({
+        ...basicFormData,
+        query_mode: QueryMode.Aggregate,
+        groupby: ['col1'],
+        metrics: ['first_metric', 'second_metric'],
+        order_desc: false,
+      }).queries[0];
+      expect(query.orderby).toEqual([['first_metric', false]]);
+    });
+
+    test('maps order_by_cols to orderby in raw records mode', () => {
+      const query = buildQueryCached({
+        ...basicFormData,
+        query_mode: QueryMode.Raw,
+        columns: ['col1', 'col2'],
+        order_by_cols: ['["col1", true]', '["col2", false]'],
+      }).queries[0];
+      expect(query.orderby).toEqual([
+        ['col1', true],
+        ['col2', false],
+      ]);
+    });
     test('should prefer extra_form_data.time_grain_sqla over 
formData.time_grain_sqla', () => {
       const query = buildQueryCached({
         ...basicFormData,
diff --git 
a/superset-frontend/plugins/plugin-chart-table/test/controlPanel.test.ts 
b/superset-frontend/plugins/plugin-chart-table/test/controlPanel.test.ts
index af51d5bc117..2cae108e67c 100644
--- a/superset-frontend/plugins/plugin-chart-table/test/controlPanel.test.ts
+++ b/superset-frontend/plugins/plugin-chart-table/test/controlPanel.test.ts
@@ -290,3 +290,58 @@ test('time_grain_sqla is hidden in raw records mode for an 
adhoc dimension', ()
     ),
   ).toBe(false);
 });
+
+/**
+ * Finds the visibility function of a control whether it is declared through a
+ * full `config` (e.g. `all_columns`) or through an `override` of a shared
+ * control (e.g. `groupby`, `metrics`).
+ */
+function getModeVisibility(controlName: string): VisibilityFn {
+  const item = (config.controlPanelSections || [])
+    .flatMap(section => section?.controlSetRows || [])
+    .flat()
+    .find(
+      c =>
+        typeof c === 'object' &&
+        c !== null &&
+        'name' in c &&
+        (c as { name: string }).name === controlName,
+    ) as
+    | {
+        config?: { visibility?: VisibilityFn };
+        override?: { visibility?: VisibilityFn };
+      }
+    | undefined;
+  const visibility = item?.config?.visibility ?? item?.override?.visibility;
+  if (typeof visibility !== 'function') {
+    throw new Error(`Control "${controlName}" with visibility not found`);
+  }
+  return visibility;
+}
+
+const modeProps = (mode: QueryMode): ControlPanelsContainerProps =>
+  ({
+    controls: { query_mode: { value: mode } },
+  }) as unknown as ControlPanelsContainerProps;
+
+test('raw mode shows the raw-only controls and hides the aggregate-only 
controls', () => {
+  const props = modeProps(QueryMode.Raw);
+
+  expect(getModeVisibility('all_columns')(props)).toBe(true);
+  expect(getModeVisibility('order_by_cols')(props)).toBe(true);
+  expect(getModeVisibility('groupby')(props)).toBe(false);
+  expect(getModeVisibility('metrics')(props)).toBe(false);
+  expect(getModeVisibility('percent_metrics')(props)).toBe(false);
+  expect(getModeVisibility('timeseries_limit_metric')(props)).toBe(false);
+});
+
+test('aggregate mode shows the aggregate-only controls and hides the raw-only 
controls', () => {
+  const props = modeProps(QueryMode.Aggregate);
+
+  expect(getModeVisibility('groupby')(props)).toBe(true);
+  expect(getModeVisibility('metrics')(props)).toBe(true);
+  expect(getModeVisibility('percent_metrics')(props)).toBe(true);
+  expect(getModeVisibility('timeseries_limit_metric')(props)).toBe(true);
+  expect(getModeVisibility('all_columns')(props)).toBe(false);
+  expect(getModeVisibility('order_by_cols')(props)).toBe(false);
+});

Reply via email to