def unlock_task_after_mapping(mapped_task: MappedTaskDTO) -> TaskDTO:
        """ Unlocks the task and sets the task history appropriately """
        task = MappingService.get_task_locked_by_user(mapped_task.project_id,
                                                      mapped_task.task_id,
                                                      mapped_task.user_id)

        new_state = TaskStatus[mapped_task.status.upper()]

        if new_state not in [
                TaskStatus.MAPPED,
                TaskStatus.BADIMAGERY,
                TaskStatus.READY,
        ]:
            raise MappingServiceError(
                "Can only set status to MAPPED, BADIMAGERY, READY after mapping"
            )

        # Update stats around the change of state
        last_state = TaskHistory.get_last_status(mapped_task.project_id,
                                                 mapped_task.task_id, True)
        StatsService.update_stats_after_task_state_change(
            mapped_task.project_id, mapped_task.user_id, last_state, new_state)

        if mapped_task.comment:
            # Parses comment to see if any users have been @'d
            MessageService.send_message_after_comment(
                mapped_task.user_id,
                mapped_task.comment,
                task.id,
                mapped_task.project_id,
            )

        task.unlock_task(mapped_task.user_id, new_state, mapped_task.comment)

        return task.as_dto_with_instructions(mapped_task.preferred_locale)
示例#2
0
    def undo_mapping(project_id: int,
                     task_id: int,
                     user_id: int,
                     preferred_locale: str = "en") -> TaskDTO:
        """ Allows a user to Undo the task state they updated """
        task = MappingService.get_task(task_id, project_id)

        if not MappingService._is_task_undoable(user_id, task):
            raise MappingServiceError("Undo not allowed for this user")

        current_state = TaskStatus(task.task_status)
        undo_state = TaskHistory.get_last_status(project_id, task_id, True)

        # Refer to last action for user of it.
        last_action = TaskHistory.get_last_action(project_id, task_id)

        StatsService.update_stats_after_task_state_change(
            project_id, last_action.user_id, current_state, undo_state, "undo")

        task.unlock_task(
            user_id,
            undo_state,
            f"Undo state from {current_state.name} to {undo_state.name}",
            True,
        )

        return task.as_dto_with_instructions(preferred_locale)
示例#3
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
示例#4
0
    def get(self):
        """
        Get HomePage Stats
        ---
        tags:
          - system
        produces:
          - application/json
        parameters:
        - in: query
          name: abbreviated
          type: boolean
          description: Set to false if complete details on projects including total area, campaigns, orgs are required
          default: True
        responses:
            200:
                description: Project stats
            500:
                description: Internal Server Error
        """
        try:
            abbreviated = (strtobool(request.args.get("abbreviated"))
                           if request.args.get("abbreviated") else True)

            stats = StatsService.get_homepage_stats(abbreviated)
            return stats.to_primitive(), 200
        except Exception as e:
            error_msg = f"Unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to fetch summary statistics"}, 500
示例#5
0
    def test_homepage_stats_returns_results(self):
        # Act
        stats = StatsService.get_homepage_stats()

        # Assert
        self.assertGreaterEqual(stats.mappers_online, 0)
        self.assertGreater(stats.tasks_mapped, 0)
        self.assertGreater(stats.total_mappers, 0)
