Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions frontend/src/components/Operations/FindingExportDialog.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,17 @@ describe('FindingExportDialog', () => {
expect(jest.mocked(operationsApi.exportFindings).mock.calls[0][1].finding_ids).toEqual([ALPHA.id, GAMMA.id])
})

it('stops with an error when the server repeats the same page offset', async () => {
;(operationsApi.listFindings as jest.Mock).mockImplementation(
async () => ({ items: [ALPHA], has_more: true, next_offset: 0 }),
)
const user = userEvent.setup()
renderDialog()
const dialog = await openDialog(user)
expect(await within(dialog).findByText(/did not advance/)).toBeInTheDocument()
expect(operationsApi.listFindings).toHaveBeenCalledTimes(1)
})

it('lists and exports once a finding that a later page repeats', async () => {
const user = userEvent.setup()
// A finding created while pages load shifts the next page, so it can start with the previous page's last row.
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/components/Operations/FindingExportDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,16 @@ export default function FindingExportDialog({ operation }: FindingExportDialogPr
let ignore = false
const loadAllPages = async (): Promise<void> => {
const loaded = new Map<string, FindingListItem>()
const seenOffsets = new Set<number>()
let offset: number | null = 0
while (offset !== null) {
// A repeated offset means the server is not advancing the page, and
// fetching it again would loop forever, so stop with an error the
// same way the shared pagination helper treats a cyclic cursor.
if (seenOffsets.has(offset)) {
throw new Error('The findings page offset did not advance.')
}
seenOffsets.add(offset)
const page = await operationsApi.listFindings(operation.id, { limit: PAGE_SIZE, offset })
if (ignore) return
// A finding created while pages load shifts later pages, so a page can repeat an earlier row.
Expand Down
Loading