From 1bc9fc2782027eee55cb4d6914bc91e5b08dda12 Mon Sep 17 00:00:00 2001 From: "Pablo F.G" Date: Fri, 19 Jun 2026 11:36:59 +0200 Subject: [PATCH] feat(ui): filter overview by provider group - Add the provider group selector to the Overview filters - Scope risk plot, risk pipeline, and severity-over-time to the selected group Co-Authored-By: Claude Opus 4.8 (1M context) --- .../risk-pipeline-view.ssr.tsx | 23 ++++++++++++++++--- .../graphs-tabs/risk-plot/risk-plot.ssr.tsx | 11 +++++++++ .../finding-severity-over-time.tsx | 2 ++ ui/app/(prowler)/page.tsx | 8 ++++++- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/ui/app/(prowler)/_overview/graphs-tabs/risk-pipeline-view/risk-pipeline-view.ssr.tsx b/ui/app/(prowler)/_overview/graphs-tabs/risk-pipeline-view/risk-pipeline-view.ssr.tsx index b8479432e6..f230f3f78f 100644 --- a/ui/app/(prowler)/_overview/graphs-tabs/risk-pipeline-view/risk-pipeline-view.ssr.tsx +++ b/ui/app/(prowler)/_overview/graphs-tabs/risk-pipeline-view/risk-pipeline-view.ssr.tsx @@ -18,11 +18,27 @@ export async function RiskPipelineViewSSR({ const providerTypeFilter = filters["filter[provider_type__in]"]; const providerIdFilter = filters["filter[provider_id__in]"]; + const providerGroupsFilter = filters["filter[provider_groups__in]"]; // Fetch providers list to know account types const providersListResponse = await getAllProviders(); const allProviders = providersListResponse?.data || []; + // Scope the provider set to the selected groups so we enumerate only their + // provider types below (the per-type API calls also carry the group filter). + const selectedGroupIds = providerGroupsFilter + ? String(providerGroupsFilter) + .split(",") + .map((id) => id.trim()) + : []; + const scopedProviders = selectedGroupIds.length + ? allProviders.filter((p) => + p.relationships.provider_groups?.data?.some((group) => + selectedGroupIds.includes(group.id), + ), + ) + : allProviders; + // Build severityByProviderType based on filters const severityByProviderType: SeverityByProviderType = {}; let selectedProviderTypes: string[] | undefined; @@ -36,7 +52,7 @@ export async function RiskPipelineViewSSR({ // Group selected accounts by provider type const accountsByType: Record = {}; for (const accountId of selectedAccountIds) { - const provider = allProviders.find((p) => p.id === accountId); + const provider = scopedProviders.find((p) => p.id === accountId); if (provider) { const type = provider.attributes.provider.toLowerCase(); if (!accountsByType[type]) { @@ -93,9 +109,10 @@ export async function RiskPipelineViewSSR({ } } } else { - // Case: No filters - get all provider types and make parallel calls + // Case: No account/type filter - enumerate provider types (scoped to the + // selected groups when a group filter is active) and make parallel calls. const allProviderTypes = Array.from( - new Set(allProviders.map((p) => p.attributes.provider.toLowerCase())), + new Set(scopedProviders.map((p) => p.attributes.provider.toLowerCase())), ); const severityPromises = allProviderTypes.map(async (providerType) => { diff --git a/ui/app/(prowler)/_overview/graphs-tabs/risk-plot/risk-plot.ssr.tsx b/ui/app/(prowler)/_overview/graphs-tabs/risk-plot/risk-plot.ssr.tsx index 887eb7a5d5..6f42261a14 100644 --- a/ui/app/(prowler)/_overview/graphs-tabs/risk-plot/risk-plot.ssr.tsx +++ b/ui/app/(prowler)/_overview/graphs-tabs/risk-plot/risk-plot.ssr.tsx @@ -19,6 +19,7 @@ export async function RiskPlotSSR({ const providerTypeFilter = filters["filter[provider_type__in]"]; const providerIdFilter = filters["filter[provider_id__in]"]; + const providerGroupsFilter = filters["filter[provider_groups__in]"]; // Fetch all providers const providersListResponse = await getAllProviders(); @@ -33,6 +34,16 @@ export async function RiskPlotSSR({ .split(",") .map((id) => id.trim()); filteredProviders = allProviders.filter((p) => selectedIds.includes(p.id)); + } else if (providerGroupsFilter) { + // Filter by provider group membership + const selectedGroupIds = String(providerGroupsFilter) + .split(",") + .map((id) => id.trim()); + filteredProviders = allProviders.filter((p) => + p.relationships.provider_groups?.data?.some((group) => + selectedGroupIds.includes(group.id), + ), + ); } else if (providerTypeFilter) { // Filter by provider types const selectedTypes = String(providerTypeFilter) diff --git a/ui/app/(prowler)/_overview/severity-over-time/_components/finding-severity-over-time.tsx b/ui/app/(prowler)/_overview/severity-over-time/_components/finding-severity-over-time.tsx index 9e0802d4ff..c39c462bfb 100644 --- a/ui/app/(prowler)/_overview/severity-over-time/_components/finding-severity-over-time.tsx +++ b/ui/app/(prowler)/_overview/severity-over-time/_components/finding-severity-over-time.tsx @@ -44,8 +44,10 @@ export const FindingSeverityOverTime = ({ const filters: Record = {}; const providerType = searchParams.get("filter[provider_type__in]"); const providerId = searchParams.get("filter[provider_id__in]"); + const providerGroups = searchParams.get("filter[provider_groups__in]"); if (providerType) filters["filter[provider_type__in]"] = providerType; if (providerId) filters["filter[provider_id__in]"] = providerId; + if (providerGroups) filters["filter[provider_groups__in]"] = providerGroups; return filters; }; diff --git a/ui/app/(prowler)/page.tsx b/ui/app/(prowler)/page.tsx index 99f152b4e4..f04f49caf4 100644 --- a/ui/app/(prowler)/page.tsx +++ b/ui/app/(prowler)/page.tsx @@ -1,10 +1,12 @@ import { Suspense } from "react"; +import { getAllProviderGroups } from "@/actions/manage-groups/manage-groups"; import { getAllProviders } from "@/actions/providers"; import { ProviderAccountSelectors } from "@/components/filters/provider-account-selectors"; import { ContentLayout } from "@/components/ui"; import { SearchParamsProps } from "@/types"; +import { ProviderGroupSelector } from "./_overview/_components/provider-group-selector"; import { AttackSurfaceSkeleton, AttackSurfaceSSR, @@ -38,12 +40,16 @@ export default async function Home({ searchParams: Promise; }) { const resolvedSearchParams = await searchParams; - const providersData = await getAllProviders(); + const [providersData, providerGroupsData] = await Promise.all([ + getAllProviders(), + getAllProviderGroups(), + ]); return (
+