Skip to content

Commit 28d5b2b

Browse files
feat(ui): integrate threat map with regions API endpoint (#9324)
Co-authored-by: alejandrobailo <alejandrobailo94@gmail.com>
1 parent c8d9f37 commit 28d5b2b

File tree

26 files changed

+721
-475
lines changed

26 files changed

+721
-475
lines changed

ui/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ All notable changes to the **Prowler UI** are documented in this file.
99
- Compliance Watchlist component to Overview page [(#9199)](https://github.com/prowler-cloud/prowler/pull/9199)
1010
- Service Watchlist component to Overview page [(#9316)](https://github.com/prowler-cloud/prowler/pull/9316)
1111
- Risk Pipeline component with Sankey chart to Overview page [(#9317)](https://github.com/prowler-cloud/prowler/pull/9317)
12+
- Threat Map component to Overview Page [(#9324)](https://github.com/prowler-cloud/prowler/pull/9324)
1213

1314
## [1.14.0] (Prowler v5.14.0)
1415

ui/actions/overview/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from "./overview";
2-
export * from "./overview.adapter";
2+
export * from "./sankey.adapter";
3+
export * from "./threat-map.adapter";
34
export * from "./types";

ui/actions/overview/overview.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { handleApiResponse } from "@/lib/server-actions-helper";
77
import {
88
FindingsSeverityOverviewResponse,
99
ProvidersOverviewResponse,
10+
RegionsOverviewResponse,
1011
ServicesOverviewResponse,
1112
} from "./types";
1213

@@ -178,3 +179,31 @@ export const getThreatScore = async ({
178179
return undefined;
179180
}
180181
};
182+
183+
export const getRegionsOverview = async ({
184+
filters = {},
185+
}: {
186+
filters?: Record<string, string | string[] | undefined>;
187+
} = {}): Promise<RegionsOverviewResponse | undefined> => {
188+
const headers = await getAuthHeaders({ contentType: false });
189+
190+
const url = new URL(`${apiBaseUrl}/overviews/regions`);
191+
192+
// Handle multiple filters
193+
Object.entries(filters).forEach(([key, value]) => {
194+
if (key !== "filter[search]" && value !== undefined) {
195+
url.searchParams.append(key, String(value));
196+
}
197+
});
198+
199+
try {
200+
const response = await fetch(url.toString(), {
201+
headers,
202+
});
203+
204+
return handleApiResponse(response);
205+
} catch (error) {
206+
console.error("Error fetching regions overview:", error);
207+
return undefined;
208+
}
209+
};
Lines changed: 8 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,36 @@
1+
import { getProviderDisplayName } from "@/types/providers";
2+
13
import {
24
FindingsSeverityOverviewResponse,
35
ProviderOverview,
46
ProvidersOverviewResponse,
57
} from "./types";
68

7-
/**
8-
* Sankey chart node structure
9-
*/
109
export interface SankeyNode {
1110
name: string;
1211
}
1312

14-
/**
15-
* Sankey chart link structure
16-
*/
1713
export interface SankeyLink {
1814
source: number;
1915
target: number;
2016
value: number;
2117
}
2218

23-
/**
24-
* Sankey chart data structure
25-
*/
2619
export interface SankeyData {
2720
nodes: SankeyNode[];
2821
links: SankeyLink[];
2922
}
3023

31-
/**
32-
* Provider display name mapping
33-
* Maps provider IDs to user-friendly display names
34-
* These names must match the COLOR_MAP keys in sankey-chart.tsx
35-
*/
36-
const PROVIDER_DISPLAY_NAMES: Record<string, string> = {
37-
aws: "AWS",
38-
azure: "Azure",
39-
gcp: "Google Cloud",
40-
kubernetes: "Kubernetes",
41-
github: "GitHub",
42-
m365: "Microsoft 365",
43-
iac: "Infrastructure as Code",
44-
oraclecloud: "Oracle Cloud Infrastructure",
45-
};
46-
47-
/**
48-
* Aggregated provider data after grouping by provider type
49-
*/
5024
interface AggregatedProvider {
5125
id: string;
5226
displayName: string;
5327
pass: number;
5428
fail: number;
5529
}
5630

57-
/**
58-
* Provider types to exclude from the Sankey chart
59-
*/
6031
const EXCLUDED_PROVIDERS = new Set(["mongo", "mongodb", "mongodbatlas"]);
6132

62-
/**
63-
* Aggregates multiple provider entries by provider type (id)
64-
* Since the API can return multiple entries for the same provider type,
65-
* we need to sum up their findings
66-
*
67-
* @param providers - Raw provider overview data from API
68-
* @returns Aggregated providers with summed findings
69-
*/
33+
// API can return multiple entries for the same provider type, so we sum their findings
7034
function aggregateProvidersByType(
7135
providers: ProviderOverview[],
7236
): AggregatedProvider[] {
@@ -75,10 +39,7 @@ function aggregateProvidersByType(
7539
for (const provider of providers) {
7640
const { id, attributes } = provider;
7741

78-
// Skip excluded providers
79-
if (EXCLUDED_PROVIDERS.has(id)) {
80-
continue;
81-
}
42+
if (EXCLUDED_PROVIDERS.has(id)) continue;
8243

8344
const existing = aggregated.get(id);
8445

@@ -88,7 +49,7 @@ function aggregateProvidersByType(
8849
} else {
8950
aggregated.set(id, {
9051
id,
91-
displayName: PROVIDER_DISPLAY_NAMES[id] || id,
52+
displayName: getProviderDisplayName(id),
9253
pass: attributes.findings.pass,
9354
fail: attributes.findings.fail,
9455
});
@@ -98,9 +59,6 @@ function aggregateProvidersByType(
9859
return Array.from(aggregated.values());
9960
}
10061

101-
/**
102-
* Severity display names in order
103-
*/
10462
const SEVERITY_ORDER = [
10563
"Critical",
10664
"High",
@@ -110,18 +68,8 @@ const SEVERITY_ORDER = [
11068
] as const;
11169

11270
/**
113-
* Adapts providers overview and findings severity API responses to Sankey chart format
114-
*
115-
* Creates a 2-level flow visualization:
116-
* - Level 1: Cloud providers (AWS, Azure, GCP, etc.)
117-
* - Level 2: Severity breakdown (Critical, High, Medium, Low, Informational)
118-
*
119-
* The severity distribution is calculated proportionally based on each provider's
120-
* fail count relative to the total fails across all providers.
121-
*
122-
* @param providersResponse - Raw API response from /overviews/providers
123-
* @param severityResponse - Raw API response from /overviews/findings_severity
124-
* @returns Sankey chart data with nodes and links
71+
* Adapts providers overview and findings severity API responses to Sankey chart format.
72+
* Severity distribution is calculated proportionally based on each provider's fail count.
12573
*/
12674
export function adaptProvidersOverviewToSankey(
12775
providersResponse: ProvidersOverviewResponse | undefined,
@@ -131,34 +79,23 @@ export function adaptProvidersOverviewToSankey(
13179
return { nodes: [], links: [] };
13280
}
13381

134-
// Aggregate providers by type
13582
const aggregatedProviders = aggregateProvidersByType(providersResponse.data);
136-
137-
// Filter out providers with no findings (only need fail > 0 for severity view)
13883
const providersWithFailures = aggregatedProviders.filter((p) => p.fail > 0);
13984

14085
if (providersWithFailures.length === 0) {
14186
return { nodes: [], links: [] };
14287
}
14388

144-
// Build nodes array: providers first, then severities
14589
const providerNodes: SankeyNode[] = providersWithFailures.map((p) => ({
14690
name: p.displayName,
14791
}));
148-
14992
const severityNodes: SankeyNode[] = SEVERITY_ORDER.map((severity) => ({
15093
name: severity,
15194
}));
152-
15395
const nodes = [...providerNodes, ...severityNodes];
154-
155-
// Calculate severity start index (after provider nodes)
15696
const severityStartIndex = providerNodes.length;
157-
158-
// Build links from each provider to severities
15997
const links: SankeyLink[] = [];
16098

161-
// If we have severity data, distribute proportionally
16299
if (severityResponse?.data?.attributes) {
163100
const { critical, high, medium, low, informational } =
164101
severityResponse.data.attributes;
@@ -167,18 +104,15 @@ export function adaptProvidersOverviewToSankey(
167104
const totalSeverity = severityValues.reduce((sum, v) => sum + v, 0);
168105

169106
if (totalSeverity > 0) {
170-
// Calculate total fails across all providers
171107
const totalFails = providersWithFailures.reduce(
172108
(sum, p) => sum + p.fail,
173109
0,
174110
);
175111

176112
providersWithFailures.forEach((provider, sourceIndex) => {
177-
// Calculate this provider's proportion of total fails
178113
const providerRatio = provider.fail / totalFails;
179114

180115
severityValues.forEach((severityValue, severityIndex) => {
181-
// Distribute severity proportionally to this provider
182116
const value = Math.round(severityValue * providerRatio);
183117

184118
if (value > 0) {
@@ -192,7 +126,7 @@ export function adaptProvidersOverviewToSankey(
192126
});
193127
}
194128
} else {
195-
// Fallback: if no severity data, just show fail counts to a generic "Fail" node
129+
// Fallback when no severity data available
196130
const failNode: SankeyNode = { name: "Fail" };
197131
nodes.push(failNode);
198132
const failIndex = nodes.length - 1;

0 commit comments

Comments
 (0)