diff --git a/app/org/maproulette/framework/controller/TaskReviewController.scala b/app/org/maproulette/framework/controller/TaskReviewController.scala index eac57530..660f83bc 100644 --- a/app/org/maproulette/framework/controller/TaskReviewController.scala +++ b/app/org/maproulette/framework/controller/TaskReviewController.scala @@ -604,15 +604,21 @@ class TaskReviewController @Inject() ( /** * Returns a list of challenges that have reviews/review requests. * - * @param reviewTasksType The type of reviews (1: To Be Reviewed, 2: User's reviewed Tasks, 3: All reviewed by users 4: meta review tasks) - * @param tStatus The task statuses to include - * @param excludeOtherReviewers Whether tasks completed by other reviewers should be included + * @param reviewTasksType The type of reviews (1: To Be Reviewed, 2: User's reviewed Tasks, 3: All reviewed by users 4: meta review tasks) + * @param tStatus The task statuses to include + * @param excludeOtherReviewers Whether tasks completed by other reviewers should be included + * @param challengeSearchQuery Query string for filtering challenges + * @param projectSearchQuery Query string for filtering projects + * @param limit Number of items per page + * @param page Page number * @return JSON challenge list */ def listChallenges( reviewTasksType: Int, tStatus: String, excludeOtherReviewers: Boolean = false, + challengeSearchQuery: String = "", + projectSearchQuery: String = "", limit: Int, page: Int ): Action[AnyContent] = @@ -623,11 +629,14 @@ class TaskReviewController @Inject() ( case _ => None } + // Filter challenges based on search query val challenges = this.serviceManager.challengeListing.withReviewList( reviewTasksType, user, taskStatus, excludeOtherReviewers, + challengeSearchQuery, + projectSearchQuery, Paging(limit, page) ) diff --git a/app/org/maproulette/framework/service/ChallengeListingService.scala b/app/org/maproulette/framework/service/ChallengeListingService.scala index 27846d11..68301fb9 100644 --- a/app/org/maproulette/framework/service/ChallengeListingService.scala +++ b/app/org/maproulette/framework/service/ChallengeListingService.scala @@ -26,13 +26,18 @@ class ChallengeListingService @Inject() ( def retrieve(parentId: Long): Option[ChallengeListing] = this.query(Query.simple(List(BaseParameter(Challenge.FIELD_PARENT_ID, parentId)))).headOption + def emptyFilterGroup: FilterGroup = FilterGroup(List.empty) + /** * Returns a list of challenges that have reviews/review requests. * - * @param reviewTasksType The type of reviews (1: To Be Reviewed, 2: User's reviewed Tasks, 3: All reviewed by users) - * @param user The user making request (for challenge permission visibility) - * @param taskStatus The task statuses to include + * @param reviewTasksType The type of reviews (1: To Be Reviewed, 2: User's reviewed Tasks, 3: All reviewed by users) + * @param user The user making request (for challenge permission visibility) + * @param taskStatus The task statuses to include * @param excludeOtherReviewers Whether tasks completed by other reviewers should be included + * @param challengeSearchQuery Search query for filtering challenges + * @param projectSearchQuery Search query for filtering projects + * @param paging Paging information * @return A list of children listing objects */ def withReviewList( @@ -40,86 +45,120 @@ class ChallengeListingService @Inject() ( user: User, taskStatus: Option[List[Int]] = None, excludeOtherReviewers: Boolean = false, + challengeSearchQuery: String = "", + projectSearchQuery: String = "", paging: Paging = Paging() ): List[ChallengeListing] = { + val challengeNameParameter = + if (challengeSearchQuery.nonEmpty) { + Some( + BaseParameter( + Challenge.FIELD_NAME, + SQLUtils.search(challengeSearchQuery), + Operator.ILIKE, + table = Some(Challenge.TABLE) + ) + ) + } else None + + val projectNameParameter = + if (projectSearchQuery.nonEmpty) { + Some( + BaseParameter( + Project.FIELD_DISPLAY_NAME, + SQLUtils.search(projectSearchQuery), + Operator.ILIKE, + table = Some(Project.TABLE) + ) + ) + } else None + + val parameters = List(challengeNameParameter, projectNameParameter).flatten + val filterGroup = + if (parameters.nonEmpty) FilterGroup(parameters) + else emptyFilterGroup + + val taskReviewFilterGroup = FilterGroup( + List( + // Has a task review + BaseParameter( + TaskReview.FIELD_ID, + "", + Operator.NULL, + negate = true, + table = Some(TaskReview.TABLE) + ), + // Exclude unnecessary review status + BaseParameter( + "review_status", + Task.REVIEW_STATUS_UNNECESSARY, + Operator.EQ, + negate = true, + table = Some(TaskReview.TABLE) + ), + // Task Status in list if given a list of task statuses + FilterParameter.conditional( + "status", + taskStatus.getOrElse(List.empty), + Operator.IN, + includeOnlyIfTrue = taskStatus.getOrElse(List.empty).nonEmpty, + table = Some("tasks") + ), + // review_requested_by != user.id unless a super user + // to be reviewed tasks (review type = 1) + FilterParameter.conditional( + TaskReview.FIELD_REVIEW_REQUESTED_BY, + user.id, + negate = true, + includeOnlyIfTrue = (reviewTasksType == 1) && !permission.isSuperUser(user), + table = Some(TaskReview.TABLE) + ), + // reviewed_by == user.id for 'tasks reviewed by me' (review type = 3) + FilterParameter.conditional( + TaskReview.FIELD_REVIEWED_BY, + user.id, + includeOnlyIfTrue = reviewTasksType == 2, + table = Some(TaskReview.TABLE) + ), + // reviewed_by == user.id for 'my reviewed tasks' (review type = 2) + FilterParameter.conditional( + TaskReview.FIELD_REVIEW_REQUESTED_BY, + user.id, + includeOnlyIfTrue = reviewTasksType == 3, + table = Some(TaskReview.TABLE) + ), + // review status = requested or disputed if reviewTasksType = 1 + FilterParameter.conditional( + TaskReview.FIELD_REVIEW_STATUS, + List(Task.REVIEW_STATUS_REQUESTED, Task.REVIEW_STATUS_DISPUTED), + Operator.IN, + includeOnlyIfTrue = reviewTasksType == 1, + table = Some(TaskReview.TABLE) + ) + ) + ) + + val excludeOtherReviewersFilterGroup = FilterGroup( + List( + BaseParameter( + TaskReview.FIELD_REVIEWED_BY, + "", + Operator.NULL, + table = Some(TaskReview.TABLE) + ), + BaseParameter(TaskReview.FIELD_REVIEWED_BY, user.id, table = Some(TaskReview.TABLE)) + ), + OR(), + excludeOtherReviewers && reviewTasksType == 1 + ) + val filter = Filter( List( - FilterGroup( - List( - // Has a task review - BaseParameter( - TaskReview.FIELD_ID, - "", - Operator.NULL, - negate = true, - table = Some(TaskReview.TABLE) - ), - // Exclude unnecessary review status - BaseParameter( - "review_status", - Task.REVIEW_STATUS_UNNECESSARY, - Operator.EQ, - negate = true, - table = Some(TaskReview.TABLE) - ), - // Task Status in list if given a list of task statuses - FilterParameter.conditional( - "status", - taskStatus.getOrElse(List.empty), - Operator.IN, - includeOnlyIfTrue = taskStatus.nonEmpty, - table = Some("tasks") - ), - // review_requested_by != user.id unless a super user - // to be reviewed tasks (review type = 1) - FilterParameter.conditional( - TaskReview.FIELD_REVIEW_REQUESTED_BY, - user.id, - negate = true, - includeOnlyIfTrue = (reviewTasksType == 1) && !permission.isSuperUser(user), - table = Some(TaskReview.TABLE) - ), - // reviewed_by == user.id for 'tasks reviewed by me' (review type = 3) - FilterParameter.conditional( - TaskReview.FIELD_REVIEWED_BY, - user.id, - includeOnlyIfTrue = reviewTasksType == 2, - table = Some(TaskReview.TABLE) - ), - // reviewed_by == user.id for 'my reviewed tasks' (review type = 2) - FilterParameter.conditional( - TaskReview.FIELD_REVIEW_REQUESTED_BY, - user.id, - includeOnlyIfTrue = reviewTasksType == 3, - table = Some(TaskReview.TABLE) - ), - // review status = requested or disputed if reviewTasksType = 1 - FilterParameter.conditional( - TaskReview.FIELD_REVIEW_STATUS, - List(Task.REVIEW_STATUS_REQUESTED, Task.REVIEW_STATUS_DISPUTED), - Operator.IN, - includeOnlyIfTrue = reviewTasksType == 1, - table = Some(TaskReview.TABLE) - ) - ) - ), - // Check project/challenge visiblity + filterGroup, + taskReviewFilterGroup, challengeService.challengeVisibilityFilter(user), - // reviewed_by is empty or user.id if excludeOtherReviewers - FilterGroup( - List( - BaseParameter( - TaskReview.FIELD_REVIEWED_BY, - "", - Operator.NULL, - table = Some(TaskReview.TABLE) - ), - BaseParameter(TaskReview.FIELD_REVIEWED_BY, user.id, table = Some(TaskReview.TABLE)) - ), - OR(), - excludeOtherReviewers && reviewTasksType == 1 - ) + excludeOtherReviewersFilterGroup ) ) this.query( diff --git a/conf/v2_route/challenge.api b/conf/v2_route/challenge.api index c568a201..c71eb799 100644 --- a/conf/v2_route/challenge.api +++ b/conf/v2_route/challenge.api @@ -539,6 +539,12 @@ POST /challenges/bulkArchive @org.maproulette.controllers # - name: excludeOtherReviewers # in: query # description: Exclude reviews by completed by other reviewers (default true) +# - name: challengeSearchQuery +# in: query +# description: Filter for challenge names that include this value +# - name: projectSearchQuery +# in: query +# description: Filter for project names that include this value # - name: limit # in: query # description: Limit the number of results returned in the response. Default value is 10. @@ -546,7 +552,7 @@ POST /challenges/bulkArchive @org.maproulette.controllers # in: query # description: Used in conjunction with the limit parameter to page through X number of responses. Default value is 0, ie. first page. ### -GET /review/challenges @org.maproulette.framework.controller.TaskReviewController.listChallenges(reviewTasksType:Int, tStatus:String ?= "", excludeOtherReviewers:Boolean ?= false, limit:Int ?= 10, page:Int ?= 0) +GET /review/challenges @org.maproulette.framework.controller.TaskReviewController.listChallenges(reviewTasksType:Int, tStatus:String ?= "", excludeOtherReviewers:Boolean ?= false, challengeSearchQuery ?= "", projectSearchQuery ?= "", limit:Int ?= 10, page:Int ?= 0) ### # tags: [ Challenge ] # summary: List all the Challenges Tasks. diff --git a/test/org/maproulette/framework/service/ChallengeListingServiceSpec.scala b/test/org/maproulette/framework/service/ChallengeListingServiceSpec.scala index 1ccb7b8d..ce9a4d6d 100644 --- a/test/org/maproulette/framework/service/ChallengeListingServiceSpec.scala +++ b/test/org/maproulette/framework/service/ChallengeListingServiceSpec.scala @@ -32,9 +32,9 @@ class ChallengeListingServiceSpec(implicit val application: Application) extends } "fetch challenges with reviews" taggedAs (ChallengeListingTag) in { - val challenges = this.service.withReviewList(1, User.superUser) - challenges.size mustEqual 1 - } + val challenges = this.service.withReviewList(1, User.superUser) + challenges.size mustEqual 1 +} } override implicit val projectTestName: String = "ChallengeListingSpecProject"