Skip to content

Commit

Permalink
Merge pull request #122 from nrotstan/issue-85-busy-indicator-when-ta…
Browse files Browse the repository at this point in the history
…sks-building

Indicate in admin if tasks building. Closes #85
  • Loading branch information
nrotstan authored Feb 20, 2018
2 parents 0d91e5e + bacc09a commit a2d0ea7
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ const WithCurrentChallenge = function(WrappedComponent,
currentChallengeId = () =>
parseInt(_get(this.props, 'match.params.challengeId'), 10)

componentDidMount() {
loadChallenge = () => {
const challengeId = this.currentChallengeId()

if (!isNaN(challengeId)) {
this.setState({loadingChallenge: true})
const timelineStartDate = subMonths(new Date(), historicalMonths)

Promise.all([
Expand All @@ -57,6 +58,10 @@ const WithCurrentChallenge = function(WrappedComponent,
}
}

componentDidMount() {
this.loadChallenge()
}

render() {
const challengeId = this.currentChallengeId()
let challenge = null
Expand All @@ -79,6 +84,7 @@ const WithCurrentChallenge = function(WrappedComponent,
clusteredTasks={clusteredTasks}
loadingChallenge={this.state.loadingChallenge}
loadingTasks={this.state.loadingTasks}
refreshStatus={this.loadChallenge}
{..._omit(this.props, ['entities',
'fetchChallenge',
'fetchChallengeComments',
Expand Down
20 changes: 20 additions & 0 deletions src/components/AdminPane/Manage/ViewChallenge/Messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,26 @@ export default defineMessages({
defaultMessage: "Metrics",
},

asOf: {
id: "Admin.Challenge.status.asOf.label",
defaultMessage: "as of",
},

tasksBuilding: {
id: "Admin.Challenge.tasksBuilding",
defaultMessage: "Tasks Building...",
},

tasksFailed: {
id: "Admin.Challenge.tasksFailed",
defaultMessage: "Tasks Failed to Build",
},

refreshStatusLabel: {
id: "Admin.Challenge.controls.refreshStatus.label",
defaultMessage: "Refresh Status",
},

tasksHeader: {
id: "Admin.Tasks.header",
defaultMessage: "Tasks",
Expand Down
78 changes: 2 additions & 76 deletions src/components/AdminPane/Manage/ViewChallenge/ViewChallenge.js
Original file line number Diff line number Diff line change
@@ -1,57 +1,32 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import { FormattedMessage, injectIntl } from 'react-intl'
import _get from 'lodash/get'
import _map from 'lodash/map'
import { Link } from 'react-router-dom'
import { TaskStatus,
keysByStatus,
messagesByStatus } from '../../../../services/Task/TaskStatus/TaskStatus'
import { MAPBOX_LIGHT,
layerSourceWithName }
from '../../../../services/VisibleLayer/LayerSources'
import WithCurrentChallenge
from '../../HOCs/WithCurrentChallenge/WithCurrentChallenge'
import WithFilteredClusteredTasks
from '../../HOCs/WithFilteredClusteredTasks/WithFilteredClusteredTasks'
import WithBoundedTasks
from '../../HOCs/WithBoundedTasks/WithBoundedTasks'
import Sidebar from '../../../Sidebar/Sidebar'
import CommentList from '../../../CommentList/CommentList'
import MapPane from '../../../EnhancedMap/MapPane/MapPane'
import ChallengeTaskMap from '../ChallengeTaskMap/ChallengeTaskMap'
import TaskAnalysisTable from '../TaskAnalysisTable/TaskAnalysisTable'
import ChallengeOverview from '../ManageChallenges/ChallengeOverview'
import BusySpinner from '../../../BusySpinner/BusySpinner'
import SvgSymbol from '../../../SvgSymbol/SvgSymbol'
import Tabs from '../../../Bulma/Tabs'
import ChallengeMetrics from '../ChallengeMetrics/ChallengeMetrics'
import ConfirmAction from '../../../ConfirmAction/ConfirmAction'
import ViewChallengeTasks from './ViewChallengeTasks'
import manageMessages from '../Messages'
import messages from './Messages'
import './ViewChallenge.css'

const BoundedTaskTable =
WithBoundedTasks(TaskAnalysisTable, 'filteredClusteredTasks', 'taskInfo')

/**
* ViewChallenge displays various challenge details and metrics of interest
* to challenge owners, along with a list of the challenge tasks.
*
* @author [Neil Rotstan](https://github.com/nrotstan)
*/
export class ViewChallenge extends Component {
state = {
mapBounds: null,
mapZoom: null,
renderingProgress: null,
}

/** Invoked by the map when the user pans or zooms */
updateMapBounds = (challengeId, bounds, zoom) =>
this.setState({mapBounds: bounds, mapZoom: zoom})

deleteChallenge = () => {
this.props.deleteChallenge(this.props.challenge.parent.id,
this.props.challenge.id)
Expand All @@ -74,36 +49,6 @@ export class ViewChallenge extends Component {
<ChallengeMetrics challenges={[this.props.challenge]} />,
}

// Use CSS Modules once supported by create-react-app
const statusColors = {
[TaskStatus.created]: '#0082C8', // $status-created-color
[TaskStatus.fixed]: '#3CB44B', // $status-fixed-color
[TaskStatus.falsePositive]: '#F58231', // $status-falsePositive-color
[TaskStatus.skipped]: '#FFE119', // $status-skipped-color
[TaskStatus.deleted]: '#46F0F0', // $status-deleted-color
[TaskStatus.alreadyFixed]: '#911EB4', // $status-alreadyFixed-color
[TaskStatus.tooHard]: '#E6194B', // $status-tooHard-color
}

const statusFilters = _map(TaskStatus, status => (
<div key={status} className="status-filter is-narrow">
<div className={classNames("field", keysByStatus[status])}
onClick={() => this.props.toggleIncludedStatus(status)}>
<input className="is-checkradio is-circle has-background-color is-success" type="checkbox"
checked={this.props.includeStatuses[status]}
onChange={() => null} />
<label>
<FormattedMessage {...messagesByStatus[status]} />
</label>
</div>
</div>
))

const filterOptions = {
includeStatuses: this.props.includeStatuses,
withinBounds: this.state.mapBounds,
}

return (
<div className="admin__manage view-challenge">
<div className="admin__manage__header">
Expand Down Expand Up @@ -153,26 +98,7 @@ export class ViewChallenge extends Component {
</Sidebar>

<div className="admin__manage__primary-content">
<div className='admin__manage-tasks'>
<div className="status-filter-options">
{statusFilters}
</div>

<MapPane>
<ChallengeTaskMap taskInfo={this.props.filteredClusteredTasks}
setChallengeMapBounds={this.updateMapBounds}
lastBounds={this.state.mapBounds}
lastZoom={this.state.mapZoom}
statusColors={statusColors}
filterOptions={filterOptions}
monochromaticClusters
defaultLayer={layerSourceWithName(MAPBOX_LIGHT)}
{...this.props} />
</MapPane>
<BoundedTaskTable filterOptions={filterOptions}
totalTaskCount={_get(this.props, 'clusteredTasks.tasks.length')}
{...this.props} />
</div>
<ViewChallengeTasks {...this.props} />
</div>
</div>
</div>
Expand Down
19 changes: 19 additions & 0 deletions src/components/AdminPane/Manage/ViewChallenge/ViewChallenge.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
.admin__manage.view-challenge {
.admin__manage__primary-content {
padding-top: 0;

.challenge-tasks-status {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;

h3 {
font-size: $size-3;

&.is-danger {
color: $coral;
}
}

.refresh-control {
margin-top: 50px;
}
}
}

section {
Expand Down
123 changes: 123 additions & 0 deletions src/components/AdminPane/Manage/ViewChallenge/ViewChallengeTasks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import React, { Component } from 'react'
import classNames from 'classnames'
import { FormattedMessage,
FormattedRelative } from 'react-intl'
import _get from 'lodash/get'
import _map from 'lodash/map'
import { ChallengeStatus }
from '../../../../services/Challenge/ChallengeStatus/ChallengeStatus'
import { TaskStatus,
keysByStatus,
messagesByStatus } from '../../../../services/Task/TaskStatus/TaskStatus'
import { MAPBOX_LIGHT,
layerSourceWithName }
from '../../../../services/VisibleLayer/LayerSources'
import WithBoundedTasks
from '../../HOCs/WithBoundedTasks/WithBoundedTasks'
import MapPane from '../../../EnhancedMap/MapPane/MapPane'
import ChallengeTaskMap from '../ChallengeTaskMap/ChallengeTaskMap'
import TaskAnalysisTable from '../TaskAnalysisTable/TaskAnalysisTable'
import SvgSymbol from '../../../SvgSymbol/SvgSymbol'
import messages from './Messages'

const BoundedTaskTable =
WithBoundedTasks(TaskAnalysisTable, 'filteredClusteredTasks', 'taskInfo')

export default class ViewChallengeTasks extends Component {
state = {
mapBounds: null,
mapZoom: null,
renderingProgress: null,
}

/** Invoked by the map when the user pans or zooms */
updateMapBounds = (challengeId, bounds, zoom) =>
this.setState({mapBounds: bounds, mapZoom: zoom})

render() {
if (this.props.challenge.status === ChallengeStatus.building) {
return (
<div>
<div className="challenge-tasks-status">
<h3><FormattedMessage {...messages.tasksBuilding} /></h3>

<div className="since-when">
<FormattedMessage {...messages.asOf} /> <FormattedRelative value={new Date(this.props.challenge._meta.fetchedAt)} />
</div>

<button className={classNames("button is-primary is-outlined has-svg-icon refresh-control",
{"is-loading": this.props.loadingChallenge})}
onClick={this.props.refreshStatus}>
<SvgSymbol viewBox='0 0 20 20' sym="refresh-icon" />
<FormattedMessage {...messages.refreshStatusLabel} />
</button>
</div>
</div>
)
}

if (this.props.challenge.status === ChallengeStatus.failed) {
return (
<div className="challenge-tasks-status title has-centered-children">
<h3 className="is-danger">
<FormattedMessage {...messages.tasksFailed} />
</h3>
</div>
)
}

// Use CSS Modules once supported by create-react-app
const statusColors = {
[TaskStatus.created]: '#0082C8', // $status-created-color
[TaskStatus.fixed]: '#3CB44B', // $status-fixed-color
[TaskStatus.falsePositive]: '#F58231', // $status-falsePositive-color
[TaskStatus.skipped]: '#FFE119', // $status-skipped-color
[TaskStatus.deleted]: '#46F0F0', // $status-deleted-color
[TaskStatus.alreadyFixed]: '#911EB4', // $status-alreadyFixed-color
[TaskStatus.tooHard]: '#E6194B', // $status-tooHard-color
}

const statusFilters = _map(TaskStatus, status => (
<div key={status} className="status-filter is-narrow">
<div className={classNames("field", keysByStatus[status])}
onClick={() => this.props.toggleIncludedStatus(status)}>
<input className="is-checkradio is-circle has-background-color is-success"
type="checkbox"
checked={this.props.includeStatuses[status]}
onChange={() => null} />
<label>
<FormattedMessage {...messagesByStatus[status]} />
</label>
</div>
</div>
))

const filterOptions = {
includeStatuses: this.props.includeStatuses,
withinBounds: this.state.mapBounds,
}

return (
<div className='admin__manage-tasks'>
<div className="status-filter-options">
{statusFilters}
</div>

<MapPane>
<ChallengeTaskMap taskInfo={this.props.filteredClusteredTasks}
setChallengeMapBounds={this.updateMapBounds}
lastBounds={this.state.mapBounds}
lastZoom={this.state.mapZoom}
statusColors={statusColors}
filterOptions={filterOptions}
monochromaticClusters
defaultLayer={layerSourceWithName(MAPBOX_LIGHT)}
{...this.props} />
</MapPane>
<BoundedTaskTable filterOptions={filterOptions}
totalTaskCount={_get(this.props, 'clusteredTasks.tasks.length')}
{...this.props} />
</div>
)
}
}
4 changes: 3 additions & 1 deletion src/components/EnhancedMap/MapPane/MapPane.scss
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,16 @@
border-radius: $radius-medium;

.leaflet-popup-content {
width: 300px;
width: 350px;
overflow-y: auto;
margin: 0;

.feature-properties {
color: $grey;
font-size: $size-7;
margin: 19px 0px;
max-height: 70vh;
overflow-y: auto;

h3 {
color: $primary;
Expand Down
3 changes: 3 additions & 0 deletions src/components/Sprites/Sprites.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/components/TaskPane/TaskMap/TaskMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { MIN_ZOOM,
MAX_ZOOM,
DEFAULT_ZOOM }
from '../../../services/Challenge/ChallengeZoom/ChallengeZoom'
import BusySpinner from '../../BusySpinner/BusySpinner'
import './TaskMap.css'

const VisibleLayerToggle = WithVisibleLayer(WithLayerSources(LayerToggle))
Expand Down Expand Up @@ -53,7 +54,7 @@ export default class TaskMap extends Component {

render() {
if (!this.props.task || !_isObject(this.props.task.parent)) {
return null
return <BusySpinner />
}

const zoom = _get(this.props.task, "parent.defaultZoom", DEFAULT_ZOOM)
Expand Down
Loading

0 comments on commit a2d0ea7

Please sign in to comment.