示例#6
0
    def get(self):
        """
        Get stats about users registered within a period of time
        ---
        tags:
            - users
        produces:
            - application/json
        parameters:
            - in: header
              name: Authorization
              description: Base64 encoded session token
              type: string
              required: true
              default: Token sessionTokenHere==
            - in: query
              name: startDate
              description: Initial date
              required: true
              type: string
            - in: query
              name: endDate
              description: Final date.
              type: string
        responses:
            200:
                description: User statistics
            400:
                description: Bad Request
            401:
                description: Request is not authenticated
            500:
                description: Internal Server Error
        """
        try:
            start_date = validate_date_input(request.args.get("startDate"))
            end_date = validate_date_input(
                request.args.get("endDate", date.today()))
            if not (start_date):
                raise KeyError("Missing start date parameter")
            if end_date < start_date:
                raise ValueError("Start date must be earlier than end date")
            if (end_date - start_date) > timedelta(days=366 * 3):
                raise ValueError("Date range can not be bigger than 3 years")

            stats = StatsService.get_all_users_statistics(start_date, end_date)
            return stats.to_primitive(), 200
        except (KeyError, ValueError) as e:
            error_msg = f"User Statistics GET - {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": error_msg}, 400
        except Exception as e:
            error_msg = f"User Statistics GET - unhandled error: {str(e)}"
            current_app.logger.critical(error_msg)
            return {"Error": "Unable to fetch user stats"}, 500
    def test_update_after_flagging_bad_imagery(self):
        # Arrange
        test_project = Project()
        test_project.tasks_bad_imagery = 0

        test_user = User()
        test_user.tasks_invalidated = 0

        # Act
        test_project, test_user = StatsService._update_tasks_stats(
            test_project, test_user, TaskStatus.READY, TaskStatus.BADIMAGERY)

        # Assert
        self.assertEqual(test_project.tasks_bad_imagery, 1)
    def test_same_state_keeps_counter(self):
        # Arrange
        test_project = Project()
        test_project.tasks_mapped = 0

        test_user = User()
        test_user.tasks_mapped = 0

        # Act
        test_project, test_user = StatsService._update_tasks_stats(
            test_project, test_user, TaskStatus.MAPPED, TaskStatus.MAPPED)

        # Assert
        self.assertEqual(test_project.tasks_mapped, 0)
        self.assertEqual(test_user.tasks_mapped, 0)
    def test_update_after_mapping_increments_counter(self):
        # Arrange
        test_project = Project()
        test_project.tasks_mapped = 0

        test_user = User()
        test_user.tasks_mapped = 0

        # Act
        test_project, test_user = StatsService._update_tasks_stats(
            test_project, test_user, TaskStatus.READY, TaskStatus.MAPPED)

        # Assert
        self.assertEqual(test_project.tasks_mapped, 1)
        self.assertEqual(test_user.tasks_mapped, 1)
    def test_update_after_invalidating_bad_imagery_task_sets_counters_correctly(self):
        # Arrange
        test_project = Project()
        test_project.tasks_bad_imagery = 1

        test_user = User()
        test_user.tasks_invalidated = 0

        # Act
        test_project, test_user = StatsService._update_tasks_stats(
            test_project, test_user, TaskStatus.BADIMAGERY, TaskStatus.INVALIDATED
        )

        # Assert
        self.assertEqual(test_project.tasks_bad_imagery, 0)
        self.assertEqual(test_user.tasks_invalidated, 1)
    def test_update_after_invalidating_mapped_task_sets_counters_correctly(
            self):
        # Arrange
        test_project = Project()
        test_project.tasks_mapped = 1

        test_user = User()
        test_user.tasks_invalidated = 0

        # Act
        test_project, test_user = StatsService._update_tasks_stats(
            test_project, test_user, TaskStatus.MAPPED, TaskStatus.INVALIDATED)

        # Assert
        self.assertEqual(test_project.tasks_mapped, 0)
        self.assertEqual(test_user.tasks_invalidated, 1)
