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
This commit is contained in:
Henrik Levkowetz 2017-06-30 20:16:22 +00:00
parent 9415d65341
commit 108c00a0a3
5 changed files with 108 additions and 8 deletions

View file

@ -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:

View file

@ -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)

View file

@ -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)

View file

@ -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

22
ietf/review/factories.py Normal file
View file

@ -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')