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 ,
@@ -49,6 +39,10 @@ import {AiAssistant} from '../../shared/ai-assistant/ai-assistant';
4939import { LighthouseCategory } from './lighthouse-category' ;
5040import { MultiSelect } from '../../shared/multi-select/multi-select' ;
5141import { FileCodeViewer } from '../../shared/file-code-viewer/file-code-viewer' ;
42+ import {
43+ calculateAverageRepairAttempts ,
44+ createRepairAttemptGraphData ,
45+ } from './repair-attempt-graph-builder' ;
5246
5347const 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 {
0 commit comments