@@ -24,8 +24,18 @@ export default class JobMonitor extends Pure {
24
24
// }
25
25
}
26
26
27
+ componentDidUpdate ( ) {
28
+ const jobList = this . refs . jobList
29
+ jobList . scrollTop = jobList . scrollHeight
30
+ }
31
+
32
+ // Remove any retired jobs from the monitor
27
33
removeAll = ( ) => {
28
- this . props . jobMonitor . retired . forEach ( job => this . props . removeRetiredJob ( job ) )
34
+ const { jobMonitor } = this . props
35
+ jobMonitor . retired . forEach ( job => this . props . removeRetiredJob ( job ) )
36
+
37
+ // If no active jobs left, then close the popover
38
+ if ( jobMonitor . jobs . length === 0 ) this . props . close ( )
29
39
}
30
40
31
41
sortByDate = ( a , b ) => new Date ( b . status . initialized ) - new Date ( a . status . initialized )
@@ -37,41 +47,59 @@ export default class JobMonitor extends Pure {
37
47
< SidebarPopover
38
48
ref = { ( SidebarPopover ) => { this . popover = SidebarPopover } }
39
49
title = 'Server Jobs'
40
- { ...this . props } >
41
- < ul className = 'list-unstyled job-list' >
42
- { retired . sort ( this . sortByDate ) . map ( job => (
43
- < RetiredJob
44
- key = { `retired-${ job . jobId } ` }
45
- job = { job }
46
- removeRetiredJob = { removeRetiredJob } />
47
- ) ) }
48
- { jobs . sort ( this . sortByDate ) . map ( job => (
49
- < li key = { job . jobId } className = 'job-container' >
50
- < div className = 'job-spinner-div' >
51
- < Icon type = 'spinner' className = 'fa-pulse' />
52
- </ div >
53
- < div className = 'job-container-inner' >
54
- < div >
55
- < strong > { job . name } </ strong >
56
- </ div >
57
- < ProgressBar
58
- label = { `${ job . status ? job . status . percentComplete : 0 } %` }
59
- active now = { job . status ? job . status . percentComplete : 0 }
60
- className = 'job-status-progress-bar' />
61
- < div className = 'job-status-message' >
62
- { job . status ? job . status . message : 'waiting' }
63
- </ div >
64
- </ div >
65
- </ li >
66
- ) ) }
67
- </ ul >
68
- < p className = 'lead text-center' > { jobs . length ? jobs . length : 'No' } active jobs.</ p >
69
- < Button
70
- block
71
- disabled = { retired . length === 0 }
72
- onClick = { this . removeAll } >
73
- < Icon type = 'times-circle' /> Clear completed
74
- </ Button >
50
+ fixedHeight = { 300 }
51
+ minMarginBottom = { 75 }
52
+ { ...this . props }
53
+ >
54
+ < div className = 'job-monitor' >
55
+ { /* The main list of jobs */ }
56
+ < div className = 'job-list' ref = 'jobList' > { /* TODO: replace w/ React 16 createRef() */ }
57
+ < ul className = 'list-unstyled' >
58
+ { retired . sort ( this . sortByDate ) . map ( job => (
59
+ < RetiredJob
60
+ key = { `retired-${ job . jobId } ` }
61
+ job = { job }
62
+ removeRetiredJob = { removeRetiredJob } />
63
+ ) ) }
64
+ { jobs . sort ( this . sortByDate ) . map ( job => {
65
+ const pctComplete = Math . round ( job . status ? job . status . percentComplete : 0 )
66
+ return (
67
+ < li key = { job . jobId } className = 'job-container' >
68
+ < div className = 'job-spinner-div' >
69
+ < Icon type = 'spinner' className = 'fa-pulse' />
70
+ </ div >
71
+ < div className = 'job-container-inner' >
72
+ < div >
73
+ < strong > { job . name } </ strong >
74
+ </ div >
75
+ < ProgressBar active
76
+ style = { { width : 190 } }
77
+ label = { `${ pctComplete } %` }
78
+ now = { pctComplete }
79
+ className = 'job-status-progress-bar' />
80
+ < div className = 'job-status-message' >
81
+ { job . status ? job . status . message : 'waiting' }
82
+ </ div >
83
+ </ div >
84
+ </ li >
85
+ )
86
+ } ) }
87
+ </ ul >
88
+ </ div >
89
+ { /* Lower panel job count and clear-all button */ }
90
+ < div style = { { marginTop : 8 } } >
91
+ < Button
92
+ style = { { float : 'right' } }
93
+ bsSize = 'small'
94
+ disabled = { retired . length === 0 }
95
+ onClick = { this . removeAll } >
96
+ < Icon type = 'times-circle' /> Clear completed
97
+ </ Button >
98
+ < div style = { { paddingTop : 6 , fontSize : 13 } } >
99
+ { jobs . length ? jobs . length : 'No' } active job{ ! jobs . length || jobs . length > 1 ? 's' : '' }
100
+ </ div >
101
+ </ div >
102
+ </ div >
75
103
</ SidebarPopover >
76
104
)
77
105
}
@@ -98,61 +126,67 @@ class RetiredJob extends Pure {
98
126
}
99
127
</ div >
100
128
< div className = 'job-container-inner' >
101
- < div style = { {
102
- overflow : 'hidden' ,
103
- textOverflow : 'ellipsis' ,
104
- width : '220px' ,
105
- whiteSpace : 'nowrap'
106
- } } >
129
+ < div style = { { float : 'right' } } >
107
130
< Button
131
+ className = 'close-job-button'
108
132
bsStyle = 'link'
109
- className = 'pull-right'
133
+ style = { { padding : 'none' } }
110
134
onClick = { this . removeJob } >
111
135
< Icon className = 'pull-right' type = 'times-circle' />
112
136
</ Button >
113
- < strong
114
- title = { job . name } >
115
- { job . name }
116
- </ strong >
117
137
</ div >
118
- < div className = 'job-status-message' >
119
- { job . status . message }
120
- { job . status . exceptionDetails
121
- ? < OverlayTrigger
122
- trigger = 'click'
123
- placement = 'right'
124
- overlay = {
125
- < Popover
126
- id = 'job-exception-detail'
127
- style = { {
128
- minWidth : '400px'
129
- } }
130
- title = {
131
- < span >
132
- < Icon type = 'bug' /> Oh no! Looks like an error has occurred.
138
+ < div >
139
+ < div style = { {
140
+ display : 'inline-block' ,
141
+ overflow : 'hidden' ,
142
+ textOverflow : 'ellipsis' ,
143
+ width : 170 ,
144
+ whiteSpace : 'nowrap'
145
+ } } >
146
+ < strong
147
+ title = { job . name } >
148
+ { job . name }
149
+ </ strong >
150
+ </ div >
151
+ < div className = 'job-status-message' >
152
+ { job . status . message }
153
+ { job . status . exceptionDetails
154
+ ? < OverlayTrigger
155
+ trigger = 'click'
156
+ placement = 'right'
157
+ overlay = {
158
+ < Popover
159
+ id = 'job-exception-detail'
160
+ style = { {
161
+ minWidth : '400px'
162
+ } }
163
+ title = {
164
+ < span >
165
+ < Icon type = 'bug' /> Oh no! Looks like an error has occurred.
166
+ </ span >
167
+ } >
168
+ < p >
169
+ To submit an error report email a screenshot of your browser
170
+ window, the following text (current URL and error details),
171
+ and a detailed description of the steps you followed
172
+ to < a href = 'mailto:support@conveyal.com' > support@conveyal.com</ a > .
173
+ </ p >
174
+ < p > { window . location . href } </ p >
175
+ < span style = { { whiteSpace : 'pre' , fontSize : 'xx-small' } } >
176
+ { job . status . exceptionDetails }
133
177
</ span >
178
+ </ Popover >
134
179
} >
135
- < p >
136
- To submit an error report email a screenshot of your browser
137
- window, the following text (current URL and error details),
138
- and a detailed description of the steps you followed
139
- to < a href = 'mailto:support@conveyal.com' > support@conveyal.com</ a > .
140
- </ p >
141
- < p > { window . location . href } </ p >
142
- < span style = { { whiteSpace : 'pre' , fontSize : 'xx-small' } } >
143
- { job . status . exceptionDetails }
144
- </ span >
145
- </ Popover >
146
- } >
147
- < Button
148
- bsSize = 'small'
149
- style = { { padding : '0px' } }
150
- bsStyle = 'link' >
151
- < Icon type = 'bug' />
152
- </ Button >
153
- </ OverlayTrigger >
154
- : null
155
- }
180
+ < Button
181
+ bsSize = 'small'
182
+ style = { { padding : '0px' } }
183
+ bsStyle = 'link' >
184
+ < Icon type = 'bug' />
185
+ </ Button >
186
+ </ OverlayTrigger >
187
+ : null
188
+ }
189
+ </ div >
156
190
</ div >
157
191
</ div >
158
192
</ li >
0 commit comments