Пример #1
0
def check_worker_allowed_new_assignment(worker):
    """
    Check if the worker can be assigned to a new task.

    Args:
        worker (orchestra.models.Worker):
            The worker submitting the task.
        task_status (orchestra.models.Task.Status):
            The status of the desired new task assignment.

    Returns:
        allowed_new_assignment (bool):
            True if the worker can be assigned to a new task.

    Raises:
        orchestra.core.errors.TaskAssignmentError:
            Worker has pending reviewer feedback or is assigned to the
            maximum number of tasks.
        orchestra.core.errors.TaskStatusError:
            New task assignment is not permitted for the given status.
    """
    if (worker_assigned_to_rejected_task(worker)
            and settings.ORCHESTRA_ENFORCE_NO_NEW_TASKS_DURING_REVIEW):
        raise TaskAssignmentError('Worker has pending reviewer feedback that '
                                  'must be addressed.')
    elif worker_assigned_to_max_tasks(worker):
        raise TaskAssignmentError('Worker assigned to max number of tasks.')
Пример #2
0
def save_task(task_id, task_data, worker):
    """
    Save the latest data to the database for a task assignment,
    overwriting previously saved data.

    Args:
        task_id (int):
            The ID of the task to save.
        task_data (str):
            A JSON blob of task data to commit to the database.
        worker (orchestra.models.Worker):
            The worker saving the task.

    Returns:
        None

    Raises:
        orchestra.core.errors.TaskAssignmentError:
            The provided worker is not assigned to the given task or the
            assignment is in a non-processing state.
    """
    task = Task.objects.get(id=task_id)
    if not task.is_worker_assigned(worker):
        raise TaskAssignmentError('Worker is not associated with task')

    # Use select_for_update to prevent concurrency issues with submit_task.
    # See https://github.com/b12io/orchestra/issues/2.
    assignment = (TaskAssignment.objects.select_for_update().get(
        task=task, worker=worker))

    if assignment.status != TaskAssignment.Status.PROCESSING:
        raise TaskAssignmentError('Worker is not allowed to save')

    assignment.in_progress_task_data = task_data
    assignment.save()
Пример #3
0
def _check_worker_allowed_new_assignment(worker, task_status):
    """
    Check if the worker can be assigned to a new task.

    Args:
        worker (orchestra.models.Worker):
            The worker submitting the task.
        task_status (orchestra.models.Task.Status):
            The status of the desired new task assignment.

    Returns:
        allowed_new_assignment (bool):
            True if the worker can be assigned to a new task.

    Raises:
        orchestra.core.errors.TaskAssignmentError:
            Worker has pending reviewer feedback or is assigned to the
            maximum number of tasks.
        orchestra.core.errors.TaskStatusError:
            New task assignment is not permitted for the given status.
    """
    valid_statuses = [
        Task.Status.AWAITING_PROCESSING, Task.Status.PENDING_REVIEW
    ]
    if task_status not in valid_statuses:
        raise TaskStatusError('Invalid status for new task assignment.')
    elif worker_assigned_to_rejected_task(worker):
        raise TaskAssignmentError('Worker has pending reviewer feedback that '
                                  'must be addressed.')
    elif worker_assigned_to_max_tasks(worker):
        raise TaskAssignmentError('Worker assigned to max number of tasks.')
Пример #4
0
def get_task_overview_for_worker(task_id, worker):
    """
    Get information about `task` and its assignment for `worker`.

    Args:
        task_id (int):
            The ID of the desired task object.
        worker (orchestra.models.Worker):
            The specified worker object.

    Returns:
        task_assignment_details (dict):
            Information about `task` and its assignment for `worker`.
    """
    task = Task.objects.get(id=task_id)
    task_details = get_task_details(task_id)
    task_details['is_project_admin'] = worker.is_project_admin()

    if task.is_worker_assigned(worker):
        task_assignment = TaskAssignment.objects.get(worker=worker, task=task)
    elif worker.is_project_admin():
        task_assignment = assignment_history(task).last()
        task_details['is_read_only'] = True
    else:
        raise TaskAssignmentError('Worker is not associated with task')

    task_assignment_details = get_task_assignment_details(task_assignment)
    task_assignment_details.update(task_details)
    return task_assignment_details
