Skip to content

Commit 27af9b8

Browse files
amishneAndrewKushnir
authored andcommitted
refactor: change code location and color gradiant of repair attempt graph
1 parent 564d1f3 commit 27af9b8

File tree

4 files changed

+128
-101
lines changed

4 files changed

+128
-101
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import {RunInfoFromReportServer} from '../../../../../runner/shared-interfaces';
2+
import {BuildResultStatus} from '../../../../../runner/workers/builder/builder-types';
3+
import {ScoreCssVariable} from '../../shared/scoring';
4+
import {StackedBarChartData} from '../../shared/visualization/stacked-bar-chart/stacked-bar-chart';
5+
6+
/**
7+
* Calculates the average number of repair attempts performed in a run.
8+
*/
9+
export function calculateAverageRepairAttempts(report: RunInfoFromReportServer) {
10+
let totalRepairs = 0;
11+
let count = 0;
12+
13+
for (const result of report.results) {
14+
// Only consider successful builds that required repairs.
15+
if (
16+
result.finalAttempt.buildResult.status === BuildResultStatus.SUCCESS &&
17+
result.repairAttempts > 0
18+
) {
19+
totalRepairs += result.repairAttempts;
20+
count++;
21+
}
22+
}
23+
24+
return count > 0 ? totalRepairs / count : null;
25+
}
26+
27+
/**
28+
* Creates graph data for the "repair attempt" graph, from a given run report.
29+
*/
30+
export function createRepairAttemptGraphData(report: RunInfoFromReportServer) {
31+
const repairsToAppCount = new Map<number | 'failed', number>();
32+
33+
// Map repair count to how many applications shared that count.
34+
let maxRepairCount = 0;
35+
for (const result of report.results) {
36+
if (result.finalAttempt.buildResult.status === BuildResultStatus.ERROR) {
37+
repairsToAppCount.set('failed', (repairsToAppCount.get('failed') || 0) + 1);
38+
} else {
39+
const repairs = result.repairAttempts;
40+
// For this graph, we ignore applications that required no repair.
41+
if (repairs > 0) {
42+
repairsToAppCount.set(repairs, (repairsToAppCount.get(repairs) || 0) + 1);
43+
maxRepairCount = Math.max(maxRepairCount, repairs);
44+
}
45+
}
46+
}
47+
48+
const data: StackedBarChartData = [];
49+
50+
// All the numeric keys, sorted by value.
51+
const intermediateRepairKeys = Array.from(repairsToAppCount.keys())
52+
.filter((k): k is number => typeof k === 'number')
53+
.sort((a, b) => a - b);
54+
55+
// This graph might involve a bunch of sections. We want to scale them among all the possible color "grades".
56+
57+
for (let repairCount = 1; repairCount <= maxRepairCount; repairCount++) {
58+
const applicationCount = repairsToAppCount.get(repairCount);
59+
if (!applicationCount) continue;
60+
61+
data.push({
62+
label: labelByRepairCount(repairCount),
63+
color: colorByRepairCount(repairCount),
64+
value: applicationCount,
65+
});
66+
}
67+
68+
// Handle 'Build failed even after all retries' - always maps to the "failure" grade.
69+
const failedCount = repairsToAppCount.get('failed') || 0;
70+
if (failedCount > 0) {
71+
data.push({
72+
label: 'Build failed even after all retries',
73+
color: ScoreCssVariable.poor,
74+
value: failedCount,
75+
});
76+
}
77+
return data;
78+
}
79+
80+
function labelByRepairCount(repairCount: number): string {
81+
switch (repairCount) {
82+
case 1:
83+
return '1 repair';
84+
case 2:
85+
case 3:
86+
case 4:
87+
return `${repairCount} repairs`;
88+
default:
89+
return '5+ repairs';
90+
}
91+
}
92+
93+
function colorByRepairCount(repairCount: number): string {
94+
// We're using mediocre1-5 since these are essentially *all* bad so we don't want green in this
95+
// graph.
96+
switch (repairCount) {
97+
case 1:
98+
return ScoreCssVariable.mediocre1;
99+
case 2:
100+
return ScoreCssVariable.mediocre2;
101+
case 3:
102+
return ScoreCssVariable.mediocre3;
103+
case 4:
104+
return ScoreCssVariable.mediocre4;
105+
default:
106+
return ScoreCssVariable.mediocre5;
107+
}
108+
}