示例#12
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.exists(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
示例#13
0
 def get(self):
     """
     Get popular projects
     ---
     tags:
       - projects
     produces:
       - application/json
     responses:
         200:
             description: Popular Projects stats
         500:
             description: Internal Server Error
     """
     try:
         stats = StatsService.get_popular_projects()
         return stats.to_primitive(), 200
     except Exception as e:
         error_msg = f"Unhandled error: {str(e)}"
         current_app.logger.critical(error_msg)
         return {"Error": error_msg}, 500
    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 unlock_tasks_after_validation(
        validated_dto: UnlockAfterValidationDTO, ) -> TaskDTOs:
        """
        Unlocks supplied tasks after validation
        :raises ValidatorServiceError
        """
        validated_tasks = validated_dto.validated_tasks
        project_id = validated_dto.project_id
        user_id = validated_dto.user_id
        tasks_to_unlock = ValidatorService.get_tasks_locked_by_user(
            project_id, validated_tasks, user_id)

        # Unlock all tasks
        dtos = []
        message_sent_to = []
        for task_to_unlock in tasks_to_unlock:
            task = task_to_unlock["task"]

            if task_to_unlock["comment"]:
                # Parses comment to see if any users have been @'d
                MessageService.send_message_after_comment(
                    validated_dto.user_id,
                    task_to_unlock["comment"],
                    task.id,
                    validated_dto.project_id,
                )
            if (task_to_unlock["new_state"] == TaskStatus.VALIDATED
                    or task_to_unlock["new_state"] == TaskStatus.INVALIDATED):
                # All mappers get a notification if their task has been validated or invalidated.
                # Only once if multiple tasks mapped
                if task.mapped_by not in message_sent_to:
                    MessageService.send_message_after_validation(
                        task_to_unlock["new_state"],
                        validated_dto.user_id,
                        task.mapped_by,
                        task.id,
                        validated_dto.project_id,
                    )
                    message_sent_to.append(task.mapped_by)

                if task_to_unlock["new_state"] == TaskStatus.VALIDATED:
                    # Set last_validation_date for the mapper to current date
                    task.mapper.last_validation_date = timestamp()

            # Update stats if user setting task to a different state from previous state
            prev_status = TaskHistory.get_last_status(project_id, task.id)
            if prev_status != task_to_unlock["new_state"]:
                StatsService.update_stats_after_task_state_change(
                    validated_dto.project_id,
                    validated_dto.user_id,
                    prev_status,
                    task_to_unlock["new_state"],
                )
            task_mapping_issues = ValidatorService.get_task_mapping_issues(
                task_to_unlock)
            task.unlock_task(
                validated_dto.user_id,
                task_to_unlock["new_state"],
                task_to_unlock["comment"],
                issues=task_mapping_issues,
            )
            dtos.append(
                task.as_dto_with_instructions(validated_dto.preferred_locale))

        task_dtos = TaskDTOs()
        task_dtos.tasks = dtos

        return task_dtos
示例#16
0
def initialise_counters(app):
    """ Initialise homepage counters so that users don't see 0 users on first load of application"""
    from backend.services.stats_service import StatsService

    with app.app_context():
        StatsService.get_homepage_stats()
示例#17
0
 def get(self):
     """
     Get Task Stats
     ---
     tags:
       - tasks
     produces:
       - application/json
     parameters:
         - in: header
           name: Authorization
           description: Base64 encoded session token
           type: string
           required: true
           default: Token sessionTokenHere==
         - in: query
           name: startDate
           description: Date to filter as minimum
           required: true
           type: string
         - in: query
           name: endDate
           description: Date to filter as maximum. Default value is the current date.
           required: false
           type: string
         - in: query
           name: organisationName
           description: Organisation name to filter by
           required: false
         - in: query
           name: organisationId
           description: Organisation ID to filter by
           required: false
         - in: query
           name: campaign
           description: Campaign name to filter by
           required: false
         - in: query
           name: projectId
           description: Project IDs to filter by
           required: false
         - in: query
           name: country
           description: Country name to filter by
           required: false
     responses:
         200:
             description: Task statistics
         400:
             description: Bad Request
         401:
             description: Request is not authenticated
         500:
             description: Internal Server Error
     """
     try:
         start_date = validate_date_input(request.args.get("startDate"))
         end_date = validate_date_input(request.args.get("endDate", date.today()))
         if not (start_date):
             raise KeyError("Missing start date parameter")
         if end_date < start_date:
             raise ValueError("Start date must be earlier than end date")
         if (end_date - start_date) > timedelta(days=366):
             raise ValueError("Date range can not be bigger than 1 year")
         organisation_id = request.args.get("organisationId", None, int)
         organisation_name = request.args.get("organisationName", None, str)
         campaign = request.args.get("campaign", None, str)
         project_id = request.args.get("projectId")
         if project_id:
             project_id = map(str, project_id.split(","))
         country = request.args.get("country", None, str)
         task_stats = StatsService.get_task_stats(
             start_date,
             end_date,
             organisation_id,
             organisation_name,
             campaign,
             project_id,
             country,
         )
         return task_stats.to_primitive(), 200
     except (KeyError, ValueError) as e:
         error_msg = f"Task Statistics GET - {str(e)}"
         return {"Error": error_msg}, 400
     except Exception as e:
         error_msg = f"Task Statistics GET - unhandled error: {str(e)}"
         current_app.logger.critical(error_msg)
         return {"Error": "Unable to fetch task statistics"}, 500
    def test_tasks_state_representation(self):

        # Arrange
        test_project = Project()
        test_project.tasks_mapped = 0
        test_project.tasks_validated = 0
        test_project.tasks_bad_imagery = 0

        test_mapper = User()
        test_mapper.tasks_mapped = 0
        test_mapper.tasks_validated = 0
        test_mapper.tasks_invalidated = 0

        test_validator = User()
        test_validator.tasks_mapped = 0
        test_validator.tasks_validated = 0
        test_validator.tasks_invalidated = 0

        test_admin = User()
        test_admin.tasks_mapped = 0
        test_admin.tasks_validated = 0
        test_admin.tasks_invalidated = 0

        # Mapper marks task as mapped
        test_project, test_mapper = StatsService._update_tasks_stats(
            test_project, test_mapper, TaskStatus.READY, TaskStatus.MAPPED)

        # Validator marks task as bad imagery
        test_project, test_validator = StatsService._update_tasks_stats(
            test_project, test_validator, TaskStatus.MAPPED,
            TaskStatus.BADIMAGERY)

        # Admin undos marking task as bad imagery
        test_project, test_admin = StatsService._update_tasks_stats(
            test_project, test_admin, TaskStatus.BADIMAGERY, TaskStatus.MAPPED,
            "undo")

        # Validator marks task as invalid
        test_project, test_validator = StatsService._update_tasks_stats(
            test_project, test_validator, TaskStatus.MAPPED,
            TaskStatus.INVALIDATED)

        # Mapper marks task as mapped
        test_project, test_mapper = StatsService._update_tasks_stats(
            test_project, test_mapper, TaskStatus.INVALIDATED,
            TaskStatus.MAPPED)

        # Admin undos marking task as mapped (test_mapper is given to the function though, as the author of the
        # last_change - compare with MappingServer.undo_mapping() method)
        test_project, test_mapper = StatsService._update_tasks_stats(
            test_project, test_mapper, TaskStatus.MAPPED,
            TaskStatus.INVALIDATED, "undo")

        # Mapper marks task as mapped
        test_project, test_mapper = StatsService._update_tasks_stats(
            test_project, test_mapper, TaskStatus.INVALIDATED,
            TaskStatus.MAPPED)

        # Validator marks task as valid
        test_project, test_validator = StatsService._update_tasks_stats(
            test_project, test_validator, TaskStatus.MAPPED,
            TaskStatus.VALIDATED)

        # Assert
        self.assertEqual(test_project.tasks_mapped, 0)
        self.assertEqual(test_project.tasks_validated, 1)
        self.assertEqual(test_project.tasks_bad_imagery, 0)
        self.assertEqual(test_mapper.tasks_mapped, 2)
        self.assertEqual(test_mapper.tasks_validated, 0)
        self.assertEqual(test_mapper.tasks_invalidated, 0)
        self.assertEqual(test_validator.tasks_mapped, 0)
        self.assertEqual(test_validator.tasks_validated, 1)
        self.assertEqual(test_validator.tasks_invalidated, 1)
        self.assertEqual(test_admin.tasks_mapped, 0)
        self.assertEqual(test_admin.tasks_validated, 0)
        self.assertEqual(test_admin.tasks_invalidated, 0)
示例#19
0
def refresh_project_stats():
    print("Started updating project stats...")
    StatsService.update_all_project_stats()
    print("Project stats updated")