def test_staff_command_errors(self): """ Test that the staffing logic errors are raised during staff command. """ bot = StaffBot() data = get_mock_slack_data( text='staff 999999999999', user_id=self.worker.slack_user_id) response = bot.dispatch(data) self.assertEqual(response['attachments'][0]['text'], bot.task_does_not_exist_error.format('999999999999')) data['text'] = 'staff' response = bot.dispatch(data) self.assertTrue(bot.default_error_text in response.get('text')) task = TaskFactory(status=Task.Status.COMPLETE) data['text'] = 'staff {}'.format(task.id) response = bot.dispatch(data) self.assertEquals(response['attachments'][0]['text'], bot.task_assignment_error .format(task.id, 'Status incompatible with new assignment'))
def get_available_requests(worker): # We want to show a worker only requests for which there is no # winner or for which they have not already replied. won_responses = StaffingResponse.objects.filter(is_winner=True) worker_provided_responses = StaffingResponse.objects.filter( request_inquiry__communication_preference__worker=worker) remaining_requests = ( StaffBotRequest.objects .filter(inquiries__communication_preference__worker=worker) .exclude(inquiries__responses__in=won_responses) .exclude(inquiries__responses__in=worker_provided_responses) .distinct()) inquiries = ( StaffingRequestInquiry.objects .filter(request__in=remaining_requests) .filter(communication_preference__worker=worker) .order_by('request__task__start_datetime')) # Because we might send multiple request inquiries to the same # worker for the same request (e.g., email and slack), we # deduplicate the inquiries so that we will return at most one # inquiry's worth of content here. request_ids = set() contexts = [] staffbot = StaffBot() for inquiry in inquiries: if inquiry.request.id in request_ids: continue request_ids.add(inquiry.request.id) metadata = staffbot.get_staffing_request_metadata(inquiry) metadata['detailed_description'] = markdown( metadata['detailed_description']) metadata['reject_url'] += '?next={}'.format( reverse('orchestra:communication:available_staffing_requests')) contexts.append(metadata) return contexts
def test_commands(self): """ Ensure that the bot can handle the following commands: /staffbot staff <task_id> /staffbot restaff <task_id> <username> This test only validates that the commands are processed, other tests verify the functionality of the command execution. """ bot = StaffBot() # Test staff command mock_slack_data = get_mock_slack_data( text='staff 5', user_id=self.worker.slack_user_id) response = bot.dispatch(mock_slack_data) self.assertFalse(bot.default_error_text in response.get('text', '')) # Test the restaff command mock_slack_data['text'] = 'restaff 5 username' response = bot.dispatch(mock_slack_data) self.assertFalse(bot.default_error_text in response.get('text', '')) # Test we fail gracefully mock_slack_data['text'] = 'invalid command' response = bot.dispatch(mock_slack_data) self.assertTrue(bot.default_error_text in response.get('text', ''))
def get_available_requests(worker): # We want to show a worker only requests for which there is no # winner or for which they have not already replied. worker_provided_responses = StaffingResponse.objects.filter( request_inquiry__communication_preference__worker=worker) remaining_requests = (StaffBotRequest.objects.filter( inquiries__communication_preference__worker=worker ).exclude(status=StaffBotRequest.Status.CLOSED.value).exclude( task__status=Task.Status.COMPLETE).exclude( task__status=Task.Status.ABORTED).exclude( inquiries__responses__in=worker_provided_responses).distinct()) inquiries = (StaffingRequestInquiry.objects.filter( request__in=remaining_requests).filter( communication_preference__worker=worker).order_by( 'request__task__start_datetime')) # Because we might send multiple request inquiries to the same # worker for the same request (e.g., email and slack), we # deduplicate the inquiries so that we will return at most one # inquiry's worth of content here. request_ids = set() contexts = [] staffbot = StaffBot() for inquiry in inquiries: if inquiry.request.id in request_ids: continue request_ids.add(inquiry.request.id) metadata = staffbot.get_staffing_request_metadata(inquiry) metadata['detailed_description'] = markdown( metadata['detailed_description'], extras=['target-blank-links']) metadata['reject_url'] += '?next={}'.format( reverse('orchestra:communication:available_staffing_requests')) contexts.append(metadata) return contexts
def test_restaff_command_errors(self): """ Test that the staffing logic errors are raised during staff command. """ bot = StaffBot() data = get_mock_slack_data( text='restaff 999999999999 unknown', user_id=self.worker.slack_user_id) response = bot.dispatch(data) self.assertEqual(response.get('text'), bot.worker_does_not_exist.format('unknown')) worker = WorkerFactory(user__username='******') data['text'] = 'restaff 999999999999 slackusername' response = bot.dispatch(data) self.assertEqual(response.get('text'), bot.task_does_not_exist_error.format('999999999999')) data['text'] = 'restaff' response = bot.dispatch(data) self.assertTrue(bot.default_error_text in response.get('text')) task = TaskFactory(status=Task.Status.COMPLETE) command = 'restaff {} {}'.format(task.id, worker.user.username) data['text'] = command response = bot.dispatch(data) self.assertEquals(response.get('text'), (bot.task_assignment_does_not_exist_error .format(worker.user.username, task.id)))
def remind_workers_about_available_tasks(): staffbot = StaffBot() workers = Worker.objects.all() for worker in workers: requests = get_available_requests(worker) # TODO(kkamalov): send out reminder only if last request was sent # at least ORCHESTRA_STAFFBOT_MIN_FOLLOWUP_TIME ago if len(requests): staffbot.send_worker_tasks_available_reminder(worker)
def setUp(self): super().setUp() setup_models(self) self.worker = self.workers[0] self.worker.user.is_superuser = True self.worker.user.save() self.request_client = RequestClient(username=self.worker.user.username, password='******') self.url = reverse('orchestra:bots:staffbot') self.staffbot = StaffBot()
def _test_staffing_requests(self, worker, task, command, can_slack=False, can_mail=False): StaffBotRequest.objects.all().delete() bot = StaffBot() communication_type = (CommunicationPreference.CommunicationType .NEW_TASK_AVAILABLE.value) communication_preference = CommunicationPreference.objects.get( worker=worker, communication_type=communication_type) communication_preference.methods.slack = can_slack communication_preference.methods.email = can_mail communication_preference.save() data = get_mock_slack_data( text=command, user_id=self.worker.slack_user_id) bot.dispatch(data) send_staffing_requests(worker_batch_size=20) self.assertEquals(StaffingRequestInquiry.objects.filter( communication_preference__worker_id=worker, request__task=task).count(), can_slack + can_mail)
def send_staffing_requests( worker_batch_size=settings.ORCHESTRA_STAFFBOT_WORKER_BATCH_SIZE, frequency=settings.ORCHESTRA_STAFFBOT_BATCH_FREQUENCY): staffbot = StaffBot() cutoff_datetime = timezone.now() - frequency requests = (StaffBotRequest.objects.filter( status=StaffBotRequest.Status.SENDING_INQUIRIES.value).filter( Q(last_inquiry_sent__isnull=True) | Q(last_inquiry_sent__lte=cutoff_datetime))) for request in requests: send_request_inquiries(staffbot, request, worker_batch_size)
def _test_staffing_requests(self, worker, task, command, can_slack=False, can_mail=False): StaffBotRequest.objects.all().delete() bot = StaffBot() communication_type = (CommunicationPreference.CommunicationType .NEW_TASK_AVAILABLE.value) communication_preference = CommunicationPreference.objects.get( worker=worker, communication_type=communication_type) communication_preference.methods.slack = can_slack communication_preference.methods.email = can_mail communication_preference.save() data = get_mock_slack_data( text=command, user_id=self.worker.slack_user_id) bot.dispatch(data) send_staffing_requests(worker_batch_size=20, frequency=timedelta(minutes=0)) self.assertEquals(StaffingRequestInquiry.objects.filter( communication_preference__worker_id=worker, request__task=task).count(), can_slack + can_mail)
def test_staff_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 = TaskFactory() init_num_request = StaffBotRequest.objects.filter(task=task).count() self.assertEqual(init_num_request, 0) bot.staff(task.id) 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 staff on the same task should close the previous request # and create a new one. bot.staff(task.id) 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_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 address_staffing_requests( worker_batch_size=settings.ORCHESTRA_STAFFBOT_WORKER_BATCH_SIZE, frequency=settings.ORCHESTRA_STAFFBOT_BATCH_FREQUENCY): staffbot = StaffBot() cutoff_datetime = timezone.now() - frequency requests = (StaffBotRequest.objects.filter(status__in=[ StaffBotRequest.Status.SENDING_INQUIRIES.value, StaffBotRequest.Status.DONE_SENDING_INQUIRIES.value ]).filter( Q(last_inquiry_sent__isnull=True) | Q(last_inquiry_sent__lte=cutoff_datetime)).order_by( '-task__project__priority', 'created_at')) for request in requests: staff_or_send_request_inquiries(staffbot, request, worker_batch_size)
def test_restaff_command_errors(self): """ Test that the staffing logic errors are raised during staff command. """ bot = StaffBot() command = 'restaff 999999999999 unknown' data = get_mock_slack_data( text=command, user_id=self.worker.slack_user_id) response = bot.dispatch(data) self.assertEqual(response.get('text'), command) self.assertEqual(response['attachments'][0]['text'], bot.worker_does_not_exist.format('unknown')) worker = WorkerFactory(user__username='******') data['text'] = 'restaff 999999999999 username' response = bot.dispatch(data) self.assertEqual(response['attachments'][0]['text'], bot.task_does_not_exist_error.format('999999999999')) # making sure it works with slack username as well. worker.slack_username = '******' worker.save() data['text'] = 'restaff 999999999999 slackusername' response = bot.dispatch(data) self.assertEqual(response['attachments'][0]['text'], bot.task_does_not_exist_error.format('999999999999')) data['text'] = 'restaff' response = bot.dispatch(data) self.assertTrue(bot.default_error_text in response.get('text')) task = TaskFactory(status=Task.Status.COMPLETE) command = 'restaff {} {}'.format(task.id, worker.user.username) data['text'] = command response = bot.dispatch(data) self.assertEquals(response['attachments'][0]['text'], (bot.task_assignment_does_not_exist_error .format(worker.user.username, task.id)))
def staff_task(request): data = load_encoded_json(request.body) errors = {} try: task = Task.objects.get(id=data.get('task_id')) request_cause = StaffBotRequest.RequestCause.USER.value bot = StaffBot() assignment = current_assignment(task) is_restaff = assignment is not None if is_restaff: username = assignment.worker.user.username bot.restaff(task.id, username, request_cause=request_cause) else: bot.staff(task.id, request_cause=request_cause) except Exception as e: raise BadRequest(e) success = len(errors) == 0 return {'success': success, 'is_restaff': is_restaff}
def staffbot_autoassign(task, **kwargs): request_cause = StaffBotRequest.RequestCause.AUTOSTAFF.value bot = StaffBot() bot.staff(task.id, request_cause=request_cause) return task
def test_assert_validate_error(self): bot = StaffBot() with self.assertRaises(SlackUserUnauthorized): mock_slack_data = get_mock_slack_data(text='staff 5') bot.dispatch(mock_slack_data)
def test_get_staffing_request_messsage(self, mock_mail): def _task_factory(status, path): description_no_kwargs = {'path': path} return TaskFactory( status=status, step=StepFactory( slug='stepslug', description='the step', detailed_description_function=description_no_kwargs), project__workflow_version__workflow__description=( 'the workflow'), project__short_description='the coolest project') # Test slack without review and with a detailed_description_function task = _task_factory( Task.Status.AWAITING_PROCESSING, 'orchestra.tests.helpers.fixtures.get_detailed_description') staffing_request_inquiry = StaffingRequestInquiryFactory( communication_preference__worker__user__first_name='test-name', request__task=task) message = StaffBot()._get_staffing_request_message( staffing_request_inquiry, 'communication/new_task_available_slack.txt') self.assertEqual(message, '''Hello test-name! A new task is available for you to work on, if you'd like! Here are the details: Project: the workflow Project description: the coolest project Task: the step Details: No text given stepslug <http://127.0.0.1:8000/orchestra/communication/accept_staffing_request_inquiry/{}/|Accept the Task> <http://127.0.0.1:8000/orchestra/communication/reject_staffing_request_inquiry/{}/|Ignore the Task> <http://127.0.0.1:8000/orchestra/communication/available_staffing_requests/|View All Available Tasks> '''.format(staffing_request_inquiry.id, staffing_request_inquiry.id)) # noqa # Test email with review and no detailed_description_function task = _task_factory( Task.Status.PENDING_REVIEW, 'orchestra.bots.tests.test_staffbot._noop_details') staffing_request_inquiry = StaffingRequestInquiryFactory( communication_preference__worker__user__first_name='test-name2', request=StaffBotRequestFactory(task=task, required_role_counter=1)) message = StaffBot()._get_staffing_request_message( staffing_request_inquiry, 'communication/new_task_available_email.txt') self.assertEqual(message, '''Hello test-name2! A new task is available for you to work on, if you'd like! Here are the details: Project: the workflow Project description: the coolest project Task: the step [Review] <a href="http://127.0.0.1:8000/orchestra/communication/accept_staffing_request_inquiry/{}/">Accept the Task</a> <a href="http://127.0.0.1:8000/orchestra/communication/reject_staffing_request_inquiry/{}/">Ignore the Task</a> <a href="http://127.0.0.1:8000/orchestra/communication/available_staffing_requests/">View All Available Tasks</a> '''.format(staffing_request_inquiry.id, staffing_request_inquiry.id)) # noqa # Test that we markdown things StaffBot()._send_staffing_request_by_mail('*****@*****.**', message) mock_mail.assert_called_once_with( 'A new task is available for you', message, settings.ORCHESTRA_NOTIFICATIONS_FROM_EMAIL, ['*****@*****.**'], html_message=html_from_plaintext(message))