From 757d99d22adb7a0ba90835037a1b9b302cf8b459 Mon Sep 17 00:00:00 2001 From: kitos Date: Wed, 3 Jun 2026 09:49:58 +0200 Subject: [PATCH] feat(dashboards): hover tooltips on all metric cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New MetricTooltip component — a small ⓘ icon showing an executive- friendly explanation panel on hover (CSS, no JS, instant). DashboardPage: tooltips on all 6 coverage summary cards (Total Techniques, Validated, Partial, In Progress, Not Covered, Not Evaluated), Coverage Evolution chart, Test Pipeline funnel, Team Activity and Validation Rate section headers. ExecutiveDashboardPage: tooltips on all 4 sub-scores (Coverage, Detection, Critical, Response), Score Trend, Top Threat Actors, 4 KPIs (MTTD, MTTR, Detection Efficacy, Validation Throughput), Coverage by Tactic, Critical Gaps table, and all 6 team metrics (Red/Blue Tests Done, Avg Time, Rejection). Each tooltip explains what the metric measures, what a good/bad value looks like, and what action to take — written for non- technical executives. --- .../src/components/CoverageSummaryCard.tsx | 15 ++++- frontend/src/components/MetricTooltip.tsx | 58 +++++++++++++++++++ frontend/src/pages/DashboardPage.tsx | 11 ++++ frontend/src/pages/ExecutiveDashboardPage.tsx | 46 ++++++++++----- 4 files changed, 114 insertions(+), 16 deletions(-) create mode 100644 frontend/src/components/MetricTooltip.tsx diff --git a/frontend/src/components/CoverageSummaryCard.tsx b/frontend/src/components/CoverageSummaryCard.tsx index b5279b7..3a074de 100644 --- a/frontend/src/components/CoverageSummaryCard.tsx +++ b/frontend/src/components/CoverageSummaryCard.tsx @@ -1,4 +1,5 @@ import type { ReactNode } from "react"; +import MetricTooltip from "./MetricTooltip"; interface CoverageSummaryCardProps { title: string; @@ -7,6 +8,8 @@ interface CoverageSummaryCardProps { icon: ReactNode; colorClass: string; bgClass: string; + /** Optional tooltip explaining this metric to non-technical users */ + tooltip?: { description: string; context?: string }; } export default function CoverageSummaryCard({ @@ -16,6 +19,7 @@ export default function CoverageSummaryCard({ icon, colorClass, bgClass, + tooltip, }: CoverageSummaryCardProps) { const percentage = total && total > 0 ? ((value / total) * 100).toFixed(1) : null; @@ -23,7 +27,16 @@ export default function CoverageSummaryCard({
-

{title}

+

+ {title} + {tooltip && ( + + )} +

{value}

{percentage !== null && (

{percentage}% of total

diff --git a/frontend/src/components/MetricTooltip.tsx b/frontend/src/components/MetricTooltip.tsx new file mode 100644 index 0000000..3e1b4c0 --- /dev/null +++ b/frontend/src/components/MetricTooltip.tsx @@ -0,0 +1,58 @@ +/** + * MetricTooltip — a small ⓘ icon that shows a plain-language explanation + * of a metric on hover. Designed for executive dashboards where not everyone + * knows what "MTTD" or "partial coverage" means. + */ + +interface MetricTooltipProps { + title: string; + description: string; + /** Optional extra context line (e.g. "Why it matters") */ + context?: string; + /** Positioning hint — defaults to 'below' */ + position?: "below" | "above" | "left"; +} + +export default function MetricTooltip({ + title, + description, + context, + position = "below", +}: MetricTooltipProps) { + const posClass = + position === "above" + ? "bottom-full mb-2 left-0" + : position === "left" + ? "right-full mr-2 top-0" + : "top-full mt-2 left-0"; + + return ( + + {/* The ⓘ icon */} + + i + + + {/* Tooltip panel */} + + {/* Arrow */} + {position !== "above" && position !== "left" && ( + + )} +

{title}

+

{description}

+ {context && ( +

+ {context} +

+ )} +
+
+ ); +} diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index b982bc9..0217992 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -42,6 +42,7 @@ import { import { getCoverageEvolution } from "../api/snapshots"; import CoverageSummaryCard from "../components/CoverageSummaryCard"; import TacticCoverageChart from "../components/TacticCoverageChart"; +import MetricTooltip from "../components/MetricTooltip"; import type { TestState } from "../types/models"; /* ── Badge colours (reused from TestsPage) ─────────────────────────── */ @@ -199,6 +200,7 @@ export default function DashboardPage() { icon={} colorClass="text-cyan-400" bgClass="bg-gray-900" + tooltip={{ description: "Total number of MITRE ATT&CK attack techniques tracked in the platform. This is the full universe of known attack methods we measure coverage against.", context: "A higher total means broader threat visibility." }} /> } colorClass="text-green-400" bgClass="bg-green-950/20" + tooltip={{ description: "Techniques confirmed as detectable by Blue Team with ≥2 successful tests. These attacks would be caught if a real adversary used them.", context: "Goal: maximise this number. Higher = stronger defence." }} /> } colorClass="text-yellow-400" bgClass="bg-yellow-950/20" + tooltip={{ description: "Techniques with incomplete coverage: only 1 validated test, mixed detection results, or tests still in progress.", context: "These need attention — detection is not yet reliable." }} /> } colorClass="text-blue-400" bgClass="bg-blue-950/20" + tooltip={{ description: "Techniques with active tests currently being executed or awaiting review by leads. Not yet counted as covered.", context: "Indicates active testing work underway." }} /> } colorClass="text-red-400" bgClass="bg-red-950/20" + tooltip={{ description: "Techniques that were tested but Blue Team failed to detect the attack. These are confirmed security gaps — a real attack would go unnoticed.", context: "High priority: invest in detection rules and monitoring." }} /> } colorClass="text-gray-400" bgClass="bg-gray-900" + tooltip={{ description: "Techniques with no tests created yet. We have no data on whether these attacks would be detected or not.", context: "Unknown risk — prioritise testing the most common ones." }} />
)} @@ -248,6 +255,7 @@ export default function DashboardPage() {

Coverage Evolution (6 months) +

{evolutionLoading ? (
@@ -310,6 +318,7 @@ export default function DashboardPage() {

Test Pipeline +

@@ -611,13 +626,14 @@ export default function ExecutiveDashboardPage() {

Blue Team +

{teamMetrics.blue_team.tests_completed}

-

Tests Done

+

Tests Done

@@ -625,13 +641,13 @@ export default function ExecutiveDashboardPage() { ? `${teamMetrics.blue_team.avg_completion_hours}h` : "N/A"}

-

Avg Time

+

Avg Time

{teamMetrics.blue_team.rejection_rate}%

-

Rejection

+

Rejection