1- import { useEffect , useState } from "react" ;
1+ import React , { useEffect , useState , useRef } from "react" ;
22import { DataTable } from "primereact/datatable" ;
33import { Column } from "primereact/column" ;
44import Head from "next/head" ;
55import { weatherTemplate , getWeatherIndex } from "../components/weatherTemplate" ;
6+ import { OverlayPanel } from 'primereact/overlaypanel' ;
7+
68
79
810export default function Home ( ) {
@@ -62,6 +64,8 @@ useEffect(() => {
6264 skips : job . skips ,
6365 required : job . required ,
6466 weather : getWeatherIndex ( job ) ,
67+ reruns : job . reruns ,
68+ total_reruns : job . reruns . reduce ( ( total , r ) => total + r , 0 ) ,
6569 } ) )
6670 ) ;
6771 setLoading ( false ) ;
@@ -81,6 +85,8 @@ useEffect(() => {
8185 skips : check . skips ,
8286 required : check . required ,
8387 weather : getWeatherIndex ( check ) ,
88+ reruns : check . reruns ,
89+ total_reruns : check . reruns . reduce ( ( total , r ) => total + r , 0 ) ,
8490 } ) )
8591 ) ;
8692 setLoading ( false ) ;
@@ -94,76 +100,129 @@ useEffect(() => {
94100
95101
96102
97- const toggleRow = ( rowData ) => {
98- const isRowExpanded = expandedRows . includes ( rowData ) ;
99-
100- let updatedExpandedRows ;
101- if ( isRowExpanded ) {
102- updatedExpandedRows = expandedRows . filter ( ( r ) => r !== rowData ) ;
103- } else {
104- updatedExpandedRows = [ ...expandedRows , rowData ] ;
105- }
106-
107- setExpandedRows ( updatedExpandedRows ) ;
108- } ;
109-
110103 const tabClass = ( active ) => `tab md:px-4 px-2 py-2 border-b-2 focus:outline-none
111104 ${ active ? "border-blue-500 bg-gray-300"
112105 : "border-gray-300 bg-white hover:bg-gray-100" } `;
113106
114107
115108 // Template for rendering the Name column as a clickable item
116- const nameTemplate = ( rowData ) => {
117- return (
118- < span onClick = { ( ) => toggleRow ( rowData ) } style = { { cursor : "pointer" } } >
119- { rowData . name }
120- </ span >
109+ const nameTemplate = ( rowData ) => (
110+ < div className = "cursor-pointer" onClick = { ( ) => toggleRow ( rowData ) } >
111+ < span style = { { userSelect : 'text' } } > { rowData . name } </ span >
112+ </ div >
113+ ) ;
114+
115+ const toggleRow = ( rowData ) => {
116+ setExpandedRows ( ( prev ) =>
117+ prev . includes ( rowData )
118+ ? prev . filter ( ( r ) => r !== rowData )
119+ : [ ...prev , rowData ]
121120 ) ;
122121 } ;
123122
123+ const rerunRefs = useRef ( [ ] ) ;
124+
124125 const rowExpansionTemplate = ( data ) => {
125126 const job = ( display === "nightly"
126127 ? jobs
127128 : checks ) . find ( ( job ) => job . name === data . name ) ;
128129
130+ if ( ! job ) return (
131+ < div className = "p-3 bg-gray-100" >
132+ No data available for this job.
133+ </ div > ) ;
134+
135+
136+ const getRunStatusIcon = ( runs ) => {
137+ if ( Array . isArray ( runs ) ) {
138+ const allPass = runs . every ( run => run === "Pass" ) ;
139+ const allFail = runs . every ( run => run === "Fail" ) ;
140+
141+ if ( allPass ) { return "✅" ; }
142+ if ( allFail ) { return "❌" ; }
143+ } else if ( runs === "Pass" ) {
144+ return "✅" ;
145+ } else if ( runs === "Fail" ) {
146+ return "❌" ;
147+ }
148+ return "⚠️" ; // return a warning if a mix of results
149+ } ;
129150
130- // Prepare run data
131- const runs = [ ] ;
132- for ( let i = 0 ; i < job . runs ; i ++ ) {
133- runs . push ( {
134- run_num : job . run_nums [ i ] ,
135- result : job . results [ i ] ,
136- url : job . urls [ i ] ,
137- } ) ;
138- }
151+ const runEntries = job . run_nums . map ( ( run_num , idx ) => ( {
152+ run_num,
153+ result : job . results [ idx ] ,
154+ reruns : job . reruns [ idx ] ,
155+ rerun_result : job . rerun_results [ idx ] ,
156+ url : job . urls [ idx ] ,
157+ attempt_urls : job . attempt_urls [ idx ] ,
158+ } ) ) ;
139159
140160 return (
141- < div
142- key = { `${ job . name } -runs` }
143- className = "p-3 bg-gray-100"
144- style = { { marginLeft : "4.5rem" , marginTop : "-2.0rem" } }
145- >
146- < div >
147- { runs . length > 0 ? (
148- runs . map ( ( run ) => {
149- const emoji =
150- run . result === "Pass"
151- ? "✅"
152- : run . result === "Fail"
153- ? "❌"
154- : "⚠️" ;
155- return (
156- < span key = { `${ job . name } -runs-${ run . run_num } ` } >
157- < a href = { run . url } >
158- { emoji } { run . run_num }
161+ < div key = { `${ job . name } -runs` } className = "p-3 bg-gray-100" >
162+ < div className = "flex flex-wrap gap-4" >
163+ { runEntries . map ( ( {
164+ run_num,
165+ result,
166+ url,
167+ reruns,
168+ rerun_result,
169+ attempt_urls
170+ } , idx ) => {
171+ const allResults = rerun_result
172+ ? [ result , ...rerun_result ]
173+ : [ result ] ;
174+
175+ const runStatuses = allResults . map ( ( result , idx ) =>
176+ `${ allResults . length - idx } . ${ result === 'Pass'
177+ ? '✅ Success'
178+ : result === 'Fail'
179+ ? '❌ Fail'
180+ : '⚠️ Warning' } `) ;
181+
182+ // IDs can't have a '/'...
183+ const sanitizedJobName = job . name . replace ( / [ ^ a - z A - Z 0 - 9 - _ ] / g, '' ) ;
184+
185+ const badgeReruns = `reruns-${ sanitizedJobName } -${ run_num } ` ;
186+
187+ rerunRefs . current [ badgeReruns ] = rerunRefs . current [ badgeReruns ]
188+ || React . createRef ( ) ;
189+
190+ return (
191+ < div key = { run_num } className = "flex" >
192+ < div key = { idx } className = "flex items-center" >
193+ { /* <a href={url} target="_blank" rel="noopener noreferrer"> */ }
194+ < a href = { attempt_urls [ 0 ] } target = "_blank" rel = "noopener noreferrer" >
195+ { getRunStatusIcon ( allResults ) } { run_num }
159196 </ a >
160-
161- </ span >
162- ) ;
163- } )
164- ) : (
165- < div > No Nightly Runs associated with this job</ div >
166- ) }
197+ </ div >
198+ { reruns > 0 && (
199+ < span className = "p-overlay-badge" >
200+ < sup className = "text-xs font-bold align-super ml-1"
201+ onMouseEnter = { ( e ) =>
202+ rerunRefs . current [ badgeReruns ] . current . toggle ( e ) } >
203+ { reruns + 1 }
204+ </ sup >
205+ < OverlayPanel ref = { rerunRefs . current [ badgeReruns ] } dismissable
206+ onMouseLeave = { ( e ) =>
207+ rerunRefs . current [ badgeReruns ] . current . toggle ( e ) } >
208+ < ul className = "bg-white border rounded shadow-lg p-2" >
209+ { runStatuses . map ( ( status , index ) => (
210+ < li key = { index } className = "p-2 hover:bg-gray-200" >
211+ < a
212+ href = { attempt_urls [ index ] || `${ url } /attempts/${ index } ` }
213+ target = "_blank"
214+ rel = "noopener noreferrer" >
215+ { status }
216+ </ a >
217+ </ li >
218+ ) ) }
219+ </ ul >
220+ </ OverlayPanel >
221+ </ span >
222+ ) }
223+ </ div >
224+ ) ;
225+ } ) }
167226 </ div >
168227 </ div >
169228 ) ;
@@ -179,6 +238,7 @@ useEffect(() => {
179238 onRowToggle = { ( e ) => setExpandedRows ( e . data ) }
180239 loading = { loading }
181240 emptyMessage = "No results found."
241+ sortField = "fails"
182242 >
183243 < Column expander />
184244 < Column
@@ -195,6 +255,7 @@ useEffect(() => {
195255 header = "Runs"
196256 className = "whitespace-nowrap px-2"
197257 sortable />
258+ < Column field = "total_reruns" header = "Reruns" sortable />
198259 < Column field = "fails" header = "Fails" sortable />
199260 < Column field = "skips" header = "Skips" sortable />
200261 < Column
@@ -214,6 +275,7 @@ useEffect(() => {
214275 onRowToggle = { ( e ) => setExpandedRows ( e . data ) }
215276 loading = { loading }
216277 emptyMessage = "No results found."
278+ sortField = "fails"
217279 >
218280 < Column expander />
219281 < Column
@@ -230,6 +292,7 @@ useEffect(() => {
230292 header = "Runs"
231293 className = "whitespace-nowrap px-2"
232294 sortable />
295+ < Column field = "total_reruns" header = "Reruns" sortable />
233296 < Column field = "fails" header = "Fails" sortable />
234297 < Column field = "skips" header = "Skips" sortable />
235298 < Column
0 commit comments