diff --git a/app/org/maproulette/framework/repository/TaskClusterRepository.scala b/app/org/maproulette/framework/repository/TaskClusterRepository.scala index a52269c6..ddc33f52 100644 --- a/app/org/maproulette/framework/repository/TaskClusterRepository.scala +++ b/app/org/maproulette/framework/repository/TaskClusterRepository.scala @@ -120,6 +120,22 @@ class TaskClusterRepository @Inject() (override val db: Database, challengeDAL: SQL(sql.toString).as(this.pointParser.*) } } + // sql query use to select list of ClusteredPoint data + private val selectTaskMarkersSQL = s""" + SELECT tasks.id, tasks.name, tasks.parent_id, c.name, tasks.instruction, tasks.status, tasks.mapped_on, + tasks.completed_time_spent, tasks.completed_by, + tasks.bundle_id, tasks.is_bundle_primary, tasks.cooperative_work_json::TEXT as cooperative_work, + task_review.review_status, task_review.review_requested_by, task_review.reviewed_by, task_review.reviewed_at, + task_review.review_started_at, task_review.meta_review_status, task_review.meta_reviewed_by, + task_review.meta_reviewed_at, task_review.additional_reviewers, + ST_AsGeoJSON(tasks.location) AS location, priority, + CASE WHEN task_review.review_started_at IS NULL + THEN 0 + ELSE EXTRACT(epoch FROM (task_review.reviewed_at - task_review.review_started_at)) END + AS reviewDuration + FROM tasks + ${joinClause.toString()} + """ /** * Querys tasks in a bounding box @@ -141,30 +157,8 @@ class TaskClusterRepository @Inject() (override val db: Database, challengeDAL: ${joinClause.toString()} """).as(SqlParser.int("count").single) - val results = - query - .copy( - order = order, - paging = paging - ) - .build( - s""" - SELECT tasks.id, tasks.name, tasks.parent_id, c.name, tasks.instruction, tasks.status, tasks.mapped_on, - tasks.completed_time_spent, tasks.completed_by, - tasks.bundle_id, tasks.is_bundle_primary, tasks.cooperative_work_json::TEXT as cooperative_work, - task_review.review_status, task_review.review_requested_by, task_review.reviewed_by, task_review.reviewed_at, - task_review.review_started_at, task_review.meta_review_status, task_review.meta_reviewed_by, - task_review.meta_reviewed_at, task_review.additional_reviewers, - ST_AsGeoJSON(tasks.location) AS location, priority, - CASE WHEN task_review.review_started_at IS NULL - THEN 0 - ELSE EXTRACT(epoch FROM (task_review.reviewed_at - task_review.review_started_at)) END - AS reviewDuration - FROM tasks - ${joinClause.toString()} - """ - ) - .as(this.pointParser.*) + val resultsQuery = query.copy(order = order, paging = paging).build(selectTaskMarkersSQL) + val results = resultsQuery.as(this.pointParser.*) (count, results) } @@ -174,6 +168,7 @@ class TaskClusterRepository @Inject() (override val db: Database, challengeDAL: * Querys task markers in a bounding box * * @param query Query to execute + * @param limit Maximum number of results to return * @param c An available connection * @return The list of Tasks found within the bounding box */ @@ -182,26 +177,8 @@ class TaskClusterRepository @Inject() (override val db: Database, challengeDAL: limit: Int ): List[ClusteredPoint] = { this.withMRTransaction { implicit c => - val finalQuery = query.copy(finalClause = s"""LIMIT $limit""") - val results = finalQuery - .build(s""" - SELECT tasks.id, tasks.name, tasks.parent_id, c.name, tasks.instruction, tasks.status, tasks.mapped_on, - tasks.completed_time_spent, tasks.completed_by, - tasks.bundle_id, tasks.is_bundle_primary, tasks.cooperative_work_json::TEXT as cooperative_work, - task_review.review_status, task_review.review_requested_by, task_review.reviewed_by, task_review.reviewed_at, - task_review.review_started_at, task_review.meta_review_status, task_review.meta_reviewed_by, - task_review.meta_reviewed_at, task_review.additional_reviewers, - ST_AsGeoJSON(tasks.location) AS location, priority, - CASE WHEN task_review.review_started_at IS NULL - THEN 0 - ELSE EXTRACT(epoch FROM (task_review.reviewed_at - task_review.review_started_at)) END - AS reviewDuration - FROM tasks - ${joinClause.toString()} - """) - .as(this.pointParser.*) - - results + val finalQuery = query.copy(finalClause = s"LIMIT $limit").build(selectTaskMarkersSQL) + finalQuery.as(this.pointParser.*) } } diff --git a/app/org/maproulette/framework/service/TaskClusterService.scala b/app/org/maproulette/framework/service/TaskClusterService.scala index a49434f8..0a518412 100644 --- a/app/org/maproulette/framework/service/TaskClusterService.scala +++ b/app/org/maproulette/framework/service/TaskClusterService.scala @@ -77,50 +77,8 @@ class TaskClusterService @Inject() (repository: TaskClusterRepository) sort: String = "", orderDirection: String = "ASC" ): (Int, List[ClusteredPoint]) = { - params.location match { - case Some(sl) => // params has location - case None => - params.boundingGeometries match { - case Some(bp) => // params has bounding polygons - case None => - throw new InvalidException( - "Bounding Box (or Bounding Polygons) required to retrieve tasks within a bounding box" - ) - } - } - - var query = - this.filterOutLocked( - user, - this.filterOutDeletedParents(this.filterOnSearchParameters(params)(false)), - ignoreLocked - ) - - query = params.taskParams.excludeTaskIds match { - case Some(excludedIds) if !excludedIds.isEmpty => - //this.appendInWhereClause(whereClause, s"(tasks.id NOT IN (${excludedIds.mkString(",")}))") - query.addFilterGroup( - FilterGroup( - List( - BaseParameter( - Task.FIELD_ID, - excludedIds.mkString(","), - Operator.IN, - negate = true, - useValueDirectly = true, - table = Some("tasks") - ) - ) - ) - ) - case _ => query - } - - this.repository.queryTasksInBoundingBox( - query, - this.getOrder(sort, orderDirection), - paging - ) + val query = buildQueryForBoundingBox(user, params, ignoreLocked) + this.repository.queryTasksInBoundingBox(query, this.getOrder(sort, orderDirection), paging) } /** @@ -136,29 +94,33 @@ class TaskClusterService @Inject() (repository: TaskClusterRepository) params: SearchParameters, limit: Int, ignoreLocked: Boolean = false - ): (List[ClusteredPoint]) = { - params.location match { - case Some(sl) => // params has location - case None => - params.boundingGeometries match { - case Some(bp) => // params has bounding polygons - case None => - throw new InvalidException( - "Bounding Box (or Bounding Polygons) required to retrieve tasks within a bounding box" - ) - } - } + ): List[ClusteredPoint] = { + val query = buildQueryForBoundingBox(user, params, ignoreLocked) + this.repository.queryTaskMarkerDataInBoundingBox(query, limit) + } - var query = - this.filterOutLocked( - user, - this.filterOutDeletedParents(this.filterOnSearchParameters(params)(false)), - ignoreLocked - ) + /** + * Builds a query to retrieve tasks within a bounding box, applying search parameters. + * + * @param user The user making the request + * @param params Search parameters including location or bounding geometries + * @param ignoreLocked Whether to exclude tasks locked by other users + * @return The constructed query + */ + private def buildQueryForBoundingBox( + user: User, + params: SearchParameters, + ignoreLocked: Boolean + ): Query = { + ensureBoundingBox(params) + var query = this.filterOutLocked( + user, + this.filterOutDeletedParents(this.filterOnSearchParameters(params)(false)), + ignoreLocked + ) - query = params.taskParams.excludeTaskIds match { - case Some(excludedIds) if !excludedIds.isEmpty => - //this.appendInWhereClause(whereClause, s"(tasks.id NOT IN (${excludedIds.mkString(",")}))") + params.taskParams.excludeTaskIds match { + case Some(excludedIds) if excludedIds.nonEmpty => query.addFilterGroup( FilterGroup( List( @@ -175,10 +137,19 @@ class TaskClusterService @Inject() (repository: TaskClusterRepository) ) case _ => query } + } - this.repository.queryTaskMarkerDataInBoundingBox( - query, - limit - ) + /** + * Ensures that either a location or bounding geometries are provided in the search parameters. + * + * @param params Search parameters + * @throws InvalidException if neither location nor bounding geometries are provided + */ + private def ensureBoundingBox(params: SearchParameters): Unit = { + if (params.location.isEmpty && params.boundingGeometries.isEmpty) { + throw new InvalidException( + "Bounding Box (or Bounding Polygons) required to retrieve tasks within a bounding box" + ) + } } }