@@ -9,6 +9,20 @@ import styles from "./styles.module.css";
99import { Tooltip } from "react-tooltip" ;
1010import Tabs from '@theme/Tabs' ;
1111import TabItem from '@theme/TabItem' ;
12+ import moment from 'moment' ;
13+
14+ // GitHub GraphQL MergeStateStatus documentation
15+ // Reference: https://docs.github.com/en/graphql/reference/enums#mergestatestatus
16+ const CI_STATUS_DESCRIPTIONS = {
17+ clean : "Mergeable and passing commit status." ,
18+ unstable : "Mergeable with non-passing commit status." ,
19+ behind : "The head ref is out of date." ,
20+ blocked : "The merge is blocked." ,
21+ dirty : "The merge commit cannot be cleanly created." ,
22+ draft : "The merge is blocked due to the pull request being a draft." ,
23+ has_hooks : "Mergeable with passing commit status and pre-receive hooks." ,
24+ unknown : "The state cannot currently be determined."
25+ } ;
1226
1327// { Done, In PR, Awaiting PR, Awaiting parents, Not solvable, Bot error }
1428// The third value is a boolean representing the default display state on load
@@ -39,6 +53,44 @@ export function measureProgress(details) {
3953 return { done, percentage, total } ;
4054}
4155
56+ // Mapping of GitHub PR status to UI display properties
57+ const STATUS_DISPLAY_MAP = {
58+ clean : { text : "Passing" , badgeClass : "success" } ,
59+ unstable : { text : "Failing" , badgeClass : "danger" } ,
60+ dirty : { text : "Conflicts" , badgeClass : "warning" } ,
61+ blocked : { text : "Blocked" , badgeClass : "danger" } ,
62+ behind : { text : "Passing*" , badgeClass : "success" } ,
63+ draft : { text : "Draft" , badgeClass : "secondary" } ,
64+ has_hooks : { text : "Unknown*" , badgeClass : "secondary" } ,
65+ unknown : { text : "Unknown" , badgeClass : "secondary" } ,
66+ } ;
67+
68+ function getStatusBadgeClass ( prStatus ) {
69+ if ( ! STATUS_DISPLAY_MAP [ prStatus ] ) {
70+ console . warn ( `Unknown PR status: "${ prStatus } ". Expected one of: ${ Object . keys ( STATUS_DISPLAY_MAP ) . join ( ", " ) } ` ) ;
71+ return "secondary" ;
72+ }
73+ return STATUS_DISPLAY_MAP [ prStatus ] . badgeClass ;
74+ }
75+
76+ function getStatusDisplayText ( prStatus ) {
77+ if ( ! STATUS_DISPLAY_MAP [ prStatus ] ) {
78+ console . warn ( `Unknown PR status: "${ prStatus } ". Expected one of: ${ Object . keys ( STATUS_DISPLAY_MAP ) . join ( ", " ) } ` ) ;
79+ return prStatus ;
80+ }
81+ return STATUS_DISPLAY_MAP [ prStatus ] . text ;
82+ }
83+
84+ function formatRelativeTime ( timestamp ) {
85+ if ( ! timestamp ) return null ;
86+ return moment ( timestamp ) . fromNow ( ) ;
87+ }
88+
89+ function formatExactDateTime ( timestamp ) {
90+ if ( ! timestamp ) return null ;
91+ return moment ( timestamp ) . format ( 'LLLL' ) ;
92+ }
93+
4294export default function MigrationDetails ( ) {
4395 const location = useLocation ( ) ;
4496 const { siteConfig } = useDocusaurusContext ( ) ;
@@ -139,6 +191,16 @@ export default function MigrationDetails() {
139191 }
140192 </ div >
141193 </ div >
194+ { view === "table" && (
195+ < div className = { `card margin-top--md` } >
196+ < div className = "card__header" >
197+ < h3 > CI Status Legend</ h3 >
198+ </ div >
199+ < div className = "card__body" >
200+ < CIStatusLegend />
201+ </ div >
202+ </ div >
203+ ) }
142204 </ main >
143205 </ Layout >
144206 ) ;
@@ -284,6 +346,7 @@ function Table({ details }) {
284346 || ORDERED . findIndex ( x => x [ 0 ] == a [ 1 ] ) - ORDERED . findIndex ( x => x [ 0 ] == b [ 1 ] )
285347 || a [ 0 ] . localeCompare ( b [ 0 ] ) )
286348 ) ;
349+
287350 return (
288351 < >
289352 < Filters
@@ -295,7 +358,9 @@ function Table({ details }) {
295358 < thead >
296359 < tr >
297360 < th style = { { width : 200 } } > Name</ th >
298- < th style = { { width : 115 } } > Status</ th >
361+ < th style = { { width : 115 } } > Migration Status</ th >
362+ < th style = { { width : 115 } } > CI Status</ th >
363+ < th style = { { width : 115 } } > Last Updated</ th >
299364 < th style = { { width : 115 } } > Total number of children</ th >
300365 < th style = { { flex : 1 } } > Immediate children</ th >
301366 </ tr >
@@ -317,6 +382,9 @@ function Row({ children }) {
317382 const total_children = feedstock [ "num_descendants" ] ;
318383 const href = feedstock [ "pr_url" ] ;
319384 const details = feedstock [ "pre_pr_migrator_status" ] ;
385+ const pr_status = feedstock [ "pr_status" ] ;
386+
387+
320388 return ( < >
321389 < tr >
322390 < td >
@@ -332,6 +400,23 @@ function Row({ children }) {
332400 ) }
333401 </ td >
334402 < td style = { { textAlign : "center" } } > { TITLES [ status ] } </ td >
403+ < td style = { { textAlign : "center" } } >
404+ { pr_status ? (
405+ < span
406+ className = { `badge badge--${ getStatusBadgeClass ( pr_status ) } ` }
407+ title = { CI_STATUS_DESCRIPTIONS [ pr_status ] || pr_status }
408+ >
409+ { getStatusDisplayText ( pr_status ) }
410+ </ span >
411+ ) : (
412+ < span > —</ span >
413+ ) }
414+ </ td >
415+ < td style = { { textAlign : "center" } } >
416+ < span title = { formatExactDateTime ( feedstock [ "updated_at" ] ) } >
417+ { formatRelativeTime ( feedstock [ "updated_at" ] ) || "—" }
418+ </ span >
419+ </ td >
335420 < td style = { { textAlign : "center" } } > { total_children || null } </ td >
336421 < td >
337422 { immediate_children . map ( ( name , index ) => ( < React . Fragment key = { index } >
@@ -343,11 +428,34 @@ function Row({ children }) {
343428 </ td >
344429 </ tr >
345430 { details && ! collapsed && ( < tr >
346- < td colSpan = { 4 } > < pre dangerouslySetInnerHTML = { { __html : details } } /> </ td >
431+ < td colSpan = { 6 } > < pre dangerouslySetInnerHTML = { { __html : details } } /> </ td >
347432 </ tr > ) }
348433 </ > ) ;
349434}
350435
436+ function CIStatusLegend ( ) {
437+ const colorOrder = { danger : 0 , success : 1 , secondary : 2 } ;
438+ const sortedStatuses = Object . entries ( CI_STATUS_DESCRIPTIONS ) . sort (
439+ ( [ statusA ] , [ statusB ] ) => {
440+ const classA = getStatusBadgeClass ( statusA ) ;
441+ const classB = getStatusBadgeClass ( statusB ) ;
442+ return ( colorOrder [ classA ] ?? 3 ) - ( colorOrder [ classB ] ?? 3 ) ;
443+ }
444+ ) ;
445+
446+ return (
447+ < div className = { styles . ci_status_legend } >
448+ { sortedStatuses . map ( ( [ status , description ] ) => (
449+ < div key = { status } className = { styles . ci_status_item } >
450+ < span className = { `badge badge--${ getStatusBadgeClass ( status ) } ` } > { getStatusDisplayText ( status ) } </ span >
451+ < span > { description } </ span >
452+ </ div >
453+ ) ) }
454+ < a href = "https://docs.github.com/en/graphql/reference/enums#mergestatestatus" target = "_blank" rel = "noopener noreferrer" > See GitHub Docs</ a >
455+ </ div >
456+ ) ;
457+ }
458+
351459async function checkPausedOrClosed ( name ) {
352460 for ( const status of [ "paused" , "closed" ] ) {
353461 try {
0 commit comments