Repository: ietf-tools/datatracker

Issue:
When assignee rejects a review request, it seems to mark him for next for assignment
### Describe the issue

When someone rejects an assignment, for example because they are too busy now, the system seems to automatically mark that parson to "someone who has indicated he wants to be next in queue", which they definately have not done. 

As a secretary I always need to go and edit those users information and remove that checkbox from them before I can continue my assignments, as otherwise it will try to assign next document to them, which they will reject as they are still busy etc.

### Code of Conduct

- [X] I agree to follow the [IETF's Code of Conduct](https://github.com/ietf-tools/.github/blob/main/CODE_OF_CONDUCT.md)


Discussion:
The reject workflow may need to have an explicit "why" added if it's not already there. This was added because of people rejecting documents because they had a conflict of interest that the review system didn't already detect, and they _should_ go review something else instead. 

That said, People who are too busy should be marking themselves as unavailable (or you should do this for them for the period that they are too busy for.
Robert Sparks writes:
> The reject workflow may need to have an explicit "why" added if it's not
> already there. This was added because of people rejecting documents because
> they had a conflict of interest that the review system didn't already detect,
> and they should go review something else instead.

That has not happened ever on secdir. All rejections have happened
because people suddenly find out that they are too busy to do review,
especially if the document they got to be reviewed happened to be
long... 

> That said, People who are too busy should be marking themselves as
> unavailable (or you should do this for them for the period that they
> are too busy for.

Yes, but usually they notice they are too busy to do reviews after
they get review assigned... Also as they only get review requests
every 2-3 months, they might not want to bother to mark themselves
unavailable for every single one or two week time.
-- 
***@***.***

It _does_ happen at genart, and a radio button at the reject step would let the right thing happen for either use case.
Checking this, it seems to mark them to be next in queue ONLY if you are using the RotateAlphabeticallyReviewerQueuePolicy.

LeastRecentlyUsedReviewerQueuePolicy will simply ignore the rejected/withdrawn reviews. Is the genart also using the rotate alphabetically policy?
genart is using least recently used

Resolving patch:
diff --git a/ietf/doc/views_review.py b/ietf/doc/views_review.py
--- a/ietf/doc/views_review.py
+++ b/ietf/doc/views_review.py
@@ -347,6 +347,7 @@ def assign_reviewer(request, name, request_id):
 
 class RejectReviewerAssignmentForm(forms.Form):
     message_to_secretary = forms.CharField(widget=forms.Textarea, required=False, help_text="Optional explanation of rejection, will be emailed to team secretary if filled in", strip=False)
+    wants_to_be_next = forms.BooleanField(label="I want to be assigned new document immediately", required=False)
 
 @login_required
 def reject_reviewer_assignment(request, name, assignment_id):
@@ -388,11 +389,12 @@ def reject_reviewer_assignment(request, name, assignment_id):
             )
 
             policy = get_reviewer_queue_policy(review_assignment.review_request.team)
-            policy.return_reviewer_to_rotation_top(review_assignment.reviewer.person)
+            policy.return_reviewer_to_rotation_top(review_assignment.reviewer.person, form.cleaned_data['wants_to_be_next'])
 
             msg = render_to_string("review/reviewer_assignment_rejected.txt", {
                 "by": request.user.person,
-                "message_to_secretary": form.cleaned_data.get("message_to_secretary")
+                "message_to_secretary": form.cleaned_data.get("message_to_secretary"),
+                "wants_to_be_next" : form.cleaned_data['wants_to_be_next']
             })
 
             email_review_assignment_change(request, review_assignment, "Reviewer assignment rejected", msg, by=request.user.person, notify_secretary=True, notify_reviewer=True, notify_requested_by=False)
@@ -438,7 +440,7 @@ def withdraw_reviewer_assignment(request, name, assignment_id):
         )            
 
         policy = get_reviewer_queue_policy(review_assignment.review_request.team)
-        policy.return_reviewer_to_rotation_top(review_assignment.reviewer.person)
+        policy.return_reviewer_to_rotation_top(review_assignment.reviewer.person, True)
         
         msg = "Review assignment withdrawn by %s"%request.user.person
 
@@ -1093,4 +1095,4 @@ def _generate_ajax_or_redirect_response(request, doc):
     elif url_is_safe:
         return HttpResponseRedirect(redirect_url)
     else:
-        return HttpResponseRedirect(doc.get_absolute_url())
\ No newline at end of file
+        return HttpResponseRedirect(doc.get_absolute_url())
diff --git a/ietf/review/policies.py b/ietf/review/policies.py
--- a/ietf/review/policies.py
+++ b/ietf/review/policies.py
@@ -84,7 +84,7 @@ def default_reviewer_rotation_list(self, include_unavailable=False):
             rotation_list = self._filter_unavailable_reviewers(rotation_list)
         return rotation_list
 
-    def return_reviewer_to_rotation_top(self, reviewer_person):
+    def return_reviewer_to_rotation_top(self, reviewer_person, wants_to_be_next):
         """
         Return a reviewer to the top of the rotation, e.g. because they rejected a review,
         and should retroactively not have been rotated over.
@@ -472,13 +472,14 @@ def default_reviewer_rotation_list(self, include_unavailable=False):
     
         return reviewers[next_reviewer_index:] + reviewers[:next_reviewer_index]
 
-    def return_reviewer_to_rotation_top(self, reviewer_person):
+    def return_reviewer_to_rotation_top(self, reviewer_person, wants_to_be_next):
         # As RotateAlphabetically does not keep a full rotation list,
         # returning someone to a particular order is complex.
         # Instead, the "assign me next" flag is set.
-        settings = self._reviewer_settings_for(reviewer_person)
-        settings.request_assignment_next = True
-        settings.save()
+        if wants_to_be_next:
+            settings = self._reviewer_settings_for(reviewer_person)
+            settings.request_assignment_next = wants_to_be_next
+            settings.save()
 
     def _update_skip_next(self, rotation_pks, assignee_person):
         """Decrement skip_next for all users skipped
@@ -566,11 +567,14 @@ def default_reviewer_rotation_list(self, include_unavailable=False):
         rotation_list += reviewers_with_assignment
         return rotation_list
 
-    def return_reviewer_to_rotation_top(self, reviewer_person):
+    def return_reviewer_to_rotation_top(self, reviewer_person, wants_to_be_next):
         # Reviewer rotation for this policy ignores rejected/withdrawn
         # reviews, so it automatically adjusts the position of someone
         # who rejected a review and no further action is needed.
-        pass
+        if wants_to_be_next:
+            settings = self._reviewer_settings_for(reviewer_person)
+            settings.request_assignment_next = wants_to_be_next
+            settings.save()
 
 
 QUEUE_POLICY_NAME_MAPPING = {


Test patch:
diff --git a/ietf/review/tests_policies.py b/ietf/review/tests_policies.py
--- a/ietf/review/tests_policies.py
+++ b/ietf/review/tests_policies.py
@@ -509,8 +509,10 @@ def test_default_reviewer_rotation_list_with_nextreviewerinteam(self):
 
     def test_return_reviewer_to_rotation_top(self):
         reviewer = self.append_reviewer()
-        self.policy.return_reviewer_to_rotation_top(reviewer)
-        self.assertTrue(ReviewerSettings.objects.get(person=reviewer).request_assignment_next)
+        self.policy.return_reviewer_to_rotation_top(reviewer, False)
+        self.assertFalse(self.reviewer_settings_for(reviewer).request_assignment_next)
+        self.policy.return_reviewer_to_rotation_top(reviewer, True)
+        self.assertTrue(self.reviewer_settings_for(reviewer).request_assignment_next)
 
     def test_update_policy_state_for_assignment(self):
         # make a bunch of reviewers
@@ -724,8 +726,11 @@ def test_default_review_rotation_list_uses_assigned_on_date(self):
                          available_reviewers[2:] + [first_reviewer, second_reviewer])
 
     def test_return_reviewer_to_rotation_top(self):
-        # Should do nothing, this is implicit in this policy, no state change is needed.
-        self.policy.return_reviewer_to_rotation_top(self.append_reviewer())
+        reviewer = self.append_reviewer()
+        self.policy.return_reviewer_to_rotation_top(reviewer, False)
+        self.assertFalse(self.reviewer_settings_for(reviewer).request_assignment_next)
+        self.policy.return_reviewer_to_rotation_top(reviewer, True)
+        self.assertTrue(self.reviewer_settings_for(reviewer).request_assignment_next)
 
     def test_assign_reviewer_updates_skip_next_without_add_skip(self):
         """Skipping reviewers with add_skip=False should update skip_counts properly"""