Skip to content

Commit 6eb5be9

Browse files
committed
Add most flaky jobs section to insights
1 parent 2434c34 commit 6eb5be9

File tree

3 files changed

+180
-4
lines changed

3 files changed

+180
-4
lines changed

deno.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,22 @@
44
"check": "deno fmt --check . && deno lint . && deno check",
55
"dev": "vite",
66
"build": "vite build",
7-
"start": "deno serve -A _fresh/server.js",
7+
// todo(https://github.com/denoland/deno/pull/31718): remove this --allow-net
8+
"start": "deno serve -P --allow-net _fresh/server.js",
89
"update": "deno run -A -r jsr:@fresh/update ."
910
},
1011
"test": {
1112
"permissions": {
1213
"env": true
1314
}
1415
},
16+
"permissions": {
17+
"default": {
18+
"env": true,
19+
"read": ["_fresh"],
20+
"net": ["*.github.com", "*.windows.net"]
21+
}
22+
},
1523
"lint": {
1624
"rules": {
1725
"tags": [

routes/insights.test.tsx

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,3 +523,104 @@ Deno.test("returns empty lists when no issues found", async () => {
523523
assertEquals(result.data.failedTests.length, 0);
524524
assertEquals(result.data.totalRunsAnalyzed, 1);
525525
});
526+
527+
Deno.test("tracks flaky job counts", async () => {
528+
const mockGithub = new MockGitHubApiClient();
529+
const mockDownloader = new MockTestResultsDownloader();
530+
531+
const runs = [
532+
createMockRun(1, "CI", "completed", "main"),
533+
createMockRun(2, "CI", "completed", "main"),
534+
];
535+
536+
mockGithub.mockRuns({ totalCount: 2, runs });
537+
538+
mockDownloader.mockResults(1, [
539+
{
540+
name: "linux-x64",
541+
tests: [
542+
{
543+
name: "test1",
544+
path: "file1.test.ts",
545+
flakyCount: 2,
546+
subTests: [],
547+
},
548+
{
549+
name: "test2",
550+
path: "file2.test.ts",
551+
flakyCount: 3,
552+
subTests: [],
553+
},
554+
],
555+
},
556+
{
557+
name: "windows-x64",
558+
tests: [
559+
{
560+
name: "test1",
561+
path: "file1.test.ts",
562+
flakyCount: 1,
563+
subTests: [],
564+
},
565+
],
566+
},
567+
{
568+
name: "macos-arm64",
569+
tests: [
570+
{
571+
name: "test3",
572+
path: "file3.test.ts",
573+
flakyCount: 4,
574+
subTests: [],
575+
},
576+
],
577+
},
578+
]);
579+
580+
mockDownloader.mockResults(2, [
581+
{
582+
name: "linux-x64",
583+
tests: [
584+
{
585+
name: "test1",
586+
path: "file1.test.ts",
587+
flakyCount: 1,
588+
subTests: [],
589+
},
590+
],
591+
},
592+
{
593+
name: "windows-x64",
594+
tests: [
595+
{
596+
name: "test2",
597+
path: "file2.test.ts",
598+
flakyCount: 2,
599+
subTests: [],
600+
},
601+
],
602+
},
603+
]);
604+
605+
const controller = new InsightsPageController(
606+
new NullLogger(),
607+
mockGithub,
608+
mockDownloader,
609+
);
610+
const result = await controller.get();
611+
612+
// Verify flakyJobs is returned and sorted by count descending
613+
assertEquals(result.data.flakyJobs.length, 3);
614+
615+
// linux-x64: 2 + 3 + 1 = 6 total flakes
616+
assertEquals(result.data.flakyJobs[0].name, "linux-x64");
617+
assertEquals(result.data.flakyJobs[0].count, 6);
618+
619+
// macos-arm64: 4 total flakes
620+
assertEquals(result.data.flakyJobs[1].name, "macos-arm64");
621+
assertEquals(result.data.flakyJobs[1].count, 4);
622+
623+
// windows-x64: 1 + 2 = 3 total flakes
624+
assertEquals(result.data.flakyJobs[2].name, "windows-x64");
625+
assertEquals(result.data.flakyJobs[2].count, 3);
626+
});

routes/insights.tsx

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ export class InsightsPageController {
8080
}
8181
>();
8282

83+
// Track flaky test counts per job
84+
const jobFlakyCountsMap = new Map<string, number>();
85+
8386
function processTest(
8487
test: RecordedTestResult,
8588
runId: number,
@@ -113,6 +116,12 @@ export class InsightsPageController {
113116
jobCounts: new Map([[jobName, test.flakyCount]]),
114117
});
115118
}
119+
120+
// Track flaky counts per job
121+
jobFlakyCountsMap.set(
122+
jobName,
123+
(jobFlakyCountsMap.get(jobName) || 0) + test.flakyCount,
124+
);
116125
}
117126

118127
// Track failed tests
@@ -166,10 +175,16 @@ export class InsightsPageController {
166175
(a, b) => b.failureCount - a.failureCount,
167176
);
168177

178+
// Convert job flaky counts to array and sort
179+
const flakyJobs = Array.from(jobFlakyCountsMap.entries())
180+
.map(([name, count]) => ({ name, count }))
181+
.sort((a, b) => b.count - a.count);
182+
169183
return {
170184
data: {
171185
flakyTests,
172186
failedTests,
187+
flakyJobs,
173188
totalRunsAnalyzed: mainBranchRuns.length,
174189
oldestRun: mainBranchRuns[mainBranchRuns.length - 1],
175190
newestRun: mainBranchRuns[0],
@@ -179,8 +194,14 @@ export class InsightsPageController {
179194
}
180195

181196
export default define.page<typeof handler>(function InsightsPage({ data }) {
182-
const { flakyTests, failedTests, totalRunsAnalyzed, oldestRun, newestRun } =
183-
data;
197+
const {
198+
flakyTests,
199+
failedTests,
200+
flakyJobs,
201+
totalRunsAnalyzed,
202+
oldestRun,
203+
newestRun,
204+
} = data;
184205

185206
return (
186207
<div class="container mx-auto px-4 py-8 max-w-7xl">
@@ -270,7 +291,7 @@ export default define.page<typeof handler>(function InsightsPage({ data }) {
270291
)}
271292
</div>
272293

273-
<div class="bg-white rounded-lg shadow">
294+
<div class="bg-white rounded-lg shadow mb-6">
274295
<div class="bg-yellow-100 px-4 py-3 rounded-t-lg border-b border-yellow-300">
275296
<div class="flex items-center justify-between">
276297
<h2 class="font-semibold text-xl">
@@ -354,6 +375,52 @@ export default define.page<typeof handler>(function InsightsPage({ data }) {
354375
</div>
355376
)}
356377
</div>
378+
379+
<div class="bg-white rounded-lg shadow">
380+
<div class="bg-purple-100 px-4 py-3 rounded-t-lg border-b border-purple-300">
381+
<div class="flex items-center justify-between">
382+
<h2 class="font-semibold text-xl">
383+
🔧 Most Flaky Jobs ({flakyJobs.length})
384+
</h2>
385+
</div>
386+
</div>
387+
{flakyJobs.length === 0
388+
? (
389+
<div class="p-8 text-center">
390+
<div class="text-6xl mb-4">🎉</div>
391+
<h3 class="text-xl font-bold mb-2">No Flaky Jobs!</h3>
392+
<p class="text-gray-600">
393+
No jobs had flaky tests across the analyzed runs.
394+
</p>
395+
</div>
396+
)
397+
: (
398+
<div class="divide-y divide-gray-200">
399+
{flakyJobs.map((job, idx) => (
400+
<div
401+
key={idx}
402+
class="px-4 py-3 hover:bg-gray-50 transition-colors"
403+
>
404+
<div class="flex items-center justify-between gap-4">
405+
<div class="flex-1 min-w-0">
406+
<div class="font-mono text-sm font-semibold text-gray-900">
407+
{job.name}
408+
</div>
409+
</div>
410+
<div class="flex-shrink-0">
411+
<div class="bg-purple-100 text-purple-800 px-3 py-2 rounded text-center">
412+
<div class="text-2xl font-bold">
413+
{job.count}
414+
</div>
415+
<div class="text-xs">flaky tests</div>
416+
</div>
417+
</div>
418+
</div>
419+
</div>
420+
))}
421+
</div>
422+
)}
423+
</div>
357424
</div>
358425
);
359426
});

0 commit comments

Comments
 (0)