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 a77fec68d40 fix(drill-detail): make page-size selector functionally 
adjustable (#37975)
a77fec68d40 is described below

commit a77fec68d405227f2dfceb176b0285d946fdfbec
Author: Varun Chawla <[email protected]>
AuthorDate: Tue May 12 13:39:41 2026 -0700

    fix(drill-detail): make page-size selector functionally adjustable (#37975)
    
    Co-authored-by: Claude Opus 4.7 <[email protected]>
    Co-authored-by: Evan Rusackas <[email protected]>
---
 .../Chart/DrillDetail/DrillDetailPane.test.tsx     | 78 ++++++++++++++++++++++
 .../Chart/DrillDetail/DrillDetailPane.tsx          | 23 +++++--
 2 files changed, 94 insertions(+), 7 deletions(-)

diff --git 
a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailPane.test.tsx 
b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailPane.test.tsx
index f9d9b924878..f470f18e50a 100644
--- 
a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailPane.test.tsx
+++ 
b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailPane.test.tsx
@@ -19,10 +19,12 @@
 import fetchMock from 'fetch-mock';
 import { QueryFormData, SupersetClient } from '@superset-ui/core';
 import {
+  fireEvent,
   render,
   screen,
   userEvent,
   waitFor,
+  within,
 } from 'spec/helpers/testing-library';
 import { getMockStoreWithNativeFilters } from 'spec/fixtures/mockStore';
 import chartQueries, { sliceId } from 'spec/fixtures/mockChartQueries';
@@ -119,6 +121,34 @@ const fetchWithData = () => {
   });
 };
 
+const fetchWithPaginatedData = () => {
+  setupDatasetEndpoint();
+  fetchMock.post(SAMPLES_ENDPOINT, {
+    result: {
+      total_count: 100,
+      data: [
+        {
+          year: 1996,
+          na_sales: 11.27,
+          eu_sales: 8.89,
+        },
+        {
+          year: 1989,
+          na_sales: 23.2,
+          eu_sales: 2.26,
+        },
+        {
+          year: 1999,
+          na_sales: 9,
+          eu_sales: 6.18,
+        },
+      ],
+      colnames: ['year', 'na_sales', 'eu_sales'],
+      coltypes: [0, 0, 0],
+    },
+  });
+};
+
 afterEach(() => {
   fetchMock.clearHistory().removeRoutes();
   supersetGetCache.clear();
@@ -254,6 +284,54 @@ describe('download actions', () => {
   });
 });
 
+test('should render pagination when results exceed page size', async () => {
+  // The "should render the error" test above leaves a SupersetClient.post
+  // rejection spy active (matching the existing pattern; "should use
+  // verbose_map" further down does the same cleanup). Reset it here so the
+  // fetch in this test actually returns data.
+  jest.restoreAllMocks();
+  fetchWithPaginatedData();
+  await waitForRender();
+  // With total_count=100 and page size=50, pagination should render
+  await waitFor(() => {
+    const pagination = document.querySelector('.ant-pagination');
+    expect(pagination).toBeTruthy();
+  });
+});
+
+test('should offer the full set of page-size options', async () => {
+  fetchWithPaginatedData();
+  await waitForRender();
+
+  // The page-size changer renders as an antd Select. In jsdom, antd opens
+  // its overlay on mouseDown of the .ant-select-selector element rather
+  // than via a click on the inner combobox input.
+  const selector = await waitFor(() => {
+    const el = document.querySelector(
+      '.ant-pagination-options-size-changer .ant-select-selector',
+    ) as HTMLElement | null;
+    expect(el).toBeTruthy();
+    return el!;
+  });
+  fireEvent.mouseDown(selector);
+
+  // The opened listbox lives in a body portal; collect its options and assert
+  // exactly the canonical [5, 15, 25, 50, 100] set is offered. Without this
+  // guard, regressing to a single hardcoded option (the pre-rework approach)
+  // would silently pass CI.
+  const listbox = await screen.findByRole('listbox');
+  const offeredSizes = within(listbox)
+    .getAllByRole('option')
+    .map(el => el.getAttribute('title'));
+  expect(offeredSizes).toEqual([
+    '5 / page',
+    '15 / page',
+    '25 / page',
+    '50 / page',
+    '100 / page',
+  ]);
+});
+
 test('should use verbose_map for column headers when available', async () => {
   jest.restoreAllMocks();
 
diff --git 
a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailPane.tsx 
b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailPane.tsx
index 9326b63b485..901f9d2421b 100644
--- a/superset-frontend/src/components/Chart/DrillDetail/DrillDetailPane.tsx
+++ b/superset-frontend/src/components/Chart/DrillDetail/DrillDetailPane.tsx
@@ -60,7 +60,7 @@ import { getDrillPayload } from './utils';
 import { ResultsPage } from './types';
 import { datasetLabelLower } from 'src/features/semanticLayers/label';
 
-const PAGE_SIZE = 50;
+const DEFAULT_PAGE_SIZE = 50;
 
 interface DataType {
   [key: string]: any;
@@ -94,6 +94,7 @@ export default function DrillDetailPane({
 }) {
   const theme = useTheme();
   const [pageIndex, setPageIndex] = useState(0);
+  const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
   const lastPageIndex = useRef(pageIndex);
   const [filters, setFilters] = useState(initialFilters);
   const [isLoading, setIsLoading] = useState(false);
@@ -307,13 +308,13 @@ export default function DrillDetailPane({
     if (!responseError && !isLoading && !resultsPages.has(pageIndex)) {
       setIsLoading(true);
       const jsonPayload = getDrillPayload(formData, filters) ?? {};
-      const cachePageLimit = Math.ceil(SAMPLES_ROW_LIMIT / PAGE_SIZE);
+      const cachePageLimit = Math.ceil(SAMPLES_ROW_LIMIT / pageSize);
       getDatasourceSamples(
         datasourceType as DatasourceType,
         Number(datasourceId),
         false,
         jsonPayload,
-        PAGE_SIZE,
+        pageSize,
         pageIndex + 1,
         dashboardId,
       )
@@ -349,6 +350,7 @@ export default function DrillDetailPane({
     formData,
     isLoading,
     pageIndex,
+    pageSize,
     responseError,
     resultsPages,
   ]);
@@ -384,13 +386,20 @@ export default function DrillDetailPane({
           data={data}
           columns={mappedColumns}
           size={TableSize.Small}
-          defaultPageSize={PAGE_SIZE}
+          defaultPageSize={DEFAULT_PAGE_SIZE}
           recordCount={resultsPage?.total}
           usePagination
           loading={isLoading}
-          onChange={pagination =>
-            setPageIndex(pagination.current ? pagination.current - 1 : 0)
-          }
+          onChange={pagination => {
+            const newPageSize = pagination.pageSize ?? pageSize;
+            if (newPageSize !== pageSize) {
+              setPageSize(newPageSize);
+              setResultsPages(new Map());
+              setPageIndex(0);
+            } else {
+              setPageIndex(pagination.current ? pagination.current - 1 : 0);
+            }
+          }}
           resizable
           virtualize
           allowHTML={allowHTML}

Reply via email to