Skip to content

Commit 58bb66f

Browse files
feat(ui/overview): add click navigation for charts and threat score improvements (#9281)
1 parent 46bfe02 commit 58bb66f

File tree

10 files changed

+226
-61
lines changed

10 files changed

+226
-61
lines changed

ui/.husky/pre-commit

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,21 @@ if [ "$CODE_REVIEW_ENABLED" = "true" ]; then
4848
echo -e "${YELLOW}🔍 Running Claude Code standards validation...${NC}"
4949
echo ""
5050
echo -e "${BLUE}📋 Files to validate:${NC}"
51-
echo "$STAGED_FILES" | sed 's/^/ - /'
51+
echo "$STAGED_FILES" | while IFS= read -r file; do echo " - $file"; done
5252
echo ""
5353

5454
echo -e "${BLUE}📤 Sending to Claude Code for validation...${NC}"
5555
echo ""
5656

57-
# Build prompt with git diff of changes AND full context
57+
# Build prompt with full file contents
5858
VALIDATION_PROMPT=$(
5959
cat <<'PROMPT_EOF'
60-
You are a code reviewer for the Prowler UI project. Analyze the code changes (git diff with full context) below and validate they comply with AGENTS.md standards.
61-
62-
**CRITICAL: You MUST check BOTH the changed lines AND the surrounding context for violations.**
60+
You are a code reviewer for the Prowler UI project. Analyze the full file contents of changed files below and validate they comply with AGENTS.md standards.
6361
6462
**RULES TO CHECK:**
6563
1. React Imports: NO `import * as React` or `import React, {` → Use `import { useState }`
6664
2. TypeScript: NO union types like `type X = "a" | "b"` → Use const-based: `const X = {...} as const`
67-
3. Tailwind: NO `var()` or hex colors in className → Use Tailwind utilities and semantic color classes (e.g., `bg-bg-neutral-tertiary`, `border-border-neutral-primary`)
65+
3. Tailwind: NO `var()` or hex colors in className → Use Tailwind utilities and semantic color classes.
6866
4. cn(): Use for merging multiple classes or for conditionals (handles Tailwind conflicts with twMerge) → `cn(BUTTON_STYLES.base, BUTTON_STYLES.active, isLoading && "opacity-50")`
6967
5. React 19: NO `useMemo`/`useCallback` without reason
7068
6. Zod v4: Use `.min(1)` not `.nonempty()`, `z.email()` not `z.string().email()`. All inputs must be validated with Zod.
@@ -76,24 +74,28 @@ You are a code reviewer for the Prowler UI project. Analyze the code changes (gi
7674
12. Use the components inside components/shadcn if possible
7775
13. Check Accessibility best practices (like alt tags in images, semantic HTML, Aria labels, etc.)
7876
79-
=== GIT DIFF WITH CONTEXT ===
77+
=== FILES TO REVIEW ===
8078
PROMPT_EOF
8179
)
8280

83-
# Add git diff to prompt with more context (U5 = 5 lines before/after)
84-
VALIDATION_PROMPT="$VALIDATION_PROMPT
85-
$(git diff --cached -U5)"
81+
# Add full file contents for each staged file
82+
for file in $STAGED_FILES; do
83+
VALIDATION_PROMPT="$VALIDATION_PROMPT
84+
85+
=== FILE: $file ===
86+
$(cat "$file" 2>/dev/null || echo "Error reading file")"
87+
done
8688

8789
VALIDATION_PROMPT="$VALIDATION_PROMPT
8890
89-
=== END DIFF ===
91+
=== END FILES ===
9092
9193
**IMPORTANT: Your response MUST start with exactly one of these lines:**
9294
STATUS: PASSED
9395
STATUS: FAILED
9496
9597
**If FAILED:** List each violation with File, Line Number, Rule Number, and Issue.
96-
**If PASSED:** Confirm all visible code (including context) complies with AGENTS.md standards.
98+
**If PASSED:** Confirm all files comply with AGENTS.md standards.
9799
98100
**Start your response now with STATUS:**"
99101

@@ -149,3 +151,19 @@ else
149151
echo ""
150152
exit 1
151153
fi
154+
155+
# Run build
156+
echo -e "${BLUE}🔨 Running build...${NC}"
157+
echo ""
158+
159+
if npm run build; then
160+
echo ""
161+
echo -e "${GREEN}✅ Build passed${NC}"
162+
echo ""
163+
else
164+
echo ""
165+
echo -e "${RED}❌ Build failed${NC}"
166+
echo -e "${RED}Fix build errors before committing${NC}"
167+
echo ""
168+
exit 1
169+
fi

ui/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ All notable changes to the **Prowler UI** are documented in this file.
1919

2020
- Resource ID moved up in the findings detail page [(#9141)](https://github.com/prowler-cloud/prowler/pull/9141)
2121
- C5 compliance logo [(#9224)](https://github.com/prowler-cloud/prowler/pull/9224)
22+
- Overview charts now support click navigation to Findings page with filters and keyboard accessibility [(#9281)](https://github.com/prowler-cloud/prowler/pull/9281)
23+
- Threat score now displays 2 decimal places with note that it doesn't include muted findings [(#9281)](https://github.com/prowler-cloud/prowler/pull/9281)
2224

2325
---
2426

ui/actions/overview/overview.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ export const getFindingsBySeverity = async ({
9595
if (page) url.searchParams.append("page[number]", page.toString());
9696
if (query) url.searchParams.append("filter[search]", query);
9797
if (sort) url.searchParams.append("sort", sort);
98-
9998
// Handle multiple filters, but exclude unsupported filters
10099
// The overviews/findings_severity endpoint does not support status or muted filters
101100
Object.entries(filters).forEach(([key, value]) => {
Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getFindingsByStatus } from "@/actions/overview/overview";
2+
import { getProviders } from "@/actions/providers";
23
import { SearchParamsProps } from "@/types";
34

45
import { pickFilterParams } from "../../lib/filter-params";
@@ -11,7 +12,10 @@ export const CheckFindingsSSR = async ({
1112
}) => {
1213
const filters = pickFilterParams(searchParams);
1314

14-
const findingsByStatus = await getFindingsByStatus({ filters });
15+
const [findingsByStatus, providersData] = await Promise.all([
16+
getFindingsByStatus({ filters }),
17+
getProviders({ page: 1, pageSize: 200 }),
18+
]);
1519

1620
if (!findingsByStatus) {
1721
return (
@@ -21,29 +25,21 @@ export const CheckFindingsSSR = async ({
2125
);
2226
}
2327

24-
const {
25-
fail = 0,
26-
pass = 0,
27-
muted_new = 0,
28-
muted_changed = 0,
29-
fail_new = 0,
30-
pass_new = 0,
31-
} = findingsByStatus?.data?.attributes || {};
28+
const attributes = findingsByStatus?.data?.attributes || {};
3229

33-
const mutedTotal = muted_new + muted_changed;
30+
const { fail = 0, pass = 0, fail_new = 0, pass_new = 0 } = attributes;
3431

3532
return (
3633
<StatusChart
3734
failFindingsData={{
3835
total: fail,
3936
new: fail_new,
40-
muted: mutedTotal,
4137
}}
4238
passFindingsData={{
4339
total: pass,
4440
new: pass_new,
45-
muted: mutedTotal,
4641
}}
42+
providers={providersData?.data}
4743
/>
4844
);
4945
};

ui/app/(prowler)/new-overview/components/risk-severity-chart/risk-severity-chart-detail.ssr.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getFindingsBySeverity } from "@/actions/overview/overview";
2+
import { getProviders } from "@/actions/providers";
23
import { SearchParamsProps } from "@/types";
34

45
import { pickFilterParams } from "../../lib/filter-params";
@@ -11,7 +12,10 @@ export const RiskSeverityChartDetailSSR = async ({
1112
}) => {
1213
const filters = pickFilterParams(searchParams);
1314

14-
const findingsBySeverity = await getFindingsBySeverity({ filters });
15+
const [findingsBySeverity, providersData] = await Promise.all([
16+
getFindingsBySeverity({ filters }),
17+
getProviders({ page: 1, pageSize: 200 }),
18+
]);
1519

1620
if (!findingsBySeverity) {
1721
return (
@@ -36,6 +40,7 @@ export const RiskSeverityChartDetailSSR = async ({
3640
medium={medium}
3741
low={low}
3842
informational={informational}
43+
providers={providersData?.data}
3944
/>
4045
);
4146
};

ui/app/(prowler)/new-overview/components/risk-severity-chart/risk-severity-chart.tsx

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"use client";
22

3+
import { useRouter, useSearchParams } from "next/navigation";
4+
35
import { HorizontalBarChart } from "@/components/graphs/horizontal-bar-chart";
46
import { BarDataPoint } from "@/components/graphs/types";
57
import {
@@ -11,12 +13,23 @@ import {
1113
} from "@/components/shadcn";
1214
import { calculatePercentage } from "@/lib/utils";
1315

16+
interface ProviderAttributes {
17+
uid: string;
18+
provider: string;
19+
}
20+
21+
interface Provider {
22+
id: string;
23+
attributes: ProviderAttributes;
24+
}
25+
1426
interface RiskSeverityChartProps {
1527
critical: number;
1628
high: number;
1729
medium: number;
1830
low: number;
1931
informational: number;
32+
providers?: Provider[];
2033
}
2134

2235
export const RiskSeverityChart = ({
@@ -25,7 +38,55 @@ export const RiskSeverityChart = ({
2538
medium,
2639
low,
2740
informational,
41+
providers = [],
2842
}: RiskSeverityChartProps) => {
43+
const router = useRouter();
44+
const searchParams = useSearchParams();
45+
46+
const handleBarClick = (dataPoint: BarDataPoint) => {
47+
// Build the URL with current filters plus severity and muted
48+
const params = new URLSearchParams(searchParams.toString());
49+
50+
// Convert filter[provider_id__in] to filter[provider_uid__in] for findings page
51+
const providerIds = params.get("filter[provider_id__in]");
52+
if (providerIds) {
53+
params.delete("filter[provider_id__in]");
54+
// Remove provider_type__in since provider_id__in is more specific
55+
params.delete("filter[provider_type__in]");
56+
57+
const ids = providerIds.split(",");
58+
const uids = ids
59+
.map((id) => {
60+
const provider = providers.find((p) => p.id === id);
61+
return provider?.attributes.uid;
62+
})
63+
.filter(Boolean);
64+
65+
if (uids.length > 0) {
66+
params.set("filter[provider_uid__in]", uids.join(","));
67+
}
68+
}
69+
70+
// Map severity name to lowercase for the filter
71+
const severityMap: Record<string, string> = {
72+
Critical: "critical",
73+
High: "high",
74+
Medium: "medium",
75+
Low: "low",
76+
Info: "informational",
77+
};
78+
79+
const severity = severityMap[dataPoint.name];
80+
if (severity) {
81+
params.set("filter[severity__in]", severity);
82+
}
83+
84+
// Add exclude muted findings filter
85+
params.set("filter[muted]", "false");
86+
87+
// Navigate to findings page
88+
router.push(`/findings?${params.toString()}`);
89+
};
2990
// Calculate total findings
3091
const totalFindings = critical + high + medium + low + informational;
3192

@@ -68,7 +129,7 @@ export const RiskSeverityChart = ({
68129
</CardHeader>
69130

70131
<CardContent className="flex flex-1 items-center justify-start px-6">
71-
<HorizontalBarChart data={chartData} />
132+
<HorizontalBarChart data={chartData} onBarClick={handleBarClick} />
72133
</CardContent>
73134
</Card>
74135
);

0 commit comments

Comments
 (0)