@@ -11,23 +11,19 @@ dayjs.extend(duration)
11
11
12
12
const estimateFinish = (
13
13
startedAt : Dayjs ,
14
- templateAverage ? : number ,
14
+ buildEstimate : number ,
15
15
) : [ number , string ] => {
16
- if ( templateAverage === undefined ) {
17
- return [ 0 , "Unknown" ]
18
- }
19
- const realPercentage = dayjs ( ) . diff ( startedAt ) / templateAverage
16
+ const realPercentage = dayjs ( ) . diff ( startedAt ) / buildEstimate
20
17
21
- // Showing a full bar is frustrating.
22
- const maxPercentage = 0.99
18
+ const maxPercentage = 1
23
19
if ( realPercentage > maxPercentage ) {
24
- return [ maxPercentage , "Any moment now..." ]
20
+ return [ maxPercentage * 100 , "Any moment now..." ]
25
21
}
26
22
27
23
return [
28
- realPercentage ,
24
+ realPercentage * 100 ,
29
25
`~${ Math . ceil (
30
- dayjs . duration ( ( 1 - realPercentage ) * templateAverage ) . asSeconds ( ) ,
26
+ dayjs . duration ( ( 1 - realPercentage ) * buildEstimate ) . asSeconds ( ) ,
31
27
) } seconds remaining...`,
32
28
]
33
29
}
@@ -62,49 +58,55 @@ export const WorkspaceBuildProgress: FC<WorkspaceBuildProgressProps> = ({
62
58
} ) => {
63
59
const styles = useStyles ( )
64
60
const job = workspace . latest_build . job
65
- const [ progressValue , setProgressValue ] = useState ( 0 )
61
+ const [ progressValue , setProgressValue ] = useState < number | undefined > ( 0 )
66
62
67
63
// By default workspace is updated every second, which can cause visual stutter
68
64
// when the build estimate is a few seconds. The timer ensures no observable
69
65
// stutter in all cases.
70
66
useEffect ( ( ) => {
71
67
const updateProgress = ( ) => {
72
- if ( job . status !== "running" ) {
73
- setProgressValue ( 0 )
68
+ if ( job . status !== "running" || buildEstimate === undefined ) {
69
+ setProgressValue ( undefined )
74
70
return
75
71
}
76
- setProgressValue (
77
- estimateFinish ( dayjs ( job . started_at ) , buildEstimate ) [ 0 ] * 100 ,
78
- )
72
+ const est = estimateFinish ( dayjs ( job . started_at ) , buildEstimate ) [ 0 ]
73
+ setProgressValue ( est )
79
74
}
80
- setTimeout ( updateProgress , 100 )
75
+ setTimeout ( updateProgress , 5 )
81
76
} , [ progressValue , job , buildEstimate ] )
82
77
83
- // buildEstimate may be undefined if the template is new or coderd hasn't
84
- // finished initial metrics collection.
85
- if ( buildEstimate === undefined ) {
86
- return (
87
- < div className = { styles . stack } >
88
- < LinearProgress value = { 0 } variant = "indeterminate" />
89
- < div className = { styles . barHelpers } >
90
- < div className = { styles . label } > { `Build ${ job . status } ` } </ div >
91
- < div className = { styles . label } > Unknown ETA</ div >
92
- </ div >
93
- </ div >
94
- )
95
- }
96
-
97
78
return (
98
79
< div className = { styles . stack } >
99
80
< LinearProgress
100
- value = { ( job . status === "running" && progressValue ) || 0 }
101
- variant = { job . status === "running" ? "determinate" : "indeterminate" }
81
+ value = { progressValue !== undefined ? progressValue : 0 }
82
+ variant = {
83
+ // There is an initial state where progressValue may be undefined
84
+ // (e.g. the build isn't yet running). If we flicker from the
85
+ // indeterminate bar to the determinate bar, the vigilant user
86
+ // perceives the bar jumping from 100% to 0%.
87
+ progressValue !== undefined &&
88
+ progressValue < 100 &&
89
+ buildEstimate !== undefined
90
+ ? "determinate"
91
+ : "indeterminate"
92
+ }
93
+ // If a transition is set, there is a moment on new load where the
94
+ // bar accelerates to progressValue and then rapidly decelerates, which
95
+ // is not indicative of true progress.
96
+ classes = { { bar : styles . noTransition } }
102
97
/>
103
98
< div className = { styles . barHelpers } >
104
99
< div className = { styles . label } > { `Build ${ job . status } ` } </ div >
105
100
< div className = { styles . label } >
106
- { job . status === "running" &&
107
- estimateFinish ( dayjs ( job . started_at ) , buildEstimate ) [ 1 ] }
101
+ { ( ( ) => {
102
+ if ( job . status !== "running" ) {
103
+ return ""
104
+ } else if ( buildEstimate !== undefined ) {
105
+ return estimateFinish ( dayjs ( job . started_at ) , buildEstimate ) [ 1 ]
106
+ } else {
107
+ return "Unknown ETA"
108
+ }
109
+ } ) ( ) }
108
110
</ div >
109
111
</ div >
110
112
</ div >
@@ -116,6 +118,9 @@ const useStyles = makeStyles((theme) => ({
116
118
paddingLeft : theme . spacing ( 0.2 ) ,
117
119
paddingRight : theme . spacing ( 0.2 ) ,
118
120
} ,
121
+ noTransition : {
122
+ transition : "none" ,
123
+ } ,
119
124
barHelpers : {
120
125
display : "flex" ,
121
126
justifyContent : "space-between" ,
0 commit comments