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
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
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
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
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
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
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()
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
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()
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)
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)
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