Ejemplo n.º 1
0
    def get(self, project_id):
        """
        Get latest user activity on all of project task
        ---
        tags:
          - projects
        produces:
          - application/json
        parameters:
            - name: project_id
              in: path
              required: true
              type: integer
              default: 1
        responses:
            200:
                description: Project activity
            404:
                description: No activity
            500:
                description: Internal Server Error
        """
        try:
            ProjectService.get_project_by_id(project_id)
        except NotFound as e:
            current_app.logger.error(f"Error validating project: {str(e)}")
            return {"Error": "Project not found"}, 404

        try:
            activity = StatsService.get_last_activity(project_id)
            return activity.to_primitive(), 200
        except Exception as e:
            error_msg = f"User GET - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to fetch user activity"}, 500
Ejemplo n.º 2
0
    def get(self, project_id):
        """
        Get all chat messages for a project
        ---
        tags:
          - comments
        produces:
          - application/json
        parameters:
            - name: project_id
              in: path
              description: Project ID to attach the chat message to
              required: true
              type: integer
              default: 1
            - in: query
              name: page
              description: Page of results user requested
              type: integer
              default: 1
            - in: query
              name: perPage
              description: Number of elements per page.
              type: integer
              default: 20
        responses:
            200:
                description: All messages
            404:
                description: No chat messages on project
            500:
                description: Internal Server Error
        """
        try:
            ProjectService.get_project_by_id(project_id)
        except NotFound as e:
            current_app.logger.error(f"Error validating project: {str(e)}")
            return {"Error": "Project not found"}, 404

        try:
            page = int(
                request.args.get("page")) if request.args.get("page") else 1
            per_page = int(request.args.get("perPage", 20))
            project_messages = ChatService.get_messages(
                project_id, page, per_page)
            return project_messages.to_primitive(), 200
        except NotFound:
            return {"Error": "Project not found"}, 404
        except Exception as e:
            error_msg = f"Chat GET - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to fetch chat messages"}, 500
Ejemplo n.º 3
0
    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 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)

        project, user = 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
Ejemplo n.º 5
0
    def create_or_update_project_interests(project_id, interests):
        project = ProjectService.get_project_by_id(project_id)
        project.create_or_update_interests(interests)

        # Return DTO.
        dto = InterestsListDTO()
        dto.interests = [i.as_dto() for i in project.interests]

        return dto
Ejemplo n.º 6
0
    def get(self, project_id: int, annotation_type: str = None):
        """
        Get all task annotations for a project
        ---
        tags:
            - annotations
        produces:
            - application/json
        parameters:
            - name: project_id
              in: path
              description: The ID of the project
              required: true
              type: integer
            - name: annotation_type
              in: path
              description: The type of annotation to fetch
              required: false
              type: integer
        responses:
            200:
                description: Project Annotations
            404:
                description: Project or annotations not found
            500:
                description: Internal Server Error
        """
        try:
            ProjectService.get_project_by_id(project_id)
        except NotFound as e:
            current_app.logger.error(f"Error validating project: {str(e)}")
            return {"Error": "Project not found"}, 404

        try:
            if annotation_type:
                annotations = TaskAnnotation.get_task_annotations_by_project_id_type(
                    project_id, annotation_type)
            else:
                annotations = TaskAnnotation.get_task_annotations_by_project_id(
                    project_id)
            return annotations.to_primitive(), 200
        except NotFound:
            return {"Error": "Annotations not found"}, 404
