1
- import { useEffect , useState } from "react" ;
1
+ import React , { useEffect , useState , useRef } from "react" ;
2
2
import { DataTable } from "primereact/datatable" ;
3
3
import { Column } from "primereact/column" ;
4
4
import Head from "next/head" ;
5
5
import { weatherTemplate , getWeatherIndex } from "../components/weatherTemplate" ;
6
+ import { OverlayPanel } from 'primereact/overlaypanel' ;
7
+
6
8
7
9
8
10
export default function Home ( ) {
@@ -62,6 +64,8 @@ useEffect(() => {
62
64
skips : job . skips ,
63
65
required : job . required ,
64
66
weather : getWeatherIndex ( job ) ,
67
+ reruns : job . reruns ,
68
+ total_reruns : job . reruns . reduce ( ( total , r ) => total + r , 0 ) ,
65
69
} ) )
66
70
) ;
67
71
setLoading ( false ) ;
@@ -81,6 +85,8 @@ useEffect(() => {
81
85
skips : check . skips ,
82
86
required : check . required ,
83
87
weather : getWeatherIndex ( check ) ,
88
+ reruns : check . reruns ,
89
+ total_reruns : check . reruns . reduce ( ( total , r ) => total + r , 0 ) ,
84
90
} ) )
85
91
) ;
86
92
setLoading ( false ) ;
@@ -94,76 +100,129 @@ useEffect(() => {
94
100
95
101
96
102
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
-
110
103
const tabClass = ( active ) => `tab md:px-4 px-2 py-2 border-b-2 focus:outline-none
111
104
${ active ? "border-blue-500 bg-gray-300"
112
105
: "border-gray-300 bg-white hover:bg-gray-100" } `;
113
106
114
107
115
108
// 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 ]
121
120
) ;
122
121
} ;
123
122
123
+ const rerunRefs = useRef ( [ ] ) ;
124
+
124
125
const rowExpansionTemplate = ( data ) => {
125
126
const job = ( display === "nightly"
126
127
? jobs
127
128
: checks ) . find ( ( job ) => job . name === data . name ) ;
128
129
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
+ } ;
129
150
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
+ } ) ) ;
139
159
140
160
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 }
159
196
</ 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
+ } ) }
167
226
</ div >
168
227
</ div >
169
228
) ;
@@ -179,6 +238,7 @@ useEffect(() => {
179
238
onRowToggle = { ( e ) => setExpandedRows ( e . data ) }
180
239
loading = { loading }
181
240
emptyMessage = "No results found."
241
+ sortField = "fails"
182
242
>
183
243
< Column expander />
184
244
< Column
@@ -195,6 +255,7 @@ useEffect(() => {
195
255
header = "Runs"
196
256
className = "whitespace-nowrap px-2"
197
257
sortable />
258
+ < Column field = "total_reruns" header = "Reruns" sortable />
198
259
< Column field = "fails" header = "Fails" sortable />
199
260
< Column field = "skips" header = "Skips" sortable />
200
261
< Column
@@ -214,6 +275,7 @@ useEffect(() => {
214
275
onRowToggle = { ( e ) => setExpandedRows ( e . data ) }
215
276
loading = { loading }
216
277
emptyMessage = "No results found."
278
+ sortField = "fails"
217
279
>
218
280
< Column expander />
219
281
< Column
@@ -230,6 +292,7 @@ useEffect(() => {
230
292
header = "Runs"
231
293
className = "whitespace-nowrap px-2"
232
294
sortable />
295
+ < Column field = "total_reruns" header = "Reruns" sortable />
233
296
< Column field = "fails" header = "Fails" sortable />
234
297
< Column field = "skips" header = "Skips" sortable />
235
298
< Column
0 commit comments