Skip to content

Commit

Permalink
Merge pull request #850 from osmlab/prerelease
Browse files Browse the repository at this point in the history
Prepare for v3.3.4 release
  • Loading branch information
nrotstan authored Aug 8, 2019
2 parents 777ad08 + 6ed205a commit 67e3405
Show file tree
Hide file tree
Showing 20 changed files with 347 additions and 27 deletions.
6 changes: 6 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ REACT_APP_ATTIC_QUERY_OFFSET_MINUTES = 10
REACT_APP_MATOMO_URL=''
REACT_APP_MATOMO_SITE_ID=''

# Optional URL to a JSON file containing system notices to be displayed to
# users, such as notice of upcoming maintenance. See:
# https://github.com/osmlab/maproulette3/wiki/Server-Admin:-System-Notices
# for details
REACT_APP_SYSTEM_NOTICES_URL=''

# Custom keyword categories configuration
# Additional categories of one or more keywords can be added into the Work On
# dropdown filter. Custom categories must be formatted as a valid JSON string.
Expand Down
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,17 @@ The format is based on
This project adheres to
[Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [v3.3.3] - 2019-07-24
## [v3.3.4] - 2019-08-08
### Added
- Support for system notices (e.g. notices of upcoming maintenance)
- Color-coded usernames in various tables
- Bulk task-status change for challenge managers

### Fixed
- Malfunctioning review-table date picker in Safari


## [v3.3.3] - 2019-07-25
### Added
- MapRoulette-specific tagging support for tasks ("MR Tags")
- Option to select next nearby task to work on from map of nearby tasks
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "maproulette3",
"version": "3.3.3",
"version": "3.3.4",
"private": true,
"dependencies": {
"@mapbox/geojsonhint": "^2.0.1",
Expand Down
2 changes: 2 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import LoadRandomChallengeTask
import LoadRandomVirtualChallengeTask
from './components/LoadRandomVirtualChallengeTask/LoadRandomVirtualChallengeTask'
import Navbar from './components/Navbar/Navbar'
import SystemNotices from './components/SystemNotices/SystemNotices'
import Footer from './components/Footer/Footer'
import ErrorModal from './components/ErrorModal/ErrorModal'
import Sprites from './components/Sprites/Sprites'
Expand Down Expand Up @@ -76,6 +77,7 @@ export class App extends Component {
return (
<React.Fragment>
<TopNav />
<SystemNotices />

<main role="main" className="mr-bg-white mr-text-grey">
<Switch>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,10 @@
}
}
}

.ChallengeTasksWidget {
.modal.is-active {
display: block;
padding-top: 500px;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { messagesByReviewStatus,
from '../../../../services/Task/TaskReview/TaskReviewStatus'
import { messagesByPriority }
from '../../../../services/Task/TaskPriority/TaskPriority'
import { mapColors } from '../../../../interactions/User/AsEndUser'
import AsManager from '../../../../interactions/User/AsManager'
import WithLoadedTask from '../../HOCs/WithLoadedTask/WithLoadedTask'
import ViewTask from '../ViewTask/ViewTask'
Expand Down Expand Up @@ -206,7 +207,8 @@ const setupColumnTypes = (props, taskBaseRoute, manager, data, openComments) =>
exportable: t => _get(t.reviewRequestedBy, 'username') || t.reviewRequestedBy,
maxWidth: 180,
Cell: ({row}) =>
<div className="row-user-column">
<div className={classNames("row-user-column",
mapColors(_get(row._original.reviewRequestedBy, 'username') || row._original.reviewRequestedBy))}>
{_get(row._original.reviewRequestedBy, 'username') || row._original.reviewRequestedBy }
</div>
}
Expand Down Expand Up @@ -238,7 +240,8 @@ const setupColumnTypes = (props, taskBaseRoute, manager, data, openComments) =>
maxWidth: 180,
Cell: ({row}) => (
!row._original.reviewedBy ? null :
<div className="row-user-column">
<div className={classNames("row-user-column",
mapColors(row._original.reviewedBy.username || row._original.reviewedBy))}>
{row._original.reviewedBy.username || row._original.reviewedBy}
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { FormattedMessage, injectIntl } from 'react-intl'
import classNames from 'classnames'
import Modal from '../../../Bulma/Modal'
import messages from '../ViewChallengeTasks/Messages'
import _noop from 'lodash/noop'
import _get from 'lodash/get'
import _map from 'lodash/map'
import _join from 'lodash/join'
import _each from 'lodash/each'
import _omit from 'lodash/omit'
import AsManager from '../../../../interactions/User/AsManager'
import Dropdown from '../../../Dropdown/Dropdown'
import SvgSymbol from '../../../SvgSymbol/SvgSymbol'
import TriStateCheckbox from '../../../Bulma/TriStateCheckbox'
import ConfirmAction from '../../../ConfirmAction/ConfirmAction'
import confirmMessages from '../../../ConfirmAction/Messages'
import DropdownButton from '../../../Bulma/DropdownButton'
import WithDeactivateOnOutsideClick from '../../../HOCs/WithDeactivateOnOutsideClick/WithDeactivateOnOutsideClick'
import { TaskStatus, statusLabels, keysByStatus } from '../../../../services/Task/TaskStatus/TaskStatus'
Expand All @@ -27,6 +29,7 @@ const DeactivatableDropdownButton = WithDeactivateOnOutsideClick(DropdownButton)
* @author [Ryan Scherler](https://github.com/ryanscherler)
*/
export class TaskAnalysisTableHeader extends Component {
state = {}

takeTaskSelectionAction = action => {
if (action.statusAction) {
Expand Down Expand Up @@ -86,6 +89,38 @@ export class TaskAnalysisTableHeader extends Component {
}))
)


const confirmModal =
<div className="confirm-action">
<Modal className="confirm-action__modal" onClose={() => this.setState({showConfirm: false})} isActive={true}>
<article className="message">
<div className="message-header mr-bg-blue-dark">
<FormattedMessage {...confirmMessages.title} />
</div>
<div className="message-body">
<div className="confirm-action__prompt mr-text-blue-dark">
<FormattedMessage {...confirmMessages.prompt} />
</div>

<div className="confirm-action__controls">
<button className="mr-button mr-button--blue"
onClick={() => this.setState({showConfirm: false})}>
<FormattedMessage {...confirmMessages.cancel} />
</button>

<button className="mr-button mr-button--danger mr-ml-4"
onClick={() => {
this.props.changeStatus(this.state.statusChange)
this.setState({showConfirm: false})
}}>
<FormattedMessage {...confirmMessages.proceed} />
</button>
</div>
</div>
</article>
</Modal>
</div>

return (
<div className="mr-flex mr-justify-between">
<div className="mr-flex mr-items-center">
Expand Down Expand Up @@ -133,14 +168,29 @@ export class TaskAnalysisTableHeader extends Component {
<ul className="mr-list-dropdown">
{manager.canWriteProject(this.props.challenge.parent) &&
<li>
<ConfirmAction>
<button className={classNames("mr-text-current",
(!this.props.someTasksAreSelected() && !this.props.allTasksAreSelected()) ? "mr-text-grey mr-cursor-default" : "")}
disabled={!this.props.someTasksAreSelected() && !this.props.allTasksAreSelected()}
onClick={this.props.markAsCreated}>
<FormattedMessage {...messages.markCreatedLabel} />
</button>
</ConfirmAction>
{this.state.showConfirm && confirmModal}
<div>
<button className={classNames("mr-text-current mr-pr-1",
(!this.props.someTasksAreSelected() && !this.props.allTasksAreSelected()) ? "mr-text-grey mr-cursor-default" : "")}
disabled={!this.props.someTasksAreSelected() && !this.props.allTasksAreSelected()}
onClick={() => this.setState({showConfirm: true})}>
<FormattedMessage {...messages.changeStatusToLabel} />
</button>
{(!this.props.someTasksAreSelected() && !this.props.allTasksAreSelected()) &&
<span className="mr-text-current mr-text-grey">...</span>}
{(this.props.someTasksAreSelected() || this.props.allTasksAreSelected()) &&
<select onChange={e => {this.setState({statusChange: e.target.value})
this.setState({showConfirm: true})}}
defaultValue={this.state.statusChange}
className="select mr-min-w-20 mr-bg-grey-lighter mr-rounded mr-px-1 mr-text-xs mr-pl-2">
{_map(_omit(TaskStatus, "deleted"), (value, key) =>
<option key={key} value={value}>
{localizedStatusLabels[key]}
</option>
)}
</select>
}
</div>
</li>
}
<li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ export default defineMessages({
defaultMessage: "Select tasks for bulk operation",
},

markCreatedLabel: {
id: "Admin.manageTasks.controls.markCreated.label",
defaultMessage: "Reset status to Created",
changeStatusToLabel: {
id: "Admin.manageTasks.controls.changeStatusTo.label",
defaultMessage: "Change status to ",
},

changePriorityLabel: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@ export class ViewChallengeTasks extends Component {
}
}

markAsCreated = () => {
changeStatus = (newStatus = TaskStatus.created) => {
const tasks = [...this.props.selectedTasks.values()]
if (tasks.length === 0) {
return
}

this.setState({bulkUpdating: true}) // will be reset by componentDidUpdate
this.props.applyBulkTaskChanges(
tasks, {status: TaskStatus.created,
tasks, {status: parseInt(newStatus),
mappedOn: null,
reviewStatus: null,
reviewRequestedBy: null,
Expand Down Expand Up @@ -214,7 +214,7 @@ export class ViewChallengeTasks extends Component {
</div>

<TaskAnalysisTable filterOptions={filterOptions}
markAsCreated={this.markAsCreated}
changeStatus={this.changeStatus}
totalTaskCount={_get(this.props, 'clusteredTasks.tasks.length')}
{...this.props} />
</div>
Expand Down
94 changes: 94 additions & 0 deletions src/components/HOCs/WithSystemNotices/WithSystemNotices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { fetchActiveSystemNotices }
from '../../../services/SystemNotices/SystemNotices'

const ACKNOWLEDGED_SETTING = 'acknowledgedNotices'

/**
* WithSystemNotices provides the WrappedComponent with an array of active,
* unacknowledged system notices along with methods for acknowledging them
*
* @author [Neil Rotstan](https://github.com/nrotstan)
*/
export const WithSystemNotices = function(WrappedComponent) {
class _WithSystemNotices extends Component {
state = {
systemNotices: null,
unacknowledgedNotices: [],
}

async componentDidMount() {
if (!this.state.systemNotices) {
const activeNotices = await fetchActiveSystemNotices()

this.setState({systemNotices: activeNotices})
}
}

/**
* Retrieves all acknowledged notices from the user's app settings
*
* @private
*/
allAcknowledgedNotices = () => {
if (!this.props.user) {
return []
}

return this.props.getUserAppSetting(this.props.user, ACKNOWLEDGED_SETTING) || []
}

/**
* Retrieves array of notices that have not yet been acknowledged by the
* user
*
* @private
*/
unacknowledgedNotices = () => {
if (!this.state.systemNotices) {
return []
}

const acknowledged = this.allAcknowledgedNotices()
return this.state.systemNotices.filter(
notice => acknowledged.indexOf(notice.uuid) === -1
)
}

/**
* Acknowledges the given notice
*/
acknowledgeNotice = async (notice) => {
if (!this.props.user || !notice.uuid) {
return
}

const updatedAcknowledgements = this.allAcknowledgedNotices().slice()
updatedAcknowledgements.push(notice.uuid)
await this.props.updateUserAppSetting(this.props.user.id, {
[ACKNOWLEDGED_SETTING]: updatedAcknowledgements,
})
}

render() {
const remainingNotices = this.unacknowledgedNotices(this.state.systemNotices)
return <WrappedComponent
{...this.props}
allSystemNotices={this.state.systemNotices}
newSystemNotices={remainingNotices}
acknowledgeNotice={notice => this.acknowledgeNotice(notice)}
/>
}
}

_WithSystemNotices.propTypes = {
user: PropTypes.object,
getUserAppSetting: PropTypes.func.isRequired,
updateUserAppSetting: PropTypes.func.isRequired,
}

return _WithSystemNotices
}

export default WrappedComponent => WithSystemNotices(WrappedComponent)
4 changes: 2 additions & 2 deletions src/components/IntlDatePicker/IntlDatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ export default class IntlDatePicker extends Component {
// This is a funky method to extract the date format to use for the date picker
// since react intl will not give us the date format directly
extractDateFormat() {
const isoString = '2018-12-31 12:00:00' // example date
const isoString = '2018-12-31T12:00:00.000Z' // example date

const intlString = this.props.intl.formatDate(isoString) // generate a formatted date
const dateParts = isoString.split(' ')[0].split('-') // prepare to replace with pattern parts
const dateParts = isoString.split('T')[0].split('-') // prepare to replace with pattern parts

return intlString
.replace(dateParts[2], 'dd')
Expand Down
4 changes: 3 additions & 1 deletion src/components/Sprites/Sprites.js

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

Loading

0 comments on commit 67e3405

Please sign in to comment.