From 108c00a0a3252e18a4569fd3c0fbfe609af0ec86 Mon Sep 17 00:00:00 2001 From: Henrik Levkowetz Date: Fri, 30 Jun 2017 20:16:22 +0000 Subject: [PATCH] Merged in [13731] from rjsparks@nostrum.com: Reworked how review assigments are processed in order to ensure a team's queue rotates when it should. Fixes #2305. - Legacy-Id: 13743 Note: SVN reference [13731] has been migrated to Git commit 7f5cef056ade1641f1d815b0a5f35962077d6d01 --- ietf/group/factories.py | 16 ++++++++++++++++ ietf/group/tests_info.py | 8 ++++++-- ietf/group/tests_review.py | 39 ++++++++++++++++++++++++++++++++++++++ ietf/group/views.py | 31 ++++++++++++++++++++++++------ ietf/review/factories.py | 22 +++++++++++++++++++++ 5 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 ietf/review/factories.py diff --git a/ietf/group/factories.py b/ietf/group/factories.py index d9d5d7b75..11ddcc27f 100644 --- a/ietf/group/factories.py +++ b/ietf/group/factories.py @@ -1,6 +1,8 @@ +import debug # pyflakes:ignore import factory from ietf.group.models import Group, Role, GroupEvent +from ietf.review.factories import ReviewTeamSettingsFactory class GroupFactory(factory.DjangoModelFactory): class Meta: @@ -8,6 +10,20 @@ class GroupFactory(factory.DjangoModelFactory): name = factory.Faker('sentence',nb_words=6) acronym = factory.Sequence(lambda n: 'acronym%d' %n) + state_id = 'active' + +class ReviewTeamFactory(factory.DjangoModelFactory): + class Meta: + model = Group + + type_id = 'dir' + name = factory.Faker('sentence',nb_words=6) + acronym = factory.Sequence(lambda n: 'acronym%d' %n) + state_id = 'active' + + @factory.post_generation + def settings(obj, create, extracted, **kwargs): + ReviewTeamSettingsFactory.create(group=obj,**kwargs) class RoleFactory(factory.DjangoModelFactory): class Meta: diff --git a/ietf/group/tests_info.py b/ietf/group/tests_info.py index 611c49e6a..4f9767d71 100644 --- a/ietf/group/tests_info.py +++ b/ietf/group/tests_info.py @@ -1269,8 +1269,12 @@ class StatusUpdateTests(TestCase): self.assertEqual(chair.group.latest_event(type='status_update').desc,'This came from a file.') def test_view_all_status_updates(self): - GroupEventFactory(type='status_update',desc='blah blah blah',group__type_id='wg') - GroupEventFactory(type='status_update',desc='blah blah blah',group__type_id='rg') + area = GroupFactory(type_id='area') + wg = GroupFactory(type_id='wg',parent=area) + irtf = GroupFactory(type_id='irtf') + rg = GroupFactory(type_id='rg',parent=irtf) + GroupEventFactory(type='status_update',desc='blah blah blah',group=wg) + GroupEventFactory(type='status_update',desc='blah blah blah',group=rg) url = urlreverse('ietf.group.views.all_status') response = self.client.get(url) self.assertEqual(response.status_code,200) diff --git a/ietf/group/tests_review.py b/ietf/group/tests_review.py index 05a75b792..9db606656 100644 --- a/ietf/group/tests_review.py +++ b/ietf/group/tests_review.py @@ -1,4 +1,5 @@ import datetime +import debug # pyflakes:ignore from pyquery import PyQuery @@ -15,12 +16,16 @@ from ietf.review.utils import ( suggested_review_requests_for_team, review_requests_needing_reviewer_reminder, email_reviewer_reminder, review_requests_needing_secretary_reminder, email_secretary_reminder, + reviewer_rotation_list, ) from ietf.name.models import ReviewTypeName, ReviewResultName, ReviewRequestStateName import ietf.group.views from ietf.utils.mail import outbox, empty_outbox from ietf.dbtemplate.factories import DBTemplateFactory from ietf.person.factories import PersonFactory +from ietf.doc.factories import DocumentFactory +from ietf.group.factories import RoleFactory, ReviewTeamFactory +from ietf.review.factories import ReviewRequestFactory class ReviewTests(TestCase): def test_review_requests(self): @@ -553,3 +558,37 @@ class ReviewTests(TestCase): self.assertEqual(len(outbox), 1) self.assertTrue(review_req.doc_id in outbox[0].get_payload(decode=True).decode("utf-8")) + + +class BulkAssignmentTests(TestCase): + + def test_rotation_queue_update(self): + group = ReviewTeamFactory.create() + empty_outbox() + reviewers = [RoleFactory.create(group=group,name_id='reviewer') for i in range(6)] # pyflakes:ignore + secretary = RoleFactory.create(group=group,name_id='secr') + docs = [DocumentFactory.create(type_id='draft',group=None) for i in range(4)] + requests = [ReviewRequestFactory(team=group,doc=docs[i]) for i in range(4)] + rot_list = reviewer_rotation_list(group) + + expected_ending_head_of_rotation = rot_list[3] + + unassigned_url = urlreverse(ietf.group.views.manage_review_requests, kwargs={ 'acronym': group.acronym, 'group_type': group.type_id, "assignment_status": "unassigned" }) + + postdict = {} + postdict['reviewrequest'] = [r.id for r in requests] + # assignments that affect the first 3 reviewers in queue + for i in range(3): + postdict['r{}-existing_reviewer'.format(requests[i].pk)] = '' + postdict['r{}-action'.format(requests[i].pk)] = 'assign' + postdict['r{}-reviewer'.format(requests[i].pk)] = rot_list[i].email_address() + # and one out of order assignment + postdict['r{}-existing_reviewer'.format(requests[3].pk)] = '' + postdict['r{}-action'.format(requests[3].pk)] = 'assign' + postdict['r{}-reviewer'.format(requests[3].pk)] = rot_list[5].email_address() + postdict['action'] = 'save' + self.client.login(username=secretary.person.user.username,password=secretary.person.user.username+'+password') + r = self.client.post(unassigned_url, postdict) + self.assertEqual(r.status_code,302) + self.assertEqual(expected_ending_head_of_rotation,reviewer_rotation_list(group)[0]) + self.assertEqual(len(outbox),4) diff --git a/ietf/group/views.py b/ietf/group/views.py index f263ba9a9..77a66161d 100644 --- a/ietf/group/views.py +++ b/ietf/group/views.py @@ -1456,13 +1456,32 @@ def manage_review_requests(request, acronym, group_type=None, assignment_status= form_results.append(req.form.is_valid()) if saving and all(form_results) and not (newly_closed > 0 or newly_opened > 0 or newly_assigned > 0): - for review_req in review_requests: - action = review_req.form.cleaned_data.get("action") - if action == "assign": - assign_review_request_to_reviewer(request, review_req, review_req.form.cleaned_data["reviewer"],review_req.form.cleaned_data["add_skip"]) - elif action == "close": - close_review_request(request, review_req, review_req.form.cleaned_data["close"]) + reqs_to_assign = [] + for review_req in review_requests: + form_action = review_req.form.cleaned_data.get("action") + if form_action=="close": + close_review_request(request, review_req, review_req.form.cleaned_data["close"]) + elif form_action=="assign": + reqs_to_assign.append(review_req) + + assignments_by_person = dict() + for r in reqs_to_assign: + assignments_by_person[r.form.cleaned_data["reviewer"].person] = r + + # Make sure the any assignments to the person at the head + # of the rotation queue are processed first so that the queue + # rotates before any more assignments are processed + head_of_rotation = reviewer_rotation_list(group)[0] + while head_of_rotation in assignments_by_person: + review_req = assignments_by_person[head_of_rotation] + assign_review_request_to_reviewer(request, review_req, review_req.form.cleaned_data["reviewer"],review_req.form.cleaned_data["add_skip"]) + reqs_to_assign.remove(review_req) + head_of_rotation = reviewer_rotation_list(group)[0] + + for review_req in reqs_to_assign: + assign_review_request_to_reviewer(request, review_req, review_req.form.cleaned_data["reviewer"],review_req.form.cleaned_data["add_skip"]) + kwargs = { "acronym": group.acronym } if group_type: kwargs["group_type"] = group_type diff --git a/ietf/review/factories.py b/ietf/review/factories.py new file mode 100644 index 000000000..043c52294 --- /dev/null +++ b/ietf/review/factories.py @@ -0,0 +1,22 @@ +import factory +import datetime + +from ietf.review.models import ReviewTeamSettings, ReviewRequest + +class ReviewTeamSettingsFactory(factory.DjangoModelFactory): + class Meta: + model = ReviewTeamSettings + + group = factory.SubFactory('ietf.group.factories.GroupFactory',type_id='dir') + +class ReviewRequestFactory(factory.DjangoModelFactory): + class Meta: + model = ReviewRequest + + state_id = 'requested' + type_id = 'lc' + doc = factory.SubFactory('ietf.doc.factories.DocumentFactory',type_id='draft') + team = factory.SubFactory('ietf.group.factories.ReviewTeamFactory',type_id='dir') + deadline = datetime.datetime.today()+datetime.timedelta(days=14) + requested_by = factory.SubFactory('ietf.person.factories.PersonFactory') +