fix(drill-detail): make page-size selector functionally adjustable (#37975)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Evan Rusackas <evan@preset.io>
This commit is contained in:
Varun Chawla
2026-05-12 13:39:41 -07:00
committed by GitHub
parent e94465208f
commit a77fec68d4
2 changed files with 94 additions and 7 deletions

View File

@@ -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();

View File

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