From ea42e4374b8295dbcfcd7900051afa78be702f5b Mon Sep 17 00:00:00 2001 From: alejandrobailo Date: Wed, 8 Apr 2026 12:43:11 +0200 Subject: [PATCH] refactor(ui): move sorting to server-side and fix filtered resource count --- ui/actions/finding-groups/finding-groups.ts | 12 ++++++------ ui/actions/findings/findings-by-resource.ts | 1 + ui/app/(prowler)/findings/page.tsx | 8 ++------ .../findings/table/findings-group-drill-down.tsx | 4 ++-- .../findings/table/inline-resource-container.tsx | 4 ++-- ui/hooks/use-infinite-resources.ts | 11 ++++++----- 6 files changed, 19 insertions(+), 21 deletions(-) diff --git a/ui/actions/finding-groups/finding-groups.ts b/ui/actions/finding-groups/finding-groups.ts index 7eed3fb30c..d041cd9353 100644 --- a/ui/actions/finding-groups/finding-groups.ts +++ b/ui/actions/finding-groups/finding-groups.ts @@ -61,10 +61,12 @@ function normalizeFindingGroupResourceFilters( return normalized; } +const DEFAULT_FINDING_GROUPS_SORT = "-severity,-delta,-fail_count,-last_seen_at"; + export const getFindingGroups = async ({ page = 1, pageSize = 10, - sort = "", + sort = DEFAULT_FINDING_GROUPS_SORT, filters = {}, }) => { const headers = await getAuthHeaders({ contentType: false }); @@ -91,7 +93,7 @@ export const getFindingGroups = async ({ export const getLatestFindingGroups = async ({ page = 1, pageSize = 10, - sort = "", + sort = DEFAULT_FINDING_GROUPS_SORT, filters = {}, }) => { const headers = await getAuthHeaders({ contentType: false }); @@ -135,8 +137,7 @@ export const getFindingGroupResources = async ({ if (page) url.searchParams.append("page[number]", page.toString()); if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); - // Keep FAIL-first ordering when multiple statuses are returned. - url.searchParams.append("sort", "-status"); + url.searchParams.append("sort", "-severity,-delta,-last_seen_at"); appendSanitizedProviderFilters(url, normalizedFilters); @@ -172,8 +173,7 @@ export const getLatestFindingGroupResources = async ({ if (page) url.searchParams.append("page[number]", page.toString()); if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); - // Keep FAIL-first ordering when multiple statuses are returned. - url.searchParams.append("sort", "-status"); + url.searchParams.append("sort", "-severity,-delta,-last_seen_at"); appendSanitizedProviderFilters(url, normalizedFilters); diff --git a/ui/actions/findings/findings-by-resource.ts b/ui/actions/findings/findings-by-resource.ts index 12900ffdca..f0fe2cdd72 100644 --- a/ui/actions/findings/findings-by-resource.ts +++ b/ui/actions/findings/findings-by-resource.ts @@ -379,6 +379,7 @@ export const getLatestFindingsByResourceUid = async ({ ); url.searchParams.append("filter[resource_uid]", resourceUid); + url.searchParams.append("sort", "-severity,status,-updated_at"); if (page) url.searchParams.append("page[number]", page.toString()); if (pageSize) url.searchParams.append("page[size]", pageSize.toString()); diff --git a/ui/app/(prowler)/findings/page.tsx b/ui/app/(prowler)/findings/page.tsx index 74ecc83c4f..991c8c2940 100644 --- a/ui/app/(prowler)/findings/page.tsx +++ b/ui/app/(prowler)/findings/page.tsx @@ -120,12 +120,8 @@ const SSRDataTable = async ({ }) => { const page = parseInt(searchParams.page?.toString() || "1", 10); const pageSize = parseInt(searchParams.pageSize?.toString() || "10", 10); - const defaultSort = "-fail_count,-severity,-last_seen_at"; - const { encodedSort } = extractSortAndKey({ - ...searchParams, - sort: searchParams.sort ?? defaultSort, - }); + const { encodedSort } = extractSortAndKey(searchParams); // Check if the searchParams contain any date or scan filter const hasDateOrScan = hasDateOrScanFilter(searchParams); @@ -135,7 +131,7 @@ const SSRDataTable = async ({ const findingGroupsData = await fetchFindingGroups({ page, - sort: encodedSort, + ...(encodedSort && { sort: encodedSort }), filters, pageSize, }); diff --git a/ui/components/findings/table/findings-group-drill-down.tsx b/ui/components/findings/table/findings-group-drill-down.tsx index 0e771f2768..5c3273abee 100644 --- a/ui/components/findings/table/findings-group-drill-down.tsx +++ b/ui/components/findings/table/findings-group-drill-down.tsx @@ -83,7 +83,7 @@ export function FindingsGroupDrillDown({ setIsLoading(loading); }; - const { sentinelRef, refresh, loadMore } = useInfiniteResources({ + const { sentinelRef, refresh, loadMore, totalCount } = useInfiniteResources({ checkId: group.checkId, hasDateOrScanFilter: hasDateOrScan, filters, @@ -96,7 +96,7 @@ export function FindingsGroupDrillDown({ const drawer = useResourceDetailDrawer({ resources, checkId: group.checkId, - totalResourceCount: group.resourcesTotal, + totalResourceCount: totalCount ?? group.resourcesTotal, onRequestMoreResources: loadMore, }); diff --git a/ui/components/findings/table/inline-resource-container.tsx b/ui/components/findings/table/inline-resource-container.tsx index 0aadef19a6..9b85b58406 100644 --- a/ui/components/findings/table/inline-resource-container.tsx +++ b/ui/components/findings/table/inline-resource-container.tsx @@ -181,7 +181,7 @@ export function InlineResourceContainer({ setIsLoading(loading); }; - const { sentinelRef, refresh, loadMore } = useInfiniteResources({ + const { sentinelRef, refresh, loadMore, totalCount } = useInfiniteResources({ checkId: group.checkId, hasDateOrScanFilter: hasDateOrScan, filters, @@ -195,7 +195,7 @@ export function InlineResourceContainer({ const drawer = useResourceDetailDrawer({ resources, checkId: group.checkId, - totalResourceCount: group.resourcesTotal, + totalResourceCount: totalCount ?? group.resourcesTotal, onRequestMoreResources: loadMore, }); diff --git a/ui/hooks/use-infinite-resources.ts b/ui/hooks/use-infinite-resources.ts index fc720dd9f7..44483c3128 100644 --- a/ui/hooks/use-infinite-resources.ts +++ b/ui/hooks/use-infinite-resources.ts @@ -32,6 +32,8 @@ interface UseInfiniteResourcesReturn { refresh: () => void; /** Imperatively load the next page (e.g. from drawer navigation). */ loadMore: () => void; + /** Total number of resources matching current filters (from API pagination). */ + totalCount: number | null; } /** @@ -60,6 +62,7 @@ export function useInfiniteResources({ const currentCheckIdRef = useRef(checkId); const controllerRef = useRef(null); const observerRef = useRef(null); + const totalCountRef = useRef(null); // Store latest values in refs so the fetch function always reads current values // without being recreated on every render @@ -104,12 +107,10 @@ export function useInfiniteResources({ return; } - const resources = adaptFindingGroupResourcesResponse( - response, - forCheckId, - ); + const resources = adaptFindingGroupResourcesResponse(response, forCheckId); const totalPages = response?.meta?.pagination?.pages ?? 1; const hasMore = page < totalPages; + totalCountRef.current = response?.meta?.pagination?.count ?? null; // Commit the page number only after a successful (non-aborted) fetch. // This prevents a premature pageRef increment from loadNextPage being @@ -209,5 +210,5 @@ export function useInfiniteResources({ fetchPage(1, false, currentCheckIdRef.current, controller.signal); } - return { sentinelRef, refresh, loadMore: loadNextPage }; + return { sentinelRef, refresh, loadMore: loadNextPage, totalCount: totalCountRef.current }; }