Skip to content

Commit 9b0eb60

Browse files
authored
Show AI advisor verdicts on HUD job cells (#7940)
## Summary Overlays AI advisor verdict badges on the main HUD grid. When autorevert's AI advisor has analyzed a suspect commit, a small colored "AI" badge appears on the matching job cell. <img width="701" height="280" alt="image" src="https://github.com/user-attachments/assets/a27d9cf9-eee2-483d-81ab-1dee96726c5a" /> ## How it works 1. **Lazy CH query**: After the HUD grid renders, a separate SWR call fetches advisor verdicts from `misc.autorevert_advisor_verdicts` for the visible commits 2. **Client-side matching**: Verdicts are matched to HUD job cells by comparing `workflow:signal_key` against HUD job names (stripping shard parentheticals) 3. **Badge rendering**: A small "AI" link badge on the cell, color-coded by verdict type, linking to the advisor GHA run 4. **Tooltip enhancement**: Pinning the tooltip shows the full verdict, confidence, summary, and link to the advisor analysis ## Visual design - **Red** badge: `revert` — advisor agrees the commit caused the failure - **Green** badge: `not_related` — advisor says the failure is unrelated - **Yellow** badge: `garbage` — signal is noise (infra flake, etc.) - **Blue** badge: `unsure` — advisor couldn't determine causality ## Files changed | File | What | |------|------| | `clickhouse_queries/advisor_verdicts_for_hud/` | New CH query | | `lib/advisorVerdictUtils.ts` | Types, dedup, matching logic | | `components/hud.module.css` | Badge styling | | `components/job/JobTooltip.tsx` | Verdict info in tooltip | | `pages/hud/.../[[...page]].tsx` | Context provider + SWR fetch + badge rendering in JobCell | ## Prerequisites ```sql GRANT SELECT ON misc.autorevert_advisor_verdicts TO hud_user; ``` ## Test plan - [x] TypeScript type-checks clean (no new errors) - [x] Build failure is pre-existing (Octokit types in bot files) - [ ] Manual verification on staging HUD with live advisor data
1 parent aab3b10 commit 9b0eb60

7 files changed

Lines changed: 450 additions & 16 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"params": {
3+
"repo": "String",
4+
"shas": "Array(String)"
5+
},
6+
"tests": [
7+
{
8+
"repo": "pytorch/pytorch",
9+
"shas": ["4fdbeb7393919717a7ae4e49e982b46cd3dc2f31"]
10+
}
11+
]
12+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-- Fetch AI advisor verdicts for a set of commits, for HUD overlay.
2+
-- Returns the most recent verdict per (commit, signal_key) pair.
3+
SELECT
4+
toString(suspect_commit) AS sha,
5+
signal_key,
6+
signal_source,
7+
workflow_name,
8+
verdict,
9+
confidence,
10+
summary,
11+
causal_reasoning,
12+
run_id,
13+
pr_number,
14+
timestamp
15+
FROM misc.autorevert_advisor_verdicts
16+
WHERE repo = {repo: String}
17+
AND suspect_commit IN {shas: Array(String)}
18+
ORDER BY suspect_commit, signal_key, timestamp DESC

torchci/components/hud.module.css

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,40 @@
225225
flex-direction: column;
226226
gap: 0.5rem;
227227
}
228+
229+
/* AI Advisor verdict overlay */
230+
.advisorVerdict {
231+
position: relative;
232+
}
233+
234+
.advisorBadge {
235+
position: absolute;
236+
bottom: -2px;
237+
right: -2px;
238+
font-size: 7px;
239+
font-weight: 700;
240+
line-height: 1;
241+
padding: 1px 2px;
242+
border-radius: 2px;
243+
pointer-events: none;
244+
}
245+
246+
.advisorVerdict_revert {
247+
background: #d32f2f;
248+
color: #fff;
249+
}
250+
251+
.advisorVerdict_not_related {
252+
background: #2e7d32;
253+
color: #fff;
254+
}
255+
256+
.advisorVerdict_garbage {
257+
background: #6d4c41;
258+
color: #fff;
259+
}
260+
261+
.advisorVerdict_unsure {
262+
background: #616161;
263+
color: #fff;
264+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { AdvisorVerdict, advisorRunUrl } from "lib/advisorVerdictUtils";
2+
import { useState } from "react";
3+
4+
const VERDICT_COLORS: Record<string, { border: string; badge: string }> = {
5+
revert: { border: "#d32f2f", badge: "#d32f2f" },
6+
not_related: { border: "#388e3c", badge: "#2e7d32" },
7+
garbage: { border: "#8d6e63", badge: "#6d4c41" },
8+
unsure: { border: "#757575", badge: "#616161" },
9+
};
10+
11+
export default function AdvisorSection({
12+
verdict,
13+
repoOwner,
14+
repoName,
15+
}: {
16+
verdict: AdvisorVerdict;
17+
repoOwner?: string;
18+
repoName?: string;
19+
}) {
20+
const [showReasoning, setShowReasoning] = useState(false);
21+
const colors = VERDICT_COLORS[verdict.verdict] ?? VERDICT_COLORS.unsure;
22+
23+
return (
24+
<div
25+
style={{
26+
marginTop: 4,
27+
padding: "4px 8px",
28+
borderRadius: 4,
29+
borderLeft: `3px solid ${colors.border}`,
30+
background: "rgba(128,128,128,0.15)",
31+
fontSize: "0.9em",
32+
maxWidth: 500,
33+
}}
34+
>
35+
<div style={{ display: "flex", alignItems: "center", gap: 6 }}>
36+
<span
37+
style={{
38+
display: "inline-block",
39+
padding: "1px 5px",
40+
borderRadius: 3,
41+
fontSize: "0.85em",
42+
fontWeight: 700,
43+
background: colors.badge,
44+
color: "#fff",
45+
}}
46+
>
47+
AI: {verdict.verdict}
48+
</span>
49+
<span
50+
style={{
51+
fontSize: "0.85em",
52+
fontWeight: 600,
53+
opacity: 0.7,
54+
}}
55+
>
56+
{Math.round(verdict.confidence * 100)}% confidence
57+
</span>
58+
</div>
59+
<div
60+
style={{
61+
marginTop: 3,
62+
wordBreak: "break-word",
63+
overflowWrap: "break-word",
64+
whiteSpace: "normal",
65+
}}
66+
>
67+
{verdict.summary}
68+
</div>
69+
{verdict.causalReasoning && (
70+
<div style={{ marginTop: 3 }}>
71+
<span
72+
onClick={() => setShowReasoning(!showReasoning)}
73+
style={{
74+
cursor: "pointer",
75+
fontSize: "0.85em",
76+
color: "var(--link-color, #1a73e8)",
77+
userSelect: "none",
78+
}}
79+
>
80+
{showReasoning ? "▼" : "▶"} Reasoning
81+
</span>
82+
{showReasoning && (
83+
<div
84+
style={{
85+
marginTop: 3,
86+
padding: "4px 6px",
87+
background: "rgba(128,128,128,0.12)",
88+
borderRadius: 3,
89+
fontSize: "0.85em",
90+
wordBreak: "break-word",
91+
overflowWrap: "break-word",
92+
whiteSpace: "pre-wrap",
93+
maxHeight: 300,
94+
overflowY: "auto",
95+
}}
96+
>
97+
{verdict.causalReasoning}
98+
</div>
99+
)}
100+
</div>
101+
)}
102+
<div style={{ marginTop: 3 }}>
103+
<a
104+
href={advisorRunUrl(verdict, `${repoOwner}/${repoName}`)}
105+
target="_blank"
106+
rel="noopener noreferrer"
107+
style={{
108+
fontSize: "0.85em",
109+
color: "var(--link-color, #1a73e8)",
110+
}}
111+
>
112+
View advisor run →
113+
</a>
114+
</div>
115+
</div>
116+
);
117+
}

torchci/components/job/JobTooltip.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
1+
import { AdvisorVerdict } from "lib/advisorVerdictUtils";
12
import { isJobViableStrictBlocking } from "lib/JobClassifierUtil";
23
import { JobData } from "../../lib/types";
34
import { SingleWorkflowDispatcher } from "../commit/WorkflowDispatcher";
45
import LogViewer from "../common/log/LogViewer";
6+
import AdvisorSection from "./AdvisorSection";
57
import JobLinks from "./JobLinks";
68

79
export default function JobTooltip({
810
job,
911
sha,
1012
isAutorevertSignal,
13+
advisorVerdict,
1114
repoOwner,
1215
repoName,
1316
}: {
1417
job: JobData;
1518
sha?: string;
1619
isAutorevertSignal?: boolean;
20+
advisorVerdict?: AdvisorVerdict;
1721
repoOwner?: string;
1822
repoName?: string;
1923
}) {
@@ -42,6 +46,13 @@ export default function JobTooltip({
4246
Failure in this job has triggered autorevert.
4347
</div>
4448
)}
49+
{advisorVerdict && (
50+
<AdvisorSection
51+
verdict={advisorVerdict}
52+
repoOwner={repoOwner}
53+
repoName={repoName}
54+
/>
55+
)}
4556
{isViableStrictBlocking && (
4657
<div style={{ color: "orange", fontWeight: "bold" }}>
4758
This job is viable/strict blocking.

0 commit comments

Comments
 (0)