@@ -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 ,
@@ -287,6 +290,116 @@ export class ReportViewer {
287290 ] ;
288291 }
289292
293+ protected hasSuccessfulResultWithMoreThanOneBuildAttempt = computed ( ( ) => {
294+ if ( ! this . selectedReport . hasValue ( ) ) {
295+ return false ;
296+ }
297+ for ( const result of this . selectedReport . value ( ) . results ) {
298+ if (
299+ result . finalAttempt . buildResult . status === BuildResultStatus . SUCCESS &&
300+ result . repairAttempts > 1
301+ ) {
302+ return true ;
303+ }
304+ }
305+ return false ;
306+ } ) ;
307+
308+ protected averageRepairAttempts = computed < number | null > ( ( ) => {
309+ const report = this . selectedReportWithSortedResults ( ) ;
310+ if ( ! report ) {
311+ return null ;
312+ }
313+
314+ let totalRepairs = 0 ;
315+ let count = 0 ;
316+
317+ for ( const result of report . results ) {
318+ // Only consider successful builds that required repairs.
319+ if (
320+ result . finalAttempt . buildResult . status === BuildResultStatus . SUCCESS &&
321+ result . repairAttempts > 0
322+ ) {
323+ totalRepairs += result . repairAttempts ;
324+ count ++ ;
325+ }
326+ }
327+
328+ return count > 0 ? totalRepairs / count : null ;
329+ } ) ;
330+
331+ protected repairAttemptsAsGraphData = computed < StackedBarChartData > ( ( ) => {
332+ const report = this . selectedReportWithSortedResults ( ) ;
333+ if ( ! report ) {
334+ return [ ] ;
335+ }
336+
337+ const repairsToAppCount = new Map < number | 'failed' , number > ( ) ;
338+
339+ // Map repair count to how many applications shared that count.
340+ let maxRepairCount = 0 ;
341+ for ( const result of report . results ) {
342+ if ( result . finalAttempt . buildResult . status === BuildResultStatus . ERROR ) {
343+ repairsToAppCount . set ( 'failed' , ( repairsToAppCount . get ( 'failed' ) || 0 ) + 1 ) ;
344+ } else {
345+ const repairs = result . repairAttempts ;
346+ // For this graph, we ignore applications that required no repair.
347+ if ( repairs > 0 ) {
348+ repairsToAppCount . set ( repairs , ( repairsToAppCount . get ( repairs ) || 0 ) + 1 ) ;
349+ maxRepairCount = Math . max ( maxRepairCount , repairs ) ;
350+ }
351+ }
352+ }
353+
354+ const data : StackedBarChartData = [ ] ;
355+
356+ // All the numeric keys, sorted by value.
357+ const intermediateRepairKeys = Array . from ( repairsToAppCount . keys ( ) )
358+ . filter ( ( k ) : k is number => typeof k === 'number' )
359+ . sort ( ( a , b ) => a - b ) ;
360+
361+ // This graph might involve a bunch of sections. We want to scale them among all the possible color "grades".
362+
363+ const minGrade = 1 ;
364+ const maxGrade = 8 ;
365+ const failureGrade = 9 ;
366+
367+ for ( let repairCount = 1 ; repairCount <= maxRepairCount ; repairCount ++ ) {
368+ const applicationCount = repairsToAppCount . get ( repairCount ) ;
369+ if ( ! applicationCount ) continue ;
370+ const label = `${ repairCount } repair${ repairCount > 1 ? 's' : '' } ` ;
371+
372+ // Normalize the repair count to the range [0, 1].
373+ const normalizedRepairCount = ( repairCount - 1 ) / ( maxRepairCount - 1 ) ;
374+
375+ let gradeIndex : number ;
376+ if ( intermediateRepairKeys . length === 1 ) {
377+ // If there's only one intermediate repair count, map it to a middle grade (e.g., --chart-grade-5)
378+ gradeIndex = Math . floor ( maxGrade / 2 ) + minGrade ;
379+ } else {
380+ // Distribute multiple intermediate repair counts evenly across available grades
381+ gradeIndex = minGrade + Math . round ( normalizedRepairCount * ( maxGrade - minGrade ) ) ;
382+ }
383+
384+ data . push ( {
385+ label,
386+ color : `var(--chart-grade-${ gradeIndex } )` ,
387+ value : applicationCount ,
388+ } ) ;
389+ }
390+
391+ // Handle 'Build failed even after all retries' - always maps to the "failure" grade.
392+ const failedCount = repairsToAppCount . get ( 'failed' ) || 0 ;
393+ if ( failedCount > 0 ) {
394+ data . push ( {
395+ label : 'Build failed even after all retries' ,
396+ color : `var(--chart-grade-${ failureGrade } )` ,
397+ value : failedCount ,
398+ } ) ;
399+ }
400+ return data ;
401+ } ) ;
402+
290403 protected testsAsGraphData ( tests : RunSummaryTests ) : StackedBarChartData {
291404 return [
292405 {
0 commit comments