report-app/src/app/pages/report-viewer/report-viewer.ts

Lines changed: 7 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,7 @@
11
import {Clipboard} from '@angular/cdk/clipboard';
22
import {DatePipe, DecimalPipe} from '@angular/common';
33
import {HttpClient} from '@angular/common/http';
4-
import {
5-
afterNextRender,
6-
Component,
7-
computed,
8-
ElementRef,
9-
inject,
10-
input,
11-
resource,
12-
signal,
13-
viewChild,
14-
} from '@angular/core';
4+
import {afterNextRender, Component, computed, inject, input, resource, signal} from '@angular/core';
155
import {NgxJsonViewerModule} from 'ngx-json-viewer';
166
import {
177
BuildErrorType,
@@ -49,6 +39,10 @@ import {AiAssistant} from '../../shared/ai-assistant/ai-assistant';
4939
import {LighthouseCategory} from './lighthouse-category';
5040
import {MultiSelect} from '../../shared/multi-select/multi-select';
5141
import {FileCodeViewer} from '../../shared/file-code-viewer/file-code-viewer';
42+
import {
43+
calculateAverageRepairAttempts,
44+
createRepairAttemptGraphData,
45+
} from './repair-attempt-graph-builder';
5246

5347
const localReportRegex = /-l\d+$/;
5448

@@ -307,21 +301,7 @@ export class ReportViewer {
307301
return null;
308302
}
309303

310-
let totalRepairs = 0;
311-
let count = 0;
312-
313-
for (const result of report.results) {
314-
// Only consider successful builds that required repairs.
315-
if (
316-
result.finalAttempt.buildResult.status === BuildResultStatus.SUCCESS &&
317-
result.repairAttempts > 0
318-
) {
319-
totalRepairs += result.repairAttempts;
320-
count++;
321-
}
322-
}
323-
324-
return count > 0 ? totalRepairs / count : null;
304+
return calculateAverageRepairAttempts(report);
325305
});
326306

327307
protected repairAttemptsAsGraphData = computed<StackedBarChartData>(() => {
@@ -330,70 +310,7 @@ export class ReportViewer {
330310
return [];
331311
}
332312

333-
const repairsToAppCount = new Map<number | 'failed', number>();
334-
335-
// Map repair count to how many applications shared that count.
336-
let maxRepairCount = 0;
337-
for (const result of report.results) {
338-
if (result.finalAttempt.buildResult.status === BuildResultStatus.ERROR) {
339-
repairsToAppCount.set('failed', (repairsToAppCount.get('failed') || 0) + 1);
340-
} else {
341-
const repairs = result.repairAttempts;
342-
// For this graph, we ignore applications that required no repair.
343-
if (repairs > 0) {
344-
repairsToAppCount.set(repairs, (repairsToAppCount.get(repairs) || 0) + 1);
345-
maxRepairCount = Math.max(maxRepairCount, repairs);
346-
}
347-
}
348-
}
349-
350-
const data: StackedBarChartData = [];
351-
352-
// All the numeric keys, sorted by value.
353-
const intermediateRepairKeys = Array.from(repairsToAppCount.keys())
354-
.filter((k): k is number => typeof k === 'number')
355-
.sort((a, b) => a - b);
356-
357-
// This graph might involve a bunch of sections. We want to scale them among all the possible color "grades".
358-
359-
const minGrade = 1;
360-
const maxGrade = 8;
361-
const failureGrade = 9;
362-
363-
for (let repairCount = 1; repairCount <= maxRepairCount; repairCount++) {
364-
const applicationCount = repairsToAppCount.get(repairCount);
365-
if (!applicationCount) continue;
366-
const label = `${repairCount} repair${repairCount > 1 ? 's' : ''}`;
367-
368-
// Normalize the repair count to the range [0, 1].
369-
const normalizedRepairCount = (repairCount - 1) / (maxRepairCount - 1);
370-
371-
let gradeIndex: number;
372-
if (intermediateRepairKeys.length === 1) {
373-
// If there's only one intermediate repair count, map it to a middle grade (e.g., --chart-grade-5)
374-
gradeIndex = Math.floor(maxGrade / 2) + minGrade;
375-
} else {
376-
// Distribute multiple intermediate repair counts evenly across available grades
377-
gradeIndex = minGrade + Math.round(normalizedRepairCount * (maxGrade - minGrade));
378-
}
379-
380-
data.push({
381-
label,
382-
color: `var(--chart-grade-${gradeIndex})`,
383-
value: applicationCount,
384-
});
385-
}
386-
387-
// Handle 'Build failed even after all retries' - always maps to the "failure" grade.
388-
const failedCount = repairsToAppCount.get('failed') || 0;
389-
if (failedCount > 0) {
390-
data.push({
391-
label: 'Build failed even after all retries',
392-
color: `var(--chart-grade-${failureGrade})`,
393-
value: failedCount,
394-
});
395-
}
396-
return data;
313+
return createRepairAttemptGraphData(report);
397314
});
398315

