From 68767a6ebf50dfb4350e753ed89a9c6f1970c681 Mon Sep 17 00:00:00 2001 From: Collin Beczak Date: Fri, 5 Jun 2026 13:29:45 -0300 Subject: [PATCH 1/2] Replace "Feature Id" with "OSM ID" and "Internal Id" with "Task ID" Renames task column headers, map popup labels, and search placeholders across the challenge admin table, review table, and nearby/bundle map popups. The OSM ID column/field now extracts the actual OSM identifier from the task's first GeoJSON feature (via AsIdentifiableFeature) and renders it as a JOSM remote-control link (load_object) when an id and type can be determined, falling back to task.name unlinked otherwise. --- lang/en-US.json | 12 ++-- src/components/TaskAnalysisTable/Messages.js | 8 +-- .../TaskAnalysisTable/columns/taskColumns.jsx | 61 ++++++++++++++----- .../Widgets/NearbyTasksWidget/Messages.js | 4 +- .../NearbyTasksWidget/TaskMarkerContent.jsx | 32 +++++++++- .../Widgets/TaskBundleWidget/Messages.js | 4 +- .../TaskBundleWidget/TaskMarkerContent.jsx | 31 +++++++++- src/pages/Review/TasksReview/Messages.js | 4 +- .../Review/TasksReview/TasksReviewTable.jsx | 36 ++++++++++- src/services/Task/TaskReview/TaskReview.js | 7 ++- 10 files changed, 159 insertions(+), 40 deletions(-) diff --git a/lang/en-US.json b/lang/en-US.json index f66f78449..e2fb1009d 100644 --- a/lang/en-US.json +++ b/lang/en-US.json @@ -1190,8 +1190,8 @@ "Review.Dashboard.tasksReviewedByMe": "Tasks Reviewed by Me", "Review.Dashboard.tasksToBeReviewed": "Tasks to be Reviewed", "Review.Dashboard.title": "Review Overview", - "Review.Task.fields.featureId.label": "Feature Id", - "Review.Task.fields.id.label": "Internal Id", + "Review.Task.fields.featureId.label": "OSM ID", + "Review.Task.fields.id.label": "Task ID", "Review.TaskAnalysisTable.configureColumns": "Configure columns", "Review.TaskAnalysisTable.controls.fixTask.label": "Fix", "Review.TaskAnalysisTable.controls.metaReviewTask.label": "Meta Review", @@ -1375,8 +1375,8 @@ "Task.taskTags.modify.label": "Modify MR Tags", "Task.taskTags.update.label": "Update MR Tags", "Task.unsave.control.tooltip": "Stop Tracking", - "TaskAnalysisTable.columns.searchFeatureId.placeholder": "Search feature ID...", - "TaskAnalysisTable.columns.searchId.placeholder": "Search ID...", + "TaskAnalysisTable.columns.searchFeatureId.placeholder": "Search OSM ID...", + "TaskAnalysisTable.columns.searchId.placeholder": "Search Task ID...", "TaskAnalysisTable.columns.searchMapper.placeholder": "Search mapper...", "TaskAnalysisTable.columns.searchMetaReviewer.placeholder": "Search meta reviewer...", "TaskAnalysisTable.columns.searchReviewer.placeholder": "Search reviewer...", @@ -1566,8 +1566,8 @@ "Widgets.ReviewNearbyTasksWidget.displayBundledTasksLabel": "Display Bundled Tasks Only", "Widgets.ReviewNearbyTasksWidget.label": "Nearby Tasks", "Widgets.ReviewNearbyTasksWidget.noVirtualChallenges": "Nearby tasks are not available for virtual challenges.", - "Widgets.ReviewNearbyTasksWidget.popup.fields.name.label": "Feature Id:", - "Widgets.ReviewNearbyTasksWidget.popup.fields.taskId.label": "Internal Id:", + "Widgets.ReviewNearbyTasksWidget.popup.fields.name.label": "OSM ID:", + "Widgets.ReviewNearbyTasksWidget.popup.fields.taskId.label": "Task ID:", "Widgets.ReviewNearbyTasksWidget.restoreDefaultFiltersLabel": "Restore Default Filters", "Widgets.ReviewNearbyTasksWidget.saveCurrentFiltersLabel": "Save Current Filters", "Widgets.ReviewNearbyTasksWidget.simultaneousTasks": "Working on {taskCount, number} tasks together", diff --git a/src/components/TaskAnalysisTable/Messages.js b/src/components/TaskAnalysisTable/Messages.js index a9c4504fd..db0f3e354 100644 --- a/src/components/TaskAnalysisTable/Messages.js +++ b/src/components/TaskAnalysisTable/Messages.js @@ -21,12 +21,12 @@ export default defineMessages({ idLabel: { id: "Review.Task.fields.id.label", - defaultMessage: "Internal Id", + defaultMessage: "Task ID", }, featureIdLabel: { id: "Review.Task.fields.featureId.label", - defaultMessage: "Feature Id", + defaultMessage: "OSM ID", }, unbundle: { @@ -282,12 +282,12 @@ export default defineMessages({ searchFeatureIdPlaceholder: { id: "TaskAnalysisTable.columns.searchFeatureId.placeholder", - defaultMessage: "Search feature ID...", + defaultMessage: "Search OSM ID...", }, searchIdPlaceholder: { id: "TaskAnalysisTable.columns.searchId.placeholder", - defaultMessage: "Search ID...", + defaultMessage: "Search Task ID...", }, clearFilterLabel: { diff --git a/src/components/TaskAnalysisTable/columns/taskColumns.jsx b/src/components/TaskAnalysisTable/columns/taskColumns.jsx index b1132b388..3ac8bda46 100644 --- a/src/components/TaskAnalysisTable/columns/taskColumns.jsx +++ b/src/components/TaskAnalysisTable/columns/taskColumns.jsx @@ -1,6 +1,8 @@ import _kebabCase from "lodash/kebabCase"; import { FormattedDate, FormattedMessage, FormattedTime } from "react-intl"; import { Link } from "react-router-dom"; +import AsIdentifiableFeature from "../../../interactions/TaskFeature/AsIdentifiableFeature"; +import { loadObjectsIntoJOSM } from "../../../services/Editor/Editor"; import { messagesByPriority } from "../../../services/Task/TaskPriority/TaskPriority"; import { keysByStatus, messagesByStatus } from "../../../services/Task/TaskStatus/TaskStatus"; import SvgSymbol from "../../SvgSymbol/SvgSymbol"; @@ -8,25 +10,56 @@ import messages from "../Messages"; import TableSearchFilter from "../TableSearchFilter"; import { StatusLabel } from "../TaskTableHelpers"; +const osmIdForTask = (task) => { + const feature = task?.geometries?.features?.[0]; + if (!feature) return null; + const identifiable = AsIdentifiableFeature(feature); + const osmId = identifiable.osmId(); + const osmType = identifiable.osmType(); + if (!osmId || !osmType) return null; + return `${osmType[0]}${osmId}`; +}; + /** - * Creates the Feature ID column + * Creates the OSM ID column */ export const createFeatureIdColumn = (props) => ({ id: "featureId", Header: props.intl.formatMessage(messages.featureIdLabel), - accessor: (t) => t.name || t.title, - Cell: ({ value }) => ( -
- {value || ""} -
- ), + accessor: (t) => osmIdForTask(t) || t.name || t.title, + Cell: ({ value, row }) => { + const josmId = osmIdForTask(row.original); + const content = ( +
+ {value || ""} +
+ ); + + if (!josmId) { + return content; + } + + return ( + { + e.preventDefault(); + loadObjectsIntoJOSM([josmId], true); + }} + className="mr-links-green-lighter" + title="Open in JOSM" + > + {content} + + ); + }, Filter: ({ column: { filterValue, setFilter } }) => ( { + if (!feature) return null; + const identifiable = AsIdentifiableFeature(feature); + const osmId = identifiable.osmId(); + const osmType = identifiable.osmType(); + if (!osmId || !osmType) return null; + return `${osmType[0]}${osmId}`; +}; + /** * The content to show in the popup when a task marker is clicked. */ @@ -17,6 +28,11 @@ class TaskMarkerContent extends Component { this.props.marker.options.priority ?? this.props.marker.options.taskPriority ]; + const firstFeature = this.props.marker.options.geometries?.features?.[0]; + const josmId = osmIdForFeature(firstFeature); + const nameValue = + josmId || this.props.marker.options.name || firstFeature?.id; + return (
@@ -25,8 +41,20 @@ class TaskMarkerContent extends Component {
- {this.props.marker.options.name || - this.props.marker.options.geometries?.features[0]?.id} + {josmId ? ( + { + e.preventDefault(); + loadObjectsIntoJOSM([josmId], true); + }} + title="Open in JOSM" + > + {nameValue} + + ) : ( + nameValue + )}
diff --git a/src/components/Widgets/TaskBundleWidget/Messages.js b/src/components/Widgets/TaskBundleWidget/Messages.js index 4d3a15ce9..d178763ec 100644 --- a/src/components/Widgets/TaskBundleWidget/Messages.js +++ b/src/components/Widgets/TaskBundleWidget/Messages.js @@ -36,12 +36,12 @@ export default defineMessages({ taskIdLabel: { id: "Widgets.ReviewNearbyTasksWidget.popup.fields.taskId.label", - defaultMessage: "Internal Id:", + defaultMessage: "Task ID:", }, nameLabel: { id: "Widgets.ReviewNearbyTasksWidget.popup.fields.name.label", - defaultMessage: "Feature Id:", + defaultMessage: "OSM ID:", }, statusLabel: { diff --git a/src/components/Widgets/TaskBundleWidget/TaskMarkerContent.jsx b/src/components/Widgets/TaskBundleWidget/TaskMarkerContent.jsx index 269b1e021..a974f7257 100644 --- a/src/components/Widgets/TaskBundleWidget/TaskMarkerContent.jsx +++ b/src/components/Widgets/TaskBundleWidget/TaskMarkerContent.jsx @@ -2,10 +2,21 @@ import { Component } from "react"; import { FormattedMessage } from "react-intl"; import { Link } from "react-router-dom"; import AsCooperativeWork from "../../../interactions/Task/AsCooperativeWork"; +import AsIdentifiableFeature from "../../../interactions/TaskFeature/AsIdentifiableFeature"; +import { loadObjectsIntoJOSM } from "../../../services/Editor/Editor"; import { messagesByPriority } from "../../../services/Task/TaskPriority/TaskPriority"; import { messagesByStatus } from "../../../services/Task/TaskStatus/TaskStatus"; import messages from "./Messages"; +const osmIdForFeature = (feature) => { + if (!feature) return null; + const identifiable = AsIdentifiableFeature(feature); + const osmId = identifiable.osmId(); + const osmType = identifiable.osmType(); + if (!osmId || !osmType) return null; + return `${osmType[0]}${osmId}`; +}; + /** * The content to show in the popup when a task marker is clicked. */ @@ -95,6 +106,10 @@ class TaskMarkerContent extends Component { !AsCooperativeWork(this.props.task).isTagType() && this.props.marker.options.taskId !== this.props.task.id; + const firstFeature = this.props.marker.options.geometries?.features?.[0]; + const josmId = osmIdForFeature(firstFeature); + const nameValue = josmId || this.props.marker.options.name || firstFeature?.id; + return (
@@ -103,8 +118,20 @@ class TaskMarkerContent extends Component {
- {this.props.marker.options.name || - this.props.marker.options.geometries?.features[0]?.id} + {josmId ? ( + { + e.preventDefault(); + loadObjectsIntoJOSM([josmId], true); + }} + title="Open in JOSM" + > + {nameValue} + + ) : ( + nameValue + )}
diff --git a/src/pages/Review/TasksReview/Messages.js b/src/pages/Review/TasksReview/Messages.js index b80f1525f..1d4af3939 100644 --- a/src/pages/Review/TasksReview/Messages.js +++ b/src/pages/Review/TasksReview/Messages.js @@ -151,12 +151,12 @@ export default defineMessages({ idLabel: { id: "Review.Task.fields.id.label", - defaultMessage: "Internal Id", + defaultMessage: "Task ID", }, featureIdLabel: { id: "Review.Task.fields.featureId.label", - defaultMessage: "Feature Id", + defaultMessage: "OSM ID", }, statusLabel: { diff --git a/src/pages/Review/TasksReview/TasksReviewTable.jsx b/src/pages/Review/TasksReview/TasksReviewTable.jsx index 0ffca7493..3e2b51d3a 100644 --- a/src/pages/Review/TasksReview/TasksReviewTable.jsx +++ b/src/pages/Review/TasksReview/TasksReviewTable.jsx @@ -21,6 +21,8 @@ import WithSavedFilters from "../../../components/HOCs/WithSavedFilters/WithSave import IntlDatePicker from "../../../components/IntlDatePicker/IntlDatePicker"; import InTableTagFilter from "../../../components/KeywordAutosuggestInput/InTableTagFilter"; import PaginationControl from "../../../components/PaginationControl/PaginationControl"; +import AsIdentifiableFeature from "../../../interactions/TaskFeature/AsIdentifiableFeature"; +import { loadObjectsIntoJOSM } from "../../../services/Editor/Editor"; import ManageSavedFilters from "../../../components/SavedFilters/ManageSavedFilters"; import SavedFiltersList from "../../../components/SavedFilters/SavedFiltersList"; import SvgSymbol from "../../../components/SvgSymbol/SvgSymbol"; @@ -716,6 +718,16 @@ const GearDropdown = ({ ); }; +const osmIdForTask = (task) => { + const feature = task?.geometries?.features?.[0]; + if (!feature) return null; + const identifiable = AsIdentifiableFeature(feature); + const osmId = identifiable.osmId(); + const osmType = identifiable.osmType(); + if (!osmId || !osmType) return null; + return `${osmType[0]}${osmId}`; +}; + export const setupColumnTypes = (props, openComments, criteria) => { const handleClick = (e, linkTo) => { e.preventDefault(); @@ -766,7 +778,7 @@ export const setupColumnTypes = (props, openComments, criteria) => { {filterValue && ( @@ -790,7 +802,25 @@ export const setupColumnTypes = (props, openComments, criteria) => { columns.featureId = { id: "featureId", Header: props.intl.formatMessage(messages.featureIdLabel), - accessor: (row) => row.name || row.title, + accessor: (row) => osmIdForTask(row) || row.name || row.title, + Cell: ({ value, row }) => { + const josmId = osmIdForTask(row.original); + if (!josmId) { + return value || ""; + } + return ( + { + e.preventDefault(); + loadObjectsIntoJOSM([josmId], true); + }} + title="Open in JOSM" + > + {value} + + ); + }, width: 120, minWidth: 80, disableSortBy: true, @@ -799,7 +829,7 @@ export const setupColumnTypes = (props, openComments, criteria) => { {filterValue && ( diff --git a/src/services/Task/TaskReview/TaskReview.js b/src/services/Task/TaskReview/TaskReview.js index 7ec07ed10..47c5dbe6f 100644 --- a/src/services/Task/TaskReview/TaskReview.js +++ b/src/services/Task/TaskReview/TaskReview.js @@ -272,7 +272,8 @@ const buildQueryFilters = function (criteria, addedColumns) { return capitalizedKey.charAt(0).toUpperCase() + capitalizedKey.slice(1); }); //Fix Headers - displayedColumns = displayedColumns.map((e) => (e === "Id" ? "Internal Id" : e)); + displayedColumns = displayedColumns.map((e) => (e === "Id" ? "Task ID" : e)); + displayedColumns = displayedColumns.map((e) => (e === "Feature Id" ? "OSM 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)); @@ -470,7 +471,7 @@ export const loadNextReviewTask = function (criteria = {}, lastTaskId, asMetaRev ); return function (dispatch) { - const params = { sort, order, ...searchParameters, asMetaReview }; + const params = { sort, order, ...searchParameters, asMetaReview, includeTags: true }; if (Number.isFinite(lastTaskId)) { params.lastTaskId = lastTaskId; } @@ -497,7 +498,7 @@ export const fetchTaskForReview = function (taskId, includeMapillary = false) { return new Endpoint(api.task.startReview, { schema: taskSchema(), variables: { id: taskId }, - params: { mapillary: includeMapillary }, + params: { mapillary: includeMapillary, includeTags: true }, }) .execute() .then((normalizedResults) => { From aee2e39d348d48104b9a69f36b22cd983b6b6f4b Mon Sep 17 00:00:00 2001 From: Collin Beczak Date: Thu, 9 Jul 2026 20:00:07 -0500 Subject: [PATCH 2/2] run formatting --- .../Widgets/NearbyTasksWidget/TaskMarkerContent.jsx | 3 +-- src/pages/Review/TasksReview/TasksReviewTable.jsx | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/Widgets/NearbyTasksWidget/TaskMarkerContent.jsx b/src/components/Widgets/NearbyTasksWidget/TaskMarkerContent.jsx index 215d63a10..c9487fb09 100644 --- a/src/components/Widgets/NearbyTasksWidget/TaskMarkerContent.jsx +++ b/src/components/Widgets/NearbyTasksWidget/TaskMarkerContent.jsx @@ -30,8 +30,7 @@ class TaskMarkerContent extends Component { const firstFeature = this.props.marker.options.geometries?.features?.[0]; const josmId = osmIdForFeature(firstFeature); - const nameValue = - josmId || this.props.marker.options.name || firstFeature?.id; + const nameValue = josmId || this.props.marker.options.name || firstFeature?.id; return (
diff --git a/src/pages/Review/TasksReview/TasksReviewTable.jsx b/src/pages/Review/TasksReview/TasksReviewTable.jsx index 3e2b51d3a..3d108884f 100644 --- a/src/pages/Review/TasksReview/TasksReviewTable.jsx +++ b/src/pages/Review/TasksReview/TasksReviewTable.jsx @@ -21,8 +21,6 @@ import WithSavedFilters from "../../../components/HOCs/WithSavedFilters/WithSave import IntlDatePicker from "../../../components/IntlDatePicker/IntlDatePicker"; import InTableTagFilter from "../../../components/KeywordAutosuggestInput/InTableTagFilter"; import PaginationControl from "../../../components/PaginationControl/PaginationControl"; -import AsIdentifiableFeature from "../../../interactions/TaskFeature/AsIdentifiableFeature"; -import { loadObjectsIntoJOSM } from "../../../services/Editor/Editor"; import ManageSavedFilters from "../../../components/SavedFilters/ManageSavedFilters"; import SavedFiltersList from "../../../components/SavedFilters/SavedFiltersList"; import SvgSymbol from "../../../components/SvgSymbol/SvgSymbol"; @@ -45,6 +43,8 @@ import { } from "../../../components/TaskAnalysisTable/TaskTableHelpers"; import TaskCommentsModal from "../../../components/TaskCommentsModal/TaskCommentsModal"; import AsColoredHashable from "../../../interactions/Hashable/AsColoredHashable"; +import AsIdentifiableFeature from "../../../interactions/TaskFeature/AsIdentifiableFeature"; +import { loadObjectsIntoJOSM } from "../../../services/Editor/Editor"; import { TaskPriority, keysByPriority,