Skip to content

Commit

Permalink
Added an optional JSON field taskWidgetLayout to the Challenge ta…
Browse files Browse the repository at this point in the history
…ble (#1067)

- Added an optional JSON field `taskWidgetLayout` to the `Challenge` model to store configurations and data related to task widget layout.
- Inserted `taskWidgetLayout` into JSON bodies in the `ChallengeController`.
- Updated GraphQL schema, `MRSchemaTypes`, to include the new field.
- Modified the `ChallengeRepository` and `ChallengeDAL` to handle CRUD operations related to the new field.
- Added the corresponding database evolution script to create the new column in the `challenges` table.
---------
Co-authored-by: Lucas Burson <[email protected]>
  • Loading branch information
CollinBeczak authored Oct 16, 2023
1 parent dcc34bf commit d9239b7
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,7 @@ class ChallengeController @Inject() (
Utils.insertIntoJson(jsonBody, "reviewSetting", Challenge.REVIEW_SETTING_NOT_REQUIRED)(
IntWrites
)
jsonBody = Utils.insertIntoJson(jsonBody, "taskWidgetLayout", "")(StringWrites)
jsonBody = Utils.insertIntoJson(jsonBody, "updateTasks", false)(BooleanWrites)
jsonBody = Utils.insertIntoJson(jsonBody, "changesetUrl", false)(BooleanWrites)
// if we can't find the parent ID, just use the user's default project instead
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,17 @@ trait MRSchemaTypes {
implicit val ChallengePriorityType: ObjectType[Unit, ChallengePriority] =
deriveObjectType[Unit, ChallengePriority](ObjectTypeName("ChallengePriority"))
implicit val ChallengeExtraType: ObjectType[Unit, ChallengeExtra] =
deriveObjectType[Unit, ChallengeExtra](ObjectTypeName("ChallengeExtra"))
deriveObjectType[Unit, ChallengeExtra](
ObjectTypeName("ChallengeExtra"),
ReplaceField(
"taskWidgetLayout",
Field(
"taskWidgetLayout",
StringType,
resolve = _.value.taskWidgetLayout.getOrElse("").toString
)
)
)
// Comment Types
implicit val CommentType: ObjectType[Unit, Comment] =
deriveObjectType[Unit, Comment](ObjectTypeName("Comment"))
Expand Down
1 change: 1 addition & 0 deletions app/org/maproulette/framework/model/Challenge.scala
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ case class ChallengeExtra(
taskBundleIdProperty: Option[String] = None,
isArchived: Boolean = false,
reviewSetting: Int = Challenge.REVIEW_SETTING_NOT_REQUIRED,
taskWidgetLayout: Option[JsValue] = None,
systemArchivedAt: Option[DateTime] = None,
presets: Option[List[String]] = None
) extends DefaultWrites
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ package org.maproulette.framework.repository
import java.sql.Connection
import anorm.SqlParser._
import anorm.{RowParser, SQL, ~}

import anorm.postgresql.jsValueColumn
import javax.inject.{Inject, Singleton}
import org.apache.commons.lang3.StringUtils
import org.joda.time.DateTime
import org.maproulette.framework.model._
import org.maproulette.framework.psql.{GroupField, Grouping, OR, Query}
import org.maproulette.framework.psql.filter._
import play.api.db.Database
import play.api.libs.json.JsValue

/**
* The challenge repository handles all the querying with the databases related to challenge objects
Expand Down Expand Up @@ -262,14 +263,15 @@ object ChallengeRepository {
get[Option[List[Long]]]("virtual_parent_ids") ~
get[Boolean]("challenges.is_archived") ~
get[Int]("challenges.review_setting") ~
get[Option[JsValue]]("challenges.task_widget_layout") ~
get[Option[DateTime]]("challenges.system_archived_at") map {
case id ~ name ~ created ~ modified ~ description ~ infoLink ~ ownerId ~ parentId ~ instruction ~
difficulty ~ blurb ~ enabled ~ featured ~ cooperativeType ~ popularity ~ checkin_comment ~
checkin_source ~ overpassql ~ overpassTargetType ~ remoteGeoJson ~ status ~ statusMessage ~ defaultPriority ~ highPriorityRule ~
mediumPriorityRule ~ lowPriorityRule ~ defaultZoom ~ minZoom ~ maxZoom ~ defaultBasemap ~ defaultBasemapId ~
customBasemap ~ updateTasks ~ exportableProperties ~ osmIdProperty ~ taskBundleIdProperty ~ preferredTags ~ preferredReviewTags ~
limitTags ~ limitReviewTags ~ taskStyles ~ lastTaskRefresh ~ dataOriginDate ~ requiresLocal ~ location ~ bounding ~
deleted ~ virtualParents ~ isArchived ~ reviewSetting ~ systemArchivedAt =>
deleted ~ virtualParents ~ isArchived ~ reviewSetting ~ taskWidgetLayout ~ systemArchivedAt =>
val hpr = highPriorityRule match {
case Some(c) if StringUtils.isEmpty(c) || StringUtils.equals(c, "{}") => None
case r => r
Expand All @@ -282,6 +284,7 @@ object ChallengeRepository {
case Some(c) if StringUtils.isEmpty(c) || StringUtils.equals(c, "{}") => None
case r => r
}

new Challenge(
id,
name,
Expand Down Expand Up @@ -325,6 +328,7 @@ object ChallengeRepository {
taskBundleIdProperty,
isArchived,
reviewSetting,
taskWidgetLayout,
systemArchivedAt
),
status,
Expand Down
34 changes: 24 additions & 10 deletions app/org/maproulette/models/dal/ChallengeDAL.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
package org.maproulette.models.dal

import java.sql.Connection

import anorm.SqlParser._
import anorm._
import anorm.postgresql.{asJson, jsValueColumn}

import javax.inject.{Inject, Singleton}
import org.apache.commons.lang3.StringUtils
import org.joda.time.DateTime
Expand All @@ -17,9 +18,9 @@ import org.maproulette.data.{Actions, ChallengeType, ProjectType}
import org.maproulette.exception.{InvalidException, NotFoundException, UniqueViolationException}
import org.maproulette.framework.model._
import org.maproulette.framework.repository.{
ChallengeListingRepository,
ProjectRepository,
TaskRepository,
ChallengeListingRepository
TaskRepository
}
import org.maproulette.framework.service.{ServiceManager, TagService}
import org.maproulette.models.dal.mixin.{OwnerMixin, TagDALMixin}
Expand Down Expand Up @@ -121,6 +122,7 @@ class ChallengeDAL @Inject() (
get[Boolean]("deleted") ~
get[Boolean]("challenges.is_archived") ~
get[Int]("challenges.review_setting") ~
get[Option[JsValue]]("challenges.task_widget_layout") ~
get[Option[Int]]("challenges.completion_percentage") ~
get[Option[Int]]("challenges.tasks_remaining") map {
case id ~ name ~ created ~ modified ~ description ~ infoLink ~ ownerId ~ parentId ~ instruction ~
Expand All @@ -130,7 +132,7 @@ class ChallengeDAL @Inject() (
minZoom ~ maxZoom ~ defaultBasemap ~ defaultBasemapId ~ customBasemap ~ updateTasks ~
exportableProperties ~ osmIdProperty ~ taskBundleIdProperty ~ preferredTags ~ preferredReviewTags ~
limitTags ~ limitReviewTags ~ taskStyles ~ lastTaskRefresh ~ dataOriginDate ~ location ~ bounding ~
requiresLocal ~ deleted ~ isArchived ~ reviewSetting ~ completionPercentage ~ tasksRemaining =>
requiresLocal ~ deleted ~ isArchived ~ reviewSetting ~ taskWidgetLayout ~ completionPercentage ~ tasksRemaining =>
val hpr = highPriorityRule match {
case Some(c) if StringUtils.isEmpty(c) || StringUtils.equals(c, "{}") => None
case r => r
Expand All @@ -143,6 +145,7 @@ class ChallengeDAL @Inject() (
case Some(c) if StringUtils.isEmpty(c) || StringUtils.equals(c, "{}") => None
case r => r
}

new Challenge(
id,
name,
Expand Down Expand Up @@ -185,7 +188,8 @@ class ChallengeDAL @Inject() (
taskStyles,
taskBundleIdProperty,
isArchived,
reviewSetting
reviewSetting,
taskWidgetLayout
),
status,
statusMessage,
Expand Down Expand Up @@ -255,6 +259,7 @@ class ChallengeDAL @Inject() (
get[Option[List[String]]]("presets") ~
get[Boolean]("challenges.is_archived") ~
get[Int]("challenges.review_setting") ~
get[Option[JsValue]]("challenges.task_widget_layout") ~
get[Option[DateTime]]("challenges.system_archived_at") ~
get[Option[Int]]("challenges.completion_percentage") ~
get[Option[Int]]("challenges.tasks_remaining") map {
Expand All @@ -266,7 +271,7 @@ class ChallengeDAL @Inject() (
customBasemap ~ updateTasks ~ exportableProperties ~ osmIdProperty ~ taskBundleIdProperty ~ preferredTags ~
preferredReviewTags ~ limitTags ~ limitReviewTags ~ taskStyles ~ lastTaskRefresh ~
dataOriginDate ~ location ~ bounding ~ requiresLocal ~ deleted ~ virtualParents ~
presets ~ isArchived ~ reviewSetting ~ systemArchivedAt ~ completionPercentage ~ tasksRemaining =>
presets ~ isArchived ~ reviewSetting ~ taskWidgetLayout ~ systemArchivedAt ~ completionPercentage ~ tasksRemaining =>
val hpr = highPriorityRule match {
case Some(c) if StringUtils.isEmpty(c) || StringUtils.equals(c, "{}") => None
case r => r
Expand All @@ -279,6 +284,7 @@ class ChallengeDAL @Inject() (
case Some(c) if StringUtils.isEmpty(c) || StringUtils.equals(c, "{}") => None
case r => r
}

new Challenge(
id,
name,
Expand Down Expand Up @@ -322,6 +328,7 @@ class ChallengeDAL @Inject() (
taskBundleIdProperty,
isArchived,
reviewSetting,
taskWidgetLayout,
systemArchivedAt,
presets
),
Expand Down Expand Up @@ -474,7 +481,7 @@ class ChallengeDAL @Inject() (
medium_priority_rule, low_priority_rule, default_zoom, min_zoom, max_zoom,
default_basemap, default_basemap_id, custom_basemap, updatetasks, exportable_properties,
osm_id_property, task_bundle_id_property, last_task_refresh, data_origin_date, preferred_tags, preferred_review_tags,
limit_tags, limit_review_tags, task_styles, requires_local, is_archived, review_setting)
limit_tags, limit_review_tags, task_styles, requires_local, is_archived, review_setting, task_widget_layout)
VALUES (${challenge.name}, ${challenge.general.owner}, ${challenge.general.parent}, ${challenge.general.difficulty},
${challenge.description}, ${challenge.infoLink}, ${challenge.general.blurb}, ${challenge.general.instruction},
${challenge.general.enabled}, ${challenge.general.featured},
Expand All @@ -488,8 +495,9 @@ class ChallengeDAL @Inject() (
${challenge.dataOriginDate.getOrElse(DateTime.now()).toString}::timestamptz,
${challenge.extra.preferredTags}, ${challenge.extra.preferredReviewTags}, ${challenge.extra.limitTags},
${challenge.extra.limitReviewTags}, ${challenge.extra.taskStyles}, ${challenge.general.requiresLocal}, ${challenge.extra.isArchived},
${challenge.extra.reviewSetting})
ON CONFLICT(parent_id, LOWER(name)) DO NOTHING RETURNING #${this.retrieveColumns}"""
${challenge.extra.reviewSetting},
${asJson(challenge.extra.taskWidgetLayout.getOrElse(Json.parse("{}")))}
) ON CONFLICT(parent_id, LOWER(name)) DO NOTHING RETURNING #${this.retrieveColumns}"""
.as(this.parser.*)
.headOption
}
Expand Down Expand Up @@ -675,6 +683,10 @@ class ChallengeDAL @Inject() (
.asOpt[Int]
.getOrElse(cachedItem.extra.reviewSetting)

val taskWidgetLayout = (updates \ "taskWidgetLayout")
.asOpt[JsValue]
.getOrElse(cachedItem.extra.taskWidgetLayout.getOrElse(Json.parse("{}")))

val presets: List[String] = (updates \ "presets")
.asOpt[List[String]]
.getOrElse(cachedItem.extra.presets.getOrElse(null))
Expand Down Expand Up @@ -704,7 +716,9 @@ class ChallengeDAL @Inject() (
custom_basemap = $customBasemap, updatetasks = $updateTasks, exportable_properties = $exportableProperties,
osm_id_property = $osmIdProperty, task_bundle_id_property = $taskBundleIdProperty, preferred_tags = $preferredTags, preferred_review_tags = $preferredReviewTags,
limit_tags = $limitTags, limit_review_tags = $limitReviewTags, task_styles = $taskStyles,
requires_local = $requiresLocal, is_archived = $isArchived, review_setting = $reviewSetting
requires_local = $requiresLocal, is_archived = $isArchived, review_setting = $reviewSetting, task_widget_layout = ${asJson(
taskWidgetLayout
)}
WHERE id = $id RETURNING #${this.retrieveColumns}""".as(parser.*).headOption

updatedChallenge match {
Expand Down
10 changes: 10 additions & 0 deletions conf/evolutions/default/93.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- --- !Ups
ALTER TABLE IF EXISTS challenges
ADD COLUMN task_widget_layout jsonb NOT NULL DEFAULT '{}'::jsonb;

COMMENT ON COLUMN challenges.task_widget_layout IS
'The challenges.task_widget_layout is json that the GUI uses as a "suggested layout" when displaying the Task Completion page.';

-- --- !Downs
ALTER TABLE IF EXISTS challenges
DROP COLUMN task_widget_layout;

0 comments on commit d9239b7

Please sign in to comment.