Пример #5
0
def assign_task(worker_id, task_id):
    """
    Return a given task after assigning or reassigning it to the specified
    worker.

    Args:
        worker_id (int):
            The ID of the worker to be assigned.
        task_id (int):
            The ID of the task to be assigned.

    Returns:
        task (orchestra.models.Task):
            The newly assigned task.

    Raises:
        orchestra.core.errors.TaskAssignmentError:
            The specified worker is already assigned to the given task
            or the task status is not compatible with new assignment.
        orchestra.core.errors.WorkerCertificationError:
            The specified worker is not certified for the given task.
    """
    worker = Worker.objects.get(id=worker_id)
    task = Task.objects.get(id=task_id)
    required_role = role_required_for_new_task(task)

    assignment = current_assignment(task)
    if not is_worker_certified_for_task(worker, task, required_role):
        raise WorkerCertificationError('Worker not certified for this task.')
    if task.is_worker_assigned(worker):
        raise TaskAssignmentError('Worker already assigned to this task.')

    assignment_counter = task.assignments.count()
    in_progress_task_data = {}

    if assignment:
        # In-progress task data is the latest
        # submission by a previous worker
        in_progress_task_data = assignment.in_progress_task_data

    previous_status = task.status
    if previous_status == Task.Status.AWAITING_PROCESSING:
        task.status = Task.Status.PROCESSING
    elif previous_status == Task.Status.PENDING_REVIEW:
        task.status = Task.Status.REVIEWING
    task.save()
    assignment = (TaskAssignment.objects.create(
        worker=worker,
        task=task,
        status=TaskAssignment.Status.PROCESSING,
        assignment_counter=assignment_counter,
        in_progress_task_data=in_progress_task_data))

    Iteration.objects.create(assignment=assignment,
                             start_datetime=assignment.start_datetime)

    add_worker_to_project_team(worker, task.project)
    notify_status_change(task, previous_status)
    return task
Пример #6
0
def role_counter_required_for_new_task(task):
    """
    Given a task find a level of expert that is required
    to be assigned for task.
    """
    if task.status not in [Task.Status.AWAITING_PROCESSING,
                           Task.Status.PENDING_REVIEW]:
        raise TaskAssignmentError('Status incompatible with new assignment')
    elif (task.status == Task.Status.AWAITING_PROCESSING and
          task.assignments.filter(assignment_counter=0).exists()):
        raise TaskAssignmentError('Task is in incorrect state')
    assignments_count = task.assignments.count()
    for assignment_counter in range(assignments_count):
        task_assignment = (task.assignments
                           .filter(assignment_counter=assignment_counter))
        if not task_assignment.exists():
            return assignment_counter
    return assignments_count
Пример #7
0
def role_required_for_new_task(task):
    roles = {
        Task.Status.AWAITING_PROCESSING: WorkerCertification.Role.ENTRY_LEVEL,
        Task.Status.PENDING_REVIEW: WorkerCertification.Role.REVIEWER
    }

    required_role = roles.get(task.status)
    if required_role is None:
        raise TaskAssignmentError('Status incompatible with new assignment')
    return required_role
Пример #8
0
def reassign_assignment(worker_id,
                        assignment_id,
                        staffing_request_inquiry=None):
    """
    Return a given assignment after reassigning it to the specified
    worker.

    Args:
        worker_id (int):
            The ID of the worker to be assigned.
        assignment_id (int):
            The ID of the assignment to be assigned.

    Returns:
        assignment (orchestra.models.TaskAssignment):
            The newly assigned assignment.

    Raises:
        orchestra.core.errors.TaskAssignmentError:
            The specified worker is already assigned to the given task.
        orchestra.core.errors.WorkerCertificationError:
            The specified worker is not certified for the given assignment.
    """
    worker = Worker.objects.get(id=worker_id)
    assignment = TaskAssignment.objects.get(id=assignment_id)
    if assignment.assignment_counter > 0:
        role = WorkerCertification.Role.REVIEWER
    else:
        role = WorkerCertification.Role.ENTRY_LEVEL

    if not is_worker_certified_for_task(worker, assignment.task, role):
        raise WorkerCertificationError(
            'Worker not certified for this assignment.')
    if assignment.task.is_worker_assigned(worker):
        raise TaskAssignmentError('Worker already assigned to this task.')

    assignment.worker = worker
    assignment.save()

    mark_worker_as_winner(worker, assignment.task,
                          assignment.assignment_counter,
                          staffing_request_inquiry)
    add_worker_to_project_team(worker, assignment.task.project)
    return assignment
Пример #9
0
def get_task_overview_for_worker(task_id, worker):
    """
    Get information about `task` and its assignment for `worker`.

    Args:
        task_id (int):
            The ID of the desired task object.
        worker (orchestra.models.Worker):
            The specified worker object.

    Returns:
        task_assignment_details (dict):
            Information about `task` and its assignment for `worker`.
    """
    task = Task.objects.get(id=task_id)
    if not task.is_worker_assigned(worker):
        raise TaskAssignmentError('Worker is not associated with task')
    task_details = get_task_details(task_id)

    task_assignment = TaskAssignment.objects.get(worker=worker, task=task)
    task_assignment_details = get_task_assignment_details(task_assignment)
    task_assignment_details.update(task_details)
    return task_assignment_details
Пример #10
0
def current_assignment(task):
    """
    Return the in-progress assignment for `task`.

    Args:
        task (orchestra.models.Task):
            The specified task object.

    Returns:
        current_assignment (orchestra.models.TaskAssignment):
            The in-progress assignment for `task`.
    """
    assignments = assignment_history(task)
    processing = task.assignments.filter(
        status=TaskAssignment.Status.PROCESSING)
    if not processing.exists():
        return assignments.last()
    else:
        if processing.count() > 1:
            raise TaskAssignmentError(
                'More than one processing assignment for task {}'.format(
                    task.id))
        else:
            return processing.first()
Пример #11
0
def submit_task(task_id, task_data, iteration_status, worker):
    """
    Returns a dict mapping task prerequisites onto their
    latest task assignment information.  The dict is of the form:
    {'previous-slug': {task_assignment_data}, ...}

    Args:
        task_id (int):
            The ID of the task to submit.
        task_data (str):
            A JSON blob of task data to submit.
        iteration_status (orchestra.models.Iteration.Status):
            The action taken upon task submission (i.e., REQUESTED_REVIEW
            or PROVIDED_REVIEW).
        worker (orchestra.models.Worker):
            The worker submitting the task.

    Returns:
        task (orchestra.models.Task):
            The modified task object.

    Raises:
        orchestra.core.errors.IllegalTaskSubmission:
            Submission prerequisites for the task are incomplete or the
            assignment is in a non-processing state.
        orchestra.core.errors.TaskAssignmentError:
            Worker belongs to more than one assignment for the given
            task.
        orchestra.core.errors.TaskStatusError:
            Task has already been completed.
    """
    submit_datetime = timezone.now()

    task = Task.objects.select_related('step', 'project').get(id=task_id)
    step = task.step
    if not _are_desired_steps_completed_on_project(step.submission_depends_on,
                                                   project=task.project):
        raise IllegalTaskSubmission('Submission prerequisites are not '
                                    'complete.')

    if task.status == Task.Status.COMPLETE:
        raise TaskStatusError('Task already completed')

    # Use select_for_update to prevent concurrency issues with save_task.
    # See https://github.com/b12io/orchestra/issues/2.
    assignments = (TaskAssignment.objects.select_for_update().filter(
        worker=worker, task=task))

    # Worker can belong to only one assignment for a given task.
    if not assignments.count() == 1:
        raise TaskAssignmentError(
            'Task assignment with worker is in broken state.')

    assignment = assignments[0]

    if assignment.status != TaskAssignment.Status.PROCESSING:
        raise IllegalTaskSubmission('Worker is not allowed to submit')

    next_status = get_next_task_status(task, iteration_status)

    assignment.in_progress_task_data = task_data

    # Submit latest iteration
    latest_iteration = get_latest_iteration(assignment)
    latest_iteration.status = iteration_status
    latest_iteration.submitted_data = assignment.in_progress_task_data
    latest_iteration.end_datetime = submit_datetime
    latest_iteration.save()

    assignment.status = TaskAssignment.Status.SUBMITTED
    assignment.save()
    previous_status = task.status
    task.status = next_status
    task.save()

    if task.status == Task.Status.PENDING_REVIEW:
        # Check the assignment policy to try to assign a reviewer automatically
        task = _preassign_workers(task, AssignmentPolicyType.REVIEWER)
    elif task.status == Task.Status.REVIEWING:
        update_related_assignment_status(task,
                                         assignment.assignment_counter + 1,
                                         assignment.in_progress_task_data,
                                         submit_datetime)
    elif task.status == Task.Status.POST_REVIEW_PROCESSING:
        update_related_assignment_status(task,
                                         assignment.assignment_counter - 1,
                                         assignment.in_progress_task_data,
                                         submit_datetime)
    elif task.status == Task.Status.COMPLETE:
        create_subsequent_tasks(task.project)

    notify_status_change(task, previous_status)
    return task
Пример #12
0
def submit_task(task_id, task_data, snapshot_type, worker, work_time_seconds):
    """
    Returns a dict mapping task prerequisites onto their
    latest task assignment information.  The dict is of the form:
    {'previous-slug': {task_assignment_data}, ...}

    Args:
        task_id (int):
            The ID of the task to submit.
        task_data (str):
            A JSON blob of task data to submit.
        snapshot_type (orchestra.models.TaskAssignment.SnapshotType):
            The action to take upon task submission (e.g., SUBMIT,
            ACCEPT, REJECT).
        worker (orchestra.models.Worker):
            The worker submitting the task.
        work_time_seconds (int):
            The time taken by the worker on the latest iteration of
            their task assignment.

    Returns:
        task (orchestra.models.Task):
            The modified task object.

    Raises:
        orchestra.core.errors.IllegalTaskSubmission:
            Submission prerequisites for the task are incomplete or the
            assignment is in a non-processing state.
        orchestra.core.errors.TaskAssignmentError:
            Worker belongs to more than one assignment for the given
            task.
        orchestra.core.errors.TaskStatusError:
            Task has already been completed.
    """
    task = Task.objects.select_related('step', 'project').get(id=task_id)
    step = task.step
    if not _are_desired_steps_completed_on_project(step.submission_depends_on,
                                                   project=task.project):
        raise IllegalTaskSubmission('Submission prerequisites are not '
                                    'complete.')

    if task.status == Task.Status.COMPLETE:
        raise TaskStatusError('Task already completed')

    # Use select_for_update to prevent concurrency issues with save_task.
    # See https://github.com/unlimitedlabs/orchestra/issues/2.
    assignments = (TaskAssignment.objects.select_for_update().filter(
        worker=worker, task=task))

    # Worker can belong to only one assignment for a given task.
    if not assignments.count() == 1:
        raise TaskAssignmentError(
            'Task assignment with worker is in broken state.')

    assignment = assignments[0]

    if assignment.status != TaskAssignment.Status.PROCESSING:
        raise IllegalTaskSubmission('Worker is not allowed to submit')

    next_status = get_next_task_status(task, snapshot_type)

    assignment.in_progress_task_data = task_data
    assignment.snapshots['snapshots'].append({
        'data':
        assignment.in_progress_task_data,
        'datetime':
        timezone.now().isoformat(),
        'type':
        snapshot_type,
        'work_time_seconds':
        work_time_seconds
    })

    assignment.status = TaskAssignment.Status.SUBMITTED
    assignment.save()
    previous_status = task.status
    task.status = next_status
    task.save()

    if task.status == Task.Status.REVIEWING:
        update_related_assignment_status(task,
                                         assignment.assignment_counter + 1,
                                         assignment.in_progress_task_data)
    elif task.status == Task.Status.POST_REVIEW_PROCESSING:
        update_related_assignment_status(task,
                                         assignment.assignment_counter - 1,
                                         assignment.in_progress_task_data)
    elif task.status == Task.Status.COMPLETE:
        create_subsequent_tasks(task.project)

    notify_status_change(task, previous_status)
    return task