From 5b998ce059b5249fc39a81a5465cb9a2bd12e209 Mon Sep 17 00:00:00 2001 From: Collin Beczak Date: Fri, 5 Jun 2026 13:29:21 -0300 Subject: [PATCH] Allow reviewers to update task completion state from Review pane MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the Completion widget to the Review workspace defaults so reviewers can correct a task's completion status without rerouting through the mapper flow. The Completion widget renders ActiveTaskControls; when the new asReviewer prop is set, the controls: - dispatch the new updateTaskCompletionStatus action, which calls the existing review-status endpoint with the current reviewStatus preserved and newTaskStatus set, then stays on the current task - use the "for revision" allowed-status progressions so reviewers see valid status-change buttons even on Fixed/AlreadyFixed tasks - hide the mapper-only Revision Complete / next-task / needs-review / load-by controls (in both the widget and the confirmation modal) Meta-reviewers are unaffected — the action honors the existing asMetaReview check from the URL. --- .../HOCs/WithTaskReview/WithTaskReview.jsx | 35 +++++++++++++++++++ .../ReviewTaskPane/ReviewTaskPane.jsx | 9 +++-- .../TaskConfirmationModal.jsx | 3 +- .../ActiveTaskControls/ActiveTaskControls.jsx | 29 ++++++++++++--- 4 files changed, 69 insertions(+), 7 deletions(-) diff --git a/src/components/HOCs/WithTaskReview/WithTaskReview.jsx b/src/components/HOCs/WithTaskReview/WithTaskReview.jsx index 1e6051b3c..9f319dd73 100644 --- a/src/components/HOCs/WithTaskReview/WithTaskReview.jsx +++ b/src/components/HOCs/WithTaskReview/WithTaskReview.jsx @@ -140,6 +140,41 @@ const mapDispatchToProps = (dispatch, ownProps) => { } }, + /** + * Standalone task-completion-status update used by reviewers from the + * Completion widget in the Review pane. Preserves the current review + * status (so it doesn't double-count as a review action) and does not + * navigate away from the current task — the reviewer is expected to + * continue reviewing after correcting the status. + */ + updateTaskCompletionStatus: (task, newTaskStatus, comment, taskBundle) => { + const submitAsMetaReview = asMetaReview(ownProps); + const action = taskBundle + ? completeBundleReview( + taskBundle.bundleId, + task.reviewStatus, + comment, + null, + newTaskStatus, + submitAsMetaReview, + undefined, + ) + : completeReview( + task.id, + task.reviewStatus, + comment, + null, + newTaskStatus, + submitAsMetaReview, + undefined, + ); + + return dispatch(action).catch((error) => { + console.log(error); + dispatch(addError(AppErrors.task.updateFailure)); + }); + }, + skipTaskReview: (task, loadBy, url) => { dispatch(cancelReviewClaim(task.id)); loadNextTaskForReview(dispatch, url, task.id, asMetaReview(ownProps)).then((nextTask) => diff --git a/src/components/ReviewTaskPane/ReviewTaskPane.jsx b/src/components/ReviewTaskPane/ReviewTaskPane.jsx index 3204a9320..4fb83e24f 100644 --- a/src/components/ReviewTaskPane/ReviewTaskPane.jsx +++ b/src/components/ReviewTaskPane/ReviewTaskPane.jsx @@ -36,6 +36,7 @@ export const defaultWorkspaceSetup = function () { widgetDescriptor("TaskHistoryWidget"), widgetDescriptor("TaskInstructionsWidget"), widgetDescriptor("TaskMapWidget"), + widgetDescriptor("TaskCompletionWidget"), ], layout: [ { i: generateWidgetId(), x: 0, y: 0, w: 4, h: 9 }, @@ -43,8 +44,9 @@ export const defaultWorkspaceSetup = function () { { i: generateWidgetId(), x: 0, y: 9, w: 4, h: 8 }, { i: generateWidgetId(), x: 0, y: 17, w: 4, h: 4 }, { i: generateWidgetId(), x: 4, y: 0, w: 8, h: 18 }, + { i: generateWidgetId(), x: 0, y: 21, w: 4, h: 8 }, ], - excludeWidgets: ["TaskCompletionWidget", "TagDiffWidget"], + excludeWidgets: ["TagDiffWidget"], }; }; @@ -59,6 +61,7 @@ export const defaultWorkspaceSetupAlt = function () { widgetDescriptor("TasksWidget"), widgetDescriptor("TaskHistoryWidget"), widgetDescriptor("TaskInstructionsWidget"), + widgetDescriptor("TaskCompletionWidget"), ], layout: [ { i: generateWidgetId(), x: 0, y: 0, w: 4, h: 9 }, @@ -66,8 +69,9 @@ export const defaultWorkspaceSetupAlt = function () { { i: generateWidgetId(), x: 0, y: 9, w: 4, h: 8 }, { i: generateWidgetId(), x: 0, y: 17, w: 4, h: 4 }, { i: generateWidgetId(), x: 4, y: 0, w: 4, h: 18 }, + { i: generateWidgetId(), x: 0, y: 21, w: 4, h: 8 }, ], - excludeWidgets: ["TaskCompletionWidget", "TagDiffWidget", "TaskMapWidget"], + excludeWidgets: ["TagDiffWidget", "TaskMapWidget"], }; }; @@ -188,6 +192,7 @@ export class ReviewTaskPane extends Component { setCompletionResponse={this.setCompletionResponse} completionResponses={completionResponses} templateRevision={true} + asReviewer /> diff --git a/src/components/TaskConfirmationModal/TaskConfirmationModal.jsx b/src/components/TaskConfirmationModal/TaskConfirmationModal.jsx index 7948f73d1..ceea3b67d 100644 --- a/src/components/TaskConfirmationModal/TaskConfirmationModal.jsx +++ b/src/components/TaskConfirmationModal/TaskConfirmationModal.jsx @@ -405,6 +405,7 @@ export class TaskConfirmationModal extends Component { )} {this.props.status !== TaskStatus.skipped && !reviewConfirmation && + !this.props.asReviewer && this.props.user.settings.needsReview !== needsReviewType.mandatory && (
- {!reviewConfirmation && ( + {!reviewConfirmation && !this.props.asReviewer && (
diff --git a/src/components/TaskPane/ActiveTaskDetails/ActiveTaskControls/ActiveTaskControls.jsx b/src/components/TaskPane/ActiveTaskDetails/ActiveTaskControls/ActiveTaskControls.jsx index 1aa5229e1..c251bbfdc 100644 --- a/src/components/TaskPane/ActiveTaskDetails/ActiveTaskControls/ActiveTaskControls.jsx +++ b/src/components/TaskPane/ActiveTaskDetails/ActiveTaskControls/ActiveTaskControls.jsx @@ -139,6 +139,19 @@ export class ActiveTaskControls extends Component { this.props.saveTaskTags(this.props.task, this.state.tags); } + // Reviewer is correcting just the completion state from the Review + // pane: keep the existing review status, update only the task status, + // and stay on the current task so reviewing can continue. + if (this.props.asReviewer) { + await this.props.updateTaskCompletionStatus( + this.props.task, + taskStatus, + this.state.comment, + taskBundle, + ); + return; + } + const revisionSubmission = this.props.task.reviewStatus === TaskReviewStatus.rejected; if (this.state.submitRevision !== undefined) { @@ -428,10 +441,14 @@ export class ActiveTaskControls extends Component { const fromInbox = this.props.history?.location?.state?.fromInbox; + // Reviewers correcting completion state need a broad set of valid + // status transitions regardless of the current task status. Reuse the + // "for revision" progression set, which lists the final completion + // statuses (Fixed/AlreadyFixed/FalsePositive/TooHard) minus the current one. const allowedProgressions = allowedStatusProgressions( this.props.task.status, false, - needsRevised, + needsRevised || this.props.asReviewer, ); const isComplete = isCompletionStatus(this.props.task.status); const isFinal = isFinalStatus(this.props.task.status); @@ -491,6 +508,7 @@ export class ActiveTaskControls extends Component { {isTagFix && (!isFinal || needsRevised || this.state.completingTask) && + !this.props.asReviewer && this.props.user.settings.seeTagFixSuggestions && ( )} - {!this.state.completingTask && isComplete && !needsRevised && ( + {!this.state.completingTask && isComplete && !needsRevised && !this.props.asReviewer && (