Skip to content

Commit

Permalink
feat(common): Clean up JobMonitor display and functionality. Addresse…
Browse files Browse the repository at this point in the history
…s #85
  • Loading branch information
David Emory committed May 3, 2018
1 parent 2332198 commit d5bb692
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 103 deletions.
200 changes: 117 additions & 83 deletions lib/common/components/JobMonitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,18 @@ export default class JobMonitor extends Pure {
// }
}

componentDidUpdate () {
const jobList = this.refs.jobList
jobList.scrollTop = jobList.scrollHeight
}

// Remove any retired jobs from the monitor
removeAll = () => {
this.props.jobMonitor.retired.forEach(job => this.props.removeRetiredJob(job))
const { jobMonitor } = this.props
jobMonitor.retired.forEach(job => this.props.removeRetiredJob(job))

// If no active jobs left, then close the popover
if (jobMonitor.jobs.length === 0) this.props.close()
}

sortByDate = (a, b) => new Date(b.status.initialized) - new Date(a.status.initialized)
Expand All @@ -37,41 +47,59 @@ export default class JobMonitor extends Pure {
<SidebarPopover
ref={(SidebarPopover) => { this.popover = SidebarPopover }}
title='Server Jobs'
{...this.props}>
<ul className='list-unstyled job-list'>
{retired.sort(this.sortByDate).map(job => (
<RetiredJob
key={`retired-${job.jobId}`}
job={job}
removeRetiredJob={removeRetiredJob} />
))}
{jobs.sort(this.sortByDate).map(job => (
<li key={job.jobId} className='job-container'>
<div className='job-spinner-div'>
<Icon type='spinner' className='fa-pulse' />
</div>
<div className='job-container-inner'>
<div>
<strong>{job.name}</strong>
</div>
<ProgressBar
label={`${job.status ? job.status.percentComplete : 0}%`}
active now={job.status ? job.status.percentComplete : 0}
className='job-status-progress-bar' />
<div className='job-status-message' >
{job.status ? job.status.message : 'waiting'}
</div>
</div>
</li>
))}
</ul>
<p className='lead text-center'>{jobs.length ? jobs.length : 'No'} active jobs.</p>
<Button
block
disabled={retired.length === 0}
onClick={this.removeAll}>
<Icon type='times-circle' /> Clear completed
</Button>
fixedHeight={300}
minMarginBottom={75}
{...this.props}
>
<div className='job-monitor'>
{/* The main list of jobs */}
<div className='job-list' ref='jobList'> {/* TODO: replace w/ React 16 createRef() */}
<ul className='list-unstyled'>
{retired.sort(this.sortByDate).map(job => (
<RetiredJob
key={`retired-${job.jobId}`}
job={job}
removeRetiredJob={removeRetiredJob} />
))}
{jobs.sort(this.sortByDate).map(job => {
const pctComplete = Math.round(job.status ? job.status.percentComplete : 0)
return (
<li key={job.jobId} className='job-container'>
<div className='job-spinner-div'>
<Icon type='spinner' className='fa-pulse' />
</div>
<div className='job-container-inner'>
<div>
<strong>{job.name}</strong>
</div>
<ProgressBar active
style={{ width: 190 }}
label={`${pctComplete}%`}
now={pctComplete}
className='job-status-progress-bar' />
<div className='job-status-message' >
{job.status ? job.status.message : 'waiting'}
</div>
</div>
</li>
)
})}
</ul>
</div>
{/* Lower panel job count and clear-all button */}
<div style={{ marginTop: 8 }}>
<Button
style={{ float: 'right' }}
bsSize='small'
disabled={retired.length === 0}
onClick={this.removeAll}>
<Icon type='times-circle' /> Clear completed
</Button>
<div style={{ paddingTop: 6, fontSize: 13 }}>
{jobs.length ? jobs.length : 'No'} active job{!jobs.length || jobs.length > 1 ? 's' : ''}
</div>
</div>
</div>
</SidebarPopover>
)
}
Expand All @@ -98,61 +126,67 @@ class RetiredJob extends Pure {
}
</div>
<div className='job-container-inner'>
<div style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
width: '220px',
whiteSpace: 'nowrap'
}}>
<div style={{ float: 'right' }}>
<Button
className='close-job-button'
bsStyle='link'
className='pull-right'
style={{ padding: 'none' }}
onClick={this.removeJob}>
<Icon className='pull-right' type='times-circle' />
</Button>
<strong
title={job.name}>
{job.name}
</strong>
</div>
<div className='job-status-message'>
{job.status.message}
{job.status.exceptionDetails
? <OverlayTrigger
trigger='click'
placement='right'
overlay={
<Popover
id='job-exception-detail'
style={{
minWidth: '400px'
}}
title={
<span>
<Icon type='bug' /> Oh no! Looks like an error has occurred.
<div>
<div style={{
display: 'inline-block',
overflow: 'hidden',
textOverflow: 'ellipsis',
width: 170,
whiteSpace: 'nowrap'
}}>
<strong
title={job.name}>
{job.name}
</strong>
</div>
<div className='job-status-message'>
{job.status.message}
{job.status.exceptionDetails
? <OverlayTrigger
trigger='click'
placement='right'
overlay={
<Popover
id='job-exception-detail'
style={{
minWidth: '400px'
}}
title={
<span>
<Icon type='bug' /> Oh no! Looks like an error has occurred.
</span>
}>
<p>
To submit an error report email a screenshot of your browser
window, the following text (current URL and error details),
and a detailed description of the steps you followed
to <a href='mailto:support@conveyal.com'>support@conveyal.com</a>.
</p>
<p>{window.location.href}</p>
<span style={{whiteSpace: 'pre', fontSize: 'xx-small'}}>
{job.status.exceptionDetails}
</span>
</Popover>
}>
<p>
To submit an error report email a screenshot of your browser
window, the following text (current URL and error details),
and a detailed description of the steps you followed
to <a href='mailto:support@conveyal.com'>support@conveyal.com</a>.
</p>
<p>{window.location.href}</p>
<span style={{whiteSpace: 'pre', fontSize: 'xx-small'}}>
{job.status.exceptionDetails}
</span>
</Popover>
}>
<Button
bsSize='small'
style={{padding: '0px'}}
bsStyle='link'>
<Icon type='bug' />
</Button>
</OverlayTrigger>
: null
}
<Button
bsSize='small'
style={{padding: '0px'}}
bsStyle='link'>
<Icon type='bug' />
</Button>
</OverlayTrigger>
: null
}
</div>
</div>
</div>
</li>
Expand Down
32 changes: 25 additions & 7 deletions lib/common/components/SidebarPopover.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {Popover} from 'react-bootstrap'
export default class SidebarPopover extends Pure {
static propTypes = {
children: PropTypes.node,
expanded: PropTypes.bool,
target: PropTypes.object,
expanded: PropTypes.bool, // whether the sidebar is expanded, i.e. wider with labels and icons
target: PropTypes.object, // the on-screen element (e.g. an icon) that the popover is triggered by
title: PropTypes.string,
close: PropTypes.func,
visible: PropTypes.bool.isRequired
Expand All @@ -18,26 +18,41 @@ export default class SidebarPopover extends Pure {
top: 0,
arrowOffset: 0
}

_onResize = () => {
this.setState({width: window.innerWidth, height: window.innerHeight})
this.reposition()
}

componentWillMount () {
this._onResize()
}

componentDidMount () {
window.addEventListener('resize', this._onResize)
}

componentWillUnmount () {
window.removeEventListener('resize', this._onResize)
}

componentWillReceiveProps (nextProps) {
if (nextProps.visible) this.reposition()
}

reposition () {
const padding = 10 // minimum space between popover and top/bottom of screen
const minMarginBottom = this.props.minMarginBottom || 10

const height = ReactDOM.findDOMNode(this.refs.popover) ? ReactDOM.findDOMNode(this.refs.popover).offsetHeight : 0
// Get the height of the popover itself. Is either a fixed, property-defined
// value or is the default height (i.e. scaled to fit content )
const height = this.props.fixedHeight
? this.props.fixedHeight
: ReactDOM.findDOMNode(this.refs.popover)
? ReactDOM.findDOMNode(this.refs.popover).offsetHeight
: 0

// Get location and height of the popover's target (i.e. trigger) element
const target = ReactDOM.findDOMNode(this.props.target)
const targetTop = target ? target.getBoundingClientRect().top : 0
const targetHeight = target ? target.getBoundingClientRect().bottom - target.getBoundingClientRect().top : 0
Expand All @@ -50,9 +65,9 @@ export default class SidebarPopover extends Pure {
top = padding
}

const maxTop = window.innerHeight - padding - height
const maxTop = window.innerHeight - minMarginBottom - height
if (top > maxTop) {
arrowOffset = Math.min(window.innerHeight - padding - 20, arrowOffset + (top - maxTop))
arrowOffset = Math.min(window.innerHeight - minMarginBottom - 20, arrowOffset + (top - maxTop))
top = maxTop
}

Expand All @@ -64,14 +79,17 @@ export default class SidebarPopover extends Pure {
}

render () {
const { expanded, fixedHeight, visible } = this.props
const style = {
position: 'fixed',
marginLeft: this.props.expanded ? 160 : 60,
marginLeft: expanded ? 140 : 60,
width: 276, // max from bootstrap
top: this.state.top,
visibility: this.props.visible ? 'visible' : 'hidden'
visibility: visible ? 'visible' : 'hidden'
}

if (fixedHeight) style.height = fixedHeight

const title = (
<div>
<span>{this.props.title}</span>
Expand Down
7 changes: 4 additions & 3 deletions lib/common/components/StatusMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,23 @@ export default class StatusMessage extends React.Component {
}

render () {
const { message, sidebarExpanded } = this.props
const styles = {
position: 'fixed',
left: '15px',
left: sidebarExpanded ? '140px' : '60px',
bottom: '0px',
height: '60px',
zIndex: 1000
}

return (
<div style={styles}>
{this.props.message && this.state.visible
{message && this.state.visible
? <Button
bsStyle='info'
bsSize='large'
onClick={this.clear}>
{this.props.message}
{message}
</Button>
: null
}
Expand Down
3 changes: 2 additions & 1 deletion lib/common/containers/CurrentStatusMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import StatusMessage from '../components/StatusMessage'

const mapStateToProps = (state, ownProps) => {
return {
message: state.status.message
message: state.status.message,
sidebarExpanded: state.ui.sidebarExpanded
}
}

Expand Down
Loading

0 comments on commit d5bb692

Please sign in to comment.