+
{IncludeMap}
-
+
{IncludeMap === null ? null : checkBoxes}
{this.props.reviewTasksType === ReviewTasksType.toBeReviewed && data.length > 0 && (
-
this.startReviewing()}>
+ this.startReviewing()}
+ >
)}
- {this.props.reviewTasksType === ReviewTasksType.metaReviewTasks && data.length > 0 && (
- this.startMetaReviewing()}>
-
-
- )}
- 0 && (
+ this.startMetaReviewing()}
+ >
+
+
+ )}
+ {
const newDisplayMap = !this.state.displayMap;
- localStorage.setItem('displayMap', JSON.stringify(newDisplayMap));
- this.setState({ displayMap: newDisplayMap })
+ localStorage.setItem("displayMap", JSON.stringify(newDisplayMap));
+ this.setState({ displayMap: newDisplayMap });
}}
>
@@ -527,7 +672,7 @@ export class TaskReviewTable extends Component {
this.props.refresh()}
>
@@ -559,11 +704,11 @@ export class TaskReviewTable extends Component {
noDataText={ }
pages={totalPages}
onFetchData={(state, instance) => this.debouncedUpdateTasks(state, instance)}
- onPageSizeChange={pageSize => this.props.changePageSize(pageSize)}
+ onPageSizeChange={(pageSize) => this.props.changePageSize(pageSize)}
getTheadFilterThProps={() => {
return { style: { position: "inherit", overflow: "inherit" } };
}}
- onFilteredChange={filtered => {
+ onFilteredChange={(filtered) => {
this.setState({ filtered });
if (this.fetchData) {
this.fetchData();
@@ -573,29 +718,35 @@ export class TaskReviewTable extends Component {
{...intlTableProps(this.props.intl)}
PaginationComponent={IntlTablePagination}
FilterComponent={({ filter, onChange }) => {
- const filterValue = filter ? filter.value : ''
- const clearFilter = () => onChange('')
+ const filterValue = filter ? filter.value : "";
+ const clearFilter = () => onChange("");
return (
-
+
{
- onChange(event.target.value)
+ onChange={(event) => {
+ onChange(event.target.value);
}}
/>
{filterValue && (
-
-
+
+
)}
- )
- }
- }
+ );
+ }}
/>
@@ -618,22 +769,21 @@ export class TaskReviewTable extends Component {
export const setupColumnTypes = (props, openComments, data, criteria) => {
const handleClick = (e, linkTo) => {
- e.preventDefault()
+ e.preventDefault();
props.history.push({
pathname: linkTo,
criteria,
- })
- }
- const columns = {}
+ });
+ };
+ const columns = {};
columns.id = {
- id: 'id',
+ id: "id",
Header: props.intl.formatMessage(messages.idLabel),
filterable: true,
- accessor: t => {
+ accessor: (t) => {
if (!t.isBundlePrimary) {
- return
{t.id}
- }
- else {
+ return
{t.id} ;
+ } else {
return (
{
/>
{t.id}
- )
+ );
}
},
sortable: true,
- exportable: t => t.id,
+ exportable: (t) => t.id,
maxWidth: 120,
- }
+ };
columns.featureId = {
- id: 'featureId',
+ id: "featureId",
Header: props.intl.formatMessage(messages.featureIdLabel),
- accessor: t => t.name || t.title,
- exportable: t => t.name || t.title,
+ accessor: (t) => t.name || t.title,
+ exportable: (t) => t.name || t.title,
sortable: false,
filterable: true,
maxWidth: 120,
- }
+ };
columns.status = {
- id: 'status',
- Header: makeInvertable(props.intl.formatMessage(messages.statusLabel),
- () => props.invertField('status'),
- criteria?.invertFields?.status),
- accessor: 'status',
+ id: "status",
+ Header: makeInvertable(
+ props.intl.formatMessage(messages.statusLabel),
+ () => props.invertField("status"),
+ criteria?.invertFields?.status,
+ ),
+ accessor: "status",
sortable: true,
filterable: true,
- exportable: t => props.intl.formatMessage(messagesByStatus[t.status]),
+ exportable: (t) => props.intl.formatMessage(messagesByStatus[t.status]),
maxWidth: 140,
- Cell: props => (
+ Cell: (props) => (
{
),
Filter: ({ filter, onChange }) => {
const options = [
- All
- ]
+
+ All
+ ,
+ ];
_each(TaskStatus, (status) => {
if (isReviewableStatus(status)) {
options.push(
{props.intl.formatMessage(messagesByStatus[status])}
-
- )
+ ,
+ );
}
- })
+ });
return (
onChange(event.target.value)}
+ onChange={(event) => onChange(event.target.value)}
className={"mr-w-full"}
- value={filter ? filter.value : 'all'}
+ value={filter ? filter.value : "all"}
>
{options}
- )
+ );
},
- }
+ };
columns.priority = {
- id: 'priority',
- Header: makeInvertable(props.intl.formatMessage(messages.priorityLabel),
- () => props.invertField('priority'),
- criteria?.invertFields?.priority),
- accessor: 'priority',
+ id: "priority",
+ Header: makeInvertable(
+ props.intl.formatMessage(messages.priorityLabel),
+ () => props.invertField("priority"),
+ criteria?.invertFields?.priority,
+ ),
+ accessor: "priority",
sortable: true,
filterable: true,
- exportable: t => props.intl.formatMessage(messagesByStatus[t.priority]),
+ exportable: (t) => props.intl.formatMessage(messagesByStatus[t.priority]),
maxWidth: 140,
- Cell: props => (
+ Cell: (props) => (
{
),
Filter: ({ filter, onChange }) => {
const options = [
- All
- ]
+
+ All
+ ,
+ ];
_each(TaskPriority, (priority) => {
options.push(
{props.intl.formatMessage(messagesByPriority[priority])}
-
- )
- })
+ ,
+ );
+ });
return (
onChange(event.target.value)}
+ onChange={(event) => onChange(event.target.value)}
className={"mr-w-full"}
- value={filter ? filter.value : 'all'}
+ value={filter ? filter.value : "all"}
>
{options}
- )
+ );
},
- }
+ };
columns.reviewRequestedBy = {
- id: 'reviewRequestedBy',
- Header: makeInvertable(props.intl.formatMessage(messages.reviewRequestedByLabel),
- () => props.invertField('reviewRequestedBy'),
- criteria?.invertFields?.reviewRequestedBy),
- accessor: 'reviewRequestedBy',
+ id: "reviewRequestedBy",
+ Header: makeInvertable(
+ props.intl.formatMessage(messages.reviewRequestedByLabel),
+ () => props.invertField("reviewRequestedBy"),
+ criteria?.invertFields?.reviewRequestedBy,
+ ),
+ accessor: "reviewRequestedBy",
filterable: true,
sortable: false,
- exportable: t => t.reviewRequestedBy?.username,
+ exportable: (t) => t.reviewRequestedBy?.username,
maxWidth: 180,
- Cell: ({row}) => (
+ Cell: ({ row }) => (
{row._original.reviewRequestedBy?.username}
),
- }
+ };
columns.additionalReviewers = {
- id: 'otherReviewers',
+ id: "otherReviewers",
Header: props.intl.formatMessage(messages.additionalReviewersLabel),
- accessor: 'additionalReviewers',
+ accessor: "additionalReviewers",
sortable: false,
filterable: false,
maxWidth: 180,
- Cell: ({row}) => (
+ Cell: ({ row }) => (
{_map(row._original.additionalReviewers, (reviewer, index) => {
return (
- {reviewer.username}
- {(index + 1) !== (row._original.additionalReviewers?.length) ? ", " : ""}
+
+ {reviewer.username}
+
+ {index + 1 !== row._original.additionalReviewers?.length ? ", " : ""}
);
})}
),
- }
+ };
columns.challengeId = {
- id: 'challengeId',
+ id: "challengeId",
Header: props.intl.formatMessage(messages.challengeIdLabel),
- accessor: t => {
- return {t.parent.id}
+ accessor: (t) => {
+ return {t.parent.id} ;
},
- exportable: t => t.id,
+ exportable: (t) => t.id,
sortable: false,
filterable: false,
maxWidth: 120,
- }
+ };
columns.challenge = {
- id: 'challenge',
- Header: makeInvertable(props.intl.formatMessage(messages.challengeLabel),
- () => props.invertField('challenge'),
- criteria?.invertFields?.challenge),
- accessor: 'parent',
+ id: "challenge",
+ Header: makeInvertable(
+ props.intl.formatMessage(messages.challengeLabel),
+ () => props.invertField("challenge"),
+ criteria?.invertFields?.challenge,
+ ),
+ accessor: "parent",
filterable: true,
sortable: false,
- exportable: t => t.parent?.name,
+ exportable: (t) => t.parent?.name,
minWidth: 120,
- Cell: ({row}) => {
- return (
-
- {row._original.parent.name}
-
- )
+ Cell: ({ row }) => {
+ return {row._original.parent.name}
;
},
Filter: ({ filter, onChange }) => {
return (
-
-
+
+
{
- onChange(item)
- setTimeout(() => props.updateChallengeFilterIds(item), 0)
+ onChange(item);
+ setTimeout(() => props.updateChallengeFilterIds(item), 0);
}}
value={filter ? filter.value : ""}
itemList={props.reviewChallenges}
@@ -839,231 +1002,258 @@ export const setupColumnTypes = (props, openComments, data, criteria) => {
/>
{props.challengeFilterIds?.length && props.challengeFilterIds?.[0] !== -2 ? (
-
{
- onChange({ id: -2, name: "All Challenges" })
- setTimeout(() => props.updateChallengeFilterIds({ id: -2, name: "All Challenges" }), 0)
+ onChange({ id: -2, name: "All Challenges" });
+ setTimeout(
+ () => props.updateChallengeFilterIds({ id: -2, name: "All Challenges" }),
+ 0,
+ );
}}
>
-
+
) : null}
);
- }
- }
+ },
+ };
columns.projectId = {
- id: 'projectId',
+ id: "projectId",
Header: props.intl.formatMessage(messages.projectIdLabel),
- accessor: t => {
- return
{t.parent.parent.id}
- },
- exportable: t => t.parent.parent.id,
- sortable: false,
- filterable: false,
- maxWidth: 120,
- }
+ accessor: (t) => {
+ return
{t.parent.parent.id} ;
+ },
+ exportable: (t) => t.parent.parent.id,
+ sortable: false,
+ filterable: false,
+ maxWidth: 120,
+ };
columns.project = {
- id: 'project',
- Header: makeInvertable(props.intl.formatMessage(messages.projectLabel),
- () => props.invertField('project'),
- criteria?.invertFields?.project),
+ id: "project",
+ Header: makeInvertable(
+ props.intl.formatMessage(messages.projectLabel),
+ () => props.invertField("project"),
+ criteria?.invertFields?.project,
+ ),
filterable: true,
sortable: false,
- exportable: t => t.parent?.parent?.displayName,
+ exportable: (t) => t.parent?.parent?.displayName,
minWidth: 120,
- Cell: ({row}) => {
- return (
-
- {row._original.parent.parent.displayName}
-
- )
+ Cell: ({ row }) => {
+ return
{row._original.parent.parent.displayName}
;
},
Filter: ({ filter, onChange }) => {
return (
-
-
+
+
{
- onChange(item)
- setTimeout(() => props.updateProjectFilterIds(item), 0)
+ onChange(item);
+ setTimeout(() => props.updateProjectFilterIds(item), 0);
}}
value={filter ? filter.value : ""}
- itemList={_map(props.reviewProjects, p => ({id: p.id, name: p.displayName}))}
+ itemList={_map(props.reviewProjects, (p) => ({ id: p.id, name: p.displayName }))}
multiselect={props.projectFilterIds}
/>
- {props.projectFilterIds?.length && props.projectFilterIds?.[0] !== -2 ? (
-
{
- onChange({ id: -2, name: "All Projects" })
- setTimeout(() => props.updateProjectFilterIds({ id: -2, name: "All Projects" }), 0)
- }}
- >
-
-
- ) : null}
+ {props.projectFilterIds?.length && props.projectFilterIds?.[0] !== -2 ? (
+
{
+ onChange({ id: -2, name: "All Projects" });
+ setTimeout(() => props.updateProjectFilterIds({ id: -2, name: "All Projects" }), 0);
+ }}
+ >
+
+
+ ) : null}
);
- }
- }
+ },
+ };
columns.mappedOn = {
- id: 'mappedOn',
+ id: "mappedOn",
Header: props.intl.formatMessage(messages.mappedOnLabel),
- accessor: 'mappedOn',
+ accessor: "mappedOn",
sortable: true,
filterable: true,
defaultSortDesc: false,
- exportable: t => t.mappedOn,
+ exportable: (t) => t.mappedOn,
maxWidth: 180,
- Cell: props => {
+ Cell: (props) => {
if (!props.value) {
- return null
+ return null;
}
return (
- )
+ );
},
Filter: () => {
- let mappedOn = criteria?.filters?.mappedOn
+ let mappedOn = criteria?.filters?.mappedOn;
if (typeof mappedOn === "string" && mappedOn !== "") {
- mappedOn = parseISO(mappedOn)
+ mappedOn = parseISO(mappedOn);
}
- const clearFilter = () => props.setFiltered("mappedOn", null)
-
+ const clearFilter = () => props.setFiltered("mappedOn", null);
+
return (
-
+
{
- props.setFiltered("mappedOn", value)
- }}
- intl={props.intl}
+ selected={mappedOn}
+ onChange={(value) => {
+ props.setFiltered("mappedOn", value);
+ }}
+ intl={props.intl}
/>
{mappedOn && (
-
-
+
+
)}
- )
+ );
},
- }
+ };
columns.reviewedAt = {
- id: 'reviewedAt',
+ id: "reviewedAt",
Header: props.intl.formatMessage(messages.reviewedAtLabel),
- accessor: 'reviewedAt',
+ accessor: "reviewedAt",
sortable: true,
filterable: true,
defaultSortDesc: false,
- exportable: t => t.reviewedAt,
+ exportable: (t) => t.reviewedAt,
minWidth: 180,
maxWidth: 200,
- Cell: props => {
+ Cell: (props) => {
if (!props.value) {
- return null
+ return null;
}
return (
- )
+ );
},
Filter: () => {
- let reviewedAt = criteria?.filters?.reviewedAt
+ let reviewedAt = criteria?.filters?.reviewedAt;
if (typeof reviewedAt === "string" && reviewedAt !== "") {
- reviewedAt = parseISO(reviewedAt)
+ reviewedAt = parseISO(reviewedAt);
}
- const clearFilter = () => props.setFiltered("reviewedAt", null)
+ const clearFilter = () => props.setFiltered("reviewedAt", null);
return (
-
+
{
- props.setFiltered("reviewedAt", value)
- }}
- intl={props.intl}
+ selected={reviewedAt}
+ onChange={(value) => {
+ props.setFiltered("reviewedAt", value);
+ }}
+ intl={props.intl}
/>
{reviewedAt && (
-
-
+
+
)}
- )
+ );
},
- }
+ };
columns.metaReviewedAt = {
- id: 'metaReviewedAt',
+ id: "metaReviewedAt",
Header: props.intl.formatMessage(messages.metaReviewedAtLabel),
- accessor: 'metaReviewedAt',
+ accessor: "metaReviewedAt",
sortable: true,
filterable: false,
defaultSortDesc: false,
- exportable: t => t.metaReviewedAt,
+ exportable: (t) => t.metaReviewedAt,
minWidth: 180,
maxWidth: 200,
- Cell: props => {
+ Cell: (props) => {
if (!props.value) {
- return null
+ return null;
}
return (
- )
- }
- }
+ );
+ },
+ };
columns.reviewedBy = {
- id: 'reviewedBy',
- Header: makeInvertable(props.intl.formatMessage(messages.reviewedByLabel),
- () => props.invertField('reviewedBy'),
- criteria?.invertFields?.reviewedBy),
- accessor: 'reviewedBy',
+ id: "reviewedBy",
+ Header: makeInvertable(
+ props.intl.formatMessage(messages.reviewedByLabel),
+ () => props.invertField("reviewedBy"),
+ criteria?.invertFields?.reviewedBy,
+ ),
+ accessor: "reviewedBy",
filterable: true,
sortable: false,
- exportable: t => t.reviewedBy?.username,
+ exportable: (t) => t.reviewedBy?.username,
maxWidth: 180,
- Cell: ({row}) => (
+ Cell: ({ row }) => (
{row._original.reviewedBy ? row._original.reviewedBy.username : "N/A"}
),
- }
+ };
columns.reviewStatus = {
- id: 'reviewStatus',
- Header: makeInvertable(props.intl.formatMessage(messages.reviewStatusLabel),
- () => props.invertField('reviewStatus'),
- criteria?.invertFields?.reviewStatus),
- accessor: 'reviewStatus',
+ id: "reviewStatus",
+ Header: makeInvertable(
+ props.intl.formatMessage(messages.reviewStatusLabel),
+ () => props.invertField("reviewStatus"),
+ criteria?.invertFields?.reviewStatus,
+ ),
+ accessor: "reviewStatus",
sortable: true,
filterable: true,
- exportable: t => props.intl.formatMessage(messagesByReviewStatus[t.reviewStatus]),
+ exportable: (t) => props.intl.formatMessage(messagesByReviewStatus[t.reviewStatus]),
maxWidth: 180,
- Cell: props => (
+ Cell: (props) => (
{
),
Filter: ({ filter, onChange }) => {
const options = [
- All
- ]
+
+ All
+ ,
+ ];
if (props.reviewTasksType === ReviewTasksType.metaReviewTasks) {
- _each([TaskReviewStatus.approved,
- TaskReviewStatus.approvedWithFixes], status =>
+ _each([TaskReviewStatus.approved, TaskReviewStatus.approvedWithFixes], (status) =>
options.push(
{props.intl.formatMessage(messagesByReviewStatus[status])}
-
- )
- )
- }
- else if (props.reviewTasksType === ReviewTasksType.reviewedByMe ||
- props.reviewTasksType === ReviewTasksType.myReviewedTasks ||
- props.reviewTasksType === ReviewTasksType.allReviewedTasks) {
+ ,
+ ),
+ );
+ } else if (
+ props.reviewTasksType === ReviewTasksType.reviewedByMe ||
+ props.reviewTasksType === ReviewTasksType.myReviewedTasks ||
+ props.reviewTasksType === ReviewTasksType.allReviewedTasks
+ ) {
_each(TaskReviewStatus, (status) => {
if (status !== TaskReviewStatus.unnecessary) {
options.push(
{props.intl.formatMessage(messagesByReviewStatus[status])}
-
- )
+ ,
+ );
}
- })
- }
- else {
+ });
+ } else {
_each(TaskReviewStatus, (status) => {
if (isNeedsReviewStatus(status)) {
options.push(
{props.intl.formatMessage(messagesByReviewStatus[status])}
-
- )
+ ,
+ );
}
- })
+ });
}
return (
onChange(event.target.value)}
+ onChange={(event) => onChange(event.target.value)}
className={"mr-w-full"}
- value={filter ? filter.value : 'all'}
+ value={filter ? filter.value : "all"}
>
{options}
- )
+ );
},
- }
+ };
columns.metaReviewStatus = {
- id: 'metaReviewStatus',
- Header: makeInvertable(props.intl.formatMessage(messages.metaReviewStatusLabel),
- () => props.invertField('metaReviewStatus'),
- criteria?.invertFields?.metaReviewStatus),
- accessor: 'metaReviewStatus',
+ id: "metaReviewStatus",
+ Header: makeInvertable(
+ props.intl.formatMessage(messages.metaReviewStatusLabel),
+ () => props.invertField("metaReviewStatus"),
+ criteria?.invertFields?.metaReviewStatus,
+ ),
+ accessor: "metaReviewStatus",
sortable: true,
filterable: true,
- exportable: t => props.intl.formatMessage(messagesByMetaReviewStatus[t.metaReviewStatus]),
+ exportable: (t) => props.intl.formatMessage(messagesByMetaReviewStatus[t.metaReviewStatus]),
maxWidth: 180,
- Cell: props => (_isUndefined(props.value) ? "" :
-
- ),
+ Cell: (props) =>
+ _isUndefined(props.value) ? (
+ ""
+ ) : (
+
+ ),
Filter: ({ filter, onChange }) => {
- const options = [
- ]
+ const options = [];
if (props.reviewTasksType === ReviewTasksType.metaReviewTasks) {
- options.push({props.intl.formatMessage(messages.allNeeded)} )
- options.push({props.intl.formatMessage(messages.metaUnreviewed)} )
+ options.push(
+
+ {props.intl.formatMessage(messages.allNeeded)}
+ ,
+ );
+ options.push(
+
+ {props.intl.formatMessage(messages.metaUnreviewed)}
+ ,
+ );
options.push(
{props.intl.formatMessage(messagesByMetaReviewStatus[TaskReviewStatus.needed])}
-
- )
- }
- else {
- options.push(All )
- options.push({props.intl.formatMessage(messages.metaUnreviewed)} )
+ ,
+ );
+ } else {
+ options.push(
+
+ All
+ ,
+ );
+ options.push(
+
+ {props.intl.formatMessage(messages.metaUnreviewed)}
+ ,
+ );
_each(TaskReviewStatus, (status) => {
- if (status !== TaskReviewStatus.unnecessary &&
- isMetaReviewStatus(status)) {
-
+ if (status !== TaskReviewStatus.unnecessary && isMetaReviewStatus(status)) {
options.push(
{props.intl.formatMessage(messagesByMetaReviewStatus[status])}
-
- )
+ ,
+ );
}
- })
+ });
}
return (
onChange(event.target.value)}
+ onChange={(event) => onChange(event.target.value)}
className={"mr-w-full"}
- value={filter ? filter.value : 'all'}
+ value={filter ? filter.value : "all"}
>
{options}
- )
+ );
},
- }
+ };
columns.metaReviewedBy = {
- id: 'metaReviewedBy',
- Header: makeInvertable(props.intl.formatMessage(messages.metaReviewedByLabel),
- () => props.invertField('metaReviewedBy'),
- criteria?.invertFields?.metaReviewedBy),
- accessor: 'metaReviewedBy',
+ id: "metaReviewedBy",
+ Header: makeInvertable(
+ props.intl.formatMessage(messages.metaReviewedByLabel),
+ () => props.invertField("metaReviewedBy"),
+ criteria?.invertFields?.metaReviewedBy,
+ ),
+ accessor: "metaReviewedBy",
filterable: true,
sortable: false,
- exportable: t => t.metaReviewedBy?.username,
+ exportable: (t) => t.metaReviewedBy?.username,
maxWidth: 180,
- Cell: ({row}) => (
+ Cell: ({ row }) => (
{row._original.metaReviewedBy ? row._original.metaReviewedBy.username : ""}
),
- }
+ };
columns.reviewerControls = {
- id: 'controls',
+ id: "controls",
Header: props.intl.formatMessage(messages.actionsColumnHeader),
sortable: false,
maxWidth: 120,
minWidth: 110,
Cell: ({ row }) => {
- const linkTo = `/challenge/${row._original.parent.id}/task/${row._original.id}/review`
+ const linkTo = `/challenge/${row._original.parent.id}/task/${row._original.id}/review`;
let action = (
- handleClick(e, linkTo)}
className="mr-text-green-lighter hover:mr-text-white mr-cursor-pointer mr-transition"
>
- )
+ );
if (row._original.reviewedBy) {
if (row._original.reviewStatus === TaskReviewStatus.needed) {
action = (
- handleClick(e, linkTo)}
className="mr-text-green-lighter hover:mr-text-white mr-cursor-pointer mr-transition"
>
- )
+ );
} else if (row._original.reviewStatus === TaskReviewStatus.disputed) {
action = (
- handleClick(e, linkTo)}
className="mr-text-green-lighter hover:mr-text-white mr-cursor-pointer mr-transition"
>
- )
+ );
}
}
- return (
-
- {action}
-
- )
- }
- }
+ return {action}
;
+ },
+ };
columns.reviewCompleteControls = {
- id: 'controls',
+ id: "controls",
Header: props.intl.formatMessage(messages.actionsColumnHeader),
sortable: false,
maxWidth: 110,
Cell: ({ row }) => {
- let linkTo = `/challenge/${row._original.parent.id}/task/${row._original.id}`
- let message =
+ let linkTo = `/challenge/${row._original.parent.id}/task/${row._original.id}`;
+ let message = ;
// The mapper needs to rereview a contested task.
- if (row._original.reviewStatus === TaskReviewStatus.disputed ||
- row._original.metaReviewStatus === TaskReviewStatus.rejected) {
- linkTo += "/review"
- message =
+ if (
+ row._original.reviewStatus === TaskReviewStatus.disputed ||
+ row._original.metaReviewStatus === TaskReviewStatus.rejected
+ ) {
+ linkTo += "/review";
+ message = ;
}
return (
- handleClick(e, linkTo)}
- >
+ handleClick(e, linkTo)}>
{message}
- )
- }
- }
+ );
+ },
+ };
columns.metaReviewerControls = {
- id: 'controls',
+ id: "controls",
Header: props.intl.formatMessage(messages.actionsColumnHeader),
sortable: false,
maxWidth: 120,
minWidth: 110,
- Cell: ({row}) =>{
- const linkTo =`/challenge/${row._original.parent.id}/task/${row._original.id}/meta-review`
- let action =(
- {
+ const linkTo = `/challenge/${row._original.parent.id}/task/${row._original.id}/meta-review`;
+ let action = (
+ handleClick(e, linkTo)}
className="mr-text-green-lighter hover:mr-text-white mr-cursor-pointer mr-transition"
>
- )
-
+ );
+
if (row._original.reviewedBy) {
if (row._original.reviewStatus === TaskReviewStatus.needed) {
action = (
- handleClick(e, linkTo)}
className="mr-text-green-lighter hover:mr-text-white mr-cursor-pointer mr-transition"
>
- )
- }
- else if (row._original.reviewStatus === TaskReviewStatus.disputed) {
+ );
+ } else if (row._original.reviewStatus === TaskReviewStatus.disputed) {
action = (
- handleClick(e, linkTo)}
className="mr-text-green-lighter hover:mr-text-white mr-cursor-pointer mr-transition"
>
- )
+ );
}
}
- return
- {action}
-
- }
- }
+ return {action}
;
+ },
+ };
columns.mapperControls = {
- id: 'controls',
+ id: "controls",
Header: props.intl.formatMessage(messages.actionsColumnHeader),
sortable: false,
minWidth: 90,
maxWidth: 120,
Cell: ({ row }) => {
- const linkTo = `/challenge/${row._original.parent.id}/task/${row._original.id}`
+ const linkTo = `/challenge/${row._original.parent.id}/task/${row._original.id}`;
let message =
row._original.reviewStatus === TaskReviewStatus.rejected ? (
) : (
- )
-
+ );
+
return (
- handleClick(e, linkTo)}
- >
+ handleClick(e, linkTo)}>
{message}
{!props.metaReviewEnabled &&
@@ -1364,53 +1563,60 @@ export const setupColumnTypes = (props, openComments, data, criteria) => {
)}
- )
- }
- }
-
+ );
+ },
+ };
+
columns.viewComments = {
- id: 'viewComments',
+ id: "viewComments",
Header: () => ,
- accessor: 'commentID',
+ accessor: "commentID",
sortable: false,
maxWidth: 110,
- Cell: props =>
- openComments(props.row._original.id)} />,
- }
+ Cell: (props) => openComments(props.row._original.id)} />,
+ };
columns.tags = {
- id: 'tags',
+ id: "tags",
Header: props.intl.formatMessage(messages.tagsLabel),
- accessor: 'tags',
+ accessor: "tags",
filterable: true,
sortable: false,
minWidth: 120,
- Cell: ({row}) => {
+ Cell: ({ row }) => {
return (
- {_map(row._original.tags, t => t.name === "" ? null : (
-
- {t.name}
-
- ))}
+ {_map(row._original.tags, (t) =>
+ t.name === "" ? null : (
+
+ {t.name}
+
+ ),
+ )}
- )
+ );
},
- Filter: ({filter, onChange}) => {
+ Filter: ({ filter, onChange }) => {
return (
-
+
);
- }
- }
-
- return columns
-}
-
-export default WithCurrentUser(WithConfigurableColumns(
- WithSavedFilters(TaskReviewTable, "reviewSearchFilters"),
- {}, [], messages, "reviewColumns", "reviewTasksType", false)
-)
\ No newline at end of file
+ },
+ };
+
+ return columns;
+};
+
+export default WithCurrentUser(
+ WithConfigurableColumns(
+ WithSavedFilters(TaskReviewTable, "reviewSearchFilters"),
+ {},
+ [],
+ messages,
+ "reviewColumns",
+ "reviewTasksType",
+ false,
+ ),
+);
diff --git a/src/pages/Review/TasksReview/TasksReviewTable.test.jsx b/src/pages/Review/TasksReview/TasksReviewTable.test.jsx
index d1b574369..ca192ddd3 100644
--- a/src/pages/Review/TasksReview/TasksReviewTable.test.jsx
+++ b/src/pages/Review/TasksReview/TasksReviewTable.test.jsx
@@ -1,7 +1,7 @@
-import { describe, it, expect } from "vitest";
+import { describe, expect, it } from "vitest";
import "@testing-library/jest-dom";
import { render } from "@testing-library/react";
-import { TaskReviewTable, setupColumnTypes, getFilterIds } from "./TasksReviewTable";
+import { TaskReviewTable, getFilterIds, setupColumnTypes } from "./TasksReviewTable";
// reimplement when tests are updated
// const testTableData = [
@@ -406,11 +406,11 @@ describe("TaskReviewTable", () => {
location={{ search: "" }}
intl={{
formatMessage: () => null,
- formatDate: () => ""
+ formatDate: () => "",
}}
isActive
resetColumnChoices={() => null}
- />
+ />,
);
const text = getByText("My Mapped Tasks after Review");
expect(text).toBeInTheDocument();
@@ -419,24 +419,27 @@ describe("TaskReviewTable", () => {
describe("setupColumnTypes", () => {
it("renders a tag with the name test-tag", () => {
- const columns = setupColumnTypes({ intl: { formatMessage: () => null } })
+ const columns = setupColumnTypes({ intl: { formatMessage: () => null } });
const Cell = columns.tags.Cell;
const { getByText } = render(
|
- )
+ />,
+ );
const text = getByText("test-tag");
expect(text).toBeInTheDocument();
@@ -445,12 +448,18 @@ describe("setupColumnTypes", () => {
describe("getChallengeFilterIds", () => {
it("finds and returns and array of challenge ids in the url", () => {
- const challengeIds = getFilterIds('http://127.0.0.1:3000/review/myReviewedTasks?filters.challenge=alskdjfalsdjfa+ioweiru&filters.challengeId=30%2C31&sortCriteria.sortBy=mappedOn&sortCriteria.direction=ASC&page=0&includeTags=true&excludeOtherReviewers=true&pageSize=20', 'filters.challengeId');
+ const challengeIds = getFilterIds(
+ "http://127.0.0.1:3000/review/myReviewedTasks?filters.challenge=alskdjfalsdjfa+ioweiru&filters.challengeId=30%2C31&sortCriteria.sortBy=mappedOn&sortCriteria.direction=ASC&page=0&includeTags=true&excludeOtherReviewers=true&pageSize=20",
+ "filters.challengeId",
+ );
expect(challengeIds[0]).toBe(30);
});
it("returns [-2] if there's no challenge ids", () => {
- const challengeIds = getFilterIds('http://127.0.0.1:3000/review/myReviewedTasks?sortCriteria.sortBy=mappedOn&sortCriteria.direction=ASC&page=0&includeTags=true&excludeOtherReviewers=true&pageSize=20', 'filters.challengeId');
+ const challengeIds = getFilterIds(
+ "http://127.0.0.1:3000/review/myReviewedTasks?sortCriteria.sortBy=mappedOn&sortCriteria.direction=ASC&page=0&includeTags=true&excludeOtherReviewers=true&pageSize=20",
+ "filters.challengeId",
+ );
expect(challengeIds[0]).toBe(-2);
});
-});
\ No newline at end of file
+});
diff --git a/src/pages/Sent/HeaderSent.jsx b/src/pages/Sent/HeaderSent.jsx
index ac41541a0..6c408a1af 100644
--- a/src/pages/Sent/HeaderSent.jsx
+++ b/src/pages/Sent/HeaderSent.jsx
@@ -1,8 +1,8 @@
-import { Component } from 'react'
-import { FormattedMessage } from 'react-intl'
-import BusySpinner from '../../components/BusySpinner/BusySpinner'
-import messages from './Messages'
-import CommentType from '../../services/Comment/CommentType'
+import { Component } from "react";
+import { FormattedMessage } from "react-intl";
+import BusySpinner from "../../components/BusySpinner/BusySpinner";
+import CommentType from "../../services/Comment/CommentType";
+import messages from "./Messages";
class HeaderSent extends Component {
render() {
@@ -15,15 +15,16 @@ class HeaderSent extends Component {
- {this.props.notificationsLoading ?
- :
- this.props.refreshData()}
- >
-
-
- }
+ {this.props.notificationsLoading ? (
+
+ ) : (
+ this.props.refreshData()}
+ >
+
+
+ )}
@@ -49,8 +50,8 @@ class HeaderSent extends Component {
- )
+ );
}
}
-export default HeaderSent
+export default HeaderSent;
diff --git a/src/pages/Sent/Messages.js b/src/pages/Sent/Messages.js
index 152f98937..6bf4aaa34 100644
--- a/src/pages/Sent/Messages.js
+++ b/src/pages/Sent/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with Sent Table
@@ -32,5 +32,5 @@ export default defineMessages({
goToChallengeLabel: {
id: "Sent.controls.goToChallenge.label",
defaultMessage: "Go to Challenge",
- }
-})
+ },
+});
diff --git a/src/pages/Sent/Notification.jsx b/src/pages/Sent/Notification.jsx
index 2c2299e02..7bd247443 100644
--- a/src/pages/Sent/Notification.jsx
+++ b/src/pages/Sent/Notification.jsx
@@ -1,54 +1,48 @@
-import { Fragment, Component } from 'react'
-import PropTypes from 'prop-types'
-import { Link } from 'react-router-dom'
-import { FormattedMessage }
- from 'react-intl'
-import External from '../../components/External/External'
-import Modal from '../../components/Modal/Modal'
-import Markdown from '../../components/MarkdownContent/MarkdownContent'
-import messages from './Messages'
-import CommentType from '../../services/Comment/CommentType'
+import PropTypes from "prop-types";
+import { Component, Fragment } from "react";
+import { FormattedMessage } from "react-intl";
+import { Link } from "react-router-dom";
+import External from "../../components/External/External";
+import Markdown from "../../components/MarkdownContent/MarkdownContent";
+import Modal from "../../components/Modal/Modal";
+import CommentType from "../../services/Comment/CommentType";
+import messages from "./Messages";
class Notification extends Component {
notificationBody = () => {
- return
- }
+ return
;
+ };
renderedNotification = () => (
-
- {this.notificationBody()}
-
+
{this.notificationBody()}
- )
+ );
render() {
return (
-
+
{this.renderedNotification()}
- )
+ );
}
}
-const CommentBody = function(props) {
+const CommentBody = function (props) {
return (
-
+
);
-}
+};
-const AttachedComment = function(props) {
+const AttachedComment = function (props) {
return (
@@ -58,31 +52,31 @@ const AttachedComment = function(props) {
);
-}
+};
-const ViewTask = function(props) {
- const path = props.type === CommentType.TASK
- ? `task/${props.id}`
- : `browse/challenges/${props.id}?tab=conversation`
- const label = props.type === CommentType.TASK
- ? messages.goToTaskLabel
- : messages.goToChallengeLabel
+const ViewTask = function (props) {
+ const path =
+ props.type === CommentType.TASK
+ ? `task/${props.id}`
+ : `browse/challenges/${props.id}?tab=conversation`;
+ const label =
+ props.type === CommentType.TASK ? messages.goToTaskLabel : messages.goToChallengeLabel;
return (
- )
-}
+ );
+};
Notification.propTypes = {
id: PropTypes.number.isRequired,
onClose: PropTypes.func.isRequired,
text: PropTypes.string.isRequired,
-}
+};
-export default Notification
+export default Notification;
diff --git a/src/pages/Sent/Sent.jsx b/src/pages/Sent/Sent.jsx
index 11ee4a487..36d7d9ffa 100644
--- a/src/pages/Sent/Sent.jsx
+++ b/src/pages/Sent/Sent.jsx
@@ -1,23 +1,23 @@
-import { useEffect, useState } from 'react'
-import ReactTable from 'react-table-6'
-import { Link } from 'react-router-dom'
-import { FormattedDate, FormattedTime, injectIntl } from 'react-intl'
-import { intlTableProps } from '../../components/IntlTable/IntlTable'
-import { useSentComments } from './SentCommentsHooks'
-import WithCurrentUser from '../../components/HOCs/WithCurrentUser/WithCurrentUser'
-import HeaderSent from './HeaderSent'
-import Notification from './Notification'
-import CommentType from '../../services/Comment/CommentType'
+import { useEffect, useState } from "react";
+import { FormattedDate, FormattedTime, injectIntl } from "react-intl";
+import { Link } from "react-router-dom";
+import ReactTable from "react-table-6";
+import WithCurrentUser from "../../components/HOCs/WithCurrentUser/WithCurrentUser";
+import { intlTableProps } from "../../components/IntlTable/IntlTable";
+import CommentType from "../../services/Comment/CommentType";
+import HeaderSent from "./HeaderSent";
+import Notification from "./Notification";
+import { useSentComments } from "./SentCommentsHooks";
const defaultSorted = {
- id: 'created',
+ id: "created",
desc: true,
-}
+};
const defaultPagination = {
page: 0,
- pageSize: 25
-}
+ pageSize: 25,
+};
const Sent = (props) => {
const [commentType, setCommentType] = useState(CommentType.TASK);
@@ -27,8 +27,15 @@ const Sent = (props) => {
const [selectedComment, setSelectedComment] = useState(null);
useEffect(() => {
- comments.fetch(props.user?.id, sortCriteria, pagination)
- }, [props.user?.id, commentType, sortCriteria.id, sortCriteria.desc, pagination.page, pagination.pageSize]);
+ comments.fetch(props.user?.id, sortCriteria, pagination);
+ }, [
+ props.user?.id,
+ commentType,
+ sortCriteria.id,
+ sortCriteria.desc,
+ pagination.page,
+ pagination.pageSize,
+ ]);
const resetTable = () => {
setSortCriteria(defaultSorted);
@@ -48,7 +55,11 @@ const Sent = (props) => {
/>
{
loading={comments.loading}
pageSize={pagination.pageSize}
pages={Math.ceil(comments.count / pagination.pageSize)}
- onSortedChange={(criteria) => { setSortCriteria(criteria[0])}}
+ onSortedChange={(criteria) => {
+ setSortCriteria(criteria[0]);
+ }}
onPageChange={(page) => setPagination({ ...pagination, page })}
onPageSizeChange={(pageSize) => setPagination({ ...pagination, pageSize })}
page={pagination.page}
getTrProps={() => {
- const styles = {}
- return { style: styles }
+ const styles = {};
+ return { style: styles };
}}
{...intlTableProps(props.intl)}
>
{(state, makeTable) => {
- return makeTable()
+ return makeTable();
}}
- {selectedComment &&
-
setSelectedComment(null)} id={selectedComment.id} text={selectedComment.text} type={selectedComment.type} />
- }
+ {selectedComment && (
+ setSelectedComment(null)}
+ id={selectedComment.id}
+ text={selectedComment.text}
+ type={selectedComment.type}
+ />
+ )}
- )
-}
+ );
+};
-const taskColumns = ({ setSelectedComment }) => [{
- id: 'task_id',
- Header: 'Task ID',
- accessor: 'taskId',
- Cell: ({value}) => (
-
{value}
- ),
- maxWidth: 100,
- sortable: true,
- resizable: false
-},{
- id: 'created',
- Header: 'Date',
- accessor: 'created',
- Cell: ({value}) => (
- <>
>
- ),
- maxWidth: 200,
- sortable: true,
- resizable: false
-},{
- id: 'comment',
- Header: 'Comment',
- accessor: 'comment',
- Cell: ({ value, row }) => {
- return
setSelectedComment({ id: row.task_id, text: value, type: CommentType.TASK })} style={{ textOverflow: "ellipsis", overflow: "hidden", whiteSpace: "nowrap" }}>{value}
+const taskColumns = ({ setSelectedComment }) => [
+ {
+ id: "task_id",
+ Header: "Task ID",
+ accessor: "taskId",
+ Cell: ({ value }) => (
+
+ {value}
+
+ ),
+ maxWidth: 100,
+ sortable: true,
+ resizable: false,
},
- sortable: true,
- resizable: false
-}]
-
-const challengeColumns = ({ setSelectedComment }) => [{
- id: 'challenge_name',
- Header: 'Challenge',
- accessor: 'challengeName',
- Cell: ({ value, original }) => {
- return
{value}
+ {
+ id: "created",
+ Header: "Date",
+ accessor: "created",
+ Cell: ({ value }) => (
+ <>
+
+ >
+ ),
+ maxWidth: 200,
+ sortable: true,
+ resizable: false,
},
- maxWidth: 200,
- sortable: true,
- resizable: false
-},{
- id: 'created',
- Header: 'Date',
- accessor: 'created',
- Cell: ({value}) => (
- <>
>
- ),
- maxWidth: 200,
- sortable: true,
- resizable: false
-},{
- id: 'comment',
- Header: 'Comment',
- accessor: 'comment',
- Cell: ({ value, original }) => {
- return
setSelectedComment({ id: original.challengeId, text: value, type: CommentType.CHALLENGE })} style={{ textOverflow: "ellipsis", overflow: "hidden", whiteSpace: "nowrap" }}>{value}
+ {
+ id: "comment",
+ Header: "Comment",
+ accessor: "comment",
+ Cell: ({ value, row }) => {
+ return (
+
+ setSelectedComment({ id: row.task_id, text: value, type: CommentType.TASK })
+ }
+ style={{ textOverflow: "ellipsis", overflow: "hidden", whiteSpace: "nowrap" }}
+ >
+ {value}
+
+ );
+ },
+ sortable: true,
+ resizable: false,
},
- sortable: true,
- resizable: false
-}]
+];
-export default injectIntl(WithCurrentUser(Sent))
+const challengeColumns = ({ setSelectedComment }) => [
+ {
+ id: "challenge_name",
+ Header: "Challenge",
+ accessor: "challengeName",
+ Cell: ({ value, original }) => {
+ return (
+
+ {value}
+
+ );
+ },
+ maxWidth: 200,
+ sortable: true,
+ resizable: false,
+ },
+ {
+ id: "created",
+ Header: "Date",
+ accessor: "created",
+ Cell: ({ value }) => (
+ <>
+
+ >
+ ),
+ maxWidth: 200,
+ sortable: true,
+ resizable: false,
+ },
+ {
+ id: "comment",
+ Header: "Comment",
+ accessor: "comment",
+ Cell: ({ value, original }) => {
+ return (
+
+ setSelectedComment({
+ id: original.challengeId,
+ text: value,
+ type: CommentType.CHALLENGE,
+ })
+ }
+ style={{ textOverflow: "ellipsis", overflow: "hidden", whiteSpace: "nowrap" }}
+ >
+ {value}
+
+ );
+ },
+ sortable: true,
+ resizable: false,
+ },
+];
+export default injectIntl(WithCurrentUser(Sent));
diff --git a/src/pages/Sent/SentCommentsHooks.js b/src/pages/Sent/SentCommentsHooks.js
index 152df0d5f..4bafa3a86 100644
--- a/src/pages/Sent/SentCommentsHooks.js
+++ b/src/pages/Sent/SentCommentsHooks.js
@@ -1,6 +1,6 @@
-import { useState } from 'react';
-import { useDispatch } from 'react-redux';
-import { fetchUserComments } from '../../services/User/User';
+import { useState } from "react";
+import { useDispatch } from "react-redux";
+import { fetchUserComments } from "../../services/User/User";
export const useSentComments = (commentType) => {
const dispatch = useDispatch();
@@ -9,7 +9,11 @@ export const useSentComments = (commentType) => {
const [error, setError] = useState("");
const [count, setCount] = useState(0);
- const fetch = async (userId, sort = { id: 'created', desc: true }, pagination = { page: 0, pageSize: 25 }) => {
+ const fetch = async (
+ userId,
+ sort = { id: "created", desc: true },
+ pagination = { page: 0, pageSize: 25 },
+ ) => {
if (userId) {
setError("");
setLoading(true);
@@ -18,8 +22,8 @@ export const useSentComments = (commentType) => {
sort: sort.id,
order: sort.desc ? "DESC" : "ASC",
page: pagination.page,
- limit: pagination.pageSize
- }
+ limit: pagination.pageSize,
+ };
const result = await dispatch(fetchUserComments(userId, commentType, apiFilters));
@@ -29,12 +33,12 @@ export const useSentComments = (commentType) => {
setCount(0);
} else {
setData(result);
- setCount(result?.[0]?.fullCount || 0)
+ setCount(result?.[0]?.fullCount || 0);
}
-
+
setLoading(false);
}
- }
+ };
return { data, fetch, loading, error, count };
-}
+};
diff --git a/src/pages/SignIn/Messages.js b/src/pages/SignIn/Messages.js
index 7c808a092..fe9b8c0a1 100644
--- a/src/pages/SignIn/Messages.js
+++ b/src/pages/SignIn/Messages.js
@@ -1,16 +1,16 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with SignIn page
*/
export default defineMessages({
modalTitle: {
- id: 'Pages.SignIn.modal.title',
+ id: "Pages.SignIn.modal.title",
defaultMessage: "Welcome Back!",
},
modalPrompt: {
id: "Pages.SignIn.modal.prompt",
defaultMessage: "Please sign in to continue",
- }
-})
+ },
+});
diff --git a/src/pages/SignIn/SignIn.jsx b/src/pages/SignIn/SignIn.jsx
index dcaf69827..94d5a3e9c 100644
--- a/src/pages/SignIn/SignIn.jsx
+++ b/src/pages/SignIn/SignIn.jsx
@@ -1,20 +1,15 @@
-import { Component } from 'react'
-import { FormattedMessage } from 'react-intl'
-import Modal from '../../components/Modal/Modal'
-import SignInButton from '../../components/SignInButton/SignInButton'
-import messages from './Messages'
+import { Component } from "react";
+import { FormattedMessage } from "react-intl";
+import Modal from "../../components/Modal/Modal";
+import SignInButton from "../../components/SignInButton/SignInButton";
+import messages from "./Messages";
export class SignIn extends Component {
render() {
return (
-
+
@@ -32,8 +27,8 @@ export class SignIn extends Component {
- )
+ );
}
}
-export default SignIn
+export default SignIn;
diff --git a/src/pages/Social/Messages.js b/src/pages/Social/Messages.js
index c59aef31f..2d34cf7f3 100644
--- a/src/pages/Social/Messages.js
+++ b/src/pages/Social/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with Social page
@@ -43,4 +43,4 @@ export default defineMessages({
id: "Social.none",
defaultMessage: "None",
},
-})
+});
diff --git a/src/pages/Social/Social.jsx b/src/pages/Social/Social.jsx
index 683a66062..1c8c6f552 100644
--- a/src/pages/Social/Social.jsx
+++ b/src/pages/Social/Social.jsx
@@ -1,87 +1,87 @@
-import { useState, useEffect } from 'react'
-import { FormattedMessage, FormattedDate, injectIntl } from 'react-intl'
-import { CopyToClipboard } from 'react-copy-to-clipboard'
-import { Link } from 'react-router-dom'
-import { parseISO } from 'date-fns'
-import _isEmpty from 'lodash/isEmpty'
-import _map from 'lodash/map'
-import { fetchSocialChallenges } from '../../services/Challenge/Challenge'
-import WithCurrentUser
- from '../../components/HOCs/WithCurrentUser/WithCurrentUser'
-import ChallengeShareControls
- from '../../components/TaskPane/ChallengeShareControls/ChallengeShareControls'
-import SvgSymbol from '../../components/SvgSymbol/SvgSymbol'
-import MarkdownContent from '../../components/MarkdownContent/MarkdownContent'
-import BusySpinner from '../../components/BusySpinner/BusySpinner'
-import SignIn from '../SignIn/SignIn'
-import messages from './Messages'
+import { parseISO } from "date-fns";
+import _isEmpty from "lodash/isEmpty";
+import _map from "lodash/map";
+import { useEffect, useState } from "react";
+import { CopyToClipboard } from "react-copy-to-clipboard";
+import { FormattedDate, FormattedMessage, injectIntl } from "react-intl";
+import { Link } from "react-router-dom";
+import BusySpinner from "../../components/BusySpinner/BusySpinner";
+import WithCurrentUser from "../../components/HOCs/WithCurrentUser/WithCurrentUser";
+import MarkdownContent from "../../components/MarkdownContent/MarkdownContent";
+import SvgSymbol from "../../components/SvgSymbol/SvgSymbol";
+import ChallengeShareControls from "../../components/TaskPane/ChallengeShareControls/ChallengeShareControls";
+import { fetchSocialChallenges } from "../../services/Challenge/Challenge";
+import SignIn from "../SignIn/SignIn";
+import messages from "./Messages";
-export const Social = props => {
- const [challenges, setChallenges] = useState({})
- const [loading, setLoading] = useState(true)
+export const Social = (props) => {
+ const [challenges, setChallenges] = useState({});
+ const [loading, setLoading] = useState(true);
useEffect(() => {
- fetchSocialChallenges(10).then(results => {
- setChallenges(results)
- setLoading(false)
- })
- }, [])
+ fetchSocialChallenges(10).then((results) => {
+ setChallenges(results);
+ setLoading(false);
+ });
+ }, []);
if (!props.user) {
- return (
- props.checkingLoginStatus ?
+ return props.checkingLoginStatus ? (
-
:
+
+ ) : (
- )
+ );
}
return (
- {loading ?
: (
-
+ {loading ? (
+
+ ) : (
+
)}
- )
-}
+ );
+};
-const ChallengeList = props => {
- const list =
- _isEmpty(props.challenges) ?
-
:
+const ChallengeList = (props) => {
+ const list = _isEmpty(props.challenges) ? (
+
+ ) : (
- {_map(
- props.challenges,
- challenge =>
- )}
+ {_map(props.challenges, (challenge) => (
+
+ ))}
+ );
return (
@@ -90,12 +90,12 @@ const ChallengeList = props => {
{list}
- )
-}
+ );
+};
-const ChallengeItem = props => {
- const shareTitle = `${props.lead} ${props.challenge.name}`
- const shareUrl = `${window.env.REACT_APP_URL}/browse/challenges/${props.challenge.id}`
+const ChallengeItem = (props) => {
+ const shareTitle = `${props.lead} ${props.challenge.name}`;
+ const shareUrl = `${window.env.REACT_APP_URL}/browse/challenges/${props.challenge.id}`;
return (
{
-
- {props.challenge.name}
-
+
{props.challenge.name}
@@ -124,7 +122,7 @@ const ChallengeItem = props => {
- )
-}
+ );
+};
-export default WithCurrentUser(injectIntl(Social))
+export default WithCurrentUser(injectIntl(Social));
diff --git a/src/pages/Teams/Teams.jsx b/src/pages/Teams/Teams.jsx
index 3ac6dd376..52e198d34 100644
--- a/src/pages/Teams/Teams.jsx
+++ b/src/pages/Teams/Teams.jsx
@@ -1,79 +1,73 @@
-import { useState } from 'react'
-import { injectIntl, FormattedMessage } from 'react-intl'
-import classNames from 'classnames'
-import _isEmpty from 'lodash/isEmpty'
-import WithCurrentUser
- from '../../components/HOCs/WithCurrentUser/WithCurrentUser'
-import WithErrors from '../../components/HOCs/WithErrors/WithErrors'
-import MyTeams from '../../components/Teams/MyTeams/MyTeams'
-import ViewTeam from '../../components/Teams/ViewTeam/ViewTeam'
-import EditTeam from '../../components/Teams/EditTeam/EditTeam'
-import SvgSymbol from '../../components/SvgSymbol/SvgSymbol'
-import BusySpinner from '../../components/BusySpinner/BusySpinner'
-import SignIn from '../SignIn/SignIn'
-import teamsImage from '../../../images/teams.svg'
-import messages from '../../components/Widgets/TeamsWidget/Messages'
+import classNames from "classnames";
+import _isEmpty from "lodash/isEmpty";
+import { useState } from "react";
+import { FormattedMessage, injectIntl } from "react-intl";
+import teamsImage from "../../../images/teams.svg";
+import BusySpinner from "../../components/BusySpinner/BusySpinner";
+import WithCurrentUser from "../../components/HOCs/WithCurrentUser/WithCurrentUser";
+import WithErrors from "../../components/HOCs/WithErrors/WithErrors";
+import SvgSymbol from "../../components/SvgSymbol/SvgSymbol";
+import EditTeam from "../../components/Teams/EditTeam/EditTeam";
+import MyTeams from "../../components/Teams/MyTeams/MyTeams";
+import ViewTeam from "../../components/Teams/ViewTeam/ViewTeam";
+import messages from "../../components/Widgets/TeamsWidget/Messages";
+import SignIn from "../SignIn/SignIn";
-export const Teams = props => {
- const [editingTeam, setEditingTeam] = useState(null)
- const [viewingTeam, setViewingTeam] = useState(null)
- const [showCards, setShowCards] = useState(false)
+export const Teams = (props) => {
+ const [editingTeam, setEditingTeam] = useState(null);
+ const [viewingTeam, setViewingTeam] = useState(null);
+ const [showCards, setShowCards] = useState(false);
if (!props.user) {
- return (
- props.checkingLoginStatus ?
+ return props.checkingLoginStatus ? (
-
:
+
+ ) : (
- )
+ );
}
- let subheader = messages.title
- let headerControls = null
- let currentView = null
+ let subheader = messages.title;
+ let headerControls = null;
+ let currentView = null;
if (editingTeam) {
currentView = (
{
- setEditingTeam(null)
+ finish={(success) => {
+ setEditingTeam(null);
if (success) {
- setViewingTeam(null)
+ setViewingTeam(null);
}
}}
/>
- )
+ );
- subheader =
- _isEmpty(editingTeam) ?
- messages.createTeamTitle :
- messages.editTeamTitle
- }
- else if (viewingTeam) {
- currentView =
- subheader = messages.viewTeamTitle
+ subheader = _isEmpty(editingTeam) ? messages.createTeamTitle : messages.editTeamTitle;
+ } else if (viewingTeam) {
+ currentView = ;
+ subheader = messages.viewTeamTitle;
headerControls = (
- )
- }
- else {
+ );
+ } else {
currentView = (
setViewingTeam(team)}
- editTeam={team => setEditingTeam(team)}
+ viewTeam={(team) => setViewingTeam(team)}
+ editTeam={(team) => setEditingTeam(team)}
createTeam={() => setEditingTeam({})}
showCards={showCards}
/>
- )
- subheader = messages.myTeamsTitle
+ );
+ subheader = messages.myTeamsTitle;
headerControls = (
- )
+ );
}
return (
@@ -109,10 +103,7 @@ export const Teams = props => {
- setEditingTeam({})}
- >
+ setEditingTeam({})}>
@@ -132,7 +123,7 @@ export const Teams = props => {
- )
-}
+ );
+};
-export default WithErrors(WithCurrentUser(injectIntl(Teams)))
+export default WithErrors(WithCurrentUser(injectIntl(Teams)));
diff --git a/src/services/Activity/ActivityActionTypes/ActivityActionTypes.js b/src/services/Activity/ActivityActionTypes/ActivityActionTypes.js
index 9b7dfaf9e..d8f4f3f24 100644
--- a/src/services/Activity/ActivityActionTypes/ActivityActionTypes.js
+++ b/src/services/Activity/ActivityActionTypes/ActivityActionTypes.js
@@ -1,17 +1,17 @@
-import _map from 'lodash/map'
-import _invert from 'lodash/invert'
-import _fromPairs from 'lodash/fromPairs'
-import messages from './Messages'
+import _fromPairs from "lodash/fromPairs";
+import _invert from "lodash/invert";
+import _map from "lodash/map";
+import messages from "./Messages";
// Constants defined on the server
-export const ACTION_TYPE_UPDATED = 0
-export const ACTION_TYPE_CREATED = 1
-export const ACTION_TYPE_DELETED = 2
-export const ACTION_TYPE_TASK_VIEWED = 3
-export const ACTION_TYPE_TASK_STATUS_SET = 4
-export const ACTION_TYPE_TAG_ADDED = 5
-export const ACTION_TYPE_TAG_REMOVED = 6
-export const ACTION_TYPE_QUESTION_ANSWERED = 7
+export const ACTION_TYPE_UPDATED = 0;
+export const ACTION_TYPE_CREATED = 1;
+export const ACTION_TYPE_DELETED = 2;
+export const ACTION_TYPE_TASK_VIEWED = 3;
+export const ACTION_TYPE_TASK_STATUS_SET = 4;
+export const ACTION_TYPE_TAG_ADDED = 5;
+export const ACTION_TYPE_TAG_REMOVED = 6;
+export const ACTION_TYPE_QUESTION_ANSWERED = 7;
export const ActivityActionType = Object.freeze({
updated: ACTION_TYPE_UPDATED,
@@ -22,19 +22,18 @@ export const ActivityActionType = Object.freeze({
tagAdded: ACTION_TYPE_TAG_ADDED,
tagRemoved: ACTION_TYPE_TAG_REMOVED,
questionAnswered: ACTION_TYPE_QUESTION_ANSWERED,
-})
+});
-export const keysByAction = Object.freeze(_invert(ActivityActionType))
+export const keysByAction = Object.freeze(_invert(ActivityActionType));
/**
* Returns an object mapping difficulty values to raw internationalized
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByAction = _fromPairs(
- _map(messages, (message, key) => [ActivityActionType[key], message])
-)
+ _map(messages, (message, key) => [ActivityActionType[key], message]),
+);
/** Returns object containing localized labels */
-export const actionLabels = intl => _fromPairs(
- _map(messages, (message, key) => [key, intl.formatMessage(message)])
-)
+export const actionLabels = (intl) =>
+ _fromPairs(_map(messages, (message, key) => [key, intl.formatMessage(message)]));
diff --git a/src/services/Activity/ActivityActionTypes/ActivityActionTypes.test.js b/src/services/Activity/ActivityActionTypes/ActivityActionTypes.test.js
index 2616a3d5d..48eb6c338 100644
--- a/src/services/Activity/ActivityActionTypes/ActivityActionTypes.test.js
+++ b/src/services/Activity/ActivityActionTypes/ActivityActionTypes.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect } from "vitest";
+import { describe, expect, it } from "vitest";
import { ActivityActionType } from "./ActivityActionTypes";
describe("ActivityActionType", () => {
diff --git a/src/services/Activity/ActivityActionTypes/Messages.js b/src/services/Activity/ActivityActionTypes/Messages.js
index 5ff2600c8..ef24fd943 100644
--- a/src/services/Activity/ActivityActionTypes/Messages.js
+++ b/src/services/Activity/ActivityActionTypes/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with ActivityActionType.
@@ -6,34 +6,34 @@ import { defineMessages } from 'react-intl'
export default defineMessages({
updated: {
id: "Activity.action.updated",
- defaultMessage: "Updated"
+ defaultMessage: "Updated",
},
created: {
id: "Activity.action.created",
- defaultMessage: "Created"
+ defaultMessage: "Created",
},
deleted: {
id: "Activity.action.deleted",
- defaultMessage: "Deleted"
+ defaultMessage: "Deleted",
},
taskViewed: {
id: "Activity.action.taskViewed",
- defaultMessage: "Viewed"
+ defaultMessage: "Viewed",
},
taskStatusSet: {
id: "Activity.action.taskStatusSet",
- defaultMessage: "Set Status on"
+ defaultMessage: "Set Status on",
},
tagAdded: {
id: "Activity.action.tagAdded",
- defaultMessage: "Added Tag to"
+ defaultMessage: "Added Tag to",
},
tagRemoved: {
id: "Activity.action.tagRemoved",
- defaultMessage: "Removed Tag from"
+ defaultMessage: "Removed Tag from",
},
questionAnswered: {
id: "Activity.action.questionAnswered",
- defaultMessage: "Answered Question on"
+ defaultMessage: "Answered Question on",
},
-})
+});
diff --git a/src/services/Activity/ActivityItemTypes/ActivityItemTypes.js b/src/services/Activity/ActivityItemTypes/ActivityItemTypes.js
index e7b10a126..e6785670f 100644
--- a/src/services/Activity/ActivityItemTypes/ActivityItemTypes.js
+++ b/src/services/Activity/ActivityItemTypes/ActivityItemTypes.js
@@ -1,19 +1,19 @@
-import _map from 'lodash/map'
-import _invert from 'lodash/invert'
-import _fromPairs from 'lodash/fromPairs'
-import messages from './Messages'
+import _fromPairs from "lodash/fromPairs";
+import _invert from "lodash/invert";
+import _map from "lodash/map";
+import messages from "./Messages";
// Constants defined on the server
-export const ITEM_TYPE_PROJECT = 0
-export const ITEM_TYPE_CHALLENGE = 1
-export const ITEM_TYPE_TASK = 2
-export const ITEM_TYPE_TAG = 3
-export const ITEM_TYPE_SURVEY = 4
-export const ITEM_TYPE_USER = 5
-export const ITEM_TYPE_GROUP = 6
-export const ITEM_TYPE_VIRTUAL_CHALLENGE = 7
-export const ITEM_TYPE_BUNDLE = 8
-export const ITEM_TYPE_GRANT = 9
+export const ITEM_TYPE_PROJECT = 0;
+export const ITEM_TYPE_CHALLENGE = 1;
+export const ITEM_TYPE_TASK = 2;
+export const ITEM_TYPE_TAG = 3;
+export const ITEM_TYPE_SURVEY = 4;
+export const ITEM_TYPE_USER = 5;
+export const ITEM_TYPE_GROUP = 6;
+export const ITEM_TYPE_VIRTUAL_CHALLENGE = 7;
+export const ITEM_TYPE_BUNDLE = 8;
+export const ITEM_TYPE_GRANT = 9;
export const ActivityItemType = Object.freeze({
project: ITEM_TYPE_PROJECT,
@@ -26,19 +26,18 @@ export const ActivityItemType = Object.freeze({
virtualChallenge: ITEM_TYPE_VIRTUAL_CHALLENGE,
bundle: ITEM_TYPE_BUNDLE,
grant: ITEM_TYPE_GRANT,
-})
+});
-export const keysByType = Object.freeze(_invert(ActivityItemType))
+export const keysByType = Object.freeze(_invert(ActivityItemType));
/**
* Returns an object mapping difficulty values to raw internationalized
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByType = _fromPairs(
- _map(messages, (message, key) => [ActivityItemType[key], message])
-)
+ _map(messages, (message, key) => [ActivityItemType[key], message]),
+);
/** Returns object containing localized labels */
-export const typeLabels = intl => _fromPairs(
- _map(messages, (message, key) => [key, intl.formatMessage(message)])
-)
+export const typeLabels = (intl) =>
+ _fromPairs(_map(messages, (message, key) => [key, intl.formatMessage(message)]));
diff --git a/src/services/Activity/ActivityItemTypes/Messages.js b/src/services/Activity/ActivityItemTypes/Messages.js
index 7ac8642d8..e7ed2a6ce 100644
--- a/src/services/Activity/ActivityItemTypes/Messages.js
+++ b/src/services/Activity/ActivityItemTypes/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with ActivityItemType.
@@ -6,42 +6,42 @@ import { defineMessages } from 'react-intl'
export default defineMessages({
project: {
id: "Activity.item.project",
- defaultMessage: "Project"
+ defaultMessage: "Project",
},
challenge: {
id: "Activity.item.challenge",
- defaultMessage: "Challenge"
+ defaultMessage: "Challenge",
},
task: {
id: "Activity.item.task",
- defaultMessage: "Task"
+ defaultMessage: "Task",
},
tag: {
id: "Activity.item.tag",
- defaultMessage: "Tag"
+ defaultMessage: "Tag",
},
survey: {
id: "Activity.item.survey",
- defaultMessage: "Survey"
+ defaultMessage: "Survey",
},
user: {
id: "Activity.item.user",
- defaultMessage: "User"
+ defaultMessage: "User",
},
group: {
id: "Activity.item.group",
- defaultMessage: "Group"
+ defaultMessage: "Group",
},
virtualChallenge: {
id: "Activity.item.virtualChallenge",
- defaultMessage: "Virtual Challenge"
+ defaultMessage: "Virtual Challenge",
},
bundle: {
id: "Activity.item.bundle",
- defaultMessage: "Bundle"
+ defaultMessage: "Bundle",
},
grant: {
id: "Activity.item.grant",
- defaultMessage: "Grant"
+ defaultMessage: "Grant",
},
-})
+});
diff --git a/src/services/AdminContext/AdminContext.js b/src/services/AdminContext/AdminContext.js
index f9bcc5076..c45c6e3fa 100644
--- a/src/services/AdminContext/AdminContext.js
+++ b/src/services/AdminContext/AdminContext.js
@@ -1,87 +1,87 @@
-import RequestStatus from '../Server/RequestStatus'
-import { fetchProjectChallenges } from '../Challenge/Challenge'
-import { fetchChallengeTasks } from '../Task/Task'
+import { fetchProjectChallenges } from "../Challenge/Challenge";
+import RequestStatus from "../Server/RequestStatus";
+import { fetchChallengeTasks } from "../Task/Task";
// redux actions
-export const MANAGE_PROJECT = 'MANAGE_PROJECT'
-export const CLEAR_MANAGED_PROJECT = 'CLEAR_MANAGED_PROJECT'
-export const SEARCH_PROJECTS = 'SEARCH_PROJECTS'
-export const MANAGE_CHALLENGE = 'MANAGE_CHALLENGE'
-export const CLEAR_MANAGED_CHALLENGE = 'CLEAR_MANAGED_CHALLENGE'
-export const SEARCH_CHALLENGES = 'SEARCH_CHALLENGES'
+export const MANAGE_PROJECT = "MANAGE_PROJECT";
+export const CLEAR_MANAGED_PROJECT = "CLEAR_MANAGED_PROJECT";
+export const SEARCH_PROJECTS = "SEARCH_PROJECTS";
+export const MANAGE_CHALLENGE = "MANAGE_CHALLENGE";
+export const CLEAR_MANAGED_CHALLENGE = "CLEAR_MANAGED_CHALLENGE";
+export const SEARCH_CHALLENGES = "SEARCH_CHALLENGES";
// redux action creators
-export const beginManagingProject = function(projectId, status = RequestStatus.success) {
+export const beginManagingProject = function (projectId, status = RequestStatus.success) {
return {
type: MANAGE_PROJECT,
projectId,
status,
- }
-}
+ };
+};
-export const clearManagedProject = function() {
+export const clearManagedProject = function () {
return {
type: CLEAR_MANAGED_PROJECT,
- }
-}
+ };
+};
-export const beginManagingChallenge = function(challengeId, status = RequestStatus.success) {
+export const beginManagingChallenge = function (challengeId, status = RequestStatus.success) {
return {
type: MANAGE_CHALLENGE,
challengeId,
status,
- }
-}
+ };
+};
-export const clearManagedChallenge = function() {
+export const clearManagedChallenge = function () {
return {
type: CLEAR_MANAGED_CHALLENGE,
- }
-}
+ };
+};
// async action creators
-export const manageProject = function(projectId) {
- return function(dispatch) {
- dispatch(beginManagingProject(projectId, RequestStatus.inProgress))
+export const manageProject = function (projectId) {
+ return function (dispatch) {
+ dispatch(beginManagingProject(projectId, RequestStatus.inProgress));
- return fetchProjectChallenges(projectId)(dispatch)
- .then(() => dispatch(beginManagingProject(projectId, RequestStatus.success)))
- }
-}
+ return fetchProjectChallenges(projectId)(dispatch).then(() =>
+ dispatch(beginManagingProject(projectId, RequestStatus.success)),
+ );
+ };
+};
-export const manageChallenge = function(challengeId) {
- return function(dispatch) {
- dispatch(beginManagingChallenge(challengeId, RequestStatus.inProgress))
+export const manageChallenge = function (challengeId) {
+ return function (dispatch) {
+ dispatch(beginManagingChallenge(challengeId, RequestStatus.inProgress));
- return fetchChallengeTasks(challengeId)(dispatch)
- .then(() => {
- dispatch(beginManagingChallenge(challengeId, RequestStatus.success))
- })
- }
-}
+ return fetchChallengeTasks(challengeId)(dispatch).then(() => {
+ dispatch(beginManagingChallenge(challengeId, RequestStatus.success));
+ });
+ };
+};
// redux reducers
-export const adminContext = function(state={}, action) {
- switch(action.type) {
+export const adminContext = function (state = {}, action) {
+ switch (action.type) {
case MANAGE_PROJECT:
return Object.assign({}, state, {
managingProject: {
id: action.projectId,
loaded: action.status === RequestStatus.success,
- }
- })
+ },
+ });
case CLEAR_MANAGED_PROJECT:
- return Object.assign({}, state, {managingProject: null})
+ return Object.assign({}, state, { managingProject: null });
case MANAGE_CHALLENGE:
return Object.assign({}, state, {
managingChallenge: {
id: action.challengeId,
loaded: action.status === RequestStatus.success,
- }
- })
+ },
+ });
case CLEAR_MANAGED_CHALLENGE:
- return Object.assign({}, state, {managingChallenge: null})
+ return Object.assign({}, state, { managingChallenge: null });
default:
- return state
+ return state;
}
-}
+};
diff --git a/src/services/Challenge/Challenge.js b/src/services/Challenge/Challenge.js
index 556b6a211..ddf6fb324 100644
--- a/src/services/Challenge/Challenge.js
+++ b/src/services/Challenge/Challenge.js
@@ -1,48 +1,44 @@
-import { normalize, schema } from "normalizr";
-import _each from "lodash/each";
-import _compact from "lodash/compact";
-import _pick from "lodash/pick";
-import _map from "lodash/map";
-import _keys from "lodash/keys";
-import _values from "lodash/values";
-import _flatten from "lodash/flatten";
+import { format, parseISO, startOfDay } from "date-fns";
+import geojsontoosm from "geojsontoosm";
import _clone from "lodash/clone";
import _cloneDeep from "lodash/cloneDeep";
+import _compact from "lodash/compact";
+import _each from "lodash/each";
+import _flatten from "lodash/flatten";
+import _fromPairs from "lodash/fromPairs";
+import _groupBy from "lodash/groupBy";
+import _isArray from "lodash/isArray";
import _isEmpty from "lodash/isEmpty";
-import _isString from "lodash/isString";
import _isFinite from "lodash/isFinite";
import _isObject from "lodash/isObject";
-import _isArray from "lodash/isArray";
-import _fromPairs from "lodash/fromPairs";
+import _isString from "lodash/isString";
import _isUndefined from "lodash/isUndefined";
-import _groupBy from "lodash/groupBy";
import _join from "lodash/join";
-import { parseISO, format, startOfDay } from "date-fns";
-import { defaultRoutes as api, isSecurityError } from "../Server/Server";
-import Endpoint from "../Server/Endpoint";
-import RequestStatus from "../Server/RequestStatus";
-import genericEntityReducer from "../Server/GenericEntityReducer";
+import _keys from "lodash/keys";
+import _map from "lodash/map";
+import _pick from "lodash/pick";
+import _values from "lodash/values";
+import { normalize, schema } from "normalizr";
import { commentSchema, receiveComments } from "../Comment/Comment";
-import {
- projectSchema,
- fetchProject,
- receiveProjects,
-} from "../Project/Project";
-import { ensureUserLoggedIn } from "../User/User";
-import { toLatLngBounds } from "../MapBounds/MapBounds";
-import { addError, addServerError } from "../Error/Error";
import AppErrors from "../Error/AppErrors";
-import { RECEIVE_CHALLENGES, REMOVE_CHALLENGE, SET_ADMIN_CHALLENGES } from "./ChallengeActions";
-import { ChallengeStatus } from "./ChallengeStatus/ChallengeStatus";
-import { zeroTaskActions } from "../Task/TaskAction/TaskAction";
+import { addError, addServerError } from "../Error/Error";
+import { toLatLngBounds } from "../MapBounds/MapBounds";
+import { fetchProject, projectSchema, receiveProjects } from "../Project/Project";
import {
- parseQueryString,
+ PARAMS_MAP,
RESULTS_PER_PAGE,
SortOptions,
generateSearchParametersString,
- PARAMS_MAP,
+ parseQueryString,
} from "../Search/Search";
-import geojsontoosm from 'geojsontoosm';
+import Endpoint from "../Server/Endpoint";
+import genericEntityReducer from "../Server/GenericEntityReducer";
+import RequestStatus from "../Server/RequestStatus";
+import { defaultRoutes as api, isSecurityError } from "../Server/Server";
+import { zeroTaskActions } from "../Task/TaskAction/TaskAction";
+import { ensureUserLoggedIn } from "../User/User";
+import { RECEIVE_CHALLENGES, REMOVE_CHALLENGE, SET_ADMIN_CHALLENGES } from "./ChallengeActions";
+import { ChallengeStatus } from "./ChallengeStatus/ChallengeStatus";
/**
* Constants defining searches to include/exclude 'local' challenges.
@@ -77,19 +73,19 @@ export const buildLinkToExportCSV = function (
criteria,
timezone = null,
page = -1,
- limit
+ limit,
) {
- let pageString = '';
+ let pageString = "";
if (page > -1) {
- pageString = `&page=${page}&limit=${limit}`
+ pageString = `&page=${page}&limit=${limit}`;
}
const queryFilters = buildQueryFilters(criteria);
return (
`${window.env.REACT_APP_MAP_ROULETTE_SERVER_URL}/api/v2/challenge/` +
`${challengeId}/tasks/extract?${queryFilters}&timezone=${encodeURIComponent(
- timezone
+ timezone,
)}${pageString}`
);
};
@@ -97,41 +93,42 @@ export const buildLinkToExportCSV = function (
/**
* Builds a link to export GeoJSON
*/
-export const buildLinkToExportGeoJSON = function (
- challengeId,
- criteria,
- timezone = "",
- filename
-) {
+export const buildLinkToExportGeoJSON = function (challengeId, criteria, timezone = "", filename) {
const queryFilters = buildQueryFilters(criteria);
return (
`${window.env.REACT_APP_MAP_ROULETTE_SERVER_URL}/api/v2/challenge/view/` +
`${challengeId}?${queryFilters}&timezone=${encodeURIComponent(
- timezone
+ timezone,
)}&filename=${encodeURIComponent(filename)}`
);
};
export const exportOSMData = function (url, filename) {
- return new Endpoint({ url: () => url, method: 'post', options: { noCache: true } }, {
- method: 'post'
- })
+ return new Endpoint(
+ { url: () => url, method: "post", options: { noCache: true } },
+ {
+ method: "post",
+ },
+ )
.execute()
- .then(geojson => {
- const osmData = geojsontoosm(geojson).replace('generator="geojsontoosm"', 'generator="geojsontoosm" download="never" upload="never"');
-
+ .then((geojson) => {
+ const osmData = geojsontoosm(geojson).replace(
+ 'generator="geojsontoosm"',
+ 'generator="geojsontoosm" download="never" upload="never"',
+ );
+
//https://stackoverflow.com/questions/5143504/how-to-create-and-download-an-xml-file-on-the-fly-using-javascript
const file = `${filename}.osm`;
- const pom = document.createElement('a');
- const bb = new Blob([osmData], {type: 'text/plain'});
+ const pom = document.createElement("a");
+ const bb = new Blob([osmData], { type: "text/plain" });
- if (process.env.NODE_ENV !== 'test') {
- pom.setAttribute('href', window.URL.createObjectURL(bb));
+ if (process.env.NODE_ENV !== "test") {
+ pom.setAttribute("href", window.URL.createObjectURL(bb));
}
- pom.setAttribute('download', file);
- pom.dataset.downloadurl = ['text/plain', pom.download, pom.href].join(':');
- pom.draggable = true;
- pom.classList.add('dragout');
+ pom.setAttribute("download", file);
+ pom.dataset.downloadurl = ["text/plain", pom.download, pom.href].join(":");
+ pom.draggable = true;
+ pom.classList.add("dragout");
pom.click();
return osmData;
@@ -148,9 +145,7 @@ const buildQueryFilters = function (criteria) {
const reviewRequestedBy = filters.reviewRequestedBy;
const reviewedBy = filters.reviewedBy;
const completedBy = filters.completedBy;
- const invf = _map(criteria.invertFields, (v, k) =>
- v ? PARAMS_MAP[k] : undefined
- );
+ const invf = _map(criteria.invertFields, (v, k) => (v ? PARAMS_MAP[k] : undefined));
return (
`status=${_join(filters.status, ",")}&` +
@@ -188,10 +183,7 @@ export const challengeResultEntity = function (normalizedChallengeResults) {
/**
* Add or update challenge data in the redux store
*/
-export const receiveChallenges = function (
- normalizedEntities,
- status = RequestStatus.success
-) {
+export const receiveChallenges = function (normalizedEntities, status = RequestStatus.success) {
_each(normalizedEntities.challenges, (c) => {
if (c.dataOriginDate) {
c.dataOriginDate = format(parseISO(c.dataOriginDate), "yyyy-MM-dd");
@@ -264,18 +256,9 @@ export const fetchPreferredChallenges = function (limit = RESULTS_PER_PAGE) {
const result = normalizedResults.result;
const challenges = normalizedResults.entities.challenges;
- _each(
- result.popular,
- (challenge) => (challenges[challenge].popular = true)
- );
- _each(
- result.newest,
- (challenge) => (challenges[challenge].newest = true)
- );
- _each(
- result.featured,
- (challenge) => (challenges[challenge].featured = true)
- );
+ _each(result.popular, (challenge) => (challenges[challenge].popular = true));
+ _each(result.newest, (challenge) => (challenges[challenge].newest = true));
+ _each(result.featured, (challenge) => (challenges[challenge].featured = true));
dispatch(receiveChallenges(normalizedResults.entities));
@@ -305,11 +288,7 @@ export const fetchSocialChallenges = function (limit = RESULTS_PER_PAGE) {
* @param {array} projectIds
* @param {number} limit
*/
-export const fetchProjectChallengeListing = function (
- projectIds,
- onlyEnabled = false,
- limit = -1
-) {
+export const fetchProjectChallengeListing = function (projectIds, onlyEnabled = false, limit = -1) {
return function (dispatch) {
return new Endpoint(api.challenges.listing, {
schema: [challengeSchema()],
@@ -326,7 +305,7 @@ export const fetchProjectChallengeListing = function (
.catch((error) => {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
dispatch(addError(AppErrors.challenge.fetchFailure));
@@ -340,10 +319,7 @@ export const fetchProjectChallengeListing = function (
* Execute a challenge search, using extendedFind, from the given challenges
* search object
*/
-export const performChallengeSearch = function (
- searchObject,
- limit = RESULTS_PER_PAGE
-) {
+export const performChallengeSearch = function (searchObject, limit = RESULTS_PER_PAGE) {
const sortCriteria = searchObject?.sort ?? {};
const archived = searchObject?.archived ?? false;
const filters = searchObject?.filters ?? {};
@@ -373,10 +349,10 @@ export const performChallengeSearch = function (
page,
challengeStatus,
archived,
- onlyEnabled
+ onlyEnabled,
},
limit,
- admin
+ admin,
);
};
@@ -394,15 +370,11 @@ export const performChallengeSearch = function (
export const extendedFind = function (criteria, limit = RESULTS_PER_PAGE, admin = false) {
const queryString = criteria.searchQuery;
const filters = criteria.filters || {};
- const onlyEnabled = _isUndefined(criteria.onlyEnabled)
- ? true
- : criteria.onlyEnabled;
+ const onlyEnabled = _isUndefined(criteria.onlyEnabled) ? true : criteria.onlyEnabled;
const bounds = criteria.bounds;
- const sortBy = criteria?.sortCriteria?.sortBy ?? (SortOptions.popular);
- const direction = (
- (criteria?.sortCriteria?.direction) || "DESC"
- ).toUpperCase();
+ const sortBy = criteria?.sortCriteria?.sortBy ?? SortOptions.popular;
+ const direction = (criteria?.sortCriteria?.direction || "DESC").toUpperCase();
const sort = sortBy ? `${sortBy}` : null;
const page = _isFinite(criteria.page) ? criteria.page : 0;
const challengeStatus = criteria.challengeStatus;
@@ -449,7 +421,7 @@ export const extendedFind = function (criteria, limit = RESULTS_PER_PAGE, admin
// Keywords/tags can come from both the the query and the filter, so we need to
// combine them into a single keywords array.
const keywords = queryParts.tagTokens.concat(
- _isArray(filters.keywords) ? filters.keywords : []
+ _isArray(filters.keywords) ? filters.keywords : [],
);
if (keywords.length > 0) {
@@ -499,7 +471,7 @@ export const fetchChallengeActions = function (
challengeId = null,
suppressReceive = false,
criteria,
- includeByPriority = true
+ includeByPriority = true,
) {
let searchParameters = {};
if (criteria) {
@@ -509,7 +481,7 @@ export const fetchChallengeActions = function (
false,
false,
null,
- criteria?.invertFields ?? {}
+ criteria?.invertFields ?? {},
);
}
@@ -520,7 +492,7 @@ export const fetchChallengeActions = function (
schema: [challengeSchema()],
variables: { id: challengeId },
params: { ...searchParameters, includeByPriority },
- }
+ },
);
return challengeActionsEndpoint
@@ -553,7 +525,7 @@ export const fetchChallengeActions = function (
//This is a temporary solution until performance degredation in
//maproulette-backend's DataController is addressed.
//Also see https://github.com/maproulette/maproulette-backend/pull/1014
- return []
+ return [];
} else {
dispatch(addError(AppErrors.challenge.fetchFailure));
console.log(error.response || error);
@@ -568,7 +540,7 @@ export const fetchChallengeActions = function (
export const fetchProjectChallengeActions = function (
projectId,
onlyEnabled = false,
- includeByPriority = true
+ includeByPriority = true,
) {
return function (dispatch) {
return new Endpoint(api.challenges.actions, {
@@ -586,7 +558,7 @@ export const fetchProjectChallengeActions = function (
//This is a temporary solution until performance degredation in
//maproulette-backend's DataController is addressed.
//Also see https://github.com/maproulette/maproulette-backend/pull/1014
- return []
+ return [];
} else {
dispatch(addError(AppErrors.challenge.fetchFailure));
console.log(error.response || error);
@@ -608,7 +580,7 @@ export const fetchTagMetrics = function (userId, criteria) {
false,
false,
null,
- criteria?.invertFields ?? {}
+ criteria?.invertFields ?? {},
);
}
@@ -623,7 +595,7 @@ export const fetchTagMetrics = function (userId, criteria) {
.catch((error) => {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
dispatch(addError(AppErrors.challenge.fetchFailure));
@@ -636,11 +608,7 @@ export const fetchTagMetrics = function (userId, criteria) {
/**
* Fetch activity timeline for the given challenge
*/
-export const fetchChallengeActivity = function (
- challengeId,
- startDate,
- endDate
-) {
+export const fetchChallengeActivity = function (challengeId, startDate, endDate) {
return function (dispatch) {
const params = {};
if (startDate) {
@@ -670,7 +638,7 @@ export const fetchChallengeActivity = function (
.catch((error) => {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
dispatch(addError(AppErrors.challenge.fetchFailure));
@@ -709,9 +677,9 @@ export const fetchLatestProjectChallengeActivity = function (projectId) {
"osmUsername",
]),
},
- { id: activity.challengeId }
+ { id: activity.challengeId },
),
- ])
+ ]),
),
},
};
@@ -721,7 +689,7 @@ export const fetchLatestProjectChallengeActivity = function (projectId) {
.catch((error) => {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
dispatch(addError(AppErrors.challenge.fetchFailure));
@@ -754,13 +722,12 @@ export const fetchChallengeComments = function (challengeId) {
challenges: {
[challengeId]: {
id: challengeId,
- comments: _map(
- _keys(normalizedComments.entities.comments),
- (id) => parseInt(id, 10)
+ comments: _map(_keys(normalizedComments.entities.comments), (id) =>
+ parseInt(id, 10),
),
},
},
- })
+ }),
);
}
@@ -790,7 +757,7 @@ export const fetchProjectChallengeComments = function (projectId) {
id: parseInt(challengeId, 10),
comments: _map(comments, "id"),
},
- ])
+ ]),
),
};
dispatch(receiveChallenges(normalizedChallenges));
@@ -851,8 +818,7 @@ export const fetchChallenge = function (challengeId, suppressReceive = false) {
})
.execute()
.then((normalizedResults) => {
- const challenge =
- normalizedResults.entities.challenges[normalizedResults.result];
+ const challenge = normalizedResults.entities.challenges[normalizedResults.result];
// If there are no virtual parents then this field will not be set by server
// so we need to indicate it's empty.
@@ -881,10 +847,7 @@ export const fetchChallenge = function (challengeId, suppressReceive = false) {
* > Note that the challenge data is retrieved via the search API and may
* therefore differ slightly from data fetched directly from the challenge API
*/
-export const fetchChallenges = function (
- challengeIds,
- suppressReceive = false
-) {
+export const fetchChallenges = function (challengeIds, suppressReceive = false) {
return function (dispatch) {
if (!_isArray(challengeIds) || challengeIds.length === 0) {
return Promise.success();
@@ -926,39 +889,34 @@ export const fetchChallenges = function (
* If storeResponse is false, the redux store will not be updated with the
* response data upon completion of a successful request.
*/
- export const saveChallenge = function (
- originalChallengeData,
- storeResponse = true
-) {
+export const saveChallenge = function (originalChallengeData, storeResponse = true) {
return function (dispatch) {
// The server wants keywords/tags represented as a comma-separated string.
let challengeData = _clone(originalChallengeData);
if (_isArray(challengeData.tags)) {
- challengeData.tags = challengeData.tags.map(t => t.trim()).join(",");
+ challengeData.tags = challengeData.tags.map((t) => t.trim()).join(",");
} else if (challengeData.tags) {
challengeData.tags = challengeData.tags.trim();
}
if (_isArray(challengeData.preferredTags)) {
- challengeData.preferredTags = challengeData.preferredTags.map(t => t.trim()).join(",");
+ challengeData.preferredTags = challengeData.preferredTags.map((t) => t.trim()).join(",");
} else if (challengeData.preferredTags) {
challengeData.preferredTags = challengeData.preferredTags.trim();
}
if (_isArray(challengeData.preferredReviewTags)) {
- challengeData.preferredReviewTags =
- challengeData.preferredReviewTags.map(t => t.trim()).join(",");
+ challengeData.preferredReviewTags = challengeData.preferredReviewTags
+ .map((t) => t.trim())
+ .join(",");
} else if (challengeData.preferredReviewTags) {
challengeData.preferredReviewTags = challengeData.preferredReviewTags.trim();
}
// If there is local GeoJSON content being transmitted as a string, parse
// it into JSON first.
- if (
- _isString(challengeData.localGeoJSON) &&
- !_isEmpty(challengeData.localGeoJSON)
- ) {
+ if (_isString(challengeData.localGeoJSON) && !_isEmpty(challengeData.localGeoJSON)) {
challengeData.localGeoJSON = JSON.parse(challengeData.localGeoJSON);
}
@@ -967,10 +925,12 @@ export const fetchChallenges = function (
if (!challengeData.taskWidgetLayout?.workspace && challengeData.taskWidgetLayout) {
try {
if (!(JSON.parse(challengeData.taskWidgetLayout).workspace.name === "taskCompletion")) {
- throw new Error("Widget layout for task completion flow with the wrong format was submitted, it was not included in the save.")
+ throw new Error(
+ "Widget layout for task completion flow with the wrong format was submitted, it was not included in the save.",
+ );
}
- challengeData.taskWidgetLayout = JSON.parse(challengeData.taskWidgetLayout)
- } catch(error) {
+ challengeData.taskWidgetLayout = JSON.parse(challengeData.taskWidgetLayout);
+ } catch (error) {
challengeData.taskWidgetLayout = "";
console.error(error);
}
@@ -978,10 +938,7 @@ export const fetchChallenges = function (
// We need to remove any old challenge keywords first, prior to the
// update.
- return removeChallengeKeywords(
- challengeData.id,
- challengeData.removedTags
- ).then(() => {
+ return removeChallengeKeywords(challengeData.id, challengeData.removedTags).then(() => {
challengeData = _pick(
challengeData, // fields in alphabetical order
[
@@ -1028,73 +985,64 @@ export const fetchChallenges = function (
"requiresLocal",
"reviewSetting",
"taskWidgetLayout",
- "automatedEditsCodeAgreement"
- ]
+ "automatedEditsCodeAgreement",
+ ],
);
if (challengeData.dataOriginDate) {
// Set the timestamp on the dataOriginDate so we get proper timezone info.
- challengeData.dataOriginDate = parseISO(
- challengeData.dataOriginDate
- ).toISOString();
+ challengeData.dataOriginDate = parseISO(challengeData.dataOriginDate).toISOString();
}
// Validate the fields before saving
- const {
- instruction,
- description,
- name,
- checkinComment
- } = challengeData;
-
- const instructionsMinLength = window.env.REACT_APP_CHALLENGE_INSTRUCTIONS_MIN_LENGTH || 150
+ const { instruction, description, name, checkinComment } = challengeData;
+
+ const instructionsMinLength = window.env.REACT_APP_CHALLENGE_INSTRUCTIONS_MIN_LENGTH || 150;
if (
- challengeData.parent != undefined &&
- (
- !instruction ||
+ challengeData.parent != undefined &&
+ (!instruction ||
instruction.length < instructionsMinLength ||
!description?.trim()?.length ||
!name ||
name.length <= 3 ||
!checkinComment ||
- checkinComment === '#maproulette'
- )
+ checkinComment === "#maproulette")
) {
- let errorMessage = '';
+ let errorMessage = "";
if (name === undefined || name.length <= 3) {
errorMessage = AppErrors.challengeSaveFailure.saveNameFailure;
- } else if (description === undefined || description === '') {
+ } else if (description === undefined || description === "") {
errorMessage = AppErrors.challengeSaveFailure.saveDescriptionFailure;
- } else if (
- instruction === undefined ||
- instruction.length < instructionsMinLength
- ) {
+ } else if (instruction === undefined || instruction.length < instructionsMinLength) {
errorMessage = AppErrors.challengeSaveFailure.saveInstructionsFailure;
errorMessage.values = { minLength: `${instructionsMinLength}` };
- } else if (checkinComment === undefined || checkinComment === '' || checkinComment === '#maproulette') {
+ } else if (
+ checkinComment === undefined ||
+ checkinComment === "" ||
+ checkinComment === "#maproulette"
+ ) {
errorMessage = AppErrors.challengeSaveFailure.saveChangesetDescriptionFailure;
} else {
errorMessage = AppErrors.challengeSaveFailure.saveDetailsFailure;
}
- dispatch(
- addServerError(errorMessage)
- );
+ dispatch(addServerError(errorMessage));
throw new Error(errorMessage);
}
// Agreement box should be checked, but if it passes as true the property should not be included
// in the data sent to server.
- if(!_isFinite(challengeData.id) && Object.hasOwn(challengeData, "automatedEditsCodeAgreement")) {
- if(!challengeData.automatedEditsCodeAgreement) {
- const errorMessage = AppErrors.challengeSaveFailure.saveEditPolicyAgreementFailure
- dispatch(
- addServerError(errorMessage)
- );
- throw new Error(errorMessage)
+ if (
+ !_isFinite(challengeData.id) &&
+ Object.hasOwn(challengeData, "automatedEditsCodeAgreement")
+ ) {
+ if (!challengeData.automatedEditsCodeAgreement) {
+ const errorMessage = AppErrors.challengeSaveFailure.saveEditPolicyAgreementFailure;
+ dispatch(addServerError(errorMessage));
+ throw new Error(errorMessage);
} else {
- delete challengeData.automatedEditsCodeAgreement
+ delete challengeData.automatedEditsCodeAgreement;
}
}
@@ -1106,7 +1054,7 @@ export const fetchChallenges = function (
schema: challengeSchema(),
variables: { id: challengeData.id },
json: challengeData,
- }
+ },
);
return saveEndpoint
@@ -1116,20 +1064,17 @@ export const fetchChallenges = function (
dispatch(receiveChallenges(normalizedResults.entities));
}
- return _get(
- normalizedResults,
- `entities.challenges.${normalizedResults.result}`
- );
+ return _get(normalizedResults, `entities.challenges.${normalizedResults.result}`);
})
.catch((serverError) => {
if (isSecurityError(serverError)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
console.log(serverError.response || serverError);
dispatch(
- addServerError(AppErrors.challengeSaveFailure.saveDetailsFailure, serverError)
+ addServerError(AppErrors.challengeSaveFailure.saveDetailsFailure, serverError),
);
// Reload challenge data to ensure our local store is in sync with the
@@ -1155,17 +1100,14 @@ export const uploadChallengeGeoJSON = function (
geoJSON,
lineByLine = true,
removeUnmatchedTasks = false,
- dataOriginDate
+ dataOriginDate,
) {
return function () {
// Server expects the file in a form part named "json"
const formData = new FormData();
formData.append(
"json",
- new File(
- [geoJSON],
- `challenge_${challengeId}_tasks_${Date.now()}.geojson`
- )
+ new File([geoJSON], `challenge_${challengeId}_tasks_${Date.now()}.geojson`),
);
if (dataOriginDate) {
@@ -1202,7 +1144,7 @@ export const setIsEnabled = function (challengeId, isEnabled) {
enabled: isEnabled,
},
},
- })
+ }),
);
saveChallenge({ id: challengeId, enabled: isEnabled }, false)(dispatch);
@@ -1215,10 +1157,7 @@ export const setIsEnabled = function (challengeId, isEnabled) {
* If removeUnmatchedTasks is set to true, then incomplete tasks that don't
* match a task in the updated source data will be removed
*/
-export const rebuildChallenge = function (
- challengeId,
- removeUnmatchedTasks = false
-) {
+export const rebuildChallenge = function (challengeId, removeUnmatchedTasks = false) {
return function (dispatch) {
return new Endpoint(api.challenge.rebuild, {
variables: { id: challengeId },
@@ -1226,12 +1165,12 @@ export const rebuildChallenge = function (
})
.execute()
.then(
- () => fetchChallenge(challengeId)(dispatch) // Refresh challenge data
+ () => fetchChallenge(challengeId)(dispatch), // Refresh challenge data
)
.catch((error) => {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
dispatch(addError(AppErrors.challenge.rebuildFailure));
@@ -1251,12 +1190,12 @@ export const moveChallenge = function (challengeId, toProjectId) {
})
.execute()
.then(
- () => fetchChallenge(challengeId)(dispatch) // Refresh challenge data
+ () => fetchChallenge(challengeId)(dispatch), // Refresh challenge data
)
.catch((error) => {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
dispatch(addError(AppErrors.challenge.moveFailure));
@@ -1269,7 +1208,7 @@ export const moveChallenge = function (challengeId, toProjectId) {
/**
* Move a list of challenges to the given project.
*/
- export const moveChallenges = function (challengeIds, toProjectId) {
+export const moveChallenges = function (challengeIds, toProjectId) {
return function (dispatch) {
return new Endpoint(api.challenges.move, {
variables: { projectId: toProjectId },
@@ -1277,14 +1216,14 @@ export const moveChallenge = function (challengeId, toProjectId) {
})
.execute()
.then(() => {
- challengeIds.forEach(id => {
- fetchChallenge(id)(dispatch)
- })
+ challengeIds.forEach((id) => {
+ fetchChallenge(id)(dispatch);
+ });
})
.catch((error) => {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
dispatch(addError(AppErrors.challenge.moveFailure));
@@ -1310,7 +1249,7 @@ export const deleteChallenge = function (challengeId) {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
dispatch(addError(AppErrors.challenge.deleteFailure));
@@ -1346,7 +1285,7 @@ export const archiveChallenge = function (challengeId, bool) {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
dispatch(addError(AppErrors.challenge.archiveChallenge));
@@ -1368,7 +1307,7 @@ export const archiveChallenges = function (projectId, challengeIds, bool) {
.catch((error) => {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
dispatch(addError(AppErrors.challenge.archiveFailure));
@@ -1385,10 +1324,7 @@ export const archiveChallenges = function (projectId, challengeIds, bool) {
* > Note that if the results contain multiple challenges, only the
* > parent project of the first result is retrieved.
*/
-export const fetchParentProject = function (
- dispatch,
- normalizedChallengeResults
-) {
+export const fetchParentProject = function (dispatch, normalizedChallengeResults) {
const challenge = challengeResultEntity(normalizedChallengeResults);
if (challenge) {
@@ -1422,9 +1358,7 @@ const removeChallengeKeywords = function (challengeId, oldKeywords = []) {
}
// strip empty tags
- const toRemove = _compact(
- _map(oldKeywords, (tag) => (_isEmpty(tag) ? null : tag))
- );
+ const toRemove = _compact(_map(oldKeywords, (tag) => (_isEmpty(tag) ? null : tag)));
// if no keywords given, nothing to do.
if (toRemove.length === 0) {
@@ -1443,11 +1377,7 @@ const removeChallengeKeywords = function (challengeId, oldKeywords = []) {
*
* @private
*/
-const reduceChallengesFurther = function (
- mergedState,
- oldState,
- challengeEntities
-) {
+const reduceChallengesFurther = function (mergedState, oldState, challengeEntities) {
// The generic reduction will merge arrays and objects, but for some fields
// we want to simply overwrite with the latest data.
challengeEntities.forEach((entity) => {
@@ -1501,21 +1431,21 @@ export const challengeEntities = function (state, action) {
return genericEntityReducer(
RECEIVE_CHALLENGES,
"challenges",
- reduceChallengesFurther
+ reduceChallengesFurther,
)(state, action);
}
};
const ADMIN_CHALLENGES_INITIAL_STATE = {
data: [],
- loadingCompleted: false
-}
+ loadingCompleted: false,
+};
-export const adminChallengeEntities = function(state = ADMIN_CHALLENGES_INITIAL_STATE, action) {
+export const adminChallengeEntities = function (state = ADMIN_CHALLENGES_INITIAL_STATE, action) {
switch (action.type) {
case SET_ADMIN_CHALLENGES:
return { data: action.payload, loadingCompleted: action.loadingCompleted };
default:
return state;
}
-}
+};
diff --git a/src/services/Challenge/Challenge.test.js b/src/services/Challenge/Challenge.test.js
index f0c6acf1c..ff9417a21 100644
--- a/src/services/Challenge/Challenge.test.js
+++ b/src/services/Challenge/Challenge.test.js
@@ -1,45 +1,42 @@
-import { describe, it, expect, vi } from "vitest";
+import { describe, expect, it, vi } from "vitest";
import { exportOSMData } from "./Challenge";
const mockGeojson = {
- "type": "FeatureCollection",
- "features": [
+ type: "FeatureCollection",
+ features: [
{
- "type": "Feature",
- "properties": {},
- "geometry": {
- "coordinates": [
- 20.28233073276013,
- 12.457374955871742
- ],
- "type": "Point"
- }
- }
- ]
-}
+ type: "Feature",
+ properties: {},
+ geometry: {
+ coordinates: [20.28233073276013, 12.457374955871742],
+ type: "Point",
+ },
+ },
+ ],
+};
describe("exportOSMData", () => {
beforeEach(() => {
const headers = new Map();
- headers.set('content-type', 'application/json')
+ headers.set("content-type", "application/json");
global.fetch = vi.fn(() =>
Promise.resolve({
json: () => mockGeojson,
status: 200,
- headers
- })
+ headers,
+ }),
);
});
afterEach(() => {
- fetch.mockClear()
- })
+ fetch.mockClear();
+ });
it("returns osm data", async () => {
- const osmData = await exportOSMData('mockEndpoint');
+ const osmData = await exportOSMData("mockEndpoint");
- expect(osmData.includes('lat=\"12.457374955871742\" lon=\"20.28233073276013\"')).toBe(true);
+ expect(osmData.includes('lat="12.457374955871742" lon="20.28233073276013"')).toBe(true);
expect(osmData.includes('download="never" upload="never"')).toBe(true);
});
-});
\ No newline at end of file
+});
diff --git a/src/services/Challenge/ChallengeActions.js b/src/services/Challenge/ChallengeActions.js
index 14c5735c6..ef46a52af 100644
--- a/src/services/Challenge/ChallengeActions.js
+++ b/src/services/Challenge/ChallengeActions.js
@@ -1,5 +1,5 @@
// This needs to be separated from Challenge to avoid a circular
// dependency with Project.
-export const RECEIVE_CHALLENGES = 'RECEIVE_CHALLENGES'
-export const REMOVE_CHALLENGE = 'REMOVE_CHALLENGE'
-export const SET_ADMIN_CHALLENGES = 'SET_ADMIN_CHALLENGES'
+export const RECEIVE_CHALLENGES = "RECEIVE_CHALLENGES";
+export const REMOVE_CHALLENGE = "REMOVE_CHALLENGE";
+export const SET_ADMIN_CHALLENGES = "SET_ADMIN_CHALLENGES";
diff --git a/src/services/Challenge/ChallengeArchived/ChallengeArchived.js b/src/services/Challenge/ChallengeArchived/ChallengeArchived.js
index 2f48987af..1964def8a 100644
--- a/src/services/Challenge/ChallengeArchived/ChallengeArchived.js
+++ b/src/services/Challenge/ChallengeArchived/ChallengeArchived.js
@@ -1,7 +1,7 @@
-export const challengePassesArchivedFilter = function(filter, challenge) {
+export const challengePassesArchivedFilter = function (filter, challenge) {
if (!filter.archived) {
- return challenge.isArchived === false
+ return challenge.isArchived === false;
}
- return true
-}
+ return true;
+};
diff --git a/src/services/Challenge/ChallengeBasemap/ChallengeBasemap.js b/src/services/Challenge/ChallengeBasemap/ChallengeBasemap.js
index f4cfd881e..a20440c9c 100644
--- a/src/services/Challenge/ChallengeBasemap/ChallengeBasemap.js
+++ b/src/services/Challenge/ChallengeBasemap/ChallengeBasemap.js
@@ -1,25 +1,23 @@
-import { OPEN_STREET_MAP,
- OPEN_CYCLE_MAP,
- BING } from '../../VisibleLayer/LayerSources'
-import _map from 'lodash/map'
-import _fromPairs from 'lodash/fromPairs'
-import messages from './Messages'
+import _fromPairs from "lodash/fromPairs";
+import _map from "lodash/map";
+import { BING, OPEN_CYCLE_MAP, OPEN_STREET_MAP } from "../../VisibleLayer/LayerSources";
+import messages from "./Messages";
/**
* Constants representing basemap layers. These constants are defined on the
* server
*/
-export const CHALLENGE_BASEMAP_NONE = -1
-export const CHALLENGE_BASEMAP_OPEN_STREET_MAP = 0
-export const CHALLENGE_BASEMAP_OPEN_CYCLE_MAP = 1
-export const CHALLENGE_BASEMAP_BING = 2
-export const CHALLENGE_BASEMAP_CUSTOM = 3
-export const CHALLENGE_BASEMAP_IDENTIFIED = 4
+export const CHALLENGE_BASEMAP_NONE = -1;
+export const CHALLENGE_BASEMAP_OPEN_STREET_MAP = 0;
+export const CHALLENGE_BASEMAP_OPEN_CYCLE_MAP = 1;
+export const CHALLENGE_BASEMAP_BING = 2;
+export const CHALLENGE_BASEMAP_CUSTOM = 3;
+export const CHALLENGE_BASEMAP_IDENTIFIED = 4;
/**
* Global variable to indicate to the frontend not to submit a basemap change
*/
-export const CHALLENGE_BASEMAP_UNCHANGED = -2
+export const CHALLENGE_BASEMAP_UNCHANGED = -2;
export const ChallengeBasemap = Object.freeze({
none: CHALLENGE_BASEMAP_NONE,
@@ -28,7 +26,7 @@ export const ChallengeBasemap = Object.freeze({
bing: CHALLENGE_BASEMAP_BING,
custom: CHALLENGE_BASEMAP_CUSTOM,
identified: CHALLENGE_BASEMAP_IDENTIFIED,
-})
+});
export const ChallengeBasemapBulkEdit = Object.freeze({
unchanged: CHALLENGE_BASEMAP_UNCHANGED,
@@ -38,35 +36,34 @@ export const ChallengeBasemapBulkEdit = Object.freeze({
bing: CHALLENGE_BASEMAP_BING,
custom: CHALLENGE_BASEMAP_CUSTOM,
identified: CHALLENGE_BASEMAP_IDENTIFIED,
-})
+});
/** Map basemap constants to layer source constants */
-export const basemapLayerSources = function() {
+export const basemapLayerSources = function () {
return {
[CHALLENGE_BASEMAP_OPEN_STREET_MAP]: OPEN_STREET_MAP,
[CHALLENGE_BASEMAP_OPEN_CYCLE_MAP]: OPEN_CYCLE_MAP,
[CHALLENGE_BASEMAP_BING]: BING,
- }
-}
+ };
+};
/**
* Returns an object mapping basemap layer values to raw internationalized
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByBasemapLayer = _fromPairs(
- _map(messages, (message, key) => [ChallengeBasemap[key], message])
-)
+ _map(messages, (message, key) => [ChallengeBasemap[key], message]),
+);
/** Returns object containing localized labels */
-export const basemapLayerLabels = intl => _fromPairs(
- _map(messages, (message, key) => [key, intl.formatMessage(message)])
-)
+export const basemapLayerLabels = (intl) =>
+ _fromPairs(_map(messages, (message, key) => [key, intl.formatMessage(message)]));
-export const challengeOwnerBasemapLayerLabels = intl => {
+export const challengeOwnerBasemapLayerLabels = (intl) => {
// Use a different `none` label for challenge owners in order to promote
// clarity as to what `none` means in the context of creating a challenge.
- const labels = basemapLayerLabels(intl)
- labels.none = intl.formatMessage(messages.noneChallengeOwner)
+ const labels = basemapLayerLabels(intl);
+ labels.none = intl.formatMessage(messages.noneChallengeOwner);
- return labels
-}
+ return labels;
+};
diff --git a/src/services/Challenge/ChallengeBasemap/Messages.js b/src/services/Challenge/ChallengeBasemap/Messages.js
index 2960549f0..d3da60cee 100644
--- a/src/services/Challenge/ChallengeBasemap/Messages.js
+++ b/src/services/Challenge/ChallengeBasemap/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with ChallengeBasemap.
@@ -28,4 +28,4 @@ export default defineMessages({
id: "Challenge.basemap.custom",
defaultMessage: "Custom",
},
-})
+});
diff --git a/src/services/Challenge/ChallengeCategorizationKeywords/ChallengeCategorizationKeywords.js b/src/services/Challenge/ChallengeCategorizationKeywords/ChallengeCategorizationKeywords.js
index 15f7cfdef..e69740361 100644
--- a/src/services/Challenge/ChallengeCategorizationKeywords/ChallengeCategorizationKeywords.js
+++ b/src/services/Challenge/ChallengeCategorizationKeywords/ChallengeCategorizationKeywords.js
@@ -1,18 +1,17 @@
-import _isArray from 'lodash/isArray'
+import _isArray from "lodash/isArray";
/**
* Determines if the given challenge passes the given categorization keywords filter.
*/
-export const challengePassesCategorizationKeywordsFilter = function(filter, challenge) {
- let passing = true
- if (_isArray(filter.categorizationKeywords)) {
- filter.categorizationKeywords.map(key => {
- if(!challenge.tags?.includes(key)){
- passing = false
- }
- })
- }
-
- return passing
+export const challengePassesCategorizationKeywordsFilter = function (filter, challenge) {
+ let passing = true;
+ if (_isArray(filter.categorizationKeywords)) {
+ filter.categorizationKeywords.map((key) => {
+ if (!challenge.tags?.includes(key)) {
+ passing = false;
+ }
+ });
}
-
\ No newline at end of file
+
+ return passing;
+};
diff --git a/src/services/Challenge/ChallengeComments.js b/src/services/Challenge/ChallengeComments.js
index 59057e71e..7693622ab 100644
--- a/src/services/Challenge/ChallengeComments.js
+++ b/src/services/Challenge/ChallengeComments.js
@@ -1,7 +1,7 @@
import _values from "lodash/values";
-import Endpoint from "../Server/Endpoint";
import AppErrors from "../Error/AppErrors";
import { addError } from "../Error/Error";
+import Endpoint from "../Server/Endpoint";
import { defaultRoutes as api } from "../Server/Server";
/**
@@ -25,7 +25,7 @@ export const fetchChallengeComments = function (challengeId) {
const challengeComments = _values(rawChallengeComments?.result);
const allComments = taskComments.concat(challengeComments);
const sortedComments = allComments.sort(
- (a, b) => new Date(a.created) - new Date(b.created)
+ (a, b) => new Date(a.created) - new Date(b.created),
);
return sortedComments;
@@ -49,7 +49,7 @@ export const postChallengeComment = function (challengeId, comment) {
})
.execute()
.catch((error) => {
- dispatch(addError(AppErrors.task.addCommentFailure))
+ dispatch(addError(AppErrors.task.addCommentFailure));
console.log(error.response || error);
});
};
diff --git a/src/services/Challenge/ChallengeDifficulty/ChallengeDifficulty.js b/src/services/Challenge/ChallengeDifficulty/ChallengeDifficulty.js
index 3ccc10af4..396d655e0 100644
--- a/src/services/Challenge/ChallengeDifficulty/ChallengeDifficulty.js
+++ b/src/services/Challenge/ChallengeDifficulty/ChallengeDifficulty.js
@@ -1,36 +1,35 @@
-import _map from 'lodash/map'
-import _fromPairs from 'lodash/fromPairs'
-import _isNumber from 'lodash/isNumber'
-import messages from './Messages'
+import _fromPairs from "lodash/fromPairs";
+import _isNumber from "lodash/isNumber";
+import _map from "lodash/map";
+import messages from "./Messages";
// These constants are defined on the server
-export const CHALLENGE_DIFFICULTY_EASY = 1
-export const CHALLENGE_DIFFICULTY_NORMAL = 2
-export const CHALLENGE_DIFFICULTY_EXPERT = 3
+export const CHALLENGE_DIFFICULTY_EASY = 1;
+export const CHALLENGE_DIFFICULTY_NORMAL = 2;
+export const CHALLENGE_DIFFICULTY_EXPERT = 3;
export const ChallengeDifficulty = Object.freeze({
easy: CHALLENGE_DIFFICULTY_EASY,
normal: CHALLENGE_DIFFICULTY_NORMAL,
expert: CHALLENGE_DIFFICULTY_EXPERT,
-})
+});
/**
* Returns an object mapping difficulty values to raw internationalized
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByDifficulty = _fromPairs(
- _map(messages, (message, key) => [ChallengeDifficulty[key], message])
-)
+ _map(messages, (message, key) => [ChallengeDifficulty[key], message]),
+);
/** Returns object containing localized labels */
-export const difficultyLabels = intl => _fromPairs(
- _map(messages, (message, key) => [key, intl.formatMessage(message)])
-)
+export const difficultyLabels = (intl) =>
+ _fromPairs(_map(messages, (message, key) => [key, intl.formatMessage(message)]));
-export const challengePassesDifficultyFilter = function(filter, challenge) {
+export const challengePassesDifficultyFilter = function (filter, challenge) {
if (_isNumber(filter.difficulty)) {
- return challenge.difficulty === filter.difficulty
+ return challenge.difficulty === filter.difficulty;
}
- return true
-}
+ return true;
+};
diff --git a/src/services/Challenge/ChallengeDifficulty/Messages.js b/src/services/Challenge/ChallengeDifficulty/Messages.js
index 72b52edc0..183b52392 100644
--- a/src/services/Challenge/ChallengeDifficulty/Messages.js
+++ b/src/services/Challenge/ChallengeDifficulty/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with ChallengeDifficulty.
@@ -19,6 +19,5 @@ export default defineMessages({
any: {
id: "Challenge.difficulty.any",
defaultMessage: "Any",
- }
-})
-
+ },
+});
diff --git a/src/services/Challenge/ChallengeGlobal/ChallengeGlobal.js b/src/services/Challenge/ChallengeGlobal/ChallengeGlobal.js
index 95ca4f597..1f6bdc795 100644
--- a/src/services/Challenge/ChallengeGlobal/ChallengeGlobal.js
+++ b/src/services/Challenge/ChallengeGlobal/ChallengeGlobal.js
@@ -1,7 +1,7 @@
-export const challengePassesGlobalFilter = function(filter, challenge) {
+export const challengePassesGlobalFilter = function (filter, challenge) {
if (!filter.global) {
- return challenge.isGlobal === false
+ return challenge.isGlobal === false;
}
- return true
-}
+ return true;
+};
diff --git a/src/services/Challenge/ChallengeKeywords/ChallengeKeywords.js b/src/services/Challenge/ChallengeKeywords/ChallengeKeywords.js
index 7d04607d5..337d18848 100644
--- a/src/services/Challenge/ChallengeKeywords/ChallengeKeywords.js
+++ b/src/services/Challenge/ChallengeKeywords/ChallengeKeywords.js
@@ -1,22 +1,22 @@
-import _isArray from 'lodash/isArray'
-import _map from 'lodash/map'
-import _fromPairs from 'lodash/fromPairs'
-import _flatten from 'lodash/flatten'
-import _keys from 'lodash/keys'
-import _values from 'lodash/values'
-import _find from 'lodash/find'
-import _intersection from 'lodash/intersection'
-import _isEmpty from 'lodash/isEmpty'
-import _startCase from 'lodash/startCase'
-import messages from './Messages'
-
-export const CHALLENGE_CATEGORY_NAVIGATION = "navigation"
-export const CHALLENGE_CATEGORY_WATER = "water"
-export const CHALLENGE_CATEGORY_POINTS_OF_INTEREST = "pointsOfInterest"
-export const CHALLENGE_CATEGORY_BUILDINGS = "buildings"
-export const CHALLENGE_CATEGORY_LAND_USE = "landUse"
-export const CHALLENGE_CATEGORY_TRANSIT = "transit"
-export const CHALLENGE_CATEGORY_OTHER = "other"
+import _find from "lodash/find";
+import _flatten from "lodash/flatten";
+import _fromPairs from "lodash/fromPairs";
+import _intersection from "lodash/intersection";
+import _isArray from "lodash/isArray";
+import _isEmpty from "lodash/isEmpty";
+import _keys from "lodash/keys";
+import _map from "lodash/map";
+import _startCase from "lodash/startCase";
+import _values from "lodash/values";
+import messages from "./Messages";
+
+export const CHALLENGE_CATEGORY_NAVIGATION = "navigation";
+export const CHALLENGE_CATEGORY_WATER = "water";
+export const CHALLENGE_CATEGORY_POINTS_OF_INTEREST = "pointsOfInterest";
+export const CHALLENGE_CATEGORY_BUILDINGS = "buildings";
+export const CHALLENGE_CATEGORY_LAND_USE = "landUse";
+export const CHALLENGE_CATEGORY_TRANSIT = "transit";
+export const CHALLENGE_CATEGORY_OTHER = "other";
/**
* Categories are groupings of one or more keywords that serve to
@@ -35,8 +35,7 @@ export const ChallengeCategoryKeywords = {
[CHALLENGE_CATEGORY_LAND_USE]: ["landuse", "boundary"],
[CHALLENGE_CATEGORY_TRANSIT]: ["railway", "public_transport"],
[CHALLENGE_CATEGORY_OTHER]: [],
-}
-
+};
/**
* Custom keyword categories setup in .env
@@ -52,92 +51,92 @@ export const ChallengeCategoryKeywords = {
* }
* }
*/
-export let customCategoryKeywords = {}
-const customCategoryJson = window.env?.REACT_APP_CUSTOM_KEYWORD_CATEGORIES
+export let customCategoryKeywords = {};
+const customCategoryJson = window.env?.REACT_APP_CUSTOM_KEYWORD_CATEGORIES;
if (!_isEmpty(customCategoryJson)) {
try {
- customCategoryKeywords = JSON.parse(customCategoryJson)
- }
- catch(error) {
- console.log(error)
+ customCategoryKeywords = JSON.parse(customCategoryJson);
+ } catch (error) {
+ console.log(error);
}
}
/**
* Object representing combination of standard keyword categories and any custom categories.
*/
-export const combinedCategoryKeywords =
- Object.assign(
- {},
- _fromPairs(_map(customCategoryKeywords, (category, key) => [key, category.keywords])),
- ChallengeCategoryKeywords
- )
+export const combinedCategoryKeywords = Object.assign(
+ {},
+ _fromPairs(_map(customCategoryKeywords, (category, key) => [key, category.keywords])),
+ ChallengeCategoryKeywords,
+);
/**
* Returns object containing localized labels for standard keyword categories.
* Set includeCustom=true to also include custom keyword labels.
*/
-export const keywordLabels = (intl, includeCustom=false) => {
- let labels = _map(messages, (message, key) => [key, intl.formatMessage(message)])
+export const keywordLabels = (intl, includeCustom = false) => {
+ let labels = _map(messages, (message, key) => [key, intl.formatMessage(message)]);
if (includeCustom) {
labels = labels.concat(
- _map(customCategoryKeywords,
- (customCategory, key) => [key, customCategory.label || _startCase(key)]
- )
- )
+ _map(customCategoryKeywords, (customCategory, key) => [
+ key,
+ customCategory.label || _startCase(key),
+ ]),
+ );
}
- return _fromPairs(labels)
-}
+ return _fromPairs(labels);
+};
/**
* Returns object containing localized labels for custom keyword categories
*/
-export const customKeywordLabels = () => _fromPairs(
- _map(customCategoryKeywords, (customCategory, key) =>
- [key, customCategory.label || _startCase(key)]
- )
-)
+export const customKeywordLabels = () =>
+ _fromPairs(
+ _map(customCategoryKeywords, (customCategory, key) => [
+ key,
+ customCategory.label || _startCase(key),
+ ]),
+ );
/** An array of all keywords referenced by a category */
-export const rawCategoryKeywords = _flatten(_values(ChallengeCategoryKeywords))
+export const rawCategoryKeywords = _flatten(_values(ChallengeCategoryKeywords));
/**
* Returns the category containing keywords that match any of the given
* keywords, or 'other' if none match. Set includeCustom=true to include any
* custom keyword categories in the search as well.
*/
-export const categoryMatchingKeywords = function(keywords, includeCustom=false) {
- const keywordArray = _isArray(keywords) ? keywords : [keywords]
+export const categoryMatchingKeywords = function (keywords, includeCustom = false) {
+ const keywordArray = _isArray(keywords) ? keywords : [keywords];
- let matchingCategory =
- _find(_keys(ChallengeCategoryKeywords), category =>
- _intersection(ChallengeCategoryKeywords[category],
- keywordArray).length > 0
- )
+ let matchingCategory = _find(
+ _keys(ChallengeCategoryKeywords),
+ (category) => _intersection(ChallengeCategoryKeywords[category], keywordArray).length > 0,
+ );
// Search custom keywords as well, if needed and requested. They have a slightly
// different structure to support customization.
if (!matchingCategory && includeCustom) {
- matchingCategory = _find(_keys(customCategoryKeywords), category =>
- _intersection(customCategoryKeywords[category].keywords,
- keywordArray).length > 0
- )
+ matchingCategory = _find(
+ _keys(customCategoryKeywords),
+ (category) =>
+ _intersection(customCategoryKeywords[category].keywords, keywordArray).length > 0,
+ );
}
- return matchingCategory ? matchingCategory : 'other'
-}
-
+ return matchingCategory ? matchingCategory : "other";
+};
/**
* Determines if the given challenge passes the given keywords filter.
*/
-export const challengePassesKeywordFilter = function(filter, challenge) {
+export const challengePassesKeywordFilter = function (filter, challenge) {
if (_isArray(filter.keywords)) {
// Any matching keyword is a pass
- return _intersection(filter.keywords, challenge.tags).length > 0
+ return _intersection(filter.keywords, challenge.tags).length > 0;
}
- return true
-}
+ return true;
+};
diff --git a/src/services/Challenge/ChallengeKeywords/Messages.js b/src/services/Challenge/ChallengeKeywords/Messages.js
index 7bfe059e8..520e60178 100644
--- a/src/services/Challenge/ChallengeKeywords/Messages.js
+++ b/src/services/Challenge/ChallengeKeywords/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with ChallengeKeywords.
@@ -36,4 +36,4 @@ export default defineMessages({
id: "Challenge.keywords.any",
defaultMessage: "Anything",
},
-})
+});
diff --git a/src/services/Challenge/ChallengeLocation/ChallengeLocation.js b/src/services/Challenge/ChallengeLocation/ChallengeLocation.js
index d609ec111..1d3b53e62 100644
--- a/src/services/Challenge/ChallengeLocation/ChallengeLocation.js
+++ b/src/services/Challenge/ChallengeLocation/ChallengeLocation.js
@@ -1,83 +1,81 @@
-import bbox from '@turf/bbox'
-import _map from 'lodash/map'
-import _fromPairs from 'lodash/fromPairs'
-import _isEmpty from 'lodash/isEmpty'
-import _each from 'lodash/each'
-import _concat from 'lodash/concat'
-import _indexOf from 'lodash/indexOf'
-import { toLatLngBounds,
- boundsWithinAllowedMaxDegrees } from '../../MapBounds/MapBounds'
-import messages from './Messages'
+import bbox from "@turf/bbox";
+import _concat from "lodash/concat";
+import _each from "lodash/each";
+import _fromPairs from "lodash/fromPairs";
+import _indexOf from "lodash/indexOf";
+import _isEmpty from "lodash/isEmpty";
+import _map from "lodash/map";
+import { boundsWithinAllowedMaxDegrees, toLatLngBounds } from "../../MapBounds/MapBounds";
+import messages from "./Messages";
-
-export const CHALLENGE_LOCATION_NEAR_USER = 'nearMe'
-export const CHALLENGE_LOCATION_WITHIN_MAPBOUNDS = 'withinMapBounds'
-export const CHALLENGE_LOCATION_INTERSECTING_MAPBOUNDS = 'intersectingMapBounds'
+export const CHALLENGE_LOCATION_NEAR_USER = "nearMe";
+export const CHALLENGE_LOCATION_WITHIN_MAPBOUNDS = "withinMapBounds";
+export const CHALLENGE_LOCATION_INTERSECTING_MAPBOUNDS = "intersectingMapBounds";
export const ChallengeLocation = Object.freeze({
[CHALLENGE_LOCATION_INTERSECTING_MAPBOUNDS]: CHALLENGE_LOCATION_INTERSECTING_MAPBOUNDS,
[CHALLENGE_LOCATION_NEAR_USER]: CHALLENGE_LOCATION_NEAR_USER,
-})
+});
/**
* Returns an object mapping difficulty values to raw internationalized
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByLocation = _fromPairs(
- _map(messages, (message, key) => [ChallengeLocation[key], message])
-)
+ _map(messages, (message, key) => [ChallengeLocation[key], message]),
+);
/** Returns object containing localized labels */
-export const locationLabels = intl => _fromPairs(
- _map(messages, (message, key) => [key, intl.formatMessage(message)])
-)
+export const locationLabels = (intl) =>
+ _fromPairs(_map(messages, (message, key) => [key, intl.formatMessage(message)]));
/**
* Returns true if the given challenge passes the location filter in the given
* challenge filters.
*/
-export const challengePassesLocationFilter = function(challengeFilters,
- challenge,
- props) {
- if (challengeFilters.location !== CHALLENGE_LOCATION_WITHIN_MAPBOUNDS &&
- challengeFilters.location !== CHALLENGE_LOCATION_INTERSECTING_MAPBOUNDS &&
- challengeFilters.location !== CHALLENGE_LOCATION_NEAR_USER ) {
- return true
+export const challengePassesLocationFilter = function (challengeFilters, challenge, props) {
+ if (
+ challengeFilters.location !== CHALLENGE_LOCATION_WITHIN_MAPBOUNDS &&
+ challengeFilters.location !== CHALLENGE_LOCATION_INTERSECTING_MAPBOUNDS &&
+ challengeFilters.location !== CHALLENGE_LOCATION_NEAR_USER
+ ) {
+ return true;
}
if (_isEmpty(props.searchCriteria?.mapBounds?.bounds)) {
- return true
+ return true;
}
- const challengeSearchMapBounds = toLatLngBounds(props.searchCriteria.mapBounds.bounds)
+ const challengeSearchMapBounds = toLatLngBounds(props.searchCriteria.mapBounds.bounds);
// Or if the challenge is listed in the TaskClusters or in the Map Bounded Tasks
- let validChallenges = []
+ let validChallenges = [];
_each(props.mapBoundedTasks?.tasks, (task) => {
- validChallenges = _concat(validChallenges, task.parentId)
- })
+ validChallenges = _concat(validChallenges, task.parentId);
+ });
_each(props.taskClusters?.clusters, (cluster) => {
- validChallenges = _concat(validChallenges, cluster.challengeIds)
- })
+ validChallenges = _concat(validChallenges, cluster.challengeIds);
+ });
if (_indexOf(validChallenges, challenge.id) > -1) {
- return true
+ return true;
}
- if (!challengeSearchMapBounds ||
- !boundsWithinAllowedMaxDegrees(challengeSearchMapBounds)) {
+ if (!challengeSearchMapBounds || !boundsWithinAllowedMaxDegrees(challengeSearchMapBounds)) {
// If user wants challenges that simply intersect the bounds, then let those
// pass if we are not analyzing individual tasks.
- if (challengeFilters.location === CHALLENGE_LOCATION_INTERSECTING_MAPBOUNDS &&
- !_isEmpty(challenge.bounding)) {
- const challengeBounds = toLatLngBounds(bbox(challenge.bounding))
+ if (
+ challengeFilters.location === CHALLENGE_LOCATION_INTERSECTING_MAPBOUNDS &&
+ !_isEmpty(challenge.bounding)
+ ) {
+ const challengeBounds = toLatLngBounds(bbox(challenge.bounding));
if (challengeSearchMapBounds.intersects(challengeBounds)) {
- return true
+ return true;
}
}
}
- return false
-}
+ return false;
+};
diff --git a/src/services/Challenge/ChallengeLocation/Messages.js b/src/services/Challenge/ChallengeLocation/Messages.js
index e3d2f15a4..41bda7398 100644
--- a/src/services/Challenge/ChallengeLocation/Messages.js
+++ b/src/services/Challenge/ChallengeLocation/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with ChallengeLocation
@@ -20,4 +20,4 @@ export default defineMessages({
id: "Challenge.location.any",
defaultMessage: "Anywhere",
},
-})
+});
diff --git a/src/services/Challenge/ChallengeProject/ChallengeProject.js b/src/services/Challenge/ChallengeProject/ChallengeProject.js
index 57a955f94..d5cce7f36 100644
--- a/src/services/Challenge/ChallengeProject/ChallengeProject.js
+++ b/src/services/Challenge/ChallengeProject/ChallengeProject.js
@@ -1,6 +1,6 @@
-import _includes from 'lodash/includes'
-import _isEmpty from 'lodash/isEmpty'
-import _isObject from 'lodash/isObject'
+import _includes from "lodash/includes";
+import _isEmpty from "lodash/isEmpty";
+import _isObject from "lodash/isObject";
/**
* Returns true if the given challenge passes the project-name filter in the
@@ -8,30 +8,29 @@ import _isObject from 'lodash/isObject'
*
* @author [Neil Rotstan](https://github.com/nrotstan)
*/
-export const challengePassesProjectFilter = function(challengeFilters, challenge) {
+export const challengePassesProjectFilter = function (challengeFilters, challenge) {
if (!_isEmpty(challengeFilters.project)) {
if (!challenge?.parent?.displayName) {
- return false
+ return false;
}
- const virtualParents = challenge?.virtualParents ?? []
+ const virtualParents = challenge?.virtualParents ?? [];
for (let i = 0; i < virtualParents.length; i++) {
- const vp = virtualParents[i]
+ const vp = virtualParents[i];
if (_isObject(vp) && vp.enabled) {
- if (_includes(vp.displayName.toLowerCase(),
- challengeFilters.project.toLowerCase())) {
- return true
+ if (_includes(vp.displayName.toLowerCase(), challengeFilters.project.toLowerCase())) {
+ return true;
}
}
}
-
// Just look for a basic case-insensitive substring
- if (!_includes(challenge.parent.displayName.toLowerCase(),
- challengeFilters.project.toLowerCase())) {
- return false
+ if (
+ !_includes(challenge.parent.displayName.toLowerCase(), challengeFilters.project.toLowerCase())
+ ) {
+ return false;
}
}
- return true
-}
+ return true;
+};
diff --git a/src/services/Challenge/ChallengeReviewSetting/ChallengeReviewSetting.js b/src/services/Challenge/ChallengeReviewSetting/ChallengeReviewSetting.js
index 3ead92518..7e5e5fa83 100644
--- a/src/services/Challenge/ChallengeReviewSetting/ChallengeReviewSetting.js
+++ b/src/services/Challenge/ChallengeReviewSetting/ChallengeReviewSetting.js
@@ -1,8 +1,8 @@
// These constants are defined on the server
-export const REVIEW_SETTING_NOT_REQUIRED = 0
-export const REVIEW_SETTING_REQUESTED = 1
+export const REVIEW_SETTING_NOT_REQUIRED = 0;
+export const REVIEW_SETTING_REQUESTED = 1;
export const ChallengeReviewSetting = Object.freeze({
requested: REVIEW_SETTING_REQUESTED,
notRequired: REVIEW_SETTING_NOT_REQUIRED,
-})
\ No newline at end of file
+});
diff --git a/src/services/Challenge/ChallengeSnapshot.js b/src/services/Challenge/ChallengeSnapshot.js
index 87af0f491..410fa7e77 100644
--- a/src/services/Challenge/ChallengeSnapshot.js
+++ b/src/services/Challenge/ChallengeSnapshot.js
@@ -1,61 +1,57 @@
-import Endpoint from '../Server/Endpoint'
-import { defaultRoutes as api } from '../Server/Server'
+import Endpoint from "../Server/Endpoint";
+import { defaultRoutes as api } from "../Server/Server";
/**
* Fetch challenge snapshot list for the given challenge.
*/
-export const fetchChallengeSnapshotList = function(challengeId, includeAllData = false) {
- return new Endpoint(
- api.challenge.snapshotList,
- {
- schema: {},
- variables: {id: challengeId},
- params: {includeAllData}
- }
- ).execute().catch((error) => {
- console.log(error.response || error)
+export const fetchChallengeSnapshotList = function (challengeId, includeAllData = false) {
+ return new Endpoint(api.challenge.snapshotList, {
+ schema: {},
+ variables: { id: challengeId },
+ params: { includeAllData },
})
-}
+ .execute()
+ .catch((error) => {
+ console.log(error.response || error);
+ });
+};
/**
* Record a challenge snapshot.
*/
-export const recordChallengeSnapshot = function(challengeId) {
- return new Endpoint(
- api.challenge.recordSnapshot,
- {
- variables: {id: challengeId},
- }
- ).execute().catch((error) => {
- console.log(error.response || error)
+export const recordChallengeSnapshot = function (challengeId) {
+ return new Endpoint(api.challenge.recordSnapshot, {
+ variables: { id: challengeId },
})
-}
+ .execute()
+ .catch((error) => {
+ console.log(error.response || error);
+ });
+};
/**
* Removes a challenge snapshot.
*/
-export const removeChallengeSnapshot = function(snapshotId) {
- return new Endpoint(
- api.challenge.removeSnapshot,
- {
- variables: {id: snapshotId},
- }
- ).execute().catch((error) => {
- console.log(error.response || error)
+export const removeChallengeSnapshot = function (snapshotId) {
+ return new Endpoint(api.challenge.removeSnapshot, {
+ variables: { id: snapshotId },
})
-}
+ .execute()
+ .catch((error) => {
+ console.log(error.response || error);
+ });
+};
/**
* Fetch challenge snapshot by id.
*/
-export const fetchChallengeSnapshot = function(snapshotId) {
- return new Endpoint(
- api.challenge.snapshot,
- {
- schema: {},
- variables: {id: snapshotId},
- }
- ).execute().catch((error) => {
- console.log(error.response || error)
+export const fetchChallengeSnapshot = function (snapshotId) {
+ return new Endpoint(api.challenge.snapshot, {
+ schema: {},
+ variables: { id: snapshotId },
})
-}
+ .execute()
+ .catch((error) => {
+ console.log(error.response || error);
+ });
+};
diff --git a/src/services/Challenge/ChallengeStatus/ChallengeStatus.js b/src/services/Challenge/ChallengeStatus/ChallengeStatus.js
index 14a2f86eb..54e77d22b 100644
--- a/src/services/Challenge/ChallengeStatus/ChallengeStatus.js
+++ b/src/services/Challenge/ChallengeStatus/ChallengeStatus.js
@@ -1,22 +1,22 @@
-import _map from 'lodash/map'
-import _fromPairs from 'lodash/fromPairs'
-import _isFinite from 'lodash/isFinite'
-import messages from './Messages'
+import _fromPairs from "lodash/fromPairs";
+import _isFinite from "lodash/isFinite";
+import _map from "lodash/map";
+import messages from "./Messages";
/**
* Constants defining the various statuses a Challenge may be
* in. These statuses are defined on the server
*/
-export const CHALLENGE_STATUS_NONE = 0 // called NA on the server
-export const CHALLENGE_STATUS_BUILDING = 1
-export const CHALLENGE_STATUS_FAILED = 2
-export const CHALLENGE_STATUS_READY = 3
-export const CHALLENGE_STATUS_PARTIALLY_LOADED = 4
-export const CHALLENGE_STATUS_FINISHED = 5
-export const CHALLENGE_STATUS_DELETING_TASKS = 6
+export const CHALLENGE_STATUS_NONE = 0; // called NA on the server
+export const CHALLENGE_STATUS_BUILDING = 1;
+export const CHALLENGE_STATUS_FAILED = 2;
+export const CHALLENGE_STATUS_READY = 3;
+export const CHALLENGE_STATUS_PARTIALLY_LOADED = 4;
+export const CHALLENGE_STATUS_FINISHED = 5;
+export const CHALLENGE_STATUS_DELETING_TASKS = 6;
// We can ask the server for challenges that have no status by using -1
-export const CHALLENGE_STATUS_EMPTY = -1
+export const CHALLENGE_STATUS_EMPTY = -1;
export const ChallengeStatus = Object.freeze({
none: CHALLENGE_STATUS_NONE,
@@ -27,29 +27,30 @@ export const ChallengeStatus = Object.freeze({
finished: CHALLENGE_STATUS_FINISHED,
deletingTasks: CHALLENGE_STATUS_DELETING_TASKS,
empty: CHALLENGE_STATUS_EMPTY,
-})
+});
/**
* Returns an object mapping status values to raw internationalized
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByStatus = _fromPairs(
- _map(messages, (message, key) => [ChallengeStatus[key], message])
-)
+ _map(messages, (message, key) => [ChallengeStatus[key], message]),
+);
/** Returns object containing localized labels */
-export const statusLayerLabels = intl => _fromPairs(
- _map(messages, (message, key) => [key, intl.formatMessage(message)])
-)
+export const statusLayerLabels = (intl) =>
+ _fromPairs(_map(messages, (message, key) => [key, intl.formatMessage(message)]));
/**
* Returns true if the given challenge status is considered to be usable
* and presentable to users, false if not.
*/
-export const isUsableChallengeStatus = function(status, allowFinishedStatus = false) {
- return status === CHALLENGE_STATUS_READY ||
- status === CHALLENGE_STATUS_PARTIALLY_LOADED ||
- status === CHALLENGE_STATUS_NONE ||
- (allowFinishedStatus && status === CHALLENGE_STATUS_FINISHED) ||
- !_isFinite(status) // treat missing as NONE
-}
+export const isUsableChallengeStatus = function (status, allowFinishedStatus = false) {
+ return (
+ status === CHALLENGE_STATUS_READY ||
+ status === CHALLENGE_STATUS_PARTIALLY_LOADED ||
+ status === CHALLENGE_STATUS_NONE ||
+ (allowFinishedStatus && status === CHALLENGE_STATUS_FINISHED) ||
+ !_isFinite(status)
+ ); // treat missing as NONE
+};
diff --git a/src/services/Challenge/ChallengeStatus/Messages.js b/src/services/Challenge/ChallengeStatus/Messages.js
index 1440925eb..250e1423a 100644
--- a/src/services/Challenge/ChallengeStatus/Messages.js
+++ b/src/services/Challenge/ChallengeStatus/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with ChallengeStatus.
@@ -6,30 +6,30 @@ import { defineMessages } from 'react-intl'
export default defineMessages({
none: {
id: "Challenge.status.none",
- defaultMessage: "Not Applicable"
+ defaultMessage: "Not Applicable",
},
building: {
id: "Challenge.status.building",
- defaultMessage: "Building"
+ defaultMessage: "Building",
},
failed: {
id: "Challenge.status.failed",
- defaultMessage: "Failed"
+ defaultMessage: "Failed",
},
ready: {
id: "Challenge.status.ready",
- defaultMessage: "Ready"
+ defaultMessage: "Ready",
},
partiallyLoaded: {
id: "Challenge.status.partiallyLoaded",
- defaultMessage: "Partially Loaded"
+ defaultMessage: "Partially Loaded",
},
finished: {
id: "Challenge.status.finished",
- defaultMessage: "Finished"
+ defaultMessage: "Finished",
},
deletingTasks: {
id: "Challenge.status.deletingTasks",
- defaultMessage: "Deleting Tasks"
+ defaultMessage: "Deleting Tasks",
},
-})
+});
diff --git a/src/services/Challenge/ChallengeZoom/ChallengeZoom.js b/src/services/Challenge/ChallengeZoom/ChallengeZoom.js
index 3b1fe4f47..1509df781 100644
--- a/src/services/Challenge/ChallengeZoom/ChallengeZoom.js
+++ b/src/services/Challenge/ChallengeZoom/ChallengeZoom.js
@@ -1,4 +1,4 @@
-import _range from 'lodash/range'
+import _range from "lodash/range";
/**
* Constants related to zoom levels. These are based on
@@ -10,13 +10,13 @@ import _range from 'lodash/range'
*/
/** Minimum possible zoom */
-export const MIN_ZOOM = 0
+export const MIN_ZOOM = 0;
/** Maximum possible zoom */
-export const MAX_ZOOM = 19
+export const MAX_ZOOM = 19;
/** Default zoom level */
-export const DEFAULT_ZOOM = 13
+export const DEFAULT_ZOOM = 13;
/** Array of all zoom levels */
-export const ZOOM_LEVELS = Object.freeze(_range(MIN_ZOOM, MAX_ZOOM + 1))
+export const ZOOM_LEVELS = Object.freeze(_range(MIN_ZOOM, MAX_ZOOM + 1));
diff --git a/src/services/Challenge/CooperativeType/CooperativeType.js b/src/services/Challenge/CooperativeType/CooperativeType.js
index 5a6f601d2..7a925b076 100644
--- a/src/services/Challenge/CooperativeType/CooperativeType.js
+++ b/src/services/Challenge/CooperativeType/CooperativeType.js
@@ -1,42 +1,40 @@
-import _map from 'lodash/map'
-import _invert from 'lodash/invert'
-import _fromPairs from 'lodash/fromPairs'
-import messages from './Messages'
+import _fromPairs from "lodash/fromPairs";
+import _invert from "lodash/invert";
+import _map from "lodash/map";
+import messages from "./Messages";
/**
* Constants defining types of cooperative challenges
* server.
*/
-export const COOPERATIVE_TYPE_NONE = 0
-export const COOPERATIVE_TYPE_TAGS = 1
-export const COOPERATIVE_TYPE_CHANGEFILE = 2
+export const COOPERATIVE_TYPE_NONE = 0;
+export const COOPERATIVE_TYPE_TAGS = 1;
+export const COOPERATIVE_TYPE_CHANGEFILE = 2;
export const CooperativeType = Object.freeze({
none: COOPERATIVE_TYPE_NONE,
tags: COOPERATIVE_TYPE_TAGS,
changeFile: COOPERATIVE_TYPE_CHANGEFILE,
-})
+});
/**
* Determines if the given cooperative type represents active cooperation, i.e.
* that it's a valid type and is not NONE
*/
-export const isCooperative = function(cooperativeType) {
- return !!keysByCooperativeType[cooperativeType] &&
- cooperativeType !== COOPERATIVE_TYPE_NONE
-}
+export const isCooperative = function (cooperativeType) {
+ return !!keysByCooperativeType[cooperativeType] && cooperativeType !== COOPERATIVE_TYPE_NONE;
+};
-export const keysByCooperativeType = Object.freeze(_invert(CooperativeType))
+export const keysByCooperativeType = Object.freeze(_invert(CooperativeType));
/**
* Returns an object mapping cooperative types to raw internationalized
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByCooperativeType = _fromPairs(
- _map(messages, (message, key) => [CooperativeType[key], message])
-)
+ _map(messages, (message, key) => [CooperativeType[key], message]),
+);
/** Returns object containing localized labels */
-export const cooperativeTypeLabels = intl => _fromPairs(
- _map(messages, (message, key) => [key, intl.formatMessage(message)])
-)
+export const cooperativeTypeLabels = (intl) =>
+ _fromPairs(_map(messages, (message, key) => [key, intl.formatMessage(message)]));
diff --git a/src/services/Challenge/CooperativeType/CooperativeType.test.js b/src/services/Challenge/CooperativeType/CooperativeType.test.js
index e0d75b45e..7fa92a1dd 100644
--- a/src/services/Challenge/CooperativeType/CooperativeType.test.js
+++ b/src/services/Challenge/CooperativeType/CooperativeType.test.js
@@ -1,20 +1,20 @@
-import _values from 'lodash/values'
-import { CooperativeType, isCooperative } from './CooperativeType'
+import _values from "lodash/values";
+import { CooperativeType, isCooperative } from "./CooperativeType";
describe("isCooperative", () => {
test("returns false if given an invalid cooperative type", () => {
- expect(isCooperative(-1)).toBe(false)
- })
+ expect(isCooperative(-1)).toBe(false);
+ });
test("returns false if NONE type is given", () => {
- expect(isCooperative(CooperativeType.none)).toBe(false)
- })
+ expect(isCooperative(CooperativeType.none)).toBe(false);
+ });
test("returns true for valid, active types", () => {
- _values(CooperativeType).forEach(cooperativeType => {
+ _values(CooperativeType).forEach((cooperativeType) => {
if (cooperativeType !== CooperativeType.none) {
- expect(isCooperative(cooperativeType)).toBe(true)
+ expect(isCooperative(cooperativeType)).toBe(true);
}
- })
- })
-})
+ });
+ });
+});
diff --git a/src/services/Challenge/CooperativeType/Messages.js b/src/services/Challenge/CooperativeType/Messages.js
index 22ad39359..54091e336 100644
--- a/src/services/Challenge/CooperativeType/Messages.js
+++ b/src/services/Challenge/CooperativeType/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with CooperativeType
@@ -16,4 +16,4 @@ export default defineMessages({
id: "Challenge.cooperativeType.changeFile",
defaultMessage: "Cooperative",
},
-})
+});
diff --git a/src/services/Comment/Comment.js b/src/services/Comment/Comment.js
index 49142afb3..ede09b805 100644
--- a/src/services/Comment/Comment.js
+++ b/src/services/Comment/Comment.js
@@ -1,29 +1,28 @@
-import { schema } from 'normalizr'
-import RequestStatus from '../Server/RequestStatus'
-import genericEntityReducer from '../Server/GenericEntityReducer'
+import { schema } from "normalizr";
+import genericEntityReducer from "../Server/GenericEntityReducer";
+import RequestStatus from "../Server/RequestStatus";
/** normalizr schema for comments */
-export const commentSchema = function() {
- return new schema.Entity('comments')
-}
+export const commentSchema = function () {
+ return new schema.Entity("comments");
+};
// redux actions
-export const RECEIVE_COMMENTS = 'RECEIVE_COMMENTS'
+export const RECEIVE_COMMENTS = "RECEIVE_COMMENTS";
// redux action creators
/**
* Add or update comment data in the redux store
*/
-export const receiveComments = function(normalizedEntities,
- status = RequestStatus.success) {
+export const receiveComments = function (normalizedEntities, status = RequestStatus.success) {
return {
type: RECEIVE_COMMENTS,
status,
entities: normalizedEntities,
- receivedAt: Date.now()
- }
-}
+ receivedAt: Date.now(),
+ };
+};
// async action creators
@@ -39,15 +38,14 @@ export const receiveComments = function(normalizedEntities,
*
* @param {function} fetchFunction - the function used to retrieve the comments
*/
-export const loadComments = function(fetchFunction) {
- return function(dispatch) {
- return fetchFunction(dispatch).then(normalizedComments => {
- dispatch(receiveComments(normalizedComments.entities))
- return normalizedComments
- })
- }
-}
+export const loadComments = function (fetchFunction) {
+ return function (dispatch) {
+ return fetchFunction(dispatch).then((normalizedComments) => {
+ dispatch(receiveComments(normalizedComments.entities));
+ return normalizedComments;
+ });
+ };
+};
// redux reducers
-export const commentEntities =
- genericEntityReducer(RECEIVE_COMMENTS, 'comments')
+export const commentEntities = genericEntityReducer(RECEIVE_COMMENTS, "comments");
diff --git a/src/services/Comment/CommentType.js b/src/services/Comment/CommentType.js
index dc4a2af88..4ad371b39 100644
--- a/src/services/Comment/CommentType.js
+++ b/src/services/Comment/CommentType.js
@@ -1,6 +1,6 @@
const CommentType = {
- TASK: 'TASK',
- CHALLENGE: 'CHALLENGE'
-}
+ TASK: "TASK",
+ CHALLENGE: "CHALLENGE",
+};
export default CommentType;
diff --git a/src/services/Editor/Editor.js b/src/services/Editor/Editor.js
index f47b667bf..546397d51 100644
--- a/src/services/Editor/Editor.js
+++ b/src/services/Editor/Editor.js
@@ -1,24 +1,24 @@
import geoViewport from "@mapbox/geo-viewport";
import _compact from "lodash/compact";
-import _fromPairs from "lodash/fromPairs";
-import _map from "lodash/map";
-import _find from "lodash/find";
import _filter from "lodash/filter";
+import _find from "lodash/find";
+import _fromPairs from "lodash/fromPairs";
import _invert from "lodash/invert";
import _isArray from "lodash/isArray";
import _isEmpty from "lodash/isEmpty";
import _isFinite from "lodash/isFinite";
+import _map from "lodash/map";
import _snakeCase from "lodash/snakeCase";
-import RequestStatus from "../Server/RequestStatus";
+import AsCooperativeWork from "../../interactions/Task/AsCooperativeWork";
import AsMappableTask from "../../interactions/Task/AsMappableTask";
import AsMappableBundle from "../../interactions/TaskBundle/AsMappableBundle";
import AsIdentifiableFeature from "../../interactions/TaskFeature/AsIdentifiableFeature";
-import AsCooperativeWork from "../../interactions/Task/AsCooperativeWork";
-import { toLatLngBounds } from "../MapBounds/MapBounds";
-import { addError } from "../Error/Error";
+import { constructChangesetUrl } from "../../utils/constructChangesetUrl";
import AppErrors from "../Error/AppErrors";
+import { addError } from "../Error/Error";
+import { toLatLngBounds } from "../MapBounds/MapBounds";
+import RequestStatus from "../Server/RequestStatus";
import messages from "./Messages";
-import { constructChangesetUrl } from "../../utils/constructChangesetUrl";
// Editor option constants based on constants defined on server
export const NONE = -1;
@@ -49,20 +49,14 @@ export const keysByEditor = Object.freeze(_invert(Editor));
/** Returns object containing localized labels */
export const editorLabels = (intl) =>
- _fromPairs(
- _map(messages, (message, key) => [key, intl.formatMessage(message)])
- );
+ _fromPairs(_map(messages, (message, key) => [key, intl.formatMessage(message)]));
// redux actions
export const EDITOR_OPENED = "EditorOpened";
export const EDITOR_CLOSED = "EditorClosed";
// redux action creators
-export const editorOpened = function (
- editor,
- taskId,
- status = RequestStatus.success
-) {
+export const editorOpened = function (editor, taskId, status = RequestStatus.success) {
return {
type: EDITOR_OPENED,
editor,
@@ -78,7 +72,7 @@ export const editTask = function (
mapBounds,
options,
taskBundle,
- replacedComment = null
+ replacedComment = null,
) {
return function (dispatch) {
if (options && window.env.REACT_APP_FEATURE_EDITOR_IMAGERY !== "enabled") {
@@ -95,15 +89,15 @@ export const editTask = function (
if (editor === ID) {
editorWindowReference = window.open(
- constructIdURI(task, mapBounds, options, taskBundle, replacedComment)
+ constructIdURI(task, mapBounds, options, taskBundle, replacedComment),
);
} else if (editor === LEVEL0) {
editorWindowReference = window.open(
- constructLevel0URI(task, mapBounds, options, taskBundle, replacedComment)
+ constructLevel0URI(task, mapBounds, options, taskBundle, replacedComment),
);
} else if (editor === RAPID) {
editorWindowReference = window.open(
- constructRapidURI(task, mapBounds, options, replacedComment)
+ constructRapidURI(task, mapBounds, options, replacedComment),
);
}
@@ -119,68 +113,30 @@ export const editTask = function (
// calls to JOSM, with a bit of a delay to give JOSM the chance to load
// the object before we try to zoom
josmCommands = josmCommands.concat([
- () =>
- openJOSM(
- dispatch,
- editor,
- task,
- mapBounds,
- josmLoadObjectURI,
- taskBundle
- ),
+ () => openJOSM(dispatch, editor, task, mapBounds, josmLoadObjectURI, taskBundle),
() => sendJOSMCommand(josmZoomURI(task, mapBounds)),
]);
} else if (AsCooperativeWork(task).isChangeFileType()) {
// Cooperative fix with XML change
josmCommands = josmCommands.concat([
() =>
- openJOSM(
- dispatch,
- editor,
- task,
- mapBounds,
- josmLoadAndZoomURI,
- taskBundle,
- {
- layerName:
- editor === JOSM_LAYER
- ? `MR Task ${task.id} OSM Data` // layers will be separate
- : `MR Task ${task.id} Changes`, // layers will be merged
- }
- ),
+ openJOSM(dispatch, editor, task, mapBounds, josmLoadAndZoomURI, taskBundle, {
+ layerName:
+ editor === JOSM_LAYER
+ ? `MR Task ${task.id} OSM Data` // layers will be separate
+ : `MR Task ${task.id} Changes`, // layers will be merged
+ }),
() =>
- sendJOSMCommand(
- josmImportCooperative(
- dispatch,
- editor,
- task,
- mapBounds,
- taskBundle
- )
- ),
+ sendJOSMCommand(josmImportCooperative(dispatch, editor, task, mapBounds, taskBundle)),
]);
} else {
josmCommands = josmCommands.concat([
- () =>
- openJOSM(
- dispatch,
- editor,
- task,
- mapBounds,
- josmLoadAndZoomURI,
- taskBundle
- ),
+ () => openJOSM(dispatch, editor, task, mapBounds, josmLoadAndZoomURI, taskBundle),
]);
const loadReferenceLayerCommands = _map(
- josmImportReferenceLayers(
- dispatch,
- editor,
- task,
- mapBounds,
- taskBundle
- ),
- (command) => () => sendJOSMCommand(command)
+ josmImportReferenceLayers(dispatch, editor, task, mapBounds, taskBundle),
+ (command) => () => sendJOSMCommand(command),
);
if (loadReferenceLayerCommands.length > 0) {
josmCommands = josmCommands.concat(loadReferenceLayerCommands);
@@ -188,9 +144,7 @@ export const editTask = function (
}
if (options?.imagery) {
- josmCommands.push(() =>
- sendJOSMCommand(josmImageryURI(options.imagery))
- );
+ josmCommands.push(() => sendJOSMCommand(josmImageryURI(options.imagery)));
}
executeJOSMBatch(josmCommands);
@@ -260,28 +214,16 @@ export const constructIdURI = function (task, mapBounds, options, taskBundle, re
const baseUriComponent = `${window.env.REACT_APP_ID_EDITOR_SERVER_URL}?editor=id`;
const centerPoint = taskCenterPoint(mapBounds, task, taskBundle);
- const mapUriComponent =
- "map=" + [mapBounds.zoom, centerPoint.lat, centerPoint.lng].join("/");
+ const mapUriComponent = "map=" + [mapBounds.zoom, centerPoint.lat, centerPoint.lng].join("/");
// iD only supports a single selected entity, so don't bother passing bundle
- const selectedEntityComponent = osmObjectParams(
- task,
- false,
- "=",
- "&",
- options
- );
+ const selectedEntityComponent = osmObjectParams(task, false, "=", "&", options);
- const commentUriComponent = replacedComment ?
- "comment=" +
- encodeURIComponent(replacedComment) +
- constructChangesetUrl(task) :
- "comment=" +
- encodeURIComponent(task.parent?.checkinComment) +
- constructChangesetUrl(task);
+ const commentUriComponent = replacedComment
+ ? "comment=" + encodeURIComponent(replacedComment) + constructChangesetUrl(task)
+ : "comment=" + encodeURIComponent(task.parent?.checkinComment) + constructChangesetUrl(task);
- const sourceComponent =
- "source=" + encodeURIComponent(task.parent.checkinSource);
+ const sourceComponent = "source=" + encodeURIComponent(task.parent.checkinSource);
const presetsComponent = _isEmpty(task.parent.presets)
? null
@@ -323,21 +265,15 @@ export const constructIdURI = function (task, mapBounds, options, taskBundle, re
export const constructRapidURI = function (task, mapBounds, options, replacedComment) {
const baseUriComponent = `${window.env.REACT_APP_RAPID_EDITOR_SERVER_URL}#`;
const centerPoint = taskCenterPoint(mapBounds, task);
- const mapUriComponent =
- "map=" + [mapBounds.zoom, centerPoint.lat, centerPoint.lng].join("/");
+ const mapUriComponent = "map=" + [mapBounds.zoom, centerPoint.lat, centerPoint.lng].join("/");
const selectedEntityComponent = "id=" + osmObjectParams(task, true);
- const commentUriComponent = replacedComment ?
- "comment=" +
- encodeURIComponent(replacedComment) +
- constructChangesetUrl(task) :
- "comment=" +
- encodeURIComponent(task.parent?.checkinComment) +
- constructChangesetUrl(task);
+ const commentUriComponent = replacedComment
+ ? "comment=" + encodeURIComponent(replacedComment) + constructChangesetUrl(task)
+ : "comment=" + encodeURIComponent(task.parent?.checkinComment) + constructChangesetUrl(task);
- const sourceComponent =
- "source=" + encodeURIComponent(task.parent.checkinSource);
+ const sourceComponent = "source=" + encodeURIComponent(task.parent.checkinSource);
const datasetUrl = task.parent?.datasetUrl
? "data=" + encodeURIComponent(task.parent.datasetUrl)
@@ -369,7 +305,7 @@ export const constructRapidURI = function (task, mapBounds, options, replacedCom
presetsComponent,
photoOverlayComponent,
mapUriComponent,
- datasetUrl
+ datasetUrl,
]).join("&")
);
};
@@ -377,33 +313,19 @@ export const constructRapidURI = function (task, mapBounds, options, replacedCom
/**
* Builds a Level0 editor URI for editing of the given task
*/
-export const constructLevel0URI = function (
- task,
- mapBounds,
- options,
- taskBundle,
- replacedComment
-) {
+export const constructLevel0URI = function (task, mapBounds, options, taskBundle, replacedComment) {
const baseUriComponent = `${window.env.REACT_APP_LEVEL0_EDITOR_SERVER_URL}?`;
const centerPoint = taskCenterPoint(mapBounds, task, taskBundle);
- const mapCenterComponent =
- "center=" + [centerPoint.lat, centerPoint.lng].join(",");
+ const mapCenterComponent = "center=" + [centerPoint.lat, centerPoint.lng].join(",");
- const commentComponent = replacedComment ?
- "comment=" +
- encodeURIComponent(replacedComment) +
- constructChangesetUrl(task) :
- "comment=" +
- encodeURIComponent(task.parent?.checkinComment) +
- constructChangesetUrl(task);
+ const commentComponent = replacedComment
+ ? "comment=" + encodeURIComponent(replacedComment) + constructChangesetUrl(task)
+ : "comment=" + encodeURIComponent(task.parent?.checkinComment) + constructChangesetUrl(task);
- const urlComponent =
- "url=" + osmObjectParams(taskBundle?.tasks ?? task, true);
+ const urlComponent = "url=" + osmObjectParams(taskBundle?.tasks ?? task, true);
- const result =
- baseUriComponent +
- [mapCenterComponent, commentComponent, urlComponent].join("&");
+ const result = baseUriComponent + [mapCenterComponent, commentComponent, urlComponent].join("&");
return result;
};
@@ -413,10 +335,10 @@ export const constructLevel0URI = function (
* missing osm ids are skipped, and an empty string is returned if the task has
* no features or none of its features have osm ids
*
- * Osm types will be extracted from osm id properties("@id", "osmid", "osmIdentifier", "id") if defined
- * to allow for customization from user
+ * Osm types will be extracted from osm id properties("@id", "osmid", "osmIdentifier", "id") if defined
+ * to allow for customization from user
* if there is no osm type defined in osm id properties, it will be generated based on geometry type
- *
+ *
* To support varying formats required by different editors, the output string
* can optionally be customized with options that control whether the entity
* type is abbreviated or not, a separator character to be used between
@@ -427,7 +349,7 @@ export const osmObjectParams = function (
task,
abbreviated = false,
entitySeparator = "",
- joinSeparator = ","
+ joinSeparator = ",",
) {
const allTasks = _isArray(task) ? task : [task];
let objects = [];
@@ -436,7 +358,7 @@ export const osmObjectParams = function (
objects = objects.concat(
_compact(
task.geometries.features.map((feature) => {
- const currentFeature = AsIdentifiableFeature(feature)
+ const currentFeature = AsIdentifiableFeature(feature);
const osmId = currentFeature.osmId();
const osmType = currentFeature.osmType();
let areAllTypesValid;
@@ -447,40 +369,32 @@ export const osmObjectParams = function (
if (osmType) {
areAllTypesValid = currentFeature.checkValidTypeMultipleIds(osmType);
}
- // We will use osm types defined by user if they exist and are consistent, if not fall back to geometry type
+ // We will use osm types defined by user if they exist and are consistent, if not fall back to geometry type
if (osmType && areAllTypesValid) {
- switch(osmType) {
+ switch (osmType) {
case "node":
- return `${
- abbreviated ? "n" : "node"
- }${entitySeparator}${osmId}`;
+ return `${abbreviated ? "n" : "node"}${entitySeparator}${osmId}`;
case "way":
return `${abbreviated ? "w" : "way"}${entitySeparator}${osmId}`;
case "relation":
- return `${
- abbreviated ? "r" : "relation"
- }${entitySeparator}${osmId}`;
+ return `${abbreviated ? "r" : "relation"}${entitySeparator}${osmId}`;
}
}
switch (feature.geometry.type) {
case "Point":
- return `${
- abbreviated ? "n" : "node"
- }${entitySeparator}${osmId}`;
+ return `${abbreviated ? "n" : "node"}${entitySeparator}${osmId}`;
case "LineString":
case "Polygon":
return `${abbreviated ? "w" : "way"}${entitySeparator}${osmId}`;
case "MultiPolygon":
case "GeometryCollection":
- return `${
- abbreviated ? "r" : "relation"
- }${entitySeparator}${osmId}`;
+ return `${abbreviated ? "r" : "relation"}${entitySeparator}${osmId}`;
default:
return null;
}
- })
- )
+ }),
+ ),
);
}
});
@@ -500,11 +414,7 @@ export const josmHost = function () {
* mapBounds, if they match the task, or else the computed bbox from the task
* itself
*/
-export const josmBoundsParams = function (
- task,
- mapBounds,
- taskBundle
-) {
+export const josmBoundsParams = function (task, mapBounds, taskBundle) {
let bounds = null;
if (taskBundle) {
// For task bundles, we currently ignore map bounds
@@ -525,18 +435,13 @@ export const josmBoundsParams = function (
* Generate appropriate JOSM editor URI layer params for setting up a new layer, if
* desired, as well as naming the layer
*/
-export const josmLayerParams = function (
- task,
- asNewLayer,
- taskBundle,
- options = {}
-) {
+export const josmLayerParams = function (task, asNewLayer, taskBundle, options = {}) {
const newLayer = asNewLayer ? "true" : "false";
const layerName = options.layerName
? options.layerName
: taskBundle
- ? `MR Bundle ${task.id} (${taskBundle.tasks.length} tasks)`
- : `MR Task ${task.id}`;
+ ? `MR Bundle ${task.id} (${taskBundle.tasks.length} tasks)`
+ : `MR Task ${task.id}`;
return `new_layer=${newLayer}&layer_name=${encodeURIComponent(layerName)}`;
};
@@ -569,9 +474,7 @@ export const josmImageryURI = function (imagery) {
imagery.attribution
? `attribution-text=${encodeURIComponent(imagery.attribution.text)}`
: null,
- imagery.attribution
- ? `attribution-url=${encodeURIComponent(imagery.attribution.url)}`
- : null,
+ imagery.attribution ? `attribution-url=${encodeURIComponent(imagery.attribution.url)}` : null,
_isFinite(imagery.max_zoom) ? `max_zoom=${imagery.max_zoom}` : null,
`url=${encodeURIComponent(imagery.url)}`, // must come last per JOSM docs
]).join("&")
@@ -589,16 +492,18 @@ export const josmLoadAndZoomURI = function (
task,
mapBounds,
taskBundle,
- options
+ options,
) {
- return josmHost() +
- "load_and_zoom?" +
- [
- josmBoundsParams(task, mapBounds, taskBundle, options),
- josmLayerParams(task, editor === JOSM_LAYER, taskBundle, options),
- josmChangesetParams(task, options),
- `select=${osmObjectParams(taskBundle?.tasks ?? task, options)}`,
- ].join("&");
+ return (
+ josmHost() +
+ "load_and_zoom?" +
+ [
+ josmBoundsParams(task, mapBounds, taskBundle, options),
+ josmLayerParams(task, editor === JOSM_LAYER, taskBundle, options),
+ josmChangesetParams(task, options),
+ `select=${osmObjectParams(taskBundle?.tasks ?? task, options)}`,
+ ].join("&")
+ );
};
/*
@@ -617,14 +522,7 @@ export const josmZoomURI = function (task, mapBounds, options) {
*
* @see See https://josm.openstreetmap.de/wiki/Help/RemoteControlCommands#load_object
*/
-export const josmLoadObjectURI = function (
- dispatch,
- editor,
- task,
- mapBounds,
- taskBundle,
- options
-) {
+export const josmLoadObjectURI = function (dispatch, editor, task, mapBounds, taskBundle, options) {
const objects = osmObjectParams(taskBundle?.tasks ?? task, options);
// We can't load objects if there are none. This is usually because the
@@ -660,17 +558,15 @@ export const josmImportURI = function (
mapBounds,
taskBundle,
uri,
- options = {}
+ options = {},
) {
return (
josmHost() +
"import?" +
[
- `new_layer=${
- editor === JOSM_LAYER || options.asNewLayer ? "true" : "false"
- }`,
+ `new_layer=${editor === JOSM_LAYER || options.asNewLayer ? "true" : "false"}`,
`layer_name=${encodeURIComponent(
- options.layerName ? options.layerName : "MR Task " + task.id
+ options.layerName ? options.layerName : "MR Task " + task.id,
)}`,
`layer_locked=${options.layerLocked ? "true" : "false"}`,
`download_policy=${options.downloadPolicy || ""}`,
@@ -683,13 +579,7 @@ export const josmImportURI = function (
/*
* Builds a JOSM import URI suitable for pulling in cooperative work
*/
-export const josmImportCooperative = function (
- dispatch,
- editor,
- task,
- mapBounds,
- taskBundle
-) {
+export const josmImportCooperative = function (dispatch, editor, task, mapBounds, taskBundle) {
return josmImportURI(
dispatch,
editor,
@@ -697,23 +587,17 @@ export const josmImportCooperative = function (
mapBounds,
taskBundle,
`${window.env.REACT_APP_MAP_ROULETTE_SERVER_URL}/api/v2/task/${task.id}/cooperative/change/task_${task.id}_change.osc`,
- { layerName: `MR Task ${task.id} Changes` }
+ { layerName: `MR Task ${task.id} Changes` },
);
};
/*
* Builds and returns JOSM import URIs for each reference layer attached to the given task
*/
-export const josmImportReferenceLayers = function (
- dispatch,
- editor,
- task,
- mapBounds,
- taskBundle
-) {
+export const josmImportReferenceLayers = function (dispatch, editor, task, mapBounds, taskBundle) {
const referenceLayers = _filter(
task?.geometries?.attachments ?? [],
- (attachment) => attachment.kind === "referenceLayer"
+ (attachment) => attachment.kind === "referenceLayer",
);
return _map(referenceLayers, (layer) => {
@@ -734,7 +618,7 @@ export const josmImportReferenceLayers = function (
layerLocked: layer?.settings?.layerLocked ?? true,
uploadPolicy: layer?.settings?.uploadPolicy ?? "never",
downloadPolicy: layer?.settings?.downloadPolicy ?? "never",
- }
+ },
);
});
};
@@ -803,16 +687,9 @@ const openJOSM = function (
mapBounds,
josmURIFunction,
taskBundle,
- options
+ options,
) {
- const uri = josmURIFunction(
- dispatch,
- editor,
- task,
- mapBounds,
- taskBundle,
- options
- );
+ const uri = josmURIFunction(dispatch, editor, task, mapBounds, taskBundle, options);
if (!uri) {
return Promise.resolve();
}
@@ -853,13 +730,7 @@ export const zoomJOSM = function (bbox) {
/**
* Computes a bbox from the given zoom, lat, lon, and target viewport size
*/
-export const viewportToBBox = function (
- zoom,
- lat,
- lon,
- viewportWidth,
- viewportHeight
-) {
+export const viewportToBBox = function (zoom, lat, lon, viewportWidth, viewportHeight) {
return geoViewport.bounds([lon, lat], zoom, [viewportWidth, viewportHeight]);
};
diff --git a/src/services/Editor/Editor.test.js b/src/services/Editor/Editor.test.js
index 808f45528..5fbb7fb44 100644
--- a/src/services/Editor/Editor.test.js
+++ b/src/services/Editor/Editor.test.js
@@ -1,43 +1,45 @@
+import _cloneDeep from "lodash/cloneDeep";
import { describe, expect, vi } from "vitest";
-import { Editor,
- osmObjectParams,
- josmLoadAndZoomURI,
- constructIdURI,
- constructLevel0URI,
- constructRapidURI } from './Editor'
-import _cloneDeep from 'lodash/cloneDeep'
-
-let dispatch = null
-let task = null
-let basicFeature = null
-let pointFeature = null
-let lineStringFeature = null
-let polygonFeature = null
-let multiPolygonFeature = null
-let taskGeometries = null
-let challenge = null
-let southWestCorner = null
-let northEastCorner = null
-let centerPoint = null
-let mapBounds = null
+import {
+ Editor,
+ constructIdURI,
+ constructLevel0URI,
+ constructRapidURI,
+ josmLoadAndZoomURI,
+ osmObjectParams,
+} from "./Editor";
+
+let dispatch = null;
+let task = null;
+let basicFeature = null;
+let pointFeature = null;
+let lineStringFeature = null;
+let polygonFeature = null;
+let multiPolygonFeature = null;
+let taskGeometries = null;
+let challenge = null;
+let southWestCorner = null;
+let northEastCorner = null;
+let centerPoint = null;
+let mapBounds = null;
beforeEach(() => {
- dispatch = vi.fn()
+ dispatch = vi.fn();
basicFeature = {
- type: 'Feature',
+ type: "Feature",
properties: {
firstTag: "foo",
secondTag: "bar",
- }
- }
+ },
+ };
pointFeature = Object.assign(_cloneDeep(basicFeature), {
geometry: {
type: "Point",
coordinates: [102.0, 0.5],
- }
- })
+ },
+ });
lineStringFeature = Object.assign(_cloneDeep(basicFeature), {
geometry: {
@@ -48,8 +50,8 @@ beforeEach(() => {
[104.0, 0.0],
[105.0, 1.0],
],
- }
- })
+ },
+ });
polygonFeature = Object.assign(_cloneDeep(basicFeature), {
geometry: {
@@ -61,10 +63,10 @@ beforeEach(() => {
[101.0, 1.0],
[100.0, 1.0],
[100.0, 0.0],
- ]
- ]
- }
- })
+ ],
+ ],
+ },
+ });
multiPolygonFeature = Object.assign(_cloneDeep(basicFeature), {
geometry: {
@@ -72,39 +74,43 @@ beforeEach(() => {
coordinates: [
[
[
- [180.0, 40.0], [180.0, 50.0], [170.0, 50.0],
- [170.0, 40.0], [180.0, 40.0],
- ]
+ [180.0, 40.0],
+ [180.0, 50.0],
+ [170.0, 50.0],
+ [170.0, 40.0],
+ [180.0, 40.0],
+ ],
],
[
[
- [-170.0, 40.0], [-170.0, 50.0], [-180.0, 50.0],
- [-180.0, 40.0], [-170.0, 40.0],
- ]
+ [-170.0, 40.0],
+ [-170.0, 50.0],
+ [-180.0, 50.0],
+ [-180.0, 40.0],
+ [-170.0, 40.0],
+ ],
],
- ]
- }
- })
+ ],
+ },
+ });
challenge = {
checkinComment: "My checkin comment",
checkinSource: "My source",
- }
+ };
taskGeometries = {
- features: [
- pointFeature,
- ],
- }
+ features: [pointFeature],
+ };
task = {
parent: challenge,
geometries: taskGeometries,
- }
+ };
- southWestCorner = {lng: 90, lat: -10}
- northEastCorner = {lng: 110, lat: 10}
- centerPoint = {lng: 100, lat: 0}
+ southWestCorner = { lng: 90, lat: -10 };
+ northEastCorner = { lng: 110, lat: 10 };
+ centerPoint = { lng: 100, lat: 0 };
mapBounds = {
bounds: {
@@ -113,455 +119,385 @@ beforeEach(() => {
getCenter: vi.fn(() => centerPoint),
},
zoom: 17,
- }
-})
+ };
+});
-
-describe('osmObjectParams', () => {
+describe("osmObjectParams", () => {
beforeEach(() => {
- pointFeature.properties.osmid = '123'
- lineStringFeature.properties.osmid = '456'
- polygonFeature.properties.osmid='789'
- multiPolygonFeature.properties.osmid='246'
+ pointFeature.properties.osmid = "123";
+ lineStringFeature.properties.osmid = "456";
+ polygonFeature.properties.osmid = "789";
+ multiPolygonFeature.properties.osmid = "246";
taskGeometries.features = [
- pointFeature, lineStringFeature, polygonFeature, multiPolygonFeature
- ]
- })
+ pointFeature,
+ lineStringFeature,
+ polygonFeature,
+ multiPolygonFeature,
+ ];
+ });
test("it builds a comma-separated string of task feature osm identifiers", () => {
- expect(osmObjectParams(task)).toEqual('node123,way456,way789,relation246')
- })
+ expect(osmObjectParams(task)).toEqual("node123,way456,way789,relation246");
+ });
test("it abbreviates objects when given abbreviated argument", () => {
- expect(osmObjectParams(task, true)).toEqual('n123,w456,w789,r246')
- })
+ expect(osmObjectParams(task, true)).toEqual("n123,w456,w789,r246");
+ });
test("it skips task features missing osm identifiers", () => {
- delete pointFeature.properties.osmid
+ delete pointFeature.properties.osmid;
- expect(osmObjectParams(task)).toEqual('way456,way789,relation246')
- })
+ expect(osmObjectParams(task)).toEqual("way456,way789,relation246");
+ });
test("it returns an empty string if there are no features", () => {
- taskGeometries.features = []
+ taskGeometries.features = [];
- expect(osmObjectParams(task)).toEqual('')
- })
+ expect(osmObjectParams(task)).toEqual("");
+ });
test("it returns an empty string if no features osm ids", () => {
- delete pointFeature.properties.osmid
- delete lineStringFeature.properties.osmid
- delete polygonFeature.properties.osmid
- delete multiPolygonFeature.properties.osmid
+ delete pointFeature.properties.osmid;
+ delete lineStringFeature.properties.osmid;
+ delete polygonFeature.properties.osmid;
+ delete multiPolygonFeature.properties.osmid;
- expect(osmObjectParams(task)).toEqual('')
- })
-})
+ expect(osmObjectParams(task)).toEqual("");
+ });
+});
-
-describe('josmLoadAndZoomURI', () => {
+describe("josmLoadAndZoomURI", () => {
test("the uri includes the load_and_zoom command", () => {
- const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds)
- expect(uri).toEqual(expect.stringContaining("load_and_zoom"))
- })
+ const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds);
+ expect(uri).toEqual(expect.stringContaining("load_and_zoom"));
+ });
test("the uri includes bounding box corners from mapbounds", () => {
- const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds)
+ const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds);
- expect(uri).toEqual(expect.stringContaining(`left=${southWestCorner.lng}`))
- expect(uri).toEqual(expect.stringContaining(`bottom=${southWestCorner.lat}`))
- expect(uri).toEqual(expect.stringContaining(`right=${northEastCorner.lng}`))
- expect(uri).toEqual(expect.stringContaining(`top=${northEastCorner.lat}`))
- })
+ expect(uri).toEqual(expect.stringContaining(`left=${southWestCorner.lng}`));
+ expect(uri).toEqual(expect.stringContaining(`bottom=${southWestCorner.lat}`));
+ expect(uri).toEqual(expect.stringContaining(`right=${northEastCorner.lng}`));
+ expect(uri).toEqual(expect.stringContaining(`top=${northEastCorner.lat}`));
+ });
test("sets new_layer to false for standard josm editor option", () => {
- const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds)
+ const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds);
- expect(uri).toEqual(expect.stringContaining("new_layer=false"))
- })
+ expect(uri).toEqual(expect.stringContaining("new_layer=false"));
+ });
test("sets new_layer to true for josm w/layer editor option", () => {
- const uri = josmLoadAndZoomURI(dispatch, Editor.josmLayer, task, mapBounds)
+ const uri = josmLoadAndZoomURI(dispatch, Editor.josmLayer, task, mapBounds);
- expect(uri).toEqual(expect.stringContaining("new_layer=true"))
- })
+ expect(uri).toEqual(expect.stringContaining("new_layer=true"));
+ });
test("uri includes a URI-encoded checkin comment from the task challenge", () => {
- challenge.checkinComment = "###Non-Conforming"
- const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds)
+ challenge.checkinComment = "###Non-Conforming";
+ const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds);
- expect(uri).toEqual(expect.stringContaining("Conforming"))
- expect(uri).not.toEqual(expect.stringContaining("#"))
- })
+ expect(uri).toEqual(expect.stringContaining("Conforming"));
+ expect(uri).not.toEqual(expect.stringContaining("#"));
+ });
test("uri includes a URI-encoded source from the task challenge", () => {
- const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds)
+ const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds);
- expect(uri).toEqual(expect.stringContaining("My%20source"))
- })
+ expect(uri).toEqual(expect.stringContaining("My%20source"));
+ });
test("uri includes a node selection for Point features with an OSM id", () => {
- pointFeature.properties.osmid = '123'
- taskGeometries.features = [ pointFeature ]
- const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds)
+ pointFeature.properties.osmid = "123";
+ taskGeometries.features = [pointFeature];
+ const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`select=node123`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`select=node123`));
+ });
test("uri includes a way selection for LineString features with an OSM id", () => {
- lineStringFeature.properties.osmid = '456'
- taskGeometries.features = [ lineStringFeature ]
- const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds)
+ lineStringFeature.properties.osmid = "456";
+ taskGeometries.features = [lineStringFeature];
+ const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`select=way456`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`select=way456`));
+ });
test("uri includes a way selection for Polygon features with an OSM id", () => {
- polygonFeature.properties.osmid = '789'
- taskGeometries.features = [ polygonFeature ]
- const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds)
+ polygonFeature.properties.osmid = "789";
+ taskGeometries.features = [polygonFeature];
+ const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`select=way789`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`select=way789`));
+ });
test("uri includes a relation selection for MultiPolygon features with an OSM id", () => {
- multiPolygonFeature.properties.osmid = '135'
- taskGeometries.features = [ multiPolygonFeature ]
- const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds)
+ multiPolygonFeature.properties.osmid = "135";
+ taskGeometries.features = [multiPolygonFeature];
+ const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`select=relation135`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`select=relation135`));
+ });
test("features lacking an OSM id are not selected", () => {
- delete pointFeature.properties.osmid
- taskGeometries.features = [ pointFeature ]
- const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds)
+ delete pointFeature.properties.osmid;
+ taskGeometries.features = [pointFeature];
+ const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds);
- expect(uri).not.toEqual(
- expect.stringContaining('select=node')
- )
- })
+ expect(uri).not.toEqual(expect.stringContaining("select=node"));
+ });
test("features using the alternate @id property are still selected", () => {
- pointFeature.properties['@id'] = '123'
- taskGeometries.features = [ pointFeature ]
- const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds)
+ pointFeature.properties["@id"] = "123";
+ taskGeometries.features = [pointFeature];
+ const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`select=node123`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`select=node123`));
+ });
test("multiple features are comma-separated in the selection", () => {
- pointFeature.properties.osmid = '123'
- lineStringFeature.properties.osmid = '456'
- multiPolygonFeature.properties.osmid = '135'
- taskGeometries.features = [ pointFeature, lineStringFeature, multiPolygonFeature ]
- const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds)
+ pointFeature.properties.osmid = "123";
+ lineStringFeature.properties.osmid = "456";
+ multiPolygonFeature.properties.osmid = "135";
+ taskGeometries.features = [pointFeature, lineStringFeature, multiPolygonFeature];
+ const uri = josmLoadAndZoomURI(dispatch, Editor.josm, task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`select=node123,way456,relation135`)
- )
- })
-})
+ expect(uri).toEqual(expect.stringContaining(`select=node123,way456,relation135`));
+ });
+});
-describe('constructIdURI', () => {
+describe("constructIdURI", () => {
test("the uri specifies the Id editor", () => {
- const uri = constructIdURI(task, mapBounds)
- expect(uri).toEqual(expect.stringContaining("editor=id#"))
- })
+ const uri = constructIdURI(task, mapBounds);
+ expect(uri).toEqual(expect.stringContaining("editor=id#"));
+ });
test("the uri includes slash-separated centerpoint and zoom from mapbounds", () => {
- const uri = constructIdURI(task, mapBounds)
+ const uri = constructIdURI(task, mapBounds);
expect(uri).toEqual(
- expect.stringContaining(`map=${mapBounds.zoom}/${centerPoint.lat}/${centerPoint.lng}`)
- )
- })
+ expect.stringContaining(`map=${mapBounds.zoom}/${centerPoint.lat}/${centerPoint.lng}`),
+ );
+ });
test("uri includes a URI-encoded checkin comment from the task challenge", () => {
- const uri = constructIdURI(task, mapBounds)
+ const uri = constructIdURI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(
- `comment=${encodeURI(challenge.checkinComment)}`
- )
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`comment=${encodeURI(challenge.checkinComment)}`));
+ });
test("uri includes a node id for Point features with an OSM id", () => {
- pointFeature.properties.osmid = '123'
- taskGeometries.features = [ pointFeature ]
- const uri = constructIdURI(task, mapBounds)
+ pointFeature.properties.osmid = "123";
+ taskGeometries.features = [pointFeature];
+ const uri = constructIdURI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`node=123`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`node=123`));
+ });
test("uri includes a way id for LineString features with an OSM id", () => {
- lineStringFeature.properties.osmid = '456'
- taskGeometries.features = [ lineStringFeature ]
- const uri = constructIdURI(task, mapBounds)
+ lineStringFeature.properties.osmid = "456";
+ taskGeometries.features = [lineStringFeature];
+ const uri = constructIdURI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`way=456`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`way=456`));
+ });
test("uri includes a way id for Polygon features with an OSM id", () => {
- polygonFeature.properties.osmid = '789'
- taskGeometries.features = [ polygonFeature ]
- const uri = constructIdURI(task, mapBounds)
+ polygonFeature.properties.osmid = "789";
+ taskGeometries.features = [polygonFeature];
+ const uri = constructIdURI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`way=789`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`way=789`));
+ });
test("uri includes a relation id for MultiPolygon features with an OSM id", () => {
- multiPolygonFeature.properties.osmid = '135'
- taskGeometries.features = [ multiPolygonFeature ]
- const uri = constructIdURI(task, mapBounds)
+ multiPolygonFeature.properties.osmid = "135";
+ taskGeometries.features = [multiPolygonFeature];
+ const uri = constructIdURI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`relation=135`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`relation=135`));
+ });
test("features lacking an OSM id are not selected", () => {
- delete pointFeature.properties.osmid
- taskGeometries.features = [ pointFeature ]
- const uri = constructIdURI(task, mapBounds)
+ delete pointFeature.properties.osmid;
+ taskGeometries.features = [pointFeature];
+ const uri = constructIdURI(task, mapBounds);
- expect(uri).not.toEqual(
- expect.stringContaining('node=')
- )
- })
+ expect(uri).not.toEqual(expect.stringContaining("node="));
+ });
test("features using the alternate @id property are still identified", () => {
- pointFeature.properties['@id'] = '123'
- taskGeometries.features = [ pointFeature ]
- const uri = constructIdURI(task, mapBounds)
+ pointFeature.properties["@id"] = "123";
+ taskGeometries.features = [pointFeature];
+ const uri = constructIdURI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`node=123`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`node=123`));
+ });
test("multiple features are comma-separated in the id", () => {
- pointFeature.properties.osmid = '123'
- lineStringFeature.properties.osmid = '456'
- multiPolygonFeature.properties.osmid = '135'
- taskGeometries.features = [ pointFeature, lineStringFeature, multiPolygonFeature ]
- const uri = constructIdURI(task, mapBounds)
+ pointFeature.properties.osmid = "123";
+ lineStringFeature.properties.osmid = "456";
+ multiPolygonFeature.properties.osmid = "135";
+ taskGeometries.features = [pointFeature, lineStringFeature, multiPolygonFeature];
+ const uri = constructIdURI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`node=123&way=456&relation=135`)
- )
- })
-})
+ expect(uri).toEqual(expect.stringContaining(`node=123&way=456&relation=135`));
+ });
+});
-describe('constructRapidURI', () => {
+describe("constructRapidURI", () => {
test("the uri specifies the RapiD editor", () => {
- const uri = constructRapidURI(task, mapBounds)
- expect(uri).toEqual(expect.stringContaining("rapid"))
- })
+ const uri = constructRapidURI(task, mapBounds);
+ expect(uri).toEqual(expect.stringContaining("rapid"));
+ });
test("the uri includes slash-separated centerpoint and zoom from mapbounds", () => {
- const uri = constructRapidURI(task, mapBounds)
+ const uri = constructRapidURI(task, mapBounds);
expect(uri).toEqual(
- expect.stringContaining(`map=${mapBounds.zoom}/${centerPoint.lat}/${centerPoint.lng}`)
- )
- })
+ expect.stringContaining(`map=${mapBounds.zoom}/${centerPoint.lat}/${centerPoint.lng}`),
+ );
+ });
test("uri includes a URI-encoded checkin comment from the task challenge", () => {
- const uri = constructRapidURI(task, mapBounds)
+ const uri = constructRapidURI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(
- `comment=${encodeURI(challenge.checkinComment)}`
- )
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`comment=${encodeURI(challenge.checkinComment)}`));
+ });
test("uri includes a node id for Point features with an OSM id", () => {
- pointFeature.properties.osmid = '123'
- taskGeometries.features = [ pointFeature ]
- const uri = constructRapidURI(task, mapBounds)
+ pointFeature.properties.osmid = "123";
+ taskGeometries.features = [pointFeature];
+ const uri = constructRapidURI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`id=n123`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`id=n123`));
+ });
test("uri includes a way id for LineString features with an OSM id", () => {
- lineStringFeature.properties.osmid = '456'
- taskGeometries.features = [ lineStringFeature ]
- const uri = constructRapidURI(task, mapBounds)
+ lineStringFeature.properties.osmid = "456";
+ taskGeometries.features = [lineStringFeature];
+ const uri = constructRapidURI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`id=w456`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`id=w456`));
+ });
test("uri includes a way id for Polygon features with an OSM id", () => {
- polygonFeature.properties.osmid = '789'
- taskGeometries.features = [ polygonFeature ]
- const uri = constructRapidURI(task, mapBounds)
+ polygonFeature.properties.osmid = "789";
+ taskGeometries.features = [polygonFeature];
+ const uri = constructRapidURI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`id=w789`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`id=w789`));
+ });
test("uri includes a relation id for MultiPolygon features with an OSM id", () => {
- multiPolygonFeature.properties.osmid = '135'
- taskGeometries.features = [ multiPolygonFeature ]
- const uri = constructRapidURI(task, mapBounds)
+ multiPolygonFeature.properties.osmid = "135";
+ taskGeometries.features = [multiPolygonFeature];
+ const uri = constructRapidURI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`id=r135`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`id=r135`));
+ });
test("features lacking an OSM id are not selected", () => {
- delete pointFeature.properties.osmid
- taskGeometries.features = [ pointFeature ]
- const uri = constructRapidURI(task, mapBounds)
+ delete pointFeature.properties.osmid;
+ taskGeometries.features = [pointFeature];
+ const uri = constructRapidURI(task, mapBounds);
- expect(uri).not.toEqual(
- expect.stringContaining('id=n')
- )
- })
+ expect(uri).not.toEqual(expect.stringContaining("id=n"));
+ });
test("features using the alternate @id property are still identified", () => {
- pointFeature.properties['@id'] = '123'
- taskGeometries.features = [ pointFeature ]
- const uri = constructRapidURI(task, mapBounds)
+ pointFeature.properties["@id"] = "123";
+ taskGeometries.features = [pointFeature];
+ const uri = constructRapidURI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`id=n123`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`id=n123`));
+ });
test("multiple features are comma-separated in the id", () => {
- pointFeature.properties.osmid = '123'
- lineStringFeature.properties.osmid = '456'
- multiPolygonFeature.properties.osmid = '135'
- taskGeometries.features = [ pointFeature, lineStringFeature, multiPolygonFeature ]
- const uri = constructRapidURI(task, mapBounds)
+ pointFeature.properties.osmid = "123";
+ lineStringFeature.properties.osmid = "456";
+ multiPolygonFeature.properties.osmid = "135";
+ taskGeometries.features = [pointFeature, lineStringFeature, multiPolygonFeature];
+ const uri = constructRapidURI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`id=n123,w456,r135`)
- )
- })
-})
+ expect(uri).toEqual(expect.stringContaining(`id=n123,w456,r135`));
+ });
+});
-describe('constructLevel0URI', () => {
+describe("constructLevel0URI", () => {
test("the uri specifies the Level0 editor", () => {
- const uri = constructLevel0URI(task, mapBounds)
- expect(uri).toEqual(expect.stringContaining("level0"))
- })
+ const uri = constructLevel0URI(task, mapBounds);
+ expect(uri).toEqual(expect.stringContaining("level0"));
+ });
test("the uri includes comma-separated centerpoint from mapbounds", () => {
- const uri = constructLevel0URI(task, mapBounds)
+ const uri = constructLevel0URI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`center=${centerPoint.lat},${centerPoint.lng}`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`center=${centerPoint.lat},${centerPoint.lng}`));
+ });
test("uri includes a URI-encoded checkin comment from the task challenge", () => {
- const uri = constructLevel0URI(task, mapBounds)
+ const uri = constructLevel0URI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(
- `comment=${encodeURI(challenge.checkinComment)}`
- )
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`comment=${encodeURI(challenge.checkinComment)}`));
+ });
test("uri includes a node id for Point features with an OSM id", () => {
- pointFeature.properties.osmid = '123'
- taskGeometries.features = [ pointFeature ]
- const uri = constructLevel0URI(task, mapBounds)
+ pointFeature.properties.osmid = "123";
+ taskGeometries.features = [pointFeature];
+ const uri = constructLevel0URI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`url=n123`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`url=n123`));
+ });
test("uri includes a way id for LineString features with an OSM id", () => {
- lineStringFeature.properties.osmid = '456'
- taskGeometries.features = [ lineStringFeature ]
- const uri = constructLevel0URI(task, mapBounds)
+ lineStringFeature.properties.osmid = "456";
+ taskGeometries.features = [lineStringFeature];
+ const uri = constructLevel0URI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`url=w456`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`url=w456`));
+ });
test("uri includes a way id for Polygon features with an OSM id", () => {
- polygonFeature.properties.osmid = '789'
- taskGeometries.features = [ polygonFeature ]
- const uri = constructLevel0URI(task, mapBounds)
+ polygonFeature.properties.osmid = "789";
+ taskGeometries.features = [polygonFeature];
+ const uri = constructLevel0URI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`url=w789`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`url=w789`));
+ });
test("uri includes a relation id for MultiPolygon features with an OSM id", () => {
- multiPolygonFeature.properties.osmid = '135'
- taskGeometries.features = [ multiPolygonFeature ]
- const uri = constructLevel0URI(task, mapBounds)
+ multiPolygonFeature.properties.osmid = "135";
+ taskGeometries.features = [multiPolygonFeature];
+ const uri = constructLevel0URI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`url=r135`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`url=r135`));
+ });
test("features lacking an OSM id are not selected", () => {
- delete pointFeature.properties.osmid
- taskGeometries.features = [ pointFeature ]
- const uri = constructLevel0URI(task, mapBounds)
+ delete pointFeature.properties.osmid;
+ taskGeometries.features = [pointFeature];
+ const uri = constructLevel0URI(task, mapBounds);
- expect(uri).not.toEqual(
- expect.stringContaining('url=n')
- )
- })
+ expect(uri).not.toEqual(expect.stringContaining("url=n"));
+ });
test("features using the alternate @id property are still identified", () => {
- pointFeature.properties['@id'] = '123'
- taskGeometries.features = [ pointFeature ]
- const uri = constructLevel0URI(task, mapBounds)
+ pointFeature.properties["@id"] = "123";
+ taskGeometries.features = [pointFeature];
+ const uri = constructLevel0URI(task, mapBounds);
- expect(uri).toEqual(
- expect.stringContaining(`url=n123`)
- )
- })
+ expect(uri).toEqual(expect.stringContaining(`url=n123`));
+ });
test("multiple features are comma-separated in the id", () => {
- pointFeature.properties.osmid = '123'
- lineStringFeature.properties.osmid = '456'
- multiPolygonFeature.properties.osmid = '135'
- taskGeometries.features = [ pointFeature, lineStringFeature, multiPolygonFeature ]
- const uri = constructLevel0URI(task, mapBounds)
-
- expect(uri).toEqual(
- expect.stringContaining(`url=n123,w456,r135`)
- )
- })
-})
-
+ pointFeature.properties.osmid = "123";
+ lineStringFeature.properties.osmid = "456";
+ multiPolygonFeature.properties.osmid = "135";
+ taskGeometries.features = [pointFeature, lineStringFeature, multiPolygonFeature];
+ const uri = constructLevel0URI(task, mapBounds);
+
+ expect(uri).toEqual(expect.stringContaining(`url=n123,w456,r135`));
+ });
+});
diff --git a/src/services/Editor/Messages.js b/src/services/Editor/Messages.js
index 9d0272215..99a69f4da 100644
--- a/src/services/Editor/Messages.js
+++ b/src/services/Editor/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with Editor
@@ -6,30 +6,30 @@ import { defineMessages } from 'react-intl'
export default defineMessages({
none: {
id: "Editor.none.label",
- defaultMessage: "None"
+ defaultMessage: "None",
},
id: {
id: "Editor.id.label",
- defaultMessage: "Edit in iD (web editor)"
+ defaultMessage: "Edit in iD (web editor)",
},
josm: {
id: "Editor.josm.label",
- defaultMessage: "Edit in JOSM"
+ defaultMessage: "Edit in JOSM",
},
josmLayer: {
id: "Editor.josmLayer.label",
- defaultMessage: "Edit in new JOSM layer"
+ defaultMessage: "Edit in new JOSM layer",
},
josmFeatures: {
id: "Editor.josmFeatures.label",
- defaultMessage: "Edit just features in JOSM"
+ defaultMessage: "Edit just features in JOSM",
},
level0: {
id: "Editor.level0.label",
- defaultMessage: "Edit in Level0"
+ defaultMessage: "Edit in Level0",
},
rapid: {
id: "Editor.rapid.label",
- defaultMessage: "Edit in RapiD"
- }
-})
+ defaultMessage: "Edit in RapiD",
+ },
+});
diff --git a/src/services/Error/AppErrors.js b/src/services/Error/AppErrors.js
index 3534b2a7d..f7f111ec1 100644
--- a/src/services/Error/AppErrors.js
+++ b/src/services/Error/AppErrors.js
@@ -14,7 +14,7 @@ export default {
updateFailure: messages.userUpdateFailure,
fetchFailure: messages.userFetchFailure,
notFound: messages.userNotFound,
- followFailure: messages.userFollowFailure
+ followFailure: messages.userFollowFailure,
},
leaderboard: {
@@ -84,7 +84,7 @@ export default {
archiveFailure: messages.challengeArchiveFailure,
rebuildFailure: messages.challengeRebuildFailure,
doesNotExist: messages.challengeDoesNotExist,
- moveFailure: messages.challengeMoveFailure
+ moveFailure: messages.challengeMoveFailure,
},
challengeSaveFailure: {
diff --git a/src/services/Error/Error.js b/src/services/Error/Error.js
index 5da050607..77789f3cd 100644
--- a/src/services/Error/Error.js
+++ b/src/services/Error/Error.js
@@ -1,19 +1,19 @@
-import PropTypes from 'prop-types'
-import _isString from 'lodash/isString'
-import _clone from 'lodash/clone'
-import _remove from 'lodash/remove'
-import _find from 'lodash/find'
-import _cloneDeep from 'lodash/cloneDeep'
+import _clone from "lodash/clone";
+import _cloneDeep from "lodash/cloneDeep";
+import _find from "lodash/find";
+import _isString from "lodash/isString";
+import _remove from "lodash/remove";
+import PropTypes from "prop-types";
export const errorShape = PropTypes.shape({
id: PropTypes.string.isRequired,
defaultMessage: PropTypes.string,
-})
+});
// redux actions
-export const ADD_ERROR = 'ADD_ERROR'
-export const REMOVE_ERROR = 'REMOVE_ERROR'
-export const CLEAR_ERRORS = 'CLEAR_ERRORS'
+export const ADD_ERROR = "ADD_ERROR";
+export const REMOVE_ERROR = "REMOVE_ERROR";
+export const CLEAR_ERRORS = "CLEAR_ERRORS";
/**
* Convenience method that attempts to extract an error message from the given
@@ -21,86 +21,90 @@ export const CLEAR_ERRORS = 'CLEAR_ERRORS'
*
* @returns error
*/
-export const addServerError = function(error, serverError) {
- return function(dispatch) {
- return new Promise(resolve => {
+export const addServerError = function (error, serverError) {
+ return function (dispatch) {
+ return new Promise((resolve) => {
if (!serverError || !serverError.response) {
- dispatch(addError(error))
- resolve(error)
+ dispatch(addError(error));
+ resolve(error);
}
- const detailedError = _cloneDeep(error)
+ const detailedError = _cloneDeep(error);
if (serverError?.response) {
- serverError.response.json().then(json => {
- if (_isString(json.message)) {
- detailedError.values = {details: `: ${json.message}`}
- }
- }).catch(
- () => {} // if message isn't valid json, just ignore
- ).then(() => {
- dispatch(addError(detailedError))
- resolve(detailedError)
- })
+ serverError.response
+ .json()
+ .then((json) => {
+ if (_isString(json.message)) {
+ detailedError.values = { details: `: ${json.message}` };
+ }
+ })
+ .catch(
+ () => {}, // if message isn't valid json, just ignore
+ )
+ .then(() => {
+ dispatch(addError(detailedError));
+ resolve(detailedError);
+ });
}
});
};
-}
+};
/**
* Add an error with an additional detailed message string. Note that the
* default error message must support the inclusion of details
*/
-export const addErrorWithDetails = function(error, detailString) {
- return function(dispatch) {
- const detailedError = _cloneDeep(error)
- detailedError.values = {details: `: ${detailString}`}
- return dispatch(addError(detailedError))
- }
-}
+export const addErrorWithDetails = function (error, detailString) {
+ return function (dispatch) {
+ const detailedError = _cloneDeep(error);
+ detailedError.values = { details: `: ${detailString}` };
+ return dispatch(addError(detailedError));
+ };
+};
// redux action creators
-export const addError = function(error) {
+export const addError = function (error) {
return {
type: ADD_ERROR,
error,
- }
-}
+ };
+};
-export const removeError = function(error) {
+export const removeError = function (error) {
return {
type: REMOVE_ERROR,
error,
- }
-}
+ };
+};
-export const clearErrors = function() {
+export const clearErrors = function () {
return {
type: CLEAR_ERRORS,
- }
-}
+ };
+};
// redux reducer
-export const currentErrors = function(state=[], action) {
- let copy = null
+export const currentErrors = function (state = [], action) {
+ let copy = null;
- switch(action.type) {
+ switch (action.type) {
case ADD_ERROR:
- copy = _clone(state)
+ copy = _clone(state);
// Don't add dup errors
- if (!_find(copy, {id: action.error.id})) {
- copy.push(action.error)
+ if (!_find(copy, { id: action.error.id })) {
+ copy.push(action.error);
}
- return copy
+ return copy;
case REMOVE_ERROR:
- copy = _clone(state)
- _remove(copy, {id: action.error.id})
- return copy
+ copy = _clone(state);
+ _remove(copy, { id: action.error.id });
+ return copy;
case CLEAR_ERRORS:
- return []
+ return [];
default:
- return state
+ return state;
}
-}
+};
diff --git a/src/services/Error/Messages.js b/src/services/Error/Messages.js
index 0a6b37a5a..b591684b9 100644
--- a/src/services/Error/Messages.js
+++ b/src/services/Error/Messages.js
@@ -88,8 +88,7 @@ export default defineMessages({
},
taskLockReleaseFailure: {
id: "Errors.task.lockReleaseFailure",
- defaultMessage:
- "Failed to release task lock. Your lock or your session may have expired.",
+ defaultMessage: "Failed to release task lock. Your lock or your session may have expired.",
},
taskBundleFailure: {
id: "Errors.task.bundleFailure",
@@ -117,11 +116,13 @@ export default defineMessages({
},
taskAlreadyBundled: {
id: "Errors.task.taskAlreadyBundled",
- defaultMessage: "Task{details} is already in a bundle and can't be put into another bundle. There might be other tasks you tried to bundle that aren't mentioned here, but the ID given is for the first task we noticed is already bundled.",
+ defaultMessage:
+ "Task{details} is already in a bundle and can't be put into another bundle. There might be other tasks you tried to bundle that aren't mentioned here, but the ID given is for the first task we noticed is already bundled.",
},
unableToBundleTasks: {
id: "Errors.task.unableToBundleTasks",
- defaultMessage: "The tasks with these IDs are locked by another user{details} and cannot be bundled.",
+ defaultMessage:
+ "The tasks with these IDs are locked by another user{details} and cannot be bundled.",
},
osmRequestTooLarge: {
id: "Errors.osm.requestTooLarge",
@@ -210,7 +211,8 @@ export default defineMessages({
},
challengeSaveNameFailure: {
id: "Errors.challengeSaveFailure.challengeSaveNameFailure",
- defaultMessage: "The 'NAME OF YOUR CHALLENGE' field is required and must be more than 3 characters long.",
+ defaultMessage:
+ "The 'NAME OF YOUR CHALLENGE' field is required and must be more than 3 characters long.",
},
challengeSaveDescriptionFailure: {
id: "Errors.challengeSaveFailure.challengeSaveDescriptionFailure",
@@ -218,7 +220,8 @@ export default defineMessages({
},
challengeSaveInstructionFailure: {
id: "Errors.challengeSaveFailure.challengeSaveInstructionFailure",
- defaultMessage: "The 'DETAILED INSTRUCTIONS FOR MAPPERS' field must have more than {minLength} characters.",
+ defaultMessage:
+ "The 'DETAILED INSTRUCTIONS FOR MAPPERS' field must have more than {minLength} characters.",
},
challengeSaveChangesetDescriptionFailure: {
id: "Errors.challengeSaveFailure.challengeSaveChangesetDescriptionFailure",
@@ -226,7 +229,8 @@ export default defineMessages({
},
challengeSaveEditPolicyAgreementFailure: {
id: "Errors.challengeSaveFailure.challengeSaveEditPolicyAgreementFailure",
- defaultMessage: "You must check the box at the bottom of the page to indicate that you acknowledge OpenStreetMap's Automated Edits code of conduct."
+ defaultMessage:
+ "You must check the box at the bottom of the page to indicate that you acknowledge OpenStreetMap's Automated Edits code of conduct.",
},
challengeRebuildFailure: {
id: "Errors.challenge.rebuildFailure",
@@ -243,8 +247,7 @@ export default defineMessages({
virtualChallengeFetchFailure: {
id: "Errors.virtualChallenge.fetchFailure",
- defaultMessage:
- "Unable to retrieve latest virtual challenge data from server.",
+ defaultMessage: "Unable to retrieve latest virtual challenge data from server.",
},
virtualChallengeCreateFailure: {
id: "Errors.virtualChallenge.createFailure",
@@ -289,8 +292,7 @@ export default defineMessages({
widgetWorkspaceRenderFailure: {
id: "Errors.widgetWorkspace.renderFailure",
- defaultMessage:
- "Unable to render workspace. Switching to a working layout.",
+ defaultMessage: "Unable to render workspace. Switching to a working layout.",
},
widgetWorkspaceImportFailure: {
@@ -321,7 +323,6 @@ export default defineMessages({
},
fileFormatIncorrect: {
id: "Errors.file.formatIncorrect",
- defaultMessage:
- "File format is unrecognized or unsupported for this operation",
+ defaultMessage: "File format is unrecognized or unsupported for this operation",
},
});
diff --git a/src/services/FundraisingNotices/FundraisingNotices.js b/src/services/FundraisingNotices/FundraisingNotices.js
index 8850e2dbb..52ec99bed 100644
--- a/src/services/FundraisingNotices/FundraisingNotices.js
+++ b/src/services/FundraisingNotices/FundraisingNotices.js
@@ -1,30 +1,30 @@
-import _isEmpty from 'lodash/isEmpty'
-import _isArray from 'lodash/isArray'
-import { isFuture, parseISO } from 'date-fns'
+import { isFuture, parseISO } from "date-fns";
+import _isArray from "lodash/isArray";
+import _isEmpty from "lodash/isEmpty";
-const NOTICES_URL = window.env.REACT_APP_FUNDRAISING_NOTICES_URL
+const NOTICES_URL = window.env.REACT_APP_FUNDRAISING_NOTICES_URL;
export const fetchActiveFundraisingNotices = async function () {
if (_isEmpty(NOTICES_URL)) {
- return []
+ return [];
}
- const response = await fetch(NOTICES_URL)
+ const response = await fetch(NOTICES_URL);
if (response.ok) {
- const fundraisingNotices = await response.json()
+ const fundraisingNotices = await response.json();
if (!fundraisingNotices || !_isArray(fundraisingNotices.notices)) {
- return []
+ return [];
}
return fundraisingNotices.notices
.map((notice) => {
// add Date instance for expiration timestamp
- notice.expirationDate = parseISO(notice.expirationTimestamp)
- return notice
+ notice.expirationDate = parseISO(notice.expirationTimestamp);
+ return notice;
})
- .filter((notice) => isFuture(notice.expirationDate))
+ .filter((notice) => isFuture(notice.expirationDate));
} else {
// Allow server admin to delete file when not in use
- return []
+ return [];
}
-}
+};
diff --git a/src/services/Grant/GranteeType.js b/src/services/Grant/GranteeType.js
index 8a89034fc..8025c0655 100644
--- a/src/services/Grant/GranteeType.js
+++ b/src/services/Grant/GranteeType.js
@@ -1,9 +1,8 @@
-import { ActivityItemType }
- from '../Activity/ActivityItemTypes/ActivityItemTypes'
+import { ActivityItemType } from "../Activity/ActivityItemTypes/ActivityItemTypes";
// Grantee types use the item type constants on the server
-export const GRANTEE_TYPE_USER = ActivityItemType.user
+export const GRANTEE_TYPE_USER = ActivityItemType.user;
export const GranteeType = Object.freeze({
user: GRANTEE_TYPE_USER,
-})
+});
diff --git a/src/services/Grant/Messages.js b/src/services/Grant/Messages.js
index fa6b7fd12..92706de07 100644
--- a/src/services/Grant/Messages.js
+++ b/src/services/Grant/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with Role
@@ -16,4 +16,4 @@ export default defineMessages({
id: "Grant.Role.read",
defaultMessage: "Read",
},
-})
+});
diff --git a/src/services/Grant/Role.js b/src/services/Grant/Role.js
index ab7feae69..8c1b6f237 100644
--- a/src/services/Grant/Role.js
+++ b/src/services/Grant/Role.js
@@ -1,42 +1,39 @@
-import _map from 'lodash/map'
-import _fromPairs from 'lodash/fromPairs'
-import _min from 'lodash/min'
-import messages from './Messages'
+import _fromPairs from "lodash/fromPairs";
+import _map from "lodash/map";
+import _min from "lodash/min";
+import messages from "./Messages";
// These constants are defined on the server
-export const ROLE_SUPERUSER = -1
-export const ROLE_ADMIN = 1
-export const ROLE_WRITE = 2
-export const ROLE_READ = 3
+export const ROLE_SUPERUSER = -1;
+export const ROLE_ADMIN = 1;
+export const ROLE_WRITE = 2;
+export const ROLE_READ = 3;
export const Role = Object.freeze({
admin: ROLE_ADMIN,
write: ROLE_WRITE,
read: ROLE_READ,
-})
+});
/**
* Returns an object mapping role values to raw internationalized messages
* suitable for use with FormattedMessage or formatMessage
*/
-export const messagesByRole = _fromPairs(
- _map(messages, (message, key) => [Role[key], message])
-)
+export const messagesByRole = _fromPairs(_map(messages, (message, key) => [Role[key], message]));
/** Returns object containing localized labels */
-export const roleLabels = intl => _fromPairs(
- _map(messages, (message, key) => [key, intl.formatMessage(message)])
-)
+export const roleLabels = (intl) =>
+ _fromPairs(_map(messages, (message, key) => [key, intl.formatMessage(message)]));
/** Returns the most privileged of the given roles **/
-export const mostPrivilegedRole = function(roles) {
- return _min(roles)
-}
+export const mostPrivilegedRole = function (roles) {
+ return _min(roles);
+};
/**
* Determines if the target role is implied by the given list of possessed
* roles
*/
-export const rolesImply = function(targetRole, roles) {
- return mostPrivilegedRole(roles) <= targetRole
-}
+export const rolesImply = function (targetRole, roles) {
+ return mostPrivilegedRole(roles) <= targetRole;
+};
diff --git a/src/services/Grant/TargetType.js b/src/services/Grant/TargetType.js
index e94e9c91b..3da25e3db 100644
--- a/src/services/Grant/TargetType.js
+++ b/src/services/Grant/TargetType.js
@@ -1,11 +1,10 @@
-import { ActivityItemType }
- from '../Activity/ActivityItemTypes/ActivityItemTypes'
+import { ActivityItemType } from "../Activity/ActivityItemTypes/ActivityItemTypes";
// Target types use the item type constants on the server
-export const TARGET_TYPE_PROJECT = ActivityItemType.project
-export const TARGET_TYPE_GROUP = ActivityItemType.group
+export const TARGET_TYPE_PROJECT = ActivityItemType.project;
+export const TARGET_TYPE_GROUP = ActivityItemType.group;
export const TargetType = Object.freeze({
project: TARGET_TYPE_PROJECT,
group: TARGET_TYPE_GROUP,
-})
+});
diff --git a/src/services/KeyboardShortcuts/KeyMappings.js b/src/services/KeyboardShortcuts/KeyMappings.js
index 46a5098f1..808163609 100644
--- a/src/services/KeyboardShortcuts/KeyMappings.js
+++ b/src/services/KeyboardShortcuts/KeyMappings.js
@@ -1,4 +1,4 @@
-import messages from './Messages'
+import messages from "./Messages";
/**
* Keyboard shortcut mappings. Top-level should be functional groupings, with
@@ -14,36 +14,36 @@ import messages from './Messages'
*/
export default {
openEditor: {
- editId: {key: 'e', label: messages.editId},
- editJosm: {key: 'r', label: messages.editJosm},
- editJosmLayer: {key: 't', label: messages.editJosmLayer},
- editJosmFeatures: {key: 'y', label: messages.editJosmFeatures},
- editLevel0: {key: 'v', label: messages.editLevel0},
- editRapid: {key: 'a', label: messages.editRapid},
+ editId: { key: "e", label: messages.editId },
+ editJosm: { key: "r", label: messages.editJosm },
+ editJosmLayer: { key: "t", label: messages.editJosmLayer },
+ editJosmFeatures: { key: "y", label: messages.editJosmFeatures },
+ editLevel0: { key: "v", label: messages.editLevel0 },
+ editRapid: { key: "a", label: messages.editRapid },
},
taskEditing: {
- cancel: {key: 'Escape', label: messages.cancel, keyLabel: messages.escapeLabel},
- fitBounds: {key: '0', label: messages.fitBounds},
- completeTogether: {key: 'b', label: messages.completeTogether}
+ cancel: { key: "Escape", label: messages.cancel, keyLabel: messages.escapeLabel },
+ fitBounds: { key: "0", label: messages.fitBounds },
+ completeTogether: { key: "b", label: messages.completeTogether },
},
layers: {
- layerOSMData: {key: 'o', label: messages.layerOSMData},
- layerTaskFeatures: {key: 's', label: messages.layerTaskFeatures},
- layerMapillary: {key: 'm', label: messages.layerMapillary},
+ layerOSMData: { key: "o", label: messages.layerOSMData },
+ layerTaskFeatures: { key: "s", label: messages.layerTaskFeatures },
+ layerMapillary: { key: "m", label: messages.layerMapillary },
},
taskCompletion: {
- skip: {key: 'w', label: messages.skip},
- falsePositive: {key: 'q', label: messages.falsePositive},
- fixed: {key: 'f', label: messages.fixed},
- tooHard: {key: 'd', label: messages.tooHard},
- alreadyFixed: {key: 'x', label: messages.alreadyFixed},
+ skip: { key: "w", label: messages.skip },
+ falsePositive: { key: "q", label: messages.falsePositive },
+ fixed: { key: "f", label: messages.fixed },
+ tooHard: { key: "d", label: messages.tooHard },
+ alreadyFixed: { key: "x", label: messages.alreadyFixed },
},
taskInspect: {
- nextTask: {key: 'l', label: messages.nextTask},
- prevTask: {key: 'h', label: messages.prevTask},
+ nextTask: { key: "l", label: messages.nextTask },
+ prevTask: { key: "h", label: messages.prevTask },
},
taskConfirmation: {
- confirmSubmit: {key: 'Enter', label: messages.confirmSubmit},
- cancel: {key: 'Escape', label: messages.cancel, keyLabel: messages.escapeLabel},
+ confirmSubmit: { key: "Enter", label: messages.confirmSubmit },
+ cancel: { key: "Escape", label: messages.cancel, keyLabel: messages.escapeLabel },
},
-}
+};
diff --git a/src/services/KeyboardShortcuts/KeyboardShortcuts.js b/src/services/KeyboardShortcuts/KeyboardShortcuts.js
index bbb4f0bf6..97ad19e64 100644
--- a/src/services/KeyboardShortcuts/KeyboardShortcuts.js
+++ b/src/services/KeyboardShortcuts/KeyboardShortcuts.js
@@ -1,131 +1,125 @@
-import _cloneDeep from 'lodash/cloneDeep'
-import _merge from 'lodash/merge'
-import _isEmpty from 'lodash/isEmpty'
+import _cloneDeep from "lodash/cloneDeep";
+import _isEmpty from "lodash/isEmpty";
+import _merge from "lodash/merge";
// redux actions
-export const ADD_KEYBOARD_SHORTCUT_GROUP = 'ADD_KEYBOARD_SHORTCUT_GROUP'
-export const REMOVE_KEYBOARD_SHORTCUT_GROUP = 'REMOVE_KEYBOARD_SHORTCUT_GROUP'
-export const ADD_KEYBOARD_SHORTCUT = 'ADD_KEYBOARD_SHORTCUT'
-export const REMOVE_KEYBOARD_SHORTCUT = 'REMOVE_KEYBOARD_SHORTCUT'
-export const PAUSE_KEYBOARD_SHORTCUTS = 'PAUSE_KEYBOARD_SHORTCUTS'
-export const RESUME_KEYBOARD_SHORTCUTS = 'RESUME_KEYBOARD_SHORTCUTS'
-export const CLEAR_KEYBOARD_SHORTCUTS = 'CLEAR_KEYBOARD_SHORTCUTS'
+export const ADD_KEYBOARD_SHORTCUT_GROUP = "ADD_KEYBOARD_SHORTCUT_GROUP";
+export const REMOVE_KEYBOARD_SHORTCUT_GROUP = "REMOVE_KEYBOARD_SHORTCUT_GROUP";
+export const ADD_KEYBOARD_SHORTCUT = "ADD_KEYBOARD_SHORTCUT";
+export const REMOVE_KEYBOARD_SHORTCUT = "REMOVE_KEYBOARD_SHORTCUT";
+export const PAUSE_KEYBOARD_SHORTCUTS = "PAUSE_KEYBOARD_SHORTCUTS";
+export const RESUME_KEYBOARD_SHORTCUTS = "RESUME_KEYBOARD_SHORTCUTS";
+export const CLEAR_KEYBOARD_SHORTCUTS = "CLEAR_KEYBOARD_SHORTCUTS";
// redux action creators
-export const addKeyboardShortcutGroup = function(shortcutGroup) {
+export const addKeyboardShortcutGroup = function (shortcutGroup) {
return {
type: ADD_KEYBOARD_SHORTCUT_GROUP,
shortcutGroup,
- }
-}
+ };
+};
-export const removeKeyboardShortcutGroup = function(groupName) {
+export const removeKeyboardShortcutGroup = function (groupName) {
return {
type: REMOVE_KEYBOARD_SHORTCUT_GROUP,
groupName,
- }
-}
+ };
+};
-export const addKeyboardShortcut = function(groupName, shortcut) {
+export const addKeyboardShortcut = function (groupName, shortcut) {
return {
type: ADD_KEYBOARD_SHORTCUT,
groupName,
shortcut,
- }
-}
+ };
+};
-export const removeKeyboardShortcut = function(groupName, shortcutName) {
+export const removeKeyboardShortcut = function (groupName, shortcutName) {
return {
type: REMOVE_KEYBOARD_SHORTCUT,
groupName,
shortcutName,
- }
-}
+ };
+};
-export const pauseKeyboardShortcuts = function() {
+export const pauseKeyboardShortcuts = function () {
return {
type: PAUSE_KEYBOARD_SHORTCUTS,
- }
-}
+ };
+};
-export const resumeKeyboardShortcuts = function() {
+export const resumeKeyboardShortcuts = function () {
return {
type: RESUME_KEYBOARD_SHORTCUTS,
- }
-}
+ };
+};
-export const clearKeyboardShortcuts = function() {
+export const clearKeyboardShortcuts = function () {
return {
type: CLEAR_KEYBOARD_SHORTCUTS,
- }
-}
+ };
+};
// redux reducers.
-export const currentKeyboardShortcuts = function(state={}, action) {
+export const currentKeyboardShortcuts = function (state = {}, action) {
if (action.type === ADD_KEYBOARD_SHORTCUT_GROUP) {
- const mergedState = Object.assign({groups: {}}, state)
- _merge(mergedState.groups, action.shortcutGroup)
+ const mergedState = Object.assign({ groups: {} }, state);
+ _merge(mergedState.groups, action.shortcutGroup);
- return mergedState
- }
- else if (action.type === REMOVE_KEYBOARD_SHORTCUT_GROUP) {
- const mergedState = Object.assign({groups: {}}, state)
- delete mergedState.groups[action.groupName]
+ return mergedState;
+ } else if (action.type === REMOVE_KEYBOARD_SHORTCUT_GROUP) {
+ const mergedState = Object.assign({ groups: {} }, state);
+ delete mergedState.groups[action.groupName];
- return mergedState
- }
- else if (action.type === ADD_KEYBOARD_SHORTCUT) {
- const mergedState = Object.assign({groups: {}}, state)
+ return mergedState;
+ } else if (action.type === ADD_KEYBOARD_SHORTCUT) {
+ const mergedState = Object.assign({ groups: {} }, state);
if (!mergedState.groups[action.groupName]) {
- mergedState.groups[action.groupName] = {}
+ mergedState.groups[action.groupName] = {};
}
- _merge(mergedState.groups[action.groupName], action.shortcut)
+ _merge(mergedState.groups[action.groupName], action.shortcut);
- return mergedState
- }
- else if (action.type === REMOVE_KEYBOARD_SHORTCUT) {
- if (_isEmpty(state.groups)) { // No shortcuts exist
- return state
+ return mergedState;
+ } else if (action.type === REMOVE_KEYBOARD_SHORTCUT) {
+ if (_isEmpty(state.groups)) {
+ // No shortcuts exist
+ return state;
}
- let mergedState = _cloneDeep(state)
+ let mergedState = _cloneDeep(state);
if (mergedState.groups[action.groupName]) {
- delete mergedState.groups[action.groupName][action.shortcutName]
+ delete mergedState.groups[action.groupName][action.shortcutName];
}
// Clean up group if left empty
if (_isEmpty(mergedState.groups[action.groupName])) {
- delete mergedState.groups[action.groupName]
+ delete mergedState.groups[action.groupName];
}
- return mergedState
- }
- else if (action.type === PAUSE_KEYBOARD_SHORTCUTS) {
+ return mergedState;
+ } else if (action.type === PAUSE_KEYBOARD_SHORTCUTS) {
// If we're already paused, ignore
if (state.paused) {
- return state
+ return state;
}
return {
paused: state.groups,
groups: {},
- }
- }
- else if (action.type === RESUME_KEYBOARD_SHORTCUTS) {
+ };
+ } else if (action.type === RESUME_KEYBOARD_SHORTCUTS) {
// If nothing is paused, ignore
if (_isEmpty(state.paused)) {
- return state
+ return state;
}
return {
- groups: Object.assign({}, state.paused)
- }
- }
- else if (action.type === CLEAR_KEYBOARD_SHORTCUTS) {
- return {groups: {}}
- }
- else {
- return state
+ groups: Object.assign({}, state.paused),
+ };
+ } else if (action.type === CLEAR_KEYBOARD_SHORTCUTS) {
+ return { groups: {} };
+ } else {
+ return state;
}
-}
+};
diff --git a/src/services/KeyboardShortcuts/Messages.js b/src/services/KeyboardShortcuts/Messages.js
index 972cb126d..df6fb2f42 100644
--- a/src/services/KeyboardShortcuts/Messages.js
+++ b/src/services/KeyboardShortcuts/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with KeyMappings.
@@ -61,7 +61,7 @@ export default defineMessages({
completeTogether: {
id: "KeyMapping.taskEditing.completeTogether",
- defaultMessage: "Complete Tasks Together"
+ defaultMessage: "Complete Tasks Together",
},
escapeLabel: {
@@ -108,4 +108,4 @@ export default defineMessages({
id: "KeyMapping.taskCompletion.confirmSubmit",
defaultMessage: "Submit",
},
-})
+});
diff --git a/src/services/Leaderboard/CountryBoundingBoxes.js b/src/services/Leaderboard/CountryBoundingBoxes.js
index c03308d1d..06712b996 100644
--- a/src/services/Leaderboard/CountryBoundingBoxes.js
+++ b/src/services/Leaderboard/CountryBoundingBoxes.js
@@ -1,23 +1,25 @@
-import _map from 'lodash/map'
-import _reduce from 'lodash/reduce'
-import countryCodeBoundingBoxJSON from '../../countryCodeBoundingBox.json'
+import _map from "lodash/map";
+import _reduce from "lodash/reduce";
+import countryCodeBoundingBoxJSON from "../../countryCodeBoundingBox.json";
/**
* Map of bounding boxes indexed by country code.
*/
-export const CountryBoundingBoxes =
- _reduce(countryCodeBoundingBoxJSON, function(result, value, key) {
- result[key] = {name: value[0], boundingBox: value[1]}
- return result
- }, {})
+export const CountryBoundingBoxes = _reduce(
+ countryCodeBoundingBoxJSON,
+ function (result, value, key) {
+ result[key] = { name: value[0], boundingBox: value[1] };
+ return result;
+ },
+ {},
+);
-
-export const boundingBoxForCountry = function(countryCode) {
+export const boundingBoxForCountry = function (countryCode) {
return CountryBoundingBoxes[countryCode]?.boundingBox;
-}
+};
-export const supportedCountries = function() {
+export const supportedCountries = function () {
return _map(CountryBoundingBoxes, (value, key) => {
- return {countryCode: key, name: value}
- })
-}
+ return { countryCode: key, name: value };
+ });
+};
diff --git a/src/services/Leaderboard/Leaderboard.js b/src/services/Leaderboard/Leaderboard.js
index 3a39dac6f..db1d8995f 100644
--- a/src/services/Leaderboard/Leaderboard.js
+++ b/src/services/Leaderboard/Leaderboard.js
@@ -1,91 +1,125 @@
-import { defaultRoutes as api } from '../Server/Server'
-import _isArray from 'lodash/isArray'
-import Endpoint from '../Server/Endpoint'
-import { startOfMonth, endOfDay } from 'date-fns'
-import { CHALLENGE_INCLUDE_LOCAL } from '../Challenge/Challenge'
-import { addError } from '../Error/Error'
-import AppErrors from '../Error/AppErrors'
+import { endOfDay, startOfMonth } from "date-fns";
+import _isArray from "lodash/isArray";
+import { CHALLENGE_INCLUDE_LOCAL } from "../Challenge/Challenge";
+import AppErrors from "../Error/AppErrors";
+import { addError } from "../Error/Error";
+import Endpoint from "../Server/Endpoint";
+import { defaultRoutes as api } from "../Server/Server";
// Default leaderboard count
-export const DEFAULT_LEADERBOARD_COUNT = 10
+export const DEFAULT_LEADERBOARD_COUNT = 10;
// Current Month duration
-export const CURRENT_MONTH = 0
+export const CURRENT_MONTH = 0;
// Use custom dates
-export const CUSTOM_RANGE = -2
+export const CUSTOM_RANGE = -2;
// User Type 'mapper'
-export const USER_TYPE_MAPPER = "mapper"
+export const USER_TYPE_MAPPER = "mapper";
// User Type 'reviewer'
-export const USER_TYPE_REVIEWER = "reviewer"
+export const USER_TYPE_REVIEWER = "reviewer";
/**
* Retrieve leaderboard data from the server for the given date range and
* filters, returning a Promise that resolves to the leaderboard data. Note
* that leaderboard data is *not* stored in the redux store.
*/
-export const fetchLeaderboard = (numberMonths=null, onlyEnabled=true,
- forProjects=null, forChallenges=null,
- forUsers=null, forCountries=null,
- limit=10, startDate=null, endDate=null) => {
+export const fetchLeaderboard = (
+ numberMonths = null,
+ onlyEnabled = true,
+ forProjects = null,
+ forChallenges = null,
+ forUsers = null,
+ forCountries = null,
+ limit = 10,
+ startDate = null,
+ endDate = null,
+) => {
const params = {
limit,
- onlyEnabled
- }
+ onlyEnabled,
+ };
return async function (dispatch) {
- initializeLeaderboardParams(params, numberMonths, forProjects, forChallenges,
- forUsers, forCountries, startDate, endDate)
+ initializeLeaderboardParams(
+ params,
+ numberMonths,
+ forProjects,
+ forChallenges,
+ forUsers,
+ forCountries,
+ startDate,
+ endDate,
+ );
try {
let results;
if (forProjects && forProjects.length > 0) {
const projectId = forProjects[0];
- results = await new Endpoint(api.user.projectLeaderboard, { params: { ...params, projectId } }).execute()
+ results = await new Endpoint(api.user.projectLeaderboard, {
+ params: { ...params, projectId },
+ }).execute();
} else if (forChallenges && forChallenges.length > 0) {
const challengeId = forChallenges[0];
- results = await new Endpoint(api.user.challengeLeaderboard, { params: { ...params, challengeId } }).execute()
+ results = await new Endpoint(api.user.challengeLeaderboard, {
+ params: { ...params, challengeId },
+ }).execute();
} else {
- results = await new Endpoint(api.users.leaderboard, { params }).execute()
+ results = await new Endpoint(api.users.leaderboard, { params }).execute();
}
- return results
+ return results;
} catch (error) {
- console.error('Error fetching leaderboard:', error)
- //Prevent error modals on leaderboard widgets, and retain error modal on leaderboard page
- if(!forProjects && !forChallenges){
- dispatch(addError(AppErrors.leaderboard.fetchFailure))
+ console.error("Error fetching leaderboard:", error);
+ //Prevent error modals on leaderboard widgets, and retain error modal on leaderboard page
+ if (!forProjects && !forChallenges) {
+ dispatch(addError(AppErrors.leaderboard.fetchFailure));
}
- return []
+ return [];
}
- }
-}
+ };
+};
/**
* Retrieve leaderboard data for a user from the server for the given date range and
* filters, returning a Promise that resolves to the leaderboard data. Note
* that leaderboard data is *not* stored in the redux store.
*/
-export const fetchLeaderboardForUser = (userId, bracket=0, numberMonths=1,
- onlyEnabled=true, forProjects=null, forChallenges=null,
- forUsers, forCountries=null, startDate=null,
- endDate=null) => {
+export const fetchLeaderboardForUser = (
+ userId,
+ bracket = 0,
+ numberMonths = 1,
+ onlyEnabled = true,
+ forProjects = null,
+ forChallenges = null,
+ forUsers,
+ forCountries = null,
+ startDate = null,
+ endDate = null,
+) => {
const params = {
bracket,
- onlyEnabled
- }
+ onlyEnabled,
+ };
return async function (dispatch) {
-
- const variables = {
- id: userId
- }
-
- initializeLeaderboardParams(params, numberMonths, forProjects, forChallenges,
- null, forCountries, startDate, endDate)
+ const variables = {
+ id: userId,
+ };
+
+ initializeLeaderboardParams(
+ params,
+ numberMonths,
+ forProjects,
+ forChallenges,
+ null,
+ forCountries,
+ startDate,
+ endDate,
+ );
try {
let results;
@@ -94,86 +128,105 @@ export const fetchLeaderboardForUser = (userId, bracket=0, numberMonths=1,
//disabling project user ranks for now, as it's not supported by the backend
//const projectId = forProjects[0];
//results = await new Endpoint(api.user.projectLeaderboardForUser, { params: { ...params, projectId }, variables: { userId } }).execute()
- return []
+ return [];
} else if (forChallenges && forChallenges.length > 0) {
const challengeId = forChallenges[0];
- results = await new Endpoint(api.user.challengeLeaderboardForUser, { params: { ...params, challengeId }, variables: { userId } }).execute()
+ results = await new Endpoint(api.user.challengeLeaderboardForUser, {
+ params: { ...params, challengeId },
+ variables: { userId },
+ }).execute();
} else {
- results = await new Endpoint(api.users.userLeaderboard, {variables, params}).execute()
+ results = await new Endpoint(api.users.userLeaderboard, { variables, params }).execute();
}
return results;
} catch (error) {
- console.error('Error fetching leaderboard:', error)
- dispatch(addError(AppErrors.leaderboard.userFetchFailure))
- return null
+ console.error("Error fetching leaderboard:", error);
+ dispatch(addError(AppErrors.leaderboard.userFetchFailure));
+ return null;
}
- }
-}
+ };
+};
/**
* Retrieve reviewer leaderboard data from the server for the given date range and
* filters, returning a Promise that resolves to the leaderboard data. Note
* that leaderboard data is *not* stored in the redux store.
*/
-export const fetchReviewerLeaderboard = (numberMonths=null, onlyEnabled=true,
- forProjects=null, forChallenges=null,
- forUsers=null, forCountries=null,
- limit=10, startDate=null, endDate=null) => {
-
+export const fetchReviewerLeaderboard = (
+ numberMonths = null,
+ onlyEnabled = true,
+ forProjects = null,
+ forChallenges = null,
+ forUsers = null,
+ forCountries = null,
+ limit = 10,
+ startDate = null,
+ endDate = null,
+) => {
const params = {
limit,
- onlyEnabled
- }
+ onlyEnabled,
+ };
return async function (dispatch) {
try {
-
- initializeLeaderboardParams(params, numberMonths, forProjects, forChallenges,
- forUsers, forCountries, startDate, endDate)
- const result = await new Endpoint(api.users.reviewerLeaderboard, {params}).execute()
- return result
+ initializeLeaderboardParams(
+ params,
+ numberMonths,
+ forProjects,
+ forChallenges,
+ forUsers,
+ forCountries,
+ startDate,
+ endDate,
+ );
+ const result = await new Endpoint(api.users.reviewerLeaderboard, { params }).execute();
+ return result;
} catch (error) {
- console.error("Error in fetchReviewerLeaderboard:", error)
- dispatch(addError(AppErrors.leaderboard.reviewerLeaderboard))
- return []
+ console.error("Error in fetchReviewerLeaderboard:", error);
+ dispatch(addError(AppErrors.leaderboard.reviewerLeaderboard));
+ return [];
}
- }
-}
-
-
-export const initializeLeaderboardParams = function (params, numberMonths,
- forProjects, forChallenges,
- forUsers, forCountries,
- startDate, endDate) {
+ };
+};
+
+export const initializeLeaderboardParams = function (
+ params,
+ numberMonths,
+ forProjects,
+ forChallenges,
+ forUsers,
+ forCountries,
+ startDate,
+ endDate,
+) {
if (numberMonths === CURRENT_MONTH) {
- params.start = startOfMonth(new Date()).toISOString()
- params.end = endOfDay(new Date()).toISOString()
- }
- else if (numberMonths === CUSTOM_RANGE && startDate && endDate) {
- params.start = new Date(startDate).toISOString()
- params.end = new Date(endDate).toISOString()
- }
- else {
- params.monthDuration = numberMonths || CURRENT_MONTH
+ params.start = startOfMonth(new Date()).toISOString();
+ params.end = endOfDay(new Date()).toISOString();
+ } else if (numberMonths === CUSTOM_RANGE && startDate && endDate) {
+ params.start = new Date(startDate).toISOString();
+ params.end = new Date(endDate).toISOString();
+ } else {
+ params.monthDuration = numberMonths || CURRENT_MONTH;
}
if (_isArray(forProjects)) {
- params.projectIds = forProjects.join(',')
+ params.projectIds = forProjects.join(",");
}
if (_isArray(forChallenges)) {
- params.challengeIds = forChallenges.join(',')
+ params.challengeIds = forChallenges.join(",");
}
if (_isArray(forUsers)) {
- params.userIds = forUsers.join(',')
+ params.userIds = forUsers.join(",");
}
if (_isArray(forCountries)) {
- params.countryCodes = forCountries.join(',')
+ params.countryCodes = forCountries.join(",");
}
// We can include work on local challenges
- params.cLocal = CHALLENGE_INCLUDE_LOCAL
-}
+ params.cLocal = CHALLENGE_INCLUDE_LOCAL;
+};
diff --git a/src/services/Leaderboard/Leaderboard.test.js b/src/services/Leaderboard/Leaderboard.test.js
index c437742e9..9d320e912 100644
--- a/src/services/Leaderboard/Leaderboard.test.js
+++ b/src/services/Leaderboard/Leaderboard.test.js
@@ -1,57 +1,57 @@
import { describe, expect } from "vitest";
import {
- fetchLeaderboard,
- initializeLeaderboardParams,
- fetchReviewerLeaderboard,
- fetchLeaderboardForUser
+ fetchLeaderboard,
+ fetchLeaderboardForUser,
+ fetchReviewerLeaderboard,
+ initializeLeaderboardParams,
} from "./Leaderboard";
describe("fetchLeaderboard", () => {
test("returns empty object if no params provided", () => {
- const leaderboard = fetchLeaderboard()
- expect(Object.keys(leaderboard).length).toBe(0)
- })
-})
+ const leaderboard = fetchLeaderboard();
+ expect(Object.keys(leaderboard).length).toBe(0);
+ });
+});
describe("fetchLeaderboardForUser", () => {
test("returns empty object if no params provided", () => {
- const leaderboard = fetchLeaderboardForUser()
- expect(Object.keys(leaderboard).length).toBe(0)
- })
-})
+ const leaderboard = fetchLeaderboardForUser();
+ expect(Object.keys(leaderboard).length).toBe(0);
+ });
+});
describe("fetchReviewerLeaderboard", () => {
test("returns empty object if no params provided", () => {
- const leaderboard = fetchReviewerLeaderboard()
- expect(Object.keys(leaderboard).length).toBe(0)
- })
-})
+ const leaderboard = fetchReviewerLeaderboard();
+ expect(Object.keys(leaderboard).length).toBe(0);
+ });
+});
describe("initializeLeaderboardParams", () => {
test("correctly sets params in params object", () => {
const params = {};
- initializeLeaderboardParams(params, 1, [10,11], [10,11], [1,2], ['DZ', 'UK'])
+ initializeLeaderboardParams(params, 1, [10, 11], [10, 11], [1, 2], ["DZ", "UK"]);
expect(params.monthDuration).toBe(1);
expect(params.countryCodes).toBe("DZ,UK");
expect(params.userIds).toBe("1,2");
expect(params.challengeIds).toBe("10,11");
expect(params.projectIds).toBe("10,11");
- })
+ });
test("returns correctly formatted start and end date if providing custom month range", () => {
const params = {};
- initializeLeaderboardParams(params, -2, null, null, null, null, 1657653373473, 1657663373473)
+ initializeLeaderboardParams(params, -2, null, null, null, null, 1657653373473, 1657663373473);
expect(params.start).toBe("2022-07-12T19:16:13.473Z");
expect(params.end).toBe("2022-07-12T22:02:53.473Z");
- })
+ });
test("returns correctly formatted start and end date if providing current month range", () => {
const params = {};
- initializeLeaderboardParams(params, 0)
+ initializeLeaderboardParams(params, 0);
expect(typeof params.start).toBe("string");
expect(typeof params.end).toBe("string");
- })
-})
+ });
+});
diff --git a/src/services/MapBounds/MapBounds.js b/src/services/MapBounds/MapBounds.js
index ac5043658..8650af4f5 100644
--- a/src/services/MapBounds/MapBounds.js
+++ b/src/services/MapBounds/MapBounds.js
@@ -1,10 +1,10 @@
-import _isEmpty from 'lodash/isEmpty'
-import _isFunction from 'lodash/isFunction'
-import _isArray from 'lodash/isArray'
-import _max from 'lodash/max'
-import _split from 'lodash/split'
-import _isString from 'lodash/isString'
-import { LatLngBounds, LatLng } from 'leaflet'
+import { LatLng, LatLngBounds } from "leaflet";
+import _isArray from "lodash/isArray";
+import _isEmpty from "lodash/isEmpty";
+import _isFunction from "lodash/isFunction";
+import _isString from "lodash/isString";
+import _max from "lodash/max";
+import _split from "lodash/split";
/** Default map bounds in absence of any state */
export const DEFAULT_MAP_BOUNDS = [
@@ -12,13 +12,13 @@ export const DEFAULT_MAP_BOUNDS = [
-22.512556954051437, // south
96.15234375, // east
22.51255695405145, // north
-]
+];
/*
* Global bounds. Note that mercator projection doesn't extend much past 85
* degrees
*/
-export const GLOBAL_MAPBOUNDS = [-180, -85, 180, 85]
+export const GLOBAL_MAPBOUNDS = [-180, -85, 180, 85];
/**
* Maximum allowed size, in degrees, of the bounding box for
@@ -26,7 +26,7 @@ export const GLOBAL_MAPBOUNDS = [-180, -85, 180, 85]
* .env setting or a system default if that hasn't been set.
*/
export const maxAllowedTaskBrowsingDegrees =
- window.env?.REACT_APP_BOUNDED_TASKS_MAX_DIMENSION ?? 70 // degrees
+ window.env?.REACT_APP_BOUNDED_TASKS_MAX_DIMENSION ?? 70; // degrees
// utility functions
@@ -43,22 +43,23 @@ export const maxAllowedTaskBrowsingDegrees =
*
* @returns an array of [west, south, east, north]
*/
-export const fromLatLngBounds = function(boundsObject) {
+export const fromLatLngBounds = function (boundsObject) {
if (_isEmpty(boundsObject)) {
- return null
- }
- else if (_isFunction(boundsObject.toBBoxString)) {
- return [boundsObject.getWest(), boundsObject.getSouth(),
- boundsObject.getEast(), boundsObject.getNorth()]
- }
- else if (_isArray(boundsObject) && boundsObject.length === 4) {
+ return null;
+ } else if (_isFunction(boundsObject.toBBoxString)) {
+ return [
+ boundsObject.getWest(),
+ boundsObject.getSouth(),
+ boundsObject.getEast(),
+ boundsObject.getNorth(),
+ ];
+ } else if (_isArray(boundsObject) && boundsObject.length === 4) {
// They gave us an array of bounds. Just return it.
- return boundsObject
+ return boundsObject;
+ } else {
+ throw new Error("Invalid bounds object given");
}
- else {
- throw new Error("Invalid bounds object given")
- }
-}
+};
/**
* Converts an arrayBounds of [west, south, east, north] to a
@@ -74,53 +75,59 @@ export const fromLatLngBounds = function(boundsObject) {
*
* @returns a LatLngBounds instance
*/
-export const toLatLngBounds = function(arrayBounds) {
+export const toLatLngBounds = function (arrayBounds) {
if (_isEmpty(arrayBounds)) {
- return null
- }
- else if (_isArray(arrayBounds) && arrayBounds.length === 4) {
- const southWest = new LatLng(arrayBounds[1], arrayBounds[0])
- const northEast = new LatLng(arrayBounds[3], arrayBounds[2])
- return new LatLngBounds(southWest, northEast)
- }
- else if (_isFunction(arrayBounds.toBBoxString)) {
+ return null;
+ } else if (_isArray(arrayBounds) && arrayBounds.length === 4) {
+ const southWest = new LatLng(arrayBounds[1], arrayBounds[0]);
+ const northEast = new LatLng(arrayBounds[3], arrayBounds[2]);
+ return new LatLngBounds(southWest, northEast);
+ } else if (_isFunction(arrayBounds.toBBoxString)) {
// they gave us a LatLngBounds. Just return it.
- return arrayBounds
- }
- else if (_isString(arrayBounds)) {
- const bounds = _split(arrayBounds, ',')
+ return arrayBounds;
+ } else if (_isString(arrayBounds)) {
+ const bounds = _split(arrayBounds, ",");
if (bounds && bounds.length === 4) {
- return toLatLngBounds(bounds)
+ return toLatLngBounds(bounds);
+ } else {
+ throw new Error("Invalid bounds given: " + arrayBounds);
}
- else {
- throw new Error("Invalid bounds given: " + arrayBounds)
- }
- }
- else {
- throw new Error("Invalid bounds array given")
+ } else {
+ throw new Error("Invalid bounds array given");
}
-}
+};
/**
* Determines if the largest dimension of the given bounding box is less
* than the given maxAllowedDegrees.
*/
-export const boundsWithinAllowedMaxDegrees = function(bounds, maxAllowedDegrees=maxAllowedTaskBrowsingDegrees) {
- const normalizedBounds = toLatLngBounds(bounds)
- return maxAllowedDegrees >
- _max([normalizedBounds.getEast() - normalizedBounds.getWest(),
- normalizedBounds.getNorth() - normalizedBounds.getSouth()])
-}
+export const boundsWithinAllowedMaxDegrees = function (
+ bounds,
+ maxAllowedDegrees = maxAllowedTaskBrowsingDegrees,
+) {
+ const normalizedBounds = toLatLngBounds(bounds);
+ return (
+ maxAllowedDegrees >
+ _max([
+ normalizedBounds.getEast() - normalizedBounds.getWest(),
+ normalizedBounds.getNorth() - normalizedBounds.getSouth(),
+ ])
+ );
+};
/**
* Determines if the two bounds are within the given degress apart from each other.
*/
-export const boundsWithinDegrees = function(bounds1, bounds2, maxAllowedDegrees) {
- const normalizedBounds1 = toLatLngBounds(bounds1)
- const normalizedBounds2 = toLatLngBounds(bounds2)
- return maxAllowedDegrees >
- _max([Math.abs(normalizedBounds1.getEast() - normalizedBounds2.getEast()),
- Math.abs(normalizedBounds1.getWest() - normalizedBounds2.getWest()),
- Math.abs(normalizedBounds1.getNorth() - normalizedBounds2.getNorth()),
- Math.abs(normalizedBounds1.getSouth() - normalizedBounds2.getSouth())])
-}
+export const boundsWithinDegrees = function (bounds1, bounds2, maxAllowedDegrees) {
+ const normalizedBounds1 = toLatLngBounds(bounds1);
+ const normalizedBounds2 = toLatLngBounds(bounds2);
+ return (
+ maxAllowedDegrees >
+ _max([
+ Math.abs(normalizedBounds1.getEast() - normalizedBounds2.getEast()),
+ Math.abs(normalizedBounds1.getWest() - normalizedBounds2.getWest()),
+ Math.abs(normalizedBounds1.getNorth() - normalizedBounds2.getNorth()),
+ Math.abs(normalizedBounds1.getSouth() - normalizedBounds2.getSouth()),
+ ])
+ );
+};
diff --git a/src/services/MapBounds/MapBounds.test.js b/src/services/MapBounds/MapBounds.test.js
index 512772cb6..473436f78 100644
--- a/src/services/MapBounds/MapBounds.test.js
+++ b/src/services/MapBounds/MapBounds.test.js
@@ -1,56 +1,60 @@
-import { describe, it, expect } from "vitest";
-import { fromLatLngBounds, toLatLngBounds } from './MapBounds'
-import L from 'leaflet'
+import L from "leaflet";
+import { describe, expect, it } from "vitest";
+import { fromLatLngBounds, toLatLngBounds } from "./MapBounds";
-const north = 45
-const south = -45
-const east = 100
-const west = -100
+const north = 45;
+const south = -45;
+const east = 100;
+const west = -100;
-describe('fromLatLngBounds', () => {
+describe("fromLatLngBounds", () => {
it("converts leaflet LatLngBounds to array of [w, s, e, n]", () => {
- const bounds = L.latLngBounds(L.latLng({lat: north, lng: west}),
- L.latLng({lat: south, lng: east}))
+ const bounds = L.latLngBounds(
+ L.latLng({ lat: north, lng: west }),
+ L.latLng({ lat: south, lng: east }),
+ );
- expect(fromLatLngBounds(bounds)).toEqual([west, south, east, north])
- })
+ expect(fromLatLngBounds(bounds)).toEqual([west, south, east, north]);
+ });
it("simply returns the array if passed array bounds", () => {
- const arrayBounds = [west, south, east, north]
- expect(fromLatLngBounds(arrayBounds)).toBe(arrayBounds)
- })
+ const arrayBounds = [west, south, east, north];
+ expect(fromLatLngBounds(arrayBounds)).toBe(arrayBounds);
+ });
it("returns null if given an empty object", () => {
- expect(fromLatLngBounds({})).toBeNull
- })
+ expect(fromLatLngBounds({})).toBeNull;
+ });
it("throws an error if given an invalid bounds object", () => {
- expect(() => fromLatLngBounds({foo: "bar"})).toThrow()
- })
-})
+ expect(() => fromLatLngBounds({ foo: "bar" })).toThrow();
+ });
+});
-describe('toLatLngBounds', () => {
+describe("toLatLngBounds", () => {
it("converts array of [w, s, e, n] to leaflet LatLngBounds", () => {
- const arrayBounds = [west, south, east, north]
- const result = toLatLngBounds(arrayBounds)
+ const arrayBounds = [west, south, east, north];
+ const result = toLatLngBounds(arrayBounds);
- expect(result.getNorth()).toBe(north)
- expect(result.getSouth()).toBe(south)
- expect(result.getEast()).toBe(east)
- expect(result.getWest()).toBe(west)
- })
+ expect(result.getNorth()).toBe(north);
+ expect(result.getSouth()).toBe(south);
+ expect(result.getEast()).toBe(east);
+ expect(result.getWest()).toBe(west);
+ });
it("simply returns the LatLngBounds if passed one", () => {
- const bounds = L.latLngBounds(L.latLng({lat: north, lng: west}),
- L.latLng({lat: south, lng: east}))
- expect(toLatLngBounds(bounds)).toBe(bounds)
- })
+ const bounds = L.latLngBounds(
+ L.latLng({ lat: north, lng: west }),
+ L.latLng({ lat: south, lng: east }),
+ );
+ expect(toLatLngBounds(bounds)).toBe(bounds);
+ });
it("returns null if given an empty array", () => {
- expect(toLatLngBounds([])).toBeNull
- })
+ expect(toLatLngBounds([])).toBeNull;
+ });
it("throws an error if given an array with missing coordinates", () => {
- expect(() => toLatLngBounds([north, south, east])).toThrow()
- })
-})
+ expect(() => toLatLngBounds([north, south, east])).toThrow();
+ });
+});
diff --git a/src/services/Mapillary/Mapillary.js b/src/services/Mapillary/Mapillary.js
index f8b5c08ea..470a3ff75 100644
--- a/src/services/Mapillary/Mapillary.js
+++ b/src/services/Mapillary/Mapillary.js
@@ -1,18 +1,18 @@
-import _isEmpty from 'lodash/isEmpty'
-import _isArray from 'lodash/isArray'
-import _isFinite from 'lodash/isFinite'
+import _isArray from "lodash/isArray";
+import _isEmpty from "lodash/isEmpty";
+import _isFinite from "lodash/isFinite";
-const EMBED_URI_V4='https://www.mapillary.com/embed'
-const IMAGES_URI_V4='https://graph.mapillary.com/images'
-export const imageCache = new Map()
+const EMBED_URI_V4 = "https://www.mapillary.com/embed";
+const IMAGES_URI_V4 = "https://graph.mapillary.com/images";
+export const imageCache = new Map();
/**
* Returns true if Mapillary support is enabled (a Mapillary client token has been
* configured), false if not
*/
-export const isMapillaryEnabled = function() {
- return !_isEmpty(window.env.REACT_APP_MAPILLARY_CLIENT_TOKEN)
-}
+export const isMapillaryEnabled = function () {
+ return !_isEmpty(window.env.REACT_APP_MAPILLARY_CLIENT_TOKEN);
+};
/**
* Fetch Mapillary images of interest. Images are limited to the given WSEN
@@ -23,49 +23,55 @@ export const isMapillaryEnabled = function() {
* `context` fields. If an additional page of results is needed, the context
* will need to be passed to `nextMapillaryPage`
*/
-export const fetchMapillaryImages = async function(bbox, point=null, radius=250, lookAt=false, pageSize=100) {
+export const fetchMapillaryImages = async function (
+ bbox,
+ point = null,
+ radius = 250,
+ lookAt = false,
+ pageSize = 100,
+) {
if (!isMapillaryEnabled()) {
- throw new Error("Missing Mapillary client token")
+ throw new Error("Missing Mapillary client token");
}
- const cacheKey = JSON.stringify({ bbox, point, radius, lookAt, pageSize })
+ const cacheKey = JSON.stringify({ bbox, point, radius, lookAt, pageSize });
if (imageCache.has(cacheKey)) {
- return imageCache.get(cacheKey)
+ return imageCache.get(cacheKey);
}
try {
// bbox and point can be either arrays or strings with comma-separated coordinates
- const params = [`bbox=${_isArray(bbox) ? bbox.join(',') : bbox}`]
+ const params = [`bbox=${_isArray(bbox) ? bbox.join(",") : bbox}`];
if (point) {
- params.push(`closeto=${_isArray(point) ? point.join(',') : point}`)
+ params.push(`closeto=${_isArray(point) ? point.join(",") : point}`);
if (_isFinite(radius)) {
- params.push(`radius=${radius}`)
+ params.push(`radius=${radius}`);
}
if (lookAt) {
- params.push(`lookat=${_isArray(point) ? point.join(',') : point}`)
+ params.push(`lookat=${_isArray(point) ? point.join(",") : point}`);
}
}
- params.push(`limit=${pageSize}`)
- params.push(`access_token=${window.env.REACT_APP_MAPILLARY_CLIENT_TOKEN}`)
+ params.push(`limit=${pageSize}`);
+ params.push(`access_token=${window.env.REACT_APP_MAPILLARY_CLIENT_TOKEN}`);
- const result = await executeMapillaryImageFetch(`${IMAGES_URI_V4}?${params.join('&')}`)
- imageCache.set(cacheKey, result) // Cache the result
- return result
+ const result = await executeMapillaryImageFetch(`${IMAGES_URI_V4}?${params.join("&")}`);
+ imageCache.set(cacheKey, result); // Cache the result
+ return result;
} catch (error) {
- console.error('Error fetching Mapillary images:', error)
- throw new Error('Unable to fetch Mapillary images. Please try again.')
+ console.error("Error fetching Mapillary images:", error);
+ throw new Error("Unable to fetch Mapillary images. Please try again.");
}
-}
+};
/**
* Returns true if an additional page of Mapillary results is available based
* on the given result context, false otherwise
*/
-export const hasMoreMapillaryResults = function(resultContext) {
- return !!nextMapillaryPageUrl(resultContext)
-}
+export const hasMoreMapillaryResults = function (resultContext) {
+ return !!nextMapillaryPageUrl(resultContext);
+};
/**
* Fetch the next page of Mapillary images using the result context from a
@@ -74,7 +80,7 @@ export const hasMoreMapillaryResults = function(resultContext) {
* page of results is needed, the context in the result object will need to be
* passed to this method on the subsequent call
*/
-export const nextMapillaryPage = async function(resultContext) {
+export const nextMapillaryPage = async function (resultContext) {
try {
const nextPageUrl = nextMapillaryPageUrl(resultContext);
if (!nextPageUrl) {
@@ -83,24 +89,24 @@ export const nextMapillaryPage = async function(resultContext) {
return await executeMapillaryImageFetch(nextPageUrl);
} catch (error) {
- console.error('Error fetching next Mapillary page:', error);
- throw new Error('Unable to fetch next page of Mapillary images.');
+ console.error("Error fetching next Mapillary page:", error);
+ throw new Error("Unable to fetch next page of Mapillary images.");
}
-}
+};
/**
* Generates a Mapillary URL for a specific image based on the given image key
* and desired size. Acceptable image sizes are 320, 640, 1024, and 2048
*/
-export const mapillaryImageUrl = function(imageId) {
- return `${EMBED_URI_V4}?image_key=${imageId}`
-}
+export const mapillaryImageUrl = function (imageId) {
+ return `${EMBED_URI_V4}?image_key=${imageId}`;
+};
/**
* Extract the URL for the next page of Mapillary results from the given result
* context object and return it, or null if there is no next page of results
*/
-export const nextMapillaryPageUrl = function(resultContext) {
+export const nextMapillaryPageUrl = function (resultContext) {
try {
if (!resultContext || !resultContext.link) {
return null;
@@ -108,10 +114,10 @@ export const nextMapillaryPageUrl = function(resultContext) {
const parseLinkHeader = (linkHeader) => {
const links = {};
- console.log('Link Header:', linkHeader);
+ console.log("Link Header:", linkHeader);
if (linkHeader) {
- linkHeader.split(',').forEach(link => {
+ linkHeader.split(",").forEach((link) => {
const match = link.match(/<([^>]+)>\s*rel="([^"]+)"/);
if (match) {
const url = match[1];
@@ -121,7 +127,7 @@ export const nextMapillaryPageUrl = function(resultContext) {
});
}
- console.log('Parsed Links:', links);
+ console.log("Parsed Links:", links);
return links;
};
@@ -134,39 +140,41 @@ export const nextMapillaryPageUrl = function(resultContext) {
return links.next.url;
} catch (error) {
- console.error('Error extracting next Mapillary page URL:', error);
+ console.error("Error extracting next Mapillary page URL:", error);
return null;
}
-}
+};
/**
* Retrieve the active access token
*/
-export const getAccessToken = function() {
- return window.env.REACT_APP_MAPILLARY_CLIENT_TOKEN
-}
+export const getAccessToken = function () {
+ return window.env.REACT_APP_MAPILLARY_CLIENT_TOKEN;
+};
/**
* Fetches Mapillary results from the given URL and returns a result object
* with `geojson` and `context` fields on success
*/
-const executeMapillaryImageFetch = async function(mapillaryUrl) {
+const executeMapillaryImageFetch = async function (mapillaryUrl) {
try {
- const response = await fetch(mapillaryUrl)
+ const response = await fetch(mapillaryUrl);
if (!response.ok) {
- const errorDetails = await response.text()
- console.error(`Failed to fetch data from Mapillary: ${response.status} - ${errorDetails}`)
- throw new Error(`Failed to fetch data from Mapillary: ${response.status}`)
+ const errorDetails = await response.text();
+ console.error(`Failed to fetch data from Mapillary: ${response.status} - ${errorDetails}`);
+ throw new Error(`Failed to fetch data from Mapillary: ${response.status}`);
}
const result = {
context: {
- link: response.headers.get('link'), // used for pagination
+ link: response.headers.get("link"), // used for pagination
},
geojson: await response.json(),
- }
- return result
+ };
+ return result;
} catch (error) {
- console.error('Error executing Mapillary image fetch:', error)
- throw new Error('Unable to fetch data from Mapillary. Please check the URL or your network connection.')
+ console.error("Error executing Mapillary image fetch:", error);
+ throw new Error(
+ "Unable to fetch data from Mapillary. Please check the URL or your network connection.",
+ );
}
-}
+};
diff --git a/src/services/Mapillary/Mapillary.test.js b/src/services/Mapillary/Mapillary.test.js
index 0ca05fe41..baf9d3f6d 100644
--- a/src/services/Mapillary/Mapillary.test.js
+++ b/src/services/Mapillary/Mapillary.test.js
@@ -1,88 +1,92 @@
import {
- isMapillaryEnabled,
- fetchMapillaryImages,
- hasMoreMapillaryResults,
- nextMapillaryPage,
- mapillaryImageUrl,
- getAccessToken,
- imageCache,
-} from './Mapillary'
+ fetchMapillaryImages,
+ getAccessToken,
+ hasMoreMapillaryResults,
+ imageCache,
+ isMapillaryEnabled,
+ mapillaryImageUrl,
+ nextMapillaryPage,
+} from "./Mapillary";
-const mockFetch = vitest.fn()
-global.fetch = mockFetch
+const mockFetch = vitest.fn();
+global.fetch = mockFetch;
-describe('Mapillary Service Functions', () => {
+describe("Mapillary Service Functions", () => {
const cachedEnv = window.env;
beforeAll(() => {
vi.resetModules();
- window.env = { ...cachedEnv, REACT_APP_MAPILLARY_CLIENT_TOKEN: 'mockToken' };
- vitest.spyOn(console, 'error').mockImplementation(() => {})
- })
+ window.env = { ...cachedEnv, REACT_APP_MAPILLARY_CLIENT_TOKEN: "mockToken" };
+ vitest.spyOn(console, "error").mockImplementation(() => {});
+ });
afterAll(() => {
- console.error.mockRestore()
+ console.error.mockRestore();
window.env = cachedEnv;
- })
+ });
beforeEach(() => {
- vitest.clearAllMocks()
- window.env.REACT_APP_MAPILLARY_CLIENT_TOKEN = 'mockToken'
- })
+ vitest.clearAllMocks();
+ window.env.REACT_APP_MAPILLARY_CLIENT_TOKEN = "mockToken";
+ });
afterEach(() => {
- delete window.env.REACT_APP_MAPILLARY_CLIENT_TOKEN
- imageCache.clear()
- })
+ delete window.env.REACT_APP_MAPILLARY_CLIENT_TOKEN;
+ imageCache.clear();
+ });
- describe('isMapillaryEnabled', () => {
- it('should return true if the Mapillary client token is set', () => {
- expect(isMapillaryEnabled()).toBe(true)
- })
+ describe("isMapillaryEnabled", () => {
+ it("should return true if the Mapillary client token is set", () => {
+ expect(isMapillaryEnabled()).toBe(true);
+ });
- it('should return false if the Mapillary client token is not set', () => {
- delete window.env.REACT_APP_MAPILLARY_CLIENT_TOKEN
- expect(isMapillaryEnabled()).toBe(false)
- })
- })
+ it("should return false if the Mapillary client token is not set", () => {
+ delete window.env.REACT_APP_MAPILLARY_CLIENT_TOKEN;
+ expect(isMapillaryEnabled()).toBe(false);
+ });
+ });
- describe('fetchMapillaryImages', () => {
- it('should throw an error if Mapillary is not enabled', async () => {
- delete window.env.REACT_APP_MAPILLARY_CLIENT_TOKEN
- await expect(fetchMapillaryImages('0,0,1,1')).rejects.toThrow("Missing Mapillary client token")
- })
+ describe("fetchMapillaryImages", () => {
+ it("should throw an error if Mapillary is not enabled", async () => {
+ delete window.env.REACT_APP_MAPILLARY_CLIENT_TOKEN;
+ await expect(fetchMapillaryImages("0,0,1,1")).rejects.toThrow(
+ "Missing Mapillary client token",
+ );
+ });
- it('should handle invalid bbox input gracefully', async () => {
- await expect(fetchMapillaryImages(null)).rejects.toThrow('Unable to fetch Mapillary images. Please try again.')
- })
- })
+ it("should handle invalid bbox input gracefully", async () => {
+ await expect(fetchMapillaryImages(null)).rejects.toThrow(
+ "Unable to fetch Mapillary images. Please try again.",
+ );
+ });
+ });
- describe('hasMoreMapillaryResults', () => {
- it('should return false if there is no next page', () => {
- const resultContext = { link: '' }
- expect(hasMoreMapillaryResults(resultContext)).toBe(false)
- })
- })
+ describe("hasMoreMapillaryResults", () => {
+ it("should return false if there is no next page", () => {
+ const resultContext = { link: "" };
+ expect(hasMoreMapillaryResults(resultContext)).toBe(false);
+ });
+ });
- describe('nextMapillaryPage', () => {
- it('should return null if there is no next page', async () => {
- const resultContext = { link: '' }
- const result = await nextMapillaryPage(resultContext)
- expect(result).toBeNull()
- })
- })
+ describe("nextMapillaryPage", () => {
+ it("should return null if there is no next page", async () => {
+ const resultContext = { link: "" };
+ const result = await nextMapillaryPage(resultContext);
+ expect(result).toBeNull();
+ });
+ });
- describe('mapillaryImageUrl', () => {
- it('should generate the correct Mapillary image URL', () => {
- const imageId = 'abc123'
- const expectedUrl = `https://www.mapillary.com/embed?image_key=${imageId}`
- expect(mapillaryImageUrl(imageId)).toBe(expectedUrl)
- })
- })
+ describe("mapillaryImageUrl", () => {
+ it("should generate the correct Mapillary image URL", () => {
+ const imageId = "abc123";
+ const expectedUrl = `https://www.mapillary.com/embed?image_key=${imageId}`;
+ expect(mapillaryImageUrl(imageId)).toBe(expectedUrl);
+ });
+ });
- describe('getAccessToken', () => {
- it('should return the access token', () => {
- expect(getAccessToken()).toBe(window.env.REACT_APP_MAPILLARY_CLIENT_TOKEN)
- })
- })
-})
+ describe("getAccessToken", () => {
+ it("should return the access token", () => {
+ expect(getAccessToken()).toBe(window.env.REACT_APP_MAPILLARY_CLIENT_TOKEN);
+ });
+ });
+});
diff --git a/src/services/Notification/NotificationSubscription/NotificationSubscription.js b/src/services/Notification/NotificationSubscription/NotificationSubscription.js
index 5cadbd81a..8bb8b38ca 100644
--- a/src/services/Notification/NotificationSubscription/NotificationSubscription.js
+++ b/src/services/Notification/NotificationSubscription/NotificationSubscription.js
@@ -1,6 +1,6 @@
-import _map from "lodash/map";
-import _invert from "lodash/invert";
import _fromPairs from "lodash/fromPairs";
+import _invert from "lodash/invert";
+import _map from "lodash/map";
import messages, { subscriptionFrequencyMessages } from "./Messages";
// These statuses are defined on the server
@@ -26,35 +26,25 @@ export const SubscriptionFrequencyType = Object.freeze({
});
export const keysBySubscriptionType = Object.freeze(_invert(SubscriptionType));
-export const keysBySubscriptionFrequencyType = Object.freeze(
- _invert(SubscriptionFrequencyType)
-);
+export const keysBySubscriptionFrequencyType = Object.freeze(_invert(SubscriptionFrequencyType));
/**
* Returns an object mapping status values to raw internationalized
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesBySubscriptionType = _fromPairs(
- _map(messages, (message, key) => [SubscriptionType[key], message])
+ _map(messages, (message, key) => [SubscriptionType[key], message]),
);
export const messagesBySubscriptionFrequencyType = _fromPairs(
- _map(subscriptionFrequencyMessages, (message, key) => [
- SubscriptionType[key],
- message,
- ])
+ _map(subscriptionFrequencyMessages, (message, key) => [SubscriptionType[key], message]),
);
/** Returns object containing localized labels */
export const subscriptionTypeLabels = (intl) =>
- _fromPairs(
- _map(messages, (message, key) => [key, intl.formatMessage(message)])
- );
+ _fromPairs(_map(messages, (message, key) => [key, intl.formatMessage(message)]));
export const subscriptionFrequencyTypeLabels = (intl) =>
_fromPairs(
- _map(subscriptionFrequencyMessages, (message, key) => [
- key,
- intl.formatMessage(message),
- ])
+ _map(subscriptionFrequencyMessages, (message, key) => [key, intl.formatMessage(message)]),
);
diff --git a/src/services/Notification/NotificationType/Messages.js b/src/services/Notification/NotificationType/Messages.js
index 6bd9df01d..36dc046b8 100644
--- a/src/services/Notification/NotificationType/Messages.js
+++ b/src/services/Notification/NotificationType/Messages.js
@@ -59,7 +59,7 @@ export default defineMessages({
challengeComment: {
id: "Notification.type.challengeComment",
defaultMessage: "Challenge Comment",
- }
+ },
});
export const subscriptionCountMessages = defineMessages({
diff --git a/src/services/Notification/NotificationType/NotificationType.js b/src/services/Notification/NotificationType/NotificationType.js
index 0871aae61..632e3274d 100644
--- a/src/services/Notification/NotificationType/NotificationType.js
+++ b/src/services/Notification/NotificationType/NotificationType.js
@@ -1,6 +1,6 @@
-import _map from "lodash/map";
-import _invert from "lodash/invert";
import _fromPairs from "lodash/fromPairs";
+import _invert from "lodash/invert";
+import _map from "lodash/map";
import messages, { subscriptionCountMessages } from "./Messages";
// These statuses are defined on the server
@@ -58,7 +58,7 @@ export const keysWithCountTypes = Object.freeze(
_invert({
...NotificationType,
...NotificationCountType,
- })
+ }),
);
/**
@@ -66,26 +66,16 @@ export const keysWithCountTypes = Object.freeze(
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByNotificationType = _fromPairs(
- _map(messages, (message, key) => [NotificationType[key], message])
+ _map(messages, (message, key) => [NotificationType[key], message]),
);
export const messagesByNotificationCountType = _fromPairs(
- _map(subscriptionCountMessages, (message, key) => [
- NotificationType[key],
- message,
- ])
+ _map(subscriptionCountMessages, (message, key) => [NotificationType[key], message]),
);
/** Returns object containing localized labels */
export const notificationTypeLabels = (intl) =>
- _fromPairs(
- _map(messages, (message, key) => [key, intl.formatMessage(message)])
- );
+ _fromPairs(_map(messages, (message, key) => [key, intl.formatMessage(message)]));
export const notificationCountTypeLabels = (intl) =>
- _fromPairs(
- _map(subscriptionCountMessages, (message, key) => [
- key,
- intl.formatMessage(message),
- ])
- );
+ _fromPairs(_map(subscriptionCountMessages, (message, key) => [key, intl.formatMessage(message)]));
diff --git a/src/services/OSM/OSM.js b/src/services/OSM/OSM.js
index 727041f6d..9ad7fec9a 100644
--- a/src/services/OSM/OSM.js
+++ b/src/services/OSM/OSM.js
@@ -1,75 +1,84 @@
-import AppErrors from '../Error/AppErrors'
-import xmlToJSON from 'xmltojson'
-import { transform, map, each, isPlainObject } from 'lodash';
+import { each, isPlainObject, map, transform } from "lodash";
+import xmlToJSON from "xmltojson";
+import AppErrors from "../Error/AppErrors";
-const API_SERVER = window.env.REACT_APP_OSM_API_SERVER
+const API_SERVER = window.env.REACT_APP_OSM_API_SERVER;
const OSM_ERRORS = {
400: AppErrors.osm.requestTooLarge,
404: AppErrors.osm.elementMissing,
509: AppErrors.osm.bandwidthExceeded,
-}
+};
const handleOSMError = (response) => {
- const error = OSM_ERRORS[response.status] || AppErrors.osm.fetchFailure
- throw error
-}
+ const error = OSM_ERRORS[response.status] || AppErrors.osm.fetchFailure;
+ throw error;
+};
const handleFetchError = (error) => {
- console.error(error)
+ console.error(error);
if (Object.values(OSM_ERRORS).includes(error)) {
- throw error
+ throw error;
} else {
- throw AppErrors.osm.fetchFailure
+ throw AppErrors.osm.fetchFailure;
}
-}
+};
/**
* Normalize the xmlToJSON representation of XML attributes into key/value
* pairs that are a bit easier to use
*/
const normalizeAttributes = (json) => {
- if (Array.isArray(json)) return json.map(normalizeAttributes)
- if (!isPlainObject(json)) return json
+ if (Array.isArray(json)) return json.map(normalizeAttributes);
+ if (!isPlainObject(json)) return json;
return transform(json, (result, value, key) => {
- if (key === '_attr') {
- Object.assign(result, transform(value, (res, v, k) => { res[k] = v['_value'] }, {}))
- } else if (key !== '_text') {
- result[key] = normalizeAttributes(value)
+ if (key === "_attr") {
+ Object.assign(
+ result,
+ transform(
+ value,
+ (res, v, k) => {
+ res[k] = v["_value"];
+ },
+ {},
+ ),
+ );
+ } else if (key !== "_text") {
+ result[key] = normalizeAttributes(value);
}
- })
-}
+ });
+};
const fetchXMLData = async (uri) => {
try {
- const response = await fetch(uri)
+ const response = await fetch(uri);
if (response.ok) {
- const rawXML = await response.text()
- return new DOMParser().parseFromString(rawXML, 'application/xml')
+ const rawXML = await response.text();
+ return new DOMParser().parseFromString(rawXML, "application/xml");
} else {
- handleOSMError(response)
+ handleOSMError(response);
}
} catch (error) {
- handleFetchError(error)
+ handleFetchError(error);
}
-}
+};
/**
* Generates a URL to the given user's OSM profile page
*/
export const osmUserProfileURL = (osmUsername) => {
- return `${window.env.REACT_APP_OSM_SERVER}/user/${encodeURIComponent(osmUsername)}`
-}
+ return `${window.env.REACT_APP_OSM_SERVER}/user/${encodeURIComponent(osmUsername)}`;
+};
/**
* Retrieve the OpenStreetMap XML data with nodes/ways/relations for the given
* WSEN (comma-separated) bounding box string
*/
export const fetchOSMData = async (bbox) => {
- const uri = `${API_SERVER}/api/0.6/map?bbox=${bbox}`
- return fetchXMLData(uri)
-}
+ const uri = `${API_SERVER}/api/0.6/map?bbox=${bbox}`;
+ return fetchXMLData(uri);
+};
/**
* Retrieve the current OpenStreetMap data for the given element `
/`
@@ -81,13 +90,13 @@ export const fetchOSMData = async (bbox) => {
* from the JSON response)
*/
export const fetchOSMElement = async (idString, asXML = false) => {
- const uri = `${API_SERVER}/api/0.6/${idString}`
- const xmlDoc = await fetchXMLData(uri)
- if (asXML) return xmlDoc
+ const uri = `${API_SERVER}/api/0.6/${idString}`;
+ const xmlDoc = await fetchXMLData(uri);
+ if (asXML) return xmlDoc;
- const osmJSON = normalizeAttributes(xmlToJSON.parseXML(xmlDoc))
- return osmJSON?.osm?.[0]?.[idString.split('/')[0]]?.[0];
-}
+ const osmJSON = normalizeAttributes(xmlToJSON.parseXML(xmlDoc));
+ return osmJSON?.osm?.[0]?.[idString.split("/")[0]]?.[0];
+};
/**
* Retrieve the history for the given OpenStreetMap element string (e.g.
@@ -95,38 +104,39 @@ export const fetchOSMElement = async (idString, asXML = false) => {
* well (requiring an additional API call)
*/
export const fetchOSMElementHistory = async (idString, includeChangesets = false) => {
- if (!idString) return null
+ if (!idString) return null;
- const uri = `${API_SERVER}/api/0.6/${idString}/history.json`
+ const uri = `${API_SERVER}/api/0.6/${idString}/history.json`;
try {
- const response = await fetch(uri)
+ const response = await fetch(uri);
if (response.ok) {
- const history = await response.json()
+ const history = await response.json();
if (includeChangesets) {
- const changesetIds = map(history.elements, 'changeset')
- const changesetMap = new Map(await fetchOSMChangesets(changesetIds))
+ const changesetIds = map(history.elements, "changeset");
+ const changesetMap = new Map(await fetchOSMChangesets(changesetIds));
each(history.elements, (entry) => {
- if (changesetMap.has(entry.changeset)) entry.changeset = changesetMap.get(entry.changeset)
- })
+ if (changesetMap.has(entry.changeset))
+ entry.changeset = changesetMap.get(entry.changeset);
+ });
}
- return history.elements
+ return history.elements;
} else {
- handleOSMError(response)
+ handleOSMError(response);
}
} catch (error) {
- handleFetchError(error)
+ handleFetchError(error);
}
-}
+};
/**
* Retrieve the specified OpenStreetMap changesets
*/
export const fetchOSMChangesets = async (changesetIds) => {
- const uri = `${API_SERVER}/api/0.6/changesets?changesets=${changesetIds.join(',')}`
- const xmlDoc = await fetchXMLData(uri)
- const osmJSON = normalizeAttributes(xmlToJSON.parseXML(xmlDoc))
- return osmJSON?.osm?.[0]?.changeset || []
-}
+ const uri = `${API_SERVER}/api/0.6/changesets?changesets=${changesetIds.join(",")}`;
+ const xmlDoc = await fetchXMLData(uri);
+ const osmJSON = normalizeAttributes(xmlToJSON.parseXML(xmlDoc));
+ return osmJSON?.osm?.[0]?.changeset || [];
+};
/**
* Retrieve OpenStreetMap user data for the user with the given OSM user id
@@ -134,19 +144,19 @@ export const fetchOSMChangesets = async (changesetIds) => {
* redux store: it simply resolves the returned promise with the user data.
*/
export const fetchOSMUser = async (osmUserId) => {
- const osmUserURI = `${API_SERVER}/api/0.6/user/${osmUserId}`
+ const osmUserURI = `${API_SERVER}/api/0.6/user/${osmUserId}`;
try {
- const response = await fetch(osmUserURI)
+ const response = await fetch(osmUserURI);
if (response.ok) {
- const xmlData = await response.text()
- const displayNameMatch = /display_name="([^"]+)"/.exec(xmlData)
- return { id: osmUserId, displayName: displayNameMatch?.[1] || null }
+ const xmlData = await response.text();
+ const displayNameMatch = /display_name="([^"]+)"/.exec(xmlData);
+ return { id: osmUserId, displayName: displayNameMatch?.[1] || null };
} else if (response.status === 404) {
- return {}
+ return {};
} else {
- handleOSMError(response)
+ handleOSMError(response);
}
} catch (error) {
- handleFetchError(error)
+ handleFetchError(error);
}
-}
+};
diff --git a/src/services/OSM/OSM.test.jsx b/src/services/OSM/OSM.test.jsx
index c87fb95ab..7c5811a8e 100644
--- a/src/services/OSM/OSM.test.jsx
+++ b/src/services/OSM/OSM.test.jsx
@@ -1,209 +1,228 @@
-import { describe, it, expect, vi } from "vitest";
-import { osmUserProfileURL, fetchOSMData, fetchOSMElement, fetchOSMElementHistory, fetchOSMChangesets, fetchOSMUser } from './OSM'
-import AppErrors from '../Error/AppErrors'
-import xmltojson from 'xmltojson'
-
-describe('OSM Service Functions', () => {
- let originalConsoleError
- let parseXMLSpy
+import { describe, expect, it, vi } from "vitest";
+import xmltojson from "xmltojson";
+import AppErrors from "../Error/AppErrors";
+import {
+ fetchOSMChangesets,
+ fetchOSMData,
+ fetchOSMElement,
+ fetchOSMElementHistory,
+ fetchOSMUser,
+ osmUserProfileURL,
+} from "./OSM";
+
+describe("OSM Service Functions", () => {
+ let originalConsoleError;
+ let parseXMLSpy;
beforeEach(() => {
- originalConsoleError = console.error
- console.error = vi.fn()
- global.fetch = vi.fn()
- parseXMLSpy = vi.spyOn(xmltojson, 'parseXML')
- })
+ originalConsoleError = console.error;
+ console.error = vi.fn();
+ global.fetch = vi.fn();
+ parseXMLSpy = vi.spyOn(xmltojson, "parseXML");
+ });
afterEach(() => {
- console.error = originalConsoleError
- global.fetch.mockClear()
- parseXMLSpy.mockRestore()
- })
+ console.error = originalConsoleError;
+ global.fetch.mockClear();
+ parseXMLSpy.mockRestore();
+ });
- describe('osmUserProfileURL', () => {
- const baseURL = 'https://example.com'
+ describe("osmUserProfileURL", () => {
+ const baseURL = "https://example.com";
beforeAll(() => {
- window.env.REACT_APP_OSM_SERVER = baseURL
- })
+ window.env.REACT_APP_OSM_SERVER = baseURL;
+ });
afterAll(() => {
- delete window.env.REACT_APP_OSM_SERVER
- })
+ delete window.env.REACT_APP_OSM_SERVER;
+ });
- it('should construct URLs correctly', () => {
+ it("should construct URLs correctly", () => {
const testCases = [
- { username: 'testUser', expected: `${baseURL}/user/testUser` },
- { username: 'test@User', expected: `${baseURL}/user/test%40User` },
- { username: '', expected: `${baseURL}/user/` },
- ]
+ { username: "testUser", expected: `${baseURL}/user/testUser` },
+ { username: "test@User", expected: `${baseURL}/user/test%40User` },
+ { username: "", expected: `${baseURL}/user/` },
+ ];
testCases.forEach(({ username, expected }) => {
- expect(osmUserProfileURL(username)).toBe(expected)
- })
- })
- })
+ expect(osmUserProfileURL(username)).toBe(expected);
+ });
+ });
+ });
- describe('fetchOSMData', () => {
+ describe("fetchOSMData", () => {
const statusToError = {
400: AppErrors.osm.requestTooLarge,
509: AppErrors.osm.bandwidthExceeded,
500: AppErrors.osm.fetchFailure,
- }
+ };
Object.entries(statusToError).forEach(([status, error]) => {
test(`should handle ${status} error`, async () => {
- global.fetch.mockResolvedValueOnce({ ok: false, status: Number(status) })
- await expect(fetchOSMData('0,0,1,1')).rejects.toEqual(error)
- })
- })
+ global.fetch.mockResolvedValueOnce({ ok: false, status: Number(status) });
+ await expect(fetchOSMData("0,0,1,1")).rejects.toEqual(error);
+ });
+ });
- test('should handle network error', async () => {
- global.fetch.mockRejectedValueOnce(new Error('Network Error'))
- await expect(fetchOSMData('0,0,1,1')).rejects.toEqual(AppErrors.osm.fetchFailure)
- })
+ test("should handle network error", async () => {
+ global.fetch.mockRejectedValueOnce(new Error("Network Error"));
+ await expect(fetchOSMData("0,0,1,1")).rejects.toEqual(AppErrors.osm.fetchFailure);
+ });
const testXMLResponses = [
{ xml: ` `, expectedLength: 1 },
{ xml: ` `, expectedLength: 1 },
{ xml: ` `, expectedLength: 1 },
- ]
-
+ ];
+
testXMLResponses.forEach(({ expectedLength }) => {
test(`should resolve with XML response of length ${expectedLength}`, async () => {
- const emptyXML = ' '
+ const emptyXML = " ";
global.fetch.mockResolvedValueOnce({
ok: true,
text: vi.fn().mockResolvedValue(emptyXML),
- })
-
- const result = await fetchOSMData('0,0,1,1')
- const parser = new DOMParser()
- const xmlDoc = parser.parseFromString(result, 'application/xml')
- const elements = xmlDoc.querySelectorAll('*')
-
- expect(elements.length).toBe(expectedLength)
- })
- })
- })
-
- describe('fetchOSMElement', () => {
+ });
+
+ const result = await fetchOSMData("0,0,1,1");
+ const parser = new DOMParser();
+ const xmlDoc = parser.parseFromString(result, "application/xml");
+ const elements = xmlDoc.querySelectorAll("*");
+
+ expect(elements.length).toBe(expectedLength);
+ });
+ });
+ });
+
+ describe("fetchOSMElement", () => {
const statusToError = {
400: AppErrors.osm.requestTooLarge,
509: AppErrors.osm.bandwidthExceeded,
500: AppErrors.osm.fetchFailure,
- }
+ };
Object.entries(statusToError).forEach(([status, error]) => {
test(`should handle ${status} error`, async () => {
- global.fetch.mockResolvedValueOnce({ ok: false, status: Number(status) })
- await expect(fetchOSMElement('node/12345')).rejects.toEqual(error)
- })
- })
+ global.fetch.mockResolvedValueOnce({ ok: false, status: Number(status) });
+ await expect(fetchOSMElement("node/12345")).rejects.toEqual(error);
+ });
+ });
- test('should handle network error', async () => {
- global.fetch.mockRejectedValueOnce(new Error('Network Error'))
- await expect(fetchOSMElement('node/12345')).rejects.toEqual(AppErrors.osm.fetchFailure)
- })
+ test("should handle network error", async () => {
+ global.fetch.mockRejectedValueOnce(new Error("Network Error"));
+ await expect(fetchOSMElement("node/12345")).rejects.toEqual(AppErrors.osm.fetchFailure);
+ });
- test('should handle various OSM element scenarios', async () => {
+ test("should handle various OSM element scenarios", async () => {
const testCases = [
{
xml: ` `,
- elementId: 'way/1',
+ elementId: "way/1",
expected: { id: 1, lat: 1.1, lon: 1.1 },
},
{
xml: ` `,
- elementId: 'node/1',
+ elementId: "node/1",
expected: { id: 1, lat: 1.1, lon: 1.1 },
},
{
xml: `${' '.repeat(10)} `,
- elementId: 'node/1',
+ elementId: "node/1",
expected: { id: 1, lat: 1.1, lon: 1.1 },
},
{
xml: ` `,
- elementId: 'node/1',
+ elementId: "node/1",
expected: { id: 1, lat: 1.1, lon: 1.1 },
asXML: true,
},
- ]
+ ];
for (const { xml, elementId, expected, asXML } of testCases) {
global.fetch.mockResolvedValueOnce({
ok: true,
text: vi.fn().mockResolvedValue(xml),
- })
+ });
- const result = await fetchOSMElement(elementId, asXML)
+ const result = await fetchOSMElement(elementId, asXML);
if (asXML) {
- expect(result.querySelector('node')).toBeDefined()
+ expect(result.querySelector("node")).toBeDefined();
} else {
- expect(result).toEqual(expected)
+ expect(result).toEqual(expected);
}
}
- })
+ });
- test('should handle empty responses', async () => {
+ test("should handle empty responses", async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
- text: vi.fn().mockResolvedValue(' '),
- })
- const result = await fetchOSMElement('way/12345')
- expect(result).toBeUndefined()
- })
+ text: vi.fn().mockResolvedValue(" "),
+ });
+ const result = await fetchOSMElement("way/12345");
+ expect(result).toBeUndefined();
+ });
- test('should handle malformed XML gracefully', async () => {
+ test("should handle malformed XML gracefully", async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
- text: vi.fn().mockResolvedValue(''),
- })
- const result = await fetchOSMElement('way/12345')
- expect(result).toBeUndefined()
- })
- })
-
- describe('fetchOSMElementHistory', () => {
+ text: vi.fn().mockResolvedValue(""),
+ });
+ const result = await fetchOSMElement("way/12345");
+ expect(result).toBeUndefined();
+ });
+ });
+
+ describe("fetchOSMElementHistory", () => {
const testCases = [
{
- history: { elements: [{ id: '1', changeset: '100' }, { id: '2', changeset: '101' }] },
+ history: {
+ elements: [
+ { id: "1", changeset: "100" },
+ { id: "2", changeset: "101" },
+ ],
+ },
changesetsXML: ` `,
expectedLength: 2,
},
{
- history: { elements: [{ id: '1', changeset: '999' }, { id: '2', changeset: '888' }] },
- changesetsXML: ' ',
+ history: {
+ elements: [
+ { id: "1", changeset: "999" },
+ { id: "2", changeset: "888" },
+ ],
+ },
+ changesetsXML: " ",
expectedLength: 2,
},
- ]
+ ];
testCases.forEach(({ history, changesetsXML, expectedLength }) => {
- test('should handle changesets correctly', async () => {
+ test("should handle changesets correctly", async () => {
global.fetch
.mockResolvedValueOnce({ ok: true, json: vi.fn().mockResolvedValue(history) })
- .mockResolvedValueOnce({ ok: true, text: vi.fn().mockResolvedValue(changesetsXML) })
-
- const result = await fetchOSMElementHistory('way/12345', true)
- expect(result).toHaveLength(expectedLength)
- expect(result[0].changeset).toBe(history.elements[0].changeset)
- expect(result[1].changeset).toBe(history.elements[1].changeset)
- })
- })
-
- test('should handle 404 not found error', async () => {
- global.fetch.mockResolvedValueOnce({ ok: false, status: 404 })
- await expect(fetchOSMElementHistory('way/12345', true)).rejects.toEqual(AppErrors.osm.elementMissing)
- })
-
- test('should handle invalid element IDs gracefully', async () => {
+ .mockResolvedValueOnce({ ok: true, text: vi.fn().mockResolvedValue(changesetsXML) });
+
+ const result = await fetchOSMElementHistory("way/12345", true);
+ expect(result).toHaveLength(expectedLength);
+ expect(result[0].changeset).toBe(history.elements[0].changeset);
+ expect(result[1].changeset).toBe(history.elements[1].changeset);
+ });
+ });
+
+ test("should handle 404 not found error", async () => {
+ global.fetch.mockResolvedValueOnce({ ok: false, status: 404 });
+ await expect(fetchOSMElementHistory("way/12345", true)).rejects.toEqual(
+ AppErrors.osm.elementMissing,
+ );
+ });
+
+ test("should handle invalid element IDs gracefully", async () => {
const mockHistory = {
elements: [
- { id: 'invalid_id', changeset: '100' },
- { id: 'another_invalid_id', changeset: '101' }
- ]
- }
+ { id: "invalid_id", changeset: "100" },
+ { id: "another_invalid_id", changeset: "101" },
+ ],
+ };
global.fetch
.mockResolvedValueOnce({
@@ -212,125 +231,125 @@ describe('OSM Service Functions', () => {
})
.mockResolvedValueOnce({
ok: true,
- text: vi.fn().mockResolvedValue(' '),
- })
+ text: vi.fn().mockResolvedValue(" "),
+ });
- const history = await fetchOSMElementHistory('way/12345', true)
+ const history = await fetchOSMElementHistory("way/12345", true);
- expect(history).toHaveLength(2)
- expect(history[0].changeset).toBe('100')
- expect(history[1].changeset).toBe('101')
- })
- })
+ expect(history).toHaveLength(2);
+ expect(history[0].changeset).toBe("100");
+ expect(history[1].changeset).toBe("101");
+ });
+ });
- describe('fetchOSMChangesets', () => {
+ describe("fetchOSMChangesets", () => {
const statusToError = {
400: AppErrors.osm.requestTooLarge,
404: AppErrors.osm.elementMissing,
509: AppErrors.osm.bandwidthExceeded,
500: AppErrors.osm.fetchFailure,
- }
+ };
Object.entries(statusToError).forEach(([status, error]) => {
test(`should handle ${status} error`, async () => {
- global.fetch.mockResolvedValueOnce({ ok: false, status: Number(status) })
- await expect(fetchOSMChangesets(['123'])).rejects.toEqual(error)
- })
- })
-
- test('should handle network error', async () => {
- global.fetch.mockRejectedValueOnce(new Error('Network Error'))
- await expect(fetchOSMChangesets(['123'])).rejects.toEqual(AppErrors.osm.fetchFailure)
- })
-
- test('should handle valid changesets response', async () => {
- const changesetXML = ' '
+ global.fetch.mockResolvedValueOnce({ ok: false, status: Number(status) });
+ await expect(fetchOSMChangesets(["123"])).rejects.toEqual(error);
+ });
+ });
+
+ test("should handle network error", async () => {
+ global.fetch.mockRejectedValueOnce(new Error("Network Error"));
+ await expect(fetchOSMChangesets(["123"])).rejects.toEqual(AppErrors.osm.fetchFailure);
+ });
+
+ test("should handle valid changesets response", async () => {
+ const changesetXML = ' ';
global.fetch.mockResolvedValueOnce({
ok: true,
text: vi.fn().mockResolvedValue(changesetXML),
- })
-
- const result = await fetchOSMChangesets(['123'])
- expect(result.length).toBe(1)
- expect(result[0].id.toString()).toBe('1')
- expect(result[0].details).toBe('details 1')
- })
-
- test('should handle empty changesets response', async () => {
+ });
+
+ const result = await fetchOSMChangesets(["123"]);
+ expect(result.length).toBe(1);
+ expect(result[0].id.toString()).toBe("1");
+ expect(result[0].details).toBe("details 1");
+ });
+
+ test("should handle empty changesets response", async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
- text: vi.fn().mockResolvedValue(' '),
- })
+ text: vi.fn().mockResolvedValue(" "),
+ });
- const result = await fetchOSMChangesets(['123'])
- expect(result).toHaveLength(0)
- })
+ const result = await fetchOSMChangesets(["123"]);
+ expect(result).toHaveLength(0);
+ });
- test('should handle malformed XML gracefully', async () => {
+ test("should handle malformed XML gracefully", async () => {
global.fetch.mockResolvedValueOnce({
ok: true,
- text: vi.fn().mockResolvedValue(''),
- })
-
- const result = await fetchOSMChangesets(['123'])
- expect(result).toHaveLength(0)
- })
- })
- describe('fetchOSMUser', () => {
- test('should return user data with display name when successful', async () => {
- const osmUserId = '12345'
- const xmlResponse = ` `
-
+ text: vi.fn().mockResolvedValue(""),
+ });
+
+ const result = await fetchOSMChangesets(["123"]);
+ expect(result).toHaveLength(0);
+ });
+ });
+ describe("fetchOSMUser", () => {
+ test("should return user data with display name when successful", async () => {
+ const osmUserId = "12345";
+ const xmlResponse = ` `;
+
global.fetch.mockResolvedValueOnce({
ok: true,
text: vi.fn().mockResolvedValue(xmlResponse),
- })
-
- const result = await fetchOSMUser(osmUserId)
- expect(result).toEqual({ id: osmUserId, displayName: 'Test User' })
- })
-
- test('should return user data with null display name when no display name present', async () => {
- const osmUserId = '12345'
- const xmlResponse = ` `
-
+ });
+
+ const result = await fetchOSMUser(osmUserId);
+ expect(result).toEqual({ id: osmUserId, displayName: "Test User" });
+ });
+
+ test("should return user data with null display name when no display name present", async () => {
+ const osmUserId = "12345";
+ const xmlResponse = ` `;
+
global.fetch.mockResolvedValueOnce({
ok: true,
text: vi.fn().mockResolvedValue(xmlResponse),
- })
-
- const result = await fetchOSMUser(osmUserId)
- expect(result).toEqual({ id: osmUserId, displayName: null })
- })
-
- test('should return an empty object when 404 error is returned', async () => {
- const osmUserId = '12345'
-
+ });
+
+ const result = await fetchOSMUser(osmUserId);
+ expect(result).toEqual({ id: osmUserId, displayName: null });
+ });
+
+ test("should return an empty object when 404 error is returned", async () => {
+ const osmUserId = "12345";
+
global.fetch.mockResolvedValueOnce({
ok: false,
status: 404,
- })
-
- const result = await fetchOSMUser(osmUserId)
- expect(result).toEqual({})
- })
-
- test('should handle other HTTP errors', async () => {
- const osmUserId = '12345'
- const mockResponse = { ok: false, status: 500 }
-
- global.fetch.mockResolvedValueOnce(mockResponse)
-
- await expect(fetchOSMUser(osmUserId)).rejects.toEqual(AppErrors.osm.fetchFailure)
- })
-
- test('should handle network errors', async () => {
- const osmUserId = '12345'
- const mockError = new Error('Network Error')
-
- global.fetch.mockRejectedValueOnce(mockError)
-
- await expect(fetchOSMUser(osmUserId)).rejects.toEqual(AppErrors.osm.fetchFailure)
- })
- })
-})
+ });
+
+ const result = await fetchOSMUser(osmUserId);
+ expect(result).toEqual({});
+ });
+
+ test("should handle other HTTP errors", async () => {
+ const osmUserId = "12345";
+ const mockResponse = { ok: false, status: 500 };
+
+ global.fetch.mockResolvedValueOnce(mockResponse);
+
+ await expect(fetchOSMUser(osmUserId)).rejects.toEqual(AppErrors.osm.fetchFailure);
+ });
+
+ test("should handle network errors", async () => {
+ const osmUserId = "12345";
+ const mockError = new Error("Network Error");
+
+ global.fetch.mockRejectedValueOnce(mockError);
+
+ await expect(fetchOSMUser(osmUserId)).rejects.toEqual(AppErrors.osm.fetchFailure);
+ });
+ });
+});
diff --git a/src/services/OSMCha/OSMCha.js b/src/services/OSMCha/OSMCha.js
index 174d0ae1b..f0cfbc0dd 100644
--- a/src/services/OSMCha/OSMCha.js
+++ b/src/services/OSMCha/OSMCha.js
@@ -1,4 +1,4 @@
-import { format } from 'date-fns'
+import { format } from "date-fns";
/**
* View changesets in OSMCha. Sets up filters for given bbox, the given start
@@ -6,38 +6,40 @@ import { format } from 'date-fns'
* participating usernames
*/
export const viewOSMCha = (bboxArray, earliestDate, participantUsernames) => {
- window.open(buildOSMChaUrl(bboxArray, earliestDate, participantUsernames))
-}
+ window.open(buildOSMChaUrl(bboxArray, earliestDate, participantUsernames));
+};
/**
* Build OSMCha URL for the given filters
*/
export const buildOSMChaUrl = (bboxArray, earliestDate, participantUsernames) => {
- const filterParams = []
+ const filterParams = [];
// Setup bbox filter
if (bboxArray) {
- const bbox = bboxArray.join(',')
- filterParams.push(`"in_bbox":[{"label":"${bbox}","value":"${bbox}"}]`)
+ const bbox = bboxArray.join(",");
+ filterParams.push(`"in_bbox":[{"label":"${bbox}","value":"${bbox}"}]`);
}
// Setup start-date filter
if (earliestDate) {
- const startDate = format(earliestDate, "yyyy-MM-dd")
- filterParams.push(`"date__gte":[{"label":"${startDate}","value":"${startDate}"}]`)
- }
- else {
+ const startDate = format(earliestDate, "yyyy-MM-dd");
+ filterParams.push(`"date__gte":[{"label":"${startDate}","value":"${startDate}"}]`);
+ } else {
// OSMCha seems to want a blank date__gte rather than omitting it entirely
- filterParams.push('"date__gte":[{"label":"","value":""}]')
+ filterParams.push('"date__gte":[{"label":"","value":""}]');
}
// Setup user filter
if (participantUsernames && participantUsernames.length > 0) {
- const userList =
- participantUsernames.map(username => `{"label":"${username}","value":"${username}"}`)
- filterParams.push(`"users":[${userList.join(',')}]`)
+ const userList = participantUsernames.map(
+ (username) => `{"label":"${username}","value":"${username}"}`,
+ );
+ filterParams.push(`"users":[${userList.join(",")}]`);
}
- return `${window.env.REACT_APP_OSMCHA_SERVER}/?filters=` +
- encodeURIComponent(`{${filterParams.join(',')}}`)
-}
+ return (
+ `${window.env.REACT_APP_OSMCHA_SERVER}/?filters=` +
+ encodeURIComponent(`{${filterParams.join(",")}}`)
+ );
+};
diff --git a/src/services/OpenStreetCam/OpenStreetCam.js b/src/services/OpenStreetCam/OpenStreetCam.js
index 851366956..15112071e 100644
--- a/src/services/OpenStreetCam/OpenStreetCam.js
+++ b/src/services/OpenStreetCam/OpenStreetCam.js
@@ -1,15 +1,15 @@
-import _isArray from 'lodash/isArray'
-import _each from 'lodash/each'
+import _each from "lodash/each";
+import _isArray from "lodash/isArray";
-const API_URI='https://openstreetcam.org/1.0'
+const API_URI = "https://openstreetcam.org/1.0";
/**
* Returns true if OpenStreetCam support is enabled (an OpenStreetCam client id has been
* configured), false if not
*/
-export const isOpenStreetCamEnabled = function() {
- return window.env.REACT_APP_IMAGERY_OPENSTREETCAM === 'enabled'
-}
+export const isOpenStreetCamEnabled = function () {
+ return window.env.REACT_APP_IMAGERY_OPENSTREETCAM === "enabled";
+};
/**
* Fetch OpenStreetCam images of interest. Images are limited to the given WSEN
@@ -20,30 +20,32 @@ export const isOpenStreetCamEnabled = function() {
* `context` fields. If an additional page of results is needed, the context
* will need to be passed to `nextOpenStreetCamPage`
*/
-export const fetchOpenStreetCamImages = async function(bbox, pageSize=1000, page=1) {
+export const fetchOpenStreetCamImages = async function (bbox, pageSize = 1000, page = 1) {
if (!isOpenStreetCamEnabled()) {
- throw new Error("OpenStreetCam is not enabled")
+ throw new Error("OpenStreetCam is not enabled");
}
// bbox and point can be either arrays or strings with comma-separated coordinates
- const bounds = _isArray(bbox) ? bbox : bbox.split(',')
- const formData = new FormData()
- formData.append('bbBottomRight', `${bounds[1]},${bounds[2]}`)
- formData.append('bbTopLeft', `${bounds[3]},${bounds[0]}`)
- formData.append('pp', pageSize)
- formData.append('page', page)
+ const bounds = _isArray(bbox) ? bbox : bbox.split(",");
+ const formData = new FormData();
+ formData.append("bbBottomRight", `${bounds[1]},${bounds[2]}`);
+ formData.append("bbTopLeft", `${bounds[3]},${bounds[0]}`);
+ formData.append("pp", pageSize);
+ formData.append("page", page);
- return executeOpenStreetCamImageFetch(formData)
-}
+ return executeOpenStreetCamImageFetch(formData);
+};
/**
* Returns true if an additional page of OpenStreetCam results is available based
* on the given result context, false otherwise
*/
-export const hasMoreOpenStreetCamResults = function(resultContext) {
- return parseInt(resultContext.params.page) * parseInt(resultContext.params.pp) <
- parseInt(resultContext.totalFilteredItems)
-}
+export const hasMoreOpenStreetCamResults = function (resultContext) {
+ return (
+ parseInt(resultContext.params.page) * parseInt(resultContext.params.pp) <
+ parseInt(resultContext.totalFilteredItems)
+ );
+};
/**
* Fetch the next page of OpenStreetCam images using the result context from a
@@ -52,72 +54,71 @@ export const hasMoreOpenStreetCamResults = function(resultContext) {
* page of results is needed, the context in the result object will need to be
* passed to this method on the subsequent call
*/
-export const nextOpenStreetCamPage = async function(resultContext) {
+export const nextOpenStreetCamPage = async function (resultContext) {
if (!hasMoreOpenStreetCamResults(resultContext)) {
- return null
+ return null;
}
- const nextPageFormData = objectToFormData(resultContext.params)
- nextPageFormData.set('page', parseInt(nextPageFormData.get('page')) + 1)
- return executeOpenStreetCamImageFetch(nextPageFormData)
-}
+ const nextPageFormData = objectToFormData(resultContext.params);
+ nextPageFormData.set("page", parseInt(nextPageFormData.get("page")) + 1);
+ return executeOpenStreetCamImageFetch(nextPageFormData);
+};
/**
* Generates a OpenStreetCam URL for a specific image based on the given image
* item and desired size. OpenStreetCam supports thumbnails in two sizes: '200'
* and '1280'
*/
-export const openStreetCamImageUrl = function(imageItem, size='200') {
- const name = (size === '1280' ? imageItem.th_name : imageItem.lth_name)
+export const openStreetCamImageUrl = function (imageItem, size = "200") {
+ const name = size === "1280" ? imageItem.th_name : imageItem.lth_name;
// Format of thumbnail name is machine/path. For example, name:
// `storage11/files/photo/2019/6/6/proc/1469233_ce36f_221.jpg`
// should become URL:
// `storage11.openstreetcam.org/files/photo/2019/6/6/proc/1469233_ce36f_221.jpg`
- const separator = name.indexOf('/')
- const machine = name.slice(0, separator)
- const path = name.slice(separator)
- return `https://${machine}.openstreetcam.org${path}`
-}
+ const separator = name.indexOf("/");
+ const machine = name.slice(0, separator);
+ const path = name.slice(separator);
+ return `https://${machine}.openstreetcam.org${path}`;
+};
/**
* Fetches OpenStreetCam results from the given URL and returns a result object
* with `currentPageItems` and `context` fields on success
*/
-const executeOpenStreetCamImageFetch = async function(formData) {
- const url = `${API_URI}/list/nearby-photos/`
- const response = await fetch(url, { method: 'POST', body: formData })
+const executeOpenStreetCamImageFetch = async function (formData) {
+ const url = `${API_URI}/list/nearby-photos/`;
+ const response = await fetch(url, { method: "POST", body: formData });
if (response.ok) {
- const responseBody = await response.json()
+ const responseBody = await response.json();
return {
context: {
params: formDataToObject(formData),
totalFilteredItems: responseBody.totalFilteredItems[0],
},
- currentPageItems: responseBody.currentPageItems
- }
+ currentPageItems: responseBody.currentPageItems,
+ };
+ } else {
+ throw new Error("Failed to fetch data from OpenStreetCam");
}
- else {
- throw new Error("Failed to fetch data from OpenStreetCam")
- }
-}
+};
/**
* Utility function that converts a simple FormData to a plain object
*/
-const formDataToObject = function(formData) {
- const result = {}
+const formDataToObject = function (formData) {
+ const result = {};
for (const [key, value] of formData.entries()) {
- result[key] = value
+ result[key] = value;
}
- return result
-}
+ return result;
+};
/**
* Utility function that converts a plain object to a FormData
*/
-const objectToFormData = function(serialized) {
- const formData = new FormData()
- _each(serialized, (value, key) => formData.set(key, value))
- return formData
-}
+const objectToFormData = function (serialized) {
+ const formData = new FormData();
+ _each(serialized, (value, key) => formData.set(key, value));
+ return formData;
+};
diff --git a/src/services/OpenStreetCam/OpenStreetCam.test.js b/src/services/OpenStreetCam/OpenStreetCam.test.js
index 123574e5f..ace9b969f 100644
--- a/src/services/OpenStreetCam/OpenStreetCam.test.js
+++ b/src/services/OpenStreetCam/OpenStreetCam.test.js
@@ -1,11 +1,11 @@
-import { fetchOpenStreetCamImages } from './OpenStreetCam'
+import { fetchOpenStreetCamImages } from "./OpenStreetCam";
-describe('OpenStreetCam Service Functions', () => {
+describe("OpenStreetCam Service Functions", () => {
const cachedEnv = window.env;
beforeEach(() => {
vi.resetModules();
- window.env = { ...cachedEnv, REACT_APP_IMAGERY_OPENSTREETCAM: 'disabled' };
+ window.env = { ...cachedEnv, REACT_APP_IMAGERY_OPENSTREETCAM: "disabled" };
});
afterAll(() => {
@@ -13,22 +13,26 @@ describe('OpenStreetCam Service Functions', () => {
});
beforeEach(() => {
- vitest.clearAllMocks()
- window.env.REACT_APP_IMAGERY_OPENSTREETCAM = 'enabled'
- })
+ vitest.clearAllMocks();
+ window.env.REACT_APP_IMAGERY_OPENSTREETCAM = "enabled";
+ });
- it('should throw an error if OpenStreetCam is not enabled', async () => {
- window.env.REACT_APP_IMAGERY_OPENSTREETCAM = 'disabled'
- await expect(fetchOpenStreetCamImages('0,0,1,1')).rejects.toThrow("OpenStreetCam is not enabled")
- })
+ it("should throw an error if OpenStreetCam is not enabled", async () => {
+ window.env.REACT_APP_IMAGERY_OPENSTREETCAM = "disabled";
+ await expect(fetchOpenStreetCamImages("0,0,1,1")).rejects.toThrow(
+ "OpenStreetCam is not enabled",
+ );
+ });
- it('should fetch OpenStreetCam images correctly', async () => {
- const mockResponse = { currentPageItems: [], totalFilteredItems: [0] }
- const mockFetch = vitest.fn().mockResolvedValueOnce({ ok: true, json: vitest.fn().mockResolvedValue(mockResponse) })
- global.fetch = mockFetch
+ it("should fetch OpenStreetCam images correctly", async () => {
+ const mockResponse = { currentPageItems: [], totalFilteredItems: [0] };
+ const mockFetch = vitest
+ .fn()
+ .mockResolvedValueOnce({ ok: true, json: vitest.fn().mockResolvedValue(mockResponse) });
+ global.fetch = mockFetch;
- const result = await fetchOpenStreetCamImages('0,0,1,1')
- expect(result.currentPageItems).toEqual([])
- expect(mockFetch).toHaveBeenCalled()
- })
-})
+ const result = await fetchOpenStreetCamImages("0,0,1,1");
+ expect(result.currentPageItems).toEqual([]);
+ expect(mockFetch).toHaveBeenCalled();
+ });
+});
diff --git a/src/services/Overpass/Overpass.js b/src/services/Overpass/Overpass.js
index 81258e872..ecb5b8352 100644
--- a/src/services/Overpass/Overpass.js
+++ b/src/services/Overpass/Overpass.js
@@ -1,35 +1,43 @@
-import { addMinutes, isAfter } from 'date-fns'
-import { isJosmEditor, sendJOSMCommand } from '../Editor/Editor'
-import { fromLatLngBounds } from '../MapBounds/MapBounds'
+import { addMinutes, isAfter } from "date-fns";
+import { isJosmEditor, sendJOSMCommand } from "../Editor/Editor";
+import { fromLatLngBounds } from "../MapBounds/MapBounds";
/**
* View the AOI defined by the given bounds (either LatLngBounds or an array)
* as of the given date via Overpass attic query
*/
-export const viewAtticOverpass = (selectedEditor, actionDate, bounds, ignoreAtticOffset = false) => {
- let adjustedDateString = offsetAtticDateMoment(actionDate).toISOString()
+export const viewAtticOverpass = (
+ selectedEditor,
+ actionDate,
+ bounds,
+ ignoreAtticOffset = false,
+) => {
+ let adjustedDateString = offsetAtticDateMoment(actionDate).toISOString();
if (ignoreAtticOffset) {
- adjustedDateString = actionDate
+ adjustedDateString = actionDate;
}
- const bbox = overpassBBox(bounds).join(',')
+ const bbox = overpassBBox(bounds).join(",");
const query =
`[out:xml][timeout:150][bbox:${bbox}][date:"${adjustedDateString}"];` +
`( node(${bbox}); <; >; );` +
- 'out meta;'
+ "out meta;";
// Try sending to JOSM if it's user's chosen editor, otherwise Overpass Turbo.
if (isJosmEditor(selectedEditor)) {
- const overpassApiURL = 'https://overpass-api.de/api/interpreter?data=' + encodeURIComponent(query)
- sendJOSMCommand('http://127.0.0.1:8111/import?new_layer=true&layer_name=' +
- adjustedDateString + '&upload_policy=never&download_policy=never&url=' +
- encodeURIComponent(overpassApiURL))
+ const overpassApiURL =
+ "https://overpass-api.de/api/interpreter?data=" + encodeURIComponent(query);
+ sendJOSMCommand(
+ "http://127.0.0.1:8111/import?new_layer=true&layer_name=" +
+ adjustedDateString +
+ "&upload_policy=never&download_policy=never&url=" +
+ encodeURIComponent(overpassApiURL),
+ );
+ } else {
+ const overpassTurboURL = "https://overpass-turbo.eu/map.html?Q=" + encodeURIComponent(query);
+ window.open(overpassTurboURL);
}
- else {
- const overpassTurboURL = 'https://overpass-turbo.eu/map.html?Q=' + encodeURIComponent(query)
- window.open(overpassTurboURL)
- }
-}
+};
/**
* View augmented diff in achavi of the AOI defined by the given bounds (either
@@ -37,44 +45,42 @@ export const viewAtticOverpass = (selectedEditor, actionDate, bounds, ignoreAtti
*/
export const viewDiffOverpass = (bounds, firstDate, secondDate) => {
// order firstDate and secondDate into earlierDate and laterDate
- let earlierDate = new Date(firstDate)
- let laterDate = new Date(secondDate)
+ let earlierDate = new Date(firstDate);
+ let laterDate = new Date(secondDate);
if (isAfter(earlierDate, laterDate)) {
- earlierDate = new Date(secondDate)
- laterDate = new Date(firstDate)
+ earlierDate = new Date(secondDate);
+ laterDate = new Date(firstDate);
}
- const bbox = overpassBBox(bounds).join(',')
+ const bbox = overpassBBox(bounds).join(",");
const query =
`[out:xml][timeout:25][bbox:${bbox}]` +
`[adiff:"${earlierDate.toISOString()}","${offsetAtticDateMoment(laterDate).toISOString()}"];` +
`( node(${bbox}); <; >; );` +
- 'out meta geom qt;'
+ "out meta geom qt;";
// Send users to achavi for visualization of augmented diff
- const overpassURL = 'https://overpass-api.de/api/interpreter?data=' + encodeURIComponent(query)
- const achaviURL = 'https://overpass-api.de/achavi/?url=' + encodeURIComponent(overpassURL)
- window.open(achaviURL)
-}
-
+ const overpassURL = "https://overpass-api.de/api/interpreter?data=" + encodeURIComponent(query);
+ const achaviURL = "https://overpass-api.de/achavi/?url=" + encodeURIComponent(overpassURL);
+ window.open(achaviURL);
+};
/**
* Returns a Moment instance representing the action date of the given
* item. The timestamp is offset by the configured `atticQueryOffsetMinutes`
* number of minutes.
*/
-const offsetAtticDateMoment = actionDate => {
- return addMinutes(actionDate,
- window.env?.REACT_APP_ATTIC_QUERY_OFFSET_MINUTES ?? 10);
-}
+const offsetAtticDateMoment = (actionDate) => {
+ return addMinutes(actionDate, window.env?.REACT_APP_ATTIC_QUERY_OFFSET_MINUTES ?? 10);
+};
/**
* Get the task bounding box, transforming to SWNE (min lat, min lon, max lat, max lon) array
* as preferred by Overpass.
*/
-const overpassBBox = bounds => {
- const bbox = fromLatLngBounds(bounds)
+const overpassBBox = (bounds) => {
+ const bbox = fromLatLngBounds(bounds);
// Transform WSEN to SWNE that Overpass prefers
- return [bbox[1], bbox[0], bbox[3], bbox[2]]
-}
+ return [bbox[1], bbox[0], bbox[3], bbox[2]];
+};
diff --git a/src/services/Place/Place.js b/src/services/Place/Place.js
index dcd525fc1..ed8682e89 100644
--- a/src/services/Place/Place.js
+++ b/src/services/Place/Place.js
@@ -1,47 +1,48 @@
-import { schema } from 'normalizr'
-import RequestStatus from '../Server/RequestStatus'
-import { fetchContent } from '../Server/Server'
-import genericEntityReducer from '../Server/GenericEntityReducer'
-import _map from 'lodash/map'
+import _map from "lodash/map";
+import { schema } from "normalizr";
+import genericEntityReducer from "../Server/GenericEntityReducer";
+import RequestStatus from "../Server/RequestStatus";
+import { fetchContent } from "../Server/Server";
/** normalizr schema for places */
-export const placeSchema = function() {
- return new schema.Entity('places', {}, {idAttribute: 'place_id'})
-}
+export const placeSchema = function () {
+ return new schema.Entity("places", {}, { idAttribute: "place_id" });
+};
// redux actions
-export const RECEIVE_PLACE = 'RECEIVE_PLACE'
+export const RECEIVE_PLACE = "RECEIVE_PLACE";
// redux action creators
/**
* Add or update place data in the redux store
*/
-export const receivePlace = function(normalizedEntities) {
+export const receivePlace = function (normalizedEntities) {
return {
type: RECEIVE_PLACE,
status: RequestStatus.success,
entities: normalizedEntities,
- receivedAt: Date.now()
- }
-}
+ receivedAt: Date.now(),
+ };
+};
// async action creators
/**
* Retrieve a description of the place at the given latititude and longitude.
*/
-export const fetchPlace = function(lat, lng) {
- const placeURI =
- `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`
+export const fetchPlace = function (lat, lng) {
+ const placeURI = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`;
- return function(dispatch) {
- return fetchContent(placeURI, placeSchema(), {omitCredentials: true}).then(normalizedResults => {
- dispatch(receivePlace(normalizedResults.entities))
- return normalizedResults
- })
- }
-}
+ return function (dispatch) {
+ return fetchContent(placeURI, placeSchema(), { omitCredentials: true }).then(
+ (normalizedResults) => {
+ dispatch(receivePlace(normalizedResults.entities));
+ return normalizedResults;
+ },
+ );
+ };
+};
/**
* Retrieve a bounding box location of the place given.
@@ -49,27 +50,26 @@ export const fetchPlace = function(lat, lng) {
* @param placeSearch - place search string
* @return boundingBox array
*/
-export const fetchPlaceLocation = function(placeSearch, limit=5) {
- const placeURI =
- `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(placeSearch)}&format=json&limit=${limit}`
+export const fetchPlaceLocation = function (placeSearch, limit = 5) {
+ const placeURI = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(placeSearch)}&format=json&limit=${limit}`;
- return fetchContent(placeURI, null, {omitCredentials: true}).then(placeResults => {
+ return fetchContent(placeURI, null, { omitCredentials: true }).then((placeResults) => {
if (!placeResults) {
- return []
+ return [];
}
- return placeResults.map(place => {
- const bounds = _map(place.boundingbox, point => parseFloat(point))
+ return placeResults.map((place) => {
+ const bounds = _map(place.boundingbox, (point) => parseFloat(point));
return {
osmId: place.osm_id,
placeId: place.place_id,
name: place.display_name,
type: place.type,
bbox: [bounds[2], bounds[1], bounds[3], bounds[0]], // NSWE => WSEN
- }
- })
- })
-}
+ };
+ });
+ });
+};
// redux reducers
-export const placeEntities = genericEntityReducer(RECEIVE_PLACE, 'places')
+export const placeEntities = genericEntityReducer(RECEIVE_PLACE, "places");
diff --git a/src/services/Preferences/Preferences.js b/src/services/Preferences/Preferences.js
index cc9b9a0e3..d1b031767 100644
--- a/src/services/Preferences/Preferences.js
+++ b/src/services/Preferences/Preferences.js
@@ -1,57 +1,58 @@
-import _cloneDeep from 'lodash/cloneDeep'
-import _set from 'lodash/set'
-import _get from 'lodash/get'
-import _merge from 'lodash/merge'
-import _omit from 'lodash/omit'
+import _cloneDeep from "lodash/cloneDeep";
+import _get from "lodash/get";
+import _merge from "lodash/merge";
+import _omit from "lodash/omit";
+import _set from "lodash/set";
-export const CHALLENGES_PREFERENCE_GROUP = 'challenges'
-export const VIRTUAL_CHALLENGES_PREFERENCE_GROUP = 'virtualChallenges'
+export const CHALLENGES_PREFERENCE_GROUP = "challenges";
+export const VIRTUAL_CHALLENGES_PREFERENCE_GROUP = "virtualChallenges";
// redux actions
-export const SET_PREFERENCES = 'SET_PREFERENCES'
-export const REMOVE_PREFERENCES = 'REMOVE_PREFERENCES'
+export const SET_PREFERENCES = "SET_PREFERENCES";
+export const REMOVE_PREFERENCES = "REMOVE_PREFERENCES";
// redux action creators
-export const setPreferences = function(preferenceGroupName, preferenceSetting) {
+export const setPreferences = function (preferenceGroupName, preferenceSetting) {
return {
type: SET_PREFERENCES,
preferenceGroupName,
preferenceSetting,
- }
-}
+ };
+};
-export const removePreferences = function(preferenceGroupName, settingNames) {
+export const removePreferences = function (preferenceGroupName, settingNames) {
return {
type: REMOVE_PREFERENCES,
preferenceGroupName,
settingNames,
- }
-}
+ };
+};
// redux reducers
-export const currentPreferences = function(state={}, action) {
- let merged = null
+export const currentPreferences = function (state = {}, action) {
+ let merged = null;
- switch(action.type) {
+ switch (action.type) {
case SET_PREFERENCES:
- merged = _cloneDeep(state)
- _set(merged, action.preferenceGroupName,
- _merge(merged[action.preferenceGroupName], action.preferenceSetting))
+ merged = _cloneDeep(state);
+ _set(
+ merged,
+ action.preferenceGroupName,
+ _merge(merged[action.preferenceGroupName], action.preferenceSetting),
+ );
- return merged
+ return merged;
case REMOVE_PREFERENCES:
- merged = _cloneDeep(state)
- _set(merged,
- action.preferenceGroupName,
- Object.assign({},
- _omit(_get(state, action.preferenceGroupName),
- action.settingNames)
- )
- )
- return merged
+ merged = _cloneDeep(state);
+ _set(
+ merged,
+ action.preferenceGroupName,
+ Object.assign({}, _omit(_get(state, action.preferenceGroupName), action.settingNames)),
+ );
+ return merged;
default:
- return state
+ return state;
}
-}
+};
diff --git a/src/services/Preferences/Preferences.test.js b/src/services/Preferences/Preferences.test.js
index 4b29bb456..dffa84705 100644
--- a/src/services/Preferences/Preferences.test.js
+++ b/src/services/Preferences/Preferences.test.js
@@ -1,76 +1,75 @@
-import { setPreferences,
- removePreferences,
- currentPreferences,
- SET_PREFERENCES,
- REMOVE_PREFERENCES,
- CHALLENGES_PREFERENCE_GROUP } from './Preferences'
-
-let challengeId = null
-let challengePreferences = null
+import {
+ CHALLENGES_PREFERENCE_GROUP,
+ REMOVE_PREFERENCES,
+ SET_PREFERENCES,
+ currentPreferences,
+ removePreferences,
+ setPreferences,
+} from "./Preferences";
+
+let challengeId = null;
+let challengePreferences = null;
beforeEach(() => {
- challengeId = 123
+ challengeId = 123;
challengePreferences = {
[challengeId]: {
minimize: true,
collapseInstructions: true,
- }
- }
-})
+ },
+ };
+});
test("setPreferences sets the group and preference on returned action", () => {
- const group = "myGroup"
- const setting = {123: {mySetting: "foo"}}
- const action = setPreferences(group, setting)
+ const group = "myGroup";
+ const setting = { 123: { mySetting: "foo" } };
+ const action = setPreferences(group, setting);
- expect(action.type).toEqual(SET_PREFERENCES)
- expect(action.preferenceGroupName).toEqual(group)
- expect(action.preferenceSetting).toEqual(setting)
-})
+ expect(action.type).toEqual(SET_PREFERENCES);
+ expect(action.preferenceGroupName).toEqual(group);
+ expect(action.preferenceSetting).toEqual(setting);
+});
test("removePreferences sets the group and setting names on returned action", () => {
- const group = "myGroup"
- const settingNames = ['123.foo']
- const action = removePreferences(group, settingNames)
+ const group = "myGroup";
+ const settingNames = ["123.foo"];
+ const action = removePreferences(group, settingNames);
- expect(action.type).toEqual(REMOVE_PREFERENCES)
- expect(action.preferenceGroupName).toEqual(group)
- expect(action.settingNames).toEqual(settingNames)
-})
+ expect(action.type).toEqual(REMOVE_PREFERENCES);
+ expect(action.preferenceGroupName).toEqual(group);
+ expect(action.settingNames).toEqual(settingNames);
+});
test("currentPreferences adds a preference to an empty group", () => {
- const action = setPreferences(CHALLENGES_PREFERENCE_GROUP, challengePreferences)
- const reduced = currentPreferences({}, action)
+ const action = setPreferences(CHALLENGES_PREFERENCE_GROUP, challengePreferences);
+ const reduced = currentPreferences({}, action);
- expect(reduced[CHALLENGES_PREFERENCE_GROUP]).toEqual(challengePreferences)
-})
+ expect(reduced[CHALLENGES_PREFERENCE_GROUP]).toEqual(challengePreferences);
+});
test("currentPreferences overwrites existing preference setting", () => {
- const state = {[CHALLENGES_PREFERENCE_GROUP]: {[challengeId]: {minimize: false}}}
+ const state = { [CHALLENGES_PREFERENCE_GROUP]: { [challengeId]: { minimize: false } } };
- const action = setPreferences(CHALLENGES_PREFERENCE_GROUP, {[challengeId]: {minimize: true}})
- const reduced = currentPreferences(state, action)
+ const action = setPreferences(CHALLENGES_PREFERENCE_GROUP, { [challengeId]: { minimize: true } });
+ const reduced = currentPreferences(state, action);
- expect(reduced[CHALLENGES_PREFERENCE_GROUP][challengeId].minimize).toBe(true)
-})
+ expect(reduced[CHALLENGES_PREFERENCE_GROUP][challengeId].minimize).toBe(true);
+});
test("currentPreferences merges preferences to an existing preference set", () => {
- const state = {[CHALLENGES_PREFERENCE_GROUP]: {[challengeId]: {foo: "bar"}}}
+ const state = { [CHALLENGES_PREFERENCE_GROUP]: { [challengeId]: { foo: "bar" } } };
- const action = setPreferences(CHALLENGES_PREFERENCE_GROUP, challengePreferences)
- const reduced = currentPreferences(state, action)
+ const action = setPreferences(CHALLENGES_PREFERENCE_GROUP, challengePreferences);
+ const reduced = currentPreferences(state, action);
- expect(reduced[CHALLENGES_PREFERENCE_GROUP][challengeId].foo).toEqual("bar")
- expect(reduced[CHALLENGES_PREFERENCE_GROUP][challengeId].minimize).toBe(true)
-})
+ expect(reduced[CHALLENGES_PREFERENCE_GROUP][challengeId].foo).toEqual("bar");
+ expect(reduced[CHALLENGES_PREFERENCE_GROUP][challengeId].minimize).toBe(true);
+});
test("removePreferences can remove an individual preference setting", () => {
- const state = {[CHALLENGES_PREFERENCE_GROUP]: challengePreferences}
- const action = removePreferences(CHALLENGES_PREFERENCE_GROUP,
- [`${challengeId}.minimize`])
- const reduced = currentPreferences(state, action)
-
- expect(
- reduced[CHALLENGES_PREFERENCE_GROUP][challengeId].minimize
- ).toBeUndefined()
-})
+ const state = { [CHALLENGES_PREFERENCE_GROUP]: challengePreferences };
+ const action = removePreferences(CHALLENGES_PREFERENCE_GROUP, [`${challengeId}.minimize`]);
+ const reduced = currentPreferences(state, action);
+
+ expect(reduced[CHALLENGES_PREFERENCE_GROUP][challengeId].minimize).toBeUndefined();
+});
diff --git a/src/services/Project/Project.js b/src/services/Project/Project.js
index 4825ac47d..c4513f4a2 100644
--- a/src/services/Project/Project.js
+++ b/src/services/Project/Project.js
@@ -1,26 +1,26 @@
-import { schema } from "normalizr";
-import _isArray from "lodash/isArray";
+import { startOfDay } from "date-fns";
import _cloneDeep from "lodash/cloneDeep";
import _find from "lodash/find";
-import _map from "lodash/map";
+import _isArray from "lodash/isArray";
import _isFinite from "lodash/isFinite";
import _isUndefined from "lodash/isUndefined";
-import { startOfDay } from 'date-fns'
-import { defaultRoutes as api, isSecurityError } from "../Server/Server";
-import Endpoint from "../Server/Endpoint";
-import RequestStatus from "../Server/RequestStatus";
-import genericEntityReducer from "../Server/GenericEntityReducer";
+import _map from "lodash/map";
+import { schema } from "normalizr";
+import { setupCustomCache } from "../../utils/setupCustomCache";
import { RECEIVE_CHALLENGES } from "../Challenge/ChallengeActions";
-import { RESULTS_PER_PAGE } from "../Search/Search";
-import { addServerError, addError } from "../Error/Error";
import AppErrors from "../Error/AppErrors";
-import { findUser, ensureUserLoggedIn, fetchUser } from "../User/User";
-import { setupCustomCache } from "../../utils/setupCustomCache";
+import { addError, addServerError } from "../Error/Error";
+import { RESULTS_PER_PAGE } from "../Search/Search";
+import Endpoint from "../Server/Endpoint";
+import genericEntityReducer from "../Server/GenericEntityReducer";
+import RequestStatus from "../Server/RequestStatus";
+import { defaultRoutes as api, isSecurityError } from "../Server/Server";
+import { ensureUserLoggedIn, fetchUser, findUser } from "../User/User";
// 5 minute cache
const CACHE_TIME = 5 * 60 * 1000;
const PROJECT_ACTIVITY_CACHE = "projectActivity";
-const FEATURED_PROJECTS_CACHE = 'featuredProjects';
+const FEATURED_PROJECTS_CACHE = "featuredProjects";
const projectCache = setupCustomCache(CACHE_TIME);
/** normalizr schema for projects */
@@ -32,7 +32,7 @@ export const projectSchema = function () {
const RECEIVE_PROJECTS = "RECEIVE_PROJECTS";
const REMOVE_PROJECT = "REMOVE_PROJECT";
-export const PROJECT_CHALLENGE_LIMIT = 100
+export const PROJECT_CHALLENGE_LIMIT = 100;
// redux action creators
@@ -90,7 +90,7 @@ export const fetchManageableProjects = function (
page = null,
limit = RESULTS_PER_PAGE,
onlyOwned = false,
- onlyEnabled = false
+ onlyEnabled = false,
) {
const pageToFetch = _isFinite(page) ? page : 0;
@@ -107,7 +107,7 @@ export const fetchManageableProjects = function (
.catch((error) => {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
dispatch(addError(AppErrors.project.fetchFailure));
@@ -123,27 +123,27 @@ export const fetchManageableProjects = function (
export const fetchFeaturedProjects = function (
onlyEnabled = true,
limit = RESULTS_PER_PAGE,
- page = null
+ page = null,
) {
return function (dispatch) {
const pageToFetch = _isFinite(page) ? page : 0;
- const params = { onlyEnabled, limit, page: pageToFetch }
+ const params = { onlyEnabled, limit, page: pageToFetch };
const cachedFeaturedProjects = projectCache.get({}, params, FEATURED_PROJECTS_CACHE);
if (cachedFeaturedProjects) {
return new Promise((resolve) => {
dispatch(receiveProjects(cachedFeaturedProjects.entities));
- resolve(cachedFeaturedProjects)
- })
+ resolve(cachedFeaturedProjects);
+ });
}
return new Endpoint(api.projects.featured, {
schema: [projectSchema()],
- params
+ params,
})
.execute()
.then((normalizedResults) => {
- projectCache.set({}, params, normalizedResults, FEATURED_PROJECTS_CACHE)
+ projectCache.set({}, params, normalizedResults, FEATURED_PROJECTS_CACHE);
dispatch(receiveProjects(normalizedResults.entities));
return normalizedResults;
})
@@ -203,19 +203,12 @@ export const fetchProjectsById = function (projectIds) {
*
* @param {string} query - the search string
*/
-export const searchProjects = function (
- searchCriteria,
- limit = RESULTS_PER_PAGE
-) {
+export const searchProjects = function (searchCriteria, limit = RESULTS_PER_PAGE) {
const query = searchCriteria?.searchQuery;
- const onlyEnabled = _isUndefined(searchCriteria.onlyEnabled)
- ? true
- : searchCriteria.onlyEnabled;
+ const onlyEnabled = _isUndefined(searchCriteria.onlyEnabled) ? true : searchCriteria.onlyEnabled;
// We are just making sure the pqge passed in is a) present and b) a number
- const page = _isFinite(searchCriteria?.page)
- ? searchCriteria?.page
- : 0;
+ const page = _isFinite(searchCriteria?.page) ? searchCriteria?.page : 0;
return function (dispatch) {
return new Endpoint(api.projects.search, {
@@ -250,14 +243,11 @@ export const saveProject = function (projectData, user) {
// on whether it has an id.
const areCreating = !_isFinite(projectData.id);
- const saveEndpoint = new Endpoint(
- areCreating ? api.project.create : api.project.edit,
- {
- schema: projectSchema(),
- variables: { id: projectData.id },
- json: projectData,
- }
- );
+ const saveEndpoint = new Endpoint(areCreating ? api.project.create : api.project.edit, {
+ schema: projectSchema(),
+ variables: { id: projectData.id },
+ json: projectData,
+ });
return saveEndpoint
.execute()
@@ -276,7 +266,7 @@ export const saveProject = function (projectData, user) {
.catch((error) => {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
console.log(error.response || error);
@@ -308,7 +298,7 @@ export const archiveProject = (id, bool) => {
.catch((error) => {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
console.log(error.response || error);
@@ -349,14 +339,14 @@ export const fetchProjectActivity = function (projectId, startDate, endDate) {
},
};
- projectCache.set({}, params, normalizedResults, PROJECT_ACTIVITY_CACHE)
+ projectCache.set({}, params, normalizedResults, PROJECT_ACTIVITY_CACHE);
return dispatch(receiveProjects(normalizedResults.entities));
})
.catch((error) => {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
dispatch(addError(AppErrors.project.fetchFailure));
@@ -383,30 +373,25 @@ export const fetchProjectManagers = function (projectId) {
new Endpoint(api.project.managers, { variables: { projectId } })
.execute()
.then(
- (rawManagers) =>
- (normalizedResults.entities.projects[
- projectId
- ].managers = rawManagers)
+ (rawManagers) => (normalizedResults.entities.projects[projectId].managers = rawManagers),
),
- new Endpoint(api.teams.projectManagers, { variables: { projectId } })
- .execute()
- .then(
- (rawManagers) =>
- (normalizedResults.entities.projects[projectId].teamManagers = _map(
- rawManagers,
- (managingTeam) =>
- Object.assign({}, managingTeam.team, {
- roles: _map(managingTeam.grants, "role"),
- })
- ))
- ),
+ new Endpoint(api.teams.projectManagers, { variables: { projectId } }).execute().then(
+ (rawManagers) =>
+ (normalizedResults.entities.projects[projectId].teamManagers = _map(
+ rawManagers,
+ (managingTeam) =>
+ Object.assign({}, managingTeam.team, {
+ roles: _map(managingTeam.grants, "role"),
+ }),
+ )),
+ ),
])
.then(() => dispatch(receiveProjects(normalizedResults.entities)))
.catch((error) => {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
dispatch(addError(AppErrors.project.fetchFailure));
@@ -419,12 +404,7 @@ export const fetchProjectManagers = function (projectId) {
/**
* Set role for user on project
*/
-export const setProjectManagerRole = function (
- projectId,
- userId,
- isOSMUserId,
- role
-) {
+export const setProjectManagerRole = function (projectId, userId, isOSMUserId, role) {
return function (dispatch) {
return new Endpoint(api.project.setManagerPermission, {
variables: { userId, projectId, role },
@@ -445,7 +425,7 @@ export const setProjectManagerRole = function (
.catch((error) => {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
dispatch(addError(AppErrors.project.saveFailure));
@@ -493,7 +473,7 @@ export const removeProjectManager = function (projectId, userId, isOSMUserId) {
.catch((error) => {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
dispatch(addError(AppErrors.project.saveFailure));
@@ -520,7 +500,7 @@ export const deleteProject = function (projectId, immediate = false) {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
dispatch(addError(AppErrors.project.deleteFailure));
@@ -532,11 +512,7 @@ export const deleteProject = function (projectId, immediate = false) {
// redux reducers
-const reduceProjectsFurther = function (
- mergedState,
- oldState,
- projectEntities
-) {
+const reduceProjectsFurther = function (mergedState, oldState, projectEntities) {
// The generic reduction will merge arrays and objects, but for some
// fields we want to simply overwrite with the latest data.
projectEntities.forEach((entity) => {
@@ -571,7 +547,7 @@ export const projectEntities = function (state, action) {
return genericEntityReducer(
[RECEIVE_PROJECTS, RECEIVE_CHALLENGES],
"projects",
- reduceProjectsFurther
+ reduceProjectsFurther,
)(state, action);
}
};
diff --git a/src/services/Project/VirtualProject.js b/src/services/Project/VirtualProject.js
index bad81b14c..a664203e3 100644
--- a/src/services/Project/VirtualProject.js
+++ b/src/services/Project/VirtualProject.js
@@ -1,51 +1,54 @@
-import { defaultRoutes as api, isSecurityError } from '../Server/Server'
-import Endpoint from '../Server/Endpoint'
-import { addError } from '../Error/Error'
-import AppErrors from '../Error/AppErrors'
-import { ensureUserLoggedIn } from '../User/User'
-import { fetchProject } from './Project'
-import { fetchChallenge, fetchProjectChallenges } from '../Challenge/Challenge'
-
+import { fetchChallenge, fetchProjectChallenges } from "../Challenge/Challenge";
+import AppErrors from "../Error/AppErrors";
+import { addError } from "../Error/Error";
+import Endpoint from "../Server/Endpoint";
+import { defaultRoutes as api, isSecurityError } from "../Server/Server";
+import { ensureUserLoggedIn } from "../User/User";
+import { fetchProject } from "./Project";
/**
* Add the given challenge to the given virtual project.
*/
-export const addChallenge = function(challengeId, toProjectId) {
- return function(dispatch) {
- return new Endpoint(
- api.project.addToVirtual,
- {variables: {challengeId, projectId: toProjectId}}
- ).execute().then(() => {
- fetchProject(toProjectId)(dispatch) // Refresh challenge data
- fetchProjectChallenges(toProjectId, -1)(dispatch) // Refresh challenge data
- }).catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
+export const addChallenge = function (challengeId, toProjectId) {
+ return function (dispatch) {
+ return new Endpoint(api.project.addToVirtual, {
+ variables: { challengeId, projectId: toProjectId },
})
- }
-}
+ .execute()
+ .then(() => {
+ fetchProject(toProjectId)(dispatch); // Refresh challenge data
+ fetchProjectChallenges(toProjectId, -1)(dispatch); // Refresh challenge data
+ })
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ }
+ });
+ };
+};
/**
* Remove the given challenge from the given virtual project.
*/
-export const removeChallenge = function(challengeId, fromProjectId) {
- return function(dispatch) {
- return new Endpoint(
- api.project.removeFromVirtual,
- {variables: {challengeId, projectId: fromProjectId}}
- ).execute().then(() => {
- fetchChallenge(challengeId)(dispatch) // Refresh Challenge data
- fetchProject(fromProjectId)(dispatch) // Refresh Project data
- fetchProjectChallenges(fromProjectId)(dispatch) // Refresh challenge data
- }).catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
+export const removeChallenge = function (challengeId, fromProjectId) {
+ return function (dispatch) {
+ return new Endpoint(api.project.removeFromVirtual, {
+ variables: { challengeId, projectId: fromProjectId },
})
- }
-}
+ .execute()
+ .then(() => {
+ fetchChallenge(challengeId)(dispatch); // Refresh Challenge data
+ fetchProject(fromProjectId)(dispatch); // Refresh Project data
+ fetchProjectChallenges(fromProjectId)(dispatch); // Refresh challenge data
+ })
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ }
+ });
+ };
+};
diff --git a/src/services/RapidEditor/RapidEditor.js b/src/services/RapidEditor/RapidEditor.js
index 92459494f..5dce92ac6 100644
--- a/src/services/RapidEditor/RapidEditor.js
+++ b/src/services/RapidEditor/RapidEditor.js
@@ -1,8 +1,8 @@
-export const SET_RAPIDEDITOR = 'SET_RAPIDEDITOR';
+export const SET_RAPIDEDITOR = "SET_RAPIDEDITOR";
-const initialState = {
+const initialState = {
isRunning: false,
- hasUnsavedChanges: false
+ hasUnsavedChanges: false,
};
export function rapidEditor(state = initialState, action) {
diff --git a/src/services/Search/Messages.js b/src/services/Search/Messages.js
index 2d9f1207a..79a669231 100644
--- a/src/services/Search/Messages.js
+++ b/src/services/Search/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with Sort.
@@ -34,10 +34,10 @@ export default defineMessages({
},
score: {
id: "User.sort.numOfChallenges",
- defaultMessage: "Score"
+ defaultMessage: "Score",
},
default: {
id: "Challenge.sort.default",
defaultMessage: "Default",
},
-})
+});
diff --git a/src/services/Search/Search.js b/src/services/Search/Search.js
index 7f76e3509..f47323229 100644
--- a/src/services/Search/Search.js
+++ b/src/services/Search/Search.js
@@ -1,66 +1,72 @@
-import { format } from 'date-fns'
-import _uniqueId from 'lodash/uniqueId'
-import _cloneDeep from 'lodash/cloneDeep'
-import _clone from 'lodash/clone'
-import _set from 'lodash/set'
-import _isEmpty from 'lodash/isEmpty'
-import _isArray from 'lodash/isArray'
-import _omit from 'lodash/omit'
-import _fromPairs from 'lodash/fromPairs'
-import _map from 'lodash/map'
-import _isFinite from 'lodash/isFinite'
-import _isUndefined from 'lodash/isUndefined'
-import _findIndex from 'lodash/findIndex'
-import _split from 'lodash/split'
-import messages from './Messages'
-
-import { fromLatLngBounds } from '../MapBounds/MapBounds'
-import { CHALLENGE_LOCATION_WITHIN_MAPBOUNDS }
- from '../Challenge/ChallengeLocation/ChallengeLocation'
-import { REVIEW_STATUS_NOT_SET } from '../Task/TaskReview/TaskReviewStatus'
-
+import { format } from "date-fns";
+import _clone from "lodash/clone";
+import _cloneDeep from "lodash/cloneDeep";
+import _findIndex from "lodash/findIndex";
+import _fromPairs from "lodash/fromPairs";
+import _isArray from "lodash/isArray";
+import _isEmpty from "lodash/isEmpty";
+import _isFinite from "lodash/isFinite";
+import _isUndefined from "lodash/isUndefined";
+import _map from "lodash/map";
+import _omit from "lodash/omit";
+import _set from "lodash/set";
+import _split from "lodash/split";
+import _uniqueId from "lodash/uniqueId";
+import messages from "./Messages";
+
+import { CHALLENGE_LOCATION_WITHIN_MAPBOUNDS } from "../Challenge/ChallengeLocation/ChallengeLocation";
+import { fromLatLngBounds } from "../MapBounds/MapBounds";
+import { REVIEW_STATUS_NOT_SET } from "../Task/TaskReview/TaskReviewStatus";
// redux actions
-export const SET_SEARCH = 'SET_SEARCH'
-export const SET_COMPLETE_SEARCH = 'SET_COMPLETE_SEARCH'
-export const CLEAR_SEARCH = 'CLEAR_SEARCH'
-export const FETCHING_RESULTS = 'FETCHING_RESULTS'
-export const RECEIVED_RESULTS = 'RECEIVED_RESULTS'
-
-export const SET_SORT = 'SET_SORT'
-export const REMOVE_SORT = 'REMOVE_SORT'
+export const SET_SEARCH = "SET_SEARCH";
+export const SET_COMPLETE_SEARCH = "SET_COMPLETE_SEARCH";
+export const CLEAR_SEARCH = "CLEAR_SEARCH";
+export const FETCHING_RESULTS = "FETCHING_RESULTS";
+export const RECEIVED_RESULTS = "RECEIVED_RESULTS";
-export const SET_PAGE = 'SET_PAGE'
-export const REMOVE_PAGE = 'REMOVE_PAGE'
+export const SET_SORT = "SET_SORT";
+export const REMOVE_SORT = "REMOVE_SORT";
-export const SET_FILTERS = 'SET_FILTERS'
-export const REMOVE_FILTERS = 'REMOVE_FILTERS'
-export const CLEAR_FILTERS = 'CLEAR_FILTERS'
+export const SET_PAGE = "SET_PAGE";
+export const REMOVE_PAGE = "REMOVE_PAGE";
-export const SET_CHALLENGE_SEARCH_MAP_BOUNDS = 'SET_CHALLENGE_SEARCH_MAP_BOUNDS'
-export const SET_CHALLENGE_BROWSE_MAP_BOUNDS = 'SET_CHALLENGE_BROWSE_MAP_BOUNDS'
-export const SET_TASK_MAP_BOUNDS = 'SET_TASK_MAP_BOUNDS'
-export const SET_CHALLENGE_OWNER_MAP_BOUNDS = 'SET_CHALLENGE_OWNER_MAP_BOUNDS'
-export const CLEAR_MAP_BOUNDS = 'CLEAR_MAP_BOUNDS'
+export const SET_FILTERS = "SET_FILTERS";
+export const REMOVE_FILTERS = "REMOVE_FILTERS";
+export const CLEAR_FILTERS = "CLEAR_FILTERS";
+export const SET_CHALLENGE_SEARCH_MAP_BOUNDS = "SET_CHALLENGE_SEARCH_MAP_BOUNDS";
+export const SET_CHALLENGE_BROWSE_MAP_BOUNDS = "SET_CHALLENGE_BROWSE_MAP_BOUNDS";
+export const SET_TASK_MAP_BOUNDS = "SET_TASK_MAP_BOUNDS";
+export const SET_CHALLENGE_OWNER_MAP_BOUNDS = "SET_CHALLENGE_OWNER_MAP_BOUNDS";
+export const CLEAR_MAP_BOUNDS = "CLEAR_MAP_BOUNDS";
// Sort options
-export const SORT_NAME = 'name'
-export const SORT_CREATED = 'created'
-export const SORT_OLDEST = 'Created'
-export const SORT_POPULARITY = 'popularity'
-export const SORT_COMPLETION = 'completion_percentage'
-export const SORT_TASKS_REMAINING = 'tasks_remaining'
-export const SORT_COOPERATIVE_WORK = 'cooperative_type'
-export const SORT_DEFAULT = 'default'
-export const SORT_NUM_OF_CHALLENGES = 'num_of_challenges'
-export const SORT_SCORE = 'score'
-export const ALL_SORT_OPTIONS = [SORT_NAME, SORT_CREATED, SORT_OLDEST, SORT_POPULARITY, SORT_COOPERATIVE_WORK, SORT_COMPLETION, SORT_TASKS_REMAINING, SORT_DEFAULT]
-export const PROJECT_SORT_OPTIONS = [SORT_NAME, SORT_CREATED, SORT_OLDEST, SORT_DEFAULT]
-export const USER_SORT_OPTIONS = [SORT_NAME, SORT_CREATED, SORT_OLDEST, SORT_SCORE, SORT_DEFAULT]
+export const SORT_NAME = "name";
+export const SORT_CREATED = "created";
+export const SORT_OLDEST = "Created";
+export const SORT_POPULARITY = "popularity";
+export const SORT_COMPLETION = "completion_percentage";
+export const SORT_TASKS_REMAINING = "tasks_remaining";
+export const SORT_COOPERATIVE_WORK = "cooperative_type";
+export const SORT_DEFAULT = "default";
+export const SORT_NUM_OF_CHALLENGES = "num_of_challenges";
+export const SORT_SCORE = "score";
+export const ALL_SORT_OPTIONS = [
+ SORT_NAME,
+ SORT_CREATED,
+ SORT_OLDEST,
+ SORT_POPULARITY,
+ SORT_COOPERATIVE_WORK,
+ SORT_COMPLETION,
+ SORT_TASKS_REMAINING,
+ SORT_DEFAULT,
+];
+export const PROJECT_SORT_OPTIONS = [SORT_NAME, SORT_CREATED, SORT_OLDEST, SORT_DEFAULT];
+export const USER_SORT_OPTIONS = [SORT_NAME, SORT_CREATED, SORT_OLDEST, SORT_SCORE, SORT_DEFAULT];
// Default Results Per page
-export const RESULTS_PER_PAGE = 50
+export const RESULTS_PER_PAGE = 50;
export const SortOptions = {
name: SORT_NAME,
@@ -71,89 +77,91 @@ export const SortOptions = {
completion_percentage: SORT_COMPLETION,
tasks_remaining: SORT_TASKS_REMAINING,
default: SORT_DEFAULT,
-}
+};
// Map for the search parameters expected by server
export const PARAMS_MAP = {
- reviewRequestedBy: 'o',
- reviewedBy: 'r',
- metaReviewedBy: 'mr',
- completedBy: 'm',
- bundleId: 'bid',
- challengeId: 'cid',
- challenge: 'cs',
- projectId: 'pid',
- project: 'ps',
- status: 'tStatus',
- priority: 'tp',
- priorities: 'priorities',
- reviewStatus: 'trStatus',
- metaReviewStatus: 'mrStatus',
- id: 'tid',
- featureId: 'fid',
- difficulty: 'cd',
- tags: 'tt',
- excludeTasks: 'tExcl',
+ reviewRequestedBy: "o",
+ reviewedBy: "r",
+ metaReviewedBy: "mr",
+ completedBy: "m",
+ bundleId: "bid",
+ challengeId: "cid",
+ challenge: "cs",
+ projectId: "pid",
+ project: "ps",
+ status: "tStatus",
+ priority: "tp",
+ priorities: "priorities",
+ reviewStatus: "trStatus",
+ metaReviewStatus: "mrStatus",
+ id: "tid",
+ featureId: "fid",
+ difficulty: "cd",
+ tags: "tt",
+ excludeTasks: "tExcl",
archived: "ca",
- global: "cg"
-}
-
+ global: "cg",
+};
/** Returns object containing localized labels */
-export const sortLabels = intl => _fromPairs(
- _map(messages, (message, key) => [key, intl.formatMessage(message)])
-)
-
+export const sortLabels = (intl) =>
+ _fromPairs(_map(messages, (message, key) => [key, intl.formatMessage(message)]));
/**
* Parse the given raw query text that may include hashtags for keywords and
* return a query object that contains separate fields for the tags and query
* string, as well as easy access to the individual query and tag tokens.
*/
-export const parseQueryString = function(rawQueryText) {
- const tagTokens = []
- const queryTokens = []
+export const parseQueryString = function (rawQueryText) {
+ const tagTokens = [];
+ const queryTokens = [];
if (_isEmpty(rawQueryText)) {
return {
tagTokens,
- tags: '',
+ tags: "",
queryTokens,
- query: '',
- rawQueryText: '',
- }
+ query: "",
+ rawQueryText: "",
+ };
}
- const tokens = rawQueryText.split(/\s+/)
+ const tokens = rawQueryText.split(/\s+/);
for (let token of tokens) {
- if (token[0] === '#') {
+ if (token[0] === "#") {
if (token.length > 1) {
- tagTokens.push(token.slice(1))
+ tagTokens.push(token.slice(1));
}
- }
- else {
- queryTokens.push(token)
+ } else {
+ queryTokens.push(token);
}
}
return {
tagTokens,
- tags: tagTokens.join(','),
+ tags: tagTokens.join(","),
queryTokens,
- query: queryTokens.join(' '),
+ query: queryTokens.join(" "),
rawQueryText,
- }
-}
+ };
+};
/**
* Generates, from the given criteria, a search parameters string that the
* server accepts for various API endpoints
*/
-export const generateSearchParametersString = (filters, boundingBox, savedChallengesOnly,
- excludeOtherReviewers, queryString,
- invertFields = {}, excludeTasks) => {
- const searchParameters = {}
- const invf = []
+export const generateSearchParametersString = (
+ filters,
+ boundingBox,
+ savedChallengesOnly,
+ excludeOtherReviewers,
+ queryString,
+ invertFields = {},
+ excludeTasks,
+) => {
+ const searchParameters = {};
+ const invf = [];
if (filters.archived) {
searchParameters[PARAMS_MAP.archived] = filters.archived;
@@ -162,139 +170,133 @@ export const generateSearchParametersString = (filters, boundingBox, savedChalle
searchParameters[PARAMS_MAP.global] = filters.global;
}
if (filters.reviewRequestedBy) {
- searchParameters[PARAMS_MAP.reviewRequestedBy] = filters.reviewRequestedBy
+ searchParameters[PARAMS_MAP.reviewRequestedBy] = filters.reviewRequestedBy;
if (invertFields.reviewRequestedBy) {
- invf.push(PARAMS_MAP.reviewRequestedBy)
+ invf.push(PARAMS_MAP.reviewRequestedBy);
}
}
if (filters.reviewedBy) {
- searchParameters[PARAMS_MAP.reviewedBy] = filters.reviewedBy
+ searchParameters[PARAMS_MAP.reviewedBy] = filters.reviewedBy;
if (invertFields.reviewedBy) {
- invf.push(PARAMS_MAP.reviewedBy)
+ invf.push(PARAMS_MAP.reviewedBy);
}
}
if (filters.metaReviewedBy) {
- searchParameters[PARAMS_MAP.metaReviewedBy] = filters.metaReviewedBy
+ searchParameters[PARAMS_MAP.metaReviewedBy] = filters.metaReviewedBy;
if (invertFields.metaReviewedBy) {
- invf.push(PARAMS_MAP.metaReviewedBy)
+ invf.push(PARAMS_MAP.metaReviewedBy);
}
}
if (filters.completedBy) {
- searchParameters[PARAMS_MAP.completedBy] = filters.completedBy
+ searchParameters[PARAMS_MAP.completedBy] = filters.completedBy;
if (invertFields.completedBy) {
- invf.push(PARAMS_MAP.completedBy)
+ invf.push(PARAMS_MAP.completedBy);
}
}
if (filters.challengeId) {
- searchParameters.cid = !_isArray(filters.challengeId) ?
- filters.challengeId :
- searchParameters[PARAMS_MAP.challengeId] = filters.challengeId.join(',')
+ searchParameters.cid = !_isArray(filters.challengeId)
+ ? filters.challengeId
+ : (searchParameters[PARAMS_MAP.challengeId] = filters.challengeId.join(","));
if (invertFields.challenge) {
- invf.push(PARAMS_MAP.challengeId)
+ invf.push(PARAMS_MAP.challengeId);
}
- }
- else if (filters.challenge || filters.challengeName) {
- searchParameters[PARAMS_MAP.challenge] = filters.challenge || filters.challengeName
+ } else if (filters.challenge || filters.challengeName) {
+ searchParameters[PARAMS_MAP.challenge] = filters.challenge || filters.challengeName;
if (invertFields.challenge) {
- invf.push(PARAMS_MAP.challenge)
+ invf.push(PARAMS_MAP.challenge);
}
}
if (filters.projectId) {
- searchParameters[PARAMS_MAP.projectId] = filters.projectId
+ searchParameters[PARAMS_MAP.projectId] = filters.projectId;
if (invertFields.project) {
- invf.push(PARAMS_MAP.projectId)
+ invf.push(PARAMS_MAP.projectId);
}
- }
- else if (filters.project) {
- searchParameters[PARAMS_MAP.project] = filters.project
+ } else if (filters.project) {
+ searchParameters[PARAMS_MAP.project] = filters.project;
if (invertFields.project) {
- invf.push(PARAMS_MAP.project)
+ invf.push(PARAMS_MAP.project);
}
}
if (!_isUndefined(filters.status) && filters.status !== "all") {
- if (Array.isArray(filters.status)){
- searchParameters[PARAMS_MAP.status] = filters.status.join(',')
- }
- else {
- searchParameters[PARAMS_MAP.status] = filters.status
+ if (Array.isArray(filters.status)) {
+ searchParameters[PARAMS_MAP.status] = filters.status.join(",");
+ } else {
+ searchParameters[PARAMS_MAP.status] = filters.status;
}
if (invertFields.status) {
- invf.push(PARAMS_MAP.status)
+ invf.push(PARAMS_MAP.status);
}
}
if (!_isUndefined(filters.priority) && filters.priority !== "all") {
- searchParameters[PARAMS_MAP.priority] = filters.priority
+ searchParameters[PARAMS_MAP.priority] = filters.priority;
if (invertFields.priority) {
- invf.push(PARAMS_MAP.priority)
+ invf.push(PARAMS_MAP.priority);
}
}
if (!_isUndefined(filters.priorities) && filters.priorities !== "all") {
- if (Array.isArray(filters.priorities)){
- searchParameters[PARAMS_MAP.priorities] = filters.priorities.join(',')
- }
- else {
- searchParameters[PARAMS_MAP.priorities] = filters.priorities
+ if (Array.isArray(filters.priorities)) {
+ searchParameters[PARAMS_MAP.priorities] = filters.priorities.join(",");
+ } else {
+ searchParameters[PARAMS_MAP.priorities] = filters.priorities;
}
if (invertFields.priorities) {
- invf.push(PARAMS_MAP.priorities)
+ invf.push(PARAMS_MAP.priorities);
}
}
if (!_isUndefined(filters.reviewStatus) && filters.reviewStatus !== "all") {
- if (Array.isArray(filters.reviewStatus)){
- searchParameters[PARAMS_MAP.reviewStatus] = filters.reviewStatus.join(',')
- }
- else {
- searchParameters[PARAMS_MAP.reviewStatus] = filters.reviewStatus
+ if (Array.isArray(filters.reviewStatus)) {
+ searchParameters[PARAMS_MAP.reviewStatus] = filters.reviewStatus.join(",");
+ } else {
+ searchParameters[PARAMS_MAP.reviewStatus] = filters.reviewStatus;
}
if (invertFields.reviewStatus) {
- invf.push(PARAMS_MAP.reviewStatus)
+ invf.push(PARAMS_MAP.reviewStatus);
}
}
if (!_isUndefined(filters.metaReviewStatus) && filters.metaReviewStatus !== "all") {
- if (Array.isArray(filters.metaReviewStatus)){
- let metaReviewStatuses = _clone(filters.metaReviewStatus)
- const reviewStatus = Array.isArray(filters.reviewStatus) ?
- filters.reviewStatus : _split(filters.reviewStatus, ",")
+ if (Array.isArray(filters.metaReviewStatus)) {
+ let metaReviewStatuses = _clone(filters.metaReviewStatus);
+ const reviewStatus = Array.isArray(filters.reviewStatus)
+ ? filters.reviewStatus
+ : _split(filters.reviewStatus, ",");
- if (_findIndex(reviewStatus,
- x => x.toString() === REVIEW_STATUS_NOT_SET.toString()) > -1) {
+ if (_findIndex(reviewStatus, (x) => x.toString() === REVIEW_STATUS_NOT_SET.toString()) > -1) {
// If we are searching for tasks that have no associated review requests
// than we should also ask for those when applying the metaReviewStatus filter as well.
- metaReviewStatuses = filters.metaReviewStatus.concat(REVIEW_STATUS_NOT_SET)
+ metaReviewStatuses = filters.metaReviewStatus.concat(REVIEW_STATUS_NOT_SET);
}
- searchParameters[PARAMS_MAP.metaReviewStatus] = metaReviewStatuses.join(',')
- }
- else {
- searchParameters[PARAMS_MAP.metaReviewStatus] = filters.metaReviewStatus
+ searchParameters[PARAMS_MAP.metaReviewStatus] = metaReviewStatuses.join(",");
+ } else {
+ searchParameters[PARAMS_MAP.metaReviewStatus] = filters.metaReviewStatus;
}
if (invertFields.metaReviewStatus) {
- invf.push(PARAMS_MAP.metaReviewStatus)
+ invf.push(PARAMS_MAP.metaReviewStatus);
}
}
if (filters.reviewedAt) {
- searchParameters.startDate = format(filters.reviewedAt, 'yyyy-MM-dd')
- searchParameters.endDate = format(filters.reviewedAt, 'yyyy-MM-dd')
+ searchParameters.startDate = format(filters.reviewedAt, "yyyy-MM-dd");
+ searchParameters.endDate = format(filters.reviewedAt, "yyyy-MM-dd");
}
if (filters.mappedOn) {
- searchParameters.mo = format(filters.mappedOn, 'yyyy-MM-dd')
+ searchParameters.mo = format(filters.mappedOn, "yyyy-MM-dd");
}
if (filters.id) {
- searchParameters[PARAMS_MAP.id] = filters.id
+ searchParameters[PARAMS_MAP.id] = filters.id;
}
if (filters.featureId) {
- searchParameters[PARAMS_MAP.featureId] = filters.featureId
+ searchParameters[PARAMS_MAP.featureId] = filters.featureId;
}
if (_isFinite(filters.difficulty)) {
- searchParameters[PARAMS_MAP.difficulty] = filters.difficulty
+ searchParameters[PARAMS_MAP.difficulty] = filters.difficulty;
}
if (filters.bundleId) {
- searchParameters[PARAMS_MAP.bundleId] = filters.bundleId
+ searchParameters[PARAMS_MAP.bundleId] = filters.bundleId;
}
if (boundingBox) {
@@ -302,138 +304,136 @@ export const generateSearchParametersString = (filters, boundingBox, savedChalle
// challenge is also within those bounds
if (filters.location === CHALLENGE_LOCATION_WITHIN_MAPBOUNDS) {
if (_isArray(boundingBox)) {
- searchParameters.bb = boundingBox.join(',')
+ searchParameters.bb = boundingBox.join(",");
+ } else {
+ searchParameters.bb = boundingBox;
}
- else {
- searchParameters.bb = boundingBox
- }
- }
- else {
+ } else {
//tbb => [left, bottom, right, top] W/S/E/N
if (_isArray(boundingBox)) {
- searchParameters.tbb = boundingBox.join(',')
- }
- else {
- searchParameters.tbb = boundingBox
+ searchParameters.tbb = boundingBox.join(",");
+ } else {
+ searchParameters.tbb = boundingBox;
}
}
}
if (savedChallengesOnly) {
- searchParameters.onlySaved = savedChallengesOnly
+ searchParameters.onlySaved = savedChallengesOnly;
}
if (excludeOtherReviewers) {
- searchParameters.excludeOtherReviewers = excludeOtherReviewers
+ searchParameters.excludeOtherReviewers = excludeOtherReviewers;
}
if (queryString || filters.keywords) {
- const queryParts = parseQueryString(queryString)
+ const queryParts = parseQueryString(queryString);
// Keywords/tags can come from both the the query and the filter, so we need to
// combine them into a single keywords array.
- const keywords =
- queryParts.tagTokens.concat(_isArray(filters.keywords) ? filters.keywords : [])
+ const keywords = queryParts.tagTokens.concat(
+ _isArray(filters.keywords) ? filters.keywords : [],
+ );
if (keywords.length > 0) {
- searchParameters.ct = keywords.join(',')
+ searchParameters.ct = keywords.join(",");
}
if (queryParts.query.length > 0) {
- searchParameters[PARAMS_MAP.challenge] = queryParts.query
+ searchParameters[PARAMS_MAP.challenge] = queryParts.query;
}
}
if (filters.tags) {
- searchParameters[PARAMS_MAP.tags] = `${filters.tags}`.trim()
- }
+ searchParameters[PARAMS_MAP.tags] = `${filters.tags}`.trim();
+ }
if (excludeTasks && excludeTasks.length > 0) {
- searchParameters[PARAMS_MAP.excludeTasks] = excludeTasks.join(',')
+ searchParameters[PARAMS_MAP.excludeTasks] = excludeTasks.join(",");
}
- searchParameters.invf = invf.join(',')
+ searchParameters.invf = invf.join(",");
- return searchParameters
-}
+ return searchParameters;
+};
// redux action creators
-export const setCompleteSearch = function(searchName, searchObject) {
+export const setCompleteSearch = function (searchName, searchObject) {
return {
type: SET_COMPLETE_SEARCH,
searchName,
searchObject,
- }
-}
+ };
+};
-export const setSearch = function(searchName, query) {
+export const setSearch = function (searchName, query) {
return {
type: SET_SEARCH,
searchName,
query,
- }
-}
+ };
+};
-export const clearSearch = function(searchName) {
+export const clearSearch = function (searchName) {
return {
type: CLEAR_SEARCH,
searchName,
- }
-}
+ };
+};
-export const setSort = function(searchName, sortCriteria) {
+export const setSort = function (searchName, sortCriteria) {
return {
type: SET_SORT,
searchName,
sortCriteria,
- }
-}
+ };
+};
-export const removeSort = function(searchName, criteriaNames) {
+export const removeSort = function (searchName, criteriaNames) {
return {
type: REMOVE_SORT,
searchName,
criteriaNames,
- }
-}
+ };
+};
-export const setPage = function(searchName, page) {
+export const setPage = function (searchName, page) {
return {
type: SET_PAGE,
searchName,
page,
- }
-}
+ };
+};
-export const removePage = function(searchName) {
+export const removePage = function (searchName) {
return {
type: REMOVE_PAGE,
searchName,
- }
-}
+ };
+};
-export const setFilters = function(searchName, filterCriteria) {
+export const setFilters = function (searchName, filterCriteria) {
return {
type: SET_FILTERS,
searchName,
filterCriteria,
- }
-}
+ };
+};
-export const removeFilters = function(searchName, criteriaNames) {
+export const removeFilters = function (searchName, criteriaNames) {
return {
type: REMOVE_FILTERS,
searchName,
criteriaNames,
- }
-}
+ };
+};
-export const clearFilters = function(searchName) {
+export const clearFilters = function (searchName) {
return {
type: CLEAR_FILTERS,
searchName,
- }
-}
+ };
+};
/**
* Set the given bounds of the challenge search map in the redux store as the current
@@ -449,14 +449,14 @@ export const clearFilters = function(searchName) {
* action, false if the bounds are simply being altered in response
* to normal panning and zooming.
*/
-export const setChallengeSearchMapBounds = function(searchName, bounds, fromUserAction=false) {
+export const setChallengeSearchMapBounds = function (searchName, bounds, fromUserAction = false) {
return {
type: SET_CHALLENGE_SEARCH_MAP_BOUNDS,
searchName,
bounds: fromLatLngBounds(bounds),
fromUserAction,
- }
-}
+ };
+};
/**
* Set the given bounds of the task map in the redux store as the current
@@ -472,7 +472,13 @@ export const setChallengeSearchMapBounds = function(searchName, bounds, fromUser
* action, false if the bounds are simply being altered in response
* to normal panning and zooming.
*/
-export const setTaskMapBounds = function(searchName, taskId, bounds, zoom, fromUserAction=false) {
+export const setTaskMapBounds = function (
+ searchName,
+ taskId,
+ bounds,
+ zoom,
+ fromUserAction = false,
+) {
return {
type: SET_TASK_MAP_BOUNDS,
searchName,
@@ -480,8 +486,8 @@ export const setTaskMapBounds = function(searchName, taskId, bounds, zoom, fromU
bounds: fromLatLngBounds(bounds),
zoom,
fromUserAction,
- }
-}
+ };
+};
/**
* Update the redux store with the given bounds of a challenge-owner map.
@@ -489,202 +495,224 @@ export const setTaskMapBounds = function(searchName, taskId, bounds, zoom, fromU
* @param bounds - either a LatLngBounds instance or an array of
* [west, south, east, north]
*/
-export const setChallengeOwnerMapBounds = function(searchName, challengeId, bounds, zoom) {
+export const setChallengeOwnerMapBounds = function (searchName, challengeId, bounds, zoom) {
return {
type: SET_CHALLENGE_OWNER_MAP_BOUNDS,
searchName,
challengeId,
bounds: fromLatLngBounds(bounds),
zoom,
- }
-}
+ };
+};
/**
* Remove from the redux store with the bounds associated with the given search name
*/
-export const clearMapBounds = function(searchName) {
+export const clearMapBounds = function (searchName) {
return {
type: CLEAR_MAP_BOUNDS,
searchName,
- }
-}
-
+ };
+};
// The fetchId is used to help keep track of the latest
// fetch request.
-export const fetchingResults = function(searchName, fetchId) {
+export const fetchingResults = function (searchName, fetchId) {
return {
type: FETCHING_RESULTS,
searchName,
fetchId,
- }
-}
+ };
+};
// The fetchId is used to help keep track of multiple
// simultaneous fetches.
-export const receivedResults = function(searchName, fetchId) {
+export const receivedResults = function (searchName, fetchId) {
return {
type: RECEIVED_RESULTS,
searchName,
fetchId,
- timestamp: (new Date()).getTime(),
- }
-}
+ timestamp: new Date().getTime(),
+ };
+};
// async action creators
-export const performSearch = function(searchName, query, asyncSearchAction, props) {
- return function(dispatch) {
- const fetchId = _uniqueId()
+export const performSearch = function (searchName, query, asyncSearchAction, props) {
+ return function (dispatch) {
+ const fetchId = _uniqueId();
if (!query || query.length < 2) {
- return null
+ return null;
}
- const resultsPerPage = query?.page?.resultsPerPage
- const actionToDo = asyncSearchAction(query, resultsPerPage, props)
+ const resultsPerPage = query?.page?.resultsPerPage;
+ const actionToDo = asyncSearchAction(query, resultsPerPage, props);
if (actionToDo) {
- dispatch(fetchingResults(searchName, fetchId))
- return dispatch(
- actionToDo
- ).then(() => dispatch(receivedResults(searchName, fetchId)))
- .catch((error) => {
- // 404 indicates no results.
- if (error.response && error.response.status === 404) {
- dispatch(receivedResults(searchName, fetchId))
- }
- })
+ dispatch(fetchingResults(searchName, fetchId));
+ return dispatch(actionToDo)
+ .then(() => dispatch(receivedResults(searchName, fetchId)))
+ .catch((error) => {
+ // 404 indicates no results.
+ if (error.response && error.response.status === 404) {
+ dispatch(receivedResults(searchName, fetchId));
+ }
+ });
}
};
-}
-
+};
// redux reducers
-export const currentSearch = function(state={}, action) {
- let mergedState = null
+export const currentSearch = function (state = {}, action) {
+ let mergedState = null;
- switch(action.type) {
+ switch (action.type) {
case SET_COMPLETE_SEARCH:
- mergedState = _cloneDeep(state)
- _set(mergedState, action.searchName, action.searchObject)
- return mergedState
+ mergedState = _cloneDeep(state);
+ _set(mergedState, action.searchName, action.searchObject);
+ return mergedState;
case SET_SEARCH:
- mergedState = _cloneDeep(state)
- _set(mergedState, `${action.searchName}.query`, action.query)
- _set(mergedState, `${action.searchName}.page`, null)
- return mergedState
+ mergedState = _cloneDeep(state);
+ _set(mergedState, `${action.searchName}.query`, action.query);
+ _set(mergedState, `${action.searchName}.page`, null);
+ return mergedState;
case CLEAR_SEARCH:
- mergedState = _cloneDeep(state)
- _set(mergedState, `${action.searchName}.query`, null)
- _set(mergedState, `${action.searchName}.page`, null)
- return mergedState
+ mergedState = _cloneDeep(state);
+ _set(mergedState, `${action.searchName}.query`, null);
+ _set(mergedState, `${action.searchName}.page`, null);
+ return mergedState;
case FETCHING_RESULTS:
- mergedState = _cloneDeep(state)
- _set(mergedState, `${action.searchName}.meta.fetchingResults`, action.fetchId)
- return mergedState
+ mergedState = _cloneDeep(state);
+ _set(mergedState, `${action.searchName}.meta.fetchingResults`, action.fetchId);
+ return mergedState;
case RECEIVED_RESULTS:
// If the fetchId of the action doesn't match the latest fetchId in the
// state, ignore this action since we're still fetching other results.
- if (action.fetchId !== (state[action.searchName]?.meta?.fetchingResults)) {
- return state
- }
- else {
- mergedState = _cloneDeep(state)
- _set(mergedState, `${action.searchName}.meta.fetchingResults`, null)
- _set(mergedState, `${action.searchName}.meta.receivedAt`, action.timestamp)
- return mergedState
+ if (action.fetchId !== state[action.searchName]?.meta?.fetchingResults) {
+ return state;
+ } else {
+ mergedState = _cloneDeep(state);
+ _set(mergedState, `${action.searchName}.meta.fetchingResults`, null);
+ _set(mergedState, `${action.searchName}.meta.receivedAt`, action.timestamp);
+ return mergedState;
}
case SET_SORT:
- mergedState = _cloneDeep(state)
- _set(mergedState, `${action.searchName}.sort`,
- Object.assign({}, state[action.searchName]?.sort, action.sortCriteria))
- _set(mergedState, `${action.searchName}.page`, null)
- return mergedState
+ mergedState = _cloneDeep(state);
+ _set(
+ mergedState,
+ `${action.searchName}.sort`,
+ Object.assign({}, state[action.searchName]?.sort, action.sortCriteria),
+ );
+ _set(mergedState, `${action.searchName}.page`, null);
+ return mergedState;
case REMOVE_SORT:
- mergedState = _cloneDeep(state)
- _set(mergedState, `${action.searchName}.sort`,
- Object.assign({}, _omit(state[action.searchName]?.sort, action.criteriaNames)))
- _set(mergedState, `${action.searchName}.page`, null)
- return mergedState
+ mergedState = _cloneDeep(state);
+ _set(
+ mergedState,
+ `${action.searchName}.sort`,
+ Object.assign({}, _omit(state[action.searchName]?.sort, action.criteriaNames)),
+ );
+ _set(mergedState, `${action.searchName}.page`, null);
+ return mergedState;
case SET_PAGE:
- mergedState = _cloneDeep(state)
- _set(mergedState, `${action.searchName}.page`,
- Object.assign({}, state[action.searchName]?.page, action.page))
- return mergedState
+ mergedState = _cloneDeep(state);
+ _set(
+ mergedState,
+ `${action.searchName}.page`,
+ Object.assign({}, state[action.searchName]?.page, action.page),
+ );
+ return mergedState;
case REMOVE_PAGE:
- mergedState = _cloneDeep(state)
- _set(mergedState, `${action.searchName}.page`,
- Object.assign({}, _omit(state[action.searchName]?.page)))
- return mergedState
+ mergedState = _cloneDeep(state);
+ _set(
+ mergedState,
+ `${action.searchName}.page`,
+ Object.assign({}, _omit(state[action.searchName]?.page)),
+ );
+ return mergedState;
case SET_FILTERS:
- mergedState = _cloneDeep(state)
- _set(mergedState, `${action.searchName}.filters`,
- Object.assign({}, state[action.searchName]?.filters, action.filterCriteria))
- _set(mergedState, `${action.searchName}.page`, null)
- return mergedState
+ mergedState = _cloneDeep(state);
+ _set(
+ mergedState,
+ `${action.searchName}.filters`,
+ Object.assign({}, state[action.searchName]?.filters, action.filterCriteria),
+ );
+ _set(mergedState, `${action.searchName}.page`, null);
+ return mergedState;
case REMOVE_FILTERS:
- mergedState = _cloneDeep(state)
- _set(mergedState, `${action.searchName}.filters`,
- Object.assign({}, _omit(state[action.searchName]?.filters, action.criteriaNames)))
- _set(mergedState, `${action.searchName}.page`, null)
- return mergedState
+ mergedState = _cloneDeep(state);
+ _set(
+ mergedState,
+ `${action.searchName}.filters`,
+ Object.assign({}, _omit(state[action.searchName]?.filters, action.criteriaNames)),
+ );
+ _set(mergedState, `${action.searchName}.page`, null);
+ return mergedState;
case CLEAR_FILTERS:
- return Object.assign({}, _omit(state, `${action.searchName}.filters`))
+ return Object.assign({}, _omit(state, `${action.searchName}.filters`));
case SET_CHALLENGE_SEARCH_MAP_BOUNDS:
- mergedState = _cloneDeep(state)
- _set(mergedState, `${action.searchName}.mapBounds`,
- Object.assign({}, state[action.searchName]?.mapBounds,
- {
- bounds: action.bounds,
- fromUserAction: action.fromUserAction,
- }))
- return mergedState
+ mergedState = _cloneDeep(state);
+ _set(
+ mergedState,
+ `${action.searchName}.mapBounds`,
+ Object.assign({}, state[action.searchName]?.mapBounds, {
+ bounds: action.bounds,
+ fromUserAction: action.fromUserAction,
+ }),
+ );
+ return mergedState;
case SET_CHALLENGE_BROWSE_MAP_BOUNDS:
- mergedState = _cloneDeep(state)
- _set(mergedState, `${action.searchName}.mapBounds`,
- Object.assign({}, state[action.searchName]?.mapBounds,
- {
- challengeId: action.challengeId,
- bounds: action.bounds,
- zoom: action.zoom,
- }))
- return mergedState
+ mergedState = _cloneDeep(state);
+ _set(
+ mergedState,
+ `${action.searchName}.mapBounds`,
+ Object.assign({}, state[action.searchName]?.mapBounds, {
+ challengeId: action.challengeId,
+ bounds: action.bounds,
+ zoom: action.zoom,
+ }),
+ );
+ return mergedState;
case SET_TASK_MAP_BOUNDS:
- mergedState = _cloneDeep(state)
- _set(mergedState, `${action.searchName}.mapBounds`,
- Object.assign({}, state[action.searchName]?.mapBounds,
- {
- taskId: action.taskId,
- bounds: action.bounds,
- zoom: action.zoom,
- fromUserAction: action.fromUserAction,
- }))
- return mergedState
+ mergedState = _cloneDeep(state);
+ _set(
+ mergedState,
+ `${action.searchName}.mapBounds`,
+ Object.assign({}, state[action.searchName]?.mapBounds, {
+ taskId: action.taskId,
+ bounds: action.bounds,
+ zoom: action.zoom,
+ fromUserAction: action.fromUserAction,
+ }),
+ );
+ return mergedState;
case SET_CHALLENGE_OWNER_MAP_BOUNDS:
- mergedState = _cloneDeep(state)
- _set(mergedState, `${action.searchName}.mapBounds`,
- Object.assign({}, state[action.searchName]?.mapBounds,
- {
- challengeId: action.challengeId,
- bounds: action.bounds,
- zoom: action.zoom,
- updatedAt: Date.now(),
- }))
- return mergedState
+ mergedState = _cloneDeep(state);
+ _set(
+ mergedState,
+ `${action.searchName}.mapBounds`,
+ Object.assign({}, state[action.searchName]?.mapBounds, {
+ challengeId: action.challengeId,
+ bounds: action.bounds,
+ zoom: action.zoom,
+ updatedAt: Date.now(),
+ }),
+ );
+ return mergedState;
case CLEAR_MAP_BOUNDS:
- return Object.assign({}, _omit(state, `${action.searchName}.mapBounds`))
+ return Object.assign({}, _omit(state, `${action.searchName}.mapBounds`));
default:
- return state
+ return state;
}
-}
+};
diff --git a/src/services/SearchCriteria/SearchCriteria.js b/src/services/SearchCriteria/SearchCriteria.js
index bdebe147b..ba429596c 100644
--- a/src/services/SearchCriteria/SearchCriteria.js
+++ b/src/services/SearchCriteria/SearchCriteria.js
@@ -1,35 +1,40 @@
-import _isString from 'lodash/isString'
-import _cloneDeep from 'lodash/cloneDeep'
-import _keys from 'lodash/keys'
-import _values from 'lodash/values'
-import _each from 'lodash/each'
-import _isUndefined from 'lodash/isUndefined'
-import _toInteger from 'lodash/toInteger'
-import queryString from 'query-string'
+import _cloneDeep from "lodash/cloneDeep";
+import _each from "lodash/each";
+import _isString from "lodash/isString";
+import _isUndefined from "lodash/isUndefined";
+import _keys from "lodash/keys";
+import _toInteger from "lodash/toInteger";
+import _values from "lodash/values";
+import queryString from "query-string";
export function buildSearchCriteria(searchParams, defaultCriteria) {
if (searchParams) {
- let sortBy = searchParams?.sortBy
- let direction = searchParams?.direction
- let filters = searchParams?.filters ?? {}
- const page = searchParams?.page
- const boundingBox = searchParams.boundingBox
- const savedChallengesOnly = searchParams.savedChallengesOnly
- const excludeOtherReviewers = searchParams.excludeOtherReviewers
+ let sortBy = searchParams?.sortBy;
+ let direction = searchParams?.direction;
+ let filters = searchParams?.filters ?? {};
+ const page = searchParams?.page;
+ const boundingBox = searchParams.boundingBox;
+ const savedChallengesOnly = searchParams.savedChallengesOnly;
+ const excludeOtherReviewers = searchParams.excludeOtherReviewers;
if (_isString(filters)) {
- filters = JSON.parse(searchParams.filters)
+ filters = JSON.parse(searchParams.filters);
}
if (searchParams.sortCriteria) {
- sortBy = searchParams?.sortCriteria?.sortBy
- direction = searchParams?.sortCriteria?.direction
+ sortBy = searchParams?.sortCriteria?.sortBy;
+ direction = searchParams?.sortCriteria?.direction;
}
- return {sortCriteria: {sortBy, direction}, filters, page, boundingBox,
- savedChallengesOnly, excludeOtherReviewers}
- }
- else return _cloneDeep(defaultCriteria)
+ return {
+ sortCriteria: { sortBy, direction },
+ filters,
+ page,
+ boundingBox,
+ savedChallengesOnly,
+ excludeOtherReviewers,
+ };
+ } else return _cloneDeep(defaultCriteria);
}
/**
@@ -39,75 +44,66 @@ export function buildSearchCriteria(searchParams, defaultCriteria) {
* eg. ?filters.projectId=8&invert.project=true&includeTags=true
**/
export function buildSearchURL(searchCriteria) {
- const params = {}
+ const params = {};
_each(_keys(searchCriteria), (key) => {
if (typeof searchCriteria[key] === "object") {
if (key === "boundingBox") {
- params.boundingBox = _values(searchCriteria.boundingBox).join()
- }
- else {
+ params.boundingBox = _values(searchCriteria.boundingBox).join();
+ } else {
_each(_keys(searchCriteria[key]), (subkey) => {
- if (!_isUndefined(searchCriteria[key][subkey]) &&
- searchCriteria[key][subkey] !== null) {
+ if (!_isUndefined(searchCriteria[key][subkey]) && searchCriteria[key][subkey] !== null) {
// taskPropertySearch is a json object
if (subkey === "taskPropertySearch") {
- params[`${key}.${subkey}`] =
- JSON.stringify(searchCriteria[key][subkey])
- }
- else {
- params[`${key}.${subkey}`] = searchCriteria[key][subkey]
+ params[`${key}.${subkey}`] = JSON.stringify(searchCriteria[key][subkey]);
+ } else {
+ params[`${key}.${subkey}`] = searchCriteria[key][subkey];
}
}
- })
+ });
}
+ } else if (!_isUndefined(searchCriteria[key]) && searchCriteria[key] !== null) {
+ params[key] = searchCriteria[key];
}
- else if (!_isUndefined(searchCriteria[key]) && searchCriteria[key] !== null) {
- params[key] = searchCriteria[key]
- }
- })
+ });
- return "?" + new URLSearchParams(params).toString()
+ return "?" + new URLSearchParams(params).toString();
}
/**
* Takes a search url and rebuilds the search criteria.
*/
export function buildSearchCriteriafromURL(searchURL) {
- const parsedURL = queryString.parse(searchURL)
- const searchCriteria = {}
+ const parsedURL = queryString.parse(searchURL);
+ const searchCriteria = {};
- const massageValue = value => {
+ const massageValue = (value) => {
if (value === "true") {
- return true
- }
- else if (value === "false") {
- return false
+ return true;
+ } else if (value === "false") {
+ return false;
+ } else if (!isNaN(value)) {
+ return _toInteger(value);
}
- else if (!isNaN(value)) {
- return _toInteger(value)
- }
- return value
- }
+ return value;
+ };
- _each(_keys(parsedURL), key => {
- const result = key.match(/(\w+)\.(\w+)/)
+ _each(_keys(parsedURL), (key) => {
+ const result = key.match(/(\w+)\.(\w+)/);
if (result) {
- const primaryKey = result[1]
- const subkey = result[2]
- searchCriteria[primaryKey] = searchCriteria[primaryKey] || {}
+ const primaryKey = result[1];
+ const subkey = result[2];
+ searchCriteria[primaryKey] = searchCriteria[primaryKey] || {};
if (subkey === "taskPropertySearch") {
- searchCriteria[primaryKey][subkey] = JSON.parse(parsedURL[key])
- }
- else {
- searchCriteria[primaryKey][subkey] = massageValue(parsedURL[key])
+ searchCriteria[primaryKey][subkey] = JSON.parse(parsedURL[key]);
+ } else {
+ searchCriteria[primaryKey][subkey] = massageValue(parsedURL[key]);
}
+ } else {
+ searchCriteria[key] = massageValue(parsedURL[key]);
}
- else {
- searchCriteria[key] = massageValue(parsedURL[key])
- }
- })
+ });
- return searchCriteria
+ return searchCriteria;
}
diff --git a/src/services/Server/APIRoutes.js b/src/services/Server/APIRoutes.js
index 627d4eacb..cf3431b47 100644
--- a/src/services/Server/APIRoutes.js
+++ b/src/services/Server/APIRoutes.js
@@ -31,17 +31,11 @@ const apiRoutes = (factory) => {
activity: factory.get("/data/project/activity"),
managers: factory.get("/user/project/:projectId"),
comments: factory.get("/project/:id/comments"),
- setManagerPermission: factory.put(
- "/user/:userId/project/:projectId/:role"
- ),
+ setManagerPermission: factory.put("/user/:userId/project/:projectId/:role"),
removeManager: factory.delete("/user/:userId/project/:projectId/-1"),
delete: factory.delete("/project/:id"),
- addToVirtual: factory.post(
- "/project/:projectId/challenge/:challengeId/add"
- ),
- removeFromVirtual: factory.post(
- "/project/:projectId/challenge/:challengeId/remove"
- ),
+ addToVirtual: factory.post("/project/:projectId/challenge/:challengeId/add"),
+ removeFromVirtual: factory.post("/project/:projectId/challenge/:challengeId/remove"),
},
challenges: {
listing: factory.get("/challenges/listing"),
@@ -54,7 +48,7 @@ const apiRoutes = (factory) => {
withReviewTasks: factory.get("/review/challenges"),
tagMetrics: factory.get("/data/tag/metrics"),
bulkArchive: factory.post("/challenges/bulkArchive"),
- move: factory.post("/challenges/project/:projectId")
+ move: factory.post("/challenges/project/:projectId"),
},
challenge: {
single: factory.get("/challenge/:id"),
@@ -68,12 +62,8 @@ const apiRoutes = (factory) => {
prioritizedTask: factory.get("/challenge/:id/tasks/prioritizedTasks", {
noCache: true,
}),
- previousSequentialTask: factory.get(
- "/challenge/:challengeId/previousTask/:taskId"
- ),
- nextSequentialTask: factory.get(
- "/challenge/:challengeId/nextTask/:taskId"
- ),
+ previousSequentialTask: factory.get("/challenge/:challengeId/previousTask/:taskId"),
+ nextSequentialTask: factory.get("/challenge/:challengeId/nextTask/:taskId"),
actions: factory.get("/data/challenge/:id"),
activity: factory.get("/data/challenge/:id/activity"),
comments: factory.get("/challenge/:id/comments"),
@@ -91,16 +81,14 @@ const apiRoutes = (factory) => {
recordSnapshot: factory.get("/snapshot/challenge/:id/record"),
removeSnapshot: factory.delete("/snapshot/:id"),
snapshot: factory.get("/snapshot/:id"),
- archive: factory.post("/challenge/:id/archive")
+ archive: factory.post("/challenge/:id/archive"),
},
virtualChallenge: {
single: factory.get("/virtualchallenge/:id"),
create: factory.post("/virtualchallenge"),
edit: factory.put("/virtualchallenge/:id"),
randomTask: factory.get("/virtualchallenge/:id/task", { noCache: true }),
- nearbyTasks: factory.get(
- "/virtualchallenge/:challengeId/tasksNearby/:taskId"
- ),
+ nearbyTasks: factory.get("/virtualchallenge/:challengeId/tasksNearby/:taskId"),
},
tasks: {
random: factory.get("/tasks/random", { noCache: true }),
@@ -125,9 +113,7 @@ const apiRoutes = (factory) => {
updateStatus: factory.put("/taskBundle/:bundleId/:status"),
addComment: factory.post("/taskBundle/:bundleId/comment"),
updateReviewStatus: factory.put("/taskBundle/:bundleId/review/:status"),
- updateMetaReviewStatus: factory.put(
- "/taskBundle/:bundleId/metareview/:status"
- ),
+ updateMetaReviewStatus: factory.put("/taskBundle/:bundleId/metareview/:status"),
},
removeReviewRequest: factory.put("/tasks/review/remove"),
},
@@ -168,7 +154,7 @@ const apiRoutes = (factory) => {
findPreferred: factory.get("/users/find"),
all: factory.get("/users"),
taskComments: factory.get("/comments/user/:id", { noCache: true }),
- challengeComments: factory.get("/challengeComments/user/:id", { noCache: true })
+ challengeComments: factory.get("/challengeComments/user/:id", { noCache: true }),
},
user: {
whoami: factory.get("/user/whoami"),
@@ -183,12 +169,8 @@ const apiRoutes = (factory) => {
lockedTasks: factory.get("/user/:userId/lockedTasks"),
unsaveTask: factory.delete("/user/:userId/unsaveTask/:taskId"),
updateSettings: factory.put("/user/:userId"),
- notificationSubscriptions: factory.get(
- "/user/:userId/notificationSubscriptions"
- ),
- updateNotificationSubscriptions: factory.put(
- "/user/:userId/notificationSubscriptions"
- ),
+ notificationSubscriptions: factory.get("/user/:userId/notificationSubscriptions"),
+ updateNotificationSubscriptions: factory.put("/user/:userId/notificationSubscriptions"),
notifications: factory.get("/user/:userId/notifications"),
markNotificationsRead: factory.put("/user/:userId/notifications"),
deleteNotifications: factory.put("/user/:userId/notifications/delete"),
@@ -208,8 +190,8 @@ const apiRoutes = (factory) => {
},
superUser: {
addSuperUserGrant: factory.put("/superuser/:userId"),
- deleteSuperUserGrant: factory.delete("/superuser/:userId")
- }
+ deleteSuperUserGrant: factory.delete("/superuser/:userId"),
+ },
};
};
diff --git a/src/services/Server/Endpoint.js b/src/services/Server/Endpoint.js
index 4ec64311e..5ade41f64 100644
--- a/src/services/Server/Endpoint.js
+++ b/src/services/Server/Endpoint.js
@@ -1,6 +1,4 @@
-import { fetchContent,
- sendContent,
- deleteContent } from './Server'
+import { deleteContent, fetchContent, sendContent } from "./Server";
/**
* Endpoint represents a single API endpoint on the server. It is capable of
@@ -25,14 +23,14 @@ export default class Endpoint {
* @param route - the desired route for this endpoint
* @param {object} [options] - optional options object: see above.
*/
- constructor(route, options={}) {
- this.route = route
- this.normalizationSchema = options.schema
- this.variables = options.variables
- this.params = options.params
- this.jsonBody = options.json
- this.formData = options.formData
- this.expectXMLResponse = options.expectXMLResponse
+ constructor(route, options = {}) {
+ this.route = route;
+ this.normalizationSchema = options.schema;
+ this.variables = options.variables;
+ this.params = options.params;
+ this.jsonBody = options.json;
+ this.formData = options.formData;
+ this.expectXMLResponse = options.expectXMLResponse;
}
/**
@@ -42,28 +40,30 @@ export default class Endpoint {
* if a normalization schema was provided) or rejects on error.
*/
execute = () => {
- switch(this.route.method) {
- case 'POST':
- case 'PUT':
- return sendContent(this.route.method,
- this.url(),
- this.jsonBody,
- this.formData,
- this.normalizationSchema,
- this.expectXMLResponse)
- case 'DELETE':
- return deleteContent(this.url())
+ switch (this.route.method) {
+ case "POST":
+ case "PUT":
+ return sendContent(
+ this.route.method,
+ this.url(),
+ this.jsonBody,
+ this.formData,
+ this.normalizationSchema,
+ this.expectXMLResponse,
+ );
+ case "DELETE":
+ return deleteContent(this.url());
default:
- return fetchContent(this.url(),
- this.normalizationSchema,
- {noCache: !!this.route.options.noCache})
+ return fetchContent(this.url(), this.normalizationSchema, {
+ noCache: !!this.route.options.noCache,
+ });
}
- }
+ };
/**
* url generates an absolute url string for this API endpoint,
*
* @returns an url string
*/
- url = () => this.route.url(this.variables, this.params)
+ url = () => this.route.url(this.variables, this.params);
}
diff --git a/src/services/Server/GenericEntityReducer.js b/src/services/Server/GenericEntityReducer.js
index 578d87e70..587d2fde1 100644
--- a/src/services/Server/GenericEntityReducer.js
+++ b/src/services/Server/GenericEntityReducer.js
@@ -1,11 +1,11 @@
-import _isArray from 'lodash/isArray'
-import _isFunction from 'lodash/isFunction'
-import _isObject from 'lodash/isObject'
-import _cloneDeep from 'lodash/cloneDeep'
-import _forOwn from 'lodash/forOwn'
-import _merge from 'lodash/merge'
-import _values from 'lodash/values'
-import RequestStatus from './RequestStatus'
+import _cloneDeep from "lodash/cloneDeep";
+import _forOwn from "lodash/forOwn";
+import _isArray from "lodash/isArray";
+import _isFunction from "lodash/isFunction";
+import _isObject from "lodash/isObject";
+import _merge from "lodash/merge";
+import _values from "lodash/values";
+import RequestStatus from "./RequestStatus";
/**
*
@@ -39,19 +39,20 @@ import RequestStatus from './RequestStatus'
* @returns {function} a function that can be used as a redux reducer.
*/
const genericEntityReducer = (actionTypes, entityName, reduceFurther) => {
- const allowedActionTypes =
- _isArray(actionTypes) ? actionTypes : [ actionTypes ]
+ const allowedActionTypes = _isArray(actionTypes) ? actionTypes : [actionTypes];
return (state = {}, action) => {
- if (allowedActionTypes.indexOf(action.type) === -1 ||
- action.status !== RequestStatus.success ||
- !_isObject(action.entities)) {
- return state
+ if (
+ allowedActionTypes.indexOf(action.type) === -1 ||
+ action.status !== RequestStatus.success ||
+ !_isObject(action.entities)
+ ) {
+ return state;
}
- return entities(state, action, entityName, reduceFurther)
- }
-}
+ return entities(state, action, entityName, reduceFurther);
+ };
+};
/**
* entities is a generic reducer function for entity data retrieved from the
@@ -63,29 +64,29 @@ const genericEntityReducer = (actionTypes, entityName, reduceFurther) => {
*
* @private
*/
-const entities = function(state = {}, action, entityName, reduceFurther) {
- const newState = _cloneDeep(state)
- const timestamp = Date.now()
+const entities = function (state = {}, action, entityName, reduceFurther) {
+ const newState = _cloneDeep(state);
+ const timestamp = Date.now();
_forOwn(action.entities[entityName], (entity, entityId) => {
- if (typeof entityId === 'undefined') {
- return
+ if (typeof entityId === "undefined") {
+ return;
}
// Add a _meta object to each entity where we can store some application
// meta data about the entity. Right now we just store a timestamp of
// when the data was fetched so that we can measure the freshness of the
// data.
- entity._meta = {fetchedAt: timestamp}
+ entity._meta = { fetchedAt: timestamp };
- newState[entityId] = _merge(newState[entityId], entity)
- })
+ newState[entityId] = _merge(newState[entityId], entity);
+ });
if (_isFunction(reduceFurther)) {
- reduceFurther(newState, state, _values(action.entities[entityName]))
+ reduceFurther(newState, state, _values(action.entities[entityName]));
}
- return newState
-}
+ return newState;
+};
-export default genericEntityReducer
+export default genericEntityReducer;
diff --git a/src/services/Server/RequestCache.js b/src/services/Server/RequestCache.js
index 1450ae281..96ced5e76 100644
--- a/src/services/Server/RequestCache.js
+++ b/src/services/Server/RequestCache.js
@@ -1,4 +1,4 @@
-import Cache from 'stale-lru-cache'
+import Cache from "stale-lru-cache";
/**
* The primary purpose of the cache is simply to reduce duplicate requests to
@@ -9,8 +9,8 @@ import Cache from 'stale-lru-cache'
export const cache = new Cache({
maxSize: 100,
maxAge: 10, // seconds
-})
+});
export const resetCache = () => {
- cache.reset()
-}
+ cache.reset();
+};
diff --git a/src/services/Server/RequestStatus.js b/src/services/Server/RequestStatus.js
index 7af93f22a..4809a7d2a 100644
--- a/src/services/Server/RequestStatus.js
+++ b/src/services/Server/RequestStatus.js
@@ -1,7 +1,7 @@
const RequestStatus = Object.freeze({
- inProgress: 'in progress',
- success: 'success',
- error: 'error',
-})
+ inProgress: "in progress",
+ success: "success",
+ error: "error",
+});
-export default RequestStatus
+export default RequestStatus;
diff --git a/src/services/Server/Route.js b/src/services/Server/Route.js
index 4c607d1f0..4580944e0 100644
--- a/src/services/Server/Route.js
+++ b/src/services/Server/Route.js
@@ -1,6 +1,6 @@
-import { routeMatcher } from 'route-matcher'
-import QueryString from 'query-string'
-import _isEmpty from 'lodash/isEmpty'
+import _isEmpty from "lodash/isEmpty";
+import QueryString from "query-string";
+import { routeMatcher } from "route-matcher";
/**
* Represents a single API route. Variable substitution in route paths is
@@ -17,12 +17,12 @@ import _isEmpty from 'lodash/isEmpty'
* @author [Neil Rotstan](https://github.com/nrotstan)
*/
export default class Route {
- constructor(baseURL, apiVersion, routePath, method='GET', options={}) {
- this.rawPath = routePath
- this.baseURL = baseURL
- this.route = routeMatcher(`/api/${apiVersion}${routePath}`)
- this.method = method
- this.options = options
+ constructor(baseURL, apiVersion, routePath, method = "GET", options = {}) {
+ this.rawPath = routePath;
+ this.baseURL = baseURL;
+ this.route = routeMatcher(`/api/${apiVersion}${routePath}`);
+ this.method = method;
+ this.options = options;
}
/**
@@ -36,10 +36,8 @@ export default class Route {
* @returns {string} an url string
*/
url = (variables, params) => {
- const urlString = this.baseURL + this.route.stringify(variables)
+ const urlString = this.baseURL + this.route.stringify(variables);
- return _isEmpty(params) ?
- urlString :
- `${urlString}?${QueryString.stringify(params)}`
- }
+ return _isEmpty(params) ? urlString : `${urlString}?${QueryString.stringify(params)}`;
+ };
}
diff --git a/src/services/Server/Route.test.js b/src/services/Server/Route.test.js
index b3b873431..5b2f90152 100644
--- a/src/services/Server/Route.test.js
+++ b/src/services/Server/Route.test.js
@@ -1,36 +1,35 @@
-import { describe, it, expect } from "vitest";
-import Route from './Route'
+import { describe, expect, it } from "vitest";
+import Route from "./Route";
-const base = 'http://localhost'
-const baseWithPort = `${base}:9000`
-const api = '2'
+const base = "http://localhost";
+const baseWithPort = `${base}:9000`;
+const api = "2";
-describe('url', () => {
+describe("url", () => {
it("includes the base url given at construction", () => {
- const route = new Route(base, api, 'somepath')
- expect(route.url()).toMatch(base)
- })
+ const route = new Route(base, api, "somepath");
+ expect(route.url()).toMatch(base);
+ });
it("includes the api version given at construction after the base", () => {
- const route = new Route(base, api, 'somepath')
- expect(route.url()).toMatch(`${base}/api/${api}`)
- })
+ const route = new Route(base, api, "somepath");
+ expect(route.url()).toMatch(`${base}/api/${api}`);
+ });
it("includes the port number if included in the base url", () => {
- const route = new Route(baseWithPort, api, 'somepath')
- expect(route.url()).toMatch(baseWithPort)
- })
+ const route = new Route(baseWithPort, api, "somepath");
+ expect(route.url()).toMatch(baseWithPort);
+ });
it("substitutes variables into the path", () => {
- const route = new Route(baseWithPort, api, 'path/:first/path/:second')
- expect(route.url({first: 'hello', second: 'world'})).toMatch('path/hello/path/world')
- })
+ const route = new Route(baseWithPort, api, "path/:first/path/:second");
+ expect(route.url({ first: "hello", second: "world" })).toMatch("path/hello/path/world");
+ });
it("appends query params to the end", () => {
- const route = new Route(baseWithPort, api, 'path/:first/path/:second')
- expect(route.url(
- {first: 'hello', second: 'world'},
- {page: 3, limit: 10}
- )).toMatch('path/hello/path/world?limit=10&page=3')
- })
-})
+ const route = new Route(baseWithPort, api, "path/:first/path/:second");
+ expect(route.url({ first: "hello", second: "world" }, { page: 3, limit: 10 })).toMatch(
+ "path/hello/path/world?limit=10&page=3",
+ );
+ });
+});
diff --git a/src/services/Server/RouteFactory.js b/src/services/Server/RouteFactory.js
index b2ce99733..60660c3e5 100644
--- a/src/services/Server/RouteFactory.js
+++ b/src/services/Server/RouteFactory.js
@@ -1,4 +1,4 @@
-import Route from './Route'
+import Route from "./Route";
/**
* Factory for generating Route instances that share a common base URL and API
@@ -19,11 +19,11 @@ export default class RouteFactory {
* with
*/
constructor(baseURL, apiVersion) {
- this.baseURL = baseURL
- this.apiVersion = apiVersion
+ this.baseURL = baseURL;
+ this.apiVersion = apiVersion;
}
- setAPIVersion = (apiVersion) => this.apiVersion = apiVersion
+ setAPIVersion = (apiVersion) => (this.apiVersion = apiVersion);
/**
* Generates a Route instance for the given path
@@ -33,14 +33,14 @@ export default class RouteFactory {
*
* @returns {APIRoute} an APIRoute instance
*/
- route = (path, method='GET', options) =>
- new Route(this.baseURL, this.apiVersion, path, method, options)
+ route = (path, method = "GET", options) =>
+ new Route(this.baseURL, this.apiVersion, path, method, options);
- get = (path, options) => this.route(path, 'GET', options)
+ get = (path, options) => this.route(path, "GET", options);
- post = (path, options) => this.route(path, 'POST', options)
+ post = (path, options) => this.route(path, "POST", options);
- put = (path, options) => this.route(path, 'PUT', options)
+ put = (path, options) => this.route(path, "PUT", options);
- delete = (path, options) => this.route(path, 'DELETE', options)
+ delete = (path, options) => this.route(path, "DELETE", options);
}
diff --git a/src/services/Server/Server.js b/src/services/Server/Server.js
index 2a67e332c..754ed89b4 100644
--- a/src/services/Server/Server.js
+++ b/src/services/Server/Server.js
@@ -10,36 +10,34 @@
* @see See also Server/Endpoints
*/
-import { normalize } from 'normalizr'
-import { cache, resetCache } from './RequestCache'
-import WebSocketClient from './WebSocketClient'
-import _isArray from 'lodash/isArray'
-import _isEmpty from 'lodash/isEmpty'
-import RouteFactory from './RouteFactory'
-import apiRoutes from './APIRoutes'
+import _isArray from "lodash/isArray";
+import _isEmpty from "lodash/isEmpty";
+import { normalize } from "normalizr";
+import apiRoutes from "./APIRoutes";
+import { cache, resetCache } from "./RequestCache";
+import RouteFactory from "./RouteFactory";
+import WebSocketClient from "./WebSocketClient";
-const baseURL = window.env.REACT_APP_MAP_ROULETTE_SERVER_URL
-const apiKey = window.env.REACT_APP_SERVER_API_KEY
+const baseURL = window.env.REACT_APP_MAP_ROULETTE_SERVER_URL;
+const apiKey = window.env.REACT_APP_SERVER_API_KEY;
// In development mode, be less strict about CORS so that the frontend and
// backend can run on separate servers/ports. Otherwise insist on same-origin
// policy
-export const credentialsPolicy =
- process.env.NODE_ENV === 'development' ? 'include' : 'same-origin'
+export const credentialsPolicy = process.env.NODE_ENV === "development" ? "include" : "same-origin";
-export const serverRouteFactory = new RouteFactory(baseURL)
-export const defaultRoutes = Object.freeze(apiRoutes(serverRouteFactory))
-export const websocketClient = new WebSocketClient()
+export const serverRouteFactory = new RouteFactory(baseURL);
+export const defaultRoutes = Object.freeze(apiRoutes(serverRouteFactory));
+export const websocketClient = new WebSocketClient();
-const dataAtUrl = function(url, fetchFunction) {
- const cachedData = cache.get(url)
+const dataAtUrl = function (url, fetchFunction) {
+ const cachedData = cache.get(url);
if (cachedData) {
- return cachedData
+ return cachedData;
+ } else {
+ return fetchFunction();
}
- else {
- return fetchFunction()
- }
-}
+};
/**
* fetchContent fetches content from the given url, and return a promise that
@@ -52,56 +50,58 @@ const dataAtUrl = function(url, fetchFunction) {
* @returns {Promise} Promise that resolves with response data or rejects on
* error
*/
-export const fetchContent = function(url, normalizationSchema, options={}) {
+export const fetchContent = function (url, normalizationSchema, options = {}) {
return dataAtUrl(url, () => {
const retrieval = new Promise((resolve, reject) => {
- const headers = new Headers()
+ const headers = new Headers();
if (!_isEmpty(apiKey)) {
- headers.append('apiKey', apiKey)
+ headers.append("apiKey", apiKey);
}
const fetchOptions = {
credentials: options.omitCredentials ? "omit" : credentialsPolicy,
headers,
- }
-
- fetch(
- url, fetchOptions
- ).then(checkStatus).then(parseJSON).then(jsonData => {
- let result = jsonData
- if (jsonData && normalizationSchema) {
- result = normalize(jsonData, normalizationSchema)
- }
+ };
- resolve(result)
- }).catch(error => {
- if (error.response) {
- // 404 is used by the scala server to indicate no results. Treat as
- // successful response with empty data
- if (error.response.status === 404) {
- resolve(normalize(_isArray(normalizationSchema) ? [] : {}, normalizationSchema))
+ fetch(url, fetchOptions)
+ .then(checkStatus)
+ .then(parseJSON)
+ .then((jsonData) => {
+ let result = jsonData;
+ if (jsonData && normalizationSchema) {
+ result = normalize(jsonData, normalizationSchema);
}
- else {
- // Attach any details in the response body to the error
- parseJSON(error.response).then(jsonData => {
- error.details = jsonData
- reject(error)
- }).catch(() => reject(error))
+
+ resolve(result);
+ })
+ .catch((error) => {
+ if (error.response) {
+ // 404 is used by the scala server to indicate no results. Treat as
+ // successful response with empty data
+ if (error.response.status === 404) {
+ resolve(normalize(_isArray(normalizationSchema) ? [] : {}, normalizationSchema));
+ } else {
+ // Attach any details in the response body to the error
+ parseJSON(error.response)
+ .then((jsonData) => {
+ error.details = jsonData;
+ reject(error);
+ })
+ .catch(() => reject(error));
+ }
+ } else {
+ reject(error);
}
- }
- else {
- reject(error)
- }
- })
- })
+ });
+ });
if (!options.noCache) {
- cache.set(url, retrieval)
+ cache.set(url, retrieval);
}
- return retrieval
- })
-}
+ return retrieval;
+ });
+};
/**
* sendContent sends JSON content to the given url, and return a promise that
@@ -118,16 +118,23 @@ export const fetchContent = function(url, normalizationSchema, options={}) {
* @returns {Promise} Promise that resolves with response data or rejects on
* error
*/
-export const sendContent = function(method, url, jsonBody, formData, normalizationSchema, expectXMLResponse) {
+export const sendContent = function (
+ method,
+ url,
+ jsonBody,
+ formData,
+ normalizationSchema,
+ expectXMLResponse,
+) {
return new Promise((resolve, reject) => {
- resetCache() // Clear the cache on updates to ensure fetches are fresh.
+ resetCache(); // Clear the cache on updates to ensure fetches are fresh.
- const headers = new Headers()
+ const headers = new Headers();
if (!_isEmpty(apiKey)) {
- headers.append('apiKey', apiKey)
+ headers.append("apiKey", apiKey);
}
if (jsonBody) {
- headers.append('Content-Type', 'text/json')
+ headers.append("Content-Type", "text/json");
}
// Note: do not set multipart/form-data Content-Type header for formData --
// fetch will set that automatically (with the correct boundary included)
@@ -137,96 +144,100 @@ export const sendContent = function(method, url, jsonBody, formData, normalizati
credentials: credentialsPolicy,
headers,
body: jsonBody ? JSON.stringify(jsonBody) : formData,
- }).then(checkStatus).then(response => {
- if (expectXMLResponse) {
- response.text().then(
- text => resolve(text)
- ).catch(error => reject(error))
- return
- }
-
- parseJSON(response).then(jsonData => {
- if (jsonData && normalizationSchema) {
- resolve(normalize(jsonData, normalizationSchema))
- }
- else {
- resolve(jsonData)
- }
- }).catch(error => reject(error))
- }).catch(error => reject(error))
- })
-}
-
- /**
- * deleteContent sends a DELETE to the given url. Returns a promise that
- * resolves to successful response or rejects if there is an error.
- *
- * @param {string} url
- *
- * @returns {Promise} Promise that resolves to the response or rejects on error
- */
- export const deleteContent = function(url) {
- return new Promise((resolve, reject) => {
- resetCache() // Clear the cache on deletes to ensure fetches are fresh.
-
- const headers = new Headers()
- if (!_isEmpty(apiKey)) {
- headers.append('apiKey', apiKey)
- }
-
- fetch(url, {method: 'DELETE', credentials: credentialsPolicy, headers})
- .then(checkStatus)
- .then(response => resolve(response))
- .catch(error => reject(error))
})
- }
+ .then(checkStatus)
+ .then((response) => {
+ if (expectXMLResponse) {
+ response
+ .text()
+ .then((text) => resolve(text))
+ .catch((error) => reject(error));
+ return;
+ }
- /**
- * Returns true for 401 and 403 errors, false for all others.
- */
- export const isSecurityError = function(serverError) {
- return serverError.response &&
- (serverError.response.status === 401 ||
- serverError.response.status === 403)
- }
+ parseJSON(response)
+ .then((jsonData) => {
+ if (jsonData && normalizationSchema) {
+ resolve(normalize(jsonData, normalizationSchema));
+ } else {
+ resolve(jsonData);
+ }
+ })
+ .catch((error) => reject(error));
+ })
+ .catch((error) => reject(error));
+ });
+};
+/**
+ * deleteContent sends a DELETE to the given url. Returns a promise that
+ * resolves to successful response or rejects if there is an error.
+ *
+ * @param {string} url
+ *
+ * @returns {Promise} Promise that resolves to the response or rejects on error
+ */
+export const deleteContent = function (url) {
+ return new Promise((resolve, reject) => {
+ resetCache(); // Clear the cache on deletes to ensure fetches are fresh.
- /**
- * checkStatus evaluates the given response and throws an error for non-2xx
- * responses, or otherwise just returns the response on success.
- *
- * @param response
- *
- * @returns the given response on success
- */
- const checkStatus = function(response) {
- if (response.status >= 200 && response.status < 300) {
- return response
- }
- else {
- const error = new Error(response.statusText)
- error.response = response
- throw error
+ const headers = new Headers();
+ if (!_isEmpty(apiKey)) {
+ headers.append("apiKey", apiKey);
}
- }
- /**
- * parseJson retrieves the JSON payload from the given response.
- *
- * @param response
- *
- * @returns null if there is no content (204) or if the response content-type
- * isn't json
- */
- const parseJSON = function(response) {
- if (response.status === 204) {
- return Promise.resolve(null)
- }
+ fetch(url, { method: "DELETE", credentials: credentialsPolicy, headers })
+ .then(checkStatus)
+ .then((response) => resolve(response))
+ .catch((error) => reject(error));
+ });
+};
- const contentType = response.headers.get('content-type')
- if (!contentType || contentType.indexOf('application/json') === -1) {
- return Promise.resolve(null)
- }
+/**
+ * Returns true for 401 and 403 errors, false for all others.
+ */
+export const isSecurityError = function (serverError) {
+ return (
+ serverError.response &&
+ (serverError.response.status === 401 || serverError.response.status === 403)
+ );
+};
+
+/**
+ * checkStatus evaluates the given response and throws an error for non-2xx
+ * responses, or otherwise just returns the response on success.
+ *
+ * @param response
+ *
+ * @returns the given response on success
+ */
+const checkStatus = function (response) {
+ if (response.status >= 200 && response.status < 300) {
+ return response;
+ } else {
+ const error = new Error(response.statusText);
+ error.response = response;
+ throw error;
+ }
+};
+
+/**
+ * parseJson retrieves the JSON payload from the given response.
+ *
+ * @param response
+ *
+ * @returns null if there is no content (204) or if the response content-type
+ * isn't json
+ */
+const parseJSON = function (response) {
+ if (response.status === 204) {
+ return Promise.resolve(null);
+ }
- return response.json()
+ const contentType = response.headers.get("content-type");
+ if (!contentType || contentType.indexOf("application/json") === -1) {
+ return Promise.resolve(null);
}
+
+ return response.json();
+};
diff --git a/src/services/Server/WebSocketClient.js b/src/services/Server/WebSocketClient.js
index 3757e49a6..baeeca8c0 100644
--- a/src/services/Server/WebSocketClient.js
+++ b/src/services/Server/WebSocketClient.js
@@ -1,14 +1,14 @@
export default class WebSocketClient {
constructor() {
- this.websocket = null
- this.reconnectionAttempts = 0
- this.reconnectionHandle = null
- this.pingHandle = null
- this.subscriptionHandlers = new Map()
- this.serverSubscriptions = new Map()
- this.queuedMessages = []
-
- this.connect()
+ this.websocket = null;
+ this.reconnectionAttempts = 0;
+ this.reconnectionHandle = null;
+ this.pingHandle = null;
+ this.subscriptionHandlers = new Map();
+ this.serverSubscriptions = new Map();
+ this.queuedMessages = [];
+
+ this.connect();
}
/**
@@ -16,15 +16,15 @@ export default class WebSocketClient {
* and optional objectId
*/
addServerSubscription(subscriptionType, objectId, handlerId, handler) {
- const subscriptionName = this.canonicalSubscriptionName(subscriptionType, objectId)
+ const subscriptionName = this.canonicalSubscriptionName(subscriptionType, objectId);
const subscribeMessage = {
messageType: "subscribe",
- data: { subscriptionName }
- }
+ data: { subscriptionName },
+ };
- this.serverSubscriptions.set(subscriptionName, subscribeMessage)
- this.addSubscriptionHandler(subscriptionName, handlerId, handler)
- this.sendMessage(subscribeMessage)
+ this.serverSubscriptions.set(subscriptionName, subscribeMessage);
+ this.addSubscriptionHandler(subscriptionName, handlerId, handler);
+ this.sendMessage(subscribeMessage);
}
/**
@@ -32,15 +32,15 @@ export default class WebSocketClient {
* subscription type and optional objectId
*/
removeServerSubscription(subscriptionType, objectId, handlerId) {
- const subscriptionName = this.canonicalSubscriptionName(subscriptionType, objectId)
+ const subscriptionName = this.canonicalSubscriptionName(subscriptionType, objectId);
const unsubscribeMessage = {
messageType: "unsubscribe",
- data: { subscriptionName }
- }
+ data: { subscriptionName },
+ };
- this.serverSubscriptions.delete(subscriptionName)
- this.removeSubscriptionHandler(subscriptionName, handlerId)
- this.sendMessage(unsubscribeMessage)
+ this.serverSubscriptions.delete(subscriptionName);
+ this.removeSubscriptionHandler(subscriptionName, handlerId);
+ this.sendMessage(unsubscribeMessage);
}
/**
@@ -50,14 +50,13 @@ export default class WebSocketClient {
* is set to true, in which case the message is discarded if it cannot be
* immediately transmitted
*/
- sendMessage(messageObject, noQueue=false) {
- const jsonMessage = JSON.stringify(messageObject)
+ sendMessage(messageObject, noQueue = false) {
+ const jsonMessage = JSON.stringify(messageObject);
if (this.websocket && this.websocket.readyState === this.websocket.OPEN) {
- this.websocket.send(jsonMessage)
- }
- else if (!noQueue) {
- this.queuedMessages.push(jsonMessage)
+ this.websocket.send(jsonMessage);
+ } else if (!noQueue) {
+ this.queuedMessages.push(jsonMessage);
}
}
@@ -72,11 +71,13 @@ export default class WebSocketClient {
connect() {
if (!this.reconnectionHandle) {
// Use exponential backoff
- this.reconnectionAttempts++
- const delay = 1000 // milliseconds
- const backoffTime = Math.floor(Math.random() * Math.pow(2, this.reconnectionAttempts) * delay)
+ this.reconnectionAttempts++;
+ const delay = 1000; // milliseconds
+ const backoffTime = Math.floor(
+ Math.random() * Math.pow(2, this.reconnectionAttempts) * delay,
+ );
- this.reconnectionHandle = setTimeout(() => this.open(), backoffTime)
+ this.reconnectionHandle = setTimeout(() => this.open(), backoffTime);
}
}
@@ -87,19 +88,19 @@ export default class WebSocketClient {
* @private
*/
open() {
- this.reconnectionHandle = null
+ this.reconnectionHandle = null;
if (this.websocket) {
- this.websocket.close()
+ this.websocket.close();
}
- this.websocket = new WebSocket(window.env.REACT_APP_MAP_ROULETTE_SERVER_WEBSOCKET_URL)
- this.websocket.onopen = e => this.handleOpen(e)
- this.websocket.onmessage = e => this.handleMessage(e)
- this.websocket.onclose = e => this.handleClose(e)
+ this.websocket = new WebSocket(window.env.REACT_APP_MAP_ROULETTE_SERVER_WEBSOCKET_URL);
+ this.websocket.onopen = (e) => this.handleOpen(e);
+ this.websocket.onmessage = (e) => this.handleMessage(e);
+ this.websocket.onclose = (e) => this.handleClose(e);
if (!this.pingHandle) {
// Ping the server every 45 seconds to avoid an idle timeout
- this.pingHandle = setInterval(() => this.sendPing(), 45000)
+ this.pingHandle = setInterval(() => this.sendPing(), 45000);
}
}
@@ -111,16 +112,16 @@ export default class WebSocketClient {
* @private
*/
handleOpen() {
- this.reconnectionAttempts = 0
+ this.reconnectionAttempts = 0;
// Reactivate any active subscriptions
for (let subscriptionMessage of this.serverSubscriptions.values()) {
- this.sendMessage(subscriptionMessage)
+ this.sendMessage(subscriptionMessage);
}
// Transmit any queued messages
- this.queuedMessages.forEach(message => this.websocket.send(message))
- this.queuedMessages = []
+ this.queuedMessages.forEach((message) => this.websocket.send(message));
+ this.queuedMessages = [];
}
/**
@@ -130,16 +131,15 @@ export default class WebSocketClient {
* @private
*/
handleMessage(messageEvent) {
- const messageObject = JSON.parse(messageEvent.data)
- const subscriptionName = messageObject?.meta?.subscriptionName
+ const messageObject = JSON.parse(messageEvent.data);
+ const subscriptionName = messageObject?.meta?.subscriptionName;
if (subscriptionName && this.subscriptionHandlers.has(subscriptionName)) {
for (let handler of this.subscriptionHandlers.get(subscriptionName).values()) {
try {
- handler(messageObject)
- }
- catch(error) {
- console.log(error)
+ handler(messageObject);
+ } catch (error) {
+ console.log(error);
}
}
}
@@ -148,9 +148,9 @@ export default class WebSocketClient {
sendPing() {
const pingMessage = {
messageType: "ping",
- }
+ };
- this.sendMessage(pingMessage, true)
+ this.sendMessage(pingMessage, true);
}
/**
@@ -159,7 +159,7 @@ export default class WebSocketClient {
* @private
*/
handleClose() {
- this.connect()
+ this.connect();
}
/**
@@ -172,10 +172,10 @@ export default class WebSocketClient {
*/
addSubscriptionHandler(subscriptionName, handlerId, handler) {
if (!this.subscriptionHandlers.has(subscriptionName)) {
- this.subscriptionHandlers.set(subscriptionName, new Map())
+ this.subscriptionHandlers.set(subscriptionName, new Map());
}
- this.subscriptionHandlers.get(subscriptionName).set(handlerId, handler)
+ this.subscriptionHandlers.get(subscriptionName).set(handlerId, handler);
}
/**
@@ -186,7 +186,7 @@ export default class WebSocketClient {
*/
removeSubscriptionHandler(subscriptionName, handlerId) {
if (this.subscriptionHandlers.has(subscriptionName)) {
- this.subscriptionHandlers.get(subscriptionName).delete(handlerId)
+ this.subscriptionHandlers.get(subscriptionName).delete(handlerId);
}
}
@@ -197,6 +197,6 @@ export default class WebSocketClient {
* @private
*/
canonicalSubscriptionName(subscriptionType, objectId) {
- return subscriptionType.toString() + (objectId ? `_${objectId}` : '')
+ return subscriptionType.toString() + (objectId ? `_${objectId}` : "");
}
}
diff --git a/src/services/Status/Status.js b/src/services/Status/Status.js
index 5b9ddc1cf..201160042 100644
--- a/src/services/Status/Status.js
+++ b/src/services/Status/Status.js
@@ -1,6 +1,6 @@
-import _cloneDeep from 'lodash/cloneDeep'
-import _get from 'lodash/get'
-import _pull from 'lodash/pull'
+import _cloneDeep from "lodash/cloneDeep";
+import _get from "lodash/get";
+import _pull from "lodash/pull";
/**
* Manage application status so that it can be reflected in various components
@@ -8,82 +8,82 @@ import _pull from 'lodash/pull'
*/
// status names
-export const FETCHING_CHALLENGES_STATUS = 'FETCHING_CHALLENGES_STATUS'
-export const CHECKING_LOGIN_STATUS = 'CHECKING_LOGIN_STATUS'
+export const FETCHING_CHALLENGES_STATUS = "FETCHING_CHALLENGES_STATUS";
+export const CHECKING_LOGIN_STATUS = "CHECKING_LOGIN_STATUS";
// redux actions
-export const PUSH_FETCHING_CHALLENGES = 'PUSH_FETCHING_CHALLENGES'
-export const POP_FETCHING_CHALLENGES = 'POP_FETCHING_CHALLENGES'
-export const CLEAR_FETCHING_CHALLENGES = 'CLEAR_FETCHING_CHALLENGES'
-export const SET_CHECKING_LOGIN_STATUS = 'SET_CHECKING_LOGIN_STATUS'
-export const CLEAR_CHECKING_LOGIN_STATUS = 'CLEAR_CHECKING_LOGIN_STATUS'
+export const PUSH_FETCHING_CHALLENGES = "PUSH_FETCHING_CHALLENGES";
+export const POP_FETCHING_CHALLENGES = "POP_FETCHING_CHALLENGES";
+export const CLEAR_FETCHING_CHALLENGES = "CLEAR_FETCHING_CHALLENGES";
+export const SET_CHECKING_LOGIN_STATUS = "SET_CHECKING_LOGIN_STATUS";
+export const CLEAR_CHECKING_LOGIN_STATUS = "CLEAR_CHECKING_LOGIN_STATUS";
// redux action creators
-export const pushFetchChallenges = function(fetchId) {
+export const pushFetchChallenges = function (fetchId) {
return {
type: PUSH_FETCHING_CHALLENGES,
fetchId,
- }
-}
+ };
+};
-export const popFetchChallenges = function(fetchId) {
+export const popFetchChallenges = function (fetchId) {
return {
type: POP_FETCHING_CHALLENGES,
fetchId,
- }
-}
+ };
+};
-export const clearFetchingChallenges = function() {
+export const clearFetchingChallenges = function () {
return {
type: CLEAR_FETCHING_CHALLENGES,
- }
-}
+ };
+};
-export const setCheckingLoginStatus = function() {
+export const setCheckingLoginStatus = function () {
return {
type: SET_CHECKING_LOGIN_STATUS,
- }
-}
+ };
+};
-export const clearCheckingLoginStatus = function() {
+export const clearCheckingLoginStatus = function () {
return {
type: CLEAR_CHECKING_LOGIN_STATUS,
- }
-}
+ };
+};
// redux reducers
-export const currentStatus = function(state={}, action) {
- let merged = null
- let fetchArray = null
+export const currentStatus = function (state = {}, action) {
+ let merged = null;
+ let fetchArray = null;
- switch(action.type) {
+ switch (action.type) {
case PUSH_FETCHING_CHALLENGES:
- merged = _cloneDeep(state)
- fetchArray = _get(merged, FETCHING_CHALLENGES_STATUS, [])
- fetchArray.push(action.fetchId)
- merged[FETCHING_CHALLENGES_STATUS] = fetchArray
-
- return merged
+ merged = _cloneDeep(state);
+ fetchArray = _get(merged, FETCHING_CHALLENGES_STATUS, []);
+ fetchArray.push(action.fetchId);
+ merged[FETCHING_CHALLENGES_STATUS] = fetchArray;
+
+ return merged;
case POP_FETCHING_CHALLENGES:
- merged = _cloneDeep(state)
- fetchArray = _get(merged, FETCHING_CHALLENGES_STATUS, [])
- _pull(fetchArray, action.fetchId)
- merged[FETCHING_CHALLENGES_STATUS] = fetchArray
+ merged = _cloneDeep(state);
+ fetchArray = _get(merged, FETCHING_CHALLENGES_STATUS, []);
+ _pull(fetchArray, action.fetchId);
+ merged[FETCHING_CHALLENGES_STATUS] = fetchArray;
- return merged
+ return merged;
case CLEAR_FETCHING_CHALLENGES:
- return Object.assign({}, state, {FETCHING_CHALLENGES_STATUS: []})
+ return Object.assign({}, state, { FETCHING_CHALLENGES_STATUS: [] });
case SET_CHECKING_LOGIN_STATUS:
- return Object.assign({}, state, {CHECKING_LOGIN_STATUS: true})
+ return Object.assign({}, state, { CHECKING_LOGIN_STATUS: true });
case CLEAR_CHECKING_LOGIN_STATUS:
- return Object.assign({}, state, {CHECKING_LOGIN_STATUS: false})
+ return Object.assign({}, state, { CHECKING_LOGIN_STATUS: false });
default:
- return state
+ return state;
}
-}
+};
diff --git a/src/services/SuperAdmin/SuperAdminChallenges.js b/src/services/SuperAdmin/SuperAdminChallenges.js
index 0169342a1..ec833d212 100644
--- a/src/services/SuperAdmin/SuperAdminChallenges.js
+++ b/src/services/SuperAdmin/SuperAdminChallenges.js
@@ -1,32 +1,28 @@
-import { performChallengeSearch } from '../Challenge/Challenge'
-import { SET_ADMIN_CHALLENGES } from '../Challenge/ChallengeActions'
+import { performChallengeSearch } from "../Challenge/Challenge";
+import { SET_ADMIN_CHALLENGES } from "../Challenge/ChallengeActions";
export const receiveAdminChallenges = function (normalizedEntities, dispatch) {
dispatch({
type: SET_ADMIN_CHALLENGES,
payload: [],
loadingCompleted: false,
- })
+ });
const results = Object.keys(normalizedEntities.challenges).map(
- (i) => normalizedEntities.challenges[i]
- )
+ (i) => normalizedEntities.challenges[i],
+ );
return {
type: SET_ADMIN_CHALLENGES,
payload: results || [],
loadingCompleted: true,
- }
-}
+ };
+};
export const fetchAdminChallenges = function (query) {
return function (dispatch) {
- return dispatch(performChallengeSearch(query, 50000, true)).then(
- (normalizedResults) => {
- return dispatch(
- receiveAdminChallenges(normalizedResults.entities, dispatch)
- )
- }
- )
- }
-}
+ return dispatch(performChallengeSearch(query, 50000, true)).then((normalizedResults) => {
+ return dispatch(receiveAdminChallenges(normalizedResults.entities, dispatch));
+ });
+ };
+};
diff --git a/src/services/SuperAdmin/SuperAdminProjects.js b/src/services/SuperAdmin/SuperAdminProjects.js
index 8e48e6f07..56ee5b94e 100644
--- a/src/services/SuperAdmin/SuperAdminProjects.js
+++ b/src/services/SuperAdmin/SuperAdminProjects.js
@@ -1,48 +1,43 @@
-import { fetchProjects } from '../Project/Project'
+import { fetchProjects } from "../Project/Project";
-const SET_ADMIN_PROJECTS = 'SET_ADMIN_PROJECTS'
+const SET_ADMIN_PROJECTS = "SET_ADMIN_PROJECTS";
export const receiveAdminProjects = function (normalizedEntities, dispatch) {
dispatch({
type: SET_ADMIN_PROJECTS,
payload: [],
loadingCompleted: false,
- })
+ });
const results = Object.keys(normalizedEntities.projects).map(
- (i) => normalizedEntities.projects[i]
- )
+ (i) => normalizedEntities.projects[i],
+ );
return {
type: SET_ADMIN_PROJECTS,
payload: results || [],
loadingCompleted: true,
- }
-}
+ };
+};
export const fetchAdminProjects = function () {
return function (dispatch) {
return dispatch(fetchProjects(50000)).then((normalizedResults) => {
- return dispatch(
- receiveAdminProjects(normalizedResults.entities, dispatch)
- )
- })
- }
-}
+ return dispatch(receiveAdminProjects(normalizedResults.entities, dispatch));
+ });
+ };
+};
const ADMIN_PROJECTS_INITIAL_STATE = {
data: [],
loadingCompleted: false,
-}
+};
-export const adminProjectEntities = function (
- state = ADMIN_PROJECTS_INITIAL_STATE,
- action
-) {
+export const adminProjectEntities = function (state = ADMIN_PROJECTS_INITIAL_STATE, action) {
switch (action.type) {
case SET_ADMIN_PROJECTS:
- return { data: action.payload, loadingCompleted: action.loadingCompleted }
+ return { data: action.payload, loadingCompleted: action.loadingCompleted };
default:
- return state
+ return state;
}
-}
+};
diff --git a/src/services/SuperAdmin/SuperAdminUsers.js b/src/services/SuperAdmin/SuperAdminUsers.js
index a0c3874f6..cb44e231e 100644
--- a/src/services/SuperAdmin/SuperAdminUsers.js
+++ b/src/services/SuperAdmin/SuperAdminUsers.js
@@ -1,46 +1,41 @@
-import { fetchUsers } from '../User/User'
+import { fetchUsers } from "../User/User";
-const SET_ADMIN_USERS = 'SET_ADMIN_USERS'
+const SET_ADMIN_USERS = "SET_ADMIN_USERS";
export const receiveAdminUsers = function (normalizedEntities, dispatch) {
dispatch({
type: SET_ADMIN_USERS,
payload: [],
loadingCompleted: false,
- })
+ });
- const results = Object.keys(normalizedEntities.users).map(
- (i) => normalizedEntities.users[i]
- )
+ const results = Object.keys(normalizedEntities.users).map((i) => normalizedEntities.users[i]);
return {
type: SET_ADMIN_USERS,
payload: results || [],
loadingCompleted: true,
- }
-}
+ };
+};
export const fetchAdminUsers = function () {
return function (dispatch) {
return dispatch(fetchUsers(50000)).then((normalizedResults) => {
- return dispatch(receiveAdminUsers(normalizedResults.entities, dispatch))
- })
- }
-}
+ return dispatch(receiveAdminUsers(normalizedResults.entities, dispatch));
+ });
+ };
+};
const ADMIN_USERS_INITIAL_STATE = {
data: [],
loadingCompleted: false,
-}
+};
-export const adminUserEntities = function (
- state = ADMIN_USERS_INITIAL_STATE,
- action
-) {
+export const adminUserEntities = function (state = ADMIN_USERS_INITIAL_STATE, action) {
switch (action.type) {
case SET_ADMIN_USERS:
- return { data: action.payload, loadingCompleted: action.loadingCompleted }
+ return { data: action.payload, loadingCompleted: action.loadingCompleted };
default:
- return state
+ return state;
}
-}
+};
diff --git a/src/services/SystemNotices/SystemNotices.js b/src/services/SystemNotices/SystemNotices.js
index 1f2d7765c..19de18e2d 100644
--- a/src/services/SystemNotices/SystemNotices.js
+++ b/src/services/SystemNotices/SystemNotices.js
@@ -1,6 +1,6 @@
-import _isArray from 'lodash/isArray'
-import { isFuture, parseISO } from 'date-fns'
-import Endpoint from '../Server/Endpoint'
+import { isFuture, parseISO } from "date-fns";
+import _isArray from "lodash/isArray";
+import Endpoint from "../Server/Endpoint";
import { defaultRoutes as api } from "../Server/Server";
/**
@@ -33,21 +33,22 @@ import { defaultRoutes as api } from "../Server/Server";
export const fetchActiveSystemNotices = () => {
return new Endpoint(api.user.announcements)
.execute()
- .then(response => {
- const systemNotices = response?.message?.notices
- if (_isArray(systemNotices)) {
- return systemNotices.map(notice => {
- // add Date instance for expiration timestamp
- notice.expirationDate = parseISO(notice.expirationTimestamp)
- return notice
- }).filter(notice => isFuture(notice.expirationDate))
- }
- else {
+ .then((response) => {
+ const systemNotices = response?.message?.notices;
+ if (_isArray(systemNotices)) {
+ return systemNotices
+ .map((notice) => {
+ // add Date instance for expiration timestamp
+ notice.expirationDate = parseISO(notice.expirationTimestamp);
+ return notice;
+ })
+ .filter((notice) => isFuture(notice.expirationDate));
+ } else {
// Allow server admin to delete file when not in use
- return []
+ return [];
}
})
.catch(() => {
- return []
- })
-}
+ return [];
+ });
+};
diff --git a/src/services/Task/BoundedTask.js b/src/services/Task/BoundedTask.js
index adbfc7616..992a7d569 100644
--- a/src/services/Task/BoundedTask.js
+++ b/src/services/Task/BoundedTask.js
@@ -1,36 +1,35 @@
-import { v1 as uuidv1 } from 'uuid'
-import uuidTime from 'uuid-time'
-import { defaultRoutes as api } from '../Server/Server'
-import Endpoint from '../Server/Endpoint'
-import RequestStatus from '../Server/RequestStatus'
-import { toLatLngBounds } from '../MapBounds/MapBounds'
-import { taskSchema } from './Task'
-import { addError } from '../Error/Error'
-import AppErrors from '../Error/AppErrors'
-import _values from 'lodash/values'
-import _isUndefined from 'lodash/isUndefined'
-import _map from 'lodash/map'
-import { generateSearchParametersString } from '../Search/Search'
-import { clearTaskClusters } from './TaskClusters'
-import { CHALLENGE_LOCATION_WITHIN_MAPBOUNDS }
- from '../Challenge/ChallengeLocation/ChallengeLocation'
-import { CHALLENGE_EXCLUDE_LOCAL, CHALLENGE_INCLUDE_LOCAL }
- from '../Challenge/Challenge'
-
+import _isUndefined from "lodash/isUndefined";
+import _map from "lodash/map";
+import _values from "lodash/values";
+import { v1 as uuidv1 } from "uuid";
+import uuidTime from "uuid-time";
+import { CHALLENGE_EXCLUDE_LOCAL, CHALLENGE_INCLUDE_LOCAL } from "../Challenge/Challenge";
+import { CHALLENGE_LOCATION_WITHIN_MAPBOUNDS } from "../Challenge/ChallengeLocation/ChallengeLocation";
+import AppErrors from "../Error/AppErrors";
+import { addError } from "../Error/Error";
+import { toLatLngBounds } from "../MapBounds/MapBounds";
+import { generateSearchParametersString } from "../Search/Search";
+import Endpoint from "../Server/Endpoint";
+import RequestStatus from "../Server/RequestStatus";
+import { defaultRoutes as api } from "../Server/Server";
+import { taskSchema } from "./Task";
+import { clearTaskClusters } from "./TaskClusters";
// redux actions
-const RECEIVE_BOUNDED_TASKS = 'RECEIVE_BOUNDED_TASKS'
-const CLEAR_BOUNDED_TASKS = 'CLEAR_BOUNDED_TASKS'
+const RECEIVE_BOUNDED_TASKS = "RECEIVE_BOUNDED_TASKS";
+const CLEAR_BOUNDED_TASKS = "CLEAR_BOUNDED_TASKS";
// redux action creators
/**
* Add or replace the map-bounded tasks in the redux store
*/
-export const receiveBoundedTasks = function(tasks,
- status=RequestStatus.success,
- fetchId,
- totalCount=null) {
+export const receiveBoundedTasks = function (
+ tasks,
+ status = RequestStatus.success,
+ fetchId,
+ totalCount = null,
+) {
return {
type: RECEIVE_BOUNDED_TASKS,
status,
@@ -38,26 +37,31 @@ export const receiveBoundedTasks = function(tasks,
fetchId,
totalCount,
receivedAt: Date.now(),
- }
-}
+ };
+};
/**
* Retrieve all task markers (up to the given limit) matching the given search
* criteria, which should at least include a boundingBox field, and may
* optionally include a filters field with additional constraints
*/
-export function fetchBoundedTaskMarkers(criteria, limit = 50, skipDispatch = false, ignoreLocked = true) {
- return function(dispatch) {
+export function fetchBoundedTaskMarkers(
+ criteria,
+ limit = 50,
+ skipDispatch = false,
+ ignoreLocked = true,
+) {
+ return function (dispatch) {
if (!skipDispatch) {
// The map is either showing task clusters or bounded tasks so we shouldn't
// have both in redux.
// (ChallengeLocation needs to know which challenge tasks pass the location)
- dispatch(clearTaskClusters())
+ dispatch(clearTaskClusters());
}
- const normalizedBounds = toLatLngBounds(criteria.boundingBox)
+ const normalizedBounds = toLatLngBounds(criteria.boundingBox);
if (!normalizedBounds) {
- return null
+ return null;
}
const filters = criteria.filters ?? {};
@@ -67,75 +71,77 @@ export function fetchBoundedTaskMarkers(criteria, limit = 50, skipDispatch = fal
criteria.savedChallengesOnly,
null,
null,
- criteria.invertFields
- )
+ criteria.invertFields,
+ );
if (!filters.challengeId) {
const onlyEnabled = criteria.onlyEnabled ?? true;
- const challengeStatus = criteria.challengeStatus
+ const challengeStatus = criteria.challengeStatus;
if (challengeStatus) {
- searchParameters.cStatus = challengeStatus.join(',')
+ searchParameters.cStatus = challengeStatus.join(",");
}
// ce: limit to enabled challenges
// pe: limit to enabled projects
- searchParameters.ce = onlyEnabled ? 'true' : 'false'
- searchParameters.pe = onlyEnabled ? 'true' : 'false'
+ searchParameters.ce = onlyEnabled ? "true" : "false";
+ searchParameters.pe = onlyEnabled ? "true" : "false";
// if we are restricting to onlyEnabled challenges then let's
// not show 'local' challenges either.
- searchParameters.cLocal = onlyEnabled ? CHALLENGE_EXCLUDE_LOCAL :
- CHALLENGE_INCLUDE_LOCAL
+ searchParameters.cLocal = onlyEnabled ? CHALLENGE_EXCLUDE_LOCAL : CHALLENGE_INCLUDE_LOCAL;
}
// If we are searching within map bounds we need to ensure the parent
// challenge is also within those bounds
if (filters.location === CHALLENGE_LOCATION_WITHIN_MAPBOUNDS) {
if (Array.isArray(criteria.boundingBox)) {
- searchParameters.bb = criteria.boundingBox.join(',')
+ searchParameters.bb = criteria.boundingBox.join(",");
} else {
- searchParameters.bb = criteria.boundingBox
+ searchParameters.bb = criteria.boundingBox;
}
}
- const fetchId = uuidv1()
- !skipDispatch && dispatch(receiveBoundedTasks(null, RequestStatus.inProgress, fetchId))
-
- return new Endpoint(
- api.tasks.markersWithinBounds, {
- schema: {
- tasks: [taskSchema()]
- },
- variables: {
- left: normalizedBounds.getWest(),
- bottom: normalizedBounds.getSouth(),
- right: normalizedBounds.getEast(),
- top: normalizedBounds.getNorth(),
- },
- params: {
- limit,
- excludeLocked: ignoreLocked,
- ...searchParameters,
- },
- json: filters.taskPropertySearch ? {
- taskPropertySearch: filters.taskPropertySearch
- } : null,
- }
- ).execute().then(({ result }) => {
- let tasks = result ? Object.values(result) : [];
- tasks = tasks.map(task => Object.assign(task, task.pointReview))
+ const fetchId = uuidv1();
+ !skipDispatch && dispatch(receiveBoundedTasks(null, RequestStatus.inProgress, fetchId));
+
+ return new Endpoint(api.tasks.markersWithinBounds, {
+ schema: {
+ tasks: [taskSchema()],
+ },
+ variables: {
+ left: normalizedBounds.getWest(),
+ bottom: normalizedBounds.getSouth(),
+ right: normalizedBounds.getEast(),
+ top: normalizedBounds.getNorth(),
+ },
+ params: {
+ limit,
+ excludeLocked: ignoreLocked,
+ ...searchParameters,
+ },
+ json: filters.taskPropertySearch
+ ? {
+ taskPropertySearch: filters.taskPropertySearch,
+ }
+ : null,
+ })
+ .execute()
+ .then(({ result }) => {
+ let tasks = result ? Object.values(result) : [];
+ tasks = tasks.map((task) => Object.assign(task, task.pointReview));
- if (!skipDispatch) {
- dispatch(receiveBoundedTasks(tasks, RequestStatus.success, fetchId, tasks.length))
- }
+ if (!skipDispatch) {
+ dispatch(receiveBoundedTasks(tasks, RequestStatus.success, fetchId, tasks.length));
+ }
- return tasks
- }).catch(error => {
- dispatch(receiveBoundedTasks([], RequestStatus.error, fetchId))
- dispatch(addError(AppErrors.boundedTask.fetchFailure))
- console.log(error.response || error)
- })
- }
+ return tasks;
+ })
+ .catch((error) => {
+ dispatch(receiveBoundedTasks([], RequestStatus.error, fetchId));
+ dispatch(addError(AppErrors.boundedTask.fetchFailure));
+ console.log(error.response || error);
+ });
+ };
}
/**
@@ -143,148 +149,158 @@ export function fetchBoundedTaskMarkers(criteria, limit = 50, skipDispatch = fal
* criteria, which should at least include a boundingBox field, and may
* optionally include a filters field with additional constraints
*/
-export function fetchBoundedTasks(criteria, limit=50, skipDispatch=false, ignoreLocked=true, withGeometries) {
- return function(dispatch) {
+export function fetchBoundedTasks(
+ criteria,
+ limit = 50,
+ skipDispatch = false,
+ ignoreLocked = true,
+ withGeometries,
+) {
+ return function (dispatch) {
if (!skipDispatch) {
// The map is either showing task clusters or bounded tasks so we shouldn't
// have both in redux.
// (ChallengeLocation needs to know which challenge tasks pass the location)
- dispatch(clearTaskClusters())
+ dispatch(clearTaskClusters());
}
- const normalizedBounds = toLatLngBounds(criteria.boundingBox)
+ const normalizedBounds = toLatLngBounds(criteria.boundingBox);
if (!normalizedBounds) {
- return null
+ return null;
}
- let includeGeometries = _isUndefined(withGeometries) ? (limit <= 100) : withGeometries
- const page = criteria?.page ?? 0
- const sortBy = criteria?.sortCriteria?.sortBy
- const direction = ((criteria?.sortCriteria?.direction) || 'ASC').toUpperCase()
+ let includeGeometries = _isUndefined(withGeometries) ? limit <= 100 : withGeometries;
+ const page = criteria?.page ?? 0;
+ const sortBy = criteria?.sortCriteria?.sortBy;
+ const direction = (criteria?.sortCriteria?.direction || "ASC").toUpperCase();
- const filters = criteria?.filters ?? {}
- const searchParameters = generateSearchParametersString(filters,
- null,
- criteria?.savedChallengesOnly,
- null, null,
- criteria?.invertFields)
- const includeTags = criteria?.includeTags ?? false
+ const filters = criteria?.filters ?? {};
+ const searchParameters = generateSearchParametersString(
+ filters,
+ null,
+ criteria?.savedChallengesOnly,
+ null,
+ null,
+ criteria?.invertFields,
+ );
+ const includeTags = criteria?.includeTags ?? false;
// If we don't have a challenge Id then we need to do some limiting.
if (!filters.challengeId) {
- includeGeometries = false
- const onlyEnabled = _isUndefined(criteria.onlyEnabled) ?
- true : criteria.onlyEnabled
- const challengeStatus = criteria.challengeStatus
+ includeGeometries = false;
+ const onlyEnabled = _isUndefined(criteria.onlyEnabled) ? true : criteria.onlyEnabled;
+ const challengeStatus = criteria.challengeStatus;
if (challengeStatus) {
- searchParameters.cStatus = challengeStatus.join(',')
+ searchParameters.cStatus = challengeStatus.join(",");
}
// ce: limit to enabled challenges
// pe: limit to enabled projects
- searchParameters.ce = onlyEnabled ? 'true' : 'false'
- searchParameters.pe = onlyEnabled ? 'true' : 'false'
+ searchParameters.ce = onlyEnabled ? "true" : "false";
+ searchParameters.pe = onlyEnabled ? "true" : "false";
// if we are restricting to onlyEnabled challenges then let's
// not show 'local' challenges either.
- searchParameters.cLocal = onlyEnabled ? CHALLENGE_EXCLUDE_LOCAL :
- CHALLENGE_INCLUDE_LOCAL
+ searchParameters.cLocal = onlyEnabled ? CHALLENGE_EXCLUDE_LOCAL : CHALLENGE_INCLUDE_LOCAL;
}
// If we are searching within map bounds we need to ensure the parent
// challenge is also within those bounds
if (filters.location === CHALLENGE_LOCATION_WITHIN_MAPBOUNDS) {
if (Array.isArray(criteria.boundingBox)) {
- searchParameters.bb = criteria.boundingBox.join(',')
- }
- else {
- searchParameters.bb = criteria.boundingBox
+ searchParameters.bb = criteria.boundingBox.join(",");
+ } else {
+ searchParameters.bb = criteria.boundingBox;
}
}
- const fetchId = uuidv1()
- !skipDispatch && dispatch(receiveBoundedTasks(null, RequestStatus.inProgress, fetchId))
-
- return new Endpoint(
- api.tasks.withinBounds, {
- schema: {tasks: [taskSchema()]},
- variables: {
- left: normalizedBounds.getWest(),
- bottom: normalizedBounds.getSouth(),
- right: normalizedBounds.getEast(),
- top: normalizedBounds.getNorth(),
- },
- params: {limit, page, sort: sortBy, order: direction,
- includeTotal: true, excludeLocked: ignoreLocked, ...searchParameters,
- includeGeometries, includeTags},
- json: filters.taskPropertySearch ?
- {taskPropertySearch: filters.taskPropertySearch} : null,
- }
- ).execute().then(normalizedResults => {
- const totalCount = normalizedResults.result.total
-
- let tasks = _values(normalizedResults?.entities?.tasks ?? {})
- tasks = _map(tasks, task =>
- Object.assign(task, {}, task.pointReview)
- )
-
- !skipDispatch && dispatch(receiveBoundedTasks(tasks, RequestStatus.success, fetchId, totalCount))
-
- return {tasks, totalCount}
- }).catch(error => {
- dispatch(receiveBoundedTasks([], RequestStatus.error, fetchId))
- dispatch(addError(AppErrors.boundedTask.fetchFailure))
- console.log(error.response || error)
- });
+ const fetchId = uuidv1();
+ !skipDispatch && dispatch(receiveBoundedTasks(null, RequestStatus.inProgress, fetchId));
+
+ return new Endpoint(api.tasks.withinBounds, {
+ schema: { tasks: [taskSchema()] },
+ variables: {
+ left: normalizedBounds.getWest(),
+ bottom: normalizedBounds.getSouth(),
+ right: normalizedBounds.getEast(),
+ top: normalizedBounds.getNorth(),
+ },
+ params: {
+ limit,
+ page,
+ sort: sortBy,
+ order: direction,
+ includeTotal: true,
+ excludeLocked: ignoreLocked,
+ ...searchParameters,
+ includeGeometries,
+ includeTags,
+ },
+ json: filters.taskPropertySearch ? { taskPropertySearch: filters.taskPropertySearch } : null,
+ })
+ .execute()
+ .then((normalizedResults) => {
+ const totalCount = normalizedResults.result.total;
+
+ let tasks = _values(normalizedResults?.entities?.tasks ?? {});
+ tasks = _map(tasks, (task) => Object.assign(task, {}, task.pointReview));
+
+ !skipDispatch &&
+ dispatch(receiveBoundedTasks(tasks, RequestStatus.success, fetchId, totalCount));
+
+ return { tasks, totalCount };
+ })
+ .catch((error) => {
+ dispatch(receiveBoundedTasks([], RequestStatus.error, fetchId));
+ dispatch(addError(AppErrors.boundedTask.fetchFailure));
+ console.log(error.response || error);
+ });
};
}
/**
* Clear the bounded tasks from the redux store
*/
-export const clearBoundedTasks = function() {
+export const clearBoundedTasks = function () {
return {
type: CLEAR_BOUNDED_TASKS,
- receivedAt: Date.now()
- }
-}
+ receivedAt: Date.now(),
+ };
+};
// redux reducers
-export const currentBoundedTasks = function(state={}, action) {
+export const currentBoundedTasks = function (state = {}, action) {
if (action.type === RECEIVE_BOUNDED_TASKS) {
// Only update the state if this represents either a later fetch
// of data or an update to the current data in the store.
if (action.fetchId !== state.fetchId || action.status !== state.status) {
- const fetchTime = parseInt(uuidTime.v1(action.fetchId))
- const lastFetch = state.fetchId ? parseInt(uuidTime.v1(state.fetchId)) : 0
+ const fetchTime = parseInt(uuidTime.v1(action.fetchId));
+ const lastFetch = state.fetchId ? parseInt(uuidTime.v1(state.fetchId)) : 0;
if (fetchTime >= lastFetch) {
const updatedTasks = {
fetchId: action.fetchId,
- }
+ };
if (action.status === RequestStatus.inProgress) {
// Don't overwrite old tasks for in-progress fetches, as they're probably
// still at least partially relevant as the user pans/zooms the map.
- updatedTasks.tasks = state.tasks
- updatedTasks.loading = true
- }
- else {
- updatedTasks.tasks = Array.isArray(action.tasks) ? action.tasks : []
- updatedTasks.loading = false
- updatedTasks.totalCount = action.totalCount
+ updatedTasks.tasks = state.tasks;
+ updatedTasks.loading = true;
+ } else {
+ updatedTasks.tasks = Array.isArray(action.tasks) ? action.tasks : [];
+ updatedTasks.loading = false;
+ updatedTasks.totalCount = action.totalCount;
}
- return updatedTasks
+ return updatedTasks;
}
}
- return state
+ return state;
+ } else if (action.type === CLEAR_BOUNDED_TASKS) {
+ return {};
+ } else {
+ return state;
}
- else if (action.type === CLEAR_BOUNDED_TASKS) {
- return {}
- }
- else {
- return state
- }
-}
+};
diff --git a/src/services/Task/ClusteredTask.js b/src/services/Task/ClusteredTask.js
index 404d7ecb4..86cab453c 100644
--- a/src/services/Task/ClusteredTask.js
+++ b/src/services/Task/ClusteredTask.js
@@ -1,30 +1,32 @@
-import { v1 as uuidv1 } from 'uuid'
-import uuidTime from 'uuid-time'
-import RequestStatus from '../Server/RequestStatus'
-import _each from 'lodash/each'
-import _isArray from 'lodash/isArray'
-import _uniqBy from 'lodash/uniqBy'
-import _cloneDeep from 'lodash/cloneDeep'
-import _set from 'lodash/set'
-import { fetchBoundedTasks } from './BoundedTask'
+import _cloneDeep from "lodash/cloneDeep";
+import _each from "lodash/each";
+import _isArray from "lodash/isArray";
+import _set from "lodash/set";
+import _uniqBy from "lodash/uniqBy";
+import { v1 as uuidv1 } from "uuid";
+import uuidTime from "uuid-time";
+import RequestStatus from "../Server/RequestStatus";
+import { fetchBoundedTasks } from "./BoundedTask";
// redux actions
-const RECEIVE_CLUSTERED_TASKS = 'RECEIVE_CLUSTERED_TASKS'
-const CLEAR_CLUSTERED_TASKS = 'CLEAR_CLUSTERED_TASKS'
+const RECEIVE_CLUSTERED_TASKS = "RECEIVE_CLUSTERED_TASKS";
+const CLEAR_CLUSTERED_TASKS = "CLEAR_CLUSTERED_TASKS";
// redux action creators
/**
* Add or replace the clustered tasks in the redux store
*/
-export const receiveClusteredTasks = function(challengeId,
- isVirtualChallenge,
- tasks,
- status=RequestStatus.success,
- fetchId,
- mergeTasks=false,
- mergeOrIgnore=false,
- totalCount=null) {
+export const receiveClusteredTasks = function (
+ challengeId,
+ isVirtualChallenge,
+ tasks,
+ status = RequestStatus.success,
+ fetchId,
+ mergeTasks = false,
+ mergeOrIgnore = false,
+ totalCount = null,
+) {
return {
type: RECEIVE_CLUSTERED_TASKS,
status,
@@ -35,20 +37,19 @@ export const receiveClusteredTasks = function(challengeId,
receivedAt: Date.now(),
mergeTasks,
mergeOrIgnore,
- totalCount
- }
-}
+ totalCount,
+ };
+};
/**
* Clear the clustered tasks from the redux store
*/
-export const clearClusteredTasks = function() {
+export const clearClusteredTasks = function () {
return {
type: CLEAR_CLUSTERED_TASKS,
- receivedAt: Date.now()
- }
-}
-
+ receivedAt: Date.now(),
+ };
+};
// async action creators
@@ -60,38 +61,56 @@ export const clearClusteredTasks = function() {
* fetched -- when its necessary to ensure tasks in a bbox are included in the
* clustered tasks
*/
-export const augmentClusteredTasks = function(challengeId, isVirtualChallenge=false, criteria, limit=15000,
- mergeTasks=true, ignoreLocked=true) {
- return function(dispatch) {
+export const augmentClusteredTasks = function (
+ challengeId,
+ isVirtualChallenge = false,
+ criteria,
+ limit = 15000,
+ mergeTasks = true,
+ ignoreLocked = true,
+) {
+ return function (dispatch) {
if (isVirtualChallenge) {
- return
+ return;
}
- const fetchId = uuidv1()
- const augmentedCriteria = _cloneDeep(criteria)
- _set(augmentedCriteria, 'filters.challengeId', challengeId)
- return fetchBoundedTasks(augmentedCriteria, limit, true, ignoreLocked)(dispatch).then(result => {
+ const fetchId = uuidv1();
+ const augmentedCriteria = _cloneDeep(criteria);
+ _set(augmentedCriteria, "filters.challengeId", challengeId);
+ return fetchBoundedTasks(
+ augmentedCriteria,
+ limit,
+ true,
+ ignoreLocked,
+ )(dispatch).then((result) => {
if (result) {
// Add parent field
- _each(result.tasks, task => task.parent = challengeId)
+ _each(result.tasks, (task) => (task.parent = challengeId));
- return dispatch(receiveClusteredTasks(
- challengeId, isVirtualChallenge, result.tasks, RequestStatus.success, fetchId,
- mergeTasks, false, result.totalCount
- ))
+ return dispatch(
+ receiveClusteredTasks(
+ challengeId,
+ isVirtualChallenge,
+ result.tasks,
+ RequestStatus.success,
+ fetchId,
+ mergeTasks,
+ false,
+ result.totalCount,
+ ),
+ );
}
- })
- }
-}
-
+ });
+ };
+};
// redux reducers
-export const currentClusteredTasks = function(state={}, action) {
+export const currentClusteredTasks = function (state = {}, action) {
if (action.type === RECEIVE_CLUSTERED_TASKS) {
// Only update the state if this represents either a later fetch
// of data or an update to the current data in the store.
- const fetchTime = parseInt(uuidTime.v1(action.fetchId))
- const lastFetch = state.fetchId ? parseInt(uuidTime.v1(state.fetchId)) : 0
+ const fetchTime = parseInt(uuidTime.v1(action.fetchId));
+ const lastFetch = state.fetchId ? parseInt(uuidTime.v1(state.fetchId)) : 0;
if (fetchTime >= lastFetch || action.mergeTasks) {
const merged = {
@@ -100,33 +119,31 @@ export const currentClusteredTasks = function(state={}, action) {
loading: action.status === RequestStatus.inProgress,
fetchId: action.fetchId,
tasks: _isArray(action.tasks) ? action.tasks : [],
- totalCount: action.totalCount
- }
+ totalCount: action.totalCount,
+ };
// If a merge is requested and the new clustered tasks are from the same
// challenge, concat the new tasks to the existing ones
- if (action.mergeTasks &&
- state.challengeId === merged.challengeId &&
- state.isVirtualChallenge === merged.isVirtualChallenge &&
- state.tasks.length > 0) {
- merged.tasks = _uniqBy(merged.tasks.concat(state.tasks), 'id')
- merged.totalCount = action.totalCount
- }
- else if (action.mergeOrIgnore) {
+ if (
+ action.mergeTasks &&
+ state.challengeId === merged.challengeId &&
+ state.isVirtualChallenge === merged.isVirtualChallenge &&
+ state.tasks.length > 0
+ ) {
+ merged.tasks = _uniqBy(merged.tasks.concat(state.tasks), "id");
+ merged.totalCount = action.totalCount;
+ } else if (action.mergeOrIgnore) {
// Ignore update if we can't merge it
- return state
+ return state;
}
- return merged
- }
- else {
- return state
+ return merged;
+ } else {
+ return state;
}
+ } else if (action.type === CLEAR_CLUSTERED_TASKS) {
+ return {};
+ } else {
+ return state;
}
- else if (action.type === CLEAR_CLUSTERED_TASKS) {
- return {}
- }
- else {
- return state
- }
-}
+};
diff --git a/src/services/Task/Task.js b/src/services/Task/Task.js
index 0c28d134b..f21c8b772 100644
--- a/src/services/Task/Task.js
+++ b/src/services/Task/Task.js
@@ -1,187 +1,199 @@
-import { schema } from 'normalizr'
-import { v1 as uuidv1 } from 'uuid'
-import _pick from 'lodash/pick'
-import _cloneDeep from 'lodash/cloneDeep'
-import _keys from 'lodash/keys'
-import _map from 'lodash/map'
-import _isEmpty from 'lodash/isEmpty'
-import _isUndefined from 'lodash/isUndefined'
-import _isString from 'lodash/isString'
-import _isFinite from 'lodash/isFinite'
-import _isArray from 'lodash/isArray'
-import _isObject from 'lodash/isObject'
-import _values from 'lodash/values'
-import _remove from 'lodash/remove'
-import { defaultRoutes as api, isSecurityError, websocketClient } from '../Server/Server'
-import Endpoint from '../Server/Endpoint'
-import RequestStatus from '../Server/RequestStatus'
-import genericEntityReducer from '../Server/GenericEntityReducer'
-import { challengeSchema } from '../Challenge/Challenge'
-import { placeSchema, fetchPlace } from '../Place/Place'
-import { commentSchema, receiveComments } from '../Comment/Comment'
-import { addServerError, addError, addErrorWithDetails } from '../Error/Error'
-import AppErrors from '../Error/AppErrors'
-import { ensureUserLoggedIn } from '../User/User'
-import { markReviewDataStale } from './TaskReview/TaskReview'
-import { receiveClusteredTasks } from './ClusteredTask'
-import { TaskStatus } from './TaskStatus/TaskStatus'
-import { generateSearchParametersString } from '../Search/Search'
+import _cloneDeep from "lodash/cloneDeep";
+import _isArray from "lodash/isArray";
+import _isEmpty from "lodash/isEmpty";
+import _isFinite from "lodash/isFinite";
+import _isObject from "lodash/isObject";
+import _isString from "lodash/isString";
+import _isUndefined from "lodash/isUndefined";
+import _keys from "lodash/keys";
+import _map from "lodash/map";
+import _pick from "lodash/pick";
+import _remove from "lodash/remove";
+import _values from "lodash/values";
+import { schema } from "normalizr";
+import { v1 as uuidv1 } from "uuid";
+import { challengeSchema } from "../Challenge/Challenge";
+import { commentSchema, receiveComments } from "../Comment/Comment";
+import AppErrors from "../Error/AppErrors";
+import { addError, addErrorWithDetails, addServerError } from "../Error/Error";
+import { fetchPlace, placeSchema } from "../Place/Place";
+import { generateSearchParametersString } from "../Search/Search";
+import Endpoint from "../Server/Endpoint";
+import genericEntityReducer from "../Server/GenericEntityReducer";
+import RequestStatus from "../Server/RequestStatus";
+import { defaultRoutes as api, isSecurityError, websocketClient } from "../Server/Server";
+import { ensureUserLoggedIn } from "../User/User";
+import { receiveClusteredTasks } from "./ClusteredTask";
+import { markReviewDataStale } from "./TaskReview/TaskReview";
+import { TaskStatus } from "./TaskStatus/TaskStatus";
/** normalizr schema for tasks */
-export const taskSchema = function() {
- return new schema.Entity('tasks')
-}
+export const taskSchema = function () {
+ return new schema.Entity("tasks");
+};
/** normalizr schema for task tags */
-export const taskTagsSchema = function() {
- return new schema.Entity('tags')
-}
+export const taskTagsSchema = function () {
+ return new schema.Entity("tags");
+};
-export const taskBundleSchema = function() {
- return new schema.Entity('taskBundles', {tasks: [ taskSchema() ]})
-}
+export const taskBundleSchema = function () {
+ return new schema.Entity("taskBundles", { tasks: [taskSchema()] });
+};
/**
* normalizr denormalization schema, which will pull in projects and places
* (fetched separately, so not needed in normal schema)
*/
-export const taskDenormalizationSchema = function() {
- return new schema.Entity('tasks', {
+export const taskDenormalizationSchema = function () {
+ return new schema.Entity("tasks", {
parent: challengeSchema(),
place: placeSchema(),
- comments: [ commentSchema() ]
- })
-}
+ comments: [commentSchema()],
+ });
+};
-export const subscribeToChallengeTaskMessages = function(dispatch, challengeId) {
+export const subscribeToChallengeTaskMessages = function (dispatch, challengeId) {
websocketClient.addServerSubscription(
- "challengeTasks", challengeId, "challengeTaskMessageHandler",
- messageObject => onChallengeTaskMessage(dispatch, messageObject)
- )
-}
-
-export const unsubscribeFromChallengeTaskMessages = function(challengeId) {
- websocketClient.removeServerSubscription("challengeTasks", challengeId, "challengeTaskMessageHandler")
-}
+ "challengeTasks",
+ challengeId,
+ "challengeTaskMessageHandler",
+ (messageObject) => onChallengeTaskMessage(dispatch, messageObject),
+ );
+};
-export const subscribeToReviewMessages = function(dispatch) {
- websocketClient.addServerSubscription(
- "reviews", null, "reviewMessageHandler",
- messageObject => onReviewMessage(dispatch, messageObject)
- )
-}
+export const unsubscribeFromChallengeTaskMessages = function (challengeId) {
+ websocketClient.removeServerSubscription(
+ "challengeTasks",
+ challengeId,
+ "challengeTaskMessageHandler",
+ );
+};
-export const unsubscribeFromReviewMessages = function() {
- websocketClient.removeServerSubscription("reviews", null, "reviewMessageHandler")
-}
+export const subscribeToReviewMessages = function (dispatch) {
+ websocketClient.addServerSubscription("reviews", null, "reviewMessageHandler", (messageObject) =>
+ onReviewMessage(dispatch, messageObject),
+ );
+};
-export const subscribeToAllTasks = function(callback, handle) {
- websocketClient.addServerSubscription(
- "tasks",
- null,
- handle,
- messageObject => callback(messageObject)
- )
-}
+export const unsubscribeFromReviewMessages = function () {
+ websocketClient.removeServerSubscription("reviews", null, "reviewMessageHandler");
+};
-export const unsubscribeFromAllTasks = function(handle) {
- websocketClient.removeServerSubscription("tasks", null, handle)
-}
+export const subscribeToAllTasks = function (callback, handle) {
+ websocketClient.addServerSubscription("tasks", null, handle, (messageObject) =>
+ callback(messageObject),
+ );
+};
+export const unsubscribeFromAllTasks = function (handle) {
+ websocketClient.removeServerSubscription("tasks", null, handle);
+};
-const onReviewMessage = function(dispatch, messageObject) {
- switch(messageObject.messageType) {
+const onReviewMessage = function (dispatch, messageObject) {
+ switch (messageObject.messageType) {
case "review-new":
case "review-claimed":
case "review-update":
// For now just mark the existing review data as stale
- dispatch(markReviewDataStale())
- break
+ dispatch(markReviewDataStale());
+ break;
default:
- break // Ignore
+ break; // Ignore
}
-}
+};
-const onChallengeTaskMessage = function(dispatch, messageObject) {
- let task = messageObject.data.task
- switch(messageObject.messageType) {
+const onChallengeTaskMessage = function (dispatch, messageObject) {
+ let task = messageObject.data.task;
+ switch (messageObject.messageType) {
case "task-claimed":
- task = Object.assign({}, task, {lockedBy: messageObject?.data?.byUser?.userId})
- dispatchTaskUpdateNotification(dispatch, task)
- break
+ task = Object.assign({}, task, { lockedBy: messageObject?.data?.byUser?.userId });
+ dispatchTaskUpdateNotification(dispatch, task);
+ break;
case "task-released":
case "task-update":
- dispatchTaskUpdateNotification(dispatch, task)
- break
+ dispatchTaskUpdateNotification(dispatch, task);
+ break;
default:
- break // Ignore
+ break; // Ignore
}
-}
-
-const dispatchTaskUpdateNotification = function(dispatch, task) {
- dispatch(receiveTasks(simulatedEntities(task)))
- dispatch(receiveClusteredTasks(
- task.parent,
- false,
- [Object.assign(
- {},
- _pick(task, ['id', 'created', 'modified', 'priority', 'status', 'difficulty', 'lockedBy']),
- {
- parentId: task.parent,
- point: {lng: task.location.coordinates[0], lat: task.location.coordinates[1]},
- title: task.name,
- type: 2,
- }
- )],
- RequestStatus.success,
- uuidv1(),
- true,
- true
- ))
-}
+};
+const dispatchTaskUpdateNotification = function (dispatch, task) {
+ dispatch(receiveTasks(simulatedEntities(task)));
+ dispatch(
+ receiveClusteredTasks(
+ task.parent,
+ false,
+ [
+ Object.assign(
+ {},
+ _pick(task, [
+ "id",
+ "created",
+ "modified",
+ "priority",
+ "status",
+ "difficulty",
+ "lockedBy",
+ ]),
+ {
+ parentId: task.parent,
+ point: { lng: task.location.coordinates[0], lat: task.location.coordinates[1] },
+ title: task.name,
+ type: 2,
+ },
+ ),
+ ],
+ RequestStatus.success,
+ uuidv1(),
+ true,
+ true,
+ ),
+ );
+};
// redux actions
-const RECEIVE_TASKS = 'RECEIVE_TASKS'
-const CLEAR_TASKS = 'CLEAR_TASKS'
-const REMOVE_TASK = 'REMOVE_TASK'
+const RECEIVE_TASKS = "RECEIVE_TASKS";
+const CLEAR_TASKS = "CLEAR_TASKS";
+const REMOVE_TASK = "REMOVE_TASK";
// redux action creators
/**
* Add or update task data in the redux store
*/
-export const receiveTasks = function(normalizedEntities) {
+export const receiveTasks = function (normalizedEntities) {
return {
type: RECEIVE_TASKS,
status: RequestStatus.success,
entities: normalizedEntities,
- receivedAt: Date.now()
- }
-}
+ receivedAt: Date.now(),
+ };
+};
/**
* Clear task data for a given challenge from the redux store
*/
-export const clearTasks = function(challengeId) {
+export const clearTasks = function (challengeId) {
return {
type: CLEAR_TASKS,
status: RequestStatus.success,
challengeId: challengeId,
- receivedAt: Date.now()
- }
-}
+ receivedAt: Date.now(),
+ };
+};
/**
* Remove a task from the redux store
*/
-export const removeTask = function(taskId) {
+export const removeTask = function (taskId) {
return {
type: REMOVE_TASK,
taskId,
- receivedAt: Date.now()
- }
-}
+ receivedAt: Date.now(),
+ };
+};
// async action creators
@@ -192,348 +204,403 @@ export const removeTask = function(taskId) {
* If info on available mapillary images for the task is also desired, set
* includeMapillary to true
*/
-export const fetchTask = function(taskId, suppressReceive=false, includeMapillary=false) {
- return function(dispatch) {
+export const fetchTask = function (taskId, suppressReceive = false, includeMapillary = false) {
+ return function (dispatch) {
return new Endpoint(api.task.single, {
schema: taskSchema(),
- variables: {id: taskId},
- params: {mapillary: includeMapillary}
- }).execute().then(normalizedResults => {
- if (!suppressReceive) {
- dispatch(receiveTasks(normalizedResults.entities))
- }
-
- return normalizedResults
+ variables: { id: taskId },
+ params: { mapillary: includeMapillary },
})
- }
-}
+ .execute()
+ .then((normalizedResults) => {
+ if (!suppressReceive) {
+ dispatch(receiveTasks(normalizedResults.entities));
+ }
+
+ return normalizedResults;
+ });
+ };
+};
/**
* Fetch tags for the given task.
*/
-export const fetchTaskTags = function(taskId) {
- return function(dispatch) {
- return new Endpoint(
- api.task.tags,
- {schema: {}, variables: {id: taskId}}
- ).execute().then(normalizedTags => {
- if (_isObject(normalizedTags.result)) {
- // Inject tags into task
- dispatch(receiveTasks(simulatedEntities({
- id: taskId,
- tags: _values(normalizedTags.result),
- })))
- }
- return normalizedTags
- })
- }
-}
+export const fetchTaskTags = function (taskId) {
+ return function (dispatch) {
+ return new Endpoint(api.task.tags, { schema: {}, variables: { id: taskId } })
+ .execute()
+ .then((normalizedTags) => {
+ if (_isObject(normalizedTags.result)) {
+ // Inject tags into task
+ dispatch(
+ receiveTasks(
+ simulatedEntities({
+ id: taskId,
+ tags: _values(normalizedTags.result),
+ }),
+ ),
+ );
+ }
+ return normalizedTags;
+ });
+ };
+};
/**
* Locks a task that is to be started.
*/
-export const startTask = function(taskId) {
- return function(dispatch) {
+export const startTask = function (taskId) {
+ return function (dispatch) {
return new Endpoint(api.task.start, {
schema: taskSchema(),
- variables: {id: taskId}
- }).execute().catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).catch(() => null)
- }
- throw error
+ variables: { id: taskId },
})
- }
-}
+ .execute()
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).catch(() => null);
+ }
+ throw error;
+ });
+ };
+};
/**
* Unlocks a task.
*/
-export const releaseTask = function(taskId) {
- return function(dispatch) {
+export const releaseTask = function (taskId) {
+ return function (dispatch) {
return new Endpoint(api.task.release, {
schema: taskSchema(),
- variables: {id: taskId}
- }).execute().then(normalizedResults => {
- dispatch(receiveTasks(normalizedResults.entities))
- return normalizedResults
- }).catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- ).catch(() => null)
- }
- else {
- dispatch(addError(AppErrors.task.lockReleaseFailure))
- console.log(error.response || error)
- }
+ variables: { id: taskId },
})
- }
-}
+ .execute()
+ .then((normalizedResults) => {
+ dispatch(receiveTasks(normalizedResults.entities));
+ return normalizedResults;
+ })
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn())
+ .then(() => dispatch(addError(AppErrors.user.unauthorized)))
+ .catch(() => null);
+ } else {
+ dispatch(addError(AppErrors.task.lockReleaseFailure));
+ console.log(error.response || error);
+ }
+ });
+ };
+};
/**
* Refreshes an active task lock owned by the current user
*/
-export const refreshTaskLock = function(taskId) {
- return function() {
+export const refreshTaskLock = function (taskId) {
+ return function () {
return new Endpoint(api.task.refreshLock, {
schema: taskSchema(),
- variables: {id: taskId}
- }).execute()
- }
-}
+ variables: { id: taskId },
+ }).execute();
+ };
+};
/**
* Mark the given task as completed with the given status.
*/
-export const completeTask = function(taskId, taskStatus, needsReview,
- tags, cooperativeWorkSummary, osmComment, completionResponses) {
- return function(dispatch) {
- return updateTaskStatus(dispatch, taskId, taskStatus, needsReview, tags,
- cooperativeWorkSummary, osmComment, completionResponses)
- }
-}
+export const completeTask = function (
+ taskId,
+ taskStatus,
+ needsReview,
+ tags,
+ cooperativeWorkSummary,
+ osmComment,
+ completionResponses,
+) {
+ return function (dispatch) {
+ return updateTaskStatus(
+ dispatch,
+ taskId,
+ taskStatus,
+ needsReview,
+ tags,
+ cooperativeWorkSummary,
+ osmComment,
+ completionResponses,
+ );
+ };
+};
/**
* Mark all tasks in the given bundle as completed with the given status
*/
-export const completeTaskBundle = function(bundleId, primaryTaskId, taskStatus, needsReview,
- tags, cooperativeWorkSummary, osmComment, completionResponses) {
- return function(dispatch) {
+export const completeTaskBundle = function (
+ bundleId,
+ primaryTaskId,
+ taskStatus,
+ needsReview,
+ tags,
+ cooperativeWorkSummary,
+ osmComment,
+ completionResponses,
+) {
+ return function (dispatch) {
return updateBundledTasksStatus(
- dispatch, bundleId, primaryTaskId, taskStatus, needsReview, tags,
- cooperativeWorkSummary, osmComment, completionResponses
- )
- }
-}
+ dispatch,
+ bundleId,
+ primaryTaskId,
+ taskStatus,
+ needsReview,
+ tags,
+ cooperativeWorkSummary,
+ osmComment,
+ completionResponses,
+ );
+ };
+};
/**
* Bulk update the given tasks. Note that the bulk update APIs require ids to
* be represented as strings, and this function will therefore automatically
* perform a conversion unless skipConversion is true.
*/
-export const bulkUpdateTasks = function(updatedTasks, skipConversion=false) {
- return function(dispatch) {
- const taskData =
- skipConversion ? updatedTasks :
- _map(updatedTasks, task => Object.assign({}, task, {id: task.id.toString()}))
+export const bulkUpdateTasks = function (updatedTasks, skipConversion = false) {
+ return function (dispatch) {
+ const taskData = skipConversion
+ ? updatedTasks
+ : _map(updatedTasks, (task) => Object.assign({}, task, { id: task.id.toString() }));
- return new Endpoint(
- api.tasks.bulkUpdate, {json: taskData}
- ).execute().then(() => {
- // Clear all tasks in challenge since we don't know exactly which tasks
- // are impacted by these changes (as bundling could be affected)
- if (taskData.length > 0) {
- dispatch(clearTasks(taskData[0].parentId))
- }
- }).catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- dispatch(addError(AppErrors.task.updateFailure))
- console.log(error.response || error)
- }
- })
- }
-}
+ return new Endpoint(api.tasks.bulkUpdate, { json: taskData })
+ .execute()
+ .then(() => {
+ // Clear all tasks in challenge since we don't know exactly which tasks
+ // are impacted by these changes (as bundling could be affected)
+ if (taskData.length > 0) {
+ dispatch(clearTasks(taskData[0].parentId));
+ }
+ })
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ } else {
+ dispatch(addError(AppErrors.task.updateFailure));
+ console.log(error.response || error);
+ }
+ });
+ };
+};
/**
* Bulk update task status on tasks that match the given criteria.
*/
-export const bulkTaskStatusChange = function(newStatus, challengeId, criteria, excludeTaskIds) {
- return function(dispatch) {
- const filters = criteria?.filters ?? {}
- const searchParameters = generateSearchParametersString(filters,
- criteria.boundingBox,
- criteria?.savedChallengesOnly,
- null,
- criteria.searchQuery,
- criteria?.invertFields,
- excludeTaskIds)
- searchParameters.cid = challengeId
-
- return new Endpoint(
- api.tasks.bulkStatusChange, {
- params: {...searchParameters, newStatus},
- json: filters.taskPropertySearch ?
- {taskPropertySearch: filters.taskPropertySearch} : null,
- }
- ).execute().then(() => {
- dispatch(clearTasks(challengeId))
- }).catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- dispatch(addError(AppErrors.task.updateFailure))
- console.log(error.response || error)
- }
+export const bulkTaskStatusChange = function (newStatus, challengeId, criteria, excludeTaskIds) {
+ return function (dispatch) {
+ const filters = criteria?.filters ?? {};
+ const searchParameters = generateSearchParametersString(
+ filters,
+ criteria.boundingBox,
+ criteria?.savedChallengesOnly,
+ null,
+ criteria.searchQuery,
+ criteria?.invertFields,
+ excludeTaskIds,
+ );
+ searchParameters.cid = challengeId;
+
+ return new Endpoint(api.tasks.bulkStatusChange, {
+ params: { ...searchParameters, newStatus },
+ json: filters.taskPropertySearch ? { taskPropertySearch: filters.taskPropertySearch } : null,
})
+ .execute()
+ .then(() => {
+ dispatch(clearTasks(challengeId));
+ })
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ } else {
+ dispatch(addError(AppErrors.task.updateFailure));
+ console.log(error.response || error);
+ }
+ });
};
-}
+};
/**
* Updates the completion responses on a task.
*/
-export const updateCompletionResponses = function(taskId, completionResponses) {
- return function(dispatch) {
- return new Endpoint(
- api.task.updateCompletionResponses,
- {variables: {id: taskId},
- json: completionResponses
- }
- ).execute().then(() => {
- fetchTask(taskId)(dispatch) // Refresh task data
- }).catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- dispatch(addError(AppErrors.task.updateFailure))
- console.log(error.response || error)
- }
+export const updateCompletionResponses = function (taskId, completionResponses) {
+ return function (dispatch) {
+ return new Endpoint(api.task.updateCompletionResponses, {
+ variables: { id: taskId },
+ json: completionResponses,
})
- }
-}
+ .execute()
+ .then(() => {
+ fetchTask(taskId)(dispatch); // Refresh task data
+ })
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ } else {
+ dispatch(addError(AppErrors.task.updateFailure));
+ console.log(error.response || error);
+ }
+ });
+ };
+};
/**
* Add a comment to the given task, associating the given task status if
* provided.
*/
-export const addTaskComment = function(taskId, comment, taskStatus) {
- return function(dispatch) {
- const params = {}
+export const addTaskComment = function (taskId, comment, taskStatus) {
+ return function (dispatch) {
+ const params = {};
if (_isFinite(taskStatus)) {
- params.actionId = taskStatus
+ params.actionId = taskStatus;
}
- return new Endpoint(
- api.task.addComment, {variables: {id: taskId}, params, json: { comment: comment }}
- ).execute().then(() => {
- fetchTaskComments(taskId)(dispatch)
- fetchTask(taskId)(dispatch) // Refresh task data
- }).catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- dispatch(addError(AppErrors.task.addCommentFailure))
- console.log(error.response || error)
- }
+ return new Endpoint(api.task.addComment, {
+ variables: { id: taskId },
+ params,
+ json: { comment: comment },
})
- }
-}
+ .execute()
+ .then(() => {
+ fetchTaskComments(taskId)(dispatch);
+ fetchTask(taskId)(dispatch); // Refresh task data
+ })
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ } else {
+ dispatch(addError(AppErrors.task.addCommentFailure));
+ console.log(error.response || error);
+ }
+ });
+ };
+};
/**
* Add a comment to tasks in the given bundle, associating the given task
* status if provided
*/
-export const addTaskBundleComment = function(bundleId, primaryTaskId, comment, taskStatus) {
- return function(dispatch) {
- const params = {}
+export const addTaskBundleComment = function (bundleId, primaryTaskId, comment, taskStatus) {
+ return function (dispatch) {
+ const params = {};
if (_isFinite(taskStatus)) {
- params.actionId = taskStatus
+ params.actionId = taskStatus;
}
return new Endpoint(api.tasks.bundled.addComment, {
- variables: {bundleId},
+ variables: { bundleId },
params,
- json: { comment: comment }
- }).execute().then(() => {
- fetchTaskComments(primaryTaskId)(dispatch)
- fetchTask(primaryTaskId)(dispatch) // Refresh task data
- }).catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- dispatch(addError(AppErrors.task.addCommentFailure))
- console.log(error.response || error)
- }
+ json: { comment: comment },
})
- }
-}
-
+ .execute()
+ .then(() => {
+ fetchTaskComments(primaryTaskId)(dispatch);
+ fetchTask(primaryTaskId)(dispatch); // Refresh task data
+ })
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ } else {
+ dispatch(addError(AppErrors.task.addCommentFailure));
+ console.log(error.response || error);
+ }
+ });
+ };
+};
/**
* Fetch task bundle with given id
*/
-export const fetchTaskBundle = function(bundleId, lockTasks) {
- return function(dispatch) {
+export const fetchTaskBundle = function (bundleId, lockTasks) {
+ return function (dispatch) {
return new Endpoint(api.tasks.fetchBundle, {
- variables: {bundleId}, params: {lockTasks}
- }).execute().catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- dispatch(addError(AppErrors.task.bundleFailure))
- console.log(error.response || error)
- }
+ variables: { bundleId },
+ params: { lockTasks },
})
- }
-}
+ .execute()
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ } else {
+ dispatch(addError(AppErrors.task.bundleFailure));
+ console.log(error.response || error);
+ }
+ });
+ };
+};
/**
* Fetch comments for the given task
*/
-export const fetchTaskComments = function(taskId) {
- return function(dispatch) {
- return new Endpoint(
- api.task.comments,
- {schema: [ commentSchema() ], variables: {id: taskId}}
- ).execute().then(normalizedComments => {
- dispatch(receiveComments(normalizedComments.entities))
-
- if (_isObject(normalizedComments.entities.comments)) {
- // Inject comment ids into task
- dispatch(receiveTasks(simulatedEntities({
- id: taskId,
- comments: _map(_keys(normalizedComments.entities.comments),
- id => parseInt(id, 10)),
- })))
- }
+export const fetchTaskComments = function (taskId) {
+ return function (dispatch) {
+ return new Endpoint(api.task.comments, { schema: [commentSchema()], variables: { id: taskId } })
+ .execute()
+ .then((normalizedComments) => {
+ dispatch(receiveComments(normalizedComments.entities));
+
+ if (_isObject(normalizedComments.entities.comments)) {
+ // Inject comment ids into task
+ dispatch(
+ receiveTasks(
+ simulatedEntities({
+ id: taskId,
+ comments: _map(_keys(normalizedComments.entities.comments), (id) =>
+ parseInt(id, 10),
+ ),
+ }),
+ ),
+ );
+ }
- return normalizedComments
- })
- }
-}
+ return normalizedComments;
+ });
+ };
+};
/**
* Fetch history for the given task
*/
-export const fetchTaskHistory = function(taskId) {
- return function(dispatch) {
- return new Endpoint(
- api.task.history,
- {schema: {}, variables: {id: taskId}}
- ).execute().then(normalizedHistory => {
- if (_isObject(normalizedHistory.result)) {
- // Inject history into task
- dispatch(receiveTasks(simulatedEntities({
- id: taskId,
- history: _values(normalizedHistory.result),
- })))
- }
+export const fetchTaskHistory = function (taskId) {
+ return function (dispatch) {
+ return new Endpoint(api.task.history, { schema: {}, variables: { id: taskId } })
+ .execute()
+ .then((normalizedHistory) => {
+ if (_isObject(normalizedHistory.result)) {
+ // Inject history into task
+ dispatch(
+ receiveTasks(
+ simulatedEntities({
+ id: taskId,
+ history: _values(normalizedHistory.result),
+ }),
+ ),
+ );
+ }
- return normalizedHistory
- }).catch(error => {
- console.log(error)
- })
- }
-}
+ return normalizedHistory;
+ })
+ .catch((error) => {
+ console.log(error);
+ });
+ };
+};
/**
* Retrieve a random task from the given challenge. If priorTaskId is given,
@@ -543,22 +610,24 @@ export const fetchTaskHistory = function(taskId) {
* If info on available mapillary images for the task is also desired, set
* includeMapillary to true
*/
-export const loadRandomTaskFromChallenge = function(challengeId,
- priorTaskId,
- includeMapillary=false) {
- return function(dispatch) {
+export const loadRandomTaskFromChallenge = function (
+ challengeId,
+ priorTaskId,
+ includeMapillary = false,
+) {
+ return function (dispatch) {
const endpoint = new Endpoint(api.challenge.prioritizedTask, {
- schema: [ taskSchema() ],
+ schema: [taskSchema()],
variables: { id: challengeId },
params: {
proximity: _isFinite(priorTaskId) ? priorTaskId : undefined,
- mapillary: includeMapillary
+ mapillary: includeMapillary,
},
- })
+ });
- return retrieveChallengeTask(dispatch, endpoint)
- }
-}
+ return retrieveChallengeTask(dispatch, endpoint);
+ };
+};
/**
* Retrieve a random task from the given virtual challenge. If priorTaskId is
@@ -568,73 +637,76 @@ export const loadRandomTaskFromChallenge = function(challengeId,
* If info on available mapillary images for the task is also desired, set
* includeMapillary to true
*/
-export const loadRandomTaskFromVirtualChallenge = function(virtualChallengeId,
- priorTaskId,
- includeMapillary=false) {
- return function(dispatch) {
- return retrieveChallengeTask(dispatch, new Endpoint(
- api.virtualChallenge.randomTask,
- {
+export const loadRandomTaskFromVirtualChallenge = function (
+ virtualChallengeId,
+ priorTaskId,
+ includeMapillary = false,
+) {
+ return function (dispatch) {
+ return retrieveChallengeTask(
+ dispatch,
+ new Endpoint(api.virtualChallenge.randomTask, {
schema: taskSchema(),
- variables: {id: virtualChallengeId},
+ variables: { id: virtualChallengeId },
params: {
proximity: _isFinite(priorTaskId) ? priorTaskId : undefined,
mapillary: includeMapillary,
- }
- }
- ))
- }
-}
+ },
+ }),
+ );
+ };
+};
/**
* Retrieve the previous sequential task from the given challenge (primarily
* intended for use during challenge inspect by challenge owners).
*/
-export const loadPreviousSequentialTaskFromChallenge = function(challengeId,
- currentTaskId) {
- return function(dispatch) {
- return retrieveChallengeTask(dispatch, new Endpoint(
- api.challenge.previousSequentialTask,
- {
+export const loadPreviousSequentialTaskFromChallenge = function (challengeId, currentTaskId) {
+ return function (dispatch) {
+ return retrieveChallengeTask(
+ dispatch,
+ new Endpoint(api.challenge.previousSequentialTask, {
schema: taskSchema(),
- variables: {challengeId: challengeId, taskId: currentTaskId},
- }
- ))
- }
-}
+ variables: { challengeId: challengeId, taskId: currentTaskId },
+ }),
+ );
+ };
+};
/**
* Retrieve the next sequential task from the given challenge (primarily intended
* for use during challenge inspect by challenge owners).
*/
-export const loadNextSequentialTaskFromChallenge = function(challengeId,
- currentTaskId) {
- return function(dispatch) {
- return retrieveChallengeTask(dispatch, new Endpoint(
- api.challenge.nextSequentialTask,
- {
+export const loadNextSequentialTaskFromChallenge = function (challengeId, currentTaskId) {
+ return function (dispatch) {
+ return retrieveChallengeTask(
+ dispatch,
+ new Endpoint(api.challenge.nextSequentialTask, {
schema: taskSchema(),
- variables: {challengeId: challengeId, taskId: currentTaskId},
- }
- ))
- }
-}
+ variables: { challengeId: challengeId, taskId: currentTaskId },
+ }),
+ );
+ };
+};
/**
* Retrieve all tasks (up to the given limit) belonging to the given
* challenge
*/
-export const fetchChallengeTasks = function(challengeId, limit=50) {
- return function(dispatch) {
- return new Endpoint(
- api.challenge.tasks,
- {schema: [ taskSchema() ], variables: {id: challengeId}, params: {limit}}
- ).execute().then(normalizedResults => {
- dispatch(receiveTasks(normalizedResults.entities))
- return normalizedResults
+export const fetchChallengeTasks = function (challengeId, limit = 50) {
+ return function (dispatch) {
+ return new Endpoint(api.challenge.tasks, {
+ schema: [taskSchema()],
+ variables: { id: challengeId },
+ params: { limit },
})
- }
-}
+ .execute()
+ .then((normalizedResults) => {
+ dispatch(receiveTasks(normalizedResults.entities));
+ return normalizedResults;
+ });
+ };
+};
/*
* Retrieve tasks geographically closest to the given task (up to the given
@@ -643,75 +715,94 @@ export const fetchChallengeTasks = function(challengeId, limit=50) {
* challenge or virtual challenge id. Note that this does not add the results
* to the redux store, but simply returns them
*/
-export const fetchNearbyTasks = function(challengeId, isVirtualChallenge, taskId, excludeSelfLocked=false, limit=5) {
- return function() {
- const params = {limit}
+export const fetchNearbyTasks = function (
+ challengeId,
+ isVirtualChallenge,
+ taskId,
+ excludeSelfLocked = false,
+ limit = 5,
+) {
+ return function () {
+ const params = { limit };
if (excludeSelfLocked) {
- params.excludeSelfLocked = 'true'
+ params.excludeSelfLocked = "true";
}
return new Endpoint(
isVirtualChallenge ? api.virtualChallenge.nearbyTasks : api.challenge.nearbyTasks,
{
- schema: [ taskSchema() ],
- variables: {challengeId, taskId},
+ schema: [taskSchema()],
+ variables: { challengeId, taskId },
params,
- }
- ).execute().then(normalizedResults => ({
- challengeId,
- isVirtualChallenge,
- loading: false,
- tasks: _map(_values(normalizedResults?.entities?.tasks ?? {}), task => {
- if (task.location) {
- // match clusteredTasks response, which returns a point with lat/lng fields
- task.point = {
- lng: task.location.coordinates[0],
- lat: task.location.coordinates[1]
+ },
+ )
+ .execute()
+ .then((normalizedResults) => ({
+ challengeId,
+ isVirtualChallenge,
+ loading: false,
+ tasks: _map(_values(normalizedResults?.entities?.tasks ?? {}), (task) => {
+ if (task.location) {
+ // match clusteredTasks response, which returns a point with lat/lng fields
+ task.point = {
+ lng: task.location.coordinates[0],
+ lat: task.location.coordinates[1],
+ };
}
- }
- return task
- })
- }));
+ return task;
+ }),
+ }));
};
-}
+};
/**
* Initiate deletion of tasks in the given statuses belonging to the given
* challenge. Note that this does not wait until the tasks have been deleted
* before resolving.
*/
-export const deleteChallengeTasks = function(challengeId, statuses=null) {
+export const deleteChallengeTasks = function (challengeId, statuses = null) {
return new Endpoint(api.challenge.deleteTasks, {
- variables: {id: challengeId},
- params: statuses ? {statusFilters: statuses.join(',')} : undefined,
- }).execute()
-}
+ variables: { id: challengeId },
+ params: statuses ? { statusFilters: statuses.join(",") } : undefined,
+ }).execute();
+};
/**
* Set the given status on the given task
* @private
*/
-const updateTaskStatus = function(dispatch, taskId, newStatus, requestReview = null,
- tags = null, cooperativeWorkSummary = null,
- osmComment = null, completionResponses = null) {
+const updateTaskStatus = function (
+ dispatch,
+ taskId,
+ newStatus,
+ requestReview = null,
+ tags = null,
+ cooperativeWorkSummary = null,
+ osmComment = null,
+ completionResponses = null,
+) {
// Optimistically assume request will succeed. The store will be updated
// with fresh task data from the server if the save encounters an error.
- dispatch(receiveTasks(simulatedEntities({
- id: taskId,
- status: newStatus,
- })))
-
- const params = {}
+ dispatch(
+ receiveTasks(
+ simulatedEntities({
+ id: taskId,
+ status: newStatus,
+ }),
+ ),
+ );
+
+ const params = {};
if (requestReview != null) {
- params.requestReview = requestReview
+ params.requestReview = requestReview;
}
if (tags != null) {
- params.tags = tags
+ params.tags = tags;
}
- let endpoint = null
+ let endpoint = null;
// Completed cooperative work goes to a different endpoint
if (cooperativeWorkSummary && newStatus === TaskStatus.fixed) {
endpoint = new Endpoint(api.task.applyTagFix, {
@@ -720,85 +811,86 @@ const updateTaskStatus = function(dispatch, taskId, newStatus, requestReview = n
json: {
comment: osmComment,
changes: cooperativeWorkSummary,
- }
- })
- }
- else {
+ },
+ });
+ } else {
endpoint = new Endpoint(api.task.updateStatus, {
schema: taskSchema(),
- variables: {id: taskId, status: newStatus}, params,
- json: completionResponses
- })
+ variables: { id: taskId, status: newStatus },
+ params,
+ json: completionResponses,
+ });
}
- return endpoint.execute().catch(error => {
+ return endpoint.execute().catch((error) => {
if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
+ dispatch(ensureUserLoggedIn()).then(() => dispatch(addError(AppErrors.user.unauthorized)));
+ } else {
+ dispatch(addError(AppErrors.task.updateFailure));
+ console.log(error.response || error);
}
- else {
- dispatch(addError(AppErrors.task.updateFailure))
- console.log(error.response || error)
- }
- fetchTask(taskId)(dispatch) // Fetch accurate task data
- })
-}
+ fetchTask(taskId)(dispatch); // Fetch accurate task data
+ });
+};
/**
* Set the given status on the tasks in the given bundle
* @private
*/
-const updateBundledTasksStatus = function(dispatch, bundleId, primaryTaskId,
- newStatus, requestReview = null, tags = null,
- cooperativeWorkSummary = null, osmComment,
- completionResponses = null) {
+const updateBundledTasksStatus = function (
+ dispatch,
+ bundleId,
+ primaryTaskId,
+ newStatus,
+ requestReview = null,
+ tags = null,
+ cooperativeWorkSummary = null,
+ osmComment,
+ completionResponses = null,
+) {
if (cooperativeWorkSummary) {
- throw new Error("Cooperative tasks cannot be updated as a bundle at this time")
+ throw new Error("Cooperative tasks cannot be updated as a bundle at this time");
}
const params = {
primaryId: primaryTaskId,
- }
+ };
if (requestReview != null) {
- params.requestReview = requestReview
+ params.requestReview = requestReview;
}
if (tags != null) {
- params.tags = tags
+ params.tags = tags;
}
const endpoint = new Endpoint(api.tasks.bundled.updateStatus, {
schema: taskBundleSchema(),
- variables: {bundleId, status: newStatus},
+ variables: { bundleId, status: newStatus },
params,
json: completionResponses,
- })
+ });
- return endpoint.execute().catch(error => {
+ return endpoint.execute().catch((error) => {
if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
+ dispatch(ensureUserLoggedIn()).then(() => dispatch(addError(AppErrors.user.unauthorized)));
+ } else {
+ dispatch(addError(AppErrors.task.updateFailure));
+ console.log(error.response || error);
}
- else {
- dispatch(addError(AppErrors.task.updateFailure))
- console.log(error.response || error)
- }
- fetchTask(primaryTaskId)(dispatch) // Fetch accurate task data
- })
-}
+ fetchTask(primaryTaskId)(dispatch); // Fetch accurate task data
+ });
+};
-export const fetchCooperativeTagFixChangeset = function(cooperativeWorkSummary) {
+export const fetchCooperativeTagFixChangeset = function (cooperativeWorkSummary) {
const endpoint = new Endpoint(api.task.testTagFix, {
- params: { changeType: 'osmchange' },
+ params: { changeType: "osmchange" },
json: cooperativeWorkSummary,
expectXMLResponse: true,
- })
+ });
- return endpoint.execute()
-}
+ return endpoint.execute();
+};
/**
* Retrieve the place description associated with the task in the
@@ -807,209 +899,227 @@ export const fetchCooperativeTagFixChangeset = function(cooperativeWorkSummary)
* > Note that if the results contain multiple tasks, only the
* > place description of the first result is retrieved.
*/
-export const fetchTaskPlace = function(task) {
- return function(dispatch) {
+export const fetchTaskPlace = function (task) {
+ return function (dispatch) {
return dispatch(
- fetchPlace(task?.location?.coordinates?.[1] ?? 0,
- task?.location?.coordinates?.[0] ?? 0)
- ).then(normalizedPlaceResults => {
+ fetchPlace(task?.location?.coordinates?.[1] ?? 0, task?.location?.coordinates?.[0] ?? 0),
+ ).then((normalizedPlaceResults) => {
// Tasks have no natural reference to places, so inject the place id into
// the task so that later denormalization will work properly.
- return dispatch(receiveTasks(simulatedEntities({
- id: task.id,
- place: normalizedPlaceResults?.result,
- })));
+ return dispatch(
+ receiveTasks(
+ simulatedEntities({
+ id: task.id,
+ place: normalizedPlaceResults?.result,
+ }),
+ ),
+ );
});
};
-}
+};
/**
* Update the tags on the task.
*
*/
-export const updateTaskTags = function(taskId, tags) {
- return function(dispatch) {
- return new Endpoint(
- api.task.updateTags,
- {schema: {}, variables: {id: taskId}, params: {tags: tags}}
- ).execute().then(normalizedTags => {
- if (_isObject(normalizedTags.result)) {
- // Inject tags into task.
- dispatch(receiveTasks(simulatedEntities({
- id: taskId,
- tags: _values(normalizedTags.result),
- })))
- }
- return normalizedTags
+export const updateTaskTags = function (taskId, tags) {
+ return function (dispatch) {
+ return new Endpoint(api.task.updateTags, {
+ schema: {},
+ variables: { id: taskId },
+ params: { tags: tags },
})
- }
-}
+ .execute()
+ .then((normalizedTags) => {
+ if (_isObject(normalizedTags.result)) {
+ // Inject tags into task.
+ dispatch(
+ receiveTasks(
+ simulatedEntities({
+ id: taskId,
+ tags: _values(normalizedTags.result),
+ }),
+ ),
+ );
+ }
+ return normalizedTags;
+ });
+ };
+};
/**
* Saves the given task (either creating it or updating it, depending on
* whether it already has an id) and updates the redux store with the latest
* version from the server.
*/
-export const saveTask = function(originalTaskData) {
- return function(dispatch) {
- const taskData = _pick(
- originalTaskData,
- ['id', 'name', 'instruction', 'geometries', 'status', 'priority', 'tags']
- )
+export const saveTask = function (originalTaskData) {
+ return function (dispatch) {
+ const taskData = _pick(originalTaskData, [
+ "id",
+ "name",
+ "instruction",
+ "geometries",
+ "status",
+ "priority",
+ "tags",
+ ]);
// If the geometries are a string, convert to JSON.
if (_isString(taskData.geometries)) {
- taskData.geometries = JSON.parse(taskData.geometries)
+ taskData.geometries = JSON.parse(taskData.geometries);
}
// Setup the save function to either edit or create the task
// depending on whether it has an id.
- const saveEndpoint = new Endpoint(
- _isFinite(taskData.id) ? api.task.edit : api.task.create,
- {
- schema: taskSchema(),
- variables: {id: taskData.id},
- json: taskData
- }
- )
+ const saveEndpoint = new Endpoint(_isFinite(taskData.id) ? api.task.edit : api.task.create, {
+ schema: taskSchema(),
+ variables: { id: taskData.id },
+ json: taskData,
+ });
- return saveEndpoint.execute().then(normalizedResults => {
- dispatch(receiveTasks(normalizedResults.entities))
- return normalizedResults?.entities?.tasks?.[normalizedResults.result];
- }).catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- console.log(error.response || error)
- dispatch(addServerError(AppErrors.task.saveFailure, error))
- }
- })
- }
-}
+ return saveEndpoint
+ .execute()
+ .then((normalizedResults) => {
+ dispatch(receiveTasks(normalizedResults.entities));
+ return normalizedResults?.entities?.tasks?.[normalizedResults.result];
+ })
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ } else {
+ console.log(error.response || error);
+ dispatch(addServerError(AppErrors.task.saveFailure, error));
+ }
+ });
+ };
+};
/**
* Deletes the given task from the server.
*/
-export const deleteTask = function(taskId) {
- return function(dispatch) {
- return new Endpoint(
- api.task.delete, {variables: {id: taskId}}
- ).execute().then(() =>
- dispatch(removeTask(taskId))
- ).catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- dispatch(addError(AppErrors.task.deleteFailure))
- console.log(error.response || error)
- }
- })
- }
-}
+export const deleteTask = function (taskId) {
+ return function (dispatch) {
+ return new Endpoint(api.task.delete, { variables: { id: taskId } })
+ .execute()
+ .then(() => dispatch(removeTask(taskId)))
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ } else {
+ dispatch(addError(AppErrors.task.deleteFailure));
+ console.log(error.response || error);
+ }
+ });
+ };
+};
-export const bundleTasks = function(primaryId, taskIds, bundleTypeMismatch, bundleName="") {
- return function(dispatch) {
+export const bundleTasks = function (primaryId, taskIds, bundleTypeMismatch, bundleName = "") {
+ return function (dispatch) {
return new Endpoint(api.tasks.bundle, {
- json: {name: bundleName, primaryId, taskIds},
- }).execute().then(results => {
- return results
- }).catch(async error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- if (bundleTypeMismatch === "cooperative") {
- dispatch(addError(AppErrors.task.bundleCooperative))
- } else if (bundleTypeMismatch === "notCooperative") {
- dispatch(addError(AppErrors.task.bundleNotCooperative))
- }
+ json: { name: bundleName, primaryId, taskIds },
+ })
+ .execute()
+ .then((results) => {
+ return results;
+ })
+ .catch(async (error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ } else {
+ if (bundleTypeMismatch === "cooperative") {
+ dispatch(addError(AppErrors.task.bundleCooperative));
+ } else if (bundleTypeMismatch === "notCooperative") {
+ dispatch(addError(AppErrors.task.bundleNotCooperative));
+ }
- const errorMessage = await error.response.text()
- if (errorMessage.includes('already assigned to bundle')) {
- const numberPattern = /\d+/
- const matchedNumber = errorMessage.match(numberPattern)
- if (matchedNumber) {
- const taskId = parseInt(matchedNumber[0])
- dispatch(addErrorWithDetails(AppErrors.task.taskAlreadyBundled, [taskId]))
- } else {
- console.log("No task ID found in the error message.")
+ const errorMessage = await error.response.text();
+ if (errorMessage.includes("already assigned to bundle")) {
+ const numberPattern = /\d+/;
+ const matchedNumber = errorMessage.match(numberPattern);
+ if (matchedNumber) {
+ const taskId = parseInt(matchedNumber[0]);
+ dispatch(addErrorWithDetails(AppErrors.task.taskAlreadyBundled, [taskId]));
+ } else {
+ console.log("No task ID found in the error message.");
+ }
}
- }
- if (errorMessage.includes('task IDs were locked')) {
- const numberPattern = /\d+/g
- const matchedNumbers = errorMessage.match(numberPattern)
- if (matchedNumbers) {
- const numbersOnly = matchedNumbers.map(Number)
- dispatch(addErrorWithDetails(AppErrors.task.unableToBundleTasks, numbersOnly))
- } else {
- console.log("No task IDs found in the error message.")
+ if (errorMessage.includes("task IDs were locked")) {
+ const numberPattern = /\d+/g;
+ const matchedNumbers = errorMessage.match(numberPattern);
+ if (matchedNumbers) {
+ const numbersOnly = matchedNumbers.map(Number);
+ dispatch(addErrorWithDetails(AppErrors.task.unableToBundleTasks, numbersOnly));
+ } else {
+ console.log("No task IDs found in the error message.");
+ }
}
+ dispatch(addError(AppErrors.task.bundleFailure));
+ console.log(error.response || error);
}
- dispatch(addError(AppErrors.task.bundleFailure))
- console.log(error.response || error)
- }
- })
- }
-}
-
- export const resetTaskBundle = function(initialBundle) {
- const params = {};
- const bundleId = initialBundle.bundleId;
- let taskIdsArray = [];
+ });
+ };
+};
- if (initialBundle?.taskIds) {
- taskIdsArray.push(...initialBundle.taskIds);
- params.taskIds = taskIdsArray;
- }
+export const resetTaskBundle = function (initialBundle) {
+ const params = {};
+ const bundleId = initialBundle.bundleId;
+ let taskIdsArray = [];
- return function(dispatch) {
- return new Endpoint(api.tasks.resetBundle, {
- variables: {bundleId},
- params
- }).execute()
- .then(results => {
- return results
- })
- .catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn())
- .then(() => dispatch(addError(AppErrors.user.unauthorized)))
- } else {
- dispatch(addError(AppErrors.task.bundleFailure))
- console.log(error.response || error)
- }
- })
- }
+ if (initialBundle?.taskIds) {
+ taskIdsArray.push(...initialBundle.taskIds);
+ params.taskIds = taskIdsArray;
}
-export const deleteTaskBundle = function(bundleId) {
- return function(dispatch) {
+ return function (dispatch) {
+ return new Endpoint(api.tasks.resetBundle, {
+ variables: { bundleId },
+ params,
+ })
+ .execute()
+ .then((results) => {
+ return results;
+ })
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ } else {
+ dispatch(addError(AppErrors.task.bundleFailure));
+ console.log(error.response || error);
+ }
+ });
+ };
+};
+
+export const deleteTaskBundle = function (bundleId) {
+ return function (dispatch) {
return new Endpoint(api.tasks.deleteBundle, {
- variables: {bundleId},
- }).execute().then(() => {
- return true
- }).catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- dispatch(addError(AppErrors.task.bundleFailure))
- console.log(error.response || error)
- }
- return false
+ variables: { bundleId },
})
- }
-}
+ .execute()
+ .then(() => {
+ return true;
+ })
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ } else {
+ dispatch(addError(AppErrors.task.bundleFailure));
+ console.log(error.response || error);
+ }
+ return false;
+ });
+ };
+};
export const removeTaskFromBundle = function (initialBundleTaskIds, bundleId, taskIds) {
return function (dispatch) {
@@ -1024,7 +1134,7 @@ export const removeTaskFromBundle = function (initialBundleTaskIds, bundleId, ta
.catch((error) => {
if (isSecurityError(error)) {
dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
+ dispatch(addError(AppErrors.user.unauthorized)),
);
} else {
dispatch(addError(AppErrors.task.removeTaskFromBundleFailure));
@@ -1040,61 +1150,63 @@ export const removeTaskFromBundle = function (initialBundleTaskIds, bundleId, ta
*
* @private
*/
-export const retrieveChallengeTask = function(dispatch, endpoint) {
- return endpoint.execute().then(normalizedTaskResults => {
- if (!normalizedTaskResults ||
- (!_isFinite(normalizedTaskResults.result) &&
- _isEmpty(normalizedTaskResults.result))) {
- return null
- }
-
- const retrievedTaskId = _isArray(normalizedTaskResults.result) ?
- normalizedTaskResults.result[0] :
- normalizedTaskResults.result
-
- if (!_isUndefined(retrievedTaskId)) {
- // Some API requests give back the parent as `parentId` instead
- // of `parent`, and the geometries back as `geometry` instead of
- // `geometries`. Normalize these.
- const taskEntity = normalizedTaskResults.entities.tasks[retrievedTaskId]
- if (!_isFinite(taskEntity.parent)) {
- taskEntity.parent = taskEntity.parentId
+export const retrieveChallengeTask = function (dispatch, endpoint) {
+ return endpoint
+ .execute()
+ .then((normalizedTaskResults) => {
+ if (
+ !normalizedTaskResults ||
+ (!_isFinite(normalizedTaskResults.result) && _isEmpty(normalizedTaskResults.result))
+ ) {
+ return null;
}
- if (!_isObject(taskEntity.geometries)) {
- taskEntity.geometries = taskEntity.geometry
- }
+ const retrievedTaskId = _isArray(normalizedTaskResults.result)
+ ? normalizedTaskResults.result[0]
+ : normalizedTaskResults.result;
+
+ if (!_isUndefined(retrievedTaskId)) {
+ // Some API requests give back the parent as `parentId` instead
+ // of `parent`, and the geometries back as `geometry` instead of
+ // `geometries`. Normalize these.
+ const taskEntity = normalizedTaskResults.entities.tasks[retrievedTaskId];
+ if (!_isFinite(taskEntity.parent)) {
+ taskEntity.parent = taskEntity.parentId;
+ }
- dispatch(receiveTasks(normalizedTaskResults.entities))
+ if (!_isObject(taskEntity.geometries)) {
+ taskEntity.geometries = taskEntity.geometry;
+ }
- // Kick off fetches of supplementary data, but don't wait for them.
- fetchTaskPlace(
- normalizedTaskResults.entities.tasks[retrievedTaskId]
- )(dispatch)
+ dispatch(receiveTasks(normalizedTaskResults.entities));
- fetchTaskComments(retrievedTaskId)(dispatch)
+ // Kick off fetches of supplementary data, but don't wait for them.
+ fetchTaskPlace(normalizedTaskResults.entities.tasks[retrievedTaskId])(dispatch);
- return taskEntity
- }
- }).catch((error) => {
- dispatch(addError(AppErrors.task.fetchFailure))
- console.log(error.response || error)
- throw error
- })
-}
+ fetchTaskComments(retrievedTaskId)(dispatch);
+
+ return taskEntity;
+ }
+ })
+ .catch((error) => {
+ dispatch(addError(AppErrors.task.fetchFailure));
+ console.log(error.response || error);
+ throw error;
+ });
+};
/**
* Builds a simulated normalized entities representation from the given task
*
* @private
*/
-export const simulatedEntities = function(task) {
+export const simulatedEntities = function (task) {
return {
tasks: {
[task.id]: task,
- }
- }
-}
+ },
+ };
+};
/**
* reduceTasksFurther will be invoked by the genericEntityReducer function to
@@ -1102,29 +1214,25 @@ export const simulatedEntities = function(task) {
*
* @private
*/
-const reduceTasksFurther = function(mergedState, oldState, taskEntities) {
+const reduceTasksFurther = function (mergedState, oldState, taskEntities) {
// The generic reduction will merge arrays and objects, but for some fields
// we want to simply overwrite with the latest data.
- taskEntities.forEach(entity => {
+ taskEntities.forEach((entity) => {
if (_isArray(entity.tags)) {
- mergedState[entity.id].tags = entity.tags
+ mergedState[entity.id].tags = entity.tags;
}
- })
-}
-
-
+ });
+};
// redux reducers
-export const taskEntities = function(state, action) {
+export const taskEntities = function (state, action) {
if (action.type === REMOVE_TASK) {
- const mergedState = _cloneDeep(state)
- delete mergedState[action.taskId]
- return mergedState
+ const mergedState = _cloneDeep(state);
+ delete mergedState[action.taskId];
+ return mergedState;
+ } else if (action.type === CLEAR_TASKS) {
+ return _remove(_cloneDeep(state), (x) => (x ? x.parent === action.challengeId : false));
+ } else {
+ return genericEntityReducer(RECEIVE_TASKS, "tasks", reduceTasksFurther)(state, action);
}
- else if (action.type === CLEAR_TASKS) {
- return _remove(_cloneDeep(state), x => (x ? x.parent === action.challengeId : false))
- }
- else {
- return genericEntityReducer(RECEIVE_TASKS, 'tasks', reduceTasksFurther)(state, action)
- }
-}
+};
diff --git a/src/services/Task/TaskAction/TaskAction.js b/src/services/Task/TaskAction/TaskAction.js
index 2526274f7..a97c71779 100644
--- a/src/services/Task/TaskAction/TaskAction.js
+++ b/src/services/Task/TaskAction/TaskAction.js
@@ -1,7 +1,7 @@
-import { TaskStatus } from '../TaskStatus/TaskStatus'
-import _fromPairs from 'lodash/fromPairs'
-import _map from 'lodash/map'
-import _keys from 'lodash/keys'
+import _fromPairs from "lodash/fromPairs";
+import _keys from "lodash/keys";
+import _map from "lodash/map";
+import { TaskStatus } from "../TaskStatus/TaskStatus";
export const TaskAction = Object.freeze({
alreadyFixed: "alreadyFixed",
@@ -11,18 +11,17 @@ export const TaskAction = Object.freeze({
falsePositive: "falsePositive",
fixed: "fixed",
skipped: "skipped",
- tooHard: "tooHard"
-})
+ tooHard: "tooHard",
+});
/**
* Returns an actions object with everything zeroed-out to represent that there
* are no actions.
*/
-export const zeroTaskActions = function() {
- const actions =
- _fromPairs(_map(_keys(TaskStatus), statusName => [statusName, 0]))
- actions.total = 0
- actions.available = 0
+export const zeroTaskActions = function () {
+ const actions = _fromPairs(_map(_keys(TaskStatus), (statusName) => [statusName, 0]));
+ actions.total = 0;
+ actions.available = 0;
- return actions
-}
+ return actions;
+};
diff --git a/src/services/Task/TaskAction/TaskAction.test.js b/src/services/Task/TaskAction/TaskAction.test.js
index a32b41dc2..56631dddb 100644
--- a/src/services/Task/TaskAction/TaskAction.test.js
+++ b/src/services/Task/TaskAction/TaskAction.test.js
@@ -1,6 +1,6 @@
-import { describe, it, expect } from "vitest";
-import { TaskAction, zeroTaskActions } from "./TaskAction";
+import { describe, expect, it } from "vitest";
import { TaskStatus } from "../TaskStatus/TaskStatus";
+import { TaskAction, zeroTaskActions } from "./TaskAction";
describe("TaskAction", () => {
it("returns specific object when ran", () => {
diff --git a/src/services/Task/TaskClusters.js b/src/services/Task/TaskClusters.js
index 191b34570..276e1bacc 100644
--- a/src/services/Task/TaskClusters.js
+++ b/src/services/Task/TaskClusters.js
@@ -1,44 +1,40 @@
-import { v1 as uuidv1 } from 'uuid'
-import uuidTime from 'uuid-time'
-import RequestStatus from '../Server/RequestStatus'
-import _isArray from 'lodash/isArray'
-import _isUndefined from 'lodash/isUndefined'
-import { clearBoundedTasks } from './BoundedTask'
-import { generateSearchParametersString } from '../Search/Search'
-import { defaultRoutes as api } from '../Server/Server'
-import { addError } from '../Error/Error'
-import AppErrors from '../Error/AppErrors'
-import Endpoint from '../Server/Endpoint'
-import { CHALLENGE_EXCLUDE_LOCAL, CHALLENGE_INCLUDE_LOCAL }
- from '../Challenge/Challenge'
-
+import _isArray from "lodash/isArray";
+import _isUndefined from "lodash/isUndefined";
+import { v1 as uuidv1 } from "uuid";
+import uuidTime from "uuid-time";
+import { CHALLENGE_EXCLUDE_LOCAL, CHALLENGE_INCLUDE_LOCAL } from "../Challenge/Challenge";
+import AppErrors from "../Error/AppErrors";
+import { addError } from "../Error/Error";
+import { generateSearchParametersString } from "../Search/Search";
+import Endpoint from "../Server/Endpoint";
+import RequestStatus from "../Server/RequestStatus";
+import { defaultRoutes as api } from "../Server/Server";
+import { clearBoundedTasks } from "./BoundedTask";
// redux actions
-const RECEIVE_TASK_CLUSTERS = 'RECEIVE_TASK_CLUSTERS'
-const CLEAR_TASK_CLUSTERS = 'CLEAR_TASK_CLUSTERS'
+const RECEIVE_TASK_CLUSTERS = "RECEIVE_TASK_CLUSTERS";
+const CLEAR_TASK_CLUSTERS = "CLEAR_TASK_CLUSTERS";
// redux action creators
-export const receiveTaskClusters = function(clusters,
- status=RequestStatus.success,
- fetchId) {
+export const receiveTaskClusters = function (clusters, status = RequestStatus.success, fetchId) {
return {
type: RECEIVE_TASK_CLUSTERS,
status,
clusters,
fetchId,
receivedAt: Date.now(),
- }
-}
+ };
+};
/**
* Clear the task clusters from the redux store
*/
-export const clearTaskClusters = function() {
+export const clearTaskClusters = function () {
return {
type: CLEAR_TASK_CLUSTERS,
- receivedAt: Date.now()
- }
-}
+ receivedAt: Date.now(),
+ };
+};
/**
* Retrieve task clusters (up to the given number of points) matching the given
@@ -46,85 +42,85 @@ export const clearTaskClusters = function() {
* boundingBox string -- see Search.generateSearchParametersString for details
* of supported filters
*/
-export const fetchTaskClusters = function(challengeId, criteria, points=25, overrideDisable=false) {
- return function(dispatch) {
- if (window.env.REACT_APP_DISABLE_TASK_CLUSTERS === 'true' && !overrideDisable) {
+export const fetchTaskClusters = function (
+ challengeId,
+ criteria,
+ points = 25,
+ overrideDisable = false,
+) {
+ return function (dispatch) {
+ if (window.env.REACT_APP_DISABLE_TASK_CLUSTERS === "true" && !overrideDisable) {
return new Promise((resolve) => resolve());
}
// The map is either showing task clusters or bounded tasks so we can't
// have both in redux.
- dispatch(clearBoundedTasks())
+ dispatch(clearBoundedTasks());
- const fetchId = uuidv1()
- const filters = criteria?.filters ?? {}
- const searchParameters = generateSearchParametersString(filters,
- criteria.boundingBox,
- criteria?.savedChallengesOnly,
- null,
- criteria.searchQuery,
- criteria?.invertFields)
- searchParameters.cid = challengeId
+ const fetchId = uuidv1();
+ const filters = criteria?.filters ?? {};
+ const searchParameters = generateSearchParametersString(
+ filters,
+ criteria.boundingBox,
+ criteria?.savedChallengesOnly,
+ null,
+ criteria.searchQuery,
+ criteria?.invertFields,
+ );
+ searchParameters.cid = challengeId;
// If we don't have a challenge Id then we need to do some limiting.
if (!challengeId) {
- const onlyEnabled = _isUndefined(criteria.onlyEnabled) ?
- true : criteria.onlyEnabled
- const challengeStatus = criteria.challengeStatus
+ const onlyEnabled = _isUndefined(criteria.onlyEnabled) ? true : criteria.onlyEnabled;
+ const challengeStatus = criteria.challengeStatus;
if (challengeStatus) {
- searchParameters.cStatus = challengeStatus.join(',')
+ searchParameters.cStatus = challengeStatus.join(",");
}
// ce: limit to enabled challenges
// pe: limit to enabled projects
- searchParameters.ce = onlyEnabled ? 'true' : 'false'
- searchParameters.pe = onlyEnabled ? 'true' : 'false'
+ searchParameters.ce = onlyEnabled ? "true" : "false";
+ searchParameters.pe = onlyEnabled ? "true" : "false";
// if we are restricting to onlyEnabled challenges then let's
// not show 'local' challenges either.
- searchParameters.cLocal = onlyEnabled ? CHALLENGE_EXCLUDE_LOCAL :
- CHALLENGE_INCLUDE_LOCAL
+ searchParameters.cLocal = onlyEnabled ? CHALLENGE_EXCLUDE_LOCAL : CHALLENGE_INCLUDE_LOCAL;
}
- return new Endpoint(
- api.challenge.taskClusters, {
- params: {points, ...searchParameters},
- json: filters.taskPropertySearch ?
- {taskPropertySearch: filters.taskPropertySearch} : null,
- }
- ).execute()
- .then(results => {
- return dispatch(receiveTaskClusters(
- results, RequestStatus.success, fetchId,
- ))
- }).catch((error) => {
- dispatch(addError(AppErrors.task.fetchFailure))
- console.log(error.response || error)
- throw error
+ return new Endpoint(api.challenge.taskClusters, {
+ params: { points, ...searchParameters },
+ json: filters.taskPropertySearch ? { taskPropertySearch: filters.taskPropertySearch } : null,
})
+ .execute()
+ .then((results) => {
+ return dispatch(receiveTaskClusters(results, RequestStatus.success, fetchId));
+ })
+ .catch((error) => {
+ dispatch(addError(AppErrors.task.fetchFailure));
+ console.log(error.response || error);
+ throw error;
+ });
};
-}
+};
// redux reducers
-export const currentTaskClusters = function(state={}, action) {
+export const currentTaskClusters = function (state = {}, action) {
if (action.type === RECEIVE_TASK_CLUSTERS) {
- const fetchTime = parseInt(uuidTime.v1(action.fetchId))
- const lastFetch = state.fetchId ? parseInt(uuidTime.v1(state.fetchId)) : 0
+ const fetchTime = parseInt(uuidTime.v1(action.fetchId));
+ const lastFetch = state.fetchId ? parseInt(uuidTime.v1(state.fetchId)) : 0;
if (fetchTime >= lastFetch) {
const merged = {
clusters: _isArray(action.clusters) ? action.clusters : [],
fetchId: action.fetchId,
- }
- return merged
+ };
+ return merged;
}
- return state
- }
- else if (action.type === CLEAR_TASK_CLUSTERS) {
- return {}
- }
- else {
- return state
+ return state;
+ } else if (action.type === CLEAR_TASK_CLUSTERS) {
+ return {};
+ } else {
+ return state;
}
-}
+};
diff --git a/src/services/Task/TaskHistory/TaskHistory.js b/src/services/Task/TaskHistory/TaskHistory.js
index 5c256c3a4..0c8a25302 100644
--- a/src/services/Task/TaskHistory/TaskHistory.js
+++ b/src/services/Task/TaskHistory/TaskHistory.js
@@ -1,9 +1,9 @@
// These statuses are defined on the server
-export const TASK_ACTION_COMMENT = 0
-export const TASK_ACTION_STATUS = 1
-export const TASK_ACTION_REVIEW = 2
-export const TASK_ACTION_UPDATE = 3
-export const TASK_ACTION_META_REVIEW = 4
+export const TASK_ACTION_COMMENT = 0;
+export const TASK_ACTION_STATUS = 1;
+export const TASK_ACTION_REVIEW = 2;
+export const TASK_ACTION_UPDATE = 3;
+export const TASK_ACTION_META_REVIEW = 4;
export const TaskHistoryAction = Object.freeze({
comment: TASK_ACTION_COMMENT,
@@ -11,4 +11,4 @@ export const TaskHistoryAction = Object.freeze({
review: TASK_ACTION_REVIEW,
update: TASK_ACTION_UPDATE,
metaReview: TASK_ACTION_META_REVIEW,
-})
+});
diff --git a/src/services/Task/TaskLoadMethod/Messages.js b/src/services/Task/TaskLoadMethod/Messages.js
index ce914bbf0..a54fa3a4a 100644
--- a/src/services/Task/TaskLoadMethod/Messages.js
+++ b/src/services/Task/TaskLoadMethod/Messages.js
@@ -1,16 +1,16 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with TaskLoadMethod
*/
export default defineMessages({
random: {
- id: 'Task.loadByMethod.random',
+ id: "Task.loadByMethod.random",
defaultMessage: "Random",
},
proximity: {
- id: 'Task.loadByMethod.proximity',
+ id: "Task.loadByMethod.proximity",
defaultMessage: "Nearby",
},
-})
+});
diff --git a/src/services/Task/TaskLoadMethod/TaskLoadMethod.js b/src/services/Task/TaskLoadMethod/TaskLoadMethod.js
index f398f9eed..0d3eea6e3 100644
--- a/src/services/Task/TaskLoadMethod/TaskLoadMethod.js
+++ b/src/services/Task/TaskLoadMethod/TaskLoadMethod.js
@@ -1,22 +1,22 @@
-import _fromPairs from 'lodash/fromPairs'
-import _map from 'lodash/map'
-import messages from './Messages'
+import _fromPairs from "lodash/fromPairs";
+import _map from "lodash/map";
+import messages from "./Messages";
/** Load tasks randomly within challenge */
-export const RANDOM_LOAD_METHOD = 'random'
+export const RANDOM_LOAD_METHOD = "random";
/** Load tasks by proximity within challenge */
-export const PROXIMITY_LOAD_METHOD = 'proximity'
+export const PROXIMITY_LOAD_METHOD = "proximity";
export const TaskLoadMethod = Object.freeze({
random: RANDOM_LOAD_METHOD,
proximity: PROXIMITY_LOAD_METHOD,
-})
+});
/**
* Returns an object mapping status values to raw internationalized
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByLoadMethod = _fromPairs(
- _map(messages, (message, key) => [TaskLoadMethod[key], message])
-)
+ _map(messages, (message, key) => [TaskLoadMethod[key], message]),
+);
diff --git a/src/services/Task/TaskLoadMethod/TaskLoadMethod.test.js b/src/services/Task/TaskLoadMethod/TaskLoadMethod.test.js
index 6844a3c46..054b529d0 100644
--- a/src/services/Task/TaskLoadMethod/TaskLoadMethod.test.js
+++ b/src/services/Task/TaskLoadMethod/TaskLoadMethod.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect } from "vitest";
+import { describe, expect, it } from "vitest";
import { messagesByLoadMethod } from "./TaskLoadMethod";
describe("messagesByLoadMethod", () => {
diff --git a/src/services/Task/TaskPriority/Messages.js b/src/services/Task/TaskPriority/Messages.js
index fd0deaa8b..f9165bbc4 100644
--- a/src/services/Task/TaskPriority/Messages.js
+++ b/src/services/Task/TaskPriority/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with TaskPriority.
@@ -6,14 +6,14 @@ import { defineMessages } from 'react-intl'
export default defineMessages({
high: {
id: "Task.priority.high",
- defaultMessage: "High"
+ defaultMessage: "High",
},
medium: {
id: "Task.priority.medium",
- defaultMessage: "Medium"
+ defaultMessage: "Medium",
},
low: {
id: "Task.priority.low",
- defaultMessage: "Low"
+ defaultMessage: "Low",
},
-})
+});
diff --git a/src/services/Task/TaskPriority/TaskPriority.js b/src/services/Task/TaskPriority/TaskPriority.js
index 9c622ac0b..bf6ebb0d1 100644
--- a/src/services/Task/TaskPriority/TaskPriority.js
+++ b/src/services/Task/TaskPriority/TaskPriority.js
@@ -1,43 +1,42 @@
-import _map from 'lodash/map'
-import _invert from 'lodash/invert'
-import _fromPairs from 'lodash/fromPairs'
-import resolveConfig from 'tailwindcss/resolveConfig'
-import tailwindConfig from '../../../tailwind.config.js'
-import messages from './Messages'
+import _fromPairs from "lodash/fromPairs";
+import _invert from "lodash/invert";
+import _map from "lodash/map";
+import resolveConfig from "tailwindcss/resolveConfig";
+import tailwindConfig from "../../../tailwind.config.js";
+import messages from "./Messages";
-const colors = resolveConfig(tailwindConfig).theme.colors
+const colors = resolveConfig(tailwindConfig).theme.colors;
/**
* Constants defining task priority levels. These statuses are defined on the
* server.
*/
-export const TASK_PRIORITY_HIGH = 0
-export const TASK_PRIORITY_MEDIUM = 1
-export const TASK_PRIORITY_LOW = 2
+export const TASK_PRIORITY_HIGH = 0;
+export const TASK_PRIORITY_MEDIUM = 1;
+export const TASK_PRIORITY_LOW = 2;
export const TaskPriority = Object.freeze({
high: TASK_PRIORITY_HIGH,
medium: TASK_PRIORITY_MEDIUM,
low: TASK_PRIORITY_LOW,
-})
+});
-export const keysByPriority = Object.freeze(_invert(TaskPriority))
+export const keysByPriority = Object.freeze(_invert(TaskPriority));
/**
* Returns an object mapping priority values to raw internationalized
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByPriority = _fromPairs(
- _map(messages, (message, key) => [TaskPriority[key], message])
-)
+ _map(messages, (message, key) => [TaskPriority[key], message]),
+);
/** Returns object containing localized labels */
-export const taskPriorityLabels = intl => _fromPairs(
- _map(messages, (message, key) => [key, intl.formatMessage(message)])
-)
+export const taskPriorityLabels = (intl) =>
+ _fromPairs(_map(messages, (message, key) => [key, intl.formatMessage(message)]));
export const TaskPriorityColors = Object.freeze({
- [TaskPriority.low]: colors['teal'],
- [TaskPriority.medium]: colors['mango'],
- [TaskPriority.high]: colors['red-light'],
-})
+ [TaskPriority.low]: colors["teal"],
+ [TaskPriority.medium]: colors["mango"],
+ [TaskPriority.high]: colors["red-light"],
+});
diff --git a/src/services/Task/TaskPriority/TaskPriority.test.js b/src/services/Task/TaskPriority/TaskPriority.test.js
index 11e1c6872..4ce9e9445 100644
--- a/src/services/Task/TaskPriority/TaskPriority.test.js
+++ b/src/services/Task/TaskPriority/TaskPriority.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect } from "vitest";
+import { describe, expect, it } from "vitest";
import { TaskPriorityColors } from "./TaskPriority";
describe("TaskPriorityColors", () => {
diff --git a/src/services/Task/TaskProperty/Messages.js b/src/services/Task/TaskProperty/Messages.js
index 5c09f35b6..c21ad726c 100644
--- a/src/services/Task/TaskProperty/Messages.js
+++ b/src/services/Task/TaskProperty/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with TaskProperty.
@@ -6,36 +6,36 @@ import { defineMessages } from 'react-intl'
export default defineMessages({
equals: {
id: "Task.property.searchType.equals",
- defaultMessage: "equals"
+ defaultMessage: "equals",
},
notEqual: {
id: "Task.property.searchType.notEqual",
- defaultMessage: "doesn’t equal"
+ defaultMessage: "doesn’t equal",
},
contains: {
id: "Task.property.searchType.contains",
- defaultMessage: "contains"
+ defaultMessage: "contains",
},
exists: {
id: "Task.property.searchType.exists",
- defaultMessage: "exists"
+ defaultMessage: "exists",
},
missing: {
id: "Task.property.searchType.missing",
- defaultMessage: "missing"
+ defaultMessage: "missing",
},
and: {
id: "Task.property.operationType.and",
- defaultMessage: "and"
+ defaultMessage: "and",
},
or: {
id: "Task.property.operationType.or",
- defaultMessage: "or"
+ defaultMessage: "or",
},
-})
+});
diff --git a/src/services/Task/TaskProperty/TaskProperty.js b/src/services/Task/TaskProperty/TaskProperty.js
index ee92e8d40..fc1794a08 100644
--- a/src/services/Task/TaskProperty/TaskProperty.js
+++ b/src/services/Task/TaskProperty/TaskProperty.js
@@ -1,22 +1,22 @@
-import _map from 'lodash/map'
-import _fromPairs from 'lodash/fromPairs'
-import _compact from 'lodash/compact'
-import _isUndefined from 'lodash/isUndefined'
-import messages from './Messages'
+import _compact from "lodash/compact";
+import _fromPairs from "lodash/fromPairs";
+import _isUndefined from "lodash/isUndefined";
+import _map from "lodash/map";
+import messages from "./Messages";
/**
* Constants defining task property search types. These are sent to the server.
*/
-export const TASK_PROPERTY_SEARCH_TYPE_EQUALS = "equals"
-export const TASK_PROPERTY_SEARCH_TYPE_NOT_EQUAL = "not_equal"
-export const TASK_PRIORITY_SEARCH_TYPE_CONTAINS = "contains"
-export const TASK_PRIORITY_SEARCH_TYPE_GREATER_THAN = "greater_than"
-export const TASK_PRIORITY_SEARCH_TYPE_LESS_THAN = "less_than"
-export const TASK_PRIORITY_SEARCH_TYPE_EXISTS = "exists"
-export const TASK_PRIORITY_SEARCH_TYPE_MISSING = "missing"
+export const TASK_PROPERTY_SEARCH_TYPE_EQUALS = "equals";
+export const TASK_PROPERTY_SEARCH_TYPE_NOT_EQUAL = "not_equal";
+export const TASK_PRIORITY_SEARCH_TYPE_CONTAINS = "contains";
+export const TASK_PRIORITY_SEARCH_TYPE_GREATER_THAN = "greater_than";
+export const TASK_PRIORITY_SEARCH_TYPE_LESS_THAN = "less_than";
+export const TASK_PRIORITY_SEARCH_TYPE_EXISTS = "exists";
+export const TASK_PRIORITY_SEARCH_TYPE_MISSING = "missing";
-export const TASK_PROPERTY_OPERATION_TYPE_AND = "and"
-export const TASK_PROPERTY_OPERATION_TYPE_OR = "or"
+export const TASK_PROPERTY_OPERATION_TYPE_AND = "and";
+export const TASK_PROPERTY_OPERATION_TYPE_OR = "or";
export const TaskPropertySearchTypeString = Object.freeze({
equals: TASK_PROPERTY_SEARCH_TYPE_EQUALS,
@@ -24,42 +24,45 @@ export const TaskPropertySearchTypeString = Object.freeze({
contains: TASK_PRIORITY_SEARCH_TYPE_CONTAINS,
exists: TASK_PRIORITY_SEARCH_TYPE_EXISTS,
missing: TASK_PRIORITY_SEARCH_TYPE_MISSING,
-})
+});
export const TaskPropertySearchTypeNumber = Object.freeze({
equals: TASK_PROPERTY_SEARCH_TYPE_EQUALS,
notEquals: TASK_PROPERTY_SEARCH_TYPE_NOT_EQUAL,
greaterThan: TASK_PRIORITY_SEARCH_TYPE_GREATER_THAN,
lessThan: TASK_PRIORITY_SEARCH_TYPE_LESS_THAN,
-})
+});
export const TaskPropertyOperationType = Object.freeze({
and: TASK_PROPERTY_OPERATION_TYPE_AND,
or: TASK_PROPERTY_OPERATION_TYPE_OR,
-})
-
+});
/**
* Returns an object mapping property search type values to raw internationalized
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByPropertySearchType = _fromPairs(
- _compact(_map(messages, (message, key) => {
- if (_isUndefined(TaskPropertySearchTypeString[key])) {
- return null
- }
- return [TaskPropertySearchTypeString[key], message]
- }))
-)
+ _compact(
+ _map(messages, (message, key) => {
+ if (_isUndefined(TaskPropertySearchTypeString[key])) {
+ return null;
+ }
+ return [TaskPropertySearchTypeString[key], message];
+ }),
+ ),
+);
/**
* Returns an object mapping property operation type values to raw internationalized
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByPropertyOperationType = _fromPairs(
- _compact(_map(messages, (message, key) =>
- !_isUndefined(TaskPropertyOperationType[key]) ?
- [TaskPropertyOperationType[key], message] :
- null
- ))
-)
+ _compact(
+ _map(messages, (message, key) =>
+ !_isUndefined(TaskPropertyOperationType[key])
+ ? [TaskPropertyOperationType[key], message]
+ : null,
+ ),
+ ),
+);
diff --git a/src/services/Task/TaskProperty/TaskProperty.test.js b/src/services/Task/TaskProperty/TaskProperty.test.js
index 0fb051ea1..f551d9d8c 100644
--- a/src/services/Task/TaskProperty/TaskProperty.test.js
+++ b/src/services/Task/TaskProperty/TaskProperty.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect } from "vitest";
+import { describe, expect, it } from "vitest";
import { messagesByPropertyOperationType } from "./TaskProperty";
describe("messagesByPropertyOperationType", () => {
diff --git a/src/services/Task/TaskReview/Messages.js b/src/services/Task/TaskReview/Messages.js
index 6e5fc0840..db8851d20 100644
--- a/src/services/Task/TaskReview/Messages.js
+++ b/src/services/Task/TaskReview/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with TaskReview.
@@ -6,96 +6,96 @@ import { defineMessages } from 'react-intl'
export default defineMessages({
needed: {
id: "Task.reviewStatus.needed",
- defaultMessage: "Review Requested"
+ defaultMessage: "Review Requested",
},
approved: {
id: "Task.reviewStatus.approved",
- defaultMessage: "Approved"
+ defaultMessage: "Approved",
},
rejected: {
id: "Task.reviewStatus.rejected",
- defaultMessage: "Needs Revision"
+ defaultMessage: "Needs Revision",
},
approvedWithFixes: {
id: "Task.reviewStatus.approvedWithFixes",
- defaultMessage: "Approved with Fixes"
+ defaultMessage: "Approved with Fixes",
},
approvedWithRevisions: {
id: "Task.reviewStatus.approvedWithRevisions",
- defaultMessage: "Approved with Revisions"
+ defaultMessage: "Approved with Revisions",
},
approvedWithFixesAfterRevisions: {
id: "Task.reviewStatus.approvedWithFixesAfterRevisions",
- defaultMessage: "Approved with Fixes after Revisions"
+ defaultMessage: "Approved with Fixes after Revisions",
},
disputed: {
id: "Task.reviewStatus.disputed",
- defaultMessage: "Contested"
+ defaultMessage: "Contested",
},
unnecessary: {
id: "Task.reviewStatus.unnecessary",
- defaultMessage: "Unnecessary"
+ defaultMessage: "Unnecessary",
},
unset: {
id: "Task.reviewStatus.unset",
- defaultMessage: "Review not yet requested"
+ defaultMessage: "Review not yet requested",
},
metaNeeded: {
id: "Task.reviewStatus.meta-needed",
- defaultMessage: "Re-Review Requested"
+ defaultMessage: "Re-Review Requested",
},
metaApproved: {
id: "Task.reviewStatus.meta-approved",
- defaultMessage: "Approved"
+ defaultMessage: "Approved",
},
metaRejected: {
id: "Task.reviewStatus.meta-rejected",
- defaultMessage: "Needs Revision"
+ defaultMessage: "Needs Revision",
},
metaApprovedWithFixes: {
id: "Task.reviewStatus.meta-approvedWithFixes",
- defaultMessage: "Approved with Fixes"
+ defaultMessage: "Approved with Fixes",
},
metaUnnecessary: {
id: "Task.reviewStatus.meta-unnecessary",
- defaultMessage: "Unnecessary"
+ defaultMessage: "Unnecessary",
},
metaUnset: {
id: "Task.reviewStatus.meta-unset",
- defaultMessage: "Unreviewed"
+ defaultMessage: "Unreviewed",
},
next: {
- id: 'Task.review.loadByMethod.next',
+ id: "Task.review.loadByMethod.next",
defaultMessage: "Next Filtered Task",
},
nearby: {
- id: 'Task.review.loadByMethod.nearby',
+ id: "Task.review.loadByMethod.nearby",
defaultMessage: "Nearby Task",
},
all: {
- id: 'Task.review.loadByMethod.all',
+ id: "Task.review.loadByMethod.all",
defaultMessage: "Back to Review All",
},
inbox: {
- id: 'Task.review.loadByMethod.inbox',
+ id: "Task.review.loadByMethod.inbox",
defaultMessage: "Back to Inbox",
},
-})
+});
diff --git a/src/services/Task/TaskReview/TaskReview.js b/src/services/Task/TaskReview/TaskReview.js
index a820f98cd..53e2a111e 100644
--- a/src/services/Task/TaskReview/TaskReview.js
+++ b/src/services/Task/TaskReview/TaskReview.js
@@ -1,185 +1,208 @@
-import { v1 as uuidv1 } from 'uuid'
-import uuidTime from 'uuid-time'
-import _set from 'lodash/set'
-import _isArray from 'lodash/isArray'
-import _cloneDeep from 'lodash/cloneDeep'
-import _snakeCase from 'lodash/snakeCase'
-import _isFinite from 'lodash/isFinite'
-import _map from 'lodash/map'
-import _values from 'lodash/values'
-import queryString from 'query-string'
-import Endpoint from '../../Server/Endpoint'
-import { defaultRoutes as api, isSecurityError } from '../../Server/Server'
-import { RECEIVE_REVIEW_NEEDED_TASKS } from './TaskReviewNeeded'
-import { RECEIVE_REVIEWED_TASKS,
- RECEIVE_MAPPER_REVIEWED_TASKS,
- RECEIVE_REVIEWED_BY_USER_TASKS } from './TaskReviewed'
-import RequestStatus from '../../Server/RequestStatus'
-import { taskSchema, taskBundleSchema, retrieveChallengeTask,
- receiveTasks, fetchTask } from '../Task'
-import { challengeSchema } from '../../Challenge/Challenge'
-import { generateSearchParametersString, PARAMS_MAP } from '../../Search/Search'
-import { addError } from '../../Error/Error'
-import AppErrors from '../../Error/AppErrors'
+import _cloneDeep from "lodash/cloneDeep";
+import _isArray from "lodash/isArray";
+import _isFinite from "lodash/isFinite";
import _join from "lodash/join";
-import { ensureUserLoggedIn } from '../../User/User'
-
-
-export const MARK_REVIEW_DATA_STALE = "MARK_REVIEW_DATA_STALE"
-
-export const REVIEW_TASKS_TO_BE_REVIEWED = 'tasksToBeReviewed'
-export const MY_REVIEWED_TASKS = 'myReviewedTasks'
-export const REVIEW_TASKS_BY_ME = 'tasksReviewedByMe'
-export const ALL_REVIEWED_TASKS = 'allReviewedTasks'
-export const META_REVIEW_TASKS = 'metaReviewTasks'
+import _map from "lodash/map";
+import _set from "lodash/set";
+import _snakeCase from "lodash/snakeCase";
+import _values from "lodash/values";
+import queryString from "query-string";
+import { v1 as uuidv1 } from "uuid";
+import uuidTime from "uuid-time";
+import { challengeSchema } from "../../Challenge/Challenge";
+import AppErrors from "../../Error/AppErrors";
+import { addError } from "../../Error/Error";
+import { PARAMS_MAP, generateSearchParametersString } from "../../Search/Search";
+import Endpoint from "../../Server/Endpoint";
+import RequestStatus from "../../Server/RequestStatus";
+import { defaultRoutes as api, isSecurityError } from "../../Server/Server";
+import { ensureUserLoggedIn } from "../../User/User";
+import {
+ fetchTask,
+ receiveTasks,
+ retrieveChallengeTask,
+ taskBundleSchema,
+ taskSchema,
+} from "../Task";
+import { RECEIVE_REVIEW_NEEDED_TASKS } from "./TaskReviewNeeded";
+import {
+ RECEIVE_MAPPER_REVIEWED_TASKS,
+ RECEIVE_REVIEWED_BY_USER_TASKS,
+ RECEIVE_REVIEWED_TASKS,
+} from "./TaskReviewed";
+
+export const MARK_REVIEW_DATA_STALE = "MARK_REVIEW_DATA_STALE";
+
+export const REVIEW_TASKS_TO_BE_REVIEWED = "tasksToBeReviewed";
+export const MY_REVIEWED_TASKS = "myReviewedTasks";
+export const REVIEW_TASKS_BY_ME = "tasksReviewedByMe";
+export const ALL_REVIEWED_TASKS = "allReviewedTasks";
+export const META_REVIEW_TASKS = "metaReviewTasks";
export const ReviewTasksType = {
toBeReviewed: REVIEW_TASKS_TO_BE_REVIEWED,
myReviewedTasks: MY_REVIEWED_TASKS,
reviewedByMe: REVIEW_TASKS_BY_ME,
allReviewedTasks: ALL_REVIEWED_TASKS,
- metaReviewTasks: META_REVIEW_TASKS
-}
+ metaReviewTasks: META_REVIEW_TASKS,
+};
// redux action creators
-export const RECEIVE_REVIEW_METRICS = 'RECEIVE_REVIEW_METRICS'
-export const RECEIVE_REVIEW_TAG_METRICS = 'RECEIVE_REVIEW_TAG_METRICS'
-export const RECEIVE_REVIEW_CLUSTERS = 'RECEIVE_REVIEW_CLUSTERS'
-export const RECEIVE_REVIEW_CHALLENGES = 'RECEIVE_REVIEW_CHALLENGES'
-export const RECEIVE_REVIEW_PROJECTS = 'RECEIVE_REVIEW_PROJECTS'
+export const RECEIVE_REVIEW_METRICS = "RECEIVE_REVIEW_METRICS";
+export const RECEIVE_REVIEW_TAG_METRICS = "RECEIVE_REVIEW_TAG_METRICS";
+export const RECEIVE_REVIEW_CLUSTERS = "RECEIVE_REVIEW_CLUSTERS";
+export const RECEIVE_REVIEW_CHALLENGES = "RECEIVE_REVIEW_CHALLENGES";
+export const RECEIVE_REVIEW_PROJECTS = "RECEIVE_REVIEW_PROJECTS";
const handleExposeError = (error, dispatch) => {
dispatch(ensureUserLoggedIn()).then(async () => {
const responseBody = await error.response.json();
- const message = responseBody.status === "Forbidden" ? { id: "Server", defaultMessage: responseBody.message } : AppErrors.user.unauthorized;
- dispatch(addError(message))
- })
-}
+ const message =
+ responseBody.status === "Forbidden"
+ ? { id: "Server", defaultMessage: responseBody.message }
+ : AppErrors.user.unauthorized;
+ dispatch(addError(message));
+ });
+};
/**
* Mark the current review data as stale, meaning the app has been
* informed or detected that updated task-review data is available
* from the server
*/
-export const markReviewDataStale = function() {
+export const markReviewDataStale = function () {
return {
type: MARK_REVIEW_DATA_STALE,
- }
-}
+ };
+};
/**
* Add or replace the review metrics in the redux store
*/
-export const receiveReviewMetrics = function(metrics, status=RequestStatus.success) {
+export const receiveReviewMetrics = function (metrics, status = RequestStatus.success) {
return {
type: RECEIVE_REVIEW_METRICS,
status,
metrics,
receivedAt: Date.now(),
- }
-}
+ };
+};
/**
* Add or replace the review metrics in the redux store
*/
-export const receiveReviewTagMetrics = function(tagMetrics, status=RequestStatus.success) {
+export const receiveReviewTagMetrics = function (tagMetrics, status = RequestStatus.success) {
return {
type: RECEIVE_REVIEW_TAG_METRICS,
status,
tagMetrics,
receivedAt: Date.now(),
- }
-}
+ };
+};
/**
* Add or replace the review clusters in the redux store
*/
-export const receiveReviewClusters = function(clusters, status=RequestStatus.success, fetchId) {
+export const receiveReviewClusters = function (clusters, status = RequestStatus.success, fetchId) {
return {
type: RECEIVE_REVIEW_CLUSTERS,
status,
clusters,
receivedAt: Date.now(),
- fetchId
- }
-}
+ fetchId,
+ };
+};
/**
* Add or replace the review challenges in the redux store
*/
-export const receiveReviewChallenges = function(reviewChallenges, status=RequestStatus.success, fetchId) {
+export const receiveReviewChallenges = function (
+ reviewChallenges,
+ status = RequestStatus.success,
+ fetchId,
+) {
return {
type: RECEIVE_REVIEW_CHALLENGES,
status,
reviewChallenges,
receivedAt: Date.now(),
- fetchId
- }
-}
+ fetchId,
+ };
+};
/**
* Add or replace the review projects in the redux store
*/
-export const receiveReviewProjects = function(reviewProjects, status=RequestStatus.success, fetchId) {
+export const receiveReviewProjects = function (
+ reviewProjects,
+ status = RequestStatus.success,
+ fetchId,
+) {
return {
type: RECEIVE_REVIEW_PROJECTS,
status,
reviewProjects,
receivedAt: Date.now(),
- fetchId
- }
-}
+ fetchId,
+ };
+};
// utility functions
/**
* Builds a link to export CSV
*/
-export const buildLinkToMapperExportCSV = function(criteria) {
- const queryFilters = generateReviewSearch(criteria)
+export const buildLinkToMapperExportCSV = function (criteria) {
+ const queryFilters = generateReviewSearch(criteria);
- return `${window.env.REACT_APP_MAP_ROULETTE_SERVER_URL}/api/v2/tasks/review/mappers/export?${queryString.stringify(queryFilters)}`
-}
+ return `${window.env.REACT_APP_MAP_ROULETTE_SERVER_URL}/api/v2/tasks/review/mappers/export?${queryString.stringify(queryFilters)}`;
+};
-export const buildLinkToReviewTableExportCSV = function(criteria, addedColumns) {
+export const buildLinkToReviewTableExportCSV = function (criteria, addedColumns) {
const queryFilters = buildQueryFilters(criteria, addedColumns);
- return `${window.env.REACT_APP_MAP_ROULETTE_SERVER_URL}/api/v2/tasks/review/reviewTable/export?${queryFilters}`
-}
+ return `${window.env.REACT_APP_MAP_ROULETTE_SERVER_URL}/api/v2/tasks/review/reviewTable/export?${queryFilters}`;
+};
-export const buildLinkToReviewerMetaExportCSV = function(criteria) {
- const queryFilters = generateReviewSearch(criteria)
+export const buildLinkToReviewerMetaExportCSV = function (criteria) {
+ const queryFilters = generateReviewSearch(criteria);
- return `${window.env.REACT_APP_MAP_ROULETTE_SERVER_URL}/api/v2/tasks/metareview/reviewers/export?${queryString.stringify(queryFilters)}`
-}
+ return `${window.env.REACT_APP_MAP_ROULETTE_SERVER_URL}/api/v2/tasks/metareview/reviewers/export?${queryString.stringify(queryFilters)}`;
+};
-export const buildLinkTaskReviewHistoryCSV = function(challengeId) {
- return `${window.env.REACT_APP_MAP_ROULETTE_SERVER_URL}/api/v2/challenge/${challengeId}/extractReviewHistory`
-}
+export const buildLinkTaskReviewHistoryCSV = function (challengeId) {
+ return `${window.env.REACT_APP_MAP_ROULETTE_SERVER_URL}/api/v2/challenge/${challengeId}/extractReviewHistory`;
+};
-const generateReviewSearch = function(criteria = {}, reviewTasksType = ReviewTasksType.allReviewedTasks, userId) {
- const searchParameters = generateSearchParametersString(criteria?.filters ?? {},
- criteria.boundingBox,
- criteria?.savedChallengesOnly,
- criteria?.excludeOtherReviewers,
- null,
- criteria?.invertFields ?? {})
+const generateReviewSearch = function (
+ criteria = {},
+ reviewTasksType = ReviewTasksType.allReviewedTasks,
+ userId,
+) {
+ const searchParameters = generateSearchParametersString(
+ criteria?.filters ?? {},
+ criteria.boundingBox,
+ criteria?.savedChallengesOnly,
+ criteria?.excludeOtherReviewers,
+ null,
+ criteria?.invertFields ?? {},
+ );
- const mappers = (reviewTasksType === ReviewTasksType.myReviewedTasks) ? [userId] : []
- const reviewers = (reviewTasksType === ReviewTasksType.reviewedByMe) ? [userId] : []
+ const mappers = reviewTasksType === ReviewTasksType.myReviewedTasks ? [userId] : [];
+ const reviewers = reviewTasksType === ReviewTasksType.reviewedByMe ? [userId] : [];
- return {...searchParameters, mappers, reviewers}
-}
+ return { ...searchParameters, mappers, reviewers };
+};
const buildQueryFilters = function (criteria, addedColumns) {
//Sort criteria filtering
- const sortCriteria = criteria?.sortCriteria ?? {}
- const direction = sortCriteria.direction
- let sortBy = sortCriteria.sortBy //Set and fix sort by values
- sortBy = sortBy == "mappedOn" ? "mapped_on" : sortBy
- sortBy = sortBy == "id" ? "tasks.id" : sortBy
- sortBy = sortBy == "reviewStatus" ? "review_status" : sortBy
- sortBy = sortBy == "reviewedAt" ? "reviewed_at" : sortBy
- sortBy = sortBy == "metaReviewedAt" ? "meta_reviewed_at" : sortBy
+ const sortCriteria = criteria?.sortCriteria ?? {};
+ const direction = sortCriteria.direction;
+ let sortBy = sortCriteria.sortBy; //Set and fix sort by values
+ sortBy = sortBy == "mappedOn" ? "mapped_on" : sortBy;
+ sortBy = sortBy == "id" ? "tasks.id" : sortBy;
+ sortBy = sortBy == "reviewStatus" ? "review_status" : sortBy;
+ sortBy = sortBy == "reviewedAt" ? "reviewed_at" : sortBy;
+ sortBy = sortBy == "metaReviewedAt" ? "meta_reviewed_at" : sortBy;
//Main Filters
const filters = criteria?.filters ?? {};
@@ -194,78 +217,76 @@ const buildQueryFilters = function (criteria, addedColumns) {
const metaReviewedBy = filters.metaReviewedBy;
//inverted filters
- let invertedFilters = _map(criteria.invertFields, (v, k) =>
- v ? PARAMS_MAP[k] : undefined
- )
-
+ let invertedFilters = _map(criteria.invertFields, (v, k) => (v ? PARAMS_MAP[k] : undefined));
+
//fix invertedFilters values
- invertedFilters = invertedFilters.map(e => e === 'tp' ? 'priorities' : e);
- invertedFilters = invertedFilters.map(e => e === 'o' ? 'm' : e);
- invertedFilters = invertedFilters.map(e => e === 'cs' ? 'cid' : e);
- invertedFilters = invertedFilters.map(e => e === 'ps' ? 'pid' : e);
-
+ invertedFilters = invertedFilters.map((e) => (e === "tp" ? "priorities" : e));
+ invertedFilters = invertedFilters.map((e) => (e === "o" ? "m" : e));
+ invertedFilters = invertedFilters.map((e) => (e === "cs" ? "cid" : e));
+ invertedFilters = invertedFilters.map((e) => (e === "ps" ? "pid" : e));
+
//Fixes mappedOn Formatting Data
- let timestamp = ""
- if(mappedOn){
+ let timestamp = "";
+ if (mappedOn) {
const date = new Date(filters.mappedOn);
- const month = String(date.getMonth() + 1).padStart(2, '0');
- const day = String(date.getDate()).padStart(2, '0');
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const day = String(date.getDate()).padStart(2, "0");
const year = String(date.getFullYear());
- timestamp = `${year}-${month}-${day}`
+ timestamp = `${year}-${month}-${day}`;
}
//Sets initial value of these parameters to negate their "all" value
- let status = ['0', '1', '2', '3', '4', '5', '6', '9']
- let reviewStatus = ['0', '1', '2', '3', '4', '5', '6', '7', '-1']
- let priority = ['0', '1', '2']
- let metaReviewStatus = ['-2', '0', '1', '2', '3', '6']
+ let status = ["0", "1", "2", "3", "4", "5", "6", "9"];
+ let reviewStatus = ["0", "1", "2", "3", "4", "5", "6", "7", "-1"];
+ let priority = ["0", "1", "2"];
+ let metaReviewStatus = ["-2", "0", "1", "2", "3", "6"];
//add configuration to remove inverting on the "all" value
function removeValueFromArray(arr, value) {
- return arr.filter(e => e !== value);
+ return arr.filter((e) => e !== value);
}
//add configuration to replace the value "all" with the needed equivalent values
//remove inversion if values are equal to "all"
- if(filters.status != "all" && filters.status != undefined){
- status = JSON.stringify(filters.status)
- } else if(filters.status == 'all' || filters.status == undefined) {
+ if (filters.status != "all" && filters.status != undefined) {
+ status = JSON.stringify(filters.status);
+ } else if (filters.status == "all" || filters.status == undefined) {
invertedFilters = removeValueFromArray(invertedFilters, "tStatus");
}
- if(filters.reviewStatus != "all" && filters.reviewStatus != undefined) {
- reviewStatus = JSON.stringify(filters.reviewStatus)
- } else if(filters.reviewStatus == 'all' || filters.reviewStatus == undefined) {
+ if (filters.reviewStatus != "all" && filters.reviewStatus != undefined) {
+ reviewStatus = JSON.stringify(filters.reviewStatus);
+ } else if (filters.reviewStatus == "all" || filters.reviewStatus == undefined) {
invertedFilters = removeValueFromArray(invertedFilters, "trStatus");
}
- if(filters.priority != "all" && filters.priority != undefined){
- priority = JSON.stringify(filters.priority)
- } else if(filters.priority == 'all' || filters.priority == undefined) {
+ if (filters.priority != "all" && filters.priority != undefined) {
+ priority = JSON.stringify(filters.priority);
+ } else if (filters.priority == "all" || filters.priority == undefined) {
invertedFilters = removeValueFromArray(invertedFilters, "priorities");
}
- if(filters.metaReviewStatus != "all" && filters.metaReviewStatus != undefined){
- metaReviewStatus = JSON.stringify(filters.metaReviewStatus)
- } else if(filters.metaReviewStatus == 'all' || filters.metaReviewStatus == undefined) {
+ if (filters.metaReviewStatus != "all" && filters.metaReviewStatus != undefined) {
+ metaReviewStatus = JSON.stringify(filters.metaReviewStatus);
+ } else if (filters.metaReviewStatus == "all" || filters.metaReviewStatus == undefined) {
invertedFilters = removeValueFromArray(invertedFilters, "mrStatus");
}
//Holds the displayed column names and their order
- let displayedColumns = Object.keys(addedColumns).map(key => {
- const capitalizedKey = key.replace(/([A-Z])/g, ' $1').trim();
+ let displayedColumns = Object.keys(addedColumns).map((key) => {
+ const capitalizedKey = key.replace(/([A-Z])/g, " $1").trim();
return capitalizedKey.charAt(0).toUpperCase() + capitalizedKey.slice(1);
});
//Fix Headers
- displayedColumns = displayedColumns.map(e => e === 'Id' ? 'Internal Id' : e);
- displayedColumns = displayedColumns.map(e => e === 'Mapper Controls' ? 'Actions' : e);
- displayedColumns = displayedColumns.map(e => e === 'Reviewer Controls' ? 'Actions' : e);
- displayedColumns = displayedColumns.map(e => e === 'Review Requested By' ? 'Mapper' : e);
- displayedColumns = displayedColumns.map(e => e === 'Reviewed By' ? 'Reviewer' : e);
- displayedColumns = displayedColumns.map(e => e === 'Reviewed At' ? 'Reviewed On' : e);
+ displayedColumns = displayedColumns.map((e) => (e === "Id" ? "Internal Id" : e));
+ displayedColumns = displayedColumns.map((e) => (e === "Mapper Controls" ? "Actions" : e));
+ displayedColumns = displayedColumns.map((e) => (e === "Reviewer Controls" ? "Actions" : e));
+ displayedColumns = displayedColumns.map((e) => (e === "Review Requested By" ? "Mapper" : e));
+ displayedColumns = displayedColumns.map((e) => (e === "Reviewed By" ? "Reviewer" : e));
+ displayedColumns = displayedColumns.map((e) => (e === "Reviewed At" ? "Reviewed On" : e));
displayedColumns = removeValueFromArray(displayedColumns, "View Comments");
displayedColumns = removeValueFromArray(displayedColumns, "Tags");
return (
`${taskId ? `taskId=${taskId}` : ""}` +
`${featureId ? `&featureId=${featureId}` : ""}` +
- `&reviewStatus=${_join(reviewStatus, ",")}`+
+ `&reviewStatus=${_join(reviewStatus, ",")}` +
`${reviewRequestedBy ? `&mapper=${reviewRequestedBy}` : ""}` +
`${challengeId ? `&challengeId=${challengeId}` : ""}` +
`${projectId ? `&projectIds=${projectId}` : ""}` +
@@ -286,173 +307,186 @@ const buildQueryFilters = function (criteria, addedColumns) {
/**
* Retrieve metrics for a given review tasks type and filter criteria
*/
-export const fetchReviewMetrics = function(userId, reviewTasksType, criteria) {
- const type = determineType(reviewTasksType)
- const params = generateReviewSearch(criteria, reviewTasksType, userId)
-
- return function(dispatch) {
- return new Endpoint(
- api.tasks.reviewMetrics,
- {
- params: {reviewTasksType: type, ...params,
- includeByPriority: true, includeByTaskStatus: true},
- }
- ).execute().then(normalizedResults => {
- dispatch(receiveReviewMetrics(normalizedResults, RequestStatus.success))
- return normalizedResults
- }).catch((error) => {
- console.log(error.response || error)
- })
- }
-}
+export const fetchReviewMetrics = function (userId, reviewTasksType, criteria) {
+ const type = determineType(reviewTasksType);
+ const params = generateReviewSearch(criteria, reviewTasksType, userId);
+
+ return function (dispatch) {
+ return new Endpoint(api.tasks.reviewMetrics, {
+ params: {
+ reviewTasksType: type,
+ ...params,
+ includeByPriority: true,
+ includeByTaskStatus: true,
+ },
+ })
+ .execute()
+ .then((normalizedResults) => {
+ dispatch(receiveReviewMetrics(normalizedResults, RequestStatus.success));
+ return normalizedResults;
+ })
+ .catch((error) => {
+ console.log(error.response || error);
+ });
+ };
+};
/**
* Retrieve metrics for a given review tasks type and filter criteria
*/
-export const fetchReviewTagMetrics = function(userId, reviewTasksType, criteria) {
- const type = determineType(reviewTasksType)
- const params = generateReviewSearch(criteria, reviewTasksType, userId)
-
- return function(dispatch) {
- return new Endpoint(
- api.tasks.reviewTagMetrics,
- {
- schema: null,
- params: {reviewTasksType: type, ...params},
- }
- ).execute().then(normalizedResults => {
- dispatch(receiveReviewTagMetrics(normalizedResults, RequestStatus.success))
- return normalizedResults
- }).catch((error) => {
- console.log(error.response || error)
- })
- }
-}
+export const fetchReviewTagMetrics = function (userId, reviewTasksType, criteria) {
+ const type = determineType(reviewTasksType);
+ const params = generateReviewSearch(criteria, reviewTasksType, userId);
+
+ return function (dispatch) {
+ return new Endpoint(api.tasks.reviewTagMetrics, {
+ schema: null,
+ params: { reviewTasksType: type, ...params },
+ })
+ .execute()
+ .then((normalizedResults) => {
+ dispatch(receiveReviewTagMetrics(normalizedResults, RequestStatus.success));
+ return normalizedResults;
+ })
+ .catch((error) => {
+ console.log(error.response || error);
+ });
+ };
+};
/**
* Retrieve clustered tasks for given review criteria
*/
-export const fetchClusteredReviewTasks = function(reviewTasksType, criteria={}) {
- const searchParameters = generateSearchParametersString(criteria?.filters ?? {},
- criteria.boundingBox,
- criteria?.savedChallengesOnly,
- criteria?.excludeOtherReviewers,
- null,
- criteria?.invertFields ?? {})
- return function(dispatch) {
- if (window.env.REACT_APP_DISABLE_TASK_CLUSTERS === 'true') {
+export const fetchClusteredReviewTasks = function (reviewTasksType, criteria = {}) {
+ const searchParameters = generateSearchParametersString(
+ criteria?.filters ?? {},
+ criteria.boundingBox,
+ criteria?.savedChallengesOnly,
+ criteria?.excludeOtherReviewers,
+ null,
+ criteria?.invertFields ?? {},
+ );
+ return function (dispatch) {
+ if (window.env.REACT_APP_DISABLE_TASK_CLUSTERS === "true") {
return new Promise((resolve) => resolve());
}
- const type = determineType(reviewTasksType)
- const fetchId = uuidv1()
-
- dispatch(receiveReviewClusters({}, RequestStatus.inProgress, fetchId))
- return new Endpoint(
- api.tasks.fetchReviewClusters,
- {
- schema: {tasks: [taskSchema()]},
- params: {reviewTasksType: type, points: 25, ...searchParameters},
- }
- ).execute().then(normalizedResults => {
- if (normalizedResults.result) {
- dispatch(receiveReviewClusters(normalizedResults.result, RequestStatus.success, fetchId))
- }
+ const type = determineType(reviewTasksType);
+ const fetchId = uuidv1();
- return normalizedResults.result
- }).catch((error) => {
- dispatch(receiveReviewClusters({}, RequestStatus.error, fetchId))
- console.log(error.response || error)
+ dispatch(receiveReviewClusters({}, RequestStatus.inProgress, fetchId));
+ return new Endpoint(api.tasks.fetchReviewClusters, {
+ schema: { tasks: [taskSchema()] },
+ params: { reviewTasksType: type, points: 25, ...searchParameters },
})
- }
-}
+ .execute()
+ .then((normalizedResults) => {
+ if (normalizedResults.result) {
+ dispatch(receiveReviewClusters(normalizedResults.result, RequestStatus.success, fetchId));
+ }
+
+ return normalizedResults.result;
+ })
+ .catch((error) => {
+ dispatch(receiveReviewClusters({}, RequestStatus.error, fetchId));
+ console.log(error.response || error);
+ });
+ };
+};
const determineType = (reviewTasksType) => {
- switch(reviewTasksType) {
+ switch (reviewTasksType) {
case ReviewTasksType.toBeReviewed:
- return 1
+ return 1;
case ReviewTasksType.reviewedByMe:
- return 2
+ return 2;
case ReviewTasksType.myReviewedTasks:
- return 3
+ return 3;
case ReviewTasksType.allReviewedTasks:
- return 4
+ return 4;
case ReviewTasksType.metaReviewTasks:
default:
- return 5
+ return 5;
}
-}
+};
/*
* Retrieve review tasks geographically closest to the given task (up to the given
* limit). Returns an object in clusteredTasks format with the tasks and meta data.
* Note that this does not add the results to the redux store, but simply returns them
*/
-export const fetchNearbyReviewTasks = function(taskId, criteria={}, limit=5, asMetaReview=false) {
- return function() {
- const searchParameters = generateSearchParametersString(criteria?.filters ?? {},
- criteria.boundingBox,
- criteria?.savedChallengesOnly,
- criteria?.excludeOtherReviewers,
- null,
- criteria?.invertFields ?? {})
-
- const params = {limit, ...searchParameters, asMetaReview}
-
- return new Endpoint(
- api.tasks.nearbyReviewTasks,
- {
- schema: [ taskSchema() ],
- variables: {taskId},
- params,
- }
- ).execute().then(normalizedResults => ({
- loading: false,
- tasks: _map(_values(normalizedResults?.entities?.tasks ?? {}), task => {
- if (task.location) {
- // match clusteredTasks response, which returns a point with lat/lng fields
- task.point = {
- lng: task.location.coordinates[0],
- lat: task.location.coordinates[1]
+export const fetchNearbyReviewTasks = function (
+ taskId,
+ criteria = {},
+ limit = 5,
+ asMetaReview = false,
+) {
+ return function () {
+ const searchParameters = generateSearchParametersString(
+ criteria?.filters ?? {},
+ criteria.boundingBox,
+ criteria?.savedChallengesOnly,
+ criteria?.excludeOtherReviewers,
+ null,
+ criteria?.invertFields ?? {},
+ );
+
+ const params = { limit, ...searchParameters, asMetaReview };
+
+ return new Endpoint(api.tasks.nearbyReviewTasks, {
+ schema: [taskSchema()],
+ variables: { taskId },
+ params,
+ })
+ .execute()
+ .then((normalizedResults) => ({
+ loading: false,
+ tasks: _map(_values(normalizedResults?.entities?.tasks ?? {}), (task) => {
+ if (task.location) {
+ // match clusteredTasks response, which returns a point with lat/lng fields
+ task.point = {
+ lng: task.location.coordinates[0],
+ lat: task.location.coordinates[1],
+ };
}
- }
- return task
- })
- }));
+ return task;
+ }),
+ }));
};
-}
-
+};
/**
* Retrieve the next task to review with the given sort and filter criteria
*/
-export const loadNextReviewTask = function(criteria={}, lastTaskId, asMetaReview) {
- const sortBy = criteria?.sortCriteria?.sortBy
- const order = ((criteria?.sortCriteria?.direction) || 'DESC').toUpperCase()
- const sort = sortBy ? `${_snakeCase(sortBy)}` : null
- const searchParameters = generateSearchParametersString(criteria?.filters ?? {},
- criteria.boundingBox,
- criteria?.savedChallengesOnly,
- criteria?.excludeOtherReviewers,
- null,
- criteria?.invertFields ?? {})
-
- return function(dispatch) {
- const params = {sort, order, ...searchParameters, asMetaReview}
+export const loadNextReviewTask = function (criteria = {}, lastTaskId, asMetaReview) {
+ const sortBy = criteria?.sortCriteria?.sortBy;
+ const order = (criteria?.sortCriteria?.direction || "DESC").toUpperCase();
+ const sort = sortBy ? `${_snakeCase(sortBy)}` : null;
+ const searchParameters = generateSearchParametersString(
+ criteria?.filters ?? {},
+ criteria.boundingBox,
+ criteria?.savedChallengesOnly,
+ criteria?.excludeOtherReviewers,
+ null,
+ criteria?.invertFields ?? {},
+ );
+
+ return function (dispatch) {
+ const params = { sort, order, ...searchParameters, asMetaReview };
if (_isFinite(lastTaskId)) {
- params.lastTaskId = lastTaskId
+ params.lastTaskId = lastTaskId;
}
- return retrieveChallengeTask(dispatch, new Endpoint(
- api.tasks.reviewNext,
- {
+ return retrieveChallengeTask(
+ dispatch,
+ new Endpoint(api.tasks.reviewNext, {
schema: taskSchema(),
variables: {},
params,
- }
- ))
- }
-}
+ }),
+ );
+ };
+};
/**
* Fetch data for the given task and claim it for review.
@@ -460,260 +494,321 @@ export const loadNextReviewTask = function(criteria={}, lastTaskId, asMetaReview
* If info on available mapillary images for the task is also desired, set
* includeMapillary to true
*/
-export const fetchTaskForReview = function(taskId, includeMapillary=false) {
- return function(dispatch) {
+export const fetchTaskForReview = function (taskId, includeMapillary = false) {
+ return function (dispatch) {
return new Endpoint(api.task.startReview, {
schema: taskSchema(),
- variables: {id: taskId},
- params: {mapillary: includeMapillary}
- }).execute().then(normalizedResults => {
- dispatch(receiveTasks(normalizedResults.entities))
- return normalizedResults
+ variables: { id: taskId },
+ params: { mapillary: includeMapillary },
})
- }
-}
+ .execute()
+ .then((normalizedResults) => {
+ dispatch(receiveTasks(normalizedResults.entities));
+ return normalizedResults;
+ });
+ };
+};
/**
* Remove the task review claim on this task.
*/
-export const cancelReviewClaim = function(taskId) {
- return function(dispatch) {
- return new Endpoint(
- api.task.cancelReview, {schema: taskSchema(), variables: {id: taskId}}
- ).execute().then(normalizedResults => {
- // Server doesn't explicitly return empty fields from JSON.
- // This field should now be null so we will set it so when the
- // task data is merged with existing task data it will be correct.
- normalizedResults.entities.tasks[taskId].reviewClaimedBy = null
- dispatch(receiveTasks(normalizedResults.entities))
- return normalizedResults
- }).catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- console.log(error.response || error)
- }
- fetchTask(taskId)(dispatch) // Fetch accurate task data
- })
- }
-}
-
-export const removeReviewRequest = function(challengeId, taskIds, criteria, excludeTaskIds, asMetaReview) {
- return function(dispatch) {
- const filters = criteria?.filters ?? {}
- const searchParameters = !criteria ? {} :
- generateSearchParametersString(filters,
- criteria.boundingBox,
- null,
- null,
- criteria.searchQuery,
- criteria.invertFields,
- excludeTaskIds)
- searchParameters.cid = challengeId
+export const cancelReviewClaim = function (taskId) {
+ return function (dispatch) {
+ return new Endpoint(api.task.cancelReview, { schema: taskSchema(), variables: { id: taskId } })
+ .execute()
+ .then((normalizedResults) => {
+ // Server doesn't explicitly return empty fields from JSON.
+ // This field should now be null so we will set it so when the
+ // task data is merged with existing task data it will be correct.
+ normalizedResults.entities.tasks[taskId].reviewClaimedBy = null;
+ dispatch(receiveTasks(normalizedResults.entities));
+ return normalizedResults;
+ })
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ } else {
+ console.log(error.response || error);
+ }
+ fetchTask(taskId)(dispatch); // Fetch accurate task data
+ });
+ };
+};
+
+export const removeReviewRequest = function (
+ challengeId,
+ taskIds,
+ criteria,
+ excludeTaskIds,
+ asMetaReview,
+) {
+ return function (dispatch) {
+ const filters = criteria?.filters ?? {};
+ const searchParameters = !criteria
+ ? {}
+ : generateSearchParametersString(
+ filters,
+ criteria.boundingBox,
+ null,
+ null,
+ criteria.searchQuery,
+ criteria.invertFields,
+ excludeTaskIds,
+ );
+ searchParameters.cid = challengeId;
if (taskIds) {
- searchParameters.ids = taskIds.join(',')
+ searchParameters.ids = taskIds.join(",");
}
- return new Endpoint(
- api.tasks.removeReviewRequest, {
- params: {...searchParameters, asMetaReview},
- json: filters.taskPropertySearch ?
- {taskPropertySearch: filters.taskPropertySearch} : null,
- }
- ).execute().catch(error => {
- if (isSecurityError(error)) {
- handleExposeError(error, dispatch)
- }
- else {
- dispatch(addError(AppErrors.task.updateFailure))
- console.log(error.response || error)
- }
+ return new Endpoint(api.tasks.removeReviewRequest, {
+ params: { ...searchParameters, asMetaReview },
+ json: filters.taskPropertySearch ? { taskPropertySearch: filters.taskPropertySearch } : null,
})
+ .execute()
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ handleExposeError(error, dispatch);
+ } else {
+ dispatch(addError(AppErrors.task.updateFailure));
+ console.log(error.response || error);
+ }
+ });
};
-}
+};
/**
*
*/
-export const completeReview = function(taskId, taskReviewStatus, comment, tags, newTaskStatus, asMetaReview = false, errorTags) {
- return function(dispatch) {
- return updateTaskReviewStatus(dispatch, taskId, taskReviewStatus, comment, tags, newTaskStatus, asMetaReview, errorTags)
- }
-}
+export const completeReview = function (
+ taskId,
+ taskReviewStatus,
+ comment,
+ tags,
+ newTaskStatus,
+ asMetaReview = false,
+ errorTags,
+) {
+ return function (dispatch) {
+ return updateTaskReviewStatus(
+ dispatch,
+ taskId,
+ taskReviewStatus,
+ comment,
+ tags,
+ newTaskStatus,
+ asMetaReview,
+ errorTags,
+ );
+ };
+};
-export const completeBundleReview = function(bundleId, taskReviewStatus, comment, tags, newTaskStatus, asMetaReview=false, errorTags) {
- return function(dispatch) {
+export const completeBundleReview = function (
+ bundleId,
+ taskReviewStatus,
+ comment,
+ tags,
+ newTaskStatus,
+ asMetaReview = false,
+ errorTags,
+) {
+ return function (dispatch) {
return new Endpoint(
- asMetaReview ? api.tasks.bundled.updateMetaReviewStatus :
- api.tasks.bundled.updateReviewStatus, {
- schema: taskBundleSchema(),
- variables: {bundleId, status: taskReviewStatus},
- params:{tags, newTaskStatus, asMetaReview, errorTags: errorTags ? errorTags.join(",") : undefined },
- json: { comment: comment }
- }).execute().catch(error => {
- if (isSecurityError(error)) {
- handleExposeError(error, dispatch)
- }
- else {
- dispatch(addError(AppErrors.task.updateFailure))
- console.log(error.response || error)
- }
- })
- }
-}
+ asMetaReview
+ ? api.tasks.bundled.updateMetaReviewStatus
+ : api.tasks.bundled.updateReviewStatus,
+ {
+ schema: taskBundleSchema(),
+ variables: { bundleId, status: taskReviewStatus },
+ params: {
+ tags,
+ newTaskStatus,
+ asMetaReview,
+ errorTags: errorTags ? errorTags.join(",") : undefined,
+ },
+ json: { comment: comment },
+ },
+ )
+ .execute()
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ handleExposeError(error, dispatch);
+ } else {
+ dispatch(addError(AppErrors.task.updateFailure));
+ console.log(error.response || error);
+ }
+ });
+ };
+};
/**
* Fetches a list of challenges which have review tasks
*/
-export const fetchReviewChallenges = function(reviewTasksType,
- includeTaskStatuses = null,
- excludeOtherReviewers = true) {
- return function(dispatch) {
- const type = determineType(reviewTasksType)
-
- const tStatus = includeTaskStatuses ? includeTaskStatuses.join(',') : ""
-
- return new Endpoint(
- api.challenges.withReviewTasks,
- {schema: [challengeSchema()],
- params:{reviewTasksType: type, excludeOtherReviewers, tStatus,
- limit: -1}}
- ).execute().then(normalizedResults => {
- dispatch(receiveReviewChallenges(normalizedResults.entities.challenges, RequestStatus.success))
- dispatch(receiveReviewProjects(normalizedResults.entities.projects, RequestStatus.success))
-
- return normalizedResults
- }).catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- dispatch(addError(AppErrors.challenge.fetchFailure))
- console.log(error.response || error)
- }
+export const fetchReviewChallenges = function (
+ reviewTasksType,
+ includeTaskStatuses = null,
+ excludeOtherReviewers = true,
+) {
+ return function (dispatch) {
+ const type = determineType(reviewTasksType);
+
+ const tStatus = includeTaskStatuses ? includeTaskStatuses.join(",") : "";
+
+ return new Endpoint(api.challenges.withReviewTasks, {
+ schema: [challengeSchema()],
+ params: { reviewTasksType: type, excludeOtherReviewers, tStatus, limit: -1 },
})
- }
-}
+ .execute()
+ .then((normalizedResults) => {
+ dispatch(
+ receiveReviewChallenges(normalizedResults.entities.challenges, RequestStatus.success),
+ );
+ dispatch(receiveReviewProjects(normalizedResults.entities.projects, RequestStatus.success));
+
+ return normalizedResults;
+ })
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ } else {
+ dispatch(addError(AppErrors.challenge.fetchFailure));
+ console.log(error.response || error);
+ }
+ });
+ };
+};
-const updateTaskReviewStatus = function(dispatch, taskId, newStatus, comment,
- tags, newTaskStatus, asMetaReview, errorTags) {
+const updateTaskReviewStatus = function (
+ dispatch,
+ taskId,
+ newStatus,
+ comment,
+ tags,
+ newTaskStatus,
+ asMetaReview,
+ errorTags,
+) {
// Optimistically assume request will succeed. The store will be updated
// with fresh task data from the server if the save encounters an error.
- dispatch(receiveTasks({
- tasks: {
- [taskId]: {
- id: taskId,
- reviewStatus: newStatus
- }
- }
- }))
+ dispatch(
+ receiveTasks({
+ tasks: {
+ [taskId]: {
+ id: taskId,
+ reviewStatus: newStatus,
+ },
+ },
+ }),
+ );
return new Endpoint(
- asMetaReview ?
- api.task.updateMetaReviewStatus : api.task.updateReviewStatus,
- {schema: taskSchema(),
- variables: {id: taskId, status: newStatus },
- params:{ tags: tags, newTaskStatus: newTaskStatus, errorTags: errorTags ? errorTags.join(",") : undefined },
- json: { comment: comment }
+ asMetaReview ? api.task.updateMetaReviewStatus : api.task.updateReviewStatus,
+ {
+ schema: taskSchema(),
+ variables: { id: taskId, status: newStatus },
+ params: {
+ tags: tags,
+ newTaskStatus: newTaskStatus,
+ errorTags: errorTags ? errorTags.join(",") : undefined,
+ },
+ json: { comment: comment },
},
- ).execute().catch(error => {
- if (isSecurityError(error)) {
- handleExposeError(error, dispatch)
- }
- else {
- dispatch(addError(AppErrors.task.updateFailure))
- console.log(error.response || error)
- }
- fetchTask(taskId)(dispatch) // Fetch accurate task data
- })
-}
+ )
+ .execute()
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ handleExposeError(error, dispatch);
+ } else {
+ dispatch(addError(AppErrors.task.updateFailure));
+ console.log(error.response || error);
+ }
+ fetchTask(taskId)(dispatch); // Fetch accurate task data
+ });
+};
// redux reducers
-export const currentReviewTasks = function(state={}, action) {
- let updatedState = null
+export const currentReviewTasks = function (state = {}, action) {
+ let updatedState = null;
- switch(action.type) {
+ switch (action.type) {
case MARK_REVIEW_DATA_STALE:
- updatedState = _cloneDeep(state)
- _set(updatedState, 'reviewNeeded.dataStale', true)
- _set(updatedState, 'reviewed.dataStale', true)
- _set(updatedState, 'reviewedByUser.dataStale', true)
- return updatedState
+ updatedState = _cloneDeep(state);
+ _set(updatedState, "reviewNeeded.dataStale", true);
+ _set(updatedState, "reviewed.dataStale", true);
+ _set(updatedState, "reviewedByUser.dataStale", true);
+ return updatedState;
case RECEIVE_REVIEWED_TASKS:
- return updateReduxState(state, action, "reviewed")
+ return updateReduxState(state, action, "reviewed");
case RECEIVE_MAPPER_REVIEWED_TASKS:
- return updateReduxState(state, action, "mapperReviewed")
+ return updateReduxState(state, action, "mapperReviewed");
case RECEIVE_REVIEWED_BY_USER_TASKS:
- return updateReduxState(state, action, "reviewedByUser")
+ return updateReduxState(state, action, "reviewedByUser");
case RECEIVE_REVIEW_NEEDED_TASKS:
- return updateReduxState(state, action, "reviewNeeded")
+ return updateReduxState(state, action, "reviewNeeded");
case RECEIVE_REVIEW_METRICS:
- return updateReduxState(state, action, "metrics")
+ return updateReduxState(state, action, "metrics");
case RECEIVE_REVIEW_TAG_METRICS:
- return updateReduxState(state, action, "tagMetrics")
+ return updateReduxState(state, action, "tagMetrics");
case RECEIVE_REVIEW_CLUSTERS:
- return updateReduxState(state, action, "clusters")
+ return updateReduxState(state, action, "clusters");
case RECEIVE_REVIEW_CHALLENGES:
- return updateReduxState(state, action, "reviewChallenges")
+ return updateReduxState(state, action, "reviewChallenges");
case RECEIVE_REVIEW_PROJECTS:
- return updateReduxState(state, action, "reviewProjects")
+ return updateReduxState(state, action, "reviewProjects");
default:
- return state
+ return state;
}
-}
+};
-const updateReduxState = function(state={}, action, listName) {
- const mergedState = _cloneDeep(state)
+const updateReduxState = function (state = {}, action, listName) {
+ const mergedState = _cloneDeep(state);
if (action.type === RECEIVE_REVIEW_METRICS) {
- mergedState[listName] = action.metrics
- return mergedState
+ mergedState[listName] = action.metrics;
+ return mergedState;
}
if (action.type === RECEIVE_REVIEW_TAG_METRICS) {
- mergedState[listName] = action.tagMetrics
- return mergedState
+ mergedState[listName] = action.tagMetrics;
+ return mergedState;
}
if (action.type === RECEIVE_REVIEW_CLUSTERS) {
if (action.fetchId !== state.fetchId || action.status !== state.status) {
- const fetchTime = parseInt(uuidTime.v1(action.fetchId))
- const lastFetch = state.fetchId ? parseInt(uuidTime.v1(state.fetchId)) : 0
+ const fetchTime = parseInt(uuidTime.v1(action.fetchId));
+ const lastFetch = state.fetchId ? parseInt(uuidTime.v1(state.fetchId)) : 0;
if (fetchTime >= lastFetch) {
- mergedState.fetchId = action.fetchId
- mergedState[listName] = action.clusters
+ mergedState.fetchId = action.fetchId;
+ mergedState[listName] = action.clusters;
}
}
- return mergedState
+ return mergedState;
}
if (action.type === RECEIVE_REVIEW_CHALLENGES) {
- mergedState[listName] = action.reviewChallenges
- return mergedState
+ mergedState[listName] = action.reviewChallenges;
+ return mergedState;
}
if (action.type === RECEIVE_REVIEW_PROJECTS) {
- mergedState[listName] = action.reviewProjects
- return mergedState
+ mergedState[listName] = action.reviewProjects;
+ return mergedState;
}
if (action.status === RequestStatus.success) {
- const updatedTasks = {}
+ const updatedTasks = {};
- updatedTasks.tasks = _isArray(action.tasks) ? action.tasks : []
- updatedTasks.totalCount = action.totalCount
- updatedTasks.dataStale = false
+ updatedTasks.tasks = _isArray(action.tasks) ? action.tasks : [];
+ updatedTasks.totalCount = action.totalCount;
+ updatedTasks.dataStale = false;
- mergedState[listName] = updatedTasks
- return mergedState
+ mergedState[listName] = updatedTasks;
+ return mergedState;
+ } else {
+ return state;
}
- else {
- return state
- }
-}
+};
diff --git a/src/services/Task/TaskReview/TaskReviewLoadMethod.js b/src/services/Task/TaskReview/TaskReviewLoadMethod.js
index 984533232..e585692c8 100644
--- a/src/services/Task/TaskReview/TaskReviewLoadMethod.js
+++ b/src/services/Task/TaskReview/TaskReviewLoadMethod.js
@@ -1,30 +1,30 @@
-import _fromPairs from 'lodash/fromPairs'
-import _map from 'lodash/map'
-import messages from './Messages'
+import _fromPairs from "lodash/fromPairs";
+import _map from "lodash/map";
+import messages from "./Messages";
/** Load next review task */
-export const NEXT_LOAD_METHOD = 'next'
+export const NEXT_LOAD_METHOD = "next";
/** Load nearby review task */
-export const NEARBY_LOAD_METHOD = 'nearby'
+export const NEARBY_LOAD_METHOD = "nearby";
/** Load review page with all review tasks */
-export const ALL_LOAD_METHOD = 'all'
+export const ALL_LOAD_METHOD = "all";
/** Load inbox */
-export const LOAD_INBOX_METHOD = 'inbox'
+export const LOAD_INBOX_METHOD = "inbox";
export const TaskReviewLoadMethod = Object.freeze({
next: NEXT_LOAD_METHOD,
all: ALL_LOAD_METHOD,
inbox: LOAD_INBOX_METHOD,
nearby: NEARBY_LOAD_METHOD,
-})
+});
/**
* Returns an object mapping status values to raw internationalized
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByReviewLoadMethod = _fromPairs(
- _map(messages, (message, key) => [TaskReviewLoadMethod[key], message])
-)
+ _map(messages, (message, key) => [TaskReviewLoadMethod[key], message]),
+);
diff --git a/src/services/Task/TaskReview/TaskReviewLoadMethod.test.js b/src/services/Task/TaskReview/TaskReviewLoadMethod.test.js
index 9f37d5667..2f53cfc5c 100644
--- a/src/services/Task/TaskReview/TaskReviewLoadMethod.test.js
+++ b/src/services/Task/TaskReview/TaskReviewLoadMethod.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect } from "vitest";
+import { describe, expect, it } from "vitest";
import { messagesByReviewLoadMethod } from "./TaskReviewLoadMethod";
describe("messagesByReviewLoadMethod", () => {
diff --git a/src/services/Task/TaskReview/TaskReviewNeeded.js b/src/services/Task/TaskReview/TaskReviewNeeded.js
index 96d1a623c..c420b0383 100644
--- a/src/services/Task/TaskReview/TaskReviewNeeded.js
+++ b/src/services/Task/TaskReview/TaskReviewNeeded.js
@@ -1,70 +1,74 @@
-import _snakeCase from 'lodash/snakeCase'
-import _map from 'lodash/map'
-import { defaultRoutes as api } from '../../Server/Server'
-import Endpoint from '../../Server/Endpoint'
-import RequestStatus from '../../Server/RequestStatus'
-import { generateSearchParametersString } from '../../Search/Search'
-import { taskSchema } from '.././Task'
-import { addError } from '../../Error/Error'
-import AppErrors from '../../Error/AppErrors'
+import _map from "lodash/map";
+import _snakeCase from "lodash/snakeCase";
+import AppErrors from "../../Error/AppErrors";
+import { addError } from "../../Error/Error";
+import { generateSearchParametersString } from "../../Search/Search";
+import Endpoint from "../../Server/Endpoint";
+import RequestStatus from "../../Server/RequestStatus";
+import { defaultRoutes as api } from "../../Server/Server";
+import { taskSchema } from ".././Task";
// redux actions
-export const RECEIVE_REVIEW_NEEDED_TASKS = 'RECEIVE_REVIEW_NEEDED_TASKS'
+export const RECEIVE_REVIEW_NEEDED_TASKS = "RECEIVE_REVIEW_NEEDED_TASKS";
// redux action creators
/**
* Add or replace the review needed tasks in the redux store
*/
-export const receiveReviewNeededTasks = function(tasks,
- status=RequestStatus.success,
- totalCount) {
+export const receiveReviewNeededTasks = function (
+ tasks,
+ status = RequestStatus.success,
+ totalCount,
+) {
return {
type: RECEIVE_REVIEW_NEEDED_TASKS,
status,
tasks,
totalCount,
receivedAt: Date.now(),
- }
-}
+ };
+};
// async action creators
/**
* Retrieve all tasks (up to the given limit) that need to be reviewed
*/
-export const fetchReviewNeededTasks = function(criteria, limit=50) {
- const sortBy = criteria?.sortCriteria?.sortBy
- const order = ((criteria?.sortCriteria?.direction) || 'DESC').toUpperCase()
- const sort = sortBy ? _snakeCase(sortBy) : null
- const page = criteria?.page ?? 0
- const searchParameters = generateSearchParametersString(criteria?.filters ?? {},
- criteria.boundingBox,
- criteria?.savedChallengesOnly,
- criteria?.excludeOtherReviewers,
- null,
- criteria?.invertFields ?? {})
- const includeTags = criteria.includeTags
+export const fetchReviewNeededTasks = function (criteria, limit = 50) {
+ const sortBy = criteria?.sortCriteria?.sortBy;
+ const order = (criteria?.sortCriteria?.direction || "DESC").toUpperCase();
+ const sort = sortBy ? _snakeCase(sortBy) : null;
+ const page = criteria?.page ?? 0;
+ const searchParameters = generateSearchParametersString(
+ criteria?.filters ?? {},
+ criteria.boundingBox,
+ criteria?.savedChallengesOnly,
+ criteria?.excludeOtherReviewers,
+ null,
+ criteria?.invertFields ?? {},
+ );
+ const includeTags = criteria.includeTags;
- return function(dispatch) {
- return new Endpoint(
- api.tasks.review,
- {
- schema: {tasks: [taskSchema()]},
- variables: {},
- params: {limit, sort, order, page, ...searchParameters,
- includeTags},
- }
- ).execute().then(normalizedResults => {
- const unsortedTaskMap = normalizedResults?.entities?.tasks ?? {}
- const tasks = _map(normalizedResults.result.tasks, (id) => unsortedTaskMap[id])
- dispatch(receiveReviewNeededTasks(tasks, RequestStatus.success,
- normalizedResults.result.total))
- return tasks
- }).catch((error) => {
- dispatch(receiveReviewNeededTasks([], RequestStatus.error))
- dispatch(addError(AppErrors.reviewTask.fetchFailure))
- console.log(error.response || error)
- });
+ return function (dispatch) {
+ return new Endpoint(api.tasks.review, {
+ schema: { tasks: [taskSchema()] },
+ variables: {},
+ params: { limit, sort, order, page, ...searchParameters, includeTags },
+ })
+ .execute()
+ .then((normalizedResults) => {
+ const unsortedTaskMap = normalizedResults?.entities?.tasks ?? {};
+ const tasks = _map(normalizedResults.result.tasks, (id) => unsortedTaskMap[id]);
+ dispatch(
+ receiveReviewNeededTasks(tasks, RequestStatus.success, normalizedResults.result.total),
+ );
+ return tasks;
+ })
+ .catch((error) => {
+ dispatch(receiveReviewNeededTasks([], RequestStatus.error));
+ dispatch(addError(AppErrors.reviewTask.fetchFailure));
+ console.log(error.response || error);
+ });
};
-}
+};
diff --git a/src/services/Task/TaskReview/TaskReviewStatus.js b/src/services/Task/TaskReview/TaskReviewStatus.js
index 899125a95..f82b10ec3 100644
--- a/src/services/Task/TaskReview/TaskReviewStatus.js
+++ b/src/services/Task/TaskReview/TaskReviewStatus.js
@@ -1,20 +1,20 @@
-import _map from 'lodash/map'
-import _invert from 'lodash/invert'
-import _fromPairs from 'lodash/fromPairs'
-import messages from './Messages'
+import _fromPairs from "lodash/fromPairs";
+import _invert from "lodash/invert";
+import _map from "lodash/map";
+import messages from "./Messages";
// These statuses are defined on the server
-export const REVIEW_STATUS_NEEDED = 0
-export const REVIEW_STATUS_APPROVED = 1
-export const REVIEW_STATUS_REJECTED = 2
-export const REVIEW_STATUS_APPROVED_WITH_FIXES = 3
-export const REVIEW_STATUS_DISPUTED = 4
-export const REVIEW_STATUS_UNNECESSARY = 5
-export const REVIEW_STATUS_APPROVED_WITH_REVISIONS = 6
-export const REVIEW_STATUS_APPROVED_WITH_FIXES_AFTER_REVISIONS = 7
+export const REVIEW_STATUS_NEEDED = 0;
+export const REVIEW_STATUS_APPROVED = 1;
+export const REVIEW_STATUS_REJECTED = 2;
+export const REVIEW_STATUS_APPROVED_WITH_FIXES = 3;
+export const REVIEW_STATUS_DISPUTED = 4;
+export const REVIEW_STATUS_UNNECESSARY = 5;
+export const REVIEW_STATUS_APPROVED_WITH_REVISIONS = 6;
+export const REVIEW_STATUS_APPROVED_WITH_FIXES_AFTER_REVISIONS = 7;
-export const REVIEW_STATUS_NOT_SET = -1
-export const META_REVIEW_STATUS_NOT_SET = -2
+export const REVIEW_STATUS_NOT_SET = -1;
+export const META_REVIEW_STATUS_NOT_SET = -2;
export const TaskReviewStatus = Object.freeze({
needed: REVIEW_STATUS_NEEDED,
@@ -24,8 +24,8 @@ export const TaskReviewStatus = Object.freeze({
disputed: REVIEW_STATUS_DISPUTED,
unnecessary: REVIEW_STATUS_UNNECESSARY,
approvedWithRevisions: REVIEW_STATUS_APPROVED_WITH_REVISIONS,
- approvedWithFixesAfterRevisions: REVIEW_STATUS_APPROVED_WITH_FIXES_AFTER_REVISIONS
-})
+ approvedWithFixesAfterRevisions: REVIEW_STATUS_APPROVED_WITH_FIXES_AFTER_REVISIONS,
+});
export const TaskReviewStatusWithUnset = Object.freeze({
needed: REVIEW_STATUS_NEEDED,
@@ -36,8 +36,8 @@ export const TaskReviewStatusWithUnset = Object.freeze({
unnecessary: REVIEW_STATUS_UNNECESSARY,
unset: REVIEW_STATUS_NOT_SET,
approvedWithRevisions: REVIEW_STATUS_APPROVED_WITH_REVISIONS,
- approvedWithFixesAfterRevisions: REVIEW_STATUS_APPROVED_WITH_FIXES_AFTER_REVISIONS
-})
+ approvedWithFixesAfterRevisions: REVIEW_STATUS_APPROVED_WITH_FIXES_AFTER_REVISIONS,
+});
export const TaskMetaReviewStatusWithUnset = Object.freeze({
metaNeeded: REVIEW_STATUS_NEEDED,
@@ -47,64 +47,65 @@ export const TaskMetaReviewStatusWithUnset = Object.freeze({
metaUnnecessary: REVIEW_STATUS_UNNECESSARY,
metaUnset: META_REVIEW_STATUS_NOT_SET,
approvedWithRevisions: REVIEW_STATUS_APPROVED_WITH_REVISIONS,
- approvedWithFixesAfterRevisions: REVIEW_STATUS_APPROVED_WITH_FIXES_AFTER_REVISIONS
-})
+ approvedWithFixesAfterRevisions: REVIEW_STATUS_APPROVED_WITH_FIXES_AFTER_REVISIONS,
+});
-export const keysByReviewStatus = Object.freeze(_invert(TaskReviewStatusWithUnset))
+export const keysByReviewStatus = Object.freeze(_invert(TaskReviewStatusWithUnset));
/**
* Returns true if the given status represents a status where review is needed.
*/
-export const isNeeded = function(status) {
- return status === TaskReviewStatus.needed
-}
+export const isNeeded = function (status) {
+ return status === TaskReviewStatus.needed;
+};
/**
* Returns true if the given status represents a status where review has been done
*/
-export const hasBeenReviewed = function(status) {
- return status !== TaskReviewStatus.needed
-}
+export const hasBeenReviewed = function (status) {
+ return status !== TaskReviewStatus.needed;
+};
/**
* Returns an object mapping status values to raw internationalized
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByReviewStatus = _fromPairs(
- _map(messages, (message, key) => [TaskReviewStatusWithUnset[key], message])
-)
+ _map(messages, (message, key) => [TaskReviewStatusWithUnset[key], message]),
+);
/**
* Returns an object mapping status values to raw internationalized
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByMetaReviewStatus = _fromPairs(
- _map(messages, (message, key) => [TaskMetaReviewStatusWithUnset[key], message])
-)
+ _map(messages, (message, key) => [TaskMetaReviewStatusWithUnset[key], message]),
+);
/** Returns object containing localized labels */
-export const reviewStatusLabels = intl => _fromPairs(
- _map(messages, (message, key) => [key, intl.formatMessage(message)])
-)
+export const reviewStatusLabels = (intl) =>
+ _fromPairs(_map(messages, (message, key) => [key, intl.formatMessage(message)]));
/**
* Returns true if the given status represents a status that is
* valid for reviewing. (ie. not skipped and not created)
*/
-export const isNeedsReviewStatus = function(status) {
- return status === TaskReviewStatus.needed || status === TaskReviewStatus.disputed
-}
+export const isNeedsReviewStatus = function (status) {
+ return status === TaskReviewStatus.needed || status === TaskReviewStatus.disputed;
+};
/**
* Returns true if the given status represents a status that is
* valid for meta-review.
*/
-export const isMetaReviewStatus = function(status) {
- return status === TaskReviewStatus.approved ||
- status === TaskReviewStatus.approvedWithRevisions ||
- status === TaskReviewStatus.approvedWithFixes ||
- status === TaskReviewStatus.approvedWithFixesAfterRevision ||
- status === TaskReviewStatus.rejected ||
- status === TaskReviewStatus.needed ||
- status === TaskReviewStatus.unnecessary
-}
+export const isMetaReviewStatus = function (status) {
+ return (
+ status === TaskReviewStatus.approved ||
+ status === TaskReviewStatus.approvedWithRevisions ||
+ status === TaskReviewStatus.approvedWithFixes ||
+ status === TaskReviewStatus.approvedWithFixesAfterRevision ||
+ status === TaskReviewStatus.rejected ||
+ status === TaskReviewStatus.needed ||
+ status === TaskReviewStatus.unnecessary
+ );
+};
diff --git a/src/services/Task/TaskReview/TaskReviewStatus.test.js b/src/services/Task/TaskReview/TaskReviewStatus.test.js
index 547ef7279..93f7d5bcb 100644
--- a/src/services/Task/TaskReview/TaskReviewStatus.test.js
+++ b/src/services/Task/TaskReview/TaskReviewStatus.test.js
@@ -1,8 +1,8 @@
-import { describe, it, expect } from "vitest";
+import { describe, expect, it } from "vitest";
import {
+ hasBeenReviewed,
isMetaReviewStatus,
isNeeded,
- hasBeenReviewed,
isNeedsReviewStatus,
} from "./TaskReviewStatus";
diff --git a/src/services/Task/TaskReview/TaskReviewed.js b/src/services/Task/TaskReview/TaskReviewed.js
index 8cba1a836..5abd7d547 100644
--- a/src/services/Task/TaskReview/TaskReviewed.js
+++ b/src/services/Task/TaskReview/TaskReviewed.js
@@ -1,34 +1,35 @@
-import { defaultRoutes as api } from '../../Server/Server'
-import Endpoint from '../../Server/Endpoint'
-import RequestStatus from '../../Server/RequestStatus'
-import { taskSchema } from '.././Task'
-import { addError } from '../../Error/Error'
-import AppErrors from '../../Error/AppErrors'
-import _map from 'lodash/map'
-import _snakeCase from 'lodash/snakeCase'
-import { generateSearchParametersString } from '../../Search/Search'
+import _map from "lodash/map";
+import _snakeCase from "lodash/snakeCase";
+import AppErrors from "../../Error/AppErrors";
+import { addError } from "../../Error/Error";
+import { generateSearchParametersString } from "../../Search/Search";
+import Endpoint from "../../Server/Endpoint";
+import RequestStatus from "../../Server/RequestStatus";
+import { defaultRoutes as api } from "../../Server/Server";
+import { taskSchema } from ".././Task";
// redux actions
-export const RECEIVE_REVIEWED_TASKS = 'RECEIVE_REVIEWED_TASKS'
-export const RECEIVE_MAPPER_REVIEWED_TASKS = 'RECEIVE_MAPPER_REVIEWED_TASKS'
-export const RECEIVE_REVIEWED_BY_USER_TASKS = 'RECEIVE_REVIEWED_BY_USER_TASKS'
-
+export const RECEIVE_REVIEWED_TASKS = "RECEIVE_REVIEWED_TASKS";
+export const RECEIVE_MAPPER_REVIEWED_TASKS = "RECEIVE_MAPPER_REVIEWED_TASKS";
+export const RECEIVE_REVIEWED_BY_USER_TASKS = "RECEIVE_REVIEWED_BY_USER_TASKS";
/**
* Add or replace the reviewed tasks in the redux store
*/
-export const receiveReviewedTasks = function(tasks,
- type,
- status=RequestStatus.success,
- totalCount) {
+export const receiveReviewedTasks = function (
+ tasks,
+ type,
+ status = RequestStatus.success,
+ totalCount,
+) {
return {
type: type,
status,
tasks,
totalCount,
receivedAt: Date.now(),
- }
-}
+ };
+};
// async action creators
@@ -36,56 +37,78 @@ export const receiveReviewedTasks = function(tasks,
* Retrieve all tasks (up to the given limit) that have been reviewed
* by user or requested by user
*/
-export const fetchReviewedTasks = function(userId, criteria, asReviewer=false,
- asMapper=false, asMetaReviewer=false, limit=50, asMetaReview=false) {
- const sortBy = criteria?.sortCriteria?.sortBy
- const order = ((criteria?.sortCriteria?.direction) || 'DESC').toUpperCase()
- const sort = sortBy ? _snakeCase(sortBy) : null
- const page = criteria?.page ?? 0
+export const fetchReviewedTasks = function (
+ userId,
+ criteria,
+ asReviewer = false,
+ asMapper = false,
+ asMetaReviewer = false,
+ limit = 50,
+ asMetaReview = false,
+) {
+ const sortBy = criteria?.sortCriteria?.sortBy;
+ const order = (criteria?.sortCriteria?.direction || "DESC").toUpperCase();
+ const sort = sortBy ? _snakeCase(sortBy) : null;
+ const page = criteria?.page ?? 0;
- const searchParameters =
- generateSearchParametersString(criteria?.filters ?? {},
- criteria.boundingBox,
- false, false, null,
- criteria?.invertFields ?? {})
- const mappers = asMapper ? [userId] : []
- const reviewers = asReviewer ? [userId] : []
- const metaReviewers = asMetaReviewer ? [userId] : []
+ const searchParameters = generateSearchParametersString(
+ criteria?.filters ?? {},
+ criteria.boundingBox,
+ false,
+ false,
+ null,
+ criteria?.invertFields ?? {},
+ );
+ const mappers = asMapper ? [userId] : [];
+ const reviewers = asReviewer ? [userId] : [];
+ const metaReviewers = asMetaReviewer ? [userId] : [];
- const includeTags = criteria.includeTags
+ const includeTags = criteria.includeTags;
- let dispatchType = RECEIVE_REVIEWED_TASKS
+ let dispatchType = RECEIVE_REVIEWED_TASKS;
if (asReviewer || asMetaReviewer) {
- dispatchType = RECEIVE_REVIEWED_BY_USER_TASKS
- }
- else if (asMapper) {
- dispatchType = RECEIVE_MAPPER_REVIEWED_TASKS
+ dispatchType = RECEIVE_REVIEWED_BY_USER_TASKS;
+ } else if (asMapper) {
+ dispatchType = RECEIVE_MAPPER_REVIEWED_TASKS;
}
- return function(dispatch) {
- dispatch(receiveReviewedTasks(null,
- dispatchType,
- RequestStatus.inProgress))
- return new Endpoint(
- api.tasks.reviewed,
- {
- schema: {tasks: [taskSchema()]},
- params: {users: mappers, reviewers, metaReviewers, limit, sort, order, page,
- ...searchParameters, includeTags, asMetaReview,
- allowReviewNeeded: (!asReviewer && !asMetaReviewer && !asMetaReview)},
- }
- ).execute().then(normalizedResults => {
- const unsortedTaskMap = normalizedResults?.entities?.tasks ?? {}
- const tasks = _map(normalizedResults.result.tasks, (id) => unsortedTaskMap[id])
+ return function (dispatch) {
+ dispatch(receiveReviewedTasks(null, dispatchType, RequestStatus.inProgress));
+ return new Endpoint(api.tasks.reviewed, {
+ schema: { tasks: [taskSchema()] },
+ params: {
+ users: mappers,
+ reviewers,
+ metaReviewers,
+ limit,
+ sort,
+ order,
+ page,
+ ...searchParameters,
+ includeTags,
+ asMetaReview,
+ allowReviewNeeded: !asReviewer && !asMetaReviewer && !asMetaReview,
+ },
+ })
+ .execute()
+ .then((normalizedResults) => {
+ const unsortedTaskMap = normalizedResults?.entities?.tasks ?? {};
+ const tasks = _map(normalizedResults.result.tasks, (id) => unsortedTaskMap[id]);
- dispatch(receiveReviewedTasks(tasks, dispatchType,
- RequestStatus.success, normalizedResults.result.total))
- return tasks
- }).catch((error) => {
- dispatch(receiveReviewedTasks([], dispatchType,
- RequestStatus.error))
- dispatch(addError(AppErrors.reviewTask.fetchFailure))
- console.log(error.response || error)
- });
+ dispatch(
+ receiveReviewedTasks(
+ tasks,
+ dispatchType,
+ RequestStatus.success,
+ normalizedResults.result.total,
+ ),
+ );
+ return tasks;
+ })
+ .catch((error) => {
+ dispatch(receiveReviewedTasks([], dispatchType, RequestStatus.error));
+ dispatch(addError(AppErrors.reviewTask.fetchFailure));
+ console.log(error.response || error);
+ });
};
-}
+};
diff --git a/src/services/Task/TaskStatus/Messages.js b/src/services/Task/TaskStatus/Messages.js
index d86f59d5f..bd136659f 100644
--- a/src/services/Task/TaskStatus/Messages.js
+++ b/src/services/Task/TaskStatus/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with TaskStatus.
@@ -6,34 +6,34 @@ import { defineMessages } from 'react-intl'
export default defineMessages({
created: {
id: "Task.status.created",
- defaultMessage: "Created"
+ defaultMessage: "Created",
},
fixed: {
id: "Task.status.fixed",
- defaultMessage: "Fixed"
+ defaultMessage: "Fixed",
},
falsePositive: {
id: "Task.status.falsePositive",
- defaultMessage: "Not an Issue"
+ defaultMessage: "Not an Issue",
},
skipped: {
id: "Task.status.skipped",
- defaultMessage: "Skipped"
+ defaultMessage: "Skipped",
},
deleted: {
id: "Task.status.deleted",
- defaultMessage: "Deleted"
+ defaultMessage: "Deleted",
},
disabled: {
id: "Task.status.disabled",
- defaultMessage: "Disabled"
+ defaultMessage: "Disabled",
},
alreadyFixed: {
id: "Task.status.alreadyFixed",
- defaultMessage: "Already Fixed"
+ defaultMessage: "Already Fixed",
},
tooHard: {
id: "Task.status.tooHard",
- defaultMessage: "Can't Complete"
+ defaultMessage: "Can't Complete",
},
-})
+});
diff --git a/src/services/Task/TaskStatus/TaskStatus.js b/src/services/Task/TaskStatus/TaskStatus.js
index 26a8c1abc..f457a6c98 100644
--- a/src/services/Task/TaskStatus/TaskStatus.js
+++ b/src/services/Task/TaskStatus/TaskStatus.js
@@ -1,22 +1,22 @@
-import _map from 'lodash/map'
-import _invert from 'lodash/invert'
-import _fromPairs from 'lodash/fromPairs'
-import _startCase from 'lodash/startCase'
-import resolveConfig from 'tailwindcss/resolveConfig'
-import tailwindConfig from '../../../tailwind.config.js'
-import messages from './Messages'
+import _fromPairs from "lodash/fromPairs";
+import _invert from "lodash/invert";
+import _map from "lodash/map";
+import _startCase from "lodash/startCase";
+import resolveConfig from "tailwindcss/resolveConfig";
+import tailwindConfig from "../../../tailwind.config.js";
+import messages from "./Messages";
-const colors = resolveConfig(tailwindConfig).theme.colors
+const colors = resolveConfig(tailwindConfig).theme.colors;
// These statuses are defined on the server
-export const TASK_STATUS_CREATED = 0
-export const TASK_STATUS_FIXED = 1
-export const TASK_STATUS_FALSE_POSITIVE = 2
-export const TASK_STATUS_SKIPPED = 3
-export const TASK_STATUS_DELETED = 4
-export const TASK_STATUS_ALREADY_FIXED = 5
-export const TASK_STATUS_TOO_HARD = 6
-export const TASK_STATUS_DISABLED = 9
+export const TASK_STATUS_CREATED = 0;
+export const TASK_STATUS_FIXED = 1;
+export const TASK_STATUS_FALSE_POSITIVE = 2;
+export const TASK_STATUS_SKIPPED = 3;
+export const TASK_STATUS_DELETED = 4;
+export const TASK_STATUS_ALREADY_FIXED = 5;
+export const TASK_STATUS_TOO_HARD = 6;
+export const TASK_STATUS_DISABLED = 9;
export const TaskStatus = Object.freeze({
created: TASK_STATUS_CREATED,
@@ -27,20 +27,20 @@ export const TaskStatus = Object.freeze({
alreadyFixed: TASK_STATUS_ALREADY_FIXED,
tooHard: TASK_STATUS_TOO_HARD,
disabled: TASK_STATUS_DISABLED,
-})
+});
-export const keysByStatus = Object.freeze(_invert(TaskStatus))
+export const keysByStatus = Object.freeze(_invert(TaskStatus));
export const TaskStatusColors = Object.freeze({
- [TaskStatus.fixed]: colors['blue-viking'],
- [TaskStatus.alreadyFixed]: colors['yellow-sand'],
- [TaskStatus.falsePositive]: colors['mango'],
- [TaskStatus.skipped]: colors['pink'],
- [TaskStatus.tooHard]: colors['red-light'],
- [TaskStatus.created]: colors['purple'],
- [TaskStatus.disabled]: colors['wild-strawberry'],
- [TaskStatus.deleted]: colors['grey'],
-})
+ [TaskStatus.fixed]: colors["blue-viking"],
+ [TaskStatus.alreadyFixed]: colors["yellow-sand"],
+ [TaskStatus.falsePositive]: colors["mango"],
+ [TaskStatus.skipped]: colors["pink"],
+ [TaskStatus.tooHard]: colors["red-light"],
+ [TaskStatus.created]: colors["purple"],
+ [TaskStatus.disabled]: colors["wild-strawberry"],
+ [TaskStatus.deleted]: colors["grey"],
+});
/**
* Returns a Set of status progressions that are allowed
@@ -52,57 +52,73 @@ export const TaskStatusColors = Object.freeze({
*
* @returns a Set of allowed status progressions
*/
-export const allowedStatusProgressions = function(status, includeSelf = false, forRevision = false) {
- let progressions = null
+export const allowedStatusProgressions = function (
+ status,
+ includeSelf = false,
+ forRevision = false,
+) {
+ let progressions = null;
if (forRevision) {
- progressions = new Set([TaskStatus.fixed, TaskStatus.falsePositive,
- TaskStatus.alreadyFixed, TaskStatus.tooHard])
+ progressions = new Set([
+ TaskStatus.fixed,
+ TaskStatus.falsePositive,
+ TaskStatus.alreadyFixed,
+ TaskStatus.tooHard,
+ ]);
if (!includeSelf) {
- progressions.delete(status)
+ progressions.delete(status);
}
- return progressions
+ return progressions;
}
- switch(status) {
+ switch (status) {
case TaskStatus.created:
- progressions = new Set([TaskStatus.fixed, TaskStatus.falsePositive,
- TaskStatus.skipped, TaskStatus.deleted,
- TaskStatus.alreadyFixed, TaskStatus.tooHard,
- TaskStatus.disabled])
- break
+ progressions = new Set([
+ TaskStatus.fixed,
+ TaskStatus.falsePositive,
+ TaskStatus.skipped,
+ TaskStatus.deleted,
+ TaskStatus.alreadyFixed,
+ TaskStatus.tooHard,
+ TaskStatus.disabled,
+ ]);
+ break;
case TaskStatus.fixed:
- progressions = new Set()
- break
+ progressions = new Set();
+ break;
case TaskStatus.falsePositive:
- progressions = new Set([TaskStatus.fixed])
- break
+ progressions = new Set([TaskStatus.fixed]);
+ break;
case TaskStatus.skipped:
case TaskStatus.tooHard:
- progressions = new Set([TaskStatus.fixed, TaskStatus.falsePositive,
- TaskStatus.skipped, TaskStatus.alreadyFixed,
- TaskStatus.tooHard])
- break
+ progressions = new Set([
+ TaskStatus.fixed,
+ TaskStatus.falsePositive,
+ TaskStatus.skipped,
+ TaskStatus.alreadyFixed,
+ TaskStatus.tooHard,
+ ]);
+ break;
case TaskStatus.deleted:
- progressions = new Set([TaskStatus.created, TaskStatus.disabled])
- break
+ progressions = new Set([TaskStatus.created, TaskStatus.disabled]);
+ break;
case TaskStatus.disabled:
- progressions = new Set([TaskStatus.created, TaskStatus.deleted])
- break
+ progressions = new Set([TaskStatus.created, TaskStatus.deleted]);
+ break;
case TaskStatus.alreadyFixed:
- progressions = new Set()
- break
+ progressions = new Set();
+ break;
default:
- throw new Error("unrecognized-task-status",
- `Unrecognized task status ${status}`)
+ throw new Error("unrecognized-task-status", `Unrecognized task status ${status}`);
}
if (includeSelf) {
- progressions.add(status)
+ progressions.add(status);
}
- return progressions
-}
+ return progressions;
+};
/**
* Returns true if the given status represents a final progression.
@@ -110,50 +126,53 @@ export const allowedStatusProgressions = function(status, includeSelf = false, f
* different status as a correction (falsePositive -> fixed), but for most
* purposes falsePositive should be treated as final.
*/
-export const isFinalStatus = function(status) {
- return status === TaskStatus.fixed ||
- status === TaskStatus.alreadyFixed ||
- status === TaskStatus.falsePositive
-}
+export const isFinalStatus = function (status) {
+ return (
+ status === TaskStatus.fixed ||
+ status === TaskStatus.alreadyFixed ||
+ status === TaskStatus.falsePositive
+ );
+};
/**
* Returns true if the given status represents a completion status (i.e., it
* isn't created or deleted)
*/
-export const isCompletionStatus = function(status) {
- return status !== TaskStatus.created &&
- status !== TaskStatus.deleted &&
- status !== TaskStatus.disabled
-}
+export const isCompletionStatus = function (status) {
+ return (
+ status !== TaskStatus.created && status !== TaskStatus.deleted && status !== TaskStatus.disabled
+ );
+};
/**
* Returns true if the given status represents a status that is
* valid for reviewing. (ie. not skipped and not created)
*/
-export const isReviewableStatus = function(status) {
- return status !== TaskStatus.created &&
- status !== TaskStatus.skipped &&
- status !== TaskStatus.deleted &&
- status !== TaskStatus.disabled
-}
+export const isReviewableStatus = function (status) {
+ return (
+ status !== TaskStatus.created &&
+ status !== TaskStatus.skipped &&
+ status !== TaskStatus.deleted &&
+ status !== TaskStatus.disabled
+ );
+};
/**
* Returns a "machine name" for the status, which is start-cased and snake-cased
* (e.g., "Already_Fixed" or "Created").
*/
-export const statusMachineName = function(status) {
- return _startCase(keysByStatus[status]).replace(/\s+/, '_')
-}
+export const statusMachineName = function (status) {
+ return _startCase(keysByStatus[status]).replace(/\s+/, "_");
+};
/**
* Returns an object mapping status values to raw internationalized
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByStatus = _fromPairs(
- _map(messages, (message, key) => [TaskStatus[key], message])
-)
+ _map(messages, (message, key) => [TaskStatus[key], message]),
+);
/** Returns object containing localized labels */
-export const statusLabels = intl => _fromPairs(
- _map(messages, (message, key) => [key, intl.formatMessage(message)])
-)
+export const statusLabels = (intl) =>
+ _fromPairs(_map(messages, (message, key) => [key, intl.formatMessage(message)]));
diff --git a/src/services/Task/TaskStatus/TaskStatus.test.js b/src/services/Task/TaskStatus/TaskStatus.test.js
index b294a69bd..b8359bf27 100644
--- a/src/services/Task/TaskStatus/TaskStatus.test.js
+++ b/src/services/Task/TaskStatus/TaskStatus.test.js
@@ -1,5 +1,5 @@
-import { describe, it, expect } from "vitest";
-import { messagesByStatus, isReviewableStatus } from "./TaskStatus";
+import { describe, expect, it } from "vitest";
+import { isReviewableStatus, messagesByStatus } from "./TaskStatus";
describe("isMetaReviewStatus", () => {
it("returns specific object when ran", () => {
diff --git a/src/services/Team/Messages.js b/src/services/Team/Messages.js
index ac4994461..830023432 100644
--- a/src/services/Team/Messages.js
+++ b/src/services/Team/Messages.js
@@ -1,4 +1,4 @@
-import { defineMessages } from 'react-intl'
+import { defineMessages } from "react-intl";
/**
* Internationalized messages for use with Team Status
@@ -12,4 +12,4 @@ export default defineMessages({
id: "Team.Status.invited",
defaultMessage: "Invited",
},
-})
+});
diff --git a/src/services/Team/Status.js b/src/services/Team/Status.js
index a0ba73b84..df27a1259 100644
--- a/src/services/Team/Status.js
+++ b/src/services/Team/Status.js
@@ -1,25 +1,24 @@
-import _map from 'lodash/map'
-import _fromPairs from 'lodash/fromPairs'
-import messages from './Messages'
+import _fromPairs from "lodash/fromPairs";
+import _map from "lodash/map";
+import messages from "./Messages";
// These constants are defined on the server
-export const STATUS_MEMBER = 0
-export const STATUS_INVITED = 1
+export const STATUS_MEMBER = 0;
+export const STATUS_INVITED = 1;
export const TeamStatus = Object.freeze({
member: STATUS_MEMBER,
invited: STATUS_INVITED,
-})
+});
/**
* Returns an object mapping team status values to raw internationalized
* messages suitable for use with FormattedMessage or formatMessage
*/
export const messagesByTeamStatus = _fromPairs(
- _map(messages, (message, key) => [TeamStatus[key], message])
-)
+ _map(messages, (message, key) => [TeamStatus[key], message]),
+);
/** Returns object containing localized labels */
-export const teamStatusLabels = intl => _fromPairs(
- _map(messages, (message, key) => [key, intl.formatMessage(message)])
-)
+export const teamStatusLabels = (intl) =>
+ _fromPairs(_map(messages, (message, key) => [key, intl.formatMessage(message)]));
diff --git a/src/services/Team/Team.js b/src/services/Team/Team.js
index d5a3f1cff..c8b72a0c0 100644
--- a/src/services/Team/Team.js
+++ b/src/services/Team/Team.js
@@ -1,51 +1,48 @@
-import { defaultRoutes as api, websocketClient } from '../Server/Server'
-import Endpoint from '../Server/Endpoint'
-import { fetchProjectManagers } from '../Project/Project'
+import { fetchProjectManagers } from "../Project/Project";
+import Endpoint from "../Server/Endpoint";
+import { defaultRoutes as api, websocketClient } from "../Server/Server";
-export const subscribeToTeamUpdates = function(callback, handle) {
- websocketClient.addServerSubscription(
- "teams",
- null,
- handle,
- messageObject => callback(messageObject)
- )
-}
+export const subscribeToTeamUpdates = function (callback, handle) {
+ websocketClient.addServerSubscription("teams", null, handle, (messageObject) =>
+ callback(messageObject),
+ );
+};
-export const unsubscribeFromTeamUpdates = function(handle) {
- websocketClient.removeServerSubscription("teams", null, handle)
-}
+export const unsubscribeFromTeamUpdates = function (handle) {
+ websocketClient.removeServerSubscription("teams", null, handle);
+};
// async action creators
/**
* Search for teams by name. Resolves with a (possibly empty) list of results
*/
-export const findTeam = function(teamName) {
- return new Endpoint(api.teams.find, {params: {name: teamName}}).execute()
-}
+export const findTeam = function (teamName) {
+ return new Endpoint(api.teams.find, { params: { name: teamName } }).execute();
+};
/**
* Set a team's granted role on a project
*/
-export const setTeamProjectRole = function(projectId, teamId, role) {
- return function(dispatch) {
+export const setTeamProjectRole = function (projectId, teamId, role) {
+ return function (dispatch) {
return new Endpoint(api.team.setProjectRole, {
- variables: {teamId, projectId, role},
- }).execute().then(() =>
- fetchProjectManagers(projectId)(dispatch)
- )
- }
-}
+ variables: { teamId, projectId, role },
+ })
+ .execute()
+ .then(() => fetchProjectManagers(projectId)(dispatch));
+ };
+};
/**
* Set a team's granted role on a project
*/
-export const removeTeamFromProject = function(projectId, teamId) {
- return function(dispatch) {
+export const removeTeamFromProject = function (projectId, teamId) {
+ return function (dispatch) {
return new Endpoint(api.team.removeFromProject, {
- variables: {teamId, projectId},
- }).execute().then(() =>
- fetchProjectManagers(projectId)(dispatch)
- )
- }
-}
+ variables: { teamId, projectId },
+ })
+ .execute()
+ .then(() => fetchProjectManagers(projectId)(dispatch));
+ };
+};
diff --git a/src/services/Templating/Handlers/CheckboxFormHandler.jsx b/src/services/Templating/Handlers/CheckboxFormHandler.jsx
index fe802718c..0ab56f6bc 100644
--- a/src/services/Templating/Handlers/CheckboxFormHandler.jsx
+++ b/src/services/Templating/Handlers/CheckboxFormHandler.jsx
@@ -1,34 +1,36 @@
-import { Fragment, useEffect } from 'react'
+import { Fragment, useEffect } from "react";
/**
* Expands checkbox shortcode containing label and name to a checkbox form
* input, e.g. `[checkbox "some text" name="propertyName"]`
*/
const CheckboxFormHandler = {
- checkboxRegex: "checkbox[/ ]?\"([^\"]+)\"\\s+name=\"([^\"]+)\"",
+ checkboxRegex: 'checkbox[/ ]?"([^"]+)"\\s+name="([^"]+)"',
handlesShortCode(shortCode, props) {
- return props.allowFormFields && new RegExp(this.checkboxRegex).test(shortCode)
+ return props.allowFormFields && new RegExp(this.checkboxRegex).test(shortCode);
},
expandShortCode(shortCode, props) {
- const match = new RegExp(this.checkboxRegex).exec(shortCode)
- return match ?
- :
+ const match = new RegExp(this.checkboxRegex).exec(shortCode);
+ return match ? (
+
+ ) : (
shortCode
+ );
},
-}
+};
-const CheckboxFormField = props => {
+const CheckboxFormField = (props) => {
// Record that a response from mapper is desired
- const { setNeedsResponses } = props
+ const { setNeedsResponses } = props;
useEffect(() => {
if (setNeedsResponses) {
- setNeedsResponses(true)
+ setNeedsResponses(true);
}
- }, [setNeedsResponses])
+ }, [setNeedsResponses]);
- const isChecked = Object.assign({}, props.completionResponses)[props.propertyName]
+ const isChecked = Object.assign({}, props.completionResponses)[props.propertyName];
return (
{
disabled={props.disableTemplate}
onChange={() => props.setCompletionResponse(props.propertyName, !isChecked)}
/>
- {props.label}
+
+ {props.label}
+
);
-}
+};
-export default CheckboxFormHandler
+export default CheckboxFormHandler;
diff --git a/src/services/Templating/Handlers/CopyableTextHandler.jsx b/src/services/Templating/Handlers/CopyableTextHandler.jsx
index 8204dc1f8..1e95c0965 100644
--- a/src/services/Templating/Handlers/CopyableTextHandler.jsx
+++ b/src/services/Templating/Handlers/CopyableTextHandler.jsx
@@ -1,41 +1,38 @@
-import { Fragment } from 'react'
-import { CopyToClipboard } from 'react-copy-to-clipboard'
-import _isEmpty from 'lodash/isEmpty'
-import SvgSymbol from '../../../components/SvgSymbol/SvgSymbol'
+import _isEmpty from "lodash/isEmpty";
+import { Fragment } from "react";
+import { CopyToClipboard } from "react-copy-to-clipboard";
+import SvgSymbol from "../../../components/SvgSymbol/SvgSymbol";
/**
* Expands copyable shortcode to inject CopyToClipboard component to make text
* easily copyable to clipboard by user, e.g. `[copyable "some useful text"]`
*/
const CopyableTextHandler = {
- copyableRegex: "copyable[/ ]?\"([^\"]*)\"",
+ copyableRegex: 'copyable[/ ]?"([^"]*)"',
handlesShortCode(shortCode) {
- return new RegExp(this.copyableRegex).test(shortCode)
+ return new RegExp(this.copyableRegex).test(shortCode);
},
expandShortCode(shortCode) {
- const match = new RegExp(this.copyableRegex).exec(shortCode)
+ const match = new RegExp(this.copyableRegex).exec(shortCode);
if (!match) {
- return shortCode
+ return shortCode;
}
// Don't offer clipboard control for empty string
if (_isEmpty(match[1])) {
- return null
+ return null;
}
return (
{match[1]}
-
+
@@ -43,6 +40,6 @@ const CopyableTextHandler = {
);
},
-}
+};
-export default CopyableTextHandler
+export default CopyableTextHandler;
diff --git a/src/services/Templating/Handlers/OSMElementHandler.jsx b/src/services/Templating/Handlers/OSMElementHandler.jsx
index fb930d868..e095906e9 100644
--- a/src/services/Templating/Handlers/OSMElementHandler.jsx
+++ b/src/services/Templating/Handlers/OSMElementHandler.jsx
@@ -1,6 +1,4 @@
-import OSMElementReference
- from '../../../components/OSMElementReference/OSMElementReference'
-
+import OSMElementReference from "../../../components/OSMElementReference/OSMElementReference";
/**
* Expands OSM element reference shortcodes to nodes, ways, and references,
@@ -17,36 +15,38 @@ const OSMElementHandler = {
osmElementRegex: "(n|w|r|node|way|rel|relation)[/ ]?(\\d+)[,\\s]*",
elementTypeMap: {
- 'n': 'node',
- 'node': 'node',
- 'w': 'way',
- 'way': 'way',
- 'r': 'relation',
- 'rel': 'relation',
- 'relation': 'relation',
+ n: "node",
+ node: "node",
+ w: "way",
+ way: "way",
+ r: "relation",
+ rel: "relation",
+ relation: "relation",
},
handlesShortCode(shortCode) {
// Add opening short-code bracket to test to help prevent false positives
- return new RegExp("\\[" + this.osmElementRegex).test(shortCode)
+ return new RegExp("\\[" + this.osmElementRegex).test(shortCode);
},
expandShortCode(shortCode) {
- const matchedElements = []
+ const matchedElements = [];
- const regex = RegExp(this.osmElementRegex, 'g')
- let osmElementMatch = null
+ const regex = RegExp(this.osmElementRegex, "g");
+ let osmElementMatch = null;
while ((osmElementMatch = regex.exec(shortCode.slice(1, -1)))) {
matchedElements.push({
elementType: this.elementTypeMap[osmElementMatch[1]],
osmId: osmElementMatch[2],
- })
+ });
}
- return matchedElements.length > 0 ?
- :
- shortCode
- }
-}
+ return matchedElements.length > 0 ? (
+
+ ) : (
+ shortCode
+ );
+ },
+};
-export default OSMElementHandler
+export default OSMElementHandler;
diff --git a/src/services/Templating/Handlers/OSMViewportHandler.jsx b/src/services/Templating/Handlers/OSMViewportHandler.jsx
index f6987e166..ac97dfa38 100644
--- a/src/services/Templating/Handlers/OSMViewportHandler.jsx
+++ b/src/services/Templating/Handlers/OSMViewportHandler.jsx
@@ -1,6 +1,4 @@
-import OSMViewportReference
- from '../../../components/OSMViewportReference/OSMViewportReference'
-
+import OSMViewportReference from "../../../components/OSMViewportReference/OSMViewportReference";
/**
* Expands viewport shortcodes containing zoom/lat/lon to links to
@@ -13,33 +11,35 @@ import OSMViewportReference
const OSMViewportHandler = {
osmViewportRegex: "(v|viewport)[/ ]?(\\d+)\\/(-?[\\.\\d]+)\\/(-?[\\.\\d]+)",
- osmMapRegex: "https?://(www.openstreetmap.org)/?#map=(\\d+)\\/(-?[\\.\\d]+)\\/(-?[\\.\\d]+)",
+ osmMapRegex: "https?://(www\\.openstreetmap\\.org)/?#map=(\\d+)\\/(-?[\\.\\d]+)\\/(-?[\\.\\d]+)",
handlesShortCode(shortCode) {
- return new RegExp(this.osmViewportRegex).test(shortCode) ||
- new RegExp(this.osmMapRegex).test(shortCode)
+ return (
+ new RegExp(this.osmViewportRegex).test(shortCode) ||
+ new RegExp(this.osmMapRegex).test(shortCode)
+ );
},
expandShortCode(shortCode) {
- const viewport = new RegExp(this.osmViewportRegex).test(shortCode) ?
- this.extractViewport(new RegExp(this.osmViewportRegex), shortCode) :
- this.extractViewport(new RegExp(this.osmMapRegex), shortCode)
+ const viewport = new RegExp(this.osmViewportRegex).test(shortCode)
+ ? this.extractViewport(new RegExp(this.osmViewportRegex), shortCode)
+ : this.extractViewport(new RegExp(this.osmMapRegex), shortCode);
- return viewport ? : shortCode
+ return viewport ? : shortCode;
},
extractViewport(regex, shortCode) {
- const match = regex.exec(shortCode.slice(1, -1))
+ const match = regex.exec(shortCode.slice(1, -1));
if (!match) {
- return null
+ return null;
}
return {
zoom: match[2],
lat: match[3],
lon: match[4],
- }
+ };
},
-}
+};
-export default OSMViewportHandler
+export default OSMViewportHandler;
diff --git a/src/services/Templating/Handlers/SelectFormHandler.jsx b/src/services/Templating/Handlers/SelectFormHandler.jsx
index 9c2e725b7..c36b645a8 100644
--- a/src/services/Templating/Handlers/SelectFormHandler.jsx
+++ b/src/services/Templating/Handlers/SelectFormHandler.jsx
@@ -1,60 +1,63 @@
-import { Fragment, useEffect } from 'react'
-import _map from 'lodash/map'
+import _map from "lodash/map";
+import { Fragment, useEffect } from "react";
/**
* Expands select shortcode containing label and values to a select form
* input, e.g. `[select "some label" values="foo,bar,baz"]`
*/
const SelectFormHandler = {
- selectRegex: "select[/ ]?\"([^\"]+)\"\\s+name=\"([^\"]+)\"\\s+values=\"([^\"]+)\"",
+ selectRegex: 'select[/ ]?"([^"]+)"\\s+name="([^"]+)"\\s+values="([^"]+)"',
handlesShortCode(shortCode, props) {
- return props.allowFormFields && new RegExp(this.selectRegex).test(shortCode)
+ return props.allowFormFields && new RegExp(this.selectRegex).test(shortCode);
},
expandShortCode(shortCode, props) {
- const match = new RegExp(this.selectRegex).exec(shortCode)
+ const match = new RegExp(this.selectRegex).exec(shortCode);
if (!match) {
- return shortCode
+ return shortCode;
}
- const values = match[3].split(/,\s*/)
- return match ?
- :
+ const values = match[3].split(/,\s*/);
+ return match ? (
+
+ ) : (
shortCode
+ );
},
-}
+};
-const SelectFormField = props => {
+const SelectFormField = (props) => {
// Record that a response from mapper is desired
- const { setNeedsResponses } = props
+ const { setNeedsResponses } = props;
useEffect(() => {
if (setNeedsResponses) {
- setNeedsResponses(true)
+ setNeedsResponses(true);
}
- }, [setNeedsResponses])
+ }, [setNeedsResponses]);
- const currentValue = Object.assign({}, props.completionResponses)[props.propertyName]
+ const currentValue = Object.assign({}, props.completionResponses)[props.propertyName];
return (
props.setCompletionResponse(props.propertyName, e.target.value)}
+ id="select-label"
+ onChange={(e) => props.setCompletionResponse(props.propertyName, e.target.value)}
className="select mr-text-black mr-text-xs"
defaultValue={currentValue}
disabled={props.disableTemplate}
>
- {_map(props.values, (value, i) => {value} )}
+ {_map(props.values, (value, i) => (
+
+ {value}
+
+ ))}
- {props.label}
+
+ {props.label}
+
);
-}
+};
-export default SelectFormHandler
+export default SelectFormHandler;
diff --git a/src/services/Templating/Handlers/UserMentionHandler.jsx b/src/services/Templating/Handlers/UserMentionHandler.jsx
index 9e08cd885..3fdab7a13 100644
--- a/src/services/Templating/Handlers/UserMentionHandler.jsx
+++ b/src/services/Templating/Handlers/UserMentionHandler.jsx
@@ -1,4 +1,4 @@
-import { Link } from 'react-router-dom'
+import { Link } from "react-router-dom";
/**
* Expands user mentions, such as `[@username]`, into links to the mentioned
@@ -16,22 +16,21 @@ const OSMUserMentionHandler = {
* `[@foo]`
*/
normalizeContent(content) {
- return this.containsSimpleMention(content) ?
- content.replace(new RegExp(this.simpleMentionRegex, 'g'), "$1[$2]") :
- content
+ return this.containsSimpleMention(content)
+ ? content.replace(new RegExp(this.simpleMentionRegex, "g"), "$1[$2]")
+ : content;
},
handlesShortCode(shortCode) {
- return new RegExp(this.mentionRegex).test(shortCode)
+ return new RegExp(this.mentionRegex).test(shortCode);
},
expandShortCode(shortCode) {
if (new RegExp(this.mentionRegex).test(shortCode)) {
- const username = shortCode.slice(2, -1)
- return @{username}
- }
- else {
- return shortCode
+ const username = shortCode.slice(2, -1);
+ return @{username};
+ } else {
+ return shortCode;
}
},
@@ -40,8 +39,8 @@ const OSMUserMentionHandler = {
* mentions, excluding the full short-code form (i.e. `@foo` but not `[@foo]`)
*/
containsSimpleMention(content) {
- return new RegExp(this.simpleMentionRegex).test(content)
+ return new RegExp(this.simpleMentionRegex).test(content);
},
-}
+};
-export default OSMUserMentionHandler
+export default OSMUserMentionHandler;
diff --git a/src/services/Templating/Templating.jsx b/src/services/Templating/Templating.jsx
index c5c8aedc7..bccb4552d 100644
--- a/src/services/Templating/Templating.jsx
+++ b/src/services/Templating/Templating.jsx
@@ -1,18 +1,18 @@
-import { cloneElement } from 'react'
-import _find from 'lodash/find'
-import _compact from 'lodash/compact'
-import _isEmpty from 'lodash/isEmpty'
-import _map from 'lodash/map'
-import _each from 'lodash/each'
-import _isString from 'lodash/isString'
-import _uniqueId from 'lodash/uniqueId'
+import _compact from "lodash/compact";
+import _each from "lodash/each";
+import _find from "lodash/find";
+import _isEmpty from "lodash/isEmpty";
+import _isString from "lodash/isString";
+import _map from "lodash/map";
+import _uniqueId from "lodash/uniqueId";
+import { cloneElement } from "react";
-import OSMElementHandler from './Handlers/OSMElementHandler'
-import OSMViewportHandler from './Handlers/OSMViewportHandler'
-import UserMentionHandler from './Handlers/UserMentionHandler'
-import CheckboxFormHandler from './Handlers/CheckboxFormHandler'
-import SelectFormHandler from './Handlers/SelectFormHandler'
-import CopyableTextHandler from './Handlers/CopyableTextHandler'
+import CheckboxFormHandler from "./Handlers/CheckboxFormHandler";
+import CopyableTextHandler from "./Handlers/CopyableTextHandler";
+import OSMElementHandler from "./Handlers/OSMElementHandler";
+import OSMViewportHandler from "./Handlers/OSMViewportHandler";
+import SelectFormHandler from "./Handlers/SelectFormHandler";
+import UserMentionHandler from "./Handlers/UserMentionHandler";
// All available short-code handlers
const shortCodeHandlers = [
@@ -22,13 +22,13 @@ const shortCodeHandlers = [
CheckboxFormHandler,
SelectFormHandler,
CopyableTextHandler,
-]
+];
// Short codes are surrounded by brackets, but -- to avoid confusion with
// Markdown links -- cannot be immediately followed by an open parenthesees
// (hence the lookahead at the end). Alternatively, triple curly braces can be
// used
-const shortCodeRegex = /(\{\{\{[^}]+}}})|(\[[^\]]+\])(?=[^(]|$)/
+const shortCodeRegex = /(\{\{\{[^}]+}}})|(\[[^\]]+\])(?=[^(]|$)/;
/**
* Recursively runs through the given JSX element tree and expands any
@@ -36,91 +36,98 @@ const shortCodeRegex = /(\{\{\{[^}]+}}})|(\[[^\]]+\])(?=[^(]|$)/
* child content (not props), returning a cloned copy of the element tree with
* supported templating expanded
*/
-export const expandTemplatingInJSX = function(jsxNode, props) {
+export const expandTemplatingInJSX = function (jsxNode, props) {
return cloneElement(
jsxNode,
{},
- jsxNode.props.children ? _map(jsxNode.props.children, child => {
- if (_isString(child)) {
- // Let short-code handlers have an opportunity to normalize the content
- // prior to tokenization
- let content = child
- _each(shortCodeHandlers, handler => {
- if (handler.normalizeContent) {
- content = handler.normalizeContent(content, props)
- }
- })
+ jsxNode.props.children
+ ? _map(jsxNode.props.children, (child) => {
+ if (_isString(child)) {
+ // Let short-code handlers have an opportunity to normalize the content
+ // prior to tokenization
+ let content = child;
+ _each(shortCodeHandlers, (handler) => {
+ if (handler.normalizeContent) {
+ content = handler.normalizeContent(content, props);
+ }
+ });
- if (!containsShortCode(content)) {
- return child
- }
+ if (!containsShortCode(content)) {
+ return child;
+ }
- return _map(tokenize(content), token => (
- {expandedTokenContent(token, props)}
- ))
- }
- else {
- return expandTemplatingInJSX(child, props)
- }
- }) : jsxNode.props.children
+ return _map(tokenize(content), (token) => (
+ {expandedTokenContent(token, props)}
+ ));
+ } else {
+ return expandTemplatingInJSX(child, props);
+ }
+ })
+ : jsxNode.props.children,
);
-}
+};
/**
* Determines if the given string content contains one or more short-codes
*/
-export const containsShortCode = function(content) {
- return shortCodeRegex.test(content)
-}
+export const containsShortCode = function (content) {
+ return shortCodeRegex.test(content);
+};
/**
* Runs through the available short-code handlers to find one that is capable
* of handling the given short-code, asks that handler to expand the
* short-code, and returns the expanded results
*/
-export const expandShortCode = function(shortCode, props) {
- const targetHandler =
- _find(shortCodeHandlers, handler => handler.handlesShortCode(shortCode, props))
+export const expandShortCode = function (shortCode, props) {
+ const targetHandler = _find(shortCodeHandlers, (handler) =>
+ handler.handlesShortCode(shortCode, props),
+ );
if (!targetHandler) {
// Unsupported short code, just return it as-is
- return shortCode
+ return shortCode;
}
- return targetHandler.expandShortCode(shortCode, props)
-}
+ return targetHandler.expandShortCode(shortCode, props);
+};
/**
* Tokenizes string content into an array to separate out short-codes from the
* rest of the content. Each element is either content free of short-codes or a
* single short-code
*/
-export const tokenize = function(content) {
+export const tokenize = function (content) {
if (!content) {
- return []
+ return [];
}
- return _compact(_map(
- // Clear out any empty tokens
- content.split(shortCodeRegex), token => _isEmpty(token) ? null : token
- ))
-}
+ return _compact(
+ _map(
+ // Clear out any empty tokens
+ content.split(shortCodeRegex),
+ (token) => (_isEmpty(token) ? null : token),
+ ),
+ );
+};
/**
* Determines if the given token (from the tokenize function) represents a
* short-code. We check the regex and also make sure the string starts and ends
* with brackets or curly braces
*/
-export const isShortCodeToken = function(token) {
- return containsShortCode(token) &&
- ((token[0] === '[' && token[token.length - 1] === ']') ||
- (token[0] === '{' && token[token.length - 1] === '}'))
-}
+export const isShortCodeToken = function (token) {
+ return (
+ containsShortCode(token) &&
+ ((token[0] === "[" && token[token.length - 1] === "]") ||
+ (token[0] === "{" && token[token.length - 1] === "}"))
+ );
+};
/**
* Returns appropriate content for the given token, either an expanded short
* code if the token represents a short-code or the original content if not
*/
-export const expandedTokenContent = function(token, props) {
- return isShortCodeToken(token) ? expandShortCode(token, props) : token
-}
+export const expandedTokenContent = function (token, props) {
+ return isShortCodeToken(token) ? expandShortCode(token, props) : token;
+};
diff --git a/src/services/User/Achievement/Achievement.js b/src/services/User/Achievement/Achievement.js
index 41e48b1f3..72baa1640 100644
--- a/src/services/User/Achievement/Achievement.js
+++ b/src/services/User/Achievement/Achievement.js
@@ -21,5 +21,4 @@ export const Achievement = Object.freeze({
fixedFinalTask: 19,
fixedCoopTask: 20,
challengeCompleted: 21,
-})
-
+});
diff --git a/src/services/User/Locale/Locale.js b/src/services/User/Locale/Locale.js
index f7a0163b4..39d1f5d5a 100644
--- a/src/services/User/Locale/Locale.js
+++ b/src/services/User/Locale/Locale.js
@@ -3,6 +3,8 @@
// to bloat the main bundle with all strings for every language) but also that
// the argument to the import() function is a literal string (so that our bundler
// can analyze it and include those assets in the output dist/ directory).
+
+// biome-ignore format: don't unqoute keys
const LOCALE_LOADERS = {
"af": () => import("../../../../lang/af.json"),
"cs-CZ": () => import("../../../../lang/cs_CZ.json"),
@@ -33,6 +35,8 @@ export const SUPPORTED_LOCALES = Object.keys(LOCALE_LOADERS);
// are intentionally NOT localized, so that if a user is "stuck" in a language
// they don't understand, they'll still be able to recognize their own language
// in this list.
+
+// biome-ignore format: don't unqoute keys
export const LOCALE_NAMES = {
"af": "Afrikaans",
"cs-CZ": "Čeština",
diff --git a/src/services/User/User.js b/src/services/User/User.js
index d6fd7abf2..5997ee875 100644
--- a/src/services/User/User.js
+++ b/src/services/User/User.js
@@ -1,34 +1,35 @@
-import { schema } from 'normalizr'
-import _set from 'lodash/set'
-import _isFinite from 'lodash/isFinite'
-import _isObject from 'lodash/isObject'
-import _isArray from 'lodash/isArray'
-import _cloneDeep from 'lodash/cloneDeep'
-import _pull from 'lodash/pull'
-import _keys from 'lodash/keys'
-import _values from 'lodash/values'
-import _each from 'lodash/each'
-import _map from 'lodash/map'
-import _toPairs from 'lodash/toPairs'
-import _omit from 'lodash/omit'
-import _sortBy from 'lodash/sortBy'
-import _reverse from 'lodash/reverse'
-import { subMonths, startOfDay } from 'date-fns'
-import { defaultRoutes as api, isSecurityError, credentialsPolicy, websocketClient }
- from '../Server/Server'
-import { resetCache } from '../Server/RequestCache'
-import Endpoint from '../Server/Endpoint'
-import RequestStatus from '../Server/RequestStatus'
-import genericEntityReducer from '../Server/GenericEntityReducer'
-import { challengeSchema, receiveChallenges, fetchChallenges }
- from '../Challenge/Challenge'
-import { taskSchema,
- taskDenormalizationSchema,
- receiveTasks } from '../Task/Task'
-import { addError } from '../Error/Error'
-import AppErrors from '../Error/AppErrors'
-import { setupCustomCache } from '../../utils/setupCustomCache'
-import CommentType from '../Comment/CommentType'
+import { startOfDay, subMonths } from "date-fns";
+import _cloneDeep from "lodash/cloneDeep";
+import _each from "lodash/each";
+import _isArray from "lodash/isArray";
+import _isFinite from "lodash/isFinite";
+import _isObject from "lodash/isObject";
+import _keys from "lodash/keys";
+import _map from "lodash/map";
+import _omit from "lodash/omit";
+import _pull from "lodash/pull";
+import _reverse from "lodash/reverse";
+import _set from "lodash/set";
+import _sortBy from "lodash/sortBy";
+import _toPairs from "lodash/toPairs";
+import _values from "lodash/values";
+import { schema } from "normalizr";
+import { setupCustomCache } from "../../utils/setupCustomCache";
+import { challengeSchema, fetchChallenges, receiveChallenges } from "../Challenge/Challenge";
+import CommentType from "../Comment/CommentType";
+import AppErrors from "../Error/AppErrors";
+import { addError } from "../Error/Error";
+import Endpoint from "../Server/Endpoint";
+import genericEntityReducer from "../Server/GenericEntityReducer";
+import { resetCache } from "../Server/RequestCache";
+import RequestStatus from "../Server/RequestStatus";
+import {
+ defaultRoutes as api,
+ credentialsPolicy,
+ isSecurityError,
+ websocketClient,
+} from "../Server/Server";
+import { receiveTasks, taskDenormalizationSchema, taskSchema } from "../Task/Task";
// 60 minutes
const CACHE_TIME = 60 * 60 * 1000;
@@ -38,168 +39,166 @@ const USER_METRICS_CACHE = "userMetrics";
const userCache = setupCustomCache(CACHE_TIME);
// constants defined on the server
-export const GUEST_USER_ID = -998 // i.e., not logged in
+export const GUEST_USER_ID = -998; // i.e., not logged in
// constants defined on the Server
-export const REVIEW_NOT_NEEDED = 0
-export const REVIEW_NEEDED = 1
-export const REVIEW_MANDATORY = 2
+export const REVIEW_NOT_NEEDED = 0;
+export const REVIEW_NEEDED = 1;
+export const REVIEW_MANDATORY = 2;
-export const FOLLOWER_STATUS_FOLLOWING = 0
-export const FOLLOWER_STATUS_BLOCKED = 1
+export const FOLLOWER_STATUS_FOLLOWING = 0;
+export const FOLLOWER_STATUS_BLOCKED = 1;
export const needsReviewType = Object.freeze({
notNeeded: REVIEW_NOT_NEEDED,
needed: REVIEW_NEEDED,
mandatory: REVIEW_MANDATORY,
-})
+});
export const FollowerStatus = Object.freeze({
following: FOLLOWER_STATUS_FOLLOWING,
blocked: FOLLOWER_STATUS_BLOCKED,
-})
+});
/** normalizr schema for users */
-export const userSchema = function() {
- return new schema.Entity('users')
-}
+export const userSchema = function () {
+ return new schema.Entity("users");
+};
/**
* normalizr denormalization schema, which will also pull in saved challenges
* and tasks (fetched separately, so not used in normal schema)
*/
-export const userDenormalizationSchema = function() {
- return new schema.Entity('users', {
- savedChallenges: [ challengeSchema() ],
- topChallenges: [ challengeSchema() ],
- savedTasks: [ taskDenormalizationSchema() ],
- })
-}
+export const userDenormalizationSchema = function () {
+ return new schema.Entity("users", {
+ savedChallenges: [challengeSchema()],
+ topChallenges: [challengeSchema()],
+ savedTasks: [taskDenormalizationSchema()],
+ });
+};
-export const subscribeToUserUpdates = function(dispatch, userId) {
+export const subscribeToUserUpdates = function (dispatch, userId) {
websocketClient.addServerSubscription(
- "user", userId, `userUpdateHandler_${userId}`,
- messageObject => onUserUpdate(dispatch, userId, messageObject)
- )
-}
+ "user",
+ userId,
+ `userUpdateHandler_${userId}`,
+ (messageObject) => onUserUpdate(dispatch, userId, messageObject),
+ );
+};
-export const unsubscribeFromUserUpdates = function(userId) {
- websocketClient.removeServerSubscription("user", userId, `newNotificationHandler_${userId}`)
-}
+export const unsubscribeFromUserUpdates = function (userId) {
+ websocketClient.removeServerSubscription("user", userId, `newNotificationHandler_${userId}`);
+};
-export const subscribeToFollowUpdates = function(callback, handle) {
- websocketClient.addServerSubscription(
- "following",
- null,
- handle,
- messageObject => callback(messageObject)
- )
-}
+export const subscribeToFollowUpdates = function (callback, handle) {
+ websocketClient.addServerSubscription("following", null, handle, (messageObject) =>
+ callback(messageObject),
+ );
+};
-export const unsubscribeFromFollowUpdates = function(handle) {
- websocketClient.removeServerSubscription("following", null, handle)
-}
+export const unsubscribeFromFollowUpdates = function (handle) {
+ websocketClient.removeServerSubscription("following", null, handle);
+};
/**
* Process updates to users received via websocket, including messages
* informing of new notifications and awarded achievements
*/
-const onUserUpdate = function(dispatch, userId, messageObject) {
- switch(messageObject.messageType) {
+const onUserUpdate = function (dispatch, userId, messageObject) {
+ switch (messageObject.messageType) {
case "notification-new":
- if ((messageObject?.data?.userId) === userId) {
+ if (messageObject?.data?.userId === userId) {
// Refresh user's notifications from server
- dispatch(fetchUserNotifications(userId))
+ dispatch(fetchUserNotifications(userId));
}
- break
+ break;
case "achievement-awarded":
- if ((messageObject?.data?.userId) === userId) {
+ if (messageObject?.data?.userId === userId) {
// Refresh user
- dispatch(fetchUser(userId))
+ dispatch(fetchUser(userId));
}
- break
+ break;
default:
- break // Ignore
+ break; // Ignore
}
-}
+};
// redux actions
-const RECEIVE_USERS = 'RECEIVE_USERS'
-const SET_CURRENT_USER = 'SET_CURRENT_USER'
-const ADD_SAVED_CHALLENGE = 'ADD_SAVED_CHALLENGE'
-const REMOVE_SAVED_CHALLENGE = 'REMOVE_SAVED_CHALLENGE'
-const ADD_SAVED_TASK = 'ADD_SAVED_TASK'
-const REMOVE_SAVED_TASK = 'REMOVE_SAVED_TASK'
+const RECEIVE_USERS = "RECEIVE_USERS";
+const SET_CURRENT_USER = "SET_CURRENT_USER";
+const ADD_SAVED_CHALLENGE = "ADD_SAVED_CHALLENGE";
+const REMOVE_SAVED_CHALLENGE = "REMOVE_SAVED_CHALLENGE";
+const ADD_SAVED_TASK = "ADD_SAVED_TASK";
+const REMOVE_SAVED_TASK = "REMOVE_SAVED_TASK";
// redux action creators
/**
* Set the current user in the redux store
*/
-export const setCurrentUser = function(userId) {
+export const setCurrentUser = function (userId) {
return {
type: SET_CURRENT_USER,
userId,
status: RequestStatus.success,
- }
-}
+ };
+};
/**
* Add or update user data in the redux store
*/
-export const receiveUsers = function(normalizedEntities) {
+export const receiveUsers = function (normalizedEntities) {
return {
type: RECEIVE_USERS,
status: RequestStatus.success,
entities: normalizedEntities,
receivedAt: Date.now(),
- }
-}
+ };
+};
/**
* Add saved challenge to a user in the redux store
*/
-export const addSavedChallenge = function(userId, challengeId) {
+export const addSavedChallenge = function (userId, challengeId) {
return {
type: ADD_SAVED_CHALLENGE,
userId,
challengeId,
- }
-}
+ };
+};
/**
* Remove saved challenge from a user in the redux store
*/
-export const removeSavedChallenge = function(userId, challengeId) {
+export const removeSavedChallenge = function (userId, challengeId) {
return {
type: REMOVE_SAVED_CHALLENGE,
userId,
challengeId,
- }
-}
+ };
+};
/**
* Add saved task to a user in the redux store
*/
-export const addSavedTask = function(userId, taskId) {
+export const addSavedTask = function (userId, taskId) {
return {
type: ADD_SAVED_TASK,
userId,
taskId,
- }
-}
+ };
+};
/**
* Remove saved task from a user in the redux store
*/
-export const removeSavedTask = function(userId, taskId) {
+export const removeSavedTask = function (userId, taskId) {
return {
type: REMOVE_SAVED_TASK,
userId,
taskId,
- }
-}
-
+ };
+};
// async action creators
@@ -208,19 +207,18 @@ export const removeSavedTask = function(userId, taskId) {
* results. Note that each result only contains a few public OSM fields such
* as OSM id and avatar URL.
*/
-export const findUser = function(username) {
- return new Endpoint(api.users.find, {variables: {username}}).execute()
-}
+export const findUser = function (username) {
+ return new Endpoint(api.users.find, { variables: { username } }).execute();
+};
/**
* Search for users by OSM username. Resolves with a (possibly empty) list of
* results. Note that each result only contains a few public OSM fields such
* as OSM id and avatar URL.
*/
-export const findPreferredUsers = function(username, taskId) {
- return new Endpoint(api.users.findPreferred,
- {params: {username, tid: taskId}}).execute()
-}
+export const findPreferredUsers = function (username, taskId) {
+ return new Endpoint(api.users.findPreferred, { params: { username, tid: taskId } }).execute();
+};
/**
* Fetch the user data for the given user. Note that this only fetches
@@ -230,27 +228,26 @@ export const findPreferredUsers = function(username, taskId) {
*
* @param userId - Can be either a userId, osmUserId, or username
*/
-export const fetchUser = function(userId) {
- return function(dispatch) {
- const endPoint = isFinite(userId) ?
- new Endpoint(
- api.users.single, {schema: userSchema(), variables: {id: userId}}
- ) :
- new Endpoint(
- api.users.singleByUsername, {schema: userSchema(), variables: {username: userId}}
- )
-
- return endPoint.execute().then(normalizedResults => {
- dispatch(receiveUsers(normalizedResults.entities))
- return normalizedResults
- })
- }
-}
+export const fetchUser = function (userId) {
+ return function (dispatch) {
+ const endPoint = isFinite(userId)
+ ? new Endpoint(api.users.single, { schema: userSchema(), variables: { id: userId } })
+ : new Endpoint(api.users.singleByUsername, {
+ schema: userSchema(),
+ variables: { username: userId },
+ });
+
+ return endPoint.execute().then((normalizedResults) => {
+ dispatch(receiveUsers(normalizedResults.entities));
+ return normalizedResults;
+ });
+ };
+};
/**
* Fetch data on all users (up to the given limit).
*/
- export const fetchUsers = function (limit = 50) {
+export const fetchUsers = function (limit = 50) {
return function (dispatch) {
return new Endpoint(api.users.all, {
schema: [userSchema()],
@@ -270,24 +267,29 @@ export const fetchUser = function(userId) {
/**
* Fetch data on all users (up to the given limit).
*/
-export const fetchUserComments = function (userId, type = CommentType.TASK, filters = { sort: 'created', order: 'DESC', page: 0, limit: 25 } ) {
- return function(dispatch) {
- const endpoint = type === CommentType.CHALLENGE ? api.users.challengeComments : api.users.taskComments
+export const fetchUserComments = function (
+ userId,
+ type = CommentType.TASK,
+ filters = { sort: "created", order: "DESC", page: 0, limit: 25 },
+) {
+ return function (dispatch) {
+ const endpoint =
+ type === CommentType.CHALLENGE ? api.users.challengeComments : api.users.taskComments;
return new Endpoint(endpoint, {
variables: { id: userId },
- params: filters
+ params: filters,
})
.execute()
.then((normalizedResults) => {
return normalizedResults;
})
.catch((error) => {
- dispatch(addError(AppErrors.user.fetchFailure))
+ dispatch(addError(AppErrors.user.fetchFailure));
console.log(error.response || error);
return error.response || error;
});
- }
+ };
};
/**
@@ -295,22 +297,21 @@ export const fetchUserComments = function (userId, type = CommentType.TASK, filt
*
* @param userId - Can be either a userId, osmUserId, or username
*/
-export const fetchBasicUser = function(userId) {
- return function(dispatch) {
- const endPoint = isFinite(userId) ?
- new Endpoint(
- api.users.public, {schema: userSchema(), variables: {id: userId}}
- ) :
- new Endpoint(
- api.users.publicByUsername, {schema: userSchema(), variables: {username: userId}}
- )
-
- return endPoint.execute().then(normalizedResults => {
- dispatch(receiveUsers(normalizedResults.entities))
- return normalizedResults
- })
- }
-}
+export const fetchBasicUser = function (userId) {
+ return function (dispatch) {
+ const endPoint = isFinite(userId)
+ ? new Endpoint(api.users.public, { schema: userSchema(), variables: { id: userId } })
+ : new Endpoint(api.users.publicByUsername, {
+ schema: userSchema(),
+ variables: { username: userId },
+ });
+
+ return endPoint.execute().then((normalizedResults) => {
+ dispatch(receiveUsers(normalizedResults.entities));
+ return normalizedResults;
+ });
+ };
+};
/**
* Fetch the osm oauth token.
@@ -318,591 +319,612 @@ export const fetchBasicUser = function(userId) {
* @param authCode - the token
*/
export const callback = async (authCode, dispatch, push) => {
- const resetURI =
- `${window.env.REACT_APP_MAP_ROULETTE_SERVER_URL}/auth/callback?code=${authCode}`
+ const resetURI = `${window.env.REACT_APP_MAP_ROULETTE_SERVER_URL}/auth/callback?code=${authCode}`;
// Since we're bypassing Endpoint and manually performing an update, we
// need to also manually reset the request cache.
- resetCache()
-
- fetch(resetURI, {credentials: credentialsPolicy}).then(async (result) => {
- const jsonData = await result.json();
- if (jsonData.token) {
- dispatch(
- ensureUserLoggedIn(true)
- ).then(userId => {
- dispatch(fetchSavedChallenges(userId))
- dispatch(fetchUserNotifications(userId))
- subscribeToUserUpdates(dispatch, userId)
-
- const redirectUrl = localStorage.getItem('redirect');
-
- if (redirectUrl) {
- push(redirectUrl)
- localStorage.removeItem('redirects')
- }
- }).catch(
- error => console.log(error)
- ).then(() => null)
- }
- }).catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- dispatch(addError(AppErrors.user.updateFailure))
- console.log(error.response || error)
- }
+ resetCache();
+
+ fetch(resetURI, { credentials: credentialsPolicy })
+ .then(async (result) => {
+ const jsonData = await result.json();
+ if (jsonData.token) {
+ dispatch(ensureUserLoggedIn(true))
+ .then((userId) => {
+ dispatch(fetchSavedChallenges(userId));
+ dispatch(fetchUserNotifications(userId));
+ subscribeToUserUpdates(dispatch, userId);
+
+ const redirectUrl = localStorage.getItem("redirect");
+
+ if (redirectUrl) {
+ push(redirectUrl);
+ localStorage.removeItem("redirects");
+ }
+ })
+ .catch((error) => console.log(error))
+ .then(() => null);
+ }
+ })
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() => dispatch(addError(AppErrors.user.unauthorized)));
+ } else {
+ dispatch(addError(AppErrors.user.updateFailure));
+ console.log(error.response || error);
+ }
- console.log(error);
- })
-}
+ console.log(error);
+ });
+};
/**
* Pings the server to ensure the current (given) user is logged in with
* the server, and automatically signs out the user locally if not.
*/
-export const ensureUserLoggedIn = function(squelchError=false) {
- return function(dispatch) {
- return new Endpoint(
- api.user.whoami, {schema: userSchema()}
- ).execute().then(normalizedResults => {
- const userId = normalizedResults.result
- if (_isFinite(userId) && userId !== GUEST_USER_ID) {
- localStorage.setItem('isLoggedIn', 'true')
- }
- dispatch(receiveUsers(normalizedResults.entities))
- dispatch(setCurrentUser(userId))
- return userId
- }).catch(error => {
- // a 401 (unauthorized) indicates that the user is not logged in. Logout
- // the current user locally to reflect that fact and dispatch an error
- // indicating the user should sign in to continue (unless squelched).
- if (error.response && error.response.status === 401) {
- dispatch(logoutUser())
- if (!squelchError) {
- dispatch(addError(AppErrors.user.unauthenticated))
+export const ensureUserLoggedIn = function (squelchError = false) {
+ return function (dispatch) {
+ return new Endpoint(api.user.whoami, { schema: userSchema() })
+ .execute()
+ .then((normalizedResults) => {
+ const userId = normalizedResults.result;
+ if (_isFinite(userId) && userId !== GUEST_USER_ID) {
+ localStorage.setItem("isLoggedIn", "true");
+ }
+ dispatch(receiveUsers(normalizedResults.entities));
+ dispatch(setCurrentUser(userId));
+ return userId;
+ })
+ .catch((error) => {
+ // a 401 (unauthorized) indicates that the user is not logged in. Logout
+ // the current user locally to reflect that fact and dispatch an error
+ // indicating the user should sign in to continue (unless squelched).
+ if (error.response && error.response.status === 401) {
+ dispatch(logoutUser());
+ if (!squelchError) {
+ dispatch(addError(AppErrors.user.unauthenticated));
+ }
}
- }
- throw error
- })
- }
-}
+ throw error;
+ });
+ };
+};
/**
* Fetch the saved challenges for the given user.
*/
-export const fetchSavedChallenges = function(userId, limit=50) {
- return function(dispatch) {
- return new Endpoint(
- api.user.savedChallenges, {
- schema: [ challengeSchema() ],
- variables: {userId},
- params: {limit}
- }
- ).execute().then(normalizedChallenges => {
- const challenges = normalizedChallenges?.entities?.challenges
- const user = {id: userId}
- user.savedChallenges = _isObject(challenges) ?
- _keys(challenges).map(key => parseInt(key, 10)) : []
-
- dispatch(receiveChallenges(normalizedChallenges.entities))
- dispatch(receiveUsers(simulatedEntities(user)))
- return normalizedChallenges
- });
+export const fetchSavedChallenges = function (userId, limit = 50) {
+ return function (dispatch) {
+ return new Endpoint(api.user.savedChallenges, {
+ schema: [challengeSchema()],
+ variables: { userId },
+ params: { limit },
+ })
+ .execute()
+ .then((normalizedChallenges) => {
+ const challenges = normalizedChallenges?.entities?.challenges;
+ const user = { id: userId };
+ user.savedChallenges = _isObject(challenges)
+ ? _keys(challenges).map((key) => parseInt(key, 10))
+ : [];
+
+ dispatch(receiveChallenges(normalizedChallenges.entities));
+ dispatch(receiveUsers(simulatedEntities(user)));
+ return normalizedChallenges;
+ });
};
-}
+};
/**
* Fetch the saved challenges for the given user.
*/
-export const fetchUsersLockedTasks = async (userId, limit=50) => {
- return new Endpoint(
- api.user.lockedTasks, {
- variables: {userId},
- params: {limit}
- }
- ).execute().then(normalizedChallenges => {
- return normalizedChallenges
+export const fetchUsersLockedTasks = async (userId, limit = 50) => {
+ return new Endpoint(api.user.lockedTasks, {
+ variables: { userId },
+ params: { limit },
})
-}
-
+ .execute()
+ .then((normalizedChallenges) => {
+ return normalizedChallenges;
+ });
+};
/**
* Fetch the user's top challengs based on recent activity beginning at
* the given startDate. If no date is given, then activity over the past
* month is used.
*/
-export const fetchTopChallenges = function(userId, startDate, limit=5) {
- return function(dispatch) {
+export const fetchTopChallenges = function (userId, startDate, limit = 5) {
+ return function (dispatch) {
// If no startDate given, default to past month.
const params = {
- start: (startDate ? startOfDay(startDate) : startOfDay(subMonths(new Date(), 1))).toISOString(),
+ start: (startDate
+ ? startOfDay(startDate)
+ : startOfDay(subMonths(new Date(), 1))
+ ).toISOString(),
limit,
- }
+ };
- const variables = { userId }
+ const variables = { userId };
const cachedTopChallenges = userCache.get(variables, params, USER_TOP_CHALLENGES);
if (cachedTopChallenges) {
- dispatch(receiveChallenges(cachedTopChallenges.challenges))
- dispatch(receiveUsers(cachedTopChallenges.user))
+ dispatch(receiveChallenges(cachedTopChallenges.challenges));
+ dispatch(receiveUsers(cachedTopChallenges.user));
- return cachedTopChallenges.challenges
+ return cachedTopChallenges.challenges;
}
+ return new Endpoint(api.user.topChallenges, {
+ schema: [challengeSchema()],
+ variables,
+ params,
+ })
+ .execute()
+ .then((normalizedChallenges) => {
+ const challenges = normalizedChallenges?.entities?.challenges;
+ const user = { id: userId, topChallenges: [] };
+
+ // Store the top challenge ids in order, sorted by user activity (descending)
+ if (_isObject(challenges)) {
+ user.topChallenges = _map(
+ _reverse(_sortBy(_toPairs(challenges), (idAndChallenge) => idAndChallenge[1].activity)),
+ (idAndChallenge) => parseInt(idAndChallenge[0], 10),
+ );
+ }
- return new Endpoint(
- api.user.topChallenges, {
- schema: [ challengeSchema() ],
- variables,
- params,
- }
- ).execute().then(normalizedChallenges => {
- const challenges = normalizedChallenges?.entities?.challenges
- const user = {id: userId, topChallenges: []}
-
- // Store the top challenge ids in order, sorted by user activity (descending)
- if (_isObject(challenges)) {
- user.topChallenges = _map(
- _reverse(_sortBy(_toPairs(challenges), idAndChallenge => idAndChallenge[1].activity)),
- idAndChallenge => parseInt(idAndChallenge[0], 10)
- )
- }
-
- // Remove the user-specific activity score before adding this challenge
- // to the general redux store.
- _each(challenges, challenge => {
- delete challenge.activity
- })
+ // Remove the user-specific activity score before adding this challenge
+ // to the general redux store.
+ _each(challenges, (challenge) => {
+ delete challenge.activity;
+ });
- userCache.set(variables, params, { challenges: normalizedChallenges.entities, user }, USER_TOP_CHALLENGES)
+ userCache.set(
+ variables,
+ params,
+ { challenges: normalizedChallenges.entities, user },
+ USER_TOP_CHALLENGES,
+ );
- dispatch(receiveChallenges(normalizedChallenges.entities))
- dispatch(receiveUsers(simulatedEntities(user)))
+ dispatch(receiveChallenges(normalizedChallenges.entities));
+ dispatch(receiveUsers(simulatedEntities(user)));
- return normalizedChallenges
- });
+ return normalizedChallenges;
+ });
};
-}
+};
/**
* Fetch the saved tasks for the given user.
*/
-export const fetchSavedTasks = function(userId, limit=50, includeChallenges=true) {
- return function(dispatch) {
+export const fetchSavedTasks = function (userId, limit = 50, includeChallenges = true) {
+ return function (dispatch) {
return new Endpoint(api.user.savedTasks, {
- schema: [ taskSchema() ],
- variables: {userId},
- params: {limit}
- }).execute().then(normalizedTasks => {
- const tasks = normalizedTasks?.entities?.tasks
- const user = {id: userId}
- user.savedTasks = []
- if (_isObject(tasks)) {
- user.savedTasks = _keys(tasks).map(key => parseInt(key, 10))
- if (includeChallenges) {
- const challengeIds = _map(_values(tasks), 'parent')
- dispatch(fetchChallenges(challengeIds))
+ schema: [taskSchema()],
+ variables: { userId },
+ params: { limit },
+ })
+ .execute()
+ .then((normalizedTasks) => {
+ const tasks = normalizedTasks?.entities?.tasks;
+ const user = { id: userId };
+ user.savedTasks = [];
+ if (_isObject(tasks)) {
+ user.savedTasks = _keys(tasks).map((key) => parseInt(key, 10));
+ if (includeChallenges) {
+ const challengeIds = _map(_values(tasks), "parent");
+ dispatch(fetchChallenges(challengeIds));
+ }
}
- }
- dispatch(receiveTasks(normalizedTasks.entities))
- dispatch(receiveUsers(simulatedEntities(user)))
- return normalizedTasks
- });
+ dispatch(receiveTasks(normalizedTasks.entities));
+ dispatch(receiveUsers(simulatedEntities(user)));
+ return normalizedTasks;
+ });
};
-}
+};
/**
* Fetch the user's notification subscriptions
*/
-export const fetchNotificationSubscriptions = function(userId) {
- return function(dispatch) {
+export const fetchNotificationSubscriptions = function (userId) {
+ return function (dispatch) {
return new Endpoint(api.user.notificationSubscriptions, {
- variables: {userId},
- }).execute().then(response => {
- const user = {id: userId}
- user.notificationSubscriptions = _omit(response, ['id', 'userId'])
- dispatch(receiveUsers(simulatedEntities(user)))
- return response
+ variables: { userId },
})
- }
-}
+ .execute()
+ .then((response) => {
+ const user = { id: userId };
+ user.notificationSubscriptions = _omit(response, ["id", "userId"]);
+ dispatch(receiveUsers(simulatedEntities(user)));
+ return response;
+ });
+ };
+};
/**
* Fetch the user's notifications
*/
-export const fetchUserNotifications = function(userId) {
- return function(dispatch) {
+export const fetchUserNotifications = function (userId) {
+ return function (dispatch) {
return new Endpoint(api.user.notifications, {
- variables: {userId},
- params: {limit: -1}
- }).execute().then(response => {
- const user = {id: userId}
- user.notifications = response
- dispatch(receiveUsers(simulatedEntities(user)))
- return response
+ variables: { userId },
+ params: { limit: -1 },
})
- }
-}
+ .execute()
+ .then((response) => {
+ const user = { id: userId };
+ user.notifications = response;
+ dispatch(receiveUsers(simulatedEntities(user)));
+ return response;
+ });
+ };
+};
/**
* Mark notifications as read
*/
-export const markNotificationsRead = function(userId, notificationIds) {
- return function(dispatch) {
+export const markNotificationsRead = function (userId, notificationIds) {
+ return function (dispatch) {
return new Endpoint(api.user.markNotificationsRead, {
- variables: {userId},
+ variables: { userId },
json: { notificationIds },
- }).execute().then(() => {
- return fetchUserNotifications(userId)(dispatch)
})
- }
-}
+ .execute()
+ .then(() => {
+ return fetchUserNotifications(userId)(dispatch);
+ });
+ };
+};
/**
* Delete notifications
*/
-export const deleteNotifications = function(userId, notificationIds) {
- return function(dispatch) {
+export const deleteNotifications = function (userId, notificationIds) {
+ return function (dispatch) {
return new Endpoint(api.user.deleteNotifications, {
- variables: {userId},
+ variables: { userId },
json: { notificationIds },
- }).execute().then(() => {
- return fetchUserNotifications(userId)(dispatch)
})
- }
-}
+ .execute()
+ .then(() => {
+ return fetchUserNotifications(userId)(dispatch);
+ });
+ };
+};
/**
* Fetch the user's recent activity.
*/
-export const fetchUserActivity = function(userId) {
- return function(dispatch) {
-
+export const fetchUserActivity = function (userId) {
+ return function (dispatch) {
const cachedUserActivity = userCache.get({}, {}, USER_ACTIVITY_CACHE);
if (cachedUserActivity) {
return dispatch(receiveUsers(simulatedEntities(cachedUserActivity)));
}
- return new Endpoint(
- api.user.activity
- ).execute().then(activity => {
- const user = {id: userId}
- user.activity = activity
+ return new Endpoint(api.user.activity).execute().then((activity) => {
+ const user = { id: userId };
+ user.activity = activity;
- userCache.set({}, {}, user, USER_ACTIVITY_CACHE)
+ userCache.set({}, {}, user, USER_ACTIVITY_CACHE);
- dispatch(receiveUsers(simulatedEntities(user)))
- return activity
- })
- }
-}
+ dispatch(receiveUsers(simulatedEntities(user)));
+ return activity;
+ });
+ };
+};
/**
* Fetch the user's recent metrics.
*/
-export const fetchUserMetrics = async (userId,
- monthDuration = -1,
- reviewDuration = -1,
- reviewerDuration = -1,
- start = null, end = null,
- reviewStart = null, reviewEnd = null,
- reviewerStart = null, reviewerEnd = null) => {
-
- const params = { monthDuration, reviewDuration, reviewerDuration,
- start, end, reviewStart, reviewEnd, reviewerStart, reviewerEnd }
+export const fetchUserMetrics = async (
+ userId,
+ monthDuration = -1,
+ reviewDuration = -1,
+ reviewerDuration = -1,
+ start = null,
+ end = null,
+ reviewStart = null,
+ reviewEnd = null,
+ reviewerStart = null,
+ reviewerEnd = null,
+) => {
+ const params = {
+ monthDuration,
+ reviewDuration,
+ reviewerDuration,
+ start,
+ end,
+ reviewStart,
+ reviewEnd,
+ reviewerStart,
+ reviewerEnd,
+ };
- const variables = { userId }
+ const variables = { userId };
const cachedUserMetrics = userCache.get(variables, params, USER_METRICS_CACHE);
if (cachedUserMetrics) {
return cachedUserMetrics;
}
-
+
const userMetrics = await new Endpoint(api.user.metrics, {
variables,
- params
- }).execute()
+ params,
+ }).execute();
if (userMetrics?.tasks) {
- userCache.set(variables, params, userMetrics, USER_METRICS_CACHE)
+ userCache.set(variables, params, userMetrics, USER_METRICS_CACHE);
}
- return userMetrics
-}
+ return userMetrics;
+};
/**
* Retrieve the given user data plus their accompanying data, performing
* multiple API requests as needed.
*/
-export const loadCompleteUser = function(userId, savedChallengesLimit=50, savedTasksLimit=50) {
- return function(dispatch) {
+export const loadCompleteUser = function (userId, savedChallengesLimit = 50, savedTasksLimit = 50) {
+ return function (dispatch) {
if (!_isFinite(userId) || userId === GUEST_USER_ID) {
- return null
+ return null;
}
- return fetchUser(userId)(dispatch).then(() => {
- fetchSavedChallenges(userId, savedChallengesLimit)(dispatch)
- // disabled untill endpoint is fixed
- // fetchTopChallenges(userId)(dispatch)
- fetchSavedTasks(userId, savedTasksLimit)(dispatch)
- fetchUserActivity(userId)(dispatch)
- fetchNotificationSubscriptions(userId)(dispatch)
- }).then(() => {
- if (_isFinite(userId) && userId !== GUEST_USER_ID) {
- localStorage.setItem('isLoggedIn', 'true')
- }
- dispatch(setCurrentUser(userId))
- }).catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- console.log(error.response || error)
- }
- })
- }
-}
+ return fetchUser(userId)(dispatch)
+ .then(() => {
+ fetchSavedChallenges(userId, savedChallengesLimit)(dispatch);
+ // disabled untill endpoint is fixed
+ // fetchTopChallenges(userId)(dispatch)
+ fetchSavedTasks(userId, savedTasksLimit)(dispatch);
+ fetchUserActivity(userId)(dispatch);
+ fetchNotificationSubscriptions(userId)(dispatch);
+ })
+ .then(() => {
+ if (_isFinite(userId) && userId !== GUEST_USER_ID) {
+ localStorage.setItem("isLoggedIn", "true");
+ }
+ dispatch(setCurrentUser(userId));
+ })
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ } else {
+ console.log(error.response || error);
+ }
+ });
+ };
+};
/**
* Retrieve the given user data plus any accompanying data needed for User Settings,
* performing multiple API requests as needed.
*/
-export const loadUserSettings = function(userId) {
- return function(dispatch) {
+export const loadUserSettings = function (userId) {
+ return function (dispatch) {
if (userId === GUEST_USER_ID) {
- return null
+ return null;
}
- return fetchUser(userId)(dispatch).then(normalizedUsers => {
- const fetchedUserId = normalizedUsers.result
- if (fetchedUserId) {
- fetchNotificationSubscriptions(fetchedUserId)(dispatch)
- }
- else {
- dispatch(addError(AppErrors.user.notFound))
- }
- }).catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- console.log(error.response || error)
- }
- })
- }
-}
+ return fetchUser(userId)(dispatch)
+ .then((normalizedUsers) => {
+ const fetchedUserId = normalizedUsers.result;
+ if (fetchedUserId) {
+ fetchNotificationSubscriptions(fetchedUserId)(dispatch);
+ } else {
+ dispatch(addError(AppErrors.user.notFound));
+ }
+ })
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ } else {
+ console.log(error.response || error);
+ }
+ });
+ };
+};
/**
* Update the given user's settings with the given settings.
*/
-export const updateUserSettings = function(userId, settings) {
- return updateUser(userId, dispatch => {
+export const updateUserSettings = function (userId, settings) {
+ return updateUser(userId, (dispatch) => {
// Optimistically assume it will succeed and update the local store.
// If it doesn't, it'll get updated properly by the server response.
- dispatch(receiveUsers({[userId]: {id: userId, settings}}))
+ dispatch(receiveUsers({ [userId]: { id: userId, settings } }));
- return new Endpoint(
- api.user.updateSettings, {variables: {userId}, json: settings}
- ).execute().then(() => dispatch(fetchUser(userId))) // fetch latest
- })
-}
+ return new Endpoint(api.user.updateSettings, { variables: { userId }, json: settings })
+ .execute()
+ .then(() => dispatch(fetchUser(userId))); // fetch latest
+ });
+};
/**
* Update the given user's notification subscription options with the given
* subscriptions
*/
-export const updateNotificationSubscriptions = function(userId, subscriptions) {
- return updateUser(userId, dispatch => {
+export const updateNotificationSubscriptions = function (userId, subscriptions) {
+ return updateUser(userId, (dispatch) => {
// Optimistically assume it will succeed and update the local store.
// If it doesn't, it'll get updated properly by the server response.
- dispatch(receiveUsers({[userId]: {id: userId, notificationSubscriptions: subscriptions}}))
+ dispatch(receiveUsers({ [userId]: { id: userId, notificationSubscriptions: subscriptions } }));
- return new Endpoint(
- api.user.updateNotificationSubscriptions, {variables: {userId}, json: {...subscriptions, userId, id: -1}}
- ).execute().then(() => dispatch(fetchNotificationSubscriptions(userId))) // fetch latest
- })
-}
+ return new Endpoint(api.user.updateNotificationSubscriptions, {
+ variables: { userId },
+ json: { ...subscriptions, userId, id: -1 },
+ })
+ .execute()
+ .then(() => dispatch(fetchNotificationSubscriptions(userId))); // fetch latest
+ });
+};
/**
* Updates an app/client-specific setting for the user on the server.
* appSettings should be a JSON-compatible object of the form
* `{ settingName: ... }`
*/
-export const updateUserAppSetting = function(userId, appId, appSetting) {
- return updateUser(userId, dispatch => {
- return new Endpoint(
- api.users.single, {schema: userSchema(), variables: {id: userId}}
- ).execute().then(normalizedResults => {
- const oldUser = normalizedResults.entities.users[normalizedResults.result]
-
- // Combined properties format for multiple client applications:
- // {
- // "my_application_id": {
- // "meta": {
- // "revision": timestamp,
- // },
- // "settings": {
- // "first_app_setting": ...,
- // "second_app_setting": ...,
- // ...
- // }
- // },
- // ...
- // }
- const userData =
- Object.assign({}, oldUser, {
+export const updateUserAppSetting = function (userId, appId, appSetting) {
+ return updateUser(userId, (dispatch) => {
+ return new Endpoint(api.users.single, { schema: userSchema(), variables: { id: userId } })
+ .execute()
+ .then((normalizedResults) => {
+ const oldUser = normalizedResults.entities.users[normalizedResults.result];
+
+ // Combined properties format for multiple client applications:
+ // {
+ // "my_application_id": {
+ // "meta": {
+ // "revision": timestamp,
+ // },
+ // "settings": {
+ // "first_app_setting": ...,
+ // "second_app_setting": ...,
+ // ...
+ // }
+ // },
+ // ...
+ // }
+ const userData = Object.assign({}, oldUser, {
properties: Object.assign({}, oldUser?.properties, {
[appId]: {
meta: Object.assign({}, oldUser?.properties?.[appId]?.meta, {
revision: Date.now(),
}),
settings: Object.assign({}, oldUser?.properties?.[appId]?.settings, appSetting),
- }
- })
- })
-
- // Optimistically assume update will succeed and update the local store.
- // If it doesn't, it'll get updated properly by the server response.
- dispatch(receiveUsers({users: {[userId]: userData}}))
-
- return new Endpoint(api.user.updateSettings, {
- variables: { userId },
- json: userData,
- }).execute()
- });
+ },
+ }),
+ });
+
+ // Optimistically assume update will succeed and update the local store.
+ // If it doesn't, it'll get updated properly by the server response.
+ dispatch(receiveUsers({ users: { [userId]: userData } }));
+
+ return new Endpoint(api.user.updateSettings, {
+ variables: { userId },
+ json: userData,
+ }).execute();
+ });
});
-}
+};
/**
* Reset the user's API key
*/
-export const resetAPIKey = function(userId) {
- return function(dispatch) {
- const resetURI =
- `${window.env.REACT_APP_MAP_ROULETTE_SERVER_URL}/auth/generateAPIKey?userId=${userId}`
+export const resetAPIKey = function (userId) {
+ return function (dispatch) {
+ const resetURI = `${window.env.REACT_APP_MAP_ROULETTE_SERVER_URL}/auth/generateAPIKey?userId=${userId}`;
// Since we're bypassing Endpoint and manually performing an update, we
// need to also manually reset the request cache.
- resetCache()
- fetch(resetURI, {credentials: credentialsPolicy}).then(() => {
- return fetchUser(userId)(dispatch)
- }).catch(error => {
- if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- dispatch(addError(AppErrors.user.updateFailure))
- console.log(error.response || error)
- }
- })
- }
-}
+ resetCache();
+ fetch(resetURI, { credentials: credentialsPolicy })
+ .then(() => {
+ return fetchUser(userId)(dispatch);
+ })
+ .catch((error) => {
+ if (isSecurityError(error)) {
+ dispatch(ensureUserLoggedIn()).then(() =>
+ dispatch(addError(AppErrors.user.unauthorized)),
+ );
+ } else {
+ dispatch(addError(AppErrors.user.updateFailure));
+ console.log(error.response || error);
+ }
+ });
+ };
+};
/**
* Add the given challenge to the given user's list of saved challenges.
*/
-export const saveChallengeForUser = function(userId, challengeId) {
+export const saveChallengeForUser = function (userId, challengeId) {
return updateUser(userId, (dispatch) => {
// Optimistically assume it will succeed and update the local store.
// If it doesn't, it'll get updated properly by the server response.
- dispatch(addSavedChallenge(userId, challengeId))
+ dispatch(addSavedChallenge(userId, challengeId));
- return new Endpoint(
- api.user.saveChallenge, {variables: {userId, challengeId}}
- ).execute()
- })
-}
+ return new Endpoint(api.user.saveChallenge, { variables: { userId, challengeId } }).execute();
+ });
+};
/**
* Remove the given challenge from the given user's list of saved
* challenges.
*/
-export const unsaveChallengeForUser = function(userId, challengeId) {
+export const unsaveChallengeForUser = function (userId, challengeId) {
return updateUser(userId, (dispatch) => {
// Optimistically assume it will succeed and update the local store.
// If it doesn't, it'll get updated by the server response.
- dispatch(removeSavedChallenge(userId, challengeId))
+ dispatch(removeSavedChallenge(userId, challengeId));
- return new Endpoint(
- api.user.unsaveChallenge, {variables: {userId, challengeId}}
- ).execute()
- })
-}
+ return new Endpoint(api.user.unsaveChallenge, { variables: { userId, challengeId } }).execute();
+ });
+};
/**
* Add the given task to the given user's list of saved tasks.
*/
-export const saveTask = function(userId, taskId) {
+export const saveTask = function (userId, taskId) {
return updateUser(userId, (dispatch) => {
// Optimistically assume it will succeed and update the local store.
// If it doesn't, it'll get updated properly by the server response.
- dispatch(addSavedTask(userId, taskId))
+ dispatch(addSavedTask(userId, taskId));
- return new Endpoint(
- api.user.saveTask, {variables: {userId, taskId}}
- ).execute()
- })
-}
+ return new Endpoint(api.user.saveTask, { variables: { userId, taskId } }).execute();
+ });
+};
/**
* Remove the given task from the given user's list of saved tasks.
*/
-export const unsaveTask = function(userId, taskId) {
+export const unsaveTask = function (userId, taskId) {
return updateUser(userId, (dispatch) => {
// Optimistically assume it will succeed and update the local store.
// If it doesn't, it'll get updated by the server response.
- dispatch(removeSavedTask(userId, taskId))
+ dispatch(removeSavedTask(userId, taskId));
- return new Endpoint(
- api.user.unsaveTask, {variables: {userId, taskId}}
- ).execute()
- })
-}
+ return new Endpoint(api.user.unsaveTask, { variables: { userId, taskId } }).execute();
+ });
+};
/**
* Logout the current user on both the client and server.
*/
-export const logoutUser = function(userId) {
+export const logoutUser = function (userId) {
//clear stale locks and isLoggedIn status but retain redirect
- const redirect = localStorage.getItem('redirect');
- const state = localStorage.getItem('state');
+ const redirect = localStorage.getItem("redirect");
+ const state = localStorage.getItem("state");
localStorage.clear();
if (redirect) {
- localStorage.setItem('redirect', redirect)
+ localStorage.setItem("redirect", redirect);
}
if (state) {
- localStorage.setItem('state', state)
+ localStorage.setItem("state", state);
}
- const logoutURI = `${window.env.REACT_APP_MAP_ROULETTE_SERVER_URL}/auth/signout`
+ const logoutURI = `${window.env.REACT_APP_MAP_ROULETTE_SERVER_URL}/auth/signout`;
if (_isFinite(userId) && userId !== GUEST_USER_ID) {
- unsubscribeFromUserUpdates(userId)
+ unsubscribeFromUserUpdates(userId);
}
- return function(dispatch) {
- dispatch(setCurrentUser(GUEST_USER_ID))
- fetch(logoutURI, {credentials: credentialsPolicy})
- }
-}
+ return function (dispatch) {
+ dispatch(setCurrentUser(GUEST_USER_ID));
+ fetch(logoutURI, { credentials: credentialsPolicy });
+ };
+};
/**
* Updates the given user, delegating to the given update function to perform
@@ -914,25 +936,22 @@ export const logoutUser = function(userId) {
* an error to ensure the local store reflects the latest data from the server
* in the event optimistic local updates were made.
*/
-const updateUser = function(userId, updateFunction) {
- return function(dispatch) {
- return updateFunction(dispatch).catch(error => {
+const updateUser = function (userId, updateFunction) {
+ return function (dispatch) {
+ return updateFunction(dispatch).catch((error) => {
if (isSecurityError(error)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- dispatch(addError(AppErrors.user.updateFailure))
- console.log(error.response || error)
+ dispatch(ensureUserLoggedIn()).then(() => dispatch(addError(AppErrors.user.unauthorized)));
+ } else {
+ dispatch(addError(AppErrors.user.updateFailure));
+ console.log(error.response || error);
// Reload user data to ensure our local store is in sync with the
// server in case optimistic changes were made.
- dispatch(loadCompleteUser(userId))
+ dispatch(loadCompleteUser(userId));
}
- })
- }
-}
+ });
+ };
+};
// redux reducers.
@@ -942,7 +961,7 @@ const updateUser = function(userId, updateFunction) {
*
* @private
*/
-const reduceUsersFurther = function(mergedState, oldState, userEntities) {
+const reduceUsersFurther = function (mergedState, oldState, userEntities) {
// The generic reduction will merge arrays, creating a union of values. We
// don't want that for many of our arrays: we want to replace the old ones
// with new ones (if we have new ones).
@@ -951,97 +970,92 @@ const reduceUsersFurther = function(mergedState, oldState, userEntities) {
// we use `en-US`
for (let entity of userEntities) {
if (_isArray(entity.groups)) {
- mergedState[entity.id].groups = entity.groups
+ mergedState[entity.id].groups = entity.groups;
}
if (_isArray(entity.activity)) {
- mergedState[entity.id].activity = entity.activity
+ mergedState[entity.id].activity = entity.activity;
}
if (_isArray(entity.savedChallenges)) {
- mergedState[entity.id].savedChallenges = entity.savedChallenges
+ mergedState[entity.id].savedChallenges = entity.savedChallenges;
}
if (_isArray(entity.topChallenges)) {
- mergedState[entity.id].topChallenges = entity.topChallenges
+ mergedState[entity.id].topChallenges = entity.topChallenges;
}
if (_isArray(entity.savedTasks)) {
- mergedState[entity.id].savedTasks = entity.savedTasks
+ mergedState[entity.id].savedTasks = entity.savedTasks;
}
if (_isArray(entity.notifications)) {
- mergedState[entity.id].notifications = entity.notifications
+ mergedState[entity.id].notifications = entity.notifications;
}
// Always completely replace app-specific properties with new ones
if (_isObject(entity.properties)) {
- mergedState[entity.id].properties = entity.properties
+ mergedState[entity.id].properties = entity.properties;
}
// Always completely replace customBasemaps
if (_isArray(entity?.settings?.customBasemaps)) {
- mergedState[entity.id].settings.customBasemaps = entity.settings.customBasemaps
+ mergedState[entity.id].settings.customBasemaps = entity.settings.customBasemaps;
}
// Normalize server's default `en` locale to `en-US`
- if ((entity?.settings?.locale) === 'en') {
- mergedState[entity.id].settings.locale = "en-US"
+ if (entity?.settings?.locale === "en") {
+ mergedState[entity.id].settings.locale = "en-US";
}
}
-}
+};
/**
* Primary reducer for user entities in the redux store
*/
-export const userEntities = function(state, action) {
+export const userEntities = function (state, action) {
if (action.type === ADD_SAVED_CHALLENGE) {
- const mergedState = _cloneDeep(state)
+ const mergedState = _cloneDeep(state);
if (!_isArray(mergedState?.[action.userId]?.savedChallenges)) {
- _set(mergedState, `${action.userId}.savedChallenges`, [])
+ _set(mergedState, `${action.userId}.savedChallenges`, []);
}
- mergedState[action.userId].savedChallenges.push(action.challengeId)
- return mergedState
- }
- else if (action.type === REMOVE_SAVED_CHALLENGE) {
- const mergedState = _cloneDeep(state)
- _pull(mergedState[action.userId]?.savedChallenges ?? [], action.challengeId)
- return mergedState
- }
- else if (action.type === ADD_SAVED_TASK) {
- const mergedState = _cloneDeep(state)
+ mergedState[action.userId].savedChallenges.push(action.challengeId);
+ return mergedState;
+ } else if (action.type === REMOVE_SAVED_CHALLENGE) {
+ const mergedState = _cloneDeep(state);
+ _pull(mergedState[action.userId]?.savedChallenges ?? [], action.challengeId);
+ return mergedState;
+ } else if (action.type === ADD_SAVED_TASK) {
+ const mergedState = _cloneDeep(state);
if (!_isArray(mergedState?.[action.userId]?.savedTasks)) {
- _set(mergedState, `${action.userId}.savedTasks`, [])
+ _set(mergedState, `${action.userId}.savedTasks`, []);
}
- mergedState[action.userId].savedTasks.push(action.taskId)
- return mergedState
- }
- else if (action.type === REMOVE_SAVED_TASK) {
- const mergedState = _cloneDeep(state)
- _pull(mergedState[action.userId]?.savedTasks ?? [], action.taskId)
- return mergedState
- }
- else {
- return genericEntityReducer(
- RECEIVE_USERS, 'users', reduceUsersFurther)(state, action)
+ mergedState[action.userId].savedTasks.push(action.taskId);
+ return mergedState;
+ } else if (action.type === REMOVE_SAVED_TASK) {
+ const mergedState = _cloneDeep(state);
+ _pull(mergedState[action.userId]?.savedTasks ?? [], action.taskId);
+ return mergedState;
+ } else {
+ return genericEntityReducer(RECEIVE_USERS, "users", reduceUsersFurther)(state, action);
}
-}
+};
/**
* Reducer for the current user in the redux store
*/
-export const currentUser = function(state=null, action) {
+export const currentUser = function (state = null, action) {
if (action.type === SET_CURRENT_USER) {
return {
userId: action.userId,
- isFetching: action.status === RequestStatus.inProgress
- }
+ isFetching: action.status === RequestStatus.inProgress,
+ };
}
- return state
-}
+ return state;
+};
/**
* Builds a simulated normalized entities representation from the given
@@ -1049,10 +1063,10 @@ export const currentUser = function(state=null, action) {
*
* @private
*/
-export const simulatedEntities = function(user) {
+export const simulatedEntities = function (user) {
return {
users: {
- [user.id]: user
- }
- }
-}
+ [user.id]: user,
+ },
+ };
+};
diff --git a/src/services/VirtualChallenge/VirtualChallenge.js b/src/services/VirtualChallenge/VirtualChallenge.js
index 9595ff9a5..2f647be8e 100644
--- a/src/services/VirtualChallenge/VirtualChallenge.js
+++ b/src/services/VirtualChallenge/VirtualChallenge.js
@@ -1,127 +1,136 @@
-import { schema } from 'normalizr'
-import _isFinite from 'lodash/isFinite'
-import _map from 'lodash/map'
-import _omit from 'lodash/omit'
-import _head from 'lodash/head'
-import { addHours } from 'date-fns'
-import { defaultRoutes as api, isSecurityError } from '../Server/Server'
-import Endpoint from '../Server/Endpoint'
-import RequestStatus from '../Server/RequestStatus'
-import genericEntityReducer from '../Server/GenericEntityReducer'
-import { ensureUserLoggedIn } from '../User/User'
-import { addError, addServerError } from '../Error/Error'
-import AppErrors from '../Error/AppErrors'
+import { addHours } from "date-fns";
+import _head from "lodash/head";
+import _isFinite from "lodash/isFinite";
+import _map from "lodash/map";
+import _omit from "lodash/omit";
+import { schema } from "normalizr";
+import AppErrors from "../Error/AppErrors";
+import { addError, addServerError } from "../Error/Error";
+import Endpoint from "../Server/Endpoint";
+import genericEntityReducer from "../Server/GenericEntityReducer";
+import RequestStatus from "../Server/RequestStatus";
+import { defaultRoutes as api, isSecurityError } from "../Server/Server";
+import { ensureUserLoggedIn } from "../User/User";
/** normalizr schema for virtual challenges */
-export const virtualChallengeSchema = function() {
- return new schema.Entity('virtualChallenges')
-}
+export const virtualChallengeSchema = function () {
+ return new schema.Entity("virtualChallenges");
+};
/*
* Time, in hours, until new virtual challenges expire. Defaults to 36 hours if
* nothing is specified in the .env file.
*/
-export const DEFAULT_EXPIRATION_DURATION=
- parseInt(window.env?.REACT_APP_VIRTUAL_CHALLENGE_DURATION ?? 36, 10)
+export const DEFAULT_EXPIRATION_DURATION = parseInt(
+ window.env?.REACT_APP_VIRTUAL_CHALLENGE_DURATION ?? 36,
+ 10,
+);
// redux actions
-const RECEIVE_VIRTUAL_CHALLENGES = 'RECEIVE_VIRTUAL_CHALLENGES'
+const RECEIVE_VIRTUAL_CHALLENGES = "RECEIVE_VIRTUAL_CHALLENGES";
// redux action creators
/**
* Add or update virtual challenge data in the redux store
*/
-export const receiveVirtualChallenges = function(normalizedEntities,
- status=RequestStatus.success) {
+export const receiveVirtualChallenges = function (
+ normalizedEntities,
+ status = RequestStatus.success,
+) {
return {
type: RECEIVE_VIRTUAL_CHALLENGES,
status,
entities: normalizedEntities,
- receivedAt: Date.now()
- }
-}
+ receivedAt: Date.now(),
+ };
+};
// async action creators
/**
* Fetch data for the given virtual challenge.
*/
-export const fetchVirtualChallenge = function(virtualChallengeId) {
- return function(dispatch) {
- return new Endpoint(
- api.virtualChallenge.single,
- {schema: virtualChallengeSchema(), variables: {id: virtualChallengeId}}
- ).execute().then(normalizedResults => {
- if (_isFinite(normalizedResults.result)) {
- // Mark that the challenge is virtual.
- normalizedResults.entities.virtualChallenges[normalizedResults.result].isVirtual = true
- }
-
- dispatch(receiveVirtualChallenges(normalizedResults.entities))
- return normalizedResults
- }).catch((error) => {
- dispatch(addError(AppErrors.virtualChallenge.fetchFailure))
- console.log(error.response || error)
+export const fetchVirtualChallenge = function (virtualChallengeId) {
+ return function (dispatch) {
+ return new Endpoint(api.virtualChallenge.single, {
+ schema: virtualChallengeSchema(),
+ variables: { id: virtualChallengeId },
})
- }
-}
+ .execute()
+ .then((normalizedResults) => {
+ if (_isFinite(normalizedResults.result)) {
+ // Mark that the challenge is virtual.
+ normalizedResults.entities.virtualChallenges[normalizedResults.result].isVirtual = true;
+ }
+
+ dispatch(receiveVirtualChallenges(normalizedResults.entities));
+ return normalizedResults;
+ })
+ .catch((error) => {
+ dispatch(addError(AppErrors.virtualChallenge.fetchFailure));
+ console.log(error.response || error);
+ });
+ };
+};
/**
* Creates a new virtual challenge with the given name and tasks. If an
* explicit expiration timestamp is given, it'll be used; otherwise the virtual
* challenge will be set to expire after the default configured duration.
*/
-export const createVirtualChallenge = function(name, taskIds, expiration, clusters) {
- return function(dispatch) {
- let searchParameters = null
+export const createVirtualChallenge = function (name, taskIds, expiration, clusters) {
+ return function (dispatch) {
+ let searchParameters = null;
if (clusters && clusters.length > 0) {
- searchParameters = _omit(_head(clusters).params,
- ['location', 'taskTagConjunction', 'challengeTagConjunction'])
- searchParameters.boundingGeometries =
- _map(clusters, (c) => {return {bounding: c.bounding}})
+ searchParameters = _omit(_head(clusters).params, [
+ "location",
+ "taskTagConjunction",
+ "challengeTagConjunction",
+ ]);
+ searchParameters.boundingGeometries = _map(clusters, (c) => {
+ return { bounding: c.bounding };
+ });
}
const challengeData = {
name,
taskIdList: taskIds,
- expiry: expiration ? expiration :
- addHours(new Date(), DEFAULT_EXPIRATION_DURATION).getTime(),
+ expiry: expiration ? expiration : addHours(new Date(), DEFAULT_EXPIRATION_DURATION).getTime(),
searchParameters: searchParameters || {},
- }
+ };
return saveVirtualChallenge(
dispatch,
new Endpoint(api.virtualChallenge.create, {
schema: virtualChallengeSchema(),
json: challengeData,
- })
- )
- }
-}
+ }),
+ );
+ };
+};
/**
* Renews the expiration time of the virtual challenge, either setting it to
* the explicit expiration timestamp given or resetting it to the default
* configured duration if no expiration is specified.
*/
-export const renewVirtualChallenge = function(virtualChallengeId, expiration) {
- return function(dispatch) {
+export const renewVirtualChallenge = function (virtualChallengeId, expiration) {
+ return function (dispatch) {
const challengeData = {
- expiry: expiration ? expiration :
- addHours(new Date(), DEFAULT_EXPIRATION_DURATION).getTime()
- }
+ expiry: expiration ? expiration : addHours(new Date(), DEFAULT_EXPIRATION_DURATION).getTime(),
+ };
return saveVirtualChallenge(
dispatch,
new Endpoint(api.virtualChallenge.edit, {
- variables: {id: virtualChallengeId},
+ variables: { id: virtualChallengeId },
schema: virtualChallengeSchema(),
json: challengeData,
- })
- )
- }
-}
+ }),
+ );
+ };
+};
/**
* Executes the given endpoint, saving the virtual challenge, and
@@ -129,22 +138,21 @@ export const renewVirtualChallenge = function(virtualChallengeId, expiration) {
*
* @private
*/
-export const saveVirtualChallenge = function(dispatch, endpoint) {
- return endpoint.execute().then(normalizedResults => {
- dispatch(receiveVirtualChallenges(normalizedResults.entities))
- return normalizedResults?.entities?.virtualChallenges?.[normalizedResults.result];
- }).catch(serverError => {
- if (isSecurityError(serverError)) {
- dispatch(ensureUserLoggedIn()).then(() =>
- dispatch(addError(AppErrors.user.unauthorized))
- )
- }
- else {
- dispatch(addServerError(AppErrors.virtualChallenge.createFailure,
- serverError))
- }
- })
-}
+export const saveVirtualChallenge = function (dispatch, endpoint) {
+ return endpoint
+ .execute()
+ .then((normalizedResults) => {
+ dispatch(receiveVirtualChallenges(normalizedResults.entities));
+ return normalizedResults?.entities?.virtualChallenges?.[normalizedResults.result];
+ })
+ .catch((serverError) => {
+ if (isSecurityError(serverError)) {
+ dispatch(ensureUserLoggedIn()).then(() => dispatch(addError(AppErrors.user.unauthorized)));
+ } else {
+ dispatch(addServerError(AppErrors.virtualChallenge.createFailure, serverError));
+ }
+ });
+};
// redux reducers
//
@@ -154,20 +162,19 @@ export const saveVirtualChallenge = function(dispatch, endpoint) {
*
* @private
*/
-const reduceVirtualChallengesFurther = function(mergedState,
- oldState,
- virtualChallengeEntities) {
- const now = Date.now()
- virtualChallengeEntities.forEach(entity => {
+const reduceVirtualChallengesFurther = function (mergedState, oldState, virtualChallengeEntities) {
+ const now = Date.now();
+ virtualChallengeEntities.forEach((entity) => {
// Ignore deleted and expired virtual challenges
if (entity.deleted || entity.expired < now) {
- delete mergedState[entity.id]
- return
+ delete mergedState[entity.id];
+ return;
}
- })
-}
-
-export const virtualChallengeEntities =
- genericEntityReducer([RECEIVE_VIRTUAL_CHALLENGES],
- 'virtualChallenges',
- reduceVirtualChallengesFurther)
+ });
+};
+
+export const virtualChallengeEntities = genericEntityReducer(
+ [RECEIVE_VIRTUAL_CHALLENGES],
+ "virtualChallenges",
+ reduceVirtualChallengesFurther,
+);
diff --git a/src/services/VisibleLayer/LayerSources.js b/src/services/VisibleLayer/LayerSources.js
index bf79f8b0a..5474222c0 100644
--- a/src/services/VisibleLayer/LayerSources.js
+++ b/src/services/VisibleLayer/LayerSources.js
@@ -1,19 +1,21 @@
-import PropTypes from 'prop-types'
-import QueryString from 'query-string'
-import _find from 'lodash/find'
-import _sortBy from 'lodash/sortBy'
-import _map from 'lodash/map'
-import _isFinite from 'lodash/isFinite'
-import { ChallengeBasemap, basemapLayerSources }
- from '../Challenge/ChallengeBasemap/ChallengeBasemap'
-import defaultLayers from '../../defaultLayers.json'
-import customLayers from '../../customLayers.json'
+import _find from "lodash/find";
+import _isFinite from "lodash/isFinite";
+import _map from "lodash/map";
+import _sortBy from "lodash/sortBy";
+import PropTypes from "prop-types";
+import QueryString from "query-string";
+import customLayers from "../../customLayers.json";
+import defaultLayers from "../../defaultLayers.json";
+import {
+ ChallengeBasemap,
+ basemapLayerSources,
+} from "../Challenge/ChallengeBasemap/ChallengeBasemap";
export const layerSourceShape = PropTypes.shape({
/** Unique id for layer */
id: PropTypes.string.isRequired,
/** The type of layer (tms, wms, bing, etc) */
- type: PropTypes.oneOf(['tms', 'wms', 'bing']),
+ type: PropTypes.oneOf(["tms", "wms", "bing"]),
/** Human-readable name of layer formatted as react-intl message */
name: PropTypes.string,
/** Description of the layer */
@@ -37,18 +39,22 @@ export const layerSourceShape = PropTypes.shape({
default: PropTypes.bool,
/** Set to true for dynamically-created layers, such as custom basemaps */
isDynamic: PropTypes.bool,
-})
-
-export const OPEN_STREET_MAP = 'MAPNIK'
-export const OPEN_CYCLE_MAP = 'tf-cycle'
-export const BING = 'Bing'
-export const MAPBOX_STREETS = 'Mapbox'
-export const MAPBOX_LIGHT = 'MapboxLight'
-export const MAPBOX_SATELLITE_STREETS = 'MapboxSatellite'
-
-export const DEFAULT_OVERLAY_ORDER = Object.freeze(
- ["priority-bounds", "task-features", "osm-data", "mapillary", "openstreetcam"]
-)
+});
+
+export const OPEN_STREET_MAP = "MAPNIK";
+export const OPEN_CYCLE_MAP = "tf-cycle";
+export const BING = "Bing";
+export const MAPBOX_STREETS = "Mapbox";
+export const MAPBOX_LIGHT = "MapboxLight";
+export const MAPBOX_SATELLITE_STREETS = "MapboxSatellite";
+
+export const DEFAULT_OVERLAY_ORDER = Object.freeze([
+ "priority-bounds",
+ "task-features",
+ "osm-data",
+ "mapillary",
+ "openstreetcam",
+]);
/**
* Array of available layer sources. Start with default layers from the [OSM
@@ -56,161 +62,161 @@ export const DEFAULT_OVERLAY_ORDER = Object.freeze(
* add/override based on local .env file settings.
*/
export const LayerSources = _sortBy(
- _map(defaultLayers.concat(customLayers), layer => (
+ _map(defaultLayers.concat(customLayers), (layer) =>
// Pull properties into the top level
- Object.assign({}, layer.properties, {geometry: layer.geometry})
- )),
- 'name'
-)
+ Object.assign({}, layer.properties, { geometry: layer.geometry }),
+ ),
+ "name",
+);
// Load any API keys from .env file
-let layerAPIKeys = {}
-if ((window.env?.REACT_APP_MAP_LAYER_API_KEYS ?? '').length > 0) {
+let layerAPIKeys = {};
+if ((window.env?.REACT_APP_MAP_LAYER_API_KEYS ?? "").length > 0) {
try {
- layerAPIKeys = JSON.parse(window.env.REACT_APP_MAP_LAYER_API_KEYS)
- }
- catch(e) {
- console.log("Failed to parse map layer API keys. Ignoring.")
- console.log(e)
+ layerAPIKeys = JSON.parse(window.env.REACT_APP_MAP_LAYER_API_KEYS);
+ } catch (e) {
+ console.log("Failed to parse map layer API keys. Ignoring.");
+ console.log(e);
}
}
-export const normalizeBingLayer = function(layer) {
- const normalizedLayer = Object.assign({}, layer)
- normalizedLayer.maxZoom = normalizedLayer.max_zoom
+export const normalizeBingLayer = function (layer) {
+ const normalizedLayer = Object.assign({}, layer);
+ normalizedLayer.maxZoom = normalizedLayer.max_zoom;
// API key should be specified in .env file
- const apiKey = layerAPIKeys[normalizedLayer.id]
+ const apiKey = layerAPIKeys[normalizedLayer.id];
if (apiKey) {
- normalizedLayer[apiKey.name] = apiKey.value
- }
- else {
+ normalizedLayer[apiKey.name] = apiKey.value;
+ } else {
// Community key circulating around OSM
- normalizedLayer.bingkey = "Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU"
+ normalizedLayer.bingkey = "Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU";
}
- return normalizedLayer
-}
+ return normalizedLayer;
+};
-export const normalizeTMSLayer = function(layer) {
- const normalizedLayer = Object.assign({}, layer)
+export const normalizeTMSLayer = function (layer) {
+ const normalizedLayer = Object.assign({}, layer);
// Only assign subdomains if we have some.
if (normalizedLayer.url.match(/{switch:(.*?)}/)) {
- normalizedLayer.subdomains = normalizedLayer.url.match(/{switch:(.*?)}/)[1].split(',')
+ normalizedLayer.subdomains = normalizedLayer.url.match(/{switch:(.*?)}/)[1].split(",");
}
- normalizedLayer.url = normalizedLayer.url.replace(/{switch:(.*?)}/, '{s}')
- normalizedLayer.url = normalizedLayer.url.replace('{zoom}', '{z}')
- normalizedLayer.maxZoom = normalizedLayer.max_zoom
+ normalizedLayer.url = normalizedLayer.url.replace(/{switch:(.*?)}/, "{s}");
+ normalizedLayer.url = normalizedLayer.url.replace("{zoom}", "{z}");
+ normalizedLayer.maxZoom = normalizedLayer.max_zoom;
// If an API key has been specified in .env file, add it into layer url
- const apiKey = layerAPIKeys[normalizedLayer.id]
+ const apiKey = layerAPIKeys[normalizedLayer.id];
if (apiKey) {
- const urlComponents = QueryString.parseUrl(normalizedLayer.url)
- urlComponents.query[apiKey.name] = apiKey.value
- normalizedLayer.url = urlComponents.url + '?' + QueryString.stringify(urlComponents.query)
+ const urlComponents = QueryString.parseUrl(normalizedLayer.url);
+ urlComponents.query[apiKey.name] = apiKey.value;
+ normalizedLayer.url = urlComponents.url + "?" + QueryString.stringify(urlComponents.query);
}
- return normalizedLayer
-}
+ return normalizedLayer;
+};
-export const normalizeLayer = function(layer) {
- switch(layer.type) {
- case 'bing':
- return normalizeBingLayer(layer)
- case 'tms':
- return normalizeTMSLayer(layer)
+export const normalizeLayer = function (layer) {
+ switch (layer.type) {
+ case "bing":
+ return normalizeBingLayer(layer);
+ case "tms":
+ return normalizeTMSLayer(layer);
default:
- return layer
+ return layer;
}
-}
+};
/**
* Returns a default layer source for use in situations where no layer source
* has been specified.
*/
-export const defaultLayerSource = function() {
- const configuredDefault = _find(LayerSources, {id: window.env.REACT_APP_DEFAULT_MAP_LAYER_ID})
- return configuredDefault ? configuredDefault : LayerSources[0]
-}
+export const defaultLayerSource = function () {
+ const configuredDefault = _find(LayerSources, { id: window.env.REACT_APP_DEFAULT_MAP_LAYER_ID });
+ return configuredDefault ? configuredDefault : LayerSources[0];
+};
/**
* Builds layers with the given function.
*/
-export const buildLayerSources = function(visibleLayers, userCustomSources, buildLayer) {
+export const buildLayerSources = function (visibleLayers, userCustomSources, buildLayer) {
return _map(visibleLayers, (layerId, index) => {
- const layerSource = layerSourceWithId(layerId, userCustomSources)
+ const layerSource = layerSourceWithId(layerId, userCustomSources);
if (layerSource) {
- return buildLayer(layerId, index, layerSource)
- }
- else {
- return null
+ return buildLayer(layerId, index, layerSource);
+ } else {
+ return null;
}
- })
-}
+ });
+};
/**
* Retrieves the (static) layer source with a matching id. Dynamic layer
* sources are not searched.
*/
-export const layerSourceWithId = function(id, userCustomSources) {
- if (_find(LayerSources, {id})) {
- return _find(LayerSources, {id})
+export const layerSourceWithId = function (id, userCustomSources) {
+ if (_find(LayerSources, { id })) {
+ return _find(LayerSources, { id });
}
- const customSource = _find(userCustomSources, source => source.name === id)
+ const customSource = _find(userCustomSources, (source) => source.name === id);
if (customSource) {
return {
id: customSource.name,
name: customSource.name,
url: customSource.url,
overlay: customSource.overlay,
- type: "tms"
- }
+ type: "tms",
+ };
}
- return undefined
-}
+ return undefined;
+};
/**
* Create and return a dynamic layer source with the given layerId
* and url. Primarily intended for use with custom basemaps.
*/
-export const createDynamicLayerSource = function(layerId, layerName, url, overlay = false) {
+export const createDynamicLayerSource = function (layerId, layerName, url, overlay = false) {
return {
id: layerId,
name: layerName,
url,
isDynamic: true,
- overlay
- }
-}
+ overlay,
+ };
+};
/**
* Returns a layer source for the given defaultBasemap, defaultBasemapId, and
* customBasemap settings, including generating a dynamic layer source with the
* given customBasemap and customLayerId if appropriate.
*/
-export const basemapLayerSource = function(defaultBasemap, defaultBasemapId, customMapUrl) {
+export const basemapLayerSource = function (defaultBasemap, defaultBasemapId, customMapUrl) {
if (defaultBasemap === ChallengeBasemap.none) {
- return null
+ return null;
} else if (defaultBasemap === ChallengeBasemap.identified) {
- return layerSourceWithId(defaultBasemapId)
+ return layerSourceWithId(defaultBasemapId);
// if challenge basemap setting is custom and a customMapUrl is provided
// we need to set this as default basemap. It will show up in the map layers
// as "Challenge Default" under the User custom basemeps, but will be preselected
} else if (defaultBasemap === ChallengeBasemap.custom && customMapUrl) {
- return createDynamicLayerSource(100, "Challenge Default", customMapUrl, false)
+ return createDynamicLayerSource(100, "Challenge Default", customMapUrl, false);
} else if (_isFinite(defaultBasemap)) {
- const basemap = basemapLayerSources()[defaultBasemap]
- return layerSourceWithId(basemap)
+ const basemap = basemapLayerSources()[defaultBasemap];
+ return layerSourceWithId(basemap);
} else if (defaultBasemap?.url) {
return createDynamicLayerSource(
- defaultBasemap.name, defaultBasemap.name, defaultBasemap.url,
- defaultBasemap.overlay)
+ defaultBasemap.name,
+ defaultBasemap.name,
+ defaultBasemap.url,
+ defaultBasemap.overlay,
+ );
} else {
- return null
+ return null;
}
-}
+};
diff --git a/src/services/VisibleLayer/LayerSources.test.js b/src/services/VisibleLayer/LayerSources.test.js
index c799e7414..b54fa659e 100644
--- a/src/services/VisibleLayer/LayerSources.test.js
+++ b/src/services/VisibleLayer/LayerSources.test.js
@@ -1,71 +1,68 @@
import { describe, expect } from "vitest";
-import { ChallengeBasemap }
- from '../Challenge/ChallengeBasemap/ChallengeBasemap'
-import { BING,
- OPEN_STREET_MAP,
- layerSourceWithId,
- createDynamicLayerSource,
- basemapLayerSource } from './LayerSources'
+import { ChallengeBasemap } from "../Challenge/ChallengeBasemap/ChallengeBasemap";
+import {
+ BING,
+ OPEN_STREET_MAP,
+ basemapLayerSource,
+ createDynamicLayerSource,
+ layerSourceWithId,
+} from "./LayerSources";
-let layerId = null
-let layerUrl = null
-let layerName = null
+let layerId = null;
+let layerUrl = null;
+let layerName = null;
beforeEach(() => {
- layerId = "fooLayer"
- layerUrl = "http://www.example.com/foo"
- layerName = "fooLayerName"
-})
+ layerId = "fooLayer";
+ layerUrl = "http://www.example.com/foo";
+ layerName = "fooLayerName";
+});
describe("layerSourceWithId", () => {
test("returns the LayerSource matching the given id", () => {
- expect(
- layerSourceWithId(OPEN_STREET_MAP).id
- ).toEqual(OPEN_STREET_MAP)
+ expect(layerSourceWithId(OPEN_STREET_MAP).id).toEqual(OPEN_STREET_MAP);
- expect(
- layerSourceWithId(BING).id
- ).toEqual(BING)
- })
+ expect(layerSourceWithId(BING).id).toEqual(BING);
+ });
test("returns undefined if no layer matches", () => {
- expect(layerSourceWithId("Foo")).toBeUndefined()
- })
-})
+ expect(layerSourceWithId("Foo")).toBeUndefined();
+ });
+});
describe("createDynamicLayerSource", () => {
test("Generates layer source with the given id and url", () => {
- const layer = createDynamicLayerSource(layerId, layerName, layerUrl)
+ const layer = createDynamicLayerSource(layerId, layerName, layerUrl);
- expect(layer.id).toEqual(layerId)
- expect(layer.name).toEqual(layerName)
- expect(layer.url).toEqual(layerUrl)
- expect(layer.isDynamic).toBe(true)
- })
-})
+ expect(layer.id).toEqual(layerId);
+ expect(layer.name).toEqual(layerName);
+ expect(layer.url).toEqual(layerUrl);
+ expect(layer.isDynamic).toBe(true);
+ });
+});
describe("basemapLayerSource", () => {
test("Returns a constant layer if defaultBasemap setting matches", () => {
- const layer = basemapLayerSource(ChallengeBasemap.openStreetMap, layerId)
- expect(layer.id).toEqual(OPEN_STREET_MAP)
- })
+ const layer = basemapLayerSource(ChallengeBasemap.openStreetMap, layerId);
+ expect(layer.id).toEqual(OPEN_STREET_MAP);
+ });
test("Returns null if basemap set to none", () => {
- const layer = basemapLayerSource(ChallengeBasemap.none, layerId)
- expect(layer).toBeNull()
- })
+ const layer = basemapLayerSource(ChallengeBasemap.none, layerId);
+ expect(layer).toBeNull();
+ });
test("Returns custom layer if set to custom and url provided", () => {
- const layer = basemapLayerSource({id: layerId, name: layerId, url: layerUrl}, layerId)
+ const layer = basemapLayerSource({ id: layerId, name: layerId, url: layerUrl }, layerId);
- expect(layer.id).toEqual(layerId)
- expect(layer.url).toEqual(layerUrl)
- expect(layer.isDynamic).toBe(true)
- })
+ expect(layer.id).toEqual(layerId);
+ expect(layer.url).toEqual(layerUrl);
+ expect(layer.isDynamic).toBe(true);
+ });
test("Returns null for custom layer if no url provided", () => {
- const layer = basemapLayerSource({id: layerId, name: layerId}, layerId)
+ const layer = basemapLayerSource({ id: layerId, name: layerId }, layerId);
- expect(layer).toBeNull()
- })
-})
+ expect(layer).toBeNull();
+ });
+});
diff --git a/src/services/VisibleLayer/VisibleLayer.js b/src/services/VisibleLayer/VisibleLayer.js
index dffca54e8..070d65d80 100644
--- a/src/services/VisibleLayer/VisibleLayer.js
+++ b/src/services/VisibleLayer/VisibleLayer.js
@@ -1,7 +1,7 @@
-import _cloneDeep from 'lodash/cloneDeep'
+import _cloneDeep from "lodash/cloneDeep";
// redux actions
-const CHANGE_VISIBLE_LAYER = 'ChangeVisibleLayer'
+const CHANGE_VISIBLE_LAYER = "ChangeVisibleLayer";
// redux action creators
@@ -16,27 +16,25 @@ const CHANGE_VISIBLE_LAYER = 'ChangeVisibleLayer'
*
* @see See VisibleLayer/LayerSources
*/
-export const changeVisibleLayer = function(layerId, mapType) {
+export const changeVisibleLayer = function (layerId, mapType) {
return {
type: CHANGE_VISIBLE_LAYER,
layerId,
- mapType
- }
-}
+ mapType,
+ };
+};
// redux reducers
-export const visibleLayer = function(state=null, action) {
+export const visibleLayer = function (state = null, action) {
if (action.type === CHANGE_VISIBLE_LAYER) {
- const newState = _cloneDeep(state) || {}
+ const newState = _cloneDeep(state) || {};
if (action.mapType) {
- newState[action.mapType] = {id: action.layerId}
- }
- else {
- newState.id = action.layerId
+ newState[action.mapType] = { id: action.layerId };
+ } else {
+ newState.id = action.layerId;
}
- return newState
- }
- else {
- return state
+ return newState;
+ } else {
+ return state;
}
-}
+};
diff --git a/src/services/VisibleLayer/VisibleOverlays.js b/src/services/VisibleLayer/VisibleOverlays.js
index 998294565..616a564a1 100644
--- a/src/services/VisibleLayer/VisibleOverlays.js
+++ b/src/services/VisibleLayer/VisibleOverlays.js
@@ -1,7 +1,7 @@
// redux actions
-const ADD_VISIBLE_OVERLAY = 'AddVisibleOverlay'
-const REMOVE_VISIBLE_OVERLAY = 'RemoveVisibleOverlay'
-const CLEAR_VISIBLE_OVERLAYS = 'ClearVisibleOverlays'
+const ADD_VISIBLE_OVERLAY = "AddVisibleOverlay";
+const REMOVE_VISIBLE_OVERLAY = "RemoveVisibleOverlay";
+const CLEAR_VISIBLE_OVERLAYS = "ClearVisibleOverlays";
// redux action creators
@@ -13,12 +13,12 @@ const CLEAR_VISIBLE_OVERLAYS = 'ClearVisibleOverlays'
*
* @see See VisibleOverlays/LayerSources
*/
-export const addVisibleOverlay = function(layerId) {
+export const addVisibleOverlay = function (layerId) {
return {
type: ADD_VISIBLE_OVERLAY,
layerId,
- }
-}
+ };
+};
/**
* Remove the given overlay layer from the visible overlays in the redux store.
@@ -28,36 +28,36 @@ export const addVisibleOverlay = function(layerId) {
*
* @see See VisibleOverlays/LayerSources
*/
-export const removeVisibleOverlay = function(layerId) {
+export const removeVisibleOverlay = function (layerId) {
return {
type: REMOVE_VISIBLE_OVERLAY,
layerId,
- }
-}
+ };
+};
/**
* Clear all visible overlay layers from the redux store
*/
-export const clearVisibleOverlays = function() {
+export const clearVisibleOverlays = function () {
return {
type: CLEAR_VISIBLE_OVERLAYS,
- }
-}
+ };
+};
// redux reducers
-export const visibleOverlays = function(state=[], action) {
- const layerSet = new Set(state)
+export const visibleOverlays = function (state = [], action) {
+ const layerSet = new Set(state);
- switch(action.type) {
+ switch (action.type) {
case ADD_VISIBLE_OVERLAY:
- layerSet.add(action.layerId)
- return [...layerSet]
+ layerSet.add(action.layerId);
+ return [...layerSet];
case REMOVE_VISIBLE_OVERLAY:
- layerSet.delete(action.layerId)
- return [...layerSet]
+ layerSet.delete(action.layerId);
+ return [...layerSet];
case CLEAR_VISIBLE_OVERLAYS:
- return []
+ return [];
default:
- return state
+ return state;
}
-}
+};
diff --git a/src/services/Widget/ChallengeFilter/ChallengeFilter.js b/src/services/Widget/ChallengeFilter/ChallengeFilter.js
index 7e54699cf..3aa8aba1d 100644
--- a/src/services/Widget/ChallengeFilter/ChallengeFilter.js
+++ b/src/services/Widget/ChallengeFilter/ChallengeFilter.js
@@ -20,12 +20,7 @@ export const defaultChallengeFilters = function () {
};
};
-export const challengePassesFilters = function (
- challenge,
- manager,
- pins,
- challengeFilters
-) {
+export const challengePassesFilters = function (challenge, manager, pins, challengeFilters) {
if (challengeFilters.visible && !challenge.enabled) {
return false;
}
@@ -46,5 +41,5 @@ export const challengePassesFilters = function (
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByFilter = _fromPairs(
- _map(messages, (message, key) => [ChallengeFilter[key], message])
+ _map(messages, (message, key) => [ChallengeFilter[key], message]),
);
diff --git a/src/services/Widget/ChallengeFilter/ChallengeFilter.test.js b/src/services/Widget/ChallengeFilter/ChallengeFilter.test.js
index 83749eeb0..6d657e2ba 100644
--- a/src/services/Widget/ChallengeFilter/ChallengeFilter.test.js
+++ b/src/services/Widget/ChallengeFilter/ChallengeFilter.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect } from "vitest";
+import { describe, expect, it } from "vitest";
import { messagesByFilter } from "./ChallengeFilter";
describe("messagesByFilter", () => {
diff --git a/src/services/Widget/GridMigrations.js b/src/services/Widget/GridMigrations.js
index ab976bdc7..2b37d0a35 100644
--- a/src/services/Widget/GridMigrations.js
+++ b/src/services/Widget/GridMigrations.js
@@ -1,4 +1,4 @@
-import _map from 'lodash/map'
+import _map from "lodash/map";
/**
* Migrates from one widget grid data-model version to the next. Each key
@@ -10,18 +10,20 @@ import _map from 'lodash/map'
* migration isn't feasible.
*/
const GridMigrations = {
- 1: configuration => {
+ 1: (configuration) => {
// Rename blocks to widgets
- configuration.widgets = _map(configuration.blocks, widgetConf => {
- widgetConf.widgetKey = widgetConf.blockKey.replace(/Block/g, 'Widget')
- widgetConf.label.id = widgetConf.label.id.replace(/GridBlocks/g, 'Widgets').replace(/Block/g, 'Widget')
- delete widgetConf.blockKey
+ configuration.widgets = _map(configuration.blocks, (widgetConf) => {
+ widgetConf.widgetKey = widgetConf.blockKey.replace(/Block/g, "Widget");
+ widgetConf.label.id = widgetConf.label.id
+ .replace(/GridBlocks/g, "Widgets")
+ .replace(/Block/g, "Widget");
+ delete widgetConf.blockKey;
- return widgetConf
- })
- delete configuration.blocks
- return configuration
+ return widgetConf;
+ });
+ delete configuration.blocks;
+ return configuration;
},
-}
+};
-export default GridMigrations
+export default GridMigrations;
diff --git a/src/services/Widget/GridMigrations.test.js b/src/services/Widget/GridMigrations.test.js
index 4126d47fd..019b444e0 100644
--- a/src/services/Widget/GridMigrations.test.js
+++ b/src/services/Widget/GridMigrations.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect } from "vitest";
+import { describe, expect, it } from "vitest";
import { GridMigrations } from "./GridMigrations";
describe("ProjectFilter", () => {
diff --git a/src/services/Widget/ProjectFilter/ProjectFilter.js b/src/services/Widget/ProjectFilter/ProjectFilter.js
index 99eef09bc..f75dc6cc1 100644
--- a/src/services/Widget/ProjectFilter/ProjectFilter.js
+++ b/src/services/Widget/ProjectFilter/ProjectFilter.js
@@ -23,12 +23,7 @@ export const defaultProjectFilters = function () {
};
};
-export const projectPassesFilters = function (
- project,
- manager,
- pins,
- projectFilters
-) {
+export const projectPassesFilters = function (project, manager, pins, projectFilters) {
if (projectFilters.visible && !project.enabled) {
return false;
}
@@ -53,5 +48,5 @@ export const projectPassesFilters = function (
* messages suitable for use with FormattedMessage or formatMessage.
*/
export const messagesByFilter = _fromPairs(
- _map(messages, (message, key) => [ProjectFilter[key], message])
+ _map(messages, (message, key) => [ProjectFilter[key], message]),
);
diff --git a/src/services/Widget/ProjectFilter/ProjectFilter.test.js b/src/services/Widget/ProjectFilter/ProjectFilter.test.js
index a7bad97f1..c6712fa89 100644
--- a/src/services/Widget/ProjectFilter/ProjectFilter.test.js
+++ b/src/services/Widget/ProjectFilter/ProjectFilter.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect } from "vitest";
+import { describe, expect, it } from "vitest";
import { ProjectFilter, defaultProjectFilters } from "./ProjectFilter";
describe("ProjectFilter", () => {
diff --git a/src/services/Widget/Widget.js b/src/services/Widget/Widget.js
index cfa054a20..2c962284d 100644
--- a/src/services/Widget/Widget.js
+++ b/src/services/Widget/Widget.js
@@ -1,41 +1,41 @@
-import { v4 as uuidv4 } from 'uuid'
-import FileSaver from 'file-saver'
-import _isFinite from 'lodash/isFinite'
-import _isObject from 'lodash/isObject'
-import _map from 'lodash/map'
-import _compact from 'lodash/compact'
-import _intersection from 'lodash/intersection'
-import _cloneDeep from 'lodash/cloneDeep'
-import _isString from 'lodash/isString'
-import _findIndex from 'lodash/findIndex'
-import _isEmpty from 'lodash/isEmpty'
-import _each from 'lodash/each'
-import _reduce from 'lodash/reduce'
-import _pick from 'lodash/pick'
-import _snakeCase from 'lodash/snakeCase'
-import _find from 'lodash/find'
-import GridMigrations from './GridMigrations'
+import FileSaver from "file-saver";
+import _cloneDeep from "lodash/cloneDeep";
+import _compact from "lodash/compact";
+import _each from "lodash/each";
+import _find from "lodash/find";
+import _findIndex from "lodash/findIndex";
+import _intersection from "lodash/intersection";
+import _isEmpty from "lodash/isEmpty";
+import _isFinite from "lodash/isFinite";
+import _isObject from "lodash/isObject";
+import _isString from "lodash/isString";
+import _map from "lodash/map";
+import _pick from "lodash/pick";
+import _reduce from "lodash/reduce";
+import _snakeCase from "lodash/snakeCase";
+import { v4 as uuidv4 } from "uuid";
+import GridMigrations from "./GridMigrations";
/**
* Current version of the widget grid configuration data model. Be sure to add
* a migration to GridMigrations.js when bumping this up to a newer version.
*/
-export const CURRENT_DATAMODEL_VERSION=2
-
-export const WIDGET_DATA_TARGET_PROJECTS = 'projects'
-export const WIDGET_DATA_TARGET_PROJECT = 'project'
-export const WIDGET_DATA_TARGET_CHALLENGES = 'challenges'
-export const WIDGET_DATA_TARGET_CHALLENGE = 'challenge'
-export const WIDGET_DATA_TARGET_TASKS = 'tasks'
-export const WIDGET_DATA_TARGET_TASK = 'task'
-export const WIDGET_DATA_TARGET_USER = 'user'
-export const WIDGET_DATA_TARGET_REVIEW = 'review'
-export const WIDGET_DATA_TARGET_ACTIVITY = 'activity'
-
-export const WIDGET_USER_TARGET_ALL = 'all'
-export const WIDGET_USER_TARGET_MANAGER_READ = 'managerRead'
-export const WIDGET_USER_TARGET_MANAGER_WRITE = 'managerWrite'
-export const WIDGET_USER_TARGET_SUPERUSER = 'superuser'
+export const CURRENT_DATAMODEL_VERSION = 2;
+
+export const WIDGET_DATA_TARGET_PROJECTS = "projects";
+export const WIDGET_DATA_TARGET_PROJECT = "project";
+export const WIDGET_DATA_TARGET_CHALLENGES = "challenges";
+export const WIDGET_DATA_TARGET_CHALLENGE = "challenge";
+export const WIDGET_DATA_TARGET_TASKS = "tasks";
+export const WIDGET_DATA_TARGET_TASK = "task";
+export const WIDGET_DATA_TARGET_USER = "user";
+export const WIDGET_DATA_TARGET_REVIEW = "review";
+export const WIDGET_DATA_TARGET_ACTIVITY = "activity";
+
+export const WIDGET_USER_TARGET_ALL = "all";
+export const WIDGET_USER_TARGET_MANAGER_READ = "managerRead";
+export const WIDGET_USER_TARGET_MANAGER_WRITE = "managerWrite";
+export const WIDGET_USER_TARGET_SUPERUSER = "superuser";
export const WidgetDataTarget = {
projects: WIDGET_DATA_TARGET_PROJECTS,
@@ -47,191 +47,200 @@ export const WidgetDataTarget = {
user: WIDGET_DATA_TARGET_USER,
review: WIDGET_DATA_TARGET_REVIEW,
activity: WIDGET_DATA_TARGET_ACTIVITY,
-}
+};
export const WidgetUserTarget = {
all: WIDGET_USER_TARGET_ALL,
managerRead: WIDGET_USER_TARGET_MANAGER_READ,
managerWrite: WIDGET_USER_TARGET_MANAGER_WRITE,
superuser: WIDGET_USER_TARGET_SUPERUSER,
-}
+};
/**
* Registered widget types with descriptors.
*
* @private
*/
-const WidgetTypes = {}
+const WidgetTypes = {};
/**
* Register a new widget type with the given component (which should be
* pre-wrapped with any needed higher-order components) and widget descriptor.
*/
-export const registerWidgetType = function(widgetComponent, widgetDescriptor) {
+export const registerWidgetType = function (widgetComponent, widgetDescriptor) {
if (!widgetDescriptor) {
- throw new Error("Cannot register widget type without descriptor")
+ throw new Error("Cannot register widget type without descriptor");
}
if (!widgetDescriptor.widgetKey) {
- throw new Error("Cannot register widget type without descriptor.widgetKey")
+ throw new Error("Cannot register widget type without descriptor.widgetKey");
}
WidgetTypes[widgetDescriptor.widgetKey || widgetDescriptor.widgetKey] = {
descriptor: widgetDescriptor,
- component: widgetComponent
- }
-}
+ component: widgetComponent,
+ };
+};
/**
* Retrieves the descriptor for the widget identified by the given key, or null
* if no widget is found.
*/
-export const widgetDescriptor = function(widgetKey) {
- return WidgetTypes[widgetKey] ? WidgetTypes[widgetKey].descriptor : null
-}
+export const widgetDescriptor = function (widgetKey) {
+ return WidgetTypes[widgetKey] ? WidgetTypes[widgetKey].descriptor : null;
+};
/**
* Looks up a widget component from either the given widgetKey (string) or widget
* descriptor (object) containing a `widgetKey` field. Returns null if no
* matching component is found.
*/
-export const widgetComponent = function(keyOrDescriptor) {
- const widgetKey = _isObject(keyOrDescriptor) ?
- keyOrDescriptor.widgetKey :
- keyOrDescriptor
+export const widgetComponent = function (keyOrDescriptor) {
+ const widgetKey = _isObject(keyOrDescriptor) ? keyOrDescriptor.widgetKey : keyOrDescriptor;
- return WidgetTypes[widgetKey] ? WidgetTypes[widgetKey].component : null
-}
+ return WidgetTypes[widgetKey] ? WidgetTypes[widgetKey].component : null;
+};
/**
* Returns an array of descriptors for widget types that have data targets
* compatible with the given dataTargets.
*/
-export const compatibleWidgetTypes = function(dataTargets) {
- return _compact(_map(WidgetTypes, widgetInfo => (
- _intersection(dataTargets, widgetInfo.descriptor.targets).length === 0 ?
- null :
- widgetInfo.descriptor
- )))
-}
+export const compatibleWidgetTypes = function (dataTargets) {
+ return _compact(
+ _map(WidgetTypes, (widgetInfo) =>
+ _intersection(dataTargets, widgetInfo.descriptor.targets).length === 0
+ ? null
+ : widgetInfo.descriptor,
+ ),
+ );
+};
/**
* Generate a new random id for a widget grid
*/
-export const generateWidgetId = function() {
- return uuidv4()
-}
+export const generateWidgetId = function () {
+ return uuidv4();
+};
/**
* Resets the given configuration back to the default, preserving its id
* and user-assigned label
*/
-export const resetGridConfigurationToDefault = function(configuration, generateDefaultConfiguration) {
+export const resetGridConfigurationToDefault = function (
+ configuration,
+ generateDefaultConfiguration,
+) {
return Object.assign(generateDefaultConfiguration(), {
id: configuration.id,
label: configuration.label,
- })
-}
+ });
+};
/**
* Migrate a given widget grid configuration format to the latest format if
* needed and possible. Returns the migrated configuration, or a fresh default
* configuration if migration is not possible.
*/
-export const migrateWidgetGridConfiguration = function(originalConfiguration,
- generateDefaultConfiguration) {
+export const migrateWidgetGridConfiguration = function (
+ originalConfiguration,
+ generateDefaultConfiguration,
+) {
// Grids lacking any version number cannot be migrated. Reset to default
// configuration.
if (!_isFinite(originalConfiguration.dataModelVersion)) {
- return resetGridConfigurationToDefault(originalConfiguration,
- generateDefaultConfiguration)
+ return resetGridConfigurationToDefault(originalConfiguration, generateDefaultConfiguration);
}
- let migratedConfiguration = null
- let version = originalConfiguration.dataModelVersion
+ let migratedConfiguration = null;
+ let version = originalConfiguration.dataModelVersion;
while (version < CURRENT_DATAMODEL_VERSION) {
if (!GridMigrations[version]) {
- throw new Error(`Unable to migrate widget grid configuration from version ${version}: no migration found`)
+ throw new Error(
+ `Unable to migrate widget grid configuration from version ${version}: no migration found`,
+ );
}
// Create a configuration copy if we haven't already
if (!migratedConfiguration) {
- migratedConfiguration = _cloneDeep(originalConfiguration)
+ migratedConfiguration = _cloneDeep(originalConfiguration);
}
- migratedConfiguration = GridMigrations[version](migratedConfiguration)
+ migratedConfiguration = GridMigrations[version](migratedConfiguration);
// A null migratedConfiguration indicates migration is not feasible. All we
// can do is reset it to the latest default configuration
if (!migratedConfiguration) {
- return resetGridConfigurationToDefault(originalConfiguration,
- generateDefaultConfiguration)
+ return resetGridConfigurationToDefault(originalConfiguration, generateDefaultConfiguration);
}
// Successful, bump the data model version
- migratedConfiguration.dataModelVersion = ++version
+ migratedConfiguration.dataModelVersion = ++version;
}
- return migratedConfiguration || originalConfiguration
-}
+ return migratedConfiguration || originalConfiguration;
+};
/**
* Scans the given gridConfiguration for any missing/decommissioned widgets and
* returns an array of any discovered, or an empty array if none
*/
-export const decommissionedWidgets = gridConfiguration => {
- return _reduce(gridConfiguration.widgets, (missing, widgetConfiguration) => {
- const WidgetComponent = widgetComponent(widgetConfiguration)
- if (!WidgetComponent && widgetConfiguration) {
- const widgetKey = _isString(widgetConfiguration) ?
- widgetConfiguration :
- widgetConfiguration.widgetKey
- missing.push(widgetKey)
- }
- return missing
- }, [])
-}
+export const decommissionedWidgets = (gridConfiguration) => {
+ return _reduce(
+ gridConfiguration.widgets,
+ (missing, widgetConfiguration) => {
+ const WidgetComponent = widgetComponent(widgetConfiguration);
+ if (!WidgetComponent && widgetConfiguration) {
+ const widgetKey = _isString(widgetConfiguration)
+ ? widgetConfiguration
+ : widgetConfiguration.widgetKey;
+ missing.push(widgetKey);
+ }
+ return missing;
+ },
+ [],
+ );
+};
/**
* Returns a copy of the given gridConfiguration pruned of any missing or
* decommissioned widgets, or the original gridConfiguration if there were none
*/
-export const pruneDecommissionedWidgets = originalGridConfiguration => {
- let gridConfiguration = originalGridConfiguration
- if (_findIndex(gridConfiguration.widgets, w => _isEmpty(w)) !== -1) {
- gridConfiguration = _cloneDeep(gridConfiguration)
- gridConfiguration.widgets = _compact(gridConfiguration.widgets)
+export const pruneDecommissionedWidgets = (originalGridConfiguration) => {
+ let gridConfiguration = originalGridConfiguration;
+ if (_findIndex(gridConfiguration.widgets, (w) => _isEmpty(w)) !== -1) {
+ gridConfiguration = _cloneDeep(gridConfiguration);
+ gridConfiguration.widgets = _compact(gridConfiguration.widgets);
}
- const decommissioned = decommissionedWidgets(gridConfiguration)
+ const decommissioned = decommissionedWidgets(gridConfiguration);
- return decommissioned.length > 0 ?
- pruneWidgets(gridConfiguration, decommissioned) :
- gridConfiguration
-}
+ return decommissioned.length > 0
+ ? pruneWidgets(gridConfiguration, decommissioned)
+ : gridConfiguration;
+};
/**
* Returns a copy of the given gridConfiguration pruned of the given widgets,
* or gridConfiguration if no pruning was needed
*/
export const pruneWidgets = (gridConfiguration, widgetKeys) => {
- let prunedConfiguration = gridConfiguration
+ let prunedConfiguration = gridConfiguration;
- _each(widgetKeys, widgetKey => {
- const widgetIndex = _findIndex(prunedConfiguration.widgets, {widgetKey})
+ _each(widgetKeys, (widgetKey) => {
+ const widgetIndex = _findIndex(prunedConfiguration.widgets, { widgetKey });
if (widgetIndex !== -1) {
// If we haven't made a fresh copy of gridConfiguration yet, do so now
if (prunedConfiguration === gridConfiguration) {
- prunedConfiguration = _cloneDeep(gridConfiguration)
+ prunedConfiguration = _cloneDeep(gridConfiguration);
}
- prunedConfiguration.widgets.splice(widgetIndex, 1)
- prunedConfiguration.layout.splice(widgetIndex, 1)
+ prunedConfiguration.widgets.splice(widgetIndex, 1);
+ prunedConfiguration.layout.splice(widgetIndex, 1);
}
- })
+ });
- return prunedConfiguration
-}
+ return prunedConfiguration;
+};
/**
* Return a gridConfiguration with the given widgetDescriptor added to it,
@@ -239,23 +248,23 @@ export const pruneWidgets = (gridConfiguration, widgetKeys) => {
* position
*/
export const addWidgetToGrid = (gridConfiguration, widgetKey, defaultConfiguration) => {
- const descriptor = widgetDescriptor(widgetKey)
+ const descriptor = widgetDescriptor(widgetKey);
if (!descriptor) {
- throw new Error(`Attempt to add unknown widget ${widgetKey} to workspace.`)
+ throw new Error(`Attempt to add unknown widget ${widgetKey} to workspace.`);
}
- const updatedConfiguration = _cloneDeep(gridConfiguration)
- updatedConfiguration.widgets.unshift(descriptor)
+ const updatedConfiguration = _cloneDeep(gridConfiguration);
+ updatedConfiguration.widgets.unshift(descriptor);
// For simplicity, we'll add the new widget to the top row (the grid should
// auto-adjust). If the widget is laid out in the default configuration,
// we'll also match its default column
- let defaultColumn = 0
+ let defaultColumn = 0;
if (defaultConfiguration) {
- const widgetIndex = _findIndex(defaultConfiguration.widgets, {widgetKey})
+ const widgetIndex = _findIndex(defaultConfiguration.widgets, { widgetKey });
if (widgetIndex !== -1) {
- defaultColumn = defaultConfiguration.layout[widgetIndex].x
+ defaultColumn = defaultConfiguration.layout[widgetIndex].x;
}
}
@@ -269,10 +278,10 @@ export const addWidgetToGrid = (gridConfiguration, widgetKey, defaultConfigurati
h: descriptor.defaultHeight,
minH: descriptor.minHeight,
maxH: descriptor.maxHeight,
- })
+ });
- return updatedConfiguration
-}
+ return updatedConfiguration;
+};
/**
* Returns a gridConfiguration that has all permanent widgets in the given
@@ -280,18 +289,16 @@ export const addWidgetToGrid = (gridConfiguration, widgetKey, defaultConfigurati
* be added, or the given gridConfiguration if no additions were necessary)
*/
export const ensurePermanentWidgetsAdded = (gridConfiguration, defaultConfiguration) => {
- let updatedConfiguration = gridConfiguration
+ let updatedConfiguration = gridConfiguration;
- _each(gridConfiguration.permanentWidgets, widgetKey => {
- if (!_find(updatedConfiguration.widgets, {widgetKey})) {
- updatedConfiguration = addWidgetToGrid(updatedConfiguration,
- widgetKey,
- defaultConfiguration)
+ _each(gridConfiguration.permanentWidgets, (widgetKey) => {
+ if (!_find(updatedConfiguration.widgets, { widgetKey })) {
+ updatedConfiguration = addWidgetToGrid(updatedConfiguration, widgetKey, defaultConfiguration);
}
- })
+ });
- return updatedConfiguration
-}
+ return updatedConfiguration;
+};
/**
* Downloads a JSON representation of the given workspace configuration
@@ -303,39 +310,45 @@ export const exportWorkspaceConfiguration = (workspaceConfiguration, exportName)
exportFormatVersion: 1,
targetWorkspace: workspaceConfiguration.name,
exportName: exportName ? exportName : workspaceConfiguration.label,
- exportTimestamp: (new Date()).toISOString(),
+ exportTimestamp: new Date().toISOString(),
},
workspace: Object.assign(
- _pick(workspaceConfiguration, ['dataModelVersion', 'name', 'cols', 'rowHeight', 'targets']),
+ _pick(workspaceConfiguration, ["dataModelVersion", "name", "cols", "rowHeight", "targets"]),
{
- widgetKeys: _map(workspaceConfiguration.widgets, 'widgetKey'),
- layout: _map(workspaceConfiguration.layout,
- widgetLayout => _pick(widgetLayout, ['h', 'w', 'x', 'y'])),
- }
+ widgetKeys: _map(workspaceConfiguration.widgets, "widgetKey"),
+ layout: _map(workspaceConfiguration.layout, (widgetLayout) =>
+ _pick(widgetLayout, ["h", "w", "x", "y"]),
+ ),
+ },
),
- }
+ };
- const exportBlob = new Blob([JSON.stringify(exportRepresentation)],
- {type: "application/json;charset=utf-8"})
+ const exportBlob = new Blob([JSON.stringify(exportRepresentation)], {
+ type: "application/json;charset=utf-8",
+ });
FileSaver.saveAs(
exportBlob,
- `${_snakeCase(workspaceConfiguration.name)}-${_snakeCase(exportRepresentation.meta.exportName)}-layout.json`
- )
-}
+ `${_snakeCase(workspaceConfiguration.name)}-${_snakeCase(exportRepresentation.meta.exportName)}-layout.json`,
+ );
+};
/**
* Parses and returns a previously-exported workspace layout from the given
* file
*/
export const importRecommendedConfiguration = (recommendedLayout) => {
- const importedConfiguration = recommendedLayout
- importedConfiguration.widgets =
- _map(importedConfiguration.widgetKeys, key => widgetDescriptor(key))
- delete importedConfiguration.widgetKeys
-
- _each(importedConfiguration.layout, taskWidgetLayout => taskWidgetLayout.i = generateWidgetId())
- return (importedConfiguration)
-}
+ const importedConfiguration = recommendedLayout;
+ importedConfiguration.widgets = _map(importedConfiguration.widgetKeys, (key) =>
+ widgetDescriptor(key),
+ );
+ delete importedConfiguration.widgetKeys;
+
+ _each(
+ importedConfiguration.layout,
+ (taskWidgetLayout) => (taskWidgetLayout.i = generateWidgetId()),
+ );
+ return importedConfiguration;
+};
/**
* Parses and returns a previously-exported workspace layout from the given
@@ -343,50 +356,56 @@ export const importRecommendedConfiguration = (recommendedLayout) => {
*/
export const importWorkspaceConfiguration = (workspaceName, importFile) => {
return new Promise((resolve, reject) => {
- const reader = new FileReader()
+ const reader = new FileReader();
reader.onload = () => {
- let representation = null
+ let representation = null;
try {
- representation = JSON.parse(reader.result)
- }
- catch(error) {
- reject(new Error("unrecognized or unsupported import format"))
- return
+ representation = JSON.parse(reader.result);
+ } catch (error) {
+ reject(new Error("unrecognized or unsupported import format"));
+ return;
}
try {
- if (!representation || !representation.meta || representation.meta.exportFormatVersion !== 1) {
- reject(new Error("unrecognized or unsupported import format"))
- return
+ if (
+ !representation ||
+ !representation.meta ||
+ representation.meta.exportFormatVersion !== 1
+ ) {
+ reject(new Error("unrecognized or unsupported import format"));
+ return;
}
// Todo: validate import against a json schema rather than these piecemeal checks
if (representation.meta.targetWorkspace !== workspaceName) {
- reject(new Error("imported layout is not intended for this workspace"))
- return
+ reject(new Error("imported layout is not intended for this workspace"));
+ return;
}
if (!representation.workspace) {
- reject(new Error("imported layout appears to be corrupted"))
- return
+ reject(new Error("imported layout appears to be corrupted"));
+ return;
}
- const importedConfiguration = representation.workspace
- importedConfiguration.label = representation.meta.exportName
- importedConfiguration.widgets =
- _map(importedConfiguration.widgetKeys, key => widgetDescriptor(key))
- delete importedConfiguration.widgetKeys
-
- _each(importedConfiguration.layout, widgetLayout => widgetLayout.i = generateWidgetId())
- resolve(importedConfiguration)
- }
- catch(error) {
- reject(error)
+ const importedConfiguration = representation.workspace;
+ importedConfiguration.label = representation.meta.exportName;
+ importedConfiguration.widgets = _map(importedConfiguration.widgetKeys, (key) =>
+ widgetDescriptor(key),
+ );
+ delete importedConfiguration.widgetKeys;
+
+ _each(
+ importedConfiguration.layout,
+ (widgetLayout) => (widgetLayout.i = generateWidgetId()),
+ );
+ resolve(importedConfiguration);
+ } catch (error) {
+ reject(error);
}
- }
- reader.readAsText(importFile)
- })
-}
+ };
+ reader.readAsText(importFile);
+ });
+};
/**
* Determines the next available (non-duplicated) version of the given
@@ -394,15 +413,19 @@ export const importWorkspaceConfiguration = (workspaceName, importFile) => {
* existing configuration names, or else an altered unique version in the form
* of `preferredName (x)`
*/
-export const nextAvailableConfigurationLabel = function(preferredLabel, existingLabels, dupValue=1) {
+export const nextAvailableConfigurationLabel = function (
+ preferredLabel,
+ existingLabels,
+ dupValue = 1,
+) {
if (dupValue <= 1 && existingLabels.indexOf(preferredLabel) === -1) {
- return preferredLabel
+ return preferredLabel;
}
- const candidateName = `${preferredLabel} (${dupValue})`
+ const candidateName = `${preferredLabel} (${dupValue})`;
if (existingLabels.indexOf(candidateName) === -1) {
- return candidateName
+ return candidateName;
}
- return nextAvailableConfigurationLabel(preferredLabel, existingLabels, dupValue + 1)
-}
+ return nextAvailableConfigurationLabel(preferredLabel, existingLabels, dupValue + 1);
+};
diff --git a/src/setupTests.jsx b/src/setupTests.jsx
index da2cc88fb..4b955b776 100644
--- a/src/setupTests.jsx
+++ b/src/setupTests.jsx
@@ -1,13 +1,13 @@
import { vi } from "vitest";
import "@testing-library/jest-dom";
-import { Fragment } from "react";
+import { render as rtlRender } from "@testing-library/react";
import Enzyme, { shallow, render, mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import { createBrowserHistory } from "history";
-import { Router } from "react-router-dom";
-import { render as rtlRender } from "@testing-library/react";
-import { Provider } from "react-redux";
+import { Fragment } from "react";
import { IntlProvider } from "react-intl";
+import { Provider } from "react-redux";
+import { Router } from "react-router-dom";
import env from "../public/env.json";
@@ -33,10 +33,7 @@ const { initializeStore } = await import("./PersistedStore");
const reduxStore = initializeStore();
const routerHistory = createBrowserHistory();
-global.withProvider = (
- ui,
- { store = reduxStore, ...renderOptions } = {}
-) => {
+global.withProvider = (ui, { store = reduxStore, ...renderOptions } = {}) => {
function Wrapper({ children }) {
return (
@@ -52,53 +49,53 @@ global.withProvider = (
return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
};
-vi.mock('@rjsf/core/lib/components/widgets/SelectWidget', () => ({
+vi.mock("@rjsf/core/lib/components/widgets/SelectWidget", () => ({
__esModule: true,
}));
-vi.mock('@rjsf/core/lib/components/widgets/SelectWidget', () => ({
+vi.mock("@rjsf/core/lib/components/widgets/SelectWidget", () => ({
__esModule: true,
- default: () => null
+ default: () => null,
}));
-vi.mock('@rjsf/core/lib/components/widgets/TextWidget', () => ({
+vi.mock("@rjsf/core/lib/components/widgets/TextWidget", () => ({
__esModule: true,
- default: () => null
+ default: () => null,
}));
-vi.mock('@rjsf/core/lib/components/widgets/CheckboxWidget', () => ({
+vi.mock("@rjsf/core/lib/components/widgets/CheckboxWidget", () => ({
__esModule: true,
- default: () => null
+ default: () => null,
}));
-vi.mock('react-syntax-highlighter/dist/esm/languages/hljs/json', () => ({
+vi.mock("react-syntax-highlighter/dist/esm/languages/hljs/json", () => ({
__esModule: true,
- default: () => null
+ default: () => null,
}));
-vi.mock('react-syntax-highlighter/dist/esm/styles/hljs/agate', () => ({
+vi.mock("react-syntax-highlighter/dist/esm/styles/hljs/agate", () => ({
__esModule: true,
default: {
hljs: {
- background: ""
- }
- }
+ background: "",
+ },
+ },
}));
-vi.mock('react-syntax-highlighter', () => ({
+vi.mock("react-syntax-highlighter", () => ({
Light: {
- registerLanguage: () => null
- }
-}))
+ registerLanguage: () => null,
+ },
+}));
-vi.mock('react-syntax-highlighter/dist/esm/languages/hljs/xml', () => ({
+vi.mock("react-syntax-highlighter/dist/esm/languages/hljs/xml", () => ({
__esModule: true,
default: {
- xmlLang: ""
- }
+ xmlLang: "",
+ },
}));
-vi.mock('@nivo/bar', () => ({
+vi.mock("@nivo/bar", () => ({
__esModule: true,
- ResponsiveBar: () => null
+ ResponsiveBar: () => null,
}));
diff --git a/src/styles/base.css b/src/styles/base.css
index 18d92480e..79dcb36f0 100644
--- a/src/styles/base.css
+++ b/src/styles/base.css
@@ -13,7 +13,6 @@ body {
a {
@apply mr-text-green-light mr-no-underline mr-transition;
-
&.active,
&:hover {
@apply mr-text-blue-light;
diff --git a/src/styles/components/buttons.css b/src/styles/components/buttons.css
index f137e85f6..ddeba574d 100644
--- a/src/styles/components/buttons.css
+++ b/src/styles/components/buttons.css
@@ -2,7 +2,6 @@
.btn {
@apply mr-appearance-none mr-inline-block mr-text-center mr-text-base mr-font-medium mr-whitespace-nowrap mr-border-green-lighter mr-text-green-lighter mr-py-2 mr-px-6 mr-rounded-sm mr-border-2 mr-leading-normal mr-transition;
-
&:hover {
@apply mr-border-white mr-text-white;
}
@@ -94,14 +93,12 @@
&--green-fill {
@apply mr-bg-green-light-60 mr-border-0 mr-text-white mr-py-1 mr-px-8;
- ;
&:hover {
@apply mr-bg-green-light;
}
}
-
&--with-icon {
@apply mr-flex mr-items-center mr-justify-center;
}
@@ -121,16 +118,16 @@ input[type="checkbox"].mr-checkbox-toggle {
&:checked {
&:after {
- content: '';
+ content: "";
@apply mr-w-2.5 mr-h-1.5 mr-absolute mr-top-0 mr-bg-transparent mr-border-2 mr-border-white mr-border-t-0 mr-border-r-0 mr-ml-2px;
transform: rotate(-45deg);
- margin-top: 1.75px
+ margin-top: 1.75px;
}
}
&:indeterminate {
&:after {
- content: '';
+ content: "";
@apply mr-w-2 mr-h-1.5 mr-absolute mr-top-0 mr-bg-transparent mr-border-b-2 mr-border-white mr-mt-2px mr-ml-3px;
}
}
@@ -144,7 +141,7 @@ input[type="radio"].mr-radio {
}
&:checked::after {
- content: '';
+ content: "";
@apply mr-w-2 mr-h-2 mr-absolute mr-top-0 mr-bg-white mr-rounded-full;
margin-top: 0.1875rem;
margin-left: 0.1875rem;
diff --git a/src/styles/components/cards/widget.css b/src/styles/components/cards/widget.css
index 4deb234fc..1816efe92 100644
--- a/src/styles/components/cards/widget.css
+++ b/src/styles/components/cards/widget.css
@@ -13,7 +13,7 @@
@apply mr-select-none mr-cursor-move;
.mr-cards-inverse & {
- @apply mr-border-t-0;
+ @apply mr-border-t-0;
}
}
@@ -37,7 +37,7 @@
@apply mr-font-bold mr-text-lg mr-text-matisse-blue mr-my-2;
&:after {
- content: '';
+ content: "";
@apply mr-block mr-my-1 mr-w-20 mr-h-3px mr-bg-blue-light;
.mr-cards-inverse & {
@@ -70,7 +70,6 @@
}
.mr-cards-inverse & {
-
a::not(.mr-text-green-light) {
@apply mr-text-green-lighter;
@@ -79,6 +78,5 @@
}
}
}
-
}
}
diff --git a/src/styles/components/dropdown.css b/src/styles/components/dropdown.css
index 62ed7592e..5fcc825db 100644
--- a/src/styles/components/dropdown.css
+++ b/src/styles/components/dropdown.css
@@ -1,6 +1,4 @@
-$self-dropdown: .mr-dropdown;
-
-$(self-dropdown) {
+.mr-dropdown {
@apply mr-relative mr-inline-block;
&__button {
@@ -11,11 +9,11 @@ $(self-dropdown) {
@apply mr-absolute mr-z-200 mr-top-100 mr-left-0;
@screen md {
- $(self-dropdown)--right & {
+ .mr-dropdown--right & {
@apply mr-inset-auto mr-top-100 mr-right-0;
}
- $(self-dropdown)--offsetright & {
+ .mr-dropdown--offsetright & {
@apply mr-inset-auto mr-top-100 mr-right-screen20;
}
}
@@ -24,11 +22,11 @@ $(self-dropdown) {
&__main {
@apply mr-pt-3;
- $(self-dropdown)--flush & {
+ .mr-dropdown--flush & {
@apply mr-pt-0;
}
- $(self-dropdown)--fixed & {
+ .mr-dropdown--fixed & {
@apply mr-fixed;
}
}
@@ -36,7 +34,7 @@ $(self-dropdown) {
&__inner {
@apply mr-relative mr-rounded-sm mr-shadow mr-bg-blue-dark mr-pl-4 mr-pr-0 mr-py-4 mr-text-white mr-font-normal;
- $(self-dropdown)--flush & {
+ .mr-dropdown--flush & {
@apply mr-rounded-t-none;
}
}
@@ -44,15 +42,15 @@ $(self-dropdown) {
&__arrow {
@apply mr-absolute mr-bottom-100 mr-ml-2 mr-left-0 mr-h-2 mr-fill-blue-dark;
- $(self-dropdown)--flush & {
+ .mr-dropdown--flush & {
@apply mr-hidden;
}
- $(self-dropdown)--right & {
+ .mr-dropdown--right & {
@apply mr-ml-0 mr-mr-2 mr-inset-auto mr-bottom-100 mr-right-0;
}
- $(self-dropdown)--offsetright & {
+ .mr-dropdown--offsetright & {
@apply mr-ml-0 mr-mr-2 mr-inset-auto mr-bottom-100 mr-right-screen20;
}
}
diff --git a/src/styles/components/forms.css b/src/styles/components/forms.css
index 5f6e095e4..fd0ba847c 100644
--- a/src/styles/components/forms.css
+++ b/src/styles/components/forms.css
@@ -34,35 +34,34 @@
}
&--2-col .fieldset {
- @apply md:mr-grid md:mr-grid-columns-2 md:mr-grid-gap-8;
+ @apply md:mr-grid md:mr-grid-columns-2 md:mr-grid-gap-8;
- legend {
- @apply md:mr-col-span-2;
- }
+ legend {
+ @apply md:mr-col-span-2;
+ }
- .field {
- @apply md:mr-mb-0;
- }
+ .field {
+ @apply md:mr-mb-0;
+ }
}
&--modified-2-col {
- @apply md:mr-w-full;
+ @apply md:mr-w-full;
+
+ .notification-email {
+ @apply md:mr-w-1/2 md:mr-pr-4;
+ }
- .notification-email {
- @apply md:mr-w-1/2 md:mr-pr-4;
+ .notification-subscriptions {
+ .fieldset {
+ @apply md:mr-grid md:mr-grid-cols-2 md:mr-gap-x-8 md:mr-grid-flow-row;
}
-
- .notification-subscriptions {
- .fieldset {
- @apply md:mr-grid md:mr-grid-cols-2 md:mr-gap-x-8 md:mr-grid-flow-row;
- }
-
- .control-label {
- @apply md:mr-cursor-default;
- }
+
+ .control-label {
+ @apply md:mr-cursor-default;
}
-
}
+ }
.custom-basemaps-item {
.fieldset {
@@ -79,7 +78,9 @@
}
}
-.control-label, .form-group.field-boolean label span, .form-group.field-number label span {
+.control-label,
+.form-group.field-boolean label span,
+.form-group.field-number label span {
@apply mr-cursor-pointer mr-inline-block mr-mb-2 mr-font-medium mr-text-white;
.field-hide-label & {
@@ -90,7 +91,8 @@
@apply mr-text-red-light;
}
- &.required, .required {
+ &.required,
+ .required {
@apply mr-text-red-light mr-ml-px;
}
}
@@ -107,7 +109,7 @@
}
}
-.form-group.field-boolean .checkbox label input[type='checkbox'] {
+.form-group.field-boolean .checkbox label input[type="checkbox"] {
@apply mr-mr-2;
&:checked:after {
@@ -144,8 +146,9 @@
}
}
-select option { /* for Firefox select when active */
- background-color: #1B3338;
+select option {
+ /* for Firefox select when active */
+ background-color: #1b3338;
color: white;
}
@@ -168,7 +171,8 @@ select option { /* for Firefox select when active */
p {
@apply mr-mb-4;
}
- h3, h4 {
+ h3,
+ h4 {
@apply mr-mb-3;
}
}
@@ -185,7 +189,7 @@ select option { /* for Firefox select when active */
@apply mr-cursor-pointer;
}
- [type='checkbox'] {
+ [type="checkbox"] {
@apply mr-appearance-none mr-outline-none mr-relative mr-shadow-inner mr-bg-transparent mr-border mr-border-white mr-w-4 mr-h-4 mr-align-text-bottom;
&:hover {
@@ -194,22 +198,22 @@ select option { /* for Firefox select when active */
&:checked {
&:after {
- content: '';
+ content: "";
@apply mr-w-2.5 mr-h-1.5 mr-absolute mr-top-0 mr-bg-transparent mr-border-2 mr-border-white mr-border-t-0 mr-border-r-0 mr-ml-2px;
transform: rotate(-45deg);
- margin-top: 1.75px
+ margin-top: 1.75px;
}
}
&:indeterminate {
&:after {
- content: '';
+ content: "";
@apply mr-w-2 mr-h-1.5 mr-absolute mr-top-0 mr-bg-transparent mr-border-b-2 mr-border-white mr-mt-2px mr-ml-3px;
}
}
}
- [type='radio'] {
+ [type="radio"] {
@apply mr-appearance-none mr-outline-none mr-relative mr-shadow-inner mr-bg-transparent mr-border mr-border-white mr-rounded-full mr-w-4 mr-h-4 mr-mr-1.5 mr-align-text-bottom;
&:hover {
@@ -217,7 +221,7 @@ select option { /* for Firefox select when active */
}
&:checked::after {
- content: '';
+ content: "";
@apply mr-w-2 mr-h-2 mr-absolute mr-top-0 mr-bg-white mr-rounded-full;
margin-top: 0.1875rem;
margin-left: 0.1875rem;
@@ -238,21 +242,26 @@ select option { /* for Firefox select when active */
@apply mr-font-medium;
}
- &::-webkit-input-placeholder { /* Chrome/Opera/Safari */
+ &::-webkit-input-placeholder {
+ /* Chrome/Opera/Safari */
@apply mr-font-medium;
}
- &::-moz-placeholder { /* Firefox 19+ */
+ &::-moz-placeholder {
+ /* Firefox 19+ */
@apply mr-font-medium;
}
- &:-ms-input-placeholder { /* IE 10+ */
+ &:-ms-input-placeholder {
+ /* IE 10+ */
@apply mr-font-medium;
}
- &:-moz-placeholder { /* Firefox 18- */
+ &:-moz-placeholder {
+ /* Firefox 18- */
@apply mr-font-medium;
}
}
.mr-select {
@apply mr-appearance-none mr-outline-none mr-pl-2 mr-bg-black-15 mr-border-none mr-rounded mr-shadow-inner;
- background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMC4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCA0LjkgMTAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDQuOSAxMDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCgkuc3Qwe2ZpbGw6I2ZmZmZmZjt9DQo8L3N0eWxlPg0KPHRpdGxlPmFycm93czwvdGl0bGU+DQo8cG9seWdvbiBjbGFzcz0ic3QwIiBwb2ludHM9IjEuNCw0LjcgMi41LDMuMiAzLjUsNC43ICIvPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSIzLjUsNS4zIDIuNSw2LjggMS40LDUuMyAiLz4NCjwvc3ZnPg==) no-repeat right 2px center rgba(0, 0, 0, 0.15);
+ background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMC4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCA0LjkgMTAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDQuOSAxMDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCgkuc3Qwe2ZpbGw6I2ZmZmZmZjt9DQo8L3N0eWxlPg0KPHRpdGxlPmFycm93czwvdGl0bGU+DQo8cG9seWdvbiBjbGFzcz0ic3QwIiBwb2ludHM9IjEuNCw0LjcgMi41LDMuMiAzLjUsNC43ICIvPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSIzLjUsNS4zIDIuNSw2LjggMS40LDUuMyAiLz4NCjwvc3ZnPg==)
+ no-repeat right 2px center rgba(0, 0, 0, 0.15);
}
diff --git a/src/styles/components/lists.css b/src/styles/components/lists.css
index d31b5e022..0f1ea3766 100644
--- a/src/styles/components/lists.css
+++ b/src/styles/components/lists.css
@@ -4,7 +4,7 @@
li {
&:not(:last-of-type) {
@apply mr-mr-6;
-
+
@apply xl:mr-mr-8;
}
@@ -62,7 +62,7 @@
@apply mr-flex mr-items-center mr-py-3;
&.active::before {
- content: '•';
+ content: "•";
@apply mr-text-xl mr-leading-none mr-text-yellow mr-mr-1;
}
}
diff --git a/src/styles/components/sections.css b/src/styles/components/sections.css
index da2fc0b34..b648445ce 100644
--- a/src/styles/components/sections.css
+++ b/src/styles/components/sections.css
@@ -1,7 +1,8 @@
.mr-section {
@apply mr-my-8;
- &--as-header, &__header {
+ &--as-header,
+ &__header {
@apply mr-my-8 mr-text-matisse-blue mr-uppercase mr-font-medium;
.mr-h4 {
diff --git a/src/styles/components/timeline.css b/src/styles/components/timeline.css
index 5cb2b9db8..553569372 100644
--- a/src/styles/components/timeline.css
+++ b/src/styles/components/timeline.css
@@ -11,7 +11,7 @@
@apply mr-relative mr-mb-6;
&:before {
- content: '';
+ content: "";
@apply mr-absolute mr-top-0 mr-left-0 mr--ml-8 mr-w-4 mr-h-4 mr-bg-blue-light mr-rounded-full;
}
diff --git a/src/styles/index.css b/src/styles/index.css
index 15b8d08b4..9d52498e5 100644
--- a/src/styles/index.css
+++ b/src/styles/index.css
@@ -1,15 +1,15 @@
-@import url('https://fonts.googleapis.com/css?family=Roboto:300,400,500,700');
+@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500,700");
/**
* This injects Tailwind's base styles, which is a combination of Normalize.css
* and some additional base styles.
*/
-@import 'tailwindcss/base';
+@import "tailwindcss/base";
/**
* This injects any component classes registered by plugins.
*/
-@import 'tailwindcss/components';
+@import "tailwindcss/components";
/**
* Here you would add any of your custom component classes; stuff that you'd
@@ -21,47 +21,47 @@
* @import "components/buttons";
* @import "components/forms";
*/
-@import 'components/buttons';
-@import 'components/dropdown';
-@import 'components/lists';
-@import 'components/cards/widget';
-@import 'components/cards/challenge';
-@import 'components/cards/project';
-@import 'components/sections';
-@import 'components/forms';
-@import 'components/ticker';
-@import 'pages/leaderboard-row';
-@import 'components/timeline';
-@import 'components/badges';
-@import 'components/bounds';
+@import "components/buttons";
+@import "components/dropdown";
+@import "components/lists";
+@import "components/cards/widget";
+@import "components/cards/challenge";
+@import "components/cards/project";
+@import "components/sections";
+@import "components/forms";
+@import "components/ticker";
+@import "pages/leaderboard-row";
+@import "components/timeline";
+@import "components/badges";
+@import "components/bounds";
/**
* Vendor styles
*/
-@import 'vendor/react-burger-menu';
-@import 'vendor/react-table';
-@import 'vendor/react-datepicker';
-@import 'vendor/leaflet-areaselect';
+@import "vendor/react-burger-menu";
+@import "vendor/react-table";
+@import "vendor/react-datepicker";
+@import "vendor/leaflet-areaselect";
/**
* This injects all of Tailwind's utility classes, generated based on your
* config file.
*/
- @import "tailwindcss/utilities";
+@import "tailwindcss/utilities";
/**
* Here you would add any custom utilities you need that don't come out of the
* box with Tailwind.
*/
-@import 'utilities/typography';
-@import 'utilities/positions';
-@import 'utilities/backgrounds';
-@import 'utilities/rules';
-@import 'utilities/statuses';
+@import "utilities/typography";
+@import "utilities/positions";
+@import "utilities/backgrounds";
+@import "utilities/rules";
+@import "utilities/statuses";
/**
* Base styles to setup your application. These are typically the lowest level
* selectors such as and classes.
*/
-@import 'base';
+@import "base";
diff --git a/src/styles/pages/leaderboard-row.css b/src/styles/pages/leaderboard-row.css
index 931846309..2aa316fa2 100644
--- a/src/styles/pages/leaderboard-row.css
+++ b/src/styles/pages/leaderboard-row.css
@@ -1,10 +1,8 @@
.mr-leaderboard-row {
@apply mr-bg-black-10 mr-text-white mr-rounded mr-p-4 mr-shadow mr-border-transparent mr-border-2;
-
@apply md:mr-p-6;
-
&--active {
@apply mr-border-green-lighter mr-bg-black-10 mr-shadow-inner;
}
diff --git a/src/styles/utilities/backgrounds.css b/src/styles/utilities/backgrounds.css
index fe3ceecc6..775a15332 100644
--- a/src/styles/utilities/backgrounds.css
+++ b/src/styles/utilities/backgrounds.css
@@ -1,116 +1,117 @@
.mr-bg-hero {
- background-image: url('../../../images/bg-hero.jpg');
+ background-image: url("../../../images/bg-hero.jpg");
@media (-webkit-min-device-pixel-ratio: 2) {
- background-image: url('../../../images/bg-hero@2x.jpg');
+ background-image: url("../../../images/bg-hero@2x.jpg");
}
}
.mr-bg-map {
- background-image: url('../../../images/bg-map.jpg');
+ background-image: url("../../../images/bg-map.jpg");
@media (-webkit-min-device-pixel-ratio: 2) {
- background-image: url('../../../images/bg-map@2x.jpg');
+ background-image: url("../../../images/bg-map@2x.jpg");
}
}
.mr-bg-fireworks {
- background-image: url('../../../images/bg-fireworks-left.svg'), url('../../../images/bg-fireworks-right.svg');
+ background-image: url("../../../images/bg-fireworks-left.svg"),
+ url("../../../images/bg-fireworks-right.svg");
background-position: left top, right top;
background-repeat: no-repeat;
}
.mr-bg-world-map {
- background-image: url('../../../images/bg-world-map.svg');
+ background-image: url("../../../images/bg-world-map.svg");
}
.mr-bg-settings {
- background-image: url('../../../images/bg-settings.svg');
+ background-image: url("../../../images/bg-settings.svg");
}
.mr-bg-cityscape {
- background-image: url('../../../images/bg-cityscape.svg');
+ background-image: url("../../../images/bg-cityscape.svg");
}
.mr-bg-home {
- background-image: url('../../../images/home.svg');
+ background-image: url("../../../images/home.svg");
background-position: right bottom;
background-repeat: no-repeat;
background-size: contain;
}
.mr-bg-lines {
- background-image: url('../../../images/lines.svg');
+ background-image: url("../../../images/lines.svg");
background-position: right bottom;
background-repeat: no-repeat;
background-size: cover;
}
.mr-bg-globe {
- background-image: url('../../../images/globe.svg');
+ background-image: url("../../../images/globe.svg");
background-position: left bottom;
background-repeat: no-repeat;
background-size: cover;
}
.mr-bg-skyline {
- background-image: url('../../../images/skyline.svg');
+ background-image: url("../../../images/skyline.svg");
background-position: center bottom;
background-size: contain;
}
.mr-bg-teams {
- background-image: url('../../../images/teams.svg');
+ background-image: url("../../../images/teams.svg");
background-position: left center;
background-repeat: no-repeat;
background-size: contain;
}
.mr-bg-map {
- background-image: url('../../../images/bg-map.svg');
+ background-image: url("../../../images/bg-map.svg");
background-position: center;
background-repeat: no-repeat;
background-size: contain;
}
.mr-bg-space {
- background-image: url('../../../images/bg-space.jpg');
+ background-image: url("../../../images/bg-space.jpg");
background-position: center;
background-repeat: no-repeat;
background-size: cover;
@media (-webkit-min-device-pixel-ratio: 2) {
- background-image: url('../../../images/bg-space@2x.jpg');
+ background-image: url("../../../images/bg-space@2x.jpg");
}
}
.mr-bg-expert {
- background-image: url('../../../images/expert.svg');
+ background-image: url("../../../images/expert.svg");
background-position: center;
background-repeat: no-repeat;
background-size: contain;
}
.mr-bg-find {
- background-image: url('../../../images/find.svg');
+ background-image: url("../../../images/find.svg");
background-position: center;
background-repeat: no-repeat;
background-size: contain;
}
.mr-bg-highway {
- background-image: url('../../../images/bg-highway.jpg');
+ background-image: url("../../../images/bg-highway.jpg");
background-position: center;
background-repeat: no-repeat;
background-size: cover;
@media (-webkit-min-device-pixel-ratio: 2) {
- background-image: url('../../../images/bg-highway@2x.jpg');
+ background-image: url("../../../images/bg-highway@2x.jpg");
}
}
.mr-bg-achievements {
- background-image: url('../../../images/trophy.svg');
+ background-image: url("../../../images/trophy.svg");
background-position: center;
background-size: contain;
background-repeat: no-repeat;
diff --git a/src/styles/utilities/typography.css b/src/styles/utilities/typography.css
index 73169f8cf..8529e6540 100644
--- a/src/styles/utilities/typography.css
+++ b/src/styles/utilities/typography.css
@@ -134,11 +134,14 @@ h4,
@apply mr-my-4;
}
- li > p, li > ul, li > ol {
+ li > p,
+ li > ul,
+ li > ol {
@apply mr-my-2;
}
- li.task-list-item { /* checklist items */
+ li.task-list-item {
+ /* checklist items */
@apply mr-list-none mr--ml-4;
}
@@ -153,7 +156,8 @@ h4,
@apply mr-bg-black-15;
}
- ol, ul {
+ ol,
+ ul {
@apply mr-ml-6;
}
diff --git a/src/styles/vendor/leaflet-areaselect.css b/src/styles/vendor/leaflet-areaselect.css
index dc255a8d6..884c47c03 100644
--- a/src/styles/vendor/leaflet-areaselect.css
+++ b/src/styles/vendor/leaflet-areaselect.css
@@ -1,15 +1,15 @@
.leaflet-areaselect-shade {
- position: absolute;
- background: rgba(0,0,0, 0.4);
+ position: absolute;
+ background: rgba(0, 0, 0, 0.4);
}
.leaflet-areaselect-handle {
- position: absolute;
- background: #fff;
- border: 1px solid #666;
- -moz-box-shadow: 1px 1px rgba(0,0,0, 0.2);
- -webkit-box-shadow: 1px 1px rgba(0,0,0, 0.2);
- box-shadow: 1px 1px rgba(0,0,0, 0.2);
- width: 14px;
- height: 14px;
- cursor: move;
+ position: absolute;
+ background: #fff;
+ border: 1px solid #666;
+ -moz-box-shadow: 1px 1px rgba(0, 0, 0, 0.2);
+ -webkit-box-shadow: 1px 1px rgba(0, 0, 0, 0.2);
+ box-shadow: 1px 1px rgba(0, 0, 0, 0.2);
+ width: 14px;
+ height: 14px;
+ cursor: move;
}
diff --git a/src/styles/vendor/react-datepicker.css b/src/styles/vendor/react-datepicker.css
index dd7ce2f04..44c26d8c0 100644
--- a/src/styles/vendor/react-datepicker.css
+++ b/src/styles/vendor/react-datepicker.css
@@ -1,7 +1,8 @@
-@import 'react-datepicker/dist/react-datepicker';
+@import "react-datepicker/dist/react-datepicker";
-.react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle, .react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle::before {
- border-bottom-color: theme('colors.blue-darker');
+.react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle,
+.react-datepicker-popper[data-placement^="bottom"] .react-datepicker__triangle::before {
+ border-bottom-color: theme("colors.blue-darker");
}
.react-datepicker {
@@ -19,18 +20,18 @@
}
&__navigation--previous {
- border-right-color: theme('colors.green-lighter');
+ border-right-color: theme("colors.green-lighter");
&:hover {
- border-right-color: theme('colors.white');
+ border-right-color: theme("colors.white");
}
}
&__navigation--next {
- border-left-color: theme('colors.green-lighter');
+ border-left-color: theme("colors.green-lighter");
&:hover {
- border-left-color: theme('colors.white');
+ border-left-color: theme("colors.white");
}
}
diff --git a/src/styles/vendor/react-table.css b/src/styles/vendor/react-table.css
index b524112e8..1844ffad8 100644
--- a/src/styles/vendor/react-table.css
+++ b/src/styles/vendor/react-table.css
@@ -1,4 +1,4 @@
-@import 'react-table-6/react-table';
+@import "react-table-6/react-table";
.ReactTable {
@apply mr-border-none mr-text-white;
@@ -22,11 +22,11 @@
line-height: 1rem;
&.-sort-asc {
- box-shadow: inset 0 2px 0 0 theme('colors.white');
+ box-shadow: inset 0 2px 0 0 theme("colors.white");
}
&.-sort-desc {
- box-shadow: inset 0 -2px 0 0 theme('colors.white');
+ box-shadow: inset 0 -2px 0 0 theme("colors.white");
}
}
@@ -66,7 +66,7 @@
.-btn {
@apply mr-py-2 mr-px-4 mr-rounded mr-border-2 mr-bg-transparent mr-font-medium mr-text-green-lighter mr-transition;
- border: theme('borderWidth.2') solid theme('colors.current');
+ border: theme("borderWidth.2") solid theme("colors.current");
&[disabled] {
@apply mr-opacity-25;
@@ -77,7 +77,8 @@
}
}
- input, select {
+ input,
+ select {
@apply mr-bg-black-15 mr-border-none mr-rounded mr-shadow-inner;
}
@@ -94,7 +95,7 @@
.rt-expander {
&:after {
- border-top-color: #FFF;
+ border-top-color: #fff;
}
}
diff --git a/src/tailwind.config.js b/src/tailwind.config.js
index 602df8525..582a65dc5 100644
--- a/src/tailwind.config.js
+++ b/src/tailwind.config.js
@@ -1,158 +1,158 @@
-import visuallyHidden from 'tailwindcss-visuallyhidden';
-import owl from 'tailwindcss-owl';
-import grid from 'tailwindcss-grid';
-import transition from 'tailwindcss-transition';
-import gradients from 'tailwindcss-gradients';
-import transforms from 'tailwindcss-transforms';
+import gradients from "tailwindcss-gradients";
+import grid from "tailwindcss-grid";
+import owl from "tailwindcss-owl";
+import transforms from "tailwindcss-transforms";
+import transition from "tailwindcss-transition";
+import visuallyHidden from "tailwindcss-visuallyhidden";
export default {
content: [
- './index.html',
- './public/**/*.html',
- './src/components/**/*.{js,jsx,ts,tsx}',
- './src/pages/**/*.{js,jsx,ts,tsx}'
+ "./index.html",
+ "./public/**/*.html",
+ "./src/components/**/*.{js,jsx,ts,tsx}",
+ "./src/pages/**/*.{js,jsx,ts,tsx}",
],
- prefix: 'mr-',
+ prefix: "mr-",
theme: {
colors: {
- transparent: 'transparent',
- current: 'currentColor',
- white: '#fff',
- 'white-40': 'rgba(255, 255, 255, .4)',
- 'white-50': 'rgba(255, 255, 255, .5)',
- 'white-25': 'rgba(255, 255, 255, .25)',
- 'white-15': 'rgba(255, 255, 255, .15)',
- 'white-10': 'rgba(255, 255, 255, .1)',
- 'white-04': 'rgba(255, 255, 255, .04)',
- 'off-white': '#FAF7F2',
- black: '#222',
- 'black-5': 'rgba(0, 0, 0, .05)',
- 'black-10': 'rgba(0, 0, 0, .1)',
- 'black-15': 'rgba(0, 0, 0, .15)',
- 'black-25': 'rgba(0, 0, 0, .25)',
- 'black-40': 'rgba(0, 0, 0, .4)',
- 'black-50': 'rgba(0, 0, 0, .5)',
- 'black-75': 'rgba(0, 0, 0, .75)',
- grey: '#737373',
- 'grey-light': '#BDB8AE',
- 'grey-light-more': '#D8D8D8',
- 'grey-lighter': '#F2EFE9',
- 'grey-lighter-10': 'rgba(242, 239, 233, .1)',
- 'grey-lighter-50': 'rgba(242, 239, 233, .5)',
- 'grey-leaflet': '#666',
- green: '#47725F',
- 'green-light': '#7EBC89',
- 'green-light-60': 'rgba(126, 188, 137, .6)',
- 'green-light-30': 'rgba(126, 188, 137, .3)',
- 'green-lighter': '#91F3A2',
- 'green-dark': '#315E4A',
- 'green-dark-75': 'rgba(74, 118, 98, .75)',
- blue: '#172E51',
- 'blue-75': 'rgba(23, 46, 81, .75)',
- 'blue-light': '#496B94',
- 'blue-light-25': 'rgba(73, 107, 148, 0.25)',
- 'blue-light-75': 'rgba(73, 107, 148, 0.75)',
- 'blue-dark': '#182841',
- 'blue-leaflet': '#2E85CB',
- 'blue-dark-50': 'rgba(24, 40, 65, .5)',
- 'blue-dark-75': 'rgba(24, 40, 65, .75)',
- 'blue-dark-90': 'rgba(24, 40, 65, .9)',
- 'blue-darker': '#0e192a',
- 'blue-firefly': '#081425',
- 'blue-firefly-75': 'rgba(8, 20, 37, .75)',
- 'blue-cloudburst': '#1D304D',
- 'blue-rhino': '#2A415C',
- 'blue-sttropaz': '#264E90',
- yellow: '#FFFD86',
- 'yellow-75': 'rgba(255, 253, 134, .75)',
- 'dark-yellow': '#F1E15B',
- 'gold': '#FFD700',
- mango: '#F7BB59',
- 'mango-60': 'rgba(247,187,89, .6)',
- 'mango-30': 'rgba(247,187,89, .3)',
- turquoise: '#17FFF3',
- pink: '#E87CE0',
- 'pink-light': '#FFB2F0',
- cerise: '#DA3ACB',
- rosebud: '#FFA8A8',
- 'pink-light-10': 'rgba(255,178,240,0.1)',
- 'pink-light-50': 'rgba(255,178,240,0.5)',
- twitter: '#1DA1F2',
- mastodon: '#595AFF',
- orange: '#f7bb59',
- 'orange-jaffa': '#EA8433',
- red: '#CA484C',
- 'red-light': '#FF5E63',
- purple: '#959DFF',
- teal: '#6FB3B8',
- 'teal-40': 'rgba(111, 179, 184, 0.40)',
- rose: '#F89294',
- 'picton-blue': '#50B7E5',
- 'picton-blue-light': '#43D5FA',
- 'lavender-rose': '#FF98F7',
- 'matisse-blue': '#6FB3B8',
- 'blue-viking': '#65D2DA',
- 'matisse-blue-50': 'rgba(26,104,139, 0.5)',
- 'yellow-sand': '#CCB186',
- 'wild-strawberry': '#FF349C',
+ transparent: "transparent",
+ current: "currentColor",
+ white: "#fff",
+ "white-40": "rgba(255, 255, 255, .4)",
+ "white-50": "rgba(255, 255, 255, .5)",
+ "white-25": "rgba(255, 255, 255, .25)",
+ "white-15": "rgba(255, 255, 255, .15)",
+ "white-10": "rgba(255, 255, 255, .1)",
+ "white-04": "rgba(255, 255, 255, .04)",
+ "off-white": "#FAF7F2",
+ black: "#222",
+ "black-5": "rgba(0, 0, 0, .05)",
+ "black-10": "rgba(0, 0, 0, .1)",
+ "black-15": "rgba(0, 0, 0, .15)",
+ "black-25": "rgba(0, 0, 0, .25)",
+ "black-40": "rgba(0, 0, 0, .4)",
+ "black-50": "rgba(0, 0, 0, .5)",
+ "black-75": "rgba(0, 0, 0, .75)",
+ grey: "#737373",
+ "grey-light": "#BDB8AE",
+ "grey-light-more": "#D8D8D8",
+ "grey-lighter": "#F2EFE9",
+ "grey-lighter-10": "rgba(242, 239, 233, .1)",
+ "grey-lighter-50": "rgba(242, 239, 233, .5)",
+ "grey-leaflet": "#666",
+ green: "#47725F",
+ "green-light": "#7EBC89",
+ "green-light-60": "rgba(126, 188, 137, .6)",
+ "green-light-30": "rgba(126, 188, 137, .3)",
+ "green-lighter": "#91F3A2",
+ "green-dark": "#315E4A",
+ "green-dark-75": "rgba(74, 118, 98, .75)",
+ blue: "#172E51",
+ "blue-75": "rgba(23, 46, 81, .75)",
+ "blue-light": "#496B94",
+ "blue-light-25": "rgba(73, 107, 148, 0.25)",
+ "blue-light-75": "rgba(73, 107, 148, 0.75)",
+ "blue-dark": "#182841",
+ "blue-leaflet": "#2E85CB",
+ "blue-dark-50": "rgba(24, 40, 65, .5)",
+ "blue-dark-75": "rgba(24, 40, 65, .75)",
+ "blue-dark-90": "rgba(24, 40, 65, .9)",
+ "blue-darker": "#0e192a",
+ "blue-firefly": "#081425",
+ "blue-firefly-75": "rgba(8, 20, 37, .75)",
+ "blue-cloudburst": "#1D304D",
+ "blue-rhino": "#2A415C",
+ "blue-sttropaz": "#264E90",
+ yellow: "#FFFD86",
+ "yellow-75": "rgba(255, 253, 134, .75)",
+ "dark-yellow": "#F1E15B",
+ gold: "#FFD700",
+ mango: "#F7BB59",
+ "mango-60": "rgba(247,187,89, .6)",
+ "mango-30": "rgba(247,187,89, .3)",
+ turquoise: "#17FFF3",
+ pink: "#E87CE0",
+ "pink-light": "#FFB2F0",
+ cerise: "#DA3ACB",
+ rosebud: "#FFA8A8",
+ "pink-light-10": "rgba(255,178,240,0.1)",
+ "pink-light-50": "rgba(255,178,240,0.5)",
+ twitter: "#1DA1F2",
+ mastodon: "#595AFF",
+ orange: "#f7bb59",
+ "orange-jaffa": "#EA8433",
+ red: "#CA484C",
+ "red-light": "#FF5E63",
+ purple: "#959DFF",
+ teal: "#6FB3B8",
+ "teal-40": "rgba(111, 179, 184, 0.40)",
+ rose: "#F89294",
+ "picton-blue": "#50B7E5",
+ "picton-blue-light": "#43D5FA",
+ "lavender-rose": "#FF98F7",
+ "matisse-blue": "#6FB3B8",
+ "blue-viking": "#65D2DA",
+ "matisse-blue-50": "rgba(26,104,139, 0.5)",
+ "yellow-sand": "#CCB186",
+ "wild-strawberry": "#FF349C",
},
screens: {
- sm: '576px',
- md: '768px',
- lg: '1024px',
- xl: '1280px',
+ sm: "576px",
+ md: "768px",
+ lg: "1024px",
+ xl: "1280px",
retina: [
- { 'min-device-pixel-ratio': 2 },
- { 'min-resolution': '192dpi' },
- { 'min-resolution': '2dppx' },
+ { "min-device-pixel-ratio": 2 },
+ { "min-resolution": "192dpi" },
+ { "min-resolution": "2dppx" },
],
},
fontFamily: {
sans: [
- 'Roboto',
- 'system-ui',
- 'BlinkMacSystemFont',
- '-apple-system',
- 'Segoe UI',
- 'Roboto',
- 'Oxygen',
- 'Ubuntu',
- 'Cantarell',
- 'Fira Sans',
- 'Droid Sans',
- 'Helvetica Neue',
- 'sans-serif',
+ "Roboto",
+ "system-ui",
+ "BlinkMacSystemFont",
+ "-apple-system",
+ "Segoe UI",
+ "Roboto",
+ "Oxygen",
+ "Ubuntu",
+ "Cantarell",
+ "Fira Sans",
+ "Droid Sans",
+ "Helvetica Neue",
+ "sans-serif",
],
mono: [
- 'IBM Plex Mono',
- 'Menlo',
- 'Monaco',
- 'Consolas',
- 'Liberation Mono',
- 'Courier New',
- 'monospace',
+ "IBM Plex Mono",
+ "Menlo",
+ "Monaco",
+ "Consolas",
+ "Liberation Mono",
+ "Courier New",
+ "monospace",
],
- bungee: 'Bungee',
- bungeeshade: 'Bungee Shade',
- bungeeinline: 'Bungee Inline',
+ bungee: "Bungee",
+ bungeeshade: "Bungee Shade",
+ bungeeinline: "Bungee Inline",
},
fontSize: {
- xxs: '.625rem', // 10px
- xs: '.75rem', // 12px
- sm: '.875rem', // 14px
- base: '1rem', // 16px
- md: '1.125rem', //18px
- lg: '1.25rem', // 20px
- xl: '1.375rem', // 22px
- '2xl': '1.5rem', // 24px
- '3xl': '1.75rem', // 28px
- '4xl': '2rem', // 32px
- '5xl': '2.5rem', // 40px
- '6xl': '3.75rem', // 60px
- '8xl': '4rem', // 64px
+ xxs: ".625rem", // 10px
+ xs: ".75rem", // 12px
+ sm: ".875rem", // 14px
+ base: "1rem", // 16px
+ md: "1.125rem", //18px
+ lg: "1.25rem", // 20px
+ xl: "1.375rem", // 22px
+ "2xl": "1.5rem", // 24px
+ "3xl": "1.75rem", // 28px
+ "4xl": "2rem", // 32px
+ "5xl": "2.5rem", // 40px
+ "6xl": "3.75rem", // 60px
+ "8xl": "4rem", // 64px
},
fontWeight: {
@@ -170,385 +170,379 @@ export default {
},
letterSpacing: {
- tight: '-0.05em',
- normal: '0',
- wide: '0.05em',
+ tight: "-0.05em",
+ normal: "0",
+ wide: "0.05em",
},
- textColor: theme => theme('colors'),
+ textColor: (theme) => theme("colors"),
- backgroundColor: theme => theme('colors'),
+ backgroundColor: (theme) => theme("colors"),
- placeholderColor: theme => theme('colors'),
+ placeholderColor: (theme) => theme("colors"),
backgroundSize: {
- auto: 'auto',
- cover: 'cover',
- contain: 'contain',
+ auto: "auto",
+ cover: "cover",
+ contain: "contain",
},
borderWidth: {
- DEFAULT: '1px',
- '0': '0',
- '2': '2px',
- '4': '4px',
- '8': '8px',
+ DEFAULT: "1px",
+ 0: "0",
+ 2: "2px",
+ 4: "4px",
+ 8: "8px",
},
- borderColor: theme => Object.assign(
- { DEFAULT: theme('colors.grey-light') },
- theme('colors')
- ),
+ borderColor: (theme) => Object.assign({ DEFAULT: theme("colors.grey-light") }, theme("colors")),
borderRadius: {
- none: '0',
- sm: '2px',
- DEFAULT: '.25rem',
- lg: '.5rem',
- full: '9999px',
+ none: "0",
+ sm: "2px",
+ DEFAULT: ".25rem",
+ lg: ".5rem",
+ full: "9999px",
},
width: {
- auto: 'auto',
- px: '1px',
- '9px': '9px',
- '10px': '10px',
- '15px': '15px',
- '1': '0.25rem',
- '1.5': '0.375rem',
- '2': '0.5rem',
- '2.5': '0.625rem',
- '3': '0.75rem',
- '4': '1rem',
- '5': '1.25rem',
- '6': '1.5rem',
- '8': '2rem',
- '9': '2.25rem',
- '10': '2.5rem',
- '12': '3rem',
- '16': '4rem',
- '20': '5rem',
- '24': '6rem',
- '30': '7.5rem',
- '32': '8rem',
- '40': '10rem',
- '48': '12rem',
- '52': '13rem',
- '56': '14rem',
- '64': '16rem',
- '76': '19rem',
- '88': '22rem',
- '96': '24rem',
- '1/2': '50%',
- '1/3': '33.33333%',
- '2/3': '66.66667%',
- '1/4': '25%',
- '3/4': '75%',
- '1/5': '20%',
- '2/5': '40%',
- '3/5': '60%',
- '4/5': '80%',
- '9/10': '90%',
- '1/6': '16.66667%',
- '5/6': '83.33333%',
- sm: '30rem',
- md: '40rem',
- full: '100%',
- screen: '100vw',
+ auto: "auto",
+ px: "1px",
+ "9px": "9px",
+ "10px": "10px",
+ "15px": "15px",
+ 1: "0.25rem",
+ 1.5: "0.375rem",
+ 2: "0.5rem",
+ 2.5: "0.625rem",
+ 3: "0.75rem",
+ 4: "1rem",
+ 5: "1.25rem",
+ 6: "1.5rem",
+ 8: "2rem",
+ 9: "2.25rem",
+ 10: "2.5rem",
+ 12: "3rem",
+ 16: "4rem",
+ 20: "5rem",
+ 24: "6rem",
+ 30: "7.5rem",
+ 32: "8rem",
+ 40: "10rem",
+ 48: "12rem",
+ 52: "13rem",
+ 56: "14rem",
+ 64: "16rem",
+ 76: "19rem",
+ 88: "22rem",
+ 96: "24rem",
+ "1/2": "50%",
+ "1/3": "33.33333%",
+ "2/3": "66.66667%",
+ "1/4": "25%",
+ "3/4": "75%",
+ "1/5": "20%",
+ "2/5": "40%",
+ "3/5": "60%",
+ "4/5": "80%",
+ "9/10": "90%",
+ "1/6": "16.66667%",
+ "5/6": "83.33333%",
+ sm: "30rem",
+ md: "40rem",
+ full: "100%",
+ screen: "100vw",
},
height: {
- auto: 'auto',
- px: '1px',
- '3px': '3px',
- '5px': '5px',
- '15px': '15px',
- '1': '0.25rem',
- '1.5': '0.375rem',
- '2': '0.5rem',
- '2.5': '0.625rem',
- '3': '0.75rem',
- '4': '1rem',
- '5': '1.25rem',
- '6': '1.5rem',
- '8': '2rem',
- '9': '2.25rem',
- '10': '2.5rem',
- '12': '3rem',
- '14': '3.5rem',
- '16': '4rem',
- '20': '5rem',
- '24': '6rem',
- '30': '7.5rem',
- '32': '8rem',
- '40': '10rem',
- '48': '12rem',
- '64': '16rem',
- '72': '18rem',
- '80': '20rem',
- '100': '25rem',
- '112': '28rem',
- hero: '43.75rem',
- full: '100%',
- half: '50%',
- '2/5': '40%',
- '3/4': '75%',
- screen50: '50vh',
- screen75: '75vh',
- screen: '100vh',
- content: 'calc(100vh - 102px)',
- challenges: 'calc(100vh - 244px)',
+ auto: "auto",
+ px: "1px",
+ "3px": "3px",
+ "5px": "5px",
+ "15px": "15px",
+ 1: "0.25rem",
+ 1.5: "0.375rem",
+ 2: "0.5rem",
+ 2.5: "0.625rem",
+ 3: "0.75rem",
+ 4: "1rem",
+ 5: "1.25rem",
+ 6: "1.5rem",
+ 8: "2rem",
+ 9: "2.25rem",
+ 10: "2.5rem",
+ 12: "3rem",
+ 14: "3.5rem",
+ 16: "4rem",
+ 20: "5rem",
+ 24: "6rem",
+ 30: "7.5rem",
+ 32: "8rem",
+ 40: "10rem",
+ 48: "12rem",
+ 64: "16rem",
+ 72: "18rem",
+ 80: "20rem",
+ 100: "25rem",
+ 112: "28rem",
+ hero: "43.75rem",
+ full: "100%",
+ half: "50%",
+ "2/5": "40%",
+ "3/4": "75%",
+ screen50: "50vh",
+ screen75: "75vh",
+ screen: "100vh",
+ content: "calc(100vh - 102px)",
+ challenges: "calc(100vh - 244px)",
},
minWidth: {
- '0': '0',
- '4': '1rem',
- '5': '1.25rem',
- '6': '2rem',
- '8': '2.5rem',
- '12': '4.5rem',
- '24': '6rem',
- '28': '7rem',
- '30': '7.5rem',
- '36': '9rem',
- '48': '12rem',
- '52': '13rem',
- '56': '14rem',
- '60': '15rem',
- '72': '18rem',
- '88': '22rem',
- '102': '24rem',
- '120': '30rem',
- auto: 'auto',
- '1/3': '33.33333%',
- '2/5': '40%',
- '1/2': '50%',
- full: '100%',
- button: '7.8125rem',
- screen50: '50vw',
+ 0: "0",
+ 4: "1rem",
+ 5: "1.25rem",
+ 6: "2rem",
+ 8: "2.5rem",
+ 12: "4.5rem",
+ 24: "6rem",
+ 28: "7rem",
+ 30: "7.5rem",
+ 36: "9rem",
+ 48: "12rem",
+ 52: "13rem",
+ 56: "14rem",
+ 60: "15rem",
+ 72: "18rem",
+ 88: "22rem",
+ 102: "24rem",
+ 120: "30rem",
+ auto: "auto",
+ "1/3": "33.33333%",
+ "2/5": "40%",
+ "1/2": "50%",
+ full: "100%",
+ button: "7.8125rem",
+ screen50: "50vw",
},
minHeight: {
- '0': '0',
- '5': '1.25rem',
- '8': '2rem',
- '72': '18rem',
- '80': '20rem',
- xs: '20rem',
- full: '100%',
- screen: '100vh',
- '40': '10rem',
- '48': '12rem',
- '100': '25rem',
- '120': '30rem',
- 'screen-50': '50vh',
- 'content-no-filters': 'calc(100vh - 103px)',
+ 0: "0",
+ 5: "1.25rem",
+ 8: "2rem",
+ 72: "18rem",
+ 80: "20rem",
+ xs: "20rem",
+ full: "100%",
+ screen: "100vh",
+ 40: "10rem",
+ 48: "12rem",
+ 100: "25rem",
+ 120: "30rem",
+ "screen-50": "50vh",
+ "content-no-filters": "calc(100vh - 103px)",
},
maxWidth: {
- xs: '20rem',
- sm: '30rem',
- md: '40rem',
- lg: '50rem',
- xl: '60rem',
- '56': '14rem',
- '88': '22rem',
- '96': '24rem',
- '2xl': '70rem',
- '3xl': '80rem',
- '4xl': '90rem',
- '5xl': '100rem',
- '6xl': '120rem',
- full: '100%',
- '48': '12rem',
- '1/3': '33%',
- '1/4': '25%',
- '3/4': '75%',
- '1/5': '20%',
- '2/5': '40%',
- '3/5': '60%',
- '4/5': '80%',
- '9/10': '90%',
- '1/6': '16.66667%',
- '5/6': '83.33333%',
- 'screen40': '40vw',
- 'screen50': '50vw',
- 'screen60': '60vw',
- 'screen80': '80vw',
- 'screen90': '90vw',
+ xs: "20rem",
+ sm: "30rem",
+ md: "40rem",
+ lg: "50rem",
+ xl: "60rem",
+ 56: "14rem",
+ 88: "22rem",
+ 96: "24rem",
+ "2xl": "70rem",
+ "3xl": "80rem",
+ "4xl": "90rem",
+ "5xl": "100rem",
+ "6xl": "120rem",
+ full: "100%",
+ 48: "12rem",
+ "1/3": "33%",
+ "1/4": "25%",
+ "3/4": "75%",
+ "1/5": "20%",
+ "2/5": "40%",
+ "3/5": "60%",
+ "4/5": "80%",
+ "9/10": "90%",
+ "1/6": "16.66667%",
+ "5/6": "83.33333%",
+ screen40: "40vw",
+ screen50: "50vw",
+ screen60: "60vw",
+ screen80: "80vw",
+ screen90: "90vw",
},
maxHeight: {
- full: '100%',
- screen: '100vh',
- '36': '9rem',
- '40': '10rem',
- '48': '12rem',
- '100': '25rem',
- '112': '28rem',
- '115': '28.75rem',
- screen40: '40vh',
- screen50: '50vh',
- screen75: '75vh',
- screen80: '80vh',
- '1/3': '33%',
+ full: "100%",
+ screen: "100vh",
+ 36: "9rem",
+ 40: "10rem",
+ 48: "12rem",
+ 100: "25rem",
+ 112: "28rem",
+ 115: "28.75rem",
+ screen40: "40vh",
+ screen50: "50vh",
+ screen75: "75vh",
+ screen80: "80vh",
+ "1/3": "33%",
},
padding: {
- px: '1px',
- '0': '0',
- '1': '0.25rem',
- '2-shy': '0.4rem',
- '2': '0.5rem',
- '3': '0.75rem',
- '4': '1rem',
- '5': '1.25rem',
- '6': '1.5rem',
- '8': '2rem',
- '10': '2.5rem',
- '12': '3rem',
- '16': '4rem',
- '20': '5rem',
- '24': '6rem',
- '32': '8rem',
- '36': '9rem',
- '1/2': '50%',
+ px: "1px",
+ 0: "0",
+ 1: "0.25rem",
+ "2-shy": "0.4rem",
+ 2: "0.5rem",
+ 3: "0.75rem",
+ 4: "1rem",
+ 5: "1.25rem",
+ 6: "1.5rem",
+ 8: "2rem",
+ 10: "2.5rem",
+ 12: "3rem",
+ 16: "4rem",
+ 20: "5rem",
+ 24: "6rem",
+ 32: "8rem",
+ 36: "9rem",
+ "1/2": "50%",
},
margin: {
- auto: 'auto',
- px: '1px',
- '2px': '2px',
- '3px': '3px',
- '4px': '4px',
- '6px': '6px',
- 'n1px': '-1px',
- 'n2px': '-2px',
- '0': '0',
- '1': '0.25rem',
- '1.5': '0.375rem',
- '2': '0.5rem',
- '3': '0.75rem',
- '4': '1rem',
- '5': '1.25rem',
- '6': '1.5rem',
- '8': '2rem',
- '10': '2.5rem',
- '12': '3rem',
- '12.8': '3.2rem',
- '14': '3.5rem',
- '16': '4rem',
- '20': '5rem',
- '24': '6rem',
- '26': '6.5rem',
- '28': '7rem',
- '31': '7.75rem',
- '32': '8rem',
- '41': '10.25rem',
- '48': '12rem',
- '1/3': '33%',
- '-px': '-1px',
- '-0': '-0',
- '-1': '-0.25rem',
- '-2': '-0.5rem',
- '-3': '-0.75rem',
- '-4': '-1rem',
- '-5': '-1.25rem',
- '-6': '-1.5rem',
- '-7': '-1.75rem',
- '-8': '-2rem',
- '-10': '-2.5rem',
- '-12': '-3rem',
- '-16': '-4rem',
- '-20': '-5rem',
- '-23': '-5.75rem',
- '-24': '-6rem',
- '-28': '-7rem',
- '-32': '-8rem',
- '-40': '-10rem',
- '-100': '-25rem',
+ auto: "auto",
+ px: "1px",
+ "2px": "2px",
+ "3px": "3px",
+ "4px": "4px",
+ "6px": "6px",
+ n1px: "-1px",
+ n2px: "-2px",
+ 0: "0",
+ 1: "0.25rem",
+ 1.5: "0.375rem",
+ 2: "0.5rem",
+ 3: "0.75rem",
+ 4: "1rem",
+ 5: "1.25rem",
+ 6: "1.5rem",
+ 8: "2rem",
+ 10: "2.5rem",
+ 12: "3rem",
+ 12.8: "3.2rem",
+ 14: "3.5rem",
+ 16: "4rem",
+ 20: "5rem",
+ 24: "6rem",
+ 26: "6.5rem",
+ 28: "7rem",
+ 31: "7.75rem",
+ 32: "8rem",
+ 41: "10.25rem",
+ 48: "12rem",
+ "1/3": "33%",
+ "-px": "-1px",
+ "-0": "-0",
+ "-1": "-0.25rem",
+ "-2": "-0.5rem",
+ "-3": "-0.75rem",
+ "-4": "-1rem",
+ "-5": "-1.25rem",
+ "-6": "-1.5rem",
+ "-7": "-1.75rem",
+ "-8": "-2rem",
+ "-10": "-2.5rem",
+ "-12": "-3rem",
+ "-16": "-4rem",
+ "-20": "-5rem",
+ "-23": "-5.75rem",
+ "-24": "-6rem",
+ "-28": "-7rem",
+ "-32": "-8rem",
+ "-40": "-10rem",
+ "-100": "-25rem",
},
boxShadow: {
- DEFAULT: '0 2px 4px 0 rgba(0,0,0,0.2)',
- md: '0 4px 8px 0 rgba(0,0,0,0.12), 0 2px 4px 0 rgba(0,0,0,0.08)',
- lg: '0 15px 30px 0 rgba(0,0,0,0.11), 0 5px 15px 0 rgba(0,0,0,0.08)',
- inner: 'inset 0 1px 3px 0 rgba(0,0,0,0.3)',
- outline: '0 0 0 3px rgba(52,144,220,0.5)',
- none: 'none',
+ DEFAULT: "0 2px 4px 0 rgba(0,0,0,0.2)",
+ md: "0 4px 8px 0 rgba(0,0,0,0.12), 0 2px 4px 0 rgba(0,0,0,0.08)",
+ lg: "0 15px 30px 0 rgba(0,0,0,0.11), 0 5px 15px 0 rgba(0,0,0,0.08)",
+ inner: "inset 0 1px 3px 0 rgba(0,0,0,0.3)",
+ outline: "0 0 0 3px rgba(52,144,220,0.5)",
+ none: "none",
},
zIndex: {
- auto: 'auto',
- '0': 0,
- '5': 5,
- '10': 10,
- '15': 15,
- '20': 20,
- '30': 30,
- '40': 40,
- '50': 50,
- '90': 90,
- '200': 200,
- '250': 250,
+ auto: "auto",
+ 0: 0,
+ 5: 5,
+ 10: 10,
+ 15: 15,
+ 20: 20,
+ 30: 30,
+ 40: 40,
+ 50: 50,
+ 90: 90,
+ 200: 200,
+ 250: 250,
},
opacity: {
- '0': '0',
- '25': '.25',
- '50': '.5',
- '75': '.75',
- '100': '1',
+ 0: "0",
+ 25: ".25",
+ 50: ".5",
+ 75: ".75",
+ 100: "1",
},
- fill: theme => Object.assign(
- { current: 'currentColor' },
- theme('colors')
- ),
+ fill: (theme) => Object.assign({ current: "currentColor" }, theme("colors")),
stroke: {
- current: 'currentColor',
+ current: "currentColor",
},
- linearGradients: theme => ({
+ linearGradients: (theme) => ({
colors: {
- 'green-blue': [theme('colors.green'), theme('colors.blue')],
- 'green-dark-blue': [theme('colors.green-dark'), theme('colors.blue')],
- 'blue-dark-green-dark': [theme('colors.blue-dark'), theme('colors.green-dark')],
- 'blue-darker-blue-dark': [theme('colors.blue-darker'), theme('colors.blue-dark')],
- 'blue-cloudburst-blue-rhino': [theme('colors.blue-cloudburst'), theme('colors.blue-rhino')],
- 'green-dark-75-blue-75': [theme('colors.green-dark-75'), theme('colors.blue-75')],
+ "green-blue": [theme("colors.green"), theme("colors.blue")],
+ "green-dark-blue": [theme("colors.green-dark"), theme("colors.blue")],
+ "blue-dark-green-dark": [theme("colors.blue-dark"), theme("colors.green-dark")],
+ "blue-darker-blue-dark": [theme("colors.blue-darker"), theme("colors.blue-dark")],
+ "blue-cloudburst-blue-rhino": [theme("colors.blue-cloudburst"), theme("colors.blue-rhino")],
+ "green-dark-75-blue-75": [theme("colors.green-dark-75"), theme("colors.blue-75")],
},
}),
rotate: {
- '90': '90deg',
- '180': '180deg',
- '270': '270deg',
+ 90: "90deg",
+ 180: "180deg",
+ 270: "270deg",
},
translate: {
- '1/4': '25%',
- '1/2': '50%',
- full: '100%',
- '-1/2': '-50%',
- '-full': '-100%',
+ "1/4": "25%",
+ "1/2": "50%",
+ full: "100%",
+ "-1/2": "-50%",
+ "-full": "-100%",
},
origins: {
- t: '50% 0%',
- r: '100% 50%',
- c: '50% 50%',
- b: '50% 100%',
- l: '0% 50%',
+ t: "50% 0%",
+ r: "100% 50%",
+ c: "50% 50%",
+ b: "50% 100%",
+ l: "0% 50%",
},
inset: {
- '0': 0,
- '45/100': '45%',
- '1/2': '50%',
- 'screen20': '-20vw',
- 'auto': 'auto',
+ 0: 0,
+ "45/100": "45%",
+ "1/2": "50%",
+ screen20: "-20vw",
+ auto: "auto",
},
},
@@ -558,30 +552,30 @@ export default {
plugins: [
visuallyHidden({
- variants: ['responsive', 'hover'],
+ variants: ["responsive", "hover"],
}),
owl,
grid({
grids: [2, 3, 5, 6, 8, 10, 12],
gaps: {
- 0: '0',
- 4: '1rem',
- 8: '2rem',
- 12: '3rem',
- 16: '4rem',
+ 0: "0",
+ 4: "1rem",
+ 8: "2rem",
+ 12: "3rem",
+ 16: "4rem",
},
- variants: ['responsive'],
+ variants: ["responsive"],
}),
transition({
- standard: 'all .3s ease',
+ standard: "all .3s ease",
transitions: {
- slow: 'all 2s ease',
- 'normal-in-out-quad': 'all 2s cubic-bezier(0.455, 0.03, 0.515, 0.955)',
- 'slow-in-out-quad': 'all 2s cubic-bezier(0.455, 0.03, 0.515, 0.955)',
+ slow: "all 2s ease",
+ "normal-in-out-quad": "all 2s cubic-bezier(0.455, 0.03, 0.515, 0.955)",
+ "slow-in-out-quad": "all 2s cubic-bezier(0.455, 0.03, 0.515, 0.955)",
},
- variants: ['responsive', 'hover'],
+ variants: ["responsive", "hover"],
}),
gradients(),
transforms(),
],
-}
+};
diff --git a/src/utils/bundleByTaskBundleId.js b/src/utils/bundleByTaskBundleId.js
index 8d9a6d763..fd914cc59 100644
--- a/src/utils/bundleByTaskBundleId.js
+++ b/src/utils/bundleByTaskBundleId.js
@@ -52,7 +52,7 @@ const bundleByTaskBundleId = (tasks, externalId) => {
if (infiniteLoopCount > 10) {
console.log(
- "There was a problem with your data that caused an infinite loop. Process stopped"
+ "There was a problem with your data that caused an infinite loop. Process stopped",
);
break;
}
diff --git a/src/utils/bundleByTaskBundleId.test.js b/src/utils/bundleByTaskBundleId.test.js
index 6d6667bf3..cef8bafe2 100644
--- a/src/utils/bundleByTaskBundleId.test.js
+++ b/src/utils/bundleByTaskBundleId.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect } from "vitest";
+import { describe, expect, it } from "vitest";
import bundleByTaskBundleId from "./bundleByTaskBundleId";
describe("bundleByTaskBundleId", () => {
@@ -9,7 +9,7 @@ describe("bundleByTaskBundleId", () => {
];
it("returns string if there are tasks", () => {
expect(bundleByTaskBundleId(tasks, 0)).toBe(
- '{"type":"FeatureCollection","features":[{"properties":[["placeholder"]]}]}'
+ '{"type":"FeatureCollection","features":[{"properties":[["placeholder"]]}]}',
);
});
it("returns empty string if there are no tasks", () => {
diff --git a/src/utils/constructChangesetUrl.js b/src/utils/constructChangesetUrl.js
index 641fe2b8b..525efbe7b 100644
--- a/src/utils/constructChangesetUrl.js
+++ b/src/utils/constructChangesetUrl.js
@@ -6,7 +6,7 @@ export const constructChangesetUrl = (task) => {
task?.parent?.enabled &&
task?.parent?.parent?.enabled
) {
- return ` ${constructTaskLink(task.parent.id, task.id)}`
+ return ` ${constructTaskLink(task.parent.id, task.id)}`;
} else {
return "";
}
@@ -14,25 +14,34 @@ export const constructChangesetUrl = (task) => {
export const constructProjectLink = (projectId) => {
const rootUrl = getRootUrl();
- const path = window.env.REACT_APP_SHORT_PATH === 'enabled' ? `/p/${projectId}` : `/browse/projects/${projectId}`
+ const path =
+ window.env.REACT_APP_SHORT_PATH === "enabled"
+ ? `/p/${projectId}`
+ : `/browse/projects/${projectId}`;
return `${rootUrl}${path}`;
};
export const constructChallengeLink = (challengeId) => {
const rootUrl = getRootUrl();
- const path = window.env.REACT_APP_SHORT_PATH === 'enabled' ? `/c/${challengeId}` : `/browse/challenges/${challengeId}`
+ const path =
+ window.env.REACT_APP_SHORT_PATH === "enabled"
+ ? `/c/${challengeId}`
+ : `/browse/challenges/${challengeId}`;
return `${rootUrl}${path}`;
};
export const constructTaskLink = (challengeId, taskId) => {
const rootUrl = getRootUrl();
- const path = window.env.REACT_APP_SHORT_PATH === 'enabled' ? `/c/${challengeId}/t/${taskId}` : `/challenge/${challengeId}/task/${taskId}`
+ const path =
+ window.env.REACT_APP_SHORT_PATH === "enabled"
+ ? `/c/${challengeId}/t/${taskId}`
+ : `/challenge/${challengeId}/task/${taskId}`;
return `${rootUrl}${path}`;
-}
+};
const getRootUrl = () => {
- return window.env.REACT_APP_SHORT_URL || window.location.origin
-}
+ return window.env.REACT_APP_SHORT_URL || window.location.origin;
+};
diff --git a/src/utils/constructChangesetUrl.test.js b/src/utils/constructChangesetUrl.test.js
index b95f2ca05..3f761e87d 100644
--- a/src/utils/constructChangesetUrl.test.js
+++ b/src/utils/constructChangesetUrl.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect, vi } from "vitest";
+import { describe, expect, it, vi } from "vitest";
import { constructChangesetUrl } from "./constructChangesetUrl";
describe("constructChangesetUrl", () => {
@@ -6,7 +6,12 @@ describe("constructChangesetUrl", () => {
beforeEach(() => {
vi.resetModules();
- window.env = { ...cachedEnv, REACT_APP_CHANGESET_URL: "enabled", REACT_APP_SHORT_URL: '', REACT_APP_SHORT_PATH: 'disabled' };
+ window.env = {
+ ...cachedEnv,
+ REACT_APP_CHANGESET_URL: "enabled",
+ REACT_APP_SHORT_URL: "",
+ REACT_APP_SHORT_PATH: "disabled",
+ };
});
afterAll(() => {
@@ -35,26 +40,20 @@ describe("constructChangesetUrl", () => {
it("returns long url if challenge and project are enabled", () => {
const task = { parent: { enabled: true, parent: { enabled: true }, id: 1 }, id: 2 };
- expect(constructChangesetUrl(task)).toBe(
- " http://localhost:3000/challenge/1/task/2"
- );
+ expect(constructChangesetUrl(task)).toBe(" http://localhost:3000/challenge/1/task/2");
});
it("returns short root url if REACT_APP_SHORT_URL is provided", () => {
window.env.REACT_APP_SHORT_URL = "mpr.lt";
window.env.REACT_APP_SHORT_PATH = "disabled";
const task = { parent: { enabled: true, parent: { enabled: true }, id: 1 }, id: 2 };
- expect(constructChangesetUrl(task)).toBe(
- " mpr.lt/challenge/1/task/2"
- );
+ expect(constructChangesetUrl(task)).toBe(" mpr.lt/challenge/1/task/2");
});
it("returns short root url and short path if REACT_APP_SHORT_URL is provided and REACT_APP_SHORT_PATH is enabled", () => {
window.env.REACT_APP_SHORT_URL = "mpr.lt";
window.env.REACT_APP_SHORT_PATH = "enabled";
const task = { parent: { enabled: true, parent: { enabled: true }, id: 1 }, id: 2 };
- expect(constructChangesetUrl(task)).toBe(
- " mpr.lt/c/1/t/2"
- );
+ expect(constructChangesetUrl(task)).toBe(" mpr.lt/c/1/t/2");
});
});
diff --git a/src/utils/createBlob.test.js b/src/utils/createBlob.test.js
index 685f32fb9..e009358fc 100644
--- a/src/utils/createBlob.test.js
+++ b/src/utils/createBlob.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect } from "vitest";
+import { describe, expect, it } from "vitest";
import createBlob from "./createBlob";
describe("createBlob", () => {
diff --git a/src/utils/errorTagUtils.js b/src/utils/errorTagUtils.js
index 0175ae60c..2b702c9ad 100644
--- a/src/utils/errorTagUtils.js
+++ b/src/utils/errorTagUtils.js
@@ -3,9 +3,9 @@ export const formatErrorTags = (errorTags, options) => {
const tags = errorTags.split(",");
return tags.map((tag) => {
- const option = options?.data.find(o => o.id === Number(tag));
+ const option = options?.data.find((o) => o.id === Number(tag));
return option?.name;
- })
+ });
}
-}
+};
diff --git a/src/utils/errorTagUtils.test.js b/src/utils/errorTagUtils.test.js
index c019382fa..8d7766cac 100644
--- a/src/utils/errorTagUtils.test.js
+++ b/src/utils/errorTagUtils.test.js
@@ -1,4 +1,4 @@
-import { describe, it, expect } from "vitest";
+import { describe, expect, it } from "vitest";
import { formatErrorTags } from "./errorTagUtils";
describe("formatErrorTags", () => {
diff --git a/src/utils/setupCustomCache.js b/src/utils/setupCustomCache.js
index 423fb2396..591080015 100644
--- a/src/utils/setupCustomCache.js
+++ b/src/utils/setupCustomCache.js
@@ -1,7 +1,9 @@
export const setupCustomCache = (cacheTime) => {
return {
get: (variables, params, type) => {
- const cachedData = localStorage.getItem(`${type}::${JSON.stringify(variables)}::${JSON.stringify(params)}`);
+ const cachedData = localStorage.getItem(
+ `${type}::${JSON.stringify(variables)}::${JSON.stringify(params)}`,
+ );
if (cachedData) {
const parsed = JSON.parse(cachedData);
@@ -17,10 +19,10 @@ export const setupCustomCache = (cacheTime) => {
set: (variables, params, data, type) => {
const obj = JSON.stringify({
date: Date.now(),
- data
- })
+ data,
+ });
- localStorage.setItem(`${type}::${JSON.stringify(variables)}::${JSON.stringify(params)}`, obj)
- }
- }
-}
\ No newline at end of file
+ localStorage.setItem(`${type}::${JSON.stringify(variables)}::${JSON.stringify(params)}`, obj);
+ },
+ };
+};
diff --git a/yarn.lock b/yarn.lock
index c652c4a88..1b5c10b1c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -297,6 +297,60 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
+"@biomejs/biome@1.9.4":
+ version "1.9.4"
+ resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-1.9.4.tgz#89766281cbc3a0aae865a7ff13d6aaffea2842bf"
+ integrity sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==
+ optionalDependencies:
+ "@biomejs/cli-darwin-arm64" "1.9.4"
+ "@biomejs/cli-darwin-x64" "1.9.4"
+ "@biomejs/cli-linux-arm64" "1.9.4"
+ "@biomejs/cli-linux-arm64-musl" "1.9.4"
+ "@biomejs/cli-linux-x64" "1.9.4"
+ "@biomejs/cli-linux-x64-musl" "1.9.4"
+ "@biomejs/cli-win32-arm64" "1.9.4"
+ "@biomejs/cli-win32-x64" "1.9.4"
+
+"@biomejs/cli-darwin-arm64@1.9.4":
+ version "1.9.4"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz#dfa376d23a54a2d8f17133c92f23c1bf2e62509f"
+ integrity sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==
+
+"@biomejs/cli-darwin-x64@1.9.4":
+ version "1.9.4"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz#eafc2ce3849d385fc02238aad1ca4a73395a64d9"
+ integrity sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==
+
+"@biomejs/cli-linux-arm64-musl@1.9.4":
+ version "1.9.4"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz#d780c3e01758fc90f3268357e3f19163d1f84fca"
+ integrity sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==
+
+"@biomejs/cli-linux-arm64@1.9.4":
+ version "1.9.4"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz#8ed1dd0e89419a4b66a47f95aefb8c46ae6041c9"
+ integrity sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==
+
+"@biomejs/cli-linux-x64-musl@1.9.4":
+ version "1.9.4"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz#f36982b966bd671a36671e1de4417963d7db15fb"
+ integrity sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==
+
+"@biomejs/cli-linux-x64@1.9.4":
+ version "1.9.4"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz#a0a7f56680c76b8034ddc149dbf398bdd3a462e8"
+ integrity sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==
+
+"@biomejs/cli-win32-arm64@1.9.4":
+ version "1.9.4"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz#e2ef4e0084e76b7e26f0fc887c5ef1265ea56200"
+ integrity sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==
+
+"@biomejs/cli-win32-x64@1.9.4":
+ version "1.9.4"
+ resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz#4c7afa90e3970213599b4095e62f87e5972b2340"
+ integrity sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==
+
"@changey/react-leaflet-markercluster@^4.0.0-rc1":
version "4.0.0-rc1"
resolved "https://registry.yarnpkg.com/@changey/react-leaflet-markercluster/-/react-leaflet-markercluster-4.0.0-rc1.tgz#88d76c74e9014ca2f669900dded8e2ccdd6dd545"