399316
protected testsAsGraphData(tests: RunSummaryTests): StackedBarChartData {

report-app/src/app/shared/scoring.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ export enum ScoreCssVariable {
88
good = 'var(--status-fill-good)',
99
poor = 'var(--status-fill-poor)',
1010
neutral = 'var(--status-fill-neutral)',
11+
// When we need more refined gradiant between "good" and "poor".
12+
mediocre1 = 'var(--status-fill-mediocre-1)',
13+
mediocre2 = 'var(--status-fill-mediocre-2)',
14+
mediocre3 = 'var(--status-fill-mediocre-3)',
15+
mediocre4 = 'var(--status-fill-mediocre-4)',
16+
mediocre5 = 'var(--status-fill-mediocre-5)',
1117
}
1218

1319
const CACHED_COLORS = {

report-app/src/styles.scss

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,19 @@
3232
--status-fill-poor: #ef4444;
3333
--status-fill-neutral: #aaa;
3434

35+
/* When we need a more gradiant spread of "meh". */
36+
--status-fill-mediocre-1: #fbbc04; /* Yellow 500 */
37+
--status-fill-mediocre-2: #f9ab00; /* Yellow 600 */
38+
--status-fill-mediocre-3: #f29900; /* Yellow 700 */
39+
--status-fill-mediocre-4: #ea8600; /* Yellow 800 */
40+
--status-fill-mediocre-5: #e37400; /* Yellow 900 */
41+
3542
--status-text-excellent: #0c855d;
3643
--status-text-great: #0c855d; // TODO: do we want to differentiate from `excellent`?
3744
--status-text-good: #c57f08;
3845
--status-text-poor: #eb1515;
3946
--status-text-neutral: #64748b;
4047

41-
/* 10-step Green-to-Red Quality Gradient */
42-
--chart-grade-1: #10b981; /* Emerald 500 (Excellent) */
43-
--chart-grade-2: #22c55e; /* Green 500 */
44-
--chart-grade-3: #4ade80; /* Green 400 */
45-
--chart-grade-4: #84cc16; /* Lime 500 (Great) */
46-
--chart-grade-5: #a3e635; /* Lime 400 */
47-
--chart-grade-6: #facc15; /* Yellow 400 */
48-
--chart-grade-7: #f59e0b; /* Amber 500 (Good) */
49-
--chart-grade-8: #f97316; /* Orange 500 */
50-
--chart-grade-9: #ef4444; /* Red 500 (Poor) */
51-
5248
--tooltip-background-color: light-dark(#111827, #f1f4f9);
5349
--tooltip-text-color: light-dark(#f9fafb, #1e293b);
5450

0 commit comments

Comments
 (0)