def test_completed_projects(self): projects = Project.objects.all() initial_task = assign_task(self.workers[6].id, self.tasks['awaiting_processing'].id) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[6], 0) self.assertEquals(completed_projects(projects).count(), 0) next_task = assign_task( self.workers[6].id, initial_task.project.tasks.order_by('-start_datetime')[0].id) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(next_task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[6], 0) self.assertEquals(completed_projects(projects).count(), 0) next_task = assign_task( self.workers[6].id, initial_task.project.tasks.order_by('-start_datetime')[0].id) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(next_task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[6], 0) self.assertEquals(completed_projects(projects).count(), 1)
def test_role_counter_required_for_new_task(self): task = TaskFactory(status=Task.Status.COMPLETE) with self.assertRaises(TaskAssignmentError): role_counter_required_for_new_task(task) project = self.projects['assignment_policy'] # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 task = project.tasks.first() counter = role_counter_required_for_new_task(task) self.assertEquals(counter, 0) initial_task = assign_task(self.workers[0].id, task.id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=True): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) counter = role_counter_required_for_new_task(initial_task) self.assertEquals(counter, 1) initial_task = assign_task(self.workers[1].id, task.id) initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[1]) counter = role_counter_required_for_new_task(initial_task) self.assertEquals(counter, 2)
def test_completed_projects(self): projects = Project.objects.all() initial_task = assign_task(self.workers[6].id, self.tasks['awaiting_processing'].id) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[6]) self.assertEquals(completed_projects(projects).count(), 0) next_task = assign_task( self.workers[6].id, initial_task.project.tasks.order_by('-start_datetime')[0].id) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(next_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[6]) self.assertEquals(completed_projects(projects).count(), 0) next_task = assign_task( self.workers[6].id, initial_task.project.tasks.order_by('-start_datetime')[0].id) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(next_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[6]) self.assertEquals(completed_projects(projects).count(), 1)
def test_completed_projects(self): projects = Project.objects.all() initial_task = assign_task(self.workers[6].id, self.tasks['processing_task'].id) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[6], 0) self.assertEquals(completed_projects(projects).count(), 0) next_task = assign_task( self.workers[6].id, initial_task.project.tasks.order_by('-start_datetime')[0].id) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(next_task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[6], 0) self.assertEquals(completed_projects(projects).count(), 0) next_task = assign_task( self.workers[6].id, initial_task.project.tasks.order_by('-start_datetime')[0].id) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(next_task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[6], 0) self.assertEquals(completed_projects(projects).count(), 1)
def handle_staffing_response(worker, staffing_request_inquiry_id, is_available=False): """ Args: worker (orchestra.models.Worker): Worker instance that responsed to a staffing request inquiry staffing_request_inquiry_id (int): Id of a staffing_request_inquiry that is associated with a response is_available (boolean): Boolean that tells whether worker accepted an inquiry or not Returns: response (orchestra.models.StaffingResponse): StaffingResponse object that has been created for the worker """ staffing_request_inquiry = get_object_or_None( StaffingRequestInquiry, communication_preference__worker=worker, id=staffing_request_inquiry_id) if staffing_request_inquiry is None: return None response = (StaffingResponse.objects.filter( request_inquiry=staffing_request_inquiry)) if response.exists(): response = response.first() if not is_available and response.is_winner: raise StaffingResponseException( 'Cannot reject after accepting the task') response.is_available = is_available else: response = StaffingResponse.objects.create( request_inquiry=staffing_request_inquiry, is_available=is_available) if (is_available and not StaffingResponse.objects.filter( request_inquiry__request=staffing_request_inquiry.request, is_winner=True).exists()): request = staffing_request_inquiry.request task_assignment = get_object_or_None( TaskAssignment, task=request.task, assignment_counter=request.required_role_counter) # if task assignment exists then reassign if task_assignment is not None: reassign_assignment(worker.id, task_assignment.id, staffing_request_inquiry) # otherwise assign task else: assign_task(worker.id, request.task.id, staffing_request_inquiry) response = (StaffingResponse.objects.filter( request_inquiry=staffing_request_inquiry).first()) check_responses_complete(staffing_request_inquiry.request) return response
def assign_task_api(request): worker_username = load_encoded_json(request.body)['worker_username'] try: worker = Worker.objects.get(user__username=worker_username) task_id = load_encoded_json(request.body)['task_id'] assign_task(worker.id, task_id) except (Worker.DoesNotExist, Task.DoesNotExist, WorkerCertificationError) as e: raise BadRequest(e)
def test_task_form_init(self): """ Test task form initialization for new, human and machine tasks """ # Create new task form # (Test form init with no task instance) TaskForm() project = Project.objects.get(workflow_slug='test_workflow_2') self.assertEquals(Task.objects.filter(project=project).count(), 0) create_subsequent_tasks(project) # Human task was created but not assigned # (Test form init with empty assignment history) self.assertEquals(Task.objects.filter(project=project).count(), 1) human_task = Task.objects.filter(project=project).first() form = TaskForm(instance=human_task) self.assertEquals(form.fields['currently_assigned_to'].initial, None) # Human task assigned to entry_level worker # (Test form init with a single entry-level worker) human_task = assign_task(self.workers[0].id, human_task.id) form = TaskForm(instance=human_task) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=True): human_task = submit_task(human_task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[0], 0) self.assertEquals(form.fields['currently_assigned_to'].initial, self.workers[0].id) # Human task under review # (Test form init with both an entry-level worker and reviewer) human_task = assign_task(self.workers[1].id, human_task.id) form = TaskForm(instance=human_task) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): human_task = submit_task(human_task.id, {}, TaskAssignment.SnapshotType.ACCEPT, self.workers[1], 0) self.assertEquals(form.fields['currently_assigned_to'].initial, self.workers[1].id) # Machine task was created # (Test form init with a machine task) self.assertEquals(Task.objects.filter(project=project).count(), 2) machine_task = (Task.objects.filter(project=project) .exclude(id=human_task.id).first()) form = TaskForm(instance=machine_task) self.assertEquals(form.fields['currently_assigned_to'].initial, None)
def test_complete_all_tasks_slack_annoucement(self, mock_slack_archive): project = self.projects['single_human_step'] create_subsequent_tasks(project) task = Task.objects.get(project=project) assign_task(self.workers[1].id, task.id) self.assertEqual(project.status, Project.Status.ACTIVE) self.assertFalse(mock_slack_archive.called) complete_and_skip_task(task.id) create_subsequent_tasks(project) project.refresh_from_db() self.assertEqual(project.status, Project.Status.COMPLETED) self.assertTrue(mock_slack_archive.called)
def handle_staffing_response(worker, staffing_request_inquiry_id, is_available=False): # TODO(kkamalov): add proper docstring staffing_request_inquiry = get_object_or_None( StaffingRequestInquiry, communication_preference__worker=worker, id=staffing_request_inquiry_id ) if staffing_request_inquiry is None: return None response = (StaffingResponse.objects .filter(request=staffing_request_inquiry)) if response.exists(): response = response.first() if not is_available and response.is_winner: raise StaffingResponseException( 'Cannot reject after accepting the task') response.is_available = is_available else: response = StaffingResponse.objects.create( request=staffing_request_inquiry, is_available=is_available) if (is_available and not StaffingResponse.objects.filter( request__request=staffing_request_inquiry.request, is_winner=True).exists()): response.is_winner = True request = staffing_request_inquiry.request task_assignment = get_object_or_None( TaskAssignment, task=request.task, assignment_counter=request.required_role_counter ) # if task assignment exists then reassign if task_assignment is not None: reassign_assignment(worker.id, task_assignment.id) # otherwise assign task else: assign_task(worker.id, request.task.id) response.save() check_responses_complete(staffing_request_inquiry.request) return response
def assign_worker_to_task(request): data = load_encoded_json(request.body) errors = {} try: assign_task(data.get('worker_id'), data.get('task_id')) except WorkerCertificationError as e: errors['worker_certification_error'] = str(e) except TaskAssignmentError as e: errors['task_assignment_error'] = str(e) except Exception as e: errors['error'] = str(e) success = len(errors) == 0 return { 'success': success, 'errors': errors, }
def test_restaff_close_requests(self, mock_slack, mock_experts_slack, mock_mail): """ Test that existing staffbot requests for a task is closed when a staff function is called. """ CLOSED = StaffBotRequest.Status.CLOSED.value bot = StaffBot() task = (Task.objects.filter( status=Task.Status.AWAITING_PROCESSING).first()) task = assign_task(self.worker.id, task.id) init_num_request = StaffBotRequest.objects.filter(task=task).count() self.assertEqual(init_num_request, 0) bot.restaff(task.id, self.worker.user.username) requests = StaffBotRequest.objects.filter(task=task) num_request = requests.count() self.assertEqual(num_request, init_num_request + 1) self.assertNotEqual(requests.last().status, CLOSED) # Calling restaff on the same task should close the previous request # and create a new one. bot.restaff(task.id, self.worker.user.username) requests = list(StaffBotRequest.objects.filter(task=task)) num_request = len(requests) self.assertEqual(num_request, init_num_request + 2) self.assertEqual(requests[-2].status, CLOSED) self.assertNotEqual(requests[-1].status, CLOSED)
def test_malformed_creation_policy(self): project = self.projects['creation_policy'] workflow_version = project.workflow_version first_step = self.workflow_steps[ workflow_version.slug]['creation_policy_step_0'] # Create an invalid machine step with an assignment policy malformed_step = StepFactory( workflow_version=workflow_version, slug='machine_step', is_human=False, creation_policy={}, ) malformed_step.creation_depends_on.add(first_step) # Create first task in project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 and mark as complete initial_task = assign_task(self.workers[4].id, project.tasks.first().id) initial_task.status = Task.Status.COMPLETE initial_task.save() # Cannot have an invalid blob for the creation_policy with self.assertRaises(CreationPolicyError): create_subsequent_tasks(project)
def test_staff_command(self, mock_slack, mock_experts_slack, mock_mail): """ Test that the staffing logic is properly executed for the staff command. """ task = (Task.objects .filter(status=Task.Status.AWAITING_PROCESSING) .first()) # Get certified worker worker = self._get_worker_for_task( task, WorkerCertification.Role.ENTRY_LEVEL) self._test_staffing_requests(worker, task, 'staff {}'.format(task.id), can_slack=True, can_mail=True) self._test_staffing_requests(worker, task, 'staff {}'.format(task.id), can_slack=False, can_mail=False) # Change the task state to pending review task = assign_task(worker.id, task.id) task.status = Task.Status.PENDING_REVIEW task.save() StaffingRequestInquiry.objects.all().delete() worker = self._get_worker_for_task(task, WorkerCertification.Role.REVIEWER) self._test_staffing_requests(worker, task, 'staff {}'.format(task.id), can_slack=False, can_mail=False) self._test_staffing_requests(worker, task, 'staff {}'.format(task.id), can_slack=True, can_mail=True) self.assertTrue(mock_mail.called) self.assertTrue(mock_experts_slack.called) self.assertTrue(mock_slack.called)
def test_malformed_creation_policy(self): project = self.projects['creation_policy'] workflow_version = project.workflow_version first_step = self.workflow_steps[ workflow_version.slug]['creation_policy_step_0'] # Create an invalid machine step with an assignment policy malformed_step = StepFactory( workflow_version=workflow_version, slug='machine_step', is_human=False, creation_policy={}, ) malformed_step.creation_depends_on.add(first_step) # Create first task in project create_subsequent_tasks(project) self.assertEqual(project.tasks.count(), 1) # Assign initial task to worker 0 and mark as complete initial_task = assign_task(self.workers[4].id, project.tasks.first().id) initial_task.status = Task.Status.COMPLETE initial_task.save() # Cannot have an invalid blob for the creation_policy with self.assertRaises(CreationPolicyError): create_subsequent_tasks(project)
def test_preassign_workers(self, mock_mail, mock_slack): request_cause = StaffBotRequest.RequestCause.TASK_POLICY.value staffing_request_count = StaffingRequestInquiry.objects.filter( request__request_cause=request_cause).count() project = self.projects['staffbot_assignment_policy'] # Create first task in test project create_subsequent_tasks(project) address_staffing_requests() self.assertEqual(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) # Mock mail should be called if we autostaff self.assertTrue(mock_mail.called) self.assertTrue(mock_slack.called) # Assert we created new StaffingRequestInquirys because of autostaff new_staffing_request_count = StaffingRequestInquiry.objects.filter( request__request_cause=request_cause).count() self.assertTrue(staffing_request_count < new_staffing_request_count) self.assertEqual(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 0 not certified for related tasks, so should not have been # auto-assigned self.assertEqual(related_task.assignments.count(), 0) self.assertEqual(related_task.status, Task.Status.AWAITING_PROCESSING)
def test_preassign_workers(self): project = self.projects['assignment_policy'] # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[0], 0) self.assertEquals(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 0 not certified for related tasks, so should not have been # auto-assigned self.assertEquals(related_task.assignments.count(), 0) self.assertEquals(related_task.status, Task.Status.AWAITING_PROCESSING) # Reset project project.tasks.all().delete() # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 4 initial_task = assign_task(self.workers[4].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[4], 0) self.assertEquals(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 4 is certified for related task and should have been assigned self.assertEquals(related_task.assignments.count(), 1) self.assertEquals(related_task.status, Task.Status.PROCESSING) self.assertTrue(is_worker_assigned_to_task(self.workers[4], related_task)) # Reset project project.tasks.all().delete()
def test_preassign_workers(self): project = self.projects['assignment_policy'] # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[0], 0) self.assertEquals(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 0 not certified for related tasks, so should not have been # auto-assigned self.assertEquals(related_task.assignments.count(), 0) self.assertEquals(related_task.status, Task.Status.AWAITING_PROCESSING) # Reset project project.tasks.all().delete() # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 4 initial_task = assign_task(self.workers[4].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[4], 0) self.assertEquals(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 4 is certified for related task and should have been assigned self.assertEquals(related_task.assignments.count(), 1) self.assertEquals(related_task.status, Task.Status.PROCESSING) self.assertTrue( is_worker_assigned_to_task(self.workers[4], related_task)) # Reset project project.tasks.all().delete()
def test_malformed_assignment_policy(self): project = self.projects['assignment_policy'] workflow_version = project.workflow_version first_step = self.workflow_steps[workflow_version.slug]['step_0'] # Create an invalid machine step with an assignment policy malformed_step = StepFactory( workflow_version=workflow_version, slug='machine_step', is_human=False, assignment_policy={ 'policy_function': { 'entry_level': { 'path': ('orchestra.assignment_policies.' 'previously_completed_steps'), 'kwargs': { 'related_steps': ['step_0'] }, } } }, creation_policy=get_default_creation_policy(), ) malformed_step.creation_depends_on.add(first_step) # Create first task in project create_subsequent_tasks(project) self.assertEqual(project.tasks.count(), 1) # Assign initial task to worker 0 and mark as complete initial_task = assign_task(self.workers[4].id, project.tasks.first().id) initial_task.status = Task.Status.COMPLETE initial_task.save() # Cannot preassign machine task with self.assertRaises(AssignmentPolicyError): create_subsequent_tasks(project) # Reset project project.tasks.all().delete() # Machine should not be member of assignment policy first_step.assignment_policy = { 'policy_function': { 'entry_level': { 'path': ('orchestra.assignment_policies.' 'previously_completed_steps'), 'kwargs': { 'related_steps': ['machine_step'] }, }, } } first_step.save() with self.assertRaises(AssignmentPolicyError): create_subsequent_tasks(project)
def test_malformed_assignment_policy(self): project = self.projects['assignment_policy'] workflow_version = project.workflow_version first_step = self.workflow_steps[workflow_version.slug]['step_0'] # Create an invalid machine step with an assignment policy malformed_step = StepFactory( workflow_version=workflow_version, slug='machine_step', is_human=False, assignment_policy={ 'policy_function': { 'entry_level': { 'path': ('orchestra.assignment_policies.' 'previously_completed_steps'), 'kwargs': { 'related_steps': ['step_0'] }, } } }, creation_policy=get_default_creation_policy(), ) malformed_step.creation_depends_on.add(first_step) # Create first task in project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 and mark as complete initial_task = assign_task(self.workers[4].id, project.tasks.first().id) initial_task.status = Task.Status.COMPLETE initial_task.save() # Cannot preassign machine task with self.assertRaises(AssignmentPolicyError): create_subsequent_tasks(project) # Reset project project.tasks.all().delete() # Machine should not be member of assignment policy first_step.assignment_policy = { 'policy_function': { 'entry_level': { 'path': ('orchestra.assignment_policies.' 'previously_completed_steps'), 'kwargs': { 'related_steps': ['machine_step'] }, }, } } first_step.save() with self.assertRaises(AssignmentPolicyError): create_subsequent_tasks(project)
def test_unarchive_slack_channel_api(self, mock_archive, mock_unarchive): project = self.projects['single_human_step'] create_subsequent_tasks(project) task = Task.objects.get(project=project) assign_task(self.workers[1].id, task.id) complete_and_skip_task(task.id) create_subsequent_tasks(project) self.assertTrue(mock_archive.called) response = self.api_client.post(reverse( ('orchestra:orchestra:project_management' ':unarchive_slack_channel')), json.dumps({ 'project_id': project.id, }), content_type='application/json') self.assertEqual(response.status_code, 200) self.assertTrue(mock_unarchive.called)
def test_completion_ends_project_false(self, mock_slack_archive): project = self.projects['test_human_and_machine'] create_subsequent_tasks(project) task = project.tasks.first() assign_task(self.workers[1].id, task.id) self.assertEqual(project.status, Project.Status.ACTIVE) self.assertFalse(mock_slack_archive.called) task.status = Task.Status.COMPLETE task.save() create_subsequent_tasks(project) project.refresh_from_db() incomplete_tasks = (Task.objects.filter(project=project).exclude( Q(status=Task.Status.COMPLETE) | Q(status=Task.Status.ABORTED))).count() self.assertTrue(incomplete_tasks > 0) self.assertEqual(project.status, Project.Status.ACTIVE) self.assertFalse(mock_slack_archive.called)
def test_assign_task(self): entry_task = TaskFactory(project=self.projects['base_test_project'], status=Task.Status.AWAITING_PROCESSING, step=self.test_step) # Assign entry-level task to entry-level worker entry_task = assign_task(self.workers[0].id, entry_task.id) self.assertTrue(is_worker_assigned_to_task(self.workers[0], entry_task)) self.assertEquals(entry_task.status, Task.Status.PROCESSING) self.assertEquals(entry_task.assignments.count(), 1) # Attempt to assign task which isn't awaiting a new assignment invalid = (Task.Status.PROCESSING, Task.Status.ABORTED, Task.Status.REVIEWING, Task.Status.COMPLETE, Task.Status.POST_REVIEW_PROCESSING) for status in invalid: invalid_status_task = Task.objects.create( project=self.projects['base_test_project'], status=status, step=self.test_step) with self.assertRaises(TaskAssignmentError): invalid_status_task = assign_task(self.workers[0].id, invalid_status_task.id) # Attempt to assign review task to worker already in review hierarchy review_task = Task.objects.create( project=self.projects['base_test_project'], status=Task.Status.PENDING_REVIEW, step=self.test_step) test_data = {'test_assign': True} TaskAssignmentFactory(worker=self.workers[1], task=review_task, status=TaskAssignment.Status.SUBMITTED, in_progress_task_data=test_data, snapshots=empty_snapshots()) with self.assertRaises(TaskAssignmentError): assign_task(self.workers[1].id, review_task.id) self.assertEqual( current_assignment(review_task).in_progress_task_data, test_data) # Attempt to assign review task to worker not certified for task with self.assertRaises(WorkerCertificationError): assign_task(self.workers[2].id, review_task.id) self.assertEqual( current_assignment(review_task).in_progress_task_data, test_data) # Assign review task to review worker self.assertEquals(review_task.assignments.count(), 1) review_task = assign_task(self.workers[3].id, review_task.id) self.assertEquals(review_task.assignments.count(), 2) self.assertEqual( current_assignment(review_task).worker, self.workers[3]) self.assertEqual( current_assignment(review_task).in_progress_task_data, test_data) self.assertEquals(review_task.status, Task.Status.REVIEWING)
def specified_worker(task, username, **kwargs): """ Assign task to a specific person. Args: username (str): Username of the worker to assign. Returns: task (orchestra.models.Task): The modified task object. """ worker = Worker.objects.get(user__username=username) return assign_task(worker.id, task.id)
def save(self, *args, **kwargs): # Create task before further modifications task = super(TaskForm, self).save(*args, **kwargs) new_worker_id = self.cleaned_data.get("currently_assigned_to", None) # TODO(jrbotros): write helper functions to move back and forth through # task statuses in the admin if new_worker_id and new_worker_id not in [worker.id for worker in all_workers(task)]: # If no pre-existing worker is present or the selected worker # has not previously been involved with the task, (re)assign it. task = assign_task(new_worker_id, task.id) return task
def test_restaff_command(self, mock_slack, mock_mail): data = get_mock_slack_data(user_id=self.worker.slack_user_id) task = (Task.objects.filter( status=Task.Status.AWAITING_PROCESSING).first()) worker = self.workers[0] task = assign_task(worker.id, task.id) command = 'restaff {} {}'.format(task.id, worker.user.username) data = get_mock_slack_data(text=command, user_id=self.worker.slack_user_id) response = self.request_client.post(self.url, data) self.assertEqual( load_encoded_json(response.content)['attachments'][0]['text'], self.staffbot.restaffing_success.format(task.id))
def test_restaff_command(self, mock_slack, mock_mail): data = get_mock_slack_data(user_id=self.worker.slack_user_id) task = ( Task.objects.filter(status=Task.Status.AWAITING_PROCESSING) .first()) worker = self.workers[0] task = assign_task(worker.id, task.id) command = 'restaff {} {}'.format(task.id, worker.user.username) data = get_mock_slack_data( text=command, user_id=self.worker.slack_user_id) response = self.request_client.post(self.url, data) self.assertEqual(load_encoded_json(response.content).get('text'), 'Restaffed task {}!'.format(task.id))
def test_restaff_command(self, mock_slack, mock_mail): """ Test that the restaffing logic is properly executed for the restaff command. """ task = (Task.objects .filter(status=Task.Status.AWAITING_PROCESSING) .first()) # Get certified worker task = assign_task(self.worker.id, task.id) command = 'restaff {} {}'.format(task.id, self.worker.user.username) worker = self.workers[3] self._test_staffing_requests(worker, task, command, can_slack=False, can_mail=True)
def previously_completed_steps(task, related_steps, **kwargs): """ Assign a new task to the entry-level worker of the specified tasks. If no worker can be assigned, return the unmodified task. Args: task (orchestra.models.Task): The newly created task to assign. related_steps ([str]): List of step slugs from which to attempt to assign a worker. Returns: task (orchestra.models.Task): The modified task object. Raises: orchestra.core.errors.AssignmentPolicyError: Machine steps cannot be included in an assignment policy. """ if related_steps is None: raise AssignmentPolicyError('No related steps given') workflow_version = task.step.workflow_version for step_slug in related_steps: step = workflow_version.steps.get(slug=step_slug) if not step.is_human: raise AssignmentPolicyError('Machine step should not be ' 'member of assignment policy') related_tasks = (Task.objects.filter( step__slug__in=related_steps, project=task.project).select_related('step')) for related_task in related_tasks: entry_level_assignment = assignment_history(related_task).first() if entry_level_assignment and entry_level_assignment.worker: try: return assign_task(entry_level_assignment.worker.id, task.id) except WorkerCertificationError: # Task could not be assigned to related worker, try with # another related worker logger.warning( 'Tried to assign worker %s to step %s, for ' 'which they are not certified', entry_level_assignment.worker.id, task.step.slug, exc_info=True) except Exception: logger.warning('Unable to assign task.', exc_info=True) return task
def test_schedule_machine_tasks_failed(self, mock_schedule, mock_preassign): project = self.projects['test_human_and_machine'] # Create first task in project create_subsequent_tasks(project) # Assign initial task to worker 0 and mark as complete initial_task = assign_task(self.workers[0].id, project.tasks.first().id) initial_task.status = Task.Status.COMPLETE initial_task.save() mock_preassign.side_effect = Exception with self.assertRaises(Exception): create_subsequent_tasks(project) mock_schedule.assert_not_called()
def test_schedule_machine_tasks(self, mock_schedule): project = self.projects['test_human_and_machine'] # Create first task in project create_subsequent_tasks(project) # Assign initial task to worker 0 and mark as complete initial_task = assign_task(self.workers[0].id, project.tasks.first().id) initial_task.status = Task.Status.COMPLETE initial_task.save() create_subsequent_tasks(project) self.assertEqual(mock_schedule.call_count, 1) self.assertEqual(mock_schedule.call_args[0][0], project) steps = list(project.workflow_version.steps.filter(is_human=False)) self.assertEqual(mock_schedule.call_args[0][1], steps)
def previously_completed_steps(task, related_steps, **kwargs): """ Assign a new task to the entry-level worker of the specified tasks. If no worker can be assigned, return the unmodified task. Args: task (orchestra.models.Task): The newly created task to assign. related_steps ([str]): List of step slugs from which to attempt to assign a worker. Returns: task (orchestra.models.Task): The modified task object. Raises: orchestra.core.errors.AssignmentPolicyError: Machine steps cannot be included in an assignment policy. """ if related_steps is None: raise AssignmentPolicyError('No related steps given') workflow_version = task.step.workflow_version for step_slug in related_steps: step = workflow_version.steps.get(slug=step_slug) if not step.is_human: raise AssignmentPolicyError('Machine step should not be ' 'member of assignment policy') related_tasks = ( Task.objects .filter(step__slug__in=related_steps, project=task.project) .select_related('step')) for related_task in related_tasks: entry_level_assignment = assignment_history(related_task).first() if entry_level_assignment and entry_level_assignment.worker: try: return assign_task(entry_level_assignment.worker.id, task.id) except WorkerCertificationError: # Task could not be assigned to related worker, try with # another related worker logger.warning('Tried to assign worker %s to step %s, for ' 'which they are not certified', entry_level_assignment.worker.id, task.step.slug, exc_info=True) except Exception: logger.warning('Unable to assign task.', exc_info=True) return task
def test_always_create_policy(self): project = self.projects['creation_policy'] # Create first task in test project create_subsequent_tasks(project) self.assertEqual(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; next task should not be created, it never is with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) self.assertEqual(project.tasks.count(), 1)
def test_malformed_assignment_policy(self): project = self.projects['assignment_policy'] # Machine should not have an assignment policy workflow = get_workflow_by_slug('assignment_policy_workflow') machine_step = Step( slug='machine_step', worker_type=Step.WorkerType.MACHINE, assignment_policy={'policy': 'previously_completed_steps', 'steps': ['step_0']}, creation_depends_on=[workflow.get_step('step_0')], function=lambda *args: None, ) workflow.add_step(machine_step) # Create first task in project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 and mark as complete initial_task = assign_task(self.workers[4].id, project.tasks.first().id) initial_task.status = Task.Status.COMPLETE initial_task.save() # Cannot preassign machine task with self.assertRaises(AssignmentPolicyError): create_subsequent_tasks(project) # Reset project project.tasks.all().delete() # Machine should not be member of assignment policy (workflow.get_step('step_0') .assignment_policy) = {'policy': 'previously_completed_steps', 'steps': ['machine_step']} with self.assertRaises(AssignmentPolicyError): create_subsequent_tasks(project) # Reset workflow and project (workflow.get_step('step_0') .assignment_policy) = {'policy': 'anyone_certified'} del workflow.steps['machine_step'] project.tasks.all().delete()
def test_restaff_command(self, mock_slack, mock_experts_slack, mock_mail): """ Test that the restaffing logic is properly executed for the restaff command. """ task = (Task.objects .filter(status=Task.Status.AWAITING_PROCESSING) .first()) # Get certified worker task = assign_task(self.worker.id, task.id) command = 'restaff {} {}'.format(task.id, self.worker.user.username) worker = self.workers[3] self._test_staffing_requests(worker, task, command, can_slack=False, can_mail=True) self.assertTrue(mock_mail.called) self.assertTrue(mock_experts_slack.called) self.assertTrue(mock_slack.called)
def test_preassign_workers(self, mock_mail, mock_slack): request_cause = StaffBotRequest.RequestCause.AUTOSTAFF.value staffing_request_count = StaffingRequestInquiry.objects.filter( request__request_cause=request_cause).count() project = self.projects['staffbot_assignment_policy'] # Create first task in test project create_subsequent_tasks(project) send_staffing_requests() self.assertEqual(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) # Mock mail should be called if we autostaff self.assertTrue(mock_mail.called) self.assertTrue(mock_slack.called) # Assert we created new StaffingRequestInquirys because of autostaff new_staffing_request_count = StaffingRequestInquiry.objects.filter( request__request_cause=request_cause).count() self.assertTrue(staffing_request_count < new_staffing_request_count) self.assertEqual(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 0 not certified for related tasks, so should not have been # auto-assigned self.assertEqual(related_task.assignments.count(), 0) self.assertEqual(related_task.status, Task.Status.AWAITING_PROCESSING) # Reset project project.tasks.all().delete()
def setup_complete_task(test_case): # Microseconds are truncated when manually saving models test_start = timezone.now().replace(microsecond=0) times = { 'awaiting_pickup': test_start, 'entry_pickup': test_start + timedelta(hours=1), 'entry_submit': test_start + timedelta(hours=2), 'reviewer_pickup': test_start + timedelta(hours=3), 'reviewer_reject': test_start + timedelta(hours=4), 'entry_resubmit': test_start + timedelta(hours=5), 'reviewer_accept': test_start + timedelta(hours=6), } task = TaskFactory( project=test_case.projects['empty_project'], status=Task.Status.AWAITING_PROCESSING, step=test_case.test_step, start_datetime=times['awaiting_pickup']) workers = { 'entry': test_case.workers[0], 'reviewer': test_case.workers[1] } assign_task(workers['entry'].id, task.id) task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.PROCESSING) submit_task( task.id, {'test': 'entry_submit'}, Iteration.Status.REQUESTED_REVIEW, workers['entry']) task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.PENDING_REVIEW) assign_task(workers['reviewer'].id, task.id) reviewer_assignment = task.assignments.get( worker=workers['reviewer']) # Modify assignment with correct datetime reviewer_assignment.start_datetime = times['reviewer_pickup'] reviewer_assignment.save() task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.REVIEWING) submit_task( task.id, {'test': 'reviewer_reject'}, Iteration.Status.PROVIDED_REVIEW, workers['reviewer']) task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.POST_REVIEW_PROCESSING) submit_task( task.id, {'test': 'entry_resubmit'}, Iteration.Status.REQUESTED_REVIEW, workers['entry']) task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.REVIEWING) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): submit_task( task.id, {'test': 'reviewer_accept'}, Iteration.Status.REQUESTED_REVIEW, workers['reviewer']) task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.COMPLETE) test_case.assertEquals(task.assignments.count(), 2) for assignment in task.assignments.all(): test_case.assertEquals( assignment.status, TaskAssignment.Status.SUBMITTED) test_case.assertEquals(assignment.iterations.count(), 2) # Modify assignments with correct datetime new_datetime_labels = ('entry_pickup', 'reviewer_pickup') for i, assignment in enumerate(assignment_history(task).all()): assignment.start_datetime = times[new_datetime_labels[i]] assignment.save() # Modify iterations with correct datetime new_datetime_labels = ( ('entry_pickup', 'entry_submit'), ('reviewer_pickup', 'reviewer_reject'), ('reviewer_reject', 'entry_resubmit'), ('entry_resubmit', 'reviewer_accept') ) new_datetimes = [ (times[start_label], times[end_label]) for start_label, end_label in new_datetime_labels] for i, iteration in enumerate(get_iteration_history(task)): iteration.start_datetime, iteration.end_datetime = new_datetimes[i] iteration.save() verify_iterations(task.id) return task
def setup_complete_task(test_case, times): task = TaskFactory(project=test_case.projects['empty_project'], status=Task.Status.AWAITING_PROCESSING, step=test_case.test_step, start_datetime=times['awaiting_pickup']) workers = {'entry': test_case.workers[0], 'reviewer': test_case.workers[1]} assign_task(workers['entry'].id, task.id) entry_assignment = task.assignments.get(worker=workers['entry']) # Modify assignment with correct datetime entry_assignment.start_datetime = times['entry_pickup'] entry_assignment.save() task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.PROCESSING) submit_task(task.id, {'test': 'entry_submit'}, TaskAssignment.SnapshotType.SUBMIT, workers['entry'], 0) task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.PENDING_REVIEW) # Modify snapshot with correct datetime entry_assignment.refresh_from_db() (entry_assignment.snapshots['snapshots'][0]['datetime'] ) = times['entry_submit'] entry_assignment.save() assign_task(workers['reviewer'].id, task.id) reviewer_assignment = task.assignments.get(worker=workers['reviewer']) # Modify assignment with correct datetime reviewer_assignment.start_datetime = times['reviewer_pickup'] reviewer_assignment.save() task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.REVIEWING) submit_task(task.id, {'test': 'reviewer_reject'}, TaskAssignment.SnapshotType.REJECT, workers['reviewer'], 0) # Modify snapshot with correct datetime reviewer_assignment.refresh_from_db() (reviewer_assignment.snapshots['snapshots'][0]['datetime'] ) = times['reviewer_reject'] reviewer_assignment.save() task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.POST_REVIEW_PROCESSING) submit_task(task.id, {'test': 'entry_resubmit'}, TaskAssignment.SnapshotType.SUBMIT, workers['entry'], 0) # Modify snapshot with correct datetime entry_assignment.refresh_from_db() (entry_assignment.snapshots['snapshots'][1]['datetime'] ) = times['entry_resubmit'] entry_assignment.save() task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.REVIEWING) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): submit_task(task.id, {'test': 'reviewer_accept'}, TaskAssignment.SnapshotType.ACCEPT, workers['reviewer'], 0) # Modify snapshot with correct datetime reviewer_assignment.refresh_from_db() (reviewer_assignment.snapshots['snapshots'][1]['datetime'] ) = times['reviewer_accept'] reviewer_assignment.save() task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.COMPLETE) test_case.assertEquals(task.assignments.count(), 2) for assignment in task.assignments.all(): test_case.assertEquals(assignment.status, TaskAssignment.Status.SUBMITTED) test_case.assertEquals(len(assignment.snapshots['snapshots']), 2) return task
def test_preassign_workers(self): project = self.projects['assignment_policy'] # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) self.assertEquals(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 0 not certified for related tasks, so should not have been # auto-assigned self.assertEquals(related_task.assignments.count(), 0) self.assertEquals(related_task.status, Task.Status.AWAITING_PROCESSING) # Reset project project.tasks.all().delete() # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; verify we use the reviewer assignment policy mock_preassign_workers = MagicMock(return_value=initial_task) patch_path = 'orchestra.utils.task_lifecycle._preassign_workers' with patch(patch_path, new=mock_preassign_workers): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) mock_preassign_workers.assert_called_once_with( initial_task, AssignmentPolicyType.REVIEWER) # Reset project project.tasks.all().delete() # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 4 initial_task = assign_task(self.workers[4].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[4]) self.assertEquals(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 4 is certified for related task and should have been assigned self.assertEquals(related_task.assignments.count(), 1) self.assertEquals(related_task.status, Task.Status.PROCESSING) self.assertTrue(related_task.is_worker_assigned(self.workers[4])) # Reset project project.tasks.all().delete()
def test_assign_task(self): entry_task = TaskFactory(project=self.projects['base_test_project'], status=Task.Status.AWAITING_PROCESSING, step=self.test_step) # No iterations should be present for task self.assertEqual( Iteration.objects.filter(assignment__task=entry_task).count(), 0) # Assign entry-level task to entry-level worker entry_task = assign_task(self.workers[0].id, entry_task.id) self.assertTrue(entry_task.is_worker_assigned(self.workers[0])) self.assertEqual(entry_task.status, Task.Status.PROCESSING) self.assertEqual(entry_task.assignments.count(), 1) entry_assignment = entry_task.assignments.first() # A single iteration was created for the assignment self.assertEqual(entry_assignment.iterations.count(), 1) self.assertEqual( Iteration.objects.filter(assignment__task=entry_task).count(), 1) self.assertEqual(entry_assignment.iterations.first().start_datetime, entry_assignment.start_datetime) # Attempt to assign task which isn't awaiting a new assignment invalid = (Task.Status.PROCESSING, Task.Status.ABORTED, Task.Status.REVIEWING, Task.Status.COMPLETE, Task.Status.POST_REVIEW_PROCESSING) for status in invalid: invalid_status_task = Task.objects.create( project=self.projects['base_test_project'], status=status, step=self.test_step) with self.assertRaises(TaskAssignmentError): invalid_status_task = assign_task(self.workers[0].id, invalid_status_task.id) # Attempt to assign review task to worker already in review hierarchy review_task = Task.objects.create( project=self.projects['base_test_project'], status=Task.Status.PENDING_REVIEW, step=self.test_step) test_data = {'test_assign': True} TaskAssignmentFactory(worker=self.workers[1], task=review_task, status=TaskAssignment.Status.SUBMITTED, in_progress_task_data=test_data) with self.assertRaises(TaskAssignmentError): assign_task(self.workers[1].id, review_task.id) self.assertEqual( current_assignment(review_task).in_progress_task_data, test_data) # Attempt to assign review task to worker not certified for task with self.assertRaises(WorkerCertificationError): assign_task(self.workers[2].id, review_task.id) self.assertEqual( current_assignment(review_task).in_progress_task_data, test_data) # Assign review task to review worker self.assertEquals(review_task.assignments.count(), 1) review_task = assign_task(self.workers[3].id, review_task.id) self.assertEquals(review_task.assignments.count(), 2) reviewer_assignment = current_assignment(review_task) self.assertEqual(reviewer_assignment.worker, self.workers[3]) self.assertEqual(reviewer_assignment.in_progress_task_data, test_data) self.assertEquals(reviewer_assignment.iterations.count(), 1) self.assertEqual(reviewer_assignment.iterations.first().start_datetime, reviewer_assignment.start_datetime) self.assertEquals(review_task.status, Task.Status.REVIEWING)
def test_task_form_save(self): """ Test task form save for new, human and machine tasks """ workflow_version = self.workflow_versions['test_workflow'] human_step = self.workflow_steps[workflow_version.slug]['step1'] project = ProjectFactory(workflow_version=workflow_version) # Add new task to project form = TaskForm({'project': project.id, 'status': Task.Status.AWAITING_PROCESSING, 'step': human_step.id, 'start_datetime': timezone.now()}) form.is_valid() self.assertTrue(form.is_valid()) task = form.save() self.assertFalse(task.assignments.exists()) # Add new task to project and assign to entry_level worker (0) form = TaskForm({'project': project.id, 'status': Task.Status.AWAITING_PROCESSING, 'step': human_step.id, 'start_datetime': timezone.now()}) self.assertTrue(form.is_valid()) form.cleaned_data['currently_assigned_to'] = self.workers[0].id task = form.save() self.assertTrue(is_worker_assigned_to_task(self.workers[0], task)) self.assertEquals(assignment_history(task).count(), 1) self.assertTrue(task.assignments.exists()) self.assertEquals(task.status, Task.Status.PROCESSING) # Render task with preexisting entry_level assignment (0) and reassign # to another entry_level worker (4) form = TaskForm(model_to_dict(task), instance=task) self.assertEquals(form.fields['currently_assigned_to'].initial, self.workers[0].id) form.is_valid() self.assertTrue(form.is_valid()) form.cleaned_data['currently_assigned_to'] = self.workers[4].id task = form.save() self.assertTrue(is_worker_assigned_to_task(self.workers[4], task)) self.assertEquals(assignment_history(task).count(), 1) self.assertEquals(task.status, Task.Status.PROCESSING) # Submit task with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=True): task = submit_task(task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[4], 0) # Assign to reviewer (1) and reassign to another reviewer (3) task = assign_task(self.workers[1].id, task.id) self.assertTrue(task.status, Task.Status.REVIEWING) self.assertTrue(is_worker_assigned_to_task(self.workers[1], task)) task = Task.objects.get(id=task.id) form = TaskForm(model_to_dict(task), instance=task) self.assertEquals(form.fields['currently_assigned_to'].initial, self.workers[1].id) self.assertTrue(form.is_valid()) form.cleaned_data['currently_assigned_to'] = self.workers[3].id task = form.save() self.assertTrue(is_worker_assigned_to_task(self.workers[3], task)) self.assertEquals(assignment_history(task).count(), 2) self.assertEquals(task.status, Task.Status.REVIEWING) # Attempt to reassign to non-certified worker (2) form = TaskForm(model_to_dict(task), instance=task) self.assertTrue(form.is_valid()) form.cleaned_data['currently_assigned_to'] = self.workers[2].id with self.assertRaises(WorkerCertificationError): form.save()
def test_task_form_save(self): """ Test task form save for new, human and machine tasks """ # Workflow steps are hard-coded on `choices` for `Project` models # regardless of `settings.py`. Once we move workflows back into the # database, we should use the test workflows rather than the production # ones in `settings.py.` Until then, the hack below suffices. workflows = get_workflows() test_workflow_slug = 'website_design' workflow = workflows[test_workflow_slug] human_steps = {step_slug: step for step_slug, step in workflow.steps.items() if step.worker_type == Step.WorkerType.HUMAN} step_slug, step = human_steps.popitem() project = ProjectFactory(workflow_slug=test_workflow_slug) for certification_slug in step.required_certifications: certification = CertificationFactory(slug=certification_slug) for uname in (0, 1, 3, 6): WorkerCertificationFactory( certification=certification, worker=self.workers[uname], role=WorkerCertification.Role.ENTRY_LEVEL) for uname in (3, 6): WorkerCertificationFactory( certification=certification, worker=self.workers[uname], role=WorkerCertification.Role.REVIEWER) # Add new task to project form = TaskForm({'project': project.id, 'status': Task.Status.AWAITING_PROCESSING, 'step_slug': step_slug}) form.is_valid() self.assertTrue(form.is_valid()) task = form.save() self.assertFalse(task.assignments.exists()) # Add new task to project and assign to entry_level worker (0) form = TaskForm({'project': project.id, 'status': Task.Status.AWAITING_PROCESSING, 'step_slug': step_slug}) self.assertTrue(form.is_valid()) form.cleaned_data['currently_assigned_to'] = self.workers[0].id task = form.save() self.assertTrue(is_worker_assigned_to_task(self.workers[0], task)) self.assertEquals(assignment_history(task).count(), 1) self.assertTrue(task.assignments.exists()) self.assertEquals(task.status, Task.Status.PROCESSING) # Render task with preexisting entry_level assignment (0) and reassign # to another entry_level worker (1) form = TaskForm(model_to_dict(task), instance=task) self.assertEquals(form.fields['currently_assigned_to'].initial, self.workers[0].id) form.is_valid() self.assertTrue(form.is_valid()) form.cleaned_data['currently_assigned_to'] = self.workers[1].id task = form.save() self.assertTrue(is_worker_assigned_to_task(self.workers[1], task)) self.assertEquals(assignment_history(task).count(), 1) self.assertEquals(task.status, Task.Status.PROCESSING) # Submit task with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=True): task = submit_task(task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[1], 0) # Assign to reviewer (3) and reassign to another reviewer (6) task = assign_task(self.workers[3].id, task.id) self.assertTrue(task.status, Task.Status.REVIEWING) self.assertTrue(is_worker_assigned_to_task(self.workers[3], task)) task = Task.objects.get(id=task.id) form = TaskForm(model_to_dict(task), instance=task) self.assertEquals(form.fields['currently_assigned_to'].initial, self.workers[3].id) self.assertTrue(form.is_valid()) form.cleaned_data['currently_assigned_to'] = self.workers[6].id task = form.save() self.assertTrue(is_worker_assigned_to_task(self.workers[6], task)) self.assertEquals(assignment_history(task).count(), 2) self.assertEquals(task.status, Task.Status.REVIEWING) # Attempt to reassign to non-certified worker (2) form = TaskForm(model_to_dict(task), instance=task) self.assertTrue(form.is_valid()) form.cleaned_data['currently_assigned_to'] = self.workers[2].id with self.assertRaises(WorkerCertificationError): form.save()
def test_assign_task(self): # Assign entry-level task to entry-level worker entry_tasks = Task.objects.filter( status=Task.Status.AWAITING_PROCESSING) self.assertEquals(entry_tasks.count(), 1) entry_task = entry_tasks.first() entry_task = assign_task(self.workers[0].id, entry_task.id) self.assertTrue(is_worker_assigned_to_task(self.workers[0], entry_task)) self.assertEquals(entry_task.status, Task.Status.PROCESSING) self.assertEquals(entry_task.assignments.count(), 1) # Attempt to reassign task to same worker with self.assertRaises(TaskAssignmentError): entry_task = assign_task(self.workers[0].id, entry_task.id) self.assertTrue(is_worker_assigned_to_task(self.workers[0], entry_task)) self.assertEquals(entry_task.status, Task.Status.PROCESSING) self.assertEquals(entry_task.assignments.count(), 1) # Reassign entry-level task to another entry-level worker entry_task = assign_task(self.workers[1].id, entry_task.id) self.assertFalse(is_worker_assigned_to_task(self.workers[0], entry_task)) self.assertTrue(is_worker_assigned_to_task(self.workers[1], entry_task)) self.assertEquals(entry_task.assignments.count(), 1) self.assertEquals(entry_task.status, Task.Status.PROCESSING) # Assign review task to review worker review_tasks = Task.objects.filter(status=Task.Status.PENDING_REVIEW) self.assertEquals(review_tasks.count(), 1) review_task = review_tasks.first() self.assertEquals(review_task.assignments.count(), 1) review_task = assign_task(self.workers[1].id, review_task.id) self.assertEquals(review_task.assignments.count(), 2) self.assertEqual(current_assignment(review_task).worker, self.workers[1]) self.assertEquals(review_task.status, Task.Status.REVIEWING) # Attempt to reassign review task to entry-level worker with self.assertRaises(WorkerCertificationError): review_task = assign_task(self.workers[0].id, review_task.id) self.assertEquals(review_task.assignments.count(), 2) self.assertEqual(current_assignment(review_task).worker, self.workers[1]) self.assertEquals(review_task.status, Task.Status.REVIEWING) # Reassign review task to another reviewer review_task = assign_task(self.workers[3].id, review_task.id) self.assertEquals(review_task.assignments.count(), 2) self.assertEqual(current_assignment(review_task).worker, self.workers[3]) self.assertEquals(review_task.status, Task.Status.REVIEWING) # Reassign rejected entry-level task to another entry-level worker reject_entry_tasks = Task.objects.filter( status=Task.Status.POST_REVIEW_PROCESSING, project=self.projects['reject_entry_proj']) self.assertEquals(reject_entry_tasks.count(), 1) reject_entry_task = reject_entry_tasks.first() reject_entry_task = assign_task(self.workers[5].id, reject_entry_task.id) self.assertFalse(is_worker_assigned_to_task(self.workers[4], reject_entry_task)) self.assertTrue(is_worker_assigned_to_task(self.workers[5], reject_entry_task)) self.assertEquals(reject_entry_task.status, Task.Status.POST_REVIEW_PROCESSING) self.assertEquals(reject_entry_task.assignments.count(), 2) # In-progress data preserved after successful reassign self.assertEquals((current_assignment(reject_entry_task) .in_progress_task_data), {'test_key': 'test_value'}) # Attempt to reassign rejected review task to entry-level worker reject_tasks = Task.objects.filter( status=Task.Status.POST_REVIEW_PROCESSING, project=self.projects['reject_rev_proj']) self.assertEquals(reject_tasks.count(), 1) reject_review_task = reject_tasks.first() with self.assertRaises(WorkerCertificationError): reject_review_task = assign_task(self.workers[4].id, reject_review_task.id) self.assertFalse(is_worker_assigned_to_task(self.workers[4], reject_review_task)) self.assertTrue(is_worker_assigned_to_task(self.workers[6], reject_review_task)) self.assertEquals(reject_review_task.status, Task.Status.POST_REVIEW_PROCESSING) self.assertEquals(reject_review_task.assignments.count(), 3) # Reassign reviewer post-review task to another reviewer reject_review_task = assign_task(self.workers[8].id, reject_review_task.id) self.assertFalse(is_worker_assigned_to_task(self.workers[6], reject_review_task)) self.assertTrue(is_worker_assigned_to_task(self.workers[8], reject_review_task)) self.assertEquals(reject_review_task.status, Task.Status.POST_REVIEW_PROCESSING) self.assertEquals(reject_review_task.assignments.count(), 3) # Attempt to reassign aborted task aborted_tasks = Task.objects.filter(status=Task.Status.ABORTED) self.assertEquals(aborted_tasks.count(), 1) aborted_task = aborted_tasks.first() with self.assertRaises(TaskStatusError): aborted_task = assign_task(self.workers[5].id, aborted_task.id) self.assertEquals(aborted_task.assignments.count(), 1) self.assertEqual(current_assignment(aborted_task).worker, self.workers[4])
def test_preassign_workers(self): project = self.projects['assignment_policy'] # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) self.assertEquals(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 0 not certified for related tasks, so should not have been # auto-assigned self.assertEquals(related_task.assignments.count(), 0) self.assertEquals(related_task.status, Task.Status.AWAITING_PROCESSING) # Reset project project.tasks.all().delete() # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; verify we use the reviewer assignment policy mock_preassign_workers = MagicMock(return_value=initial_task) patch_path = 'orchestra.utils.task_lifecycle._preassign_workers' with patch(patch_path, new=mock_preassign_workers): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) mock_preassign_workers.assert_called_once_with( initial_task, AssignmentPolicyType.REVIEWER) # Reset project project.tasks.all().delete() # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 4 initial_task = assign_task(self.workers[4].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[4]) self.assertEquals(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 4 is certified for related task and should have been assigned self.assertEquals(related_task.assignments.count(), 1) self.assertEquals(related_task.status, Task.Status.PROCESSING) self.assertTrue( related_task.is_worker_assigned(self.workers[4]))
def test_notify_status_change(self): project = self.projects['empty_project'] internal_name = settings.SLACK_INTERNAL_NOTIFICATION_CHANNEL.strip('#') internal_groups = [ group for group in self.slack.groups.list().body['groups'] if group['name'] == internal_name] internal_group_id = internal_groups[0]['id'] internal_slack_messages = self.slack.get_messages(internal_group_id) experts_slack_messages = self.slack.get_messages( project.slack_group_id) def _validate_slack_messages(message_stub): """ Check that correct slack message was sent if API key present. """ self.assertIn(message_stub, internal_slack_messages.pop()) self.assertIn(message_stub, experts_slack_messages.pop()) task = TaskFactory(project=project, step_slug=self.test_step_slug, status=Task.Status.AWAITING_PROCESSING) # Entry-level worker picks up task self.assertEquals(task.status, Task.Status.AWAITING_PROCESSING) task = assign_task(self.workers[0].id, task.id) self.assertTrue(is_worker_assigned_to_task(self.workers[0], task)) # Notification should be sent to entry-level worker self.assertEquals(len(self.mail.inbox), 1) notification = self.mail.inbox.pop() self.assertEquals(notification['recipient'], self.workers[0].user.email) self.assertEquals(notification['subject'], "You've been assigned to a new task!") _validate_slack_messages('Task has been picked up by a worker.') self.assertEquals(len(internal_slack_messages), 0) self.assertEquals(len(experts_slack_messages), 0) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=True): # Entry-level worker submits task task = submit_task(task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[0], 0) self.assertEquals(task.status, Task.Status.PENDING_REVIEW) # Notification should be sent to entry-level worker self.assertEquals(len(self.mail.inbox), 1) notification = self.mail.inbox.pop() self.assertEquals(notification['recipient'], self.workers[0].user.email) self.assertEquals(notification['subject'], 'Your task is under review!') _validate_slack_messages('Task is awaiting review.') self.assertEquals(len(internal_slack_messages), 0) self.assertEquals(len(experts_slack_messages), 0) # Reviewer picks up task task = assign_task(self.workers[1].id, task.id) self.assertEquals(task.status, Task.Status.REVIEWING) # No notification should be sent self.assertEquals(len(self.mail.inbox), 0) _validate_slack_messages('Task is under review.') self.assertEquals(len(internal_slack_messages), 0) self.assertEquals(len(experts_slack_messages), 0) # Reviewer rejects task task = submit_task(task.id, {}, TaskAssignment.SnapshotType.REJECT, self.workers[1], 0) self.assertEquals(task.status, Task.Status.POST_REVIEW_PROCESSING) # Notification should be sent to original worker self.assertEquals(len(self.mail.inbox), 1) notification = self.mail.inbox.pop() self.assertEquals(notification['recipient'], self.workers[0].user.email) self.assertEquals(notification['subject'], 'Your task has been returned') _validate_slack_messages('Task was returned by reviewer.') self.assertEquals(len(internal_slack_messages), 0) self.assertEquals(len(experts_slack_messages), 0) # Entry-level worker resubmits task task = submit_task(task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[0], 0) self.assertEquals(task.status, Task.Status.REVIEWING) # Notification should be sent to reviewer self.assertEquals(len(self.mail.inbox), 1) notification = self.mail.inbox.pop() self.assertEquals(notification['recipient'], self.workers[1].user.email) self.assertEquals(notification['subject'], 'A task is ready for re-review!') _validate_slack_messages('Task is under review.') self.assertEquals(len(internal_slack_messages), 0) self.assertEquals(len(experts_slack_messages), 0) # First reviewer accepts task with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=True): task = submit_task(task.id, {}, TaskAssignment.SnapshotType.ACCEPT, self.workers[1], 0) self.assertEquals(task.status, Task.Status.PENDING_REVIEW) # Notification should be sent to first reviewer self.assertEquals(len(self.mail.inbox), 1) notification = self.mail.inbox.pop() self.assertEquals(notification['recipient'], self.workers[1].user.email) self.assertEquals(notification['subject'], 'Your task is under review!') _validate_slack_messages('Task is awaiting review.') self.assertEquals(len(internal_slack_messages), 0) self.assertEquals(len(experts_slack_messages), 0) # Second reviewer picks up task task = assign_task(self.workers[3].id, task.id) self.assertEquals(task.status, Task.Status.REVIEWING) # No notification should be sent self.assertEquals(len(self.mail.inbox), 0) _validate_slack_messages('Task is under review.') self.assertEquals(len(internal_slack_messages), 0) self.assertEquals(len(experts_slack_messages), 0) # Second reviewer rejects task task = submit_task(task.id, {}, TaskAssignment.SnapshotType.REJECT, self.workers[3], 0) self.assertEquals(task.status, Task.Status.POST_REVIEW_PROCESSING) # Notification should be sent to first reviewer self.assertEquals(len(self.mail.inbox), 1) notification = self.mail.inbox.pop() self.assertEquals(notification['recipient'], self.workers[1].user.email) self.assertEquals(notification['subject'], 'Your task has been returned') _validate_slack_messages('Task was returned by reviewer.') self.assertEquals(len(internal_slack_messages), 0) self.assertEquals(len(experts_slack_messages), 0) # First reviewer resubmits task task = submit_task(task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[1], 0) self.assertEquals(task.status, Task.Status.REVIEWING) # Notification should be sent to second reviewer self.assertEquals(len(self.mail.inbox), 1) notification = self.mail.inbox.pop() self.assertEquals(notification['recipient'], self.workers[3].user.email) self.assertEquals(notification['subject'], 'A task is ready for re-review!') _validate_slack_messages('Task is under review.') self.assertEquals(len(internal_slack_messages), 0) self.assertEquals(len(experts_slack_messages), 0) # Second reviewer accepts task; task is complete with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): task = submit_task(task.id, {}, TaskAssignment.SnapshotType.ACCEPT, self.workers[3], 0) self.assertEquals(task.status, Task.Status.COMPLETE) # Notification should be sent to all workers on task self.assertEquals(len(self.mail.inbox), 3) recipients = {mail['recipient'] for mail in self.mail.inbox} subjects = {mail['subject'] for mail in self.mail.inbox} self.assertEquals(recipients, {self.workers[uid].user.email for uid in (0, 1, 3)}) self.assertEquals(subjects, {'Task complete!'}) self.mail.clear() _validate_slack_messages('Task has been completed.') self.assertEquals(len(internal_slack_messages), 0) self.assertEquals(len(experts_slack_messages), 0) # End project end_project(task.project.id) task = Task.objects.get(id=task.id) self.assertEquals(task.status, Task.Status.ABORTED) # Notification should be sent to all workers on task self.assertEquals(len(self.mail.inbox), 3) recipients = {mail['recipient'] for mail in self.mail.inbox} subjects = {mail['subject'] for mail in self.mail.inbox} self.assertEquals(recipients, {self.workers[uid].user.email for uid in (0, 1, 3)}) self.assertEquals(subjects, {'A task you were working on has been ended'}) self.mail.clear() for task in project.tasks.all(): _validate_slack_messages('Task has been aborted.') self.assertEquals(len(internal_slack_messages), 0) self.assertEquals(len(experts_slack_messages), 0)
def test_assign_task(self): entry_task = TaskFactory( project=self.projects['base_test_project'], status=Task.Status.AWAITING_PROCESSING, step=self.test_step) # Assign entry-level task to entry-level worker entry_task = assign_task(self.workers[0].id, entry_task.id) self.assertTrue(is_worker_assigned_to_task(self.workers[0], entry_task)) self.assertEquals(entry_task.status, Task.Status.PROCESSING) self.assertEquals(entry_task.assignments.count(), 1) # Attempt to assign task which isn't awaiting a new assignment invalid = (Task.Status.PROCESSING, Task.Status.ABORTED, Task.Status.REVIEWING, Task.Status.COMPLETE, Task.Status.POST_REVIEW_PROCESSING) for status in invalid: invalid_status_task = Task.objects.create( project=self.projects['base_test_project'], status=status, step=self.test_step) with self.assertRaises(TaskAssignmentError): invalid_status_task = assign_task( self.workers[0].id, invalid_status_task.id) # Attempt to assign review task to worker already in review hierarchy review_task = Task.objects.create( project=self.projects['base_test_project'], status=Task.Status.PENDING_REVIEW, step=self.test_step) test_data = {'test_assign': True} TaskAssignmentFactory( worker=self.workers[1], task=review_task, status=TaskAssignment.Status.SUBMITTED, in_progress_task_data=test_data, snapshots=empty_snapshots()) with self.assertRaises(TaskAssignmentError): assign_task(self.workers[1].id, review_task.id) self.assertEqual( current_assignment(review_task).in_progress_task_data, test_data) # Attempt to assign review task to worker not certified for task with self.assertRaises(WorkerCertificationError): assign_task(self.workers[2].id, review_task.id) self.assertEqual( current_assignment(review_task).in_progress_task_data, test_data) # Assign review task to review worker self.assertEquals(review_task.assignments.count(), 1) review_task = assign_task(self.workers[3].id, review_task.id) self.assertEquals(review_task.assignments.count(), 2) self.assertEqual( current_assignment(review_task).worker, self.workers[3]) self.assertEqual( current_assignment(review_task).in_progress_task_data, test_data) self.assertEquals( review_task.status, Task.Status.REVIEWING)
def handle_staffing_response(worker, staffing_request_inquiry_id, is_available=False): """ Args: worker (orchestra.models.Worker): Worker instance that responsed to a staffing request inquiry staffing_request_inquiry_id (int): Id of a staffing_request_inquiry that is associated with a response is_available (boolean): Boolean that tells whether worker accepted an inquiry or not Returns: response (orchestra.models.StaffingResponse): StaffingResponse object that has been created for the worker """ # TODO(kkamalov): add proper docstring staffing_request_inquiry = get_object_or_None( StaffingRequestInquiry, communication_preference__worker=worker, id=staffing_request_inquiry_id ) if staffing_request_inquiry is None: return None response = (StaffingResponse.objects .filter(request_inquiry=staffing_request_inquiry)) if response.exists(): response = response.first() if not is_available and response.is_winner: raise StaffingResponseException( 'Cannot reject after accepting the task') response.is_available = is_available else: response = StaffingResponse.objects.create( request_inquiry=staffing_request_inquiry, is_available=is_available) if (is_available and not StaffingResponse.objects.filter( request_inquiry__request=staffing_request_inquiry.request, is_winner=True).exists()): response.is_winner = True request = staffing_request_inquiry.request task_assignment = get_object_or_None( TaskAssignment, task=request.task, assignment_counter=request.required_role_counter ) # if task assignment exists then reassign if task_assignment is not None: reassign_assignment(worker.id, task_assignment.id) # otherwise assign task else: assign_task(worker.id, request.task.id) response.save() check_responses_complete(staffing_request_inquiry.request) return response
def test_notify_status_change(self): project = self.projects['empty_project'] internal_name = settings.SLACK_INTERNAL_NOTIFICATION_CHANNEL.strip('#') internal_groups = [ group for group in self.slack.groups.list().body['groups'] if group['name'] == internal_name ] internal_group_id = internal_groups[0]['id'] internal_slack_messages = self.slack.get_messages(internal_group_id) experts_slack_messages = self.slack.get_messages( project.slack_group_id) def _validate_slack_messages(message_stub): """ Check that correct slack message was sent if API key present. """ self.assertIn(message_stub, internal_slack_messages.pop()) self.assertIn(message_stub, experts_slack_messages.pop()) task = TaskFactory(project=project, step=self.test_step, status=Task.Status.AWAITING_PROCESSING) # Entry-level worker picks up task self.assertEqual(task.status, Task.Status.AWAITING_PROCESSING) task = assign_task(self.workers[0].id, task.id) self.assertTrue(task.is_worker_assigned(self.workers[0])) # Notification should be sent to entry-level worker self.assertEqual(len(self.mail.inbox), 1) notification = self.mail.inbox.pop() self.assertEqual(notification['recipient'], self.workers[0].user.email) self.assertEqual(notification['subject'], "You've been assigned to a new task!") _validate_slack_messages('Task has been picked up by a worker.') self.assertEqual(len(internal_slack_messages), 0) self.assertEqual(len(experts_slack_messages), 0) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=True): # Entry-level worker submits task task = submit_task(task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) self.assertEqual(task.status, Task.Status.PENDING_REVIEW) # Notification should be sent to entry-level worker self.assertEqual(len(self.mail.inbox), 1) notification = self.mail.inbox.pop() self.assertEqual(notification['recipient'], self.workers[0].user.email) self.assertEqual(notification['subject'], 'Your task is under review!') _validate_slack_messages('Task is awaiting review.') self.assertEqual(len(internal_slack_messages), 0) self.assertEqual(len(experts_slack_messages), 0) # Reviewer picks up task task = assign_task(self.workers[1].id, task.id) self.assertEqual(task.status, Task.Status.REVIEWING) # No notification should be sent self.assertEqual(len(self.mail.inbox), 0) _validate_slack_messages('Task is under review.') self.assertEqual(len(internal_slack_messages), 0) self.assertEqual(len(experts_slack_messages), 0) # Reviewer rejects task task = submit_task(task.id, {}, Iteration.Status.PROVIDED_REVIEW, self.workers[1]) self.assertEqual(task.status, Task.Status.POST_REVIEW_PROCESSING) # Notification should be sent to original worker self.assertEqual(len(self.mail.inbox), 1) notification = self.mail.inbox.pop() self.assertEqual(notification['recipient'], self.workers[0].user.email) self.assertEqual(notification['subject'], 'Your task has been returned') _validate_slack_messages('Task was returned by reviewer.') self.assertEqual(len(internal_slack_messages), 0) self.assertEqual(len(experts_slack_messages), 0) # Entry-level worker resubmits task task = submit_task(task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) self.assertEqual(task.status, Task.Status.REVIEWING) # Notification should be sent to reviewer self.assertEqual(len(self.mail.inbox), 1) notification = self.mail.inbox.pop() self.assertEqual(notification['recipient'], self.workers[1].user.email) self.assertEqual(notification['subject'], 'A task is ready for re-review!') _validate_slack_messages('Task is under review.') self.assertEqual(len(internal_slack_messages), 0) self.assertEqual(len(experts_slack_messages), 0) # First reviewer accepts task with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=True): task = submit_task(task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[1]) self.assertEqual(task.status, Task.Status.PENDING_REVIEW) # Notification should be sent to first reviewer self.assertEqual(len(self.mail.inbox), 1) notification = self.mail.inbox.pop() self.assertEqual(notification['recipient'], self.workers[1].user.email) self.assertEqual(notification['subject'], 'Your task is under review!') _validate_slack_messages('Task is awaiting review.') self.assertEqual(len(internal_slack_messages), 0) self.assertEqual(len(experts_slack_messages), 0) # Second reviewer picks up task task = assign_task(self.workers[3].id, task.id) self.assertEqual(task.status, Task.Status.REVIEWING) # No notification should be sent self.assertEqual(len(self.mail.inbox), 0) _validate_slack_messages('Task is under review.') self.assertEqual(len(internal_slack_messages), 0) self.assertEqual(len(experts_slack_messages), 0) # Second reviewer rejects task task = submit_task(task.id, {}, Iteration.Status.PROVIDED_REVIEW, self.workers[3]) self.assertEqual(task.status, Task.Status.POST_REVIEW_PROCESSING) # Notification should be sent to first reviewer self.assertEqual(len(self.mail.inbox), 1) notification = self.mail.inbox.pop() self.assertEqual(notification['recipient'], self.workers[1].user.email) self.assertEqual(notification['subject'], 'Your task has been returned') _validate_slack_messages('Task was returned by reviewer.') self.assertEqual(len(internal_slack_messages), 0) self.assertEqual(len(experts_slack_messages), 0) # First reviewer resubmits task task = submit_task(task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[1]) self.assertEqual(task.status, Task.Status.REVIEWING) # Notification should be sent to second reviewer self.assertEqual(len(self.mail.inbox), 1) notification = self.mail.inbox.pop() self.assertEqual(notification['recipient'], self.workers[3].user.email) self.assertEqual(notification['subject'], 'A task is ready for re-review!') _validate_slack_messages('Task is under review.') self.assertEqual(len(internal_slack_messages), 0) self.assertEqual(len(experts_slack_messages), 0) # Second reviewer accepts task; task is complete with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): task = submit_task(task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[3]) self.assertEqual(task.status, Task.Status.COMPLETE) # Notification should be sent to all workers on task self.assertEqual(len(self.mail.inbox), 3) recipients = {mail['recipient'] for mail in self.mail.inbox} subjects = {mail['subject'] for mail in self.mail.inbox} self.assertEqual(recipients, {self.workers[uid].user.email for uid in (0, 1, 3)}) self.assertEqual(subjects, {'Task complete!'}) self.mail.clear() _validate_slack_messages('Task has been completed.') self.assertEqual(len(internal_slack_messages), 0) self.assertEqual(len(experts_slack_messages), 0) # End project end_project(task.project.id) task = Task.objects.get(id=task.id) self.assertEqual(task.status, Task.Status.ABORTED) # Notification should be sent to all workers on task self.assertEqual(len(self.mail.inbox), 3) recipients = {mail['recipient'] for mail in self.mail.inbox} subjects = {mail['subject'] for mail in self.mail.inbox} self.assertEqual(recipients, {self.workers[uid].user.email for uid in (0, 1, 3)}) self.assertEqual(subjects, {'A task you were working on has been ended'}) self.mail.clear() for task in project.tasks.all(): _validate_slack_messages('Task has been aborted.') self.assertEqual(len(internal_slack_messages), 0) self.assertEqual(len(experts_slack_messages), 0)
def test_assign_task(self): entry_task = TaskFactory( project=self.projects['base_test_project'], status=Task.Status.AWAITING_PROCESSING, step=self.test_step) # No iterations should be present for task self.assertEqual( Iteration.objects.filter(assignment__task=entry_task).count(), 0) # Assign entry-level task to entry-level worker entry_task = assign_task(self.workers[0].id, entry_task.id) self.assertTrue(entry_task.is_worker_assigned(self.workers[0])) self.assertEqual(entry_task.status, Task.Status.PROCESSING) self.assertEqual(entry_task.assignments.count(), 1) entry_assignment = entry_task.assignments.first() # A single iteration was created for the assignment self.assertEqual(entry_assignment.iterations.count(), 1) self.assertEqual( Iteration.objects.filter(assignment__task=entry_task).count(), 1) self.assertEqual( entry_assignment.iterations.first().start_datetime, entry_assignment.start_datetime) # Attempt to assign task which isn't awaiting a new assignment invalid = (Task.Status.PROCESSING, Task.Status.ABORTED, Task.Status.REVIEWING, Task.Status.COMPLETE, Task.Status.POST_REVIEW_PROCESSING) for status in invalid: invalid_status_task = Task.objects.create( project=self.projects['base_test_project'], status=status, step=self.test_step) with self.assertRaises(TaskAssignmentError): invalid_status_task = assign_task( self.workers[0].id, invalid_status_task.id) # Attempt to assign review task to worker already in review hierarchy review_task = Task.objects.create( project=self.projects['base_test_project'], status=Task.Status.PENDING_REVIEW, step=self.test_step) test_data = {'test_assign': True} TaskAssignmentFactory( worker=self.workers[1], task=review_task, status=TaskAssignment.Status.SUBMITTED, in_progress_task_data=test_data) with self.assertRaises(TaskAssignmentError): assign_task(self.workers[1].id, review_task.id) self.assertEqual( current_assignment(review_task).in_progress_task_data, test_data) # Attempt to assign review task to worker not certified for task with self.assertRaises(WorkerCertificationError): assign_task(self.workers[2].id, review_task.id) self.assertEqual( current_assignment(review_task).in_progress_task_data, test_data) # Assign review task to review worker self.assertEquals(review_task.assignments.count(), 1) review_task = assign_task(self.workers[3].id, review_task.id) self.assertEquals(review_task.assignments.count(), 2) reviewer_assignment = current_assignment(review_task) self.assertEqual( reviewer_assignment.worker, self.workers[3]) self.assertEqual( reviewer_assignment.in_progress_task_data, test_data) self.assertEquals( reviewer_assignment.iterations.count(), 1) self.assertEqual( reviewer_assignment.iterations.first().start_datetime, reviewer_assignment.start_datetime) self.assertEquals( review_task.status, Task.Status.REVIEWING)
def setup_complete_task(test_case): # Microseconds are truncated when manually saving models test_start = timezone.now().replace(microsecond=0) times = { 'awaiting_pickup': test_start, 'entry_pickup': test_start + timedelta(hours=1), 'entry_submit': test_start + timedelta(hours=2), 'reviewer_pickup': test_start + timedelta(hours=3), 'reviewer_reject': test_start + timedelta(hours=4), 'entry_resubmit': test_start + timedelta(hours=5), 'reviewer_accept': test_start + timedelta(hours=6), } task = TaskFactory( project=test_case.projects['empty_project'], status=Task.Status.AWAITING_PROCESSING, step=test_case.test_step, start_datetime=times['awaiting_pickup']) workers = { 'entry': test_case.workers[0], 'reviewer': test_case.workers[1] } assign_task(workers['entry'].id, task.id) task.refresh_from_db() test_case.assertEqual(task.status, Task.Status.PROCESSING) submit_task( task.id, {'test': 'entry_submit'}, Iteration.Status.REQUESTED_REVIEW, workers['entry']) task.refresh_from_db() test_case.assertEqual(task.status, Task.Status.PENDING_REVIEW) assign_task(workers['reviewer'].id, task.id) reviewer_assignment = task.assignments.get( worker=workers['reviewer']) # Modify assignment with correct datetime reviewer_assignment.start_datetime = times['reviewer_pickup'] reviewer_assignment.save() task.refresh_from_db() test_case.assertEqual(task.status, Task.Status.REVIEWING) submit_task( task.id, {'test': 'reviewer_reject'}, Iteration.Status.PROVIDED_REVIEW, workers['reviewer']) task.refresh_from_db() test_case.assertEqual(task.status, Task.Status.POST_REVIEW_PROCESSING) submit_task( task.id, {'test': 'entry_resubmit'}, Iteration.Status.REQUESTED_REVIEW, workers['entry']) task.refresh_from_db() test_case.assertEqual(task.status, Task.Status.REVIEWING) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): submit_task( task.id, {'test': 'reviewer_accept'}, Iteration.Status.REQUESTED_REVIEW, workers['reviewer']) task.refresh_from_db() test_case.assertEqual(task.status, Task.Status.COMPLETE) test_case.assertEqual(task.assignments.count(), 2) for assignment in task.assignments.all(): test_case.assertEqual( assignment.status, TaskAssignment.Status.SUBMITTED) test_case.assertEqual(assignment.iterations.count(), 2) # Modify assignments with correct datetime new_datetime_labels = ('entry_pickup', 'reviewer_pickup') for i, assignment in enumerate(assignment_history(task).all()): assignment.start_datetime = times[new_datetime_labels[i]] assignment.save() # Modify iterations with correct datetime new_datetime_labels = ( ('entry_pickup', 'entry_submit'), ('reviewer_pickup', 'reviewer_reject'), ('reviewer_reject', 'entry_resubmit'), ('entry_resubmit', 'reviewer_accept') ) new_datetimes = [ (times[start_label], times[end_label]) for start_label, end_label in new_datetime_labels] for i, iteration in enumerate(get_iteration_history(task)): iteration.start_datetime, iteration.end_datetime = new_datetimes[i] iteration.save() verify_iterations(task.id) return task
def setup_complete_task(test_case, times): task = TaskFactory( project=test_case.projects['empty_project'], status=Task.Status.AWAITING_PROCESSING, step=test_case.test_step, start_datetime=times['awaiting_pickup']) workers = { 'entry': test_case.workers[0], 'reviewer': test_case.workers[1] } assign_task(workers['entry'].id, task.id) entry_assignment = task.assignments.get(worker=workers['entry']) # Modify assignment with correct datetime entry_assignment.start_datetime = times['entry_pickup'] entry_assignment.save() task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.PROCESSING) submit_task( task.id, {'test': 'entry_submit'}, TaskAssignment.SnapshotType.SUBMIT, workers['entry'], 0) task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.PENDING_REVIEW) # Modify snapshot with correct datetime entry_assignment.refresh_from_db() (entry_assignment.snapshots ['snapshots'][0]['datetime']) = times['entry_submit'] entry_assignment.save() assign_task(workers['reviewer'].id, task.id) reviewer_assignment = task.assignments.get( worker=workers['reviewer']) # Modify assignment with correct datetime reviewer_assignment.start_datetime = times['reviewer_pickup'] reviewer_assignment.save() task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.REVIEWING) submit_task( task.id, {'test': 'reviewer_reject'}, TaskAssignment.SnapshotType.REJECT, workers['reviewer'], 0) # Modify snapshot with correct datetime reviewer_assignment.refresh_from_db() (reviewer_assignment.snapshots ['snapshots'][0]['datetime']) = times['reviewer_reject'] reviewer_assignment.save() task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.POST_REVIEW_PROCESSING) submit_task( task.id, {'test': 'entry_resubmit'}, TaskAssignment.SnapshotType.SUBMIT, workers['entry'], 0) # Modify snapshot with correct datetime entry_assignment.refresh_from_db() (entry_assignment.snapshots ['snapshots'][1]['datetime']) = times['entry_resubmit'] entry_assignment.save() task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.REVIEWING) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): submit_task( task.id, {'test': 'reviewer_accept'}, TaskAssignment.SnapshotType.ACCEPT, workers['reviewer'], 0) # Modify snapshot with correct datetime reviewer_assignment.refresh_from_db() (reviewer_assignment.snapshots ['snapshots'][1]['datetime']) = times['reviewer_accept'] reviewer_assignment.save() task.refresh_from_db() test_case.assertEquals(task.status, Task.Status.COMPLETE) test_case.assertEquals(task.assignments.count(), 2) for assignment in task.assignments.all(): test_case.assertEquals( assignment.status, TaskAssignment.Status.SUBMITTED) test_case.assertEquals(len(assignment.snapshots['snapshots']), 2) return task