Ejemplo n.º 7
0
    def get(self, project_id):
        """
        Get all user activity on a project
        ---
        tags:
          - projects
        produces:
          - application/json
        parameters:
            - name: project_id
              in: path
              description: Unique project ID
              required: true
              type: integer
              default: 1
            - in: query
              name: page
              description: Page of results user requested
              type: integer
        responses:
            200:
                description: Project activity
            404:
                description: No activity
            500:
                description: Internal Server Error
        """
        try:
            ProjectService.get_project_by_id(project_id)
        except NotFound as e:
            current_app.logger.error(f"Error validating project: {str(e)}")
            return {"Error": "Project not found"}, 404

        try:
            page = int(request.args.get("page")) if request.args.get("page") else 1
            activity = StatsService.get_latest_activity(project_id, page)
            return activity.to_primitive(), 200
        except Exception as e:
            error_msg = f"User GET - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to fetch user activity"}, 500
Ejemplo n.º 8
0
    def update_project_stats(project_id: int):
        project = ProjectService.get_project_by_id(project_id)
        tasks = Task.query.filter(Task.project_id == project_id)

        project.total_tasks = tasks.count()
        project.tasks_mapped = tasks.filter(
            Task.task_status == TaskStatus.MAPPED.value).count()
        project.tasks_validated = tasks.filter(
            Task.task_status == TaskStatus.VALIDATED.value).count()
        project.tasks_bad_imagery = tasks.filter(
            Task.task_status == TaskStatus.BADIMAGERY.value).count()
        project.save()
Ejemplo n.º 9
0
    def reset_all_badimagery(project_id: int, user_id: int):
        """ Marks all bad imagery tasks ready for mapping """
        badimagery_tasks = Task.query.filter(
            Task.task_status == TaskStatus.BADIMAGERY.value).all()

        for task in badimagery_tasks:
            task.lock_task_for_mapping(user_id)
            task.unlock_task(user_id, new_state=TaskStatus.READY)

        # Reset bad imagery counter
        project = ProjectService.get_project_by_id(project_id)
        project.tasks_bad_imagery = 0
        project.save()
    def get(self, project_id):
        """
        Get all user contributions on a project
        ---
        tags:
          - projects
        produces:
          - application/json
        parameters:
            - name: project_id
              in: path
              description: Unique project ID
              required: true
              type: integer
              default: 1
        responses:
            200:
                description: User contributions
            404:
                description: No contributions
            500:
                description: Internal Server Error
        """
        try:
            ProjectService.get_project_by_id(project_id)
        except NotFound as e:
            current_app.logger.error(f"Error validating project: {str(e)}")
            return {"Error": "Project not found"}, 404

        try:
            contributions = StatsService.get_user_contributions(project_id)
            return contributions.to_primitive(), 200
        except Exception as e:
            error_msg = f"User GET - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to fetch user contributions"}, 500
Ejemplo n.º 11
0
    def post_message(chat_dto: ChatMessageDTO, project_id: int,
                     authenticated_user_id: int) -> ProjectChatDTO:
        """ Save message to DB and return latest chat"""
        current_app.logger.debug("Posting Chat Message")

        if UserService.is_user_blocked(authenticated_user_id):
            raise ValueError("User is on read only mode")

        project = ProjectService.get_project_by_id(project_id)
        is_allowed_user = True
        is_manager_permission = ProjectAdminService.is_user_action_permitted_on_project(
            authenticated_user_id, project_id)
        is_team_member = False

        # Draft (public/private) accessible only for is_manager_permission
        if (ProjectStatus(project.status) == ProjectStatus.DRAFT
                and not is_manager_permission):
            raise ValueError("User not permitted to post Comment")

        if project.private:
            is_allowed_user = False
            if not is_manager_permission:
                allowed_roles = [
                    TeamRoles.PROJECT_MANAGER.value,
                    TeamRoles.VALIDATOR.value,
                    TeamRoles.MAPPER.value,
                ]
                is_team_member = TeamService.check_team_membership(
                    project_id, allowed_roles, authenticated_user_id)
                if not is_team_member:
                    is_allowed_user = (len([
                        user for user in project.allowed_users
                        if user.id == authenticated_user_id
                    ]) > 0)

        if is_manager_permission or is_team_member or is_allowed_user:
            chat_message = ProjectChat.create_from_dto(chat_dto)
            MessageService.send_message_after_chat(chat_dto.user_id,
                                                   chat_message.message,
                                                   chat_dto.project_id)
            db.session.commit()
            # Ensure we return latest messages after post
            return ProjectChat.get_messages(chat_dto.project_id, 1)
        else:
            raise ValueError("User not permitted to post Comment")
    def invalidate_all_tasks(project_id: int, user_id: int):
        """ Invalidates all mapped tasks on a project"""
        mapped_tasks = Task.query.filter(
            Task.project_id == project_id,
            ~Task.task_status.in_(
                [TaskStatus.READY.value, TaskStatus.BADIMAGERY.value]),
        ).all()
        for task in mapped_tasks:
            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.INVALIDATED)

        # Reset counters
        project = ProjectService.get_project_by_id(project_id)
        project.tasks_mapped = 0
        project.tasks_validated = 0
        project.save()
    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.º 14
