You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by jl...@apache.org on 2026/10/01 00:25:43 UTC
(superset) branch test-table-chart-gaps created (now 71d613dc84d)
This is an automated email from the ASF dual-hosted git repository.
sadpandajoe pushed a change to branch test-table-chart-gaps
in repository https://gitbox.apache.org/repos/asf/superset.git
at 71d613dc84d test(plugin-chart-table): cover server-side sort, query mode controls, and sort ordering
This branch includes the following new commits:
new 71d613dc84d test(plugin-chart-table): cover server-side sort, query mode controls, and sort ordering
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(superset) 01/01: test(plugin-chart-table): cover server-side sort, query mode controls, and sort ordering
Posted by jl...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
sadpandajoe pushed a commit to branch test-table-chart-gaps
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 71d613dc84da8cb7e56385637bc987fddbceee6c
Author: Joe Li <jo...@preset.io>
AuthorDate: Wed Sep 30 17:25:28 2026 -0700
test(plugin-chart-table): cover server-side sort, query mode controls, and sort ordering
Co-Authored-By: Claude Opus 5.5 <no...@anthropic.com>
---
.../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 01ab2b7651f..24c78b656af 100644
--- a/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
+++ b/superset-frontend/plugins/plugin-chart-table/test/TableChart.test.tsx
@@ -3246,3 +3246,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 275dbf46097..5a30f24e9c0 100644
--- a/superset-frontend/plugins/plugin-chart-table/test/controlPanel.test.ts
+++ b/superset-frontend/plugins/plugin-chart-table/test/controlPanel.test.ts
@@ -148,3 +148,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);
+});