diff --git a/app/controllers/LabelController.scala b/app/controllers/LabelController.scala index 78c9f625b6..e6f652b39c 100644 --- a/app/controllers/LabelController.scala +++ b/app/controllers/LabelController.scala @@ -5,12 +5,14 @@ import com.mohiva.play.silhouette.api.{Environment, Silhouette} import com.mohiva.play.silhouette.impl.authenticators.SessionAuthenticator import controllers.headers.ProvidesHeader import models.label._ +import models.label.{LabelTable, LabelTypeTable, LabelValidationTable, Tag, TagCount, TagTable, DetailedTag} import models.user.User import play.api.libs.json._ import play.api.mvc.Action import scala.concurrent.Future import models.gsv.GSVDataTable import play.api.Logger + /** * Holds the HTTP requests associated with getting label data. * @@ -75,12 +77,24 @@ class LabelController @Inject() (implicit val env: Environment[User, SessionAuth */ def getLabelTags() = Action.async { implicit request => val tags: List[Tag] = TagTable.getTagsForCurrentCity - Future.successful(Ok(JsArray(tags.map { tag => Json.obj( - "tag_id" -> tag.tagId, - "label_type" -> LabelTypeTable.labelTypeIdToLabelType(tag.labelTypeId).get, - "tag" -> tag.tag, - "mutually_exclusive_with" -> tag.mutuallyExclusiveWith - )}))) + val tagCounts: List[TagCount] = LabelTable.getTagCounts() + + val tagCountMap: Map[(String, String), Int] = tagCounts.map(tc => (tc.labelType, tc.tag) -> tc.count).toMap + + val tagsWithCount: Seq[JsObject] = tags.map { tag => + val labelType = LabelTypeTable.labelTypeIdToLabelType(tag.labelTypeId).getOrElse("") + val count = tagCountMap.getOrElse((labelType, tag.tag), 0) + val detailedTag = DetailedTag(tag.tagId, tag.labelTypeId, tag.tag, tag.mutuallyExclusiveWith, count) + + Json.obj( + "tag_id" -> detailedTag.tagId, + "label_type" -> labelType, + "tag" -> detailedTag.tag, + "mutually_exclusive_with" -> detailedTag.mutuallyExclusiveWith, + "count" -> detailedTag.count + ) + } + Future.successful(Ok(JsArray(tagsWithCount))) } } diff --git a/app/controllers/ValidationController.scala b/app/controllers/ValidationController.scala index a0bdd65bb2..8626812d31 100644 --- a/app/controllers/ValidationController.scala +++ b/app/controllers/ValidationController.scala @@ -13,7 +13,7 @@ import formats.json.CommentSubmissionFormats._ import formats.json.LabelFormat import models.amt.AMTAssignmentTable import models.daos.slick.DBTableDefinitions.{DBUser, UserTable} -import models.label.{LabelTable, LabelTypeTable, LabelValidationTable, Tag, TagTable} +import models.label.{LabelTable, LabelTypeTable, LabelValidationTable, Tag, TagCount, TagTable, DetailedTag} import models.label.LabelTable.{AdminValidationData, LabelValidationMetadata} import models.mission.{Mission, MissionSetProgress, MissionTable} import models.region.{Region, RegionTable} @@ -24,6 +24,7 @@ import play.api.libs.json._ import play.api.Logger import play.api.i18n.Lang import play.api.mvc._ + import javax.naming.AuthenticationException import scala.concurrent.Future import scala.util.Try @@ -114,8 +115,21 @@ class ValidationController @Inject() (implicit val env: Environment[User, Sessio // If all went well, load the data for Admin NewValidateBeta with the specified filters. val adminParams: AdminValidateParams = AdminValidateParams(adminVersion=true, parsedLabelTypeId.flatten, userIdsList.map(_.flatten), neighborhoodIdList.map(_.flatten)) val validationData = getDataForValidationPages(request, labelCount=10, "Visit_NewValidateBeta", adminParams) + + // Add counts to corresponding tags val tags: List[Tag] = TagTable.getTagsForCurrentCity - Future.successful(Ok(views.html.newValidateBeta("Sidewalk - NewValidateBeta", request.identity, adminParams, validationData._1, validationData._2, validationData._3, validationData._4.numComplete, validationData._5, validationData._6, tags))) + val tagCounts: List[TagCount] = LabelTable.getTagCounts() + + val tagCountMap: Map[(String, String), Int] = tagCounts.map(tc => (tc.labelType, tc.tag) -> tc.count).toMap + + val tagsWithCounts: List[DetailedTag] = tags.map { tag => + val labelType = LabelTypeTable.labelTypeIdToLabelType(tag.labelTypeId).getOrElse("") + val count = tagCountMap.getOrElse((labelType, tag.tag), 0) + DetailedTag(tag.tagId, tag.labelTypeId, tag.tag, tag.mutuallyExclusiveWith, count) + } + + + Future.successful(Ok(views.html.newValidateBeta("Sidewalk - NewValidateBeta", request.identity, adminParams, validationData._1, validationData._2, validationData._3, validationData._4.numComplete, validationData._5, validationData._6, tagsWithCounts))) } } else { Future.failed(new AuthenticationException("This is a beta currently only open to Admins.")) diff --git a/app/formats/json/LabelFormat.scala b/app/formats/json/LabelFormat.scala index 732f227651..64a399e824 100644 --- a/app/formats/json/LabelFormat.scala +++ b/app/formats/json/LabelFormat.scala @@ -189,7 +189,17 @@ object LabelFormat { implicit val tagWrites: Writes[Tag] = ( (__ \ "tag_id").write[Int] and (__ \ "label_type_id").write[Int] and - (__ \ "tag_name").write[String] and + (__ \ "tag").write[String] and (__ \ "mutually_exclusive_with").writeNullable[String] )(unlift(Tag.unapply)) + + implicit val detailedTagWrites: Writes[DetailedTag] = new Writes[DetailedTag] { + def writes(tag: DetailedTag) = Json.obj( + "tag_id" -> tag.tagId, + "label_type_id" -> tag.labelTypeId, + "tag" -> tag.tag, + "mutually_exclusive_with" -> tag.mutuallyExclusiveWith, + "count" -> tag.count + ) + } } diff --git a/app/models/label/TagTable.scala b/app/models/label/TagTable.scala index 30f0928632..1248408146 100644 --- a/app/models/label/TagTable.scala +++ b/app/models/label/TagTable.scala @@ -10,13 +10,16 @@ import scala.slick.lifted.{ForeignKeyQuery, Index} case class Tag(tagId: Int, labelTypeId: Int, tag: String, mutuallyExclusiveWith: Option[String]) +case class DetailedTag(tagId: Int, labelTypeId: Int, tag: String, mutuallyExclusiveWith: Option[String], count: Int) + class TagTable(tagParam: slick.lifted.Tag) extends Table[Tag](tagParam, "tag") { def tagId: Column[Int] = column[Int]("tag_id", O.PrimaryKey, O.AutoInc) def labelTypeId: Column[Int] = column[Int]("label_type_id") def tag: Column[String] = column[String]("tag") def mutuallyExclusiveWith: Column[Option[String]] = column[Option[String]]("mutually_exclusive_with") - def * = (tagId, labelTypeId, tag, mutuallyExclusiveWith) <> ((Tag.apply _).tupled, Tag.unapply) + def * = (tagId, labelTypeId, tag, mutuallyExclusiveWith) <> ((Tag.apply(_: Int, _: Int, _: String, _: Option[String])).tupled, + { t: Tag => Some((t.tagId, t.labelTypeId, t.tag, t.mutuallyExclusiveWith)) }) def labelType: ForeignKeyQuery[LabelTypeTable, LabelType] = foreignKey("tag_label_type_id_fkey", labelTypeId, TableQuery[LabelTypeTable])(_.labelTypeId) @@ -50,6 +53,7 @@ object TagTable { } } + def selectTagsByLabelType(labelType: String): List[Tag] = db.withSession { implicit session => Cache.getOrElse(s"selectTagsByLabelType($labelType)") { tagTable diff --git a/app/views/newValidateBeta.scala.html b/app/views/newValidateBeta.scala.html index 0829d7d858..82072b2bd8 100644 --- a/app/views/newValidateBeta.scala.html +++ b/app/views/newValidateBeta.scala.html @@ -2,11 +2,11 @@ @import models.amt.AMTAssignmentTable @import play.api.libs.json.JsValue @import controllers.helper.ValidateHelper.AdminValidateParams -@import models.label.Tag +@import models.label.{Tag, DetailedTag} @import play.api.libs.json.Json @import play.api.libs.json.JsArray -@import formats.json.LabelFormat.tagWrites -@(title: String, user: Option[User] = None, adminParams: AdminValidateParams, mission: Option[JsValue], labelList: Option[JsValue], progress: Option[JsValue], missionSetProgress: Int, hasNextMission: Boolean, completedValidations: Int, tagList: List[Tag])(implicit lang: Lang) +@import formats.json.LabelFormat.{tagWrites, detailedTagWrites} +@(title: String, user: Option[User] = None, adminParams: AdminValidateParams, mission: Option[JsValue], labelList: Option[JsValue], progress: Option[JsValue], missionSetProgress: Int, hasNextMission: Boolean, completedValidations: Int, tagList: List[DetailedTag])(implicit lang: Lang) @main(title, Some("/newValidateBeta")) { @navbar(user, Some("/newValidateBeta")) diff --git a/docker-compose-override.yml b/docker-compose-override.yml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/javascripts/SVLabel/src/SVLabel/canvas/ContextMenu.js b/public/javascripts/SVLabel/src/SVLabel/canvas/ContextMenu.js index 09cf6d49cd..84f529e452 100644 --- a/public/javascripts/SVLabel/src/SVLabel/canvas/ContextMenu.js +++ b/public/javascripts/SVLabel/src/SVLabel/canvas/ContextMenu.js @@ -157,7 +157,8 @@ function ContextMenu (uiContextMenu) { url: "/label/tags", type: 'get', success: function(json) { - self.labelTags = json; + // Sort tags by popularity and group mutually exclusive tags using the common utility function + self.labelTags = util.sortTagsByPopularityAndGroupMutuallyExclusive(json, 'count', 'tag', 'mutually_exclusive_with'); }, error: function(result) { throw result; @@ -629,4 +630,4 @@ function ContextMenu (uiContextMenu) { self.enableTagging = enableTagging; self.isTaggingDisabled = isTaggingDisabled; return self; -} +} \ No newline at end of file diff --git a/public/javascripts/SVValidate/src/menu/RightMenu.js b/public/javascripts/SVValidate/src/menu/RightMenu.js index 180233463b..62aa975305 100644 --- a/public/javascripts/SVValidate/src/menu/RightMenu.js +++ b/public/javascripts/SVValidate/src/menu/RightMenu.js @@ -46,18 +46,17 @@ function RightMenu(menuUI) { $tagSelect = $('#select-tag').selectize({ maxItems: 1, placeholder: 'Add more tags here', - labelField: 'tag_name', - valueField: 'tag_name', - searchField: 'tag_name', - sortField: 'popularity', // TODO include data abt frequency of use on this server. + labelField: 'tag', + valueField: 'tag', + searchField: 'tag', onFocus: function() { svv.tracker.push('Click=TagSearch'); }, onItemAdd: function (value, $item) { let currLabel = svv.panorama.getCurrentLabel(); // If the tag is mutually exclusive with another tag that's been added, remove the other tag. const allTags = svv.tagsByLabelType[currLabel.getAuditProperty('labelType')]; - const mutuallyExclusiveWith = allTags.find(t => t.tag_name === value).mutually_exclusive_with; - const currTags = currLabel.getProperty('newTags'); + const mutuallyExclusiveWith = allTags.find(t => t.tag === value).mutually_exclusive_with; + const currTags = currLabel.getProperty('newTags') || []; if (currTags.some(t => t === mutuallyExclusiveWith)) { svv.tracker.push(`TagAutoRemove_Tag="${mutuallyExclusiveWith}"`); currLabel.setProperty('newTags', currTags.filter(t => t !== mutuallyExclusiveWith)); @@ -72,7 +71,7 @@ function RightMenu(menuUI) { render: { option: function(item, escape) { // Add an example image tooltip to the tag. - const translatedTagName = i18next.t('common:tag.' + item.tag_name.replace(/:/g, '-')); + const translatedTagName = i18next.t('common:tag.' + item.tag.replace(/:/g, '-')); let $tagDiv = $(`