From 13401d4eb40c17067e07c689c576e44f91f4b800 Mon Sep 17 00:00:00 2001 From: Binh Date: Thu, 3 Sep 2026 10:25:03 +0200 Subject: [PATCH] feat(results-browser): implement load all functionality for results db --- .../results-browser/ResultsBrowser.tsx | 24 +++++++++++++++---- src/styles/datahub.css | 1 + .../results-browser-timeslot-scope.test.ts | 4 ++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/features/results-browser/ResultsBrowser.tsx b/src/features/results-browser/ResultsBrowser.tsx index 0d3f9d4..22e9517 100644 --- a/src/features/results-browser/ResultsBrowser.tsx +++ b/src/features/results-browser/ResultsBrowser.tsx @@ -15,6 +15,7 @@ import { BackendConnectionDialog } from '../../components/BackendConnectionDialo const assessorApi = new AssessorApiClient(); const PAGE_SIZE = 50; export const DEFAULT_RESULT_DB_FILTERS: UnifiedResultQuery = { deviation: true }; +const MAX_QUERY_PAGE_SIZE = 10000; type BackendStatus = 'connected' | 'checking' | 'unavailable'; interface ResultsBrowserProps { @@ -27,6 +28,7 @@ interface ResultsBrowserProps { export function ResultsBrowser({ backendStatus, projectId, scanRunIds, assessmentIds }: ResultsBrowserProps) { const [filters, setFilters] = useState(() => ({ ...DEFAULT_RESULT_DB_FILTERS })); const [limit, setLimit] = useState(PAGE_SIZE); + const [loadAll, setLoadAll] = useState(false); const [facetResult, setFacetResult] = useState(null); const [queryResult, setQueryResult] = useState(null); const [loading, setLoading] = useState(true); @@ -51,24 +53,35 @@ export function ResultsBrowser({ backendStatus, projectId, scanRunIds, assessmen scanRunIds, assessmentIds }; - const [facets, results] = await Promise.all([ + const [facets, firstPage] = await Promise.all([ assessorApi.queryUnifiedResultFacets(scopedQuery), assessorApi.queryUnifiedResults({ ...scopedQuery, offset: 0, limit }) ]); + const items = [...firstPage.items]; + while (loadAll && items.length < firstPage.total) { + const nextPage = await assessorApi.queryUnifiedResults({ + ...scopedQuery, + offset: items.length, + limit: MAX_QUERY_PAGE_SIZE + }); + if (!nextPage.items.length) break; + items.push(...nextPage.items); + } setFacetResult(facets); - setQueryResult(results); + setQueryResult({ ...firstPage, items }); } catch (reason) { setError(reason instanceof Error ? reason.message : 'Results could not be loaded.'); } finally { setLoading(false); } - }, [backendStatus, projectId, scanRunIds, assessmentIds, hasTimeslotScope, filters, limit]); + }, [backendStatus, projectId, scanRunIds, assessmentIds, hasTimeslotScope, filters, limit, loadAll]); useEffect(() => { void load(); }, [load]); const updateFilters = (next: UnifiedResultQuery) => { setExpandedId(null); setLimit(PAGE_SIZE); + setLoadAll(false); setFilters(next); }; const exportFilteredResults = async () => { @@ -141,7 +154,10 @@ export function ResultsBrowser({ backendStatus, projectId, scanRunIds, assessmen expanded={expandedId === item.resultProjectionId} onToggle={() => setExpandedId(current => current === item.resultProjectionId ? null : item.resultProjectionId)}/>)} } - {queryResult && queryResult.total > queryResult.items.length && } + {queryResult && queryResult.total > queryResult.items.length &&
+ + +
} } ; diff --git a/src/styles/datahub.css b/src/styles/datahub.css index f0cc655..012d5e7 100644 --- a/src/styles/datahub.css +++ b/src/styles/datahub.css @@ -854,6 +854,7 @@ button.client-overall-ring:focus-visible { outline: var(--bw-emphasis) solid var .results-environment-value strong { color: var(--heading); } .results-environment-value p { margin: var(--legacy-space-1) 0 var(--legacy-space-3); overflow-wrap: anywhere; white-space: pre-wrap; } .results-processing-alert { margin-bottom: var(--legacy-space-3); } +.results-browser-load-actions { display: flex; gap: var(--legacy-space-2); margin-top: var(--legacy-space-3); } @media (max-width: 1150px) { .results-browser-layout { grid-template-columns: 1fr; } .results-filter-panel { position: static; max-height: none; } diff --git a/tests/features/results-browser-timeslot-scope.test.ts b/tests/features/results-browser-timeslot-scope.test.ts index 12b6489..5a66576 100644 --- a/tests/features/results-browser-timeslot-scope.test.ts +++ b/tests/features/results-browser-timeslot-scope.test.ts @@ -25,6 +25,10 @@ describe('Result DB timeslot scope', () => { expect(browserSource).toContain("'Export JSONL'"); expect(browserSource).toContain('queryAllUnifiedResults'); expect(browserSource).toContain('buildUnifiedResultJsonl(scopedQuery, results, generatedAt)'); + expect(browserSource).toContain('while (loadAll && items.length < firstPage.total)'); + expect(browserSource).toContain('limit: MAX_QUERY_PAGE_SIZE'); + expect(browserSource).toContain('>Load more'); + expect(browserSource).toContain('>Load all'); expect(browserSource).toContain("item.sourceKind === 'environment-observation'"); expect(browserSource).toContain("item.sourceKind === 'policy-observation'"); expect(browserSource).toContain('item.policyValues');