Skip to content

Commit

Permalink
Merge branch 'main' of github.com:maproulette/maproulette-backend int…
Browse files Browse the repository at this point in the history
…o AndrewPhilbin/review-table-task-property-filtering
  • Loading branch information
AndrewPhilbin committed Jun 25, 2024
2 parents 1b534c9 + 5949dc0 commit e80eda2
Show file tree
Hide file tree
Showing 26 changed files with 618 additions and 172 deletions.
50 changes: 33 additions & 17 deletions .github/workflows/scala.yml
Original file line number Diff line number Diff line change
@@ -1,35 +1,45 @@
name: Scala CI
name: MapRoulette Backend CI

on:
push:
pull_request:

jobs:
generate_app_secret:
runs-on: ubuntu-latest
outputs:
application_secret: ${{ steps.generate_app_secret.outputs.application_app_secret }}
steps:
- name: Generate Playframework APPLICATION_SECRET
id: generate_app_secret
run: echo "application_app_secret=$(openssl rand -base64 32)" >> "$GITHUB_OUTPUT"

sbt_formatChecks_dependencyTree:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: 11
java-version: 17
distribution: 'temurin'
cache: sbt
- name: Create sbt dependencyTree
env:
CI: true
run: |
sbt -Dsbt.log.format=false 'set asciiGraphWidth := 10000' 'dependencyTree'
- name: Verify scalafix passes
sbt -Dsbt.log.format=false 'set asciiGraphWidth := 10000' 'dependencyTree' 'evicted'
- name: Verify code format checks pass
env:
CI: true
run: |
sbt -Dsbt.log.format=false generateRoutesFile scalafmtCheckAll scalafmtSbtCheck 'scalafixAll --check'
sbt_tests_jacoco:
runs-on: ubuntu-latest
needs: generate_app_secret
services:
postgis11:
image: postgis/postgis:13-3.3
postgis:
image: postgis/postgis:16-3.4
ports:
- 5432:5432
env:
Expand All @@ -38,16 +48,18 @@ jobs:
POSTGRES_PASSWORD: osm
strategy:
matrix:
java: [ 11 ]
java: [ 11, 17 ]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.java }}
distribution: 'temurin'
cache: sbt
- name: Run sbt tests with jacoco analysis
env:
APPLICATION_SECRET: ${{ needs.generate_app_secret.outputs.application_secret }}
CI: true
MR_TEST_DB_NAME: "mr_test"
MR_TEST_DB_USER: "osm"
Expand All @@ -57,8 +69,9 @@ jobs:
build:
runs-on: ubuntu-latest
needs: generate_app_secret
services:
postgis11:
postgis:
image: postgis/postgis:13-3.3
ports:
- 5432:5432
Expand All @@ -68,18 +81,20 @@ jobs:
POSTGRES_PASSWORD: osm
strategy:
matrix:
java: [11]
java: [17]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: 'osmlab/maproulette-java-client'
path: 'java-client'
- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
# https://github.com/actions/setup-java?tab=readme-ov-file#install-multiple-jdks
java-version: ${{ matrix.java }}
distribution: 'temurin'
cache: sbt
- name: Run sbt compile
env:
CI: true
Expand All @@ -95,6 +110,7 @@ jobs:
- name: Run maproulette and the maproulette-java-client integration tests
env:
# maproulette overrides
APPLICATION_SECRET: ${{ needs.generate_app_secret.outputs.application_secret }}
CI: true
SBT_OPTS: "-Xms512M -Xmx1024M -Xss2M -XX:MaxMetaspaceSize=1024M"
MR_SUPER_KEY: 1234
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/AuthController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ class AuthController @Inject() (
case Some(updated) =>
updated.apiKey match {
case Some(api) => {
Future(storeAPIKeyInOSM(user))
Future(storeAPIKeyInOSM(updated))
Ok(api)
}
case None => NoContent
Expand Down
44 changes: 32 additions & 12 deletions app/org/maproulette/framework/controller/TaskBundleController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -237,12 +237,13 @@ class TaskBundleController @Inject() (
*/
def createTaskBundle(): Action[JsValue] = Action.async(bodyParsers.json) { implicit request =>
this.sessionManager.authenticatedRequest { implicit user =>
val name = (request.body \ "name").asOpt[String].getOrElse("")
val name = (request.body \ "name").asOpt[String].getOrElse("")
val primaryId = (request.body \ "primaryId").asOpt[Long]
val taskIds = (request.body \ "taskIds").asOpt[List[Long]] match {
case Some(tasks) => tasks
case None => throw new InvalidException("No task ids provided for task bundle")
}
val bundle = this.serviceManager.taskBundle.createTaskBundle(user, name, taskIds)
val bundle = this.serviceManager.taskBundle.createTaskBundle(user, name, primaryId, taskIds)
Created(Json.toJson(bundle))
}
}
Expand All @@ -253,8 +254,25 @@ class TaskBundleController @Inject() (
* @param id The id for the bundle
* @return Task Bundle
*/
def getTaskBundle(id: Long): Action[AnyContent] = Action.async { implicit request =>
def getTaskBundle(id: Long, lockTasks: Boolean): Action[AnyContent] = Action.async {
implicit request =>
this.sessionManager.authenticatedRequest { implicit user =>
Ok(Json.toJson(this.serviceManager.taskBundle.getTaskBundle(user, id, lockTasks)))
}
}

/**
* Resets the bundle to the tasks provided, and unlock all tasks removed from current bundle
*
* @param bundleId The id of the bundle
* @param taskIds The task ids the bundle will reset to
*/
def resetTaskBundle(
id: Long,
taskIds: List[Long]
): Action[AnyContent] = Action.async { implicit request =>
this.sessionManager.authenticatedRequest { implicit user =>
this.serviceManager.taskBundle.resetTaskBundle(user, id, taskIds)
Ok(Json.toJson(this.serviceManager.taskBundle.getTaskBundle(user, id)))
}
}
Expand All @@ -266,24 +284,26 @@ class TaskBundleController @Inject() (
* @param taskIds List of task ids to remove
* @return Task Bundle
*/
def unbundleTasks(id: Long, taskIds: List[Long]): Action[AnyContent] = Action.async {
implicit request =>
this.sessionManager.authenticatedRequest { implicit user =>
this.serviceManager.taskBundle.unbundleTasks(user, id, taskIds)
Ok(Json.toJson(this.serviceManager.taskBundle.getTaskBundle(user, id)))
}
def unbundleTasks(
id: Long,
taskIds: List[Long],
preventTaskIdUnlocks: List[Long]
): Action[AnyContent] = Action.async { implicit request =>
this.sessionManager.authenticatedRequest { implicit user =>
this.serviceManager.taskBundle.unbundleTasks(user, id, taskIds, preventTaskIdUnlocks)
Ok(Json.toJson(this.serviceManager.taskBundle.getTaskBundle(user, id)))
}
}

/**
* Delete bundle.
*
* @param id The id for the bundle
* @param primaryId optional task id to no unlock after deleting this bundle
*/
def deleteTaskBundle(id: Long, primaryId: Option[Long] = None): Action[AnyContent] =
def deleteTaskBundle(id: Long): Action[AnyContent] =
Action.async { implicit request =>
this.sessionManager.authenticatedRequest { implicit user =>
this.serviceManager.taskBundle.deleteTaskBundle(user, id, primaryId)
this.serviceManager.taskBundle.deleteTaskBundle(user, id)
Ok
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ class TaskReviewController @Inject() (
*/
def extractReviewTableData(
taskId: String,
featureId: String,
reviewStatus: String,
mapper: String,
challengeId: String,
Expand Down Expand Up @@ -303,6 +304,7 @@ class TaskReviewController @Inject() (
val projectIdFilter = parseParameterLong(projectId)
val challengeIdsFilter = parseParameterLong(challengeId)
val taskIdFilter = parseParameterLong(taskId).map(_.head)
val taskFeatureIdFilter = parseParameterString(featureId).map(_.head)
val mappedOnFilter = parseParameterString(mappedOn).map(_.head)
val mapperFilter = parseParameterString(mapper).map(_.head)
val metaReviewedByFilter = parseParameterString(metaReviewedBy).map(_.head)
Expand All @@ -318,6 +320,7 @@ class TaskReviewController @Inject() (
),
taskParams = params.taskParams.copy(
taskId = taskIdFilter,
taskFeatureId = taskFeatureIdFilter,
taskStatus = statusFilter,
taskReviewStatus = reviewStatusFilter,
taskPriorities = priorityFilter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ trait ReviewSearchMixin extends SearchParametersMixin {
.addFilterGroup(this.filterLocation(searchParameters))
.addFilterGroup(this.filterProjectSearch(searchParameters))
.addFilterGroup(this.filterTaskId(searchParameters))
.addFilterGroup(this.filterTaskFeatureId(searchParameters))
.addFilterGroup(this.filterPriority(searchParameters))
.addFilterGroup(this.filterTaskTags(searchParameters))
.addFilterGroup(this.filterReviewDate(searchParameters))
Expand Down
36 changes: 36 additions & 0 deletions app/org/maproulette/framework/mixins/SearchParametersMixin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ trait SearchParametersMixin {
this.filterBounding(params),
this.filterTaskStatus(params),
this.filterTaskId(params),
this.filterTaskFeatureId(params),
this.filterProjectSearch(params),
this.filterTaskReviewStatus(params),
this.filterMetaReviewStatus(params),
Expand All @@ -39,6 +40,7 @@ trait SearchParametersMixin {
this.filterChallengeStatus(params),
this.filterChallengeRequiresLocal(params),
this.filterBoundingGeometries(params),
this.filterBundleId(params),
// For efficiency can only query on task properties with a parent challenge id
this.filterTaskProps(params),
this.filterChallenges(params),
Expand Down Expand Up @@ -305,6 +307,40 @@ trait SearchParametersMixin {
}
}

/**
* Filters by tasks.name
* @param params with inverting on 'fid'
*/
def filterTaskFeatureId(params: SearchParameters): FilterGroup = {
params.taskParams.taskFeatureId match {
case Some(fid) =>
FilterGroup(
List(
CustomParameter(
s"LOWER(TRIM(${Task.TABLE}.${Task.FIELD_NAME}::TEXT)) LIKE LOWER('%${fid.trim}%')"
)
)
)
case None => FilterGroup(List())
}
}

/**
* Filters by bundle id
* @param params with inverting on 'bid'
*/
def filterBundleId(params: SearchParameters): FilterGroup = {
params.taskParams.bundleId match {
case Some(bid) =>
FilterGroup(
List(
CustomParameter(s"${Task.TABLE}.${Task.FIELD_BUNDLE_ID} = $bid")
)
)
case _ => FilterGroup(List())
}
}

/**
* Filters by tasks.priority
* @param params with inverting on 'priorities'
Expand Down
7 changes: 4 additions & 3 deletions app/org/maproulette/framework/model/User.scala
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ object User extends CommonField {
implicit val osmReads: Reads[OSMProfile] = Json.reads[OSMProfile]
implicit val searchResultWrites: Writes[UserSearchResult] = Json.writes[UserSearchResult]
implicit val projectManagerWrites: Writes[ProjectManager] = Json.writes[ProjectManager]
val logger = LoggerFactory.getLogger(this.getClass)

val TABLE = "users"
val FIELD_OSM_ID = "osm_id"
Expand Down Expand Up @@ -416,9 +417,9 @@ object User extends CommonField {
user.copy(apiKey = decryptedAPIKey)
} catch {
case _: BadPaddingException | _: IllegalBlockSizeException =>
LoggerFactory
.getLogger(this.getClass)
.debug("Invalid key found, could be that the application secret on server changed.")
logger.debug(
"Invalid key found, could be that the application secret on server changed."
)
user
case e: Throwable => throw e
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,12 @@ class ChallengeRepository @Inject() (override val db: Database) extends Reposito
SQL(
s"""
|UPDATE challenges
|SET completion_percentage = new_completion_percentage
|SET completion_percentage = new_completion_percentage, tasks_remaining = new_tasks_remaining
|FROM (
| SELECT
| challenges.id AS challenge_id,
| completed_task_counts.completed_tasks * 100 / total_task_counts.total_tasks AS new_completion_percentage
| completed_task_counts.completed_tasks * 100 / total_task_counts.total_tasks AS new_completion_percentage,
| total_task_counts.total_tasks - completed_task_counts.completed_tasks AS new_tasks_remaining
| FROM
| challenges
| JOIN (
Expand Down
Loading

0 comments on commit e80eda2

Please sign in to comment.