11import { Clipboard } from '@angular/cdk/clipboard' ;
22import { DatePipe , DecimalPipe } from '@angular/common' ;
33import { 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' ;
155import { NgxJsonViewerModule } from 'ngx-json-viewer' ;
166import {
177 BuildErrorType ,
@@ -48,6 +38,10 @@ import {ProviderLabel} from '../../shared/provider-label';
4838import { AiAssistant } from '../../shared/ai-assistant/ai-assistant' ;
4939import { LighthouseCategory } from './lighthouse-category' ;
5040import { MultiSelect } from '../../shared/multi-select/multi-select' ;
41+ import {
42+ calculateAverageRepairAttempts ,
43+ createRepairAttemptGraphData ,
44+ } from './repair-attempt-graph-builder' ;
5145
5246const localReportRegex = / - l \d + $ / ;
5347
@@ -305,21 +299,7 @@ export class ReportViewer {
305299 return null ;
306300 }
307301
308- let totalRepairs = 0 ;
309- let count = 0 ;
310-
311- for ( const result of report . results ) {
312- // Only consider successful builds that required repairs.
313- if (
314- result . finalAttempt . buildResult . status === BuildResultStatus . SUCCESS &&
315- result . repairAttempts > 0
316- ) {
317- totalRepairs += result . repairAttempts ;
318- count ++ ;
319- }
320- }
321-
322- return count > 0 ? totalRepairs / count : null ;
302+ return calculateAverageRepairAttempts ( report ) ;
323303 } ) ;
324304
325305 protected repairAttemptsAsGraphData = computed < StackedBarChartData > ( ( ) => {
@@ -328,70 +308,7 @@ export class ReportViewer {
328308 return [ ] ;
329309 }
330310
331- const repairsToAppCount = new Map < number | 'failed' , number > ( ) ;
332-
333- // Map repair count to how many applications shared that count.
334- let maxRepairCount = 0 ;
335- for ( const result of report . results ) {
336- if ( result . finalAttempt . buildResult . status === BuildResultStatus . ERROR ) {
337- repairsToAppCount . set ( 'failed' , ( repairsToAppCount . get ( 'failed' ) || 0 ) + 1 ) ;
338- } else {
339- const repairs = result . repairAttempts ;
340- // For this graph, we ignore applications that required no repair.
341- if ( repairs > 0 ) {
342- repairsToAppCount . set ( repairs , ( repairsToAppCount . get ( repairs ) || 0 ) + 1 ) ;
343- maxRepairCount = Math . max ( maxRepairCount , repairs ) ;
344- }
345- }
346- }
347-
348- const data : StackedBarChartData = [ ] ;
349-
350- // All the numeric keys, sorted by value.
351- const intermediateRepairKeys = Array . from ( repairsToAppCount . keys ( ) )
352- . filter ( ( k ) : k is number => typeof k === 'number' )
353- . sort ( ( a , b ) => a - b ) ;
354-
355- // This graph might involve a bunch of sections. We want to scale them among all the possible color "grades".
356-
357- const minGrade = 1 ;
358- const maxGrade = 8 ;
359- const failureGrade = 9 ;
360-
361- for ( let repairCount = 1 ; repairCount <= maxRepairCount ; repairCount ++ ) {
362- const applicationCount = repairsToAppCount . get ( repairCount ) ;
363- if ( ! applicationCount ) continue ;
364- const label = `${ repairCount } repair${ repairCount > 1 ? 's' : '' } ` ;
365-
366- // Normalize the repair count to the range [0, 1].
367- const normalizedRepairCount = ( repairCount - 1 ) / ( maxRepairCount - 1 ) ;
368-
369- let gradeIndex : number ;
370- if ( intermediateRepairKeys . length === 1 ) {
371- // If there's only one intermediate repair count, map it to a middle grade (e.g., --chart-grade-5)
372- gradeIndex = Math . floor ( maxGrade / 2 ) + minGrade ;
373- } else {
374- // Distribute multiple intermediate repair counts evenly across available grades
375- gradeIndex = minGrade + Math . round ( normalizedRepairCount * ( maxGrade - minGrade ) ) ;
376- }
377-
378- data . push ( {
379- label,
380- color : `var(--chart-grade-${ gradeIndex } )` ,
381- value : applicationCount ,
382- } ) ;
383- }
384-
385- // Handle 'Build failed even after all retries' - always maps to the "failure" grade.
386- const failedCount = repairsToAppCount . get ( 'failed' ) || 0 ;
387- if ( failedCount > 0 ) {
388- data . push ( {
389- label : 'Build failed even after all retries' ,
390- color : `var(--chart-grade-${ failureGrade } )` ,
391- value : failedCount ,
392- } ) ;
393- }
394- return data ;
311+ return createRepairAttemptGraphData ( report ) ;
395312 } ) ;
396313
397314 protected testsAsGraphData ( tests : RunSummaryTests ) : StackedBarChartData {
0 commit comments