@@ -13,7 +13,10 @@ import {
1313 viewChild ,
1414} from '@angular/core' ;
1515import { NgxJsonViewerModule } from 'ngx-json-viewer' ;
16- import { BuildErrorType } from '../../../../../runner/workers/builder/builder-types' ;
16+ import {
17+ BuildErrorType ,
18+ BuildResultStatus ,
19+ } from '../../../../../runner/workers/builder/builder-types' ;
1720import {
1821 AssessmentResult ,
1922 AssessmentResultFromReportServer ,
@@ -281,6 +284,116 @@ export class ReportViewer {
281284 ] ;
282285 }
283286
287+ protected hasSuccessfulResultWithMoreThanOneBuildAttempt = computed ( ( ) => {
288+ if ( ! this . selectedReport . hasValue ( ) ) {
289+ return false ;
290+ }
291+ for ( const result of this . selectedReport . value ( ) . results ) {
292+ if (
293+ result . finalAttempt . buildResult . status === BuildResultStatus . SUCCESS &&
294+ result . repairAttempts > 1
295+ ) {
296+ return true ;
297+ }
298+ }
299+ return false ;
300+ } ) ;
301+
302+ protected averageRepairAttempts = computed < number | null > ( ( ) => {
303+ const report = this . selectedReportWithSortedResults ( ) ;
304+ if ( ! report ) {
305+ return null ;
306+ }
307+
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 ;
323+ } ) ;
324+
325+ protected repairAttemptsAsGraphData = computed < StackedBarChartData > ( ( ) => {
326+ const report = this . selectedReportWithSortedResults ( ) ;
327+ if ( ! report ) {
328+ return [ ] ;
329+ }
330+
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 ;
395+ } ) ;
396+
284397 protected testsAsGraphData ( tests : RunSummaryTests ) : StackedBarChartData {
285398 return [
286399 {
0 commit comments