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 }  } 
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 } ${ run . run_num }  } > 
157-                   < a  href = { run . url } > 
158-                     { emoji }  { run . run_num } 
161+       < div  key = { `${ job . name }  }  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 } ${ 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,8 @@ useEffect(() => {
179238      onRowToggle = { ( e )  =>  setExpandedRows ( e . data ) } 
180239      loading = { loading } 
181240      emptyMessage = "No results found." 
241+       sortField = "fails" 
242+       sortOrder = { - 1 } 
182243    > 
183244      < Column  expander /> 
184245      < Column 
@@ -195,6 +256,7 @@ useEffect(() => {
195256        header  =  "Runs" 
196257        className = "whitespace-nowrap px-2" 
197258        sortable  /> 
259+       < Column  field  =  "total_reruns"   header  =  "Reruns"   sortable /> 
198260      < Column  field  =  "fails"          header  =  "Fails"    sortable /> 
199261      < Column  field  =  "skips"          header  =  "Skips"    sortable /> 
200262      < Column  
@@ -214,6 +276,8 @@ useEffect(() => {
214276      onRowToggle = { ( e )  =>  setExpandedRows ( e . data ) } 
215277      loading = { loading } 
216278      emptyMessage = "No results found." 
279+       sortField = "fails" 
280+       sortOrder = { - 1 } 
217281    > 
218282      < Column  expander /> 
219283      < Column 
@@ -230,6 +294,7 @@ useEffect(() => {
230294        header  =  "Runs" 
231295        className = "whitespace-nowrap px-2" 
232296        sortable  /> 
297+       < Column  field  =  "total_reruns"   header  =  "Reruns"   sortable /> 
233298      < Column  field  =  "fails"          header  =  "Fails"    sortable /> 
234299      < Column  field  =  "skips"          header  =  "Skips"    sortable /> 
235300      < Column  
0 commit comments