0
    def post_message(
        chat_dto: ChatMessageDTO, project_id: int, authenticated_user_id: int
    ) -> ProjectChatDTO:
        """ Save message to DB and return latest chat"""
        current_app.logger.debug("Posting Chat Message")

        if UserService.is_user_blocked(authenticated_user_id):
            return ValueError("User is on read only mode")

        project = ProjectService.get_project_by_id(project_id)
        if project.private:
            author_id = project.author_id
            allowed_roles = [
                TeamRoles.PROJECT_MANAGER.value,
                TeamRoles.VALIDATOR.value,
                TeamRoles.MAPPER.value,
            ]

            is_admin = UserService.is_user_an_admin(authenticated_user_id)
            is_author = UserService.is_user_the_project_author(
                authenticated_user_id, author_id
            )
            is_org_manager = False
            if hasattr(project, "organisation_id") and project.organisation_id:
                org_id = project.organisation_id
                org = OrganisationService.get_organisation_by_id_as_dto(
                    org_id, authenticated_user_id
                )
                if org.is_manager:
                    is_org_manager = True

            is_team_member = None
            if hasattr(project, "project_teams") and project.project_teams:
                teams_dto = TeamService.get_project_teams_as_dto(project_id)
                if teams_dto.teams:
                    teams_allowed = [
                        team_dto
                        for team_dto in teams_dto.teams
                        if team_dto.role in allowed_roles
                    ]
                    user_membership = [
                        team_dto.team_id
                        for team_dto in teams_allowed
                        if TeamService.is_user_member_of_team(
                            team_dto.team_id, authenticated_user_id
                        )
                    ]
                    if user_membership:
                        is_team_member = True

            for user in project.allowed_users:
                if user.id == authenticated_user_id:
                    is_allowed_user = True
                    break

            if (
                is_admin
                or is_author
                or is_org_manager
                or is_team_member
                or is_allowed_user
            ):
                chat_message = ProjectChat.create_from_dto(chat_dto)
                MessageService.send_message_after_chat(
                    chat_dto.user_id, chat_message.message, chat_dto.project_id
                )
                db.session.commit()
                # Ensure we return latest messages after post
                return ProjectChat.get_messages(chat_dto.project_id, 1)
            else:
                raise ValueError("User not permitted to post Comment")
        else:
            chat_message = ProjectChat.create_from_dto(chat_dto)
            MessageService.send_message_after_chat(
                chat_dto.user_id, chat_message.message, chat_dto.project_id
            )
            db.session.commit()
            # Ensure we return latest messages after post
            return ProjectChat.get_messages(chat_dto.project_id, 1)
Ejemplo n.º 15
0
    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.º 16
0
    def post(self, project_id: int, annotation_type: str):
        """
        Store new task annotations for tasks of a project
        ---
        tags:
            - 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: 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(  # noqa
                    application_token)
            except NotFound:
                current_app.logger.error("Invalid token")
                return {"Error": "Invalid token"}, 500
        else:
            current_app.logger.error("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:
            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