@@ -75,6 +75,8 @@ export default function Home() {
75
75
skips : job . skips ,
76
76
required : job . required ,
77
77
weather : getWeatherIndex ( job ) ,
78
+ reruns : job . reruns ,
79
+ total_reruns : job . reruns . reduce ( ( total , r ) => total + r , 0 ) ,
78
80
} ) )
79
81
) ;
80
82
setLoading ( false ) ;
@@ -94,6 +96,8 @@ export default function Home() {
94
96
skips : check . skips ,
95
97
required : check . required ,
96
98
weather : getWeatherIndex ( check ) ,
99
+ reruns : check . reruns ,
100
+ total_reruns : check . reruns . reduce ( ( total , r ) => total + r , 0 ) ,
97
101
} ) )
98
102
) ;
99
103
setLoading ( false ) ;
@@ -137,21 +141,63 @@ export default function Home() {
137
141
} ;
138
142
139
143
const maintainRefs = useRef ( [ ] ) ;
144
+ const rerunRefs = useRef ( [ ] ) ;
140
145
141
146
const rowExpansionTemplate = ( data ) => {
142
147
const job = ( display === "nightly"
143
148
? jobs
144
149
: checks ) . find ( ( job ) => job . name === data . name ) ;
145
150
151
+ if ( ! job ) return (
152
+ < div className = "p-3 bg-gray-100" >
153
+ No data available for this job.
154
+ </ div >
155
+ ) ;
156
+
146
157
// Prepare run data
147
- const runs = [ ] ;
148
- for ( let i = 0 ; i < job . runs ; i ++ ) {
149
- runs . push ( {
150
- run_num : job . run_nums [ i ] ,
151
- result : job . results [ i ] ,
152
- url : job . urls [ i ] ,
153
- } ) ;
154
- }
158
+ const getRunStatusIcon = ( runs ) => {
159
+ if ( Array . isArray ( runs ) ) {
160
+ const allPass = runs . every ( run => run === "Pass" ) ;
161
+ const allFail = runs . every ( run => run === "Fail" ) ;
162
+
163
+ if ( allPass ) { return "✅" ; }
164
+ if ( allFail ) { return "❌" ; }
165
+ } else if ( runs === "Pass" ) {
166
+ return "✅" ;
167
+ } else if ( runs === "Fail" ) {
168
+ return "❌" ;
169
+ }
170
+ return "⚠️" ; // return a warning if a mix of results
171
+ } ;
172
+
173
+ const runEntries = job . run_nums . map ( ( run_num , idx ) => ( {
174
+ run_num,
175
+ result : job . results [ idx ] ,
176
+ reruns : job . reruns [ idx ] ,
177
+ rerun_result : job . rerun_results [ idx ] ,
178
+ url : job . urls [ idx ] ,
179
+ attempt_urls : job . attempt_urls [ idx ] ,
180
+ } ) ) ;
181
+
182
+ // Find maintainers for the given job
183
+ const maintainerData = MaintainerMapping . mappings
184
+ . filter ( ( { regex } ) => new RegExp ( regex ) . test ( job . name ) )
185
+ . flatMap ( ( match ) =>
186
+ match . owners . map ( ( owner ) => ( {
187
+ ...owner ,
188
+ group : match . group ,
189
+ } ) )
190
+ ) ;
191
+
192
+ // Group maintainers by their group name
193
+ const groupedMaintainers = maintainerData . reduce ( ( acc , owner ) => {
194
+ if ( ! acc [ owner . group ] ) {
195
+ acc [ owner . group ] = [ ] ;
196
+ }
197
+ acc [ owner . group ] . push ( owner ) ;
198
+ return acc ;
199
+ } , { } ) ;
200
+
155
201
156
202
// Find maintainers for the given job
157
203
const maintainerData = MaintainerMapping . mappings
@@ -177,25 +223,154 @@ export default function Home() {
177
223
< div key = { `${ job . name } -runs` } className = "p-3 bg-gray-100" >
178
224
{ /* Display last 10 runs */ }
179
225
< div className = "flex flex-wrap gap-4" >
180
- { runs . length > 0 ? (
181
- runs . map ( ( run ) => {
182
- const emoji =
183
- run . result === "Pass"
184
- ? "✅"
185
- : run . result === "Fail"
186
- ? "❌"
187
- : "⚠️" ;
188
- return (
189
- < span key = { `${ job . name } -runs-${ run . run_num } ` } >
190
- < a href = { run . url } target = "_blank" rel = "noopener noreferrer" >
191
- { emoji } { run . run_num }
226
+ { runEntries . map ( ( {
227
+ run_num,
228
+ result,
229
+ url,
230
+ reruns,
231
+ rerun_result,
232
+ attempt_urls
233
+ } , idx ) => {
234
+ const allResults = rerun_result
235
+ ? [ result , ...rerun_result ]
236
+ : [ result ] ;
237
+
238
+ const runStatuses = allResults . map ( ( result , idx ) =>
239
+ `${ allResults . length - idx } . ${ result === 'Pass'
240
+ ? '✅ Success'
241
+ : result === 'Fail'
242
+ ? '❌ Fail'
243
+ : '⚠️ Warning' } `) ;
244
+
245
+ // IDs can't have a '/'...
246
+ const sanitizedJobName = job . name . replace ( / [ ^ a - z A - Z 0 - 9 - _ ] / g, '' ) ;
247
+
248
+ const badgeReruns = `reruns-${ sanitizedJobName } -${ run_num } ` ;
249
+
250
+ rerunRefs . current [ badgeReruns ] = rerunRefs . current [ badgeReruns ]
251
+ || React . createRef ( ) ;
252
+
253
+ return (
254
+ < div key = { run_num } className = "flex" >
255
+ < div key = { idx } className = "flex items-center" >
256
+ { /* <a href={url} target="_blank" rel="noopener noreferrer"> */ }
257
+ < a href = { attempt_urls [ 0 ] } target = "_blank" rel = "noopener noreferrer" >
258
+ { getRunStatusIcon ( allResults ) } { run_num }
192
259
</ a >
193
-
194
- </ span >
195
- ) ;
196
- } )
260
+ </ div >
261
+ { reruns > 0 && (
262
+ < span className = "p-overlay-badge" >
263
+ < sup className = "text-xs font-bold align-super ml-1"
264
+ onMouseEnter = { ( e ) =>
265
+ rerunRefs . current [ badgeReruns ] . current . toggle ( e ) } >
266
+ { reruns + 1 }
267
+ </ sup >
268
+ < OverlayPanel ref = { rerunRefs . current [ badgeReruns ] } dismissable
269
+ onMouseLeave = { ( e ) =>
270
+ rerunRefs . current [ badgeReruns ] . current . toggle ( e ) } >
271
+ < ul className = "bg-white border rounded shadow-lg p-2" >
272
+ { runStatuses . map ( ( status , index ) => (
273
+ < li key = { index } className = "p-2 hover:bg-gray-200" >
274
+ < a
275
+ href = { attempt_urls [ index ] || `${ url } /attempts/${ index } ` }
276
+ target = "_blank"
277
+ rel = "noopener noreferrer" >
278
+ { status }
279
+ </ a >
280
+ </ li >
281
+ ) ) }
282
+ </ ul >
283
+ </ OverlayPanel >
284
+ </ span >
285
+ ) }
286
+ </ div >
287
+ ) ;
288
+ } ) }
289
+ </ div >
290
+ { /* Display Maintainers, if there's any */ }
291
+ < div className = "mt-4 p-2 bg-gray-300 w-full" >
292
+ { Object . keys ( groupedMaintainers ) . length > 0 ? (
293
+ < div className = "grid grid-cols-2 p-2 gap-6" >
294
+ { Object . entries ( groupedMaintainers ) . map (
295
+ ( [ group , owners ] , groupIndex ) => (
296
+ < div key = { groupIndex } className = "flex flex-col max-w-xs" >
297
+ { /* List the group name */ }
298
+ < strong className = "pl-2" > { group } :</ strong >
299
+ < div >
300
+ { /* List all maintainers for the group */ }
301
+ { owners . map ( ( owner , ownerIndex ) => {
302
+ const badgeMaintain = `maintain-${ owner . github } ` ;
303
+ maintainRefs . current [ badgeMaintain ] =
304
+ maintainRefs . current [ badgeMaintain ] || React . createRef ( ) ;
305
+
306
+ return (
307
+ // Create the OverlayPanel with contact information.
308
+ < span key = { ownerIndex } >
309
+ < span
310
+ onMouseEnter = { ( e ) =>
311
+ maintainRefs . current [ badgeMaintain ] . current . toggle ( e )
312
+ }
313
+ >
314
+ < a
315
+ href = { `https://github.com/${ owner . github } ` }
316
+ target = "_blank"
317
+ rel = "noopener noreferrer"
318
+ className = "text-blue-500 underline pl-2 whitespace-nowrap"
319
+ >
320
+ { owner . fullname }
321
+ </ a >
322
+ { ownerIndex < owners . length - 1 && ", " }
323
+ </ span >
324
+ < OverlayPanel
325
+ ref = { maintainRefs . current [ badgeMaintain ] }
326
+ dismissable
327
+ onMouseLeave = { ( e ) =>
328
+ maintainRefs . current [ badgeMaintain ] . current . toggle ( e )
329
+ }
330
+ >
331
+ < ul className = "bg-white border rounded shadow-lg p-2" >
332
+ < li className = "p-2 hover:bg-gray-200" >
333
+ < span className = "font-bold mr-4" > Email:</ span > { " " }
334
+ { owner . email }
335
+ </ li >
336
+ < a
337
+ href = { `https://github.com/${ owner . github } ` }
338
+ target = "_blank"
339
+ rel = "noopener noreferrer"
340
+ >
341
+ < li className = "p-2 hover:bg-gray-200 flex justify-between" >
342
+ < span className = "font-bold mr-4" >
343
+ GitHub:
344
+ </ span >
345
+ < span className = "text-right" >
346
+ { owner . github }
347
+ </ span >
348
+ </ li >
349
+ </ a >
350
+ < a
351
+ href = { `${ owner . slackurl } ` }
352
+ target = "_blank"
353
+ rel = "noopener noreferrer"
354
+ >
355
+ < li className = "p-2 hover:bg-gray-200 flex justify-between" >
356
+ < span className = "font-bold mr-4" > Slack:</ span >
357
+ < span className = "text-right" >
358
+ @{ owner . slack }
359
+ </ span >
360
+ </ li >
361
+ </ a >
362
+ </ ul >
363
+ </ OverlayPanel >
364
+ </ span >
365
+ ) ;
366
+ } ) }
367
+ </ div >
368
+ </ div >
369
+ )
370
+ ) }
371
+ </ div >
197
372
) : (
198
- < div > No Nightly Runs associated with this job </ div >
373
+ < div > No Maintainer Information Available </ div >
199
374
) }
200
375
</ div >
201
376
{ /* Display Maintainers, if there's any */ }
@@ -315,6 +490,7 @@ export default function Home() {
315
490
header = "Runs"
316
491
className = "whitespace-nowrap px-2"
317
492
sortable />
493
+ < Column field = "total_reruns" header = "Reruns" sortable />
318
494
< Column field = "fails" header = "Fails" sortable />
319
495
< Column field = "skips" header = "Skips" sortable />
320
496
< Column
@@ -351,6 +527,7 @@ export default function Home() {
351
527
header = "Runs"
352
528
className = "whitespace-nowrap px-2"
353
529
sortable />
530
+ < Column field = "total_reruns" header = "Reruns" sortable />
354
531
< Column field = "fails" header = "Fails" sortable />
355
532
< Column field = "skips" header = "Skips" sortable />
356
533
< Column
0 commit comments