Ejemplo n.º 1
0
    def update_stats_after_task_state_change(project_id: int, user_id: int, new_state: TaskStatus, task_id: int):
        """ Update stats when a task has had a state change """
        if new_state in [TaskStatus.READY, TaskStatus.LOCKED_FOR_VALIDATION, TaskStatus.LOCKED_FOR_MAPPING]:
            return  # No stats to record for these states

        project = ProjectService.get_project_by_id(project_id)
        user = UserService.get_user_by_id(user_id)

        if new_state == TaskStatus.MAPPED:
            StatsService._set_counters_after_mapping(project, user)
        elif new_state == TaskStatus.INVALIDATED:
            StatsService._set_counters_after_invalidated(task_id, project, user)
        elif new_state == TaskStatus.VALIDATED:
            StatsService._set_counters_after_validated(project, user)
        elif new_state == TaskStatus.BADIMAGERY:
            StatsService._set_counters_after_bad_imagery(project)

        UserService.upsert_mapped_projects(user_id, project_id)
        project.last_updated = timestamp()

        # Transaction will be saved when task is saved
        return project, user
    def lock_task_for_mapping(lock_task_dto: LockTaskDTO) -> TaskDTO:
        """
        Sets the task_locked status to locked so no other user can work on it
        :param lock_task_dto: DTO with data needed to lock the task
        :raises TaskServiceError
        :return: Updated task, or None if not found
        """
        task = MappingService.get_task(lock_task_dto.task_id, lock_task_dto.project_id)

        if not task.is_mappable():
            raise MappingServiceError('Task in invalid state for mapping')

        user_can_map, error_reason = ProjectService.is_user_permitted_to_map(lock_task_dto.project_id,
                                                                             lock_task_dto.user_id)
        if not user_can_map:
            if error_reason == MappingNotAllowed.USER_NOT_ACCEPTED_LICENSE:
                raise UserLicenseError('User must accept license to map this task')
            else:
                raise MappingServiceError(f'Mapping not allowed because: {error_reason.name}')

        task.lock_task_for_mapping(lock_task_dto.user_id)
        return task.as_dto_with_instructions(lock_task_dto.preferred_locale)
Ejemplo n.º 3
0
 def get(self, project_id: int):
     """
     Gets project summary
     ---
     tags:
       - mapping
     produces:
       - application/json
     parameters:
         - in: header
           name: Accept-Language
           description: Language user is requesting
           type: string
           required: true
           default: en
         - name: project_id
           in: path
           description: The ID of the project
           required: true
           type: integer
           default: 1
     responses:
         200:
             description: Project Summary
         404:
             description: Project not found
         500:
             description: Internal Server Error
     """
     try:
         preferred_locale = request.environ.get('HTTP_ACCEPT_LANGUAGE')
         summary = ProjectService.get_project_summary(project_id, preferred_locale)
         return summary.to_primitive(), 200
     except NotFound:
         return {"Error": "Project not found"}, 404
     except Exception as e:
         error_msg = f'Project Summary GET - unhandled error: {str(e)}'
         current_app.logger.critical(error_msg)
         return {"error": error_msg}, 500
 def get(self, project_id, username):
     """
     Get detailed stats about user
     ---
     tags:
       - user
     produces:
       - application/json
     parameters:
         - name: project_id
           in: path
           required: true
           type: integer
           default: 1
         - name: username
           in: path
           description: The users username
           required: true
           type: string
           default: Thinkwhere
     responses:
         200:
             description: User found
         404:
             description: User not found
         500:
             description: Internal Server Error
     """
     try:
         stats_dto = ProjectService.get_project_user_stats(
             project_id, username)
         return stats_dto.to_primitive(), 200
     except NotFound:
         return {"Error": "User not found"}, 404
     except Exception as e:
         error_msg = f'User GET - unhandled error: {str(e)}'
         current_app.logger.critical(error_msg)
         return {"error": error_msg}, 500
Ejemplo n.º 5
0
    def validate_all_tasks(project_id: int, user_id: int):
        """ Validates all mapped tasks on a project"""
        tasks_to_validate = Task.query.filter(
            Task.project_id == project_id,
            Task.task_status != TaskStatus.BADIMAGERY.value).all()

        for task in tasks_to_validate:
            task.mapped_by = task.mapped_by or user_id  # Ensure we set mapped by value
            if TaskStatus(task.task_status) not in [
                    TaskStatus.LOCKED_FOR_MAPPING,
                    TaskStatus.LOCKED_FOR_VALIDATION
            ]:
                # Only lock tasks that are not already locked to avoid double lock issue
                task.lock_task_for_validating(user_id)

            task.unlock_task(user_id, new_state=TaskStatus.VALIDATED)

        # Set counters to fully mapped and validated
        project = ProjectService.get_project_by_id(project_id)
        project.tasks_mapped = (project.total_tasks -
                                project.tasks_bad_imagery)
        project.tasks_validated = project.total_tasks
        project.save()
Ejemplo n.º 6
0
    def update_stats_after_task_state_change(project_id: int,
                                             user_id: int,
                                             last_state: TaskStatus,
                                             new_state: TaskStatus,
                                             action='change'):
        """ Update stats when a task has had a state change """

        if new_state in [
                TaskStatus.READY, TaskStatus.LOCKED_FOR_VALIDATION,
                TaskStatus.LOCKED_FOR_MAPPING
        ]:
            return  # No stats to record for these states

        project = ProjectService.get_project_by_id(project_id)
        user = UserService.get_user_by_id(user_id)

        StatsService._update_tasks_stats(project, user, last_state, new_state,
                                         action)
        UserService.upsert_mapped_projects(user_id, project_id)
        project.last_updated = timestamp()

        # Transaction will be saved when task is saved
        return project, user
    def map_all_tasks(project_id: int, user_id: int):
        """ Marks all tasks on a project as mapped """
        tasks_to_map = Task.query.filter(
            Task.project_id == project_id,
            Task.task_status.notin_([
                TaskStatus.BADIMAGERY.value, TaskStatus.MAPPED.value,
                TaskStatus.VALIDATED.value
            ])).all()

        for task in tasks_to_map:
            if TaskStatus(task.task_status) not in [
                    TaskStatus.LOCKED_FOR_MAPPING,
                    TaskStatus.LOCKED_FOR_VALIDATION
            ]:
                # Only lock tasks that are not already locked to avoid double lock issue
                task.lock_task_for_mapping(user_id)

            task.unlock_task(user_id, new_state=TaskStatus.MAPPED)

        # Set counters to fully mapped
        project = ProjectService.get_project_by_id(project_id)
        project.tasks_mapped = (project.total_tasks -
                                project.tasks_bad_imagery)
        project.save()
    def send_message_after_comment(comment_from: int, comment: str,
                                   task_id: int, project_id: int):
        """ Will send a canned message to anyone @'d in a comment """
        usernames = MessageService._parse_message_for_username(comment)

        if len(usernames) == 0:
            return  # Nobody @'d so return

        task_link = MessageService.get_task_link(project_id, task_id)
        project_title = ProjectService.get_project_title(project_id)
        for username in usernames:

            try:
                user = UserService.get_user_by_username(username)
            except NotFound:
                continue  # If we can't find the user, keep going no need to fail

            message = Message()
            message.from_user_id = comment_from
            message.to_user_id = user.id
            message.subject = f'You were mentioned in a comment in Project {project_id} on {task_link}'
            message.message = comment
            message.add_message()
            SMTPService.send_email_alert(user.email_address, user.username)
Ejemplo n.º 9
0
    def get(self, project_id):
        """
        Get HOT Project for mapping
        ---
        tags:
            - mapping
        produces:
            - application/json
        parameters:
            - in: header
              name: Accept-Language
              description: Language user is requesting
              type: string
              required: true
              default: en
            - name: project_id
              in: path
              description: The unique project ID
              required: true
              type: integer
              default: 1
            - in: query
              name: as_file
              type: boolean
              description: Set to true if file download is preferred
              default: False
            - in: query
              name: abbreviated
              type: boolean
              description: Set to true if only state information is desired
              default: False
        responses:
            200:
                description: Project found
            403:
                description: Forbidden
            404:
                description: Project not found
            500:
                description: Internal Server Error
        """
        try:
            as_file = strtobool(request.args.get(
                'as_file')) if request.args.get('as_file') else False
            abbreviated = strtobool(request.args.get(
                'abbreviated')) if request.args.get('abbreviated') else False

            project_dto = ProjectService.get_project_dto_for_mapper(
                project_id, request.environ.get('HTTP_ACCEPT_LANGUAGE'),
                abbreviated)
            project_dto = project_dto.to_primitive()

            if as_file:
                return send_file(
                    io.BytesIO(geojson.dumps(project_dto).encode('utf-8')),
                    mimetype='application/json',
                    as_attachment=True,
                    attachment_filename=f'project_{str(project_id)}.json')

            return project_dto, 200
        except NotFound:
            return {"Error": "Project Not Found"}, 404
        except ProjectServiceError as e:
            return {"error": str(e)}, 403
        except Exception as e:
            error_msg = f'Project GET - unhandled error: {str(e)}'
            current_app.logger.critical(error_msg)
            return {"error": error_msg}, 500
        finally:
            # this will try to unlock tasks that have been locked too long
            try:
                ProjectService.auto_unlock_tasks(project_id)
            except Exception as e:
                current_app.logger.critical(str(e))
    def test_project_service_raises_error_if_project_not_found(
            self, mock_project):
        mock_project.return_value = None

        with self.assertRaises(NotFound):
            ProjectService.get_project_by_id(123)
Ejemplo n.º 11
0
    def post(self, project_id: int, annotation_type: str):
        """
        Store new task annotations for tasks of a project
        ---
        tags:
            - task annotations
        produces:
            - application/json
        parameters:
            - in: header
              name: Content-Type
              description: Content type for post body
              required: true
              type: string
              default: application/json
            - name: project_id
              in: path
              description: The unique project ID
              required: true
              type: integer
            - name: annotation_type
              in: path
              description: Annotation type
              required: true
              type: string
            - name: Application-Token
              in: header
              description: Application token registered with TM
              required: true
              type: string
            - in: body
              name: body
              required: true
              description: JSON object for creating draft project
              schema:
                projectId:
                    type: integer
                    required: true
                annotationType:
                    type: string
                    required: true
                tasks:
                    type: array
                    required: true
                    items:
                        schema:
                            taskId:
                                type: integer
                                required: true
                            annotationSource:
                                type: string
                            annotationMarkdown:
                                type: string
                            properties:
                                description: JSON object with properties
        responses:
            200:
                description: Project updated
            400:
                description: Client Error - Invalid Request
            404:
                description: Project or task not found
            500:
                description: Internal Server Error
        """

        if 'Application-Token' in request.headers:
            application_token = request.headers['Application-Token']
            try:
                is_valid_token = ApplicationService.check_token(
                    application_token)
            except NotFound as e:
                current_app.logger.error(f'Invalid token')
                return {"error": "Invalid token"}, 500
        else:
            current_app.logger.error(f'No token supplied')
            return {"error": "No token supplied"}, 500

        try:
            annotations = request.get_json() or {}
        except DataError as e:
            current_app.logger.error(f'Error validating request: {str(e)}')

        try:
            project = ProjectService.get_project_by_id(project_id)
        except NotFound as e:
            current_app.logger.error(f'Error validating project: {str(e)}')

        task_ids = [t['taskId'] for t in annotations['tasks']]

        # check if task ids are valid
        tasks = Task.get_tasks(project_id, task_ids)
        tasks_ids_db = [t.id for t in tasks]
        if (len(task_ids) != len(tasks_ids_db)):
            return {"error": 'Invalid task id'}, 500

        for annotation in annotations['tasks']:
            try:
                TaskAnnotationsService.add_or_update_annotation(
                    annotation, project_id, annotation_type)
            except DataError as e:
                current_app.logger.error(
                    f'Error creating annotations: {str(e)}')
                return {"Error": "Error creating annotations"}, 500

        return project_id, 200
Ejemplo n.º 12
0
    def test_user_is_allowed_to_map_if_level_enforced(self, mock_level):
        # Arrange
        mock_level.return_value = MappingLevel.ADVANCED

        # Act / Assert
        self.assertTrue(ProjectService._is_user_mapping_level_at_or_above_level_requests(MappingLevel.ADVANCED, 1))
Ejemplo n.º 13
0
    def test_user_not_allowed_to_map_if_level_enforced(self, mock_level):
        # Arrange
        mock_level.return_value = MappingLevel.BEGINNER

        # Act / Assert
        self.assertFalse(ProjectService._is_user_mapping_level_at_or_above_level_requests(MappingLevel.INTERMEDIATE, 1))