From be37784cc683531e31df696a5af19addcef52994 Mon Sep 17 00:00:00 2001 From: Florian M Date: Fri, 8 Jul 2022 12:48:34 +0200 Subject: [PATCH 1/5] [WIP] make annotation layer names unique and url-safe --- app/controllers/AnnotationController.scala | 16 ++++++++-------- app/models/annotation/Annotation.scala | 2 +- frontend/javascripts/messages.ts | 4 +++- .../model/accessors/volumetracing_accessor.ts | 2 +- .../modals/add_volume_layer_modal.tsx | 6 +++--- tools/postgres/schema.sql | 4 ++-- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/app/controllers/AnnotationController.scala b/app/controllers/AnnotationController.scala index bea2542623b..e246a97ff41 100755 --- a/app/controllers/AnnotationController.scala +++ b/app/controllers/AnnotationController.scala @@ -5,27 +5,27 @@ import com.mohiva.play.silhouette.api.Silhouette import com.scalableminds.util.accesscontext.{DBAccessContext, GlobalAccessContext} import com.scalableminds.util.geometry.BoundingBox import com.scalableminds.util.tools.{Fox, FoxImplicits} -import com.scalableminds.webknossos.tracingstore.tracings.{TracingIds, TracingType} import com.scalableminds.webknossos.tracingstore.tracings.volume.ResolutionRestrictions -import io.swagger.annotations.{Api, ApiOperation, ApiParam, ApiResponse, ApiResponses} +import com.scalableminds.webknossos.tracingstore.tracings.{TracingIds, TracingType} +import io.swagger.annotations._ +import javax.inject.Inject +import models.analytics.{AnalyticsService, CreateAnnotationEvent, OpenAnnotationEvent} +import models.annotation.AnnotationLayerType.AnnotationLayerType import models.annotation.AnnotationState.Cancelled import models.annotation._ import models.binary.{DataSetDAO, DataSetService} +import models.organization.OrganizationDAO import models.project.ProjectDAO import models.task.TaskDAO import models.team.{TeamDAO, TeamService} import models.user.time._ import models.user.{User, UserDAO, UserService} +import oxalis.mail.{MailchimpClient, MailchimpTag} import oxalis.security.{URLSharing, WkEnv} import play.api.i18n.{Messages, MessagesProvider} import play.api.libs.json.{JsArray, _} import play.api.mvc.{Action, AnyContent, PlayBodyParsers} import utils.{ObjectId, WkConf} -import javax.inject.Inject -import models.analytics.{AnalyticsService, CreateAnnotationEvent, OpenAnnotationEvent} -import models.annotation.AnnotationLayerType.AnnotationLayerType -import models.organization.OrganizationDAO -import oxalis.mail.{MailchimpClient, MailchimpTag} import scala.concurrent.ExecutionContext import scala.concurrent.duration._ @@ -384,7 +384,7 @@ class AnnotationController @Inject()( annotation <- provider.provideAnnotation(typ, id, request.identity) ~> NOT_FOUND restrictions <- provider.restrictionsFor(typ, id) ?~> "restrictions.notFound" ~> NOT_FOUND _ <- restrictions.allowUpdate(request.identity) ?~> "notAllowed" ~> FORBIDDEN - newLayerName = (request.body \ "name").asOpt[String] + newLayerName = (request.body \ "name").as[String] _ <- annotationLayerDAO.updateName(annotation._id, tracingId, newLayerName) ?~> "annotation.edit.failed" } yield JsonOk(Messages("annotation.edit.success")) } diff --git a/app/models/annotation/Annotation.scala b/app/models/annotation/Annotation.scala index b72d47395f1..def9fc8b968 100755 --- a/app/models/annotation/Annotation.scala +++ b/app/models/annotation/Annotation.scala @@ -125,7 +125,7 @@ class AnnotationLayerDAO @Inject()(SQLClient: SQLClient)(implicit ec: ExecutionC sqlu"update webknossos.annotation_layers set tracingId = $newTracingId where _annotation = $annotationId and tracingId = $oldTracingId") } yield () - def updateName(annotationId: ObjectId, tracingId: String, newName: Option[String]): Fox[Unit] = + def updateName(annotationId: ObjectId, tracingId: String, newName: String): Fox[Unit] = for { _ <- run( sqlu"update webknossos.annotation_layers set name = $newName where _annotation = $annotationId and tracingId = $tracingId") diff --git a/frontend/javascripts/messages.ts b/frontend/javascripts/messages.ts index 8d8e7430592..2a032b8692b 100644 --- a/frontend/javascripts/messages.ts +++ b/frontend/javascripts/messages.ts @@ -173,7 +173,9 @@ instead. Only enable this option if you understand its effect. All layers will n "tracing.volume_layer_name_includes_invalid_characters": (disallowedCharacters: string) => `This layer name includes the disallowed character${ disallowedCharacters.length > 1 ? "s" : "" - } "${disallowedCharacters}". Please delete ${disallowedCharacters.length > 1 ? "them" : "it"}.`, + } "${disallowedCharacters}". Please remove ${ + disallowedCharacters.length > 1 ? "them" : "it" + } to set the layer name.`, "tracing.delete_initial_node": "Do you really want to delete the initial node?", "tracing.delete_tree": "Do you really want to delete the whole tree?", "tracing.delete_tree_with_initial_node": diff --git a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts index 75cd1568278..a6c9ccd21be 100644 --- a/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts +++ b/frontend/javascripts/oxalis/model/accessors/volumetracing_accessor.ts @@ -102,7 +102,7 @@ export function getAllReadableLayerNames(dataset: APIDataset, tracing: Tracing) : currentLayer.name, ); if (tracing.skeleton != null) { - allReadableLayerNames.push("Skeletons"); + allReadableLayerNames.push("Skeleton"); } return allReadableLayerNames; } diff --git a/frontend/javascripts/oxalis/view/left-border-tabs/modals/add_volume_layer_modal.tsx b/frontend/javascripts/oxalis/view/left-border-tabs/modals/add_volume_layer_modal.tsx index bd1c58a0af7..e7bec96f51e 100644 --- a/frontend/javascripts/oxalis/view/left-border-tabs/modals/add_volume_layer_modal.tsx +++ b/frontend/javascripts/oxalis/view/left-border-tabs/modals/add_volume_layer_modal.tsx @@ -36,9 +36,9 @@ export function checkForLayerNameDuplication( } export function checkLayerNameForInvalidCharacters(readableLayerName: string): ValidationResult { - const uriSaveCharactersRegex = /[0-9a-zA-Z-._~]+/g; - // Removing all URISaveCharacters from readableLayerName. The left over chars are all invalid. - const allInvalidChars = readableLayerName.replace(uriSaveCharactersRegex, ""); + const uriSafeCharactersRegex = /[0-9a-zA-Z-._]+/g; + // Removing all URISaveCharacters from readableLayerName. The leftover chars are all invalid. + const allInvalidChars = readableLayerName.replace(uriSafeCharactersRegex, ""); const allUniqueInvalidCharsAsSet = new Set(allInvalidChars); const allUniqueInvalidCharsAsString = "".concat(...allUniqueInvalidCharsAsSet.values()); const isValid = allUniqueInvalidCharsAsString.length === 0; diff --git a/tools/postgres/schema.sql b/tools/postgres/schema.sql index c376d1f999e..a60baa68589 100644 --- a/tools/postgres/schema.sql +++ b/tools/postgres/schema.sql @@ -21,7 +21,7 @@ START TRANSACTION; CREATE TABLE webknossos.releaseInformation ( schemaVersion BIGINT NOT NULL ); -INSERT INTO webknossos.releaseInformation(schemaVersion) values(82); +INSERT INTO webknossos.releaseInformation(schemaVersion) values(83); COMMIT TRANSACTION; @@ -56,7 +56,7 @@ CREATE TABLE webknossos.annotation_layers( _annotation CHAR(24) NOT NULL, tracingId CHAR(36) NOT NULL UNIQUE, typ webknossos.ANNOTATION_LAYER_TYPE NOT NULL, - name VARCHAR(256), + name VARCHAR(256) NOT NULL UNIQUE CHECK (name ~* '^[A-Za-z0-9\-_\.]+$'), PRIMARY KEY (_annotation, tracingId) ); From 0be8b06aab34c8e0a5df2a83bc7edcb7f6a57b80 Mon Sep 17 00:00:00 2001 From: Florian M Date: Mon, 11 Jul 2022 11:59:09 +0200 Subject: [PATCH 2/5] add migration guide --- MIGRATIONS.released.md | 2 +- MIGRATIONS.unreleased.md | 21 +++++++++++++++++++ conf/evolutions/083-unique-layer-names.sql | 16 ++++++++++++++ .../reversions/083-unique-layer-names.sql | 14 +++++++++++++ tools/postgres/schema.sql | 3 ++- 5 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 conf/evolutions/083-unique-layer-names.sql create mode 100644 conf/evolutions/reversions/083-unique-layer-names.sql diff --git a/MIGRATIONS.released.md b/MIGRATIONS.released.md index 5ee57698ace..0668939e0d1 100644 --- a/MIGRATIONS.released.md +++ b/MIGRATIONS.released.md @@ -407,4 +407,4 @@ No migrations necessary. ## [18.07.0](https://github.com/scalableminds/webknossos/releases/tag/18.07.0) - 2018-07-05 -First release \ No newline at end of file +First release diff --git a/MIGRATIONS.unreleased.md b/MIGRATIONS.unreleased.md index 7ff19f1f09e..096c61c08e9 100644 --- a/MIGRATIONS.unreleased.md +++ b/MIGRATIONS.unreleased.md @@ -8,4 +8,25 @@ User-facing changes are documented in the [changelog](CHANGELOG.released.md). ## Unreleased [Commits](https://github.com/scalableminds/webknossos/compare/22.07.0...HEAD) + - Postgres evolution 83 (see below) introduces unique and url-safe constraints for annotation layer names. If the database contains entries violating those new constraints, they need to be fixed manually, otherwise the evolution will abort: + - change null names to the front-end-side defaults: + ``` + update webknossos.annotation_layers set name = 'Volume' where name is null and typ = 'Volume' + update webknossos.annotation_layers set name = 'Skeleton' where name is null and typ = 'Skeleton' + ``` + + - find annotations with multiple layers, make unique manually + ``` + select _annotation, name from webknossos.annotation_layers where _annotation in (select s._annotation from + (select _annotation, count(_annotation) from webknossos.annotation_layers where typ = 'Volume' group by _annotation order by count(_annotation) desc limit 1000) as s + where count > 1) and typ = 'Volume' order by _annotation + ``` + + - find layers with interesting names, manually remove spaces and special characters + ``` + select * from webknossos.annotation_layers where not name ~* '^[A-Za-z0-9\-_\.]+$' + ``` + ### Postgres Evolutions: + +- [083-unique-layer-names.sql](conf/evolutions/083-unique-layer-names.sql) Note: Note that this evolution introduces constraints which may not be met by existing data. See above for manual steps diff --git a/conf/evolutions/083-unique-layer-names.sql b/conf/evolutions/083-unique-layer-names.sql new file mode 100644 index 00000000000..d763d179e60 --- /dev/null +++ b/conf/evolutions/083-unique-layer-names.sql @@ -0,0 +1,16 @@ +-- Note that this evolution introduces constraints which may not be met by existing data. See migration guide for manual steps + +START TRANSACTION; + +ALTER TABLE webknossos.annotation_layers +ALTER COLUMN name SET NOT NULL; + +ALTER TABLE webknossos.annotation_layers +ADD CONSTRAINT annotation_layers_name__annotation_key UNIQUE(name, _annotation); + +ALTER TABLE webknossos.annotation_layers +ADD CONSTRAINT annotation_layers_name_check CHECK (name ~* '^[A-Za-z0-9\-_\.]+$'); + +UPDATE webknossos.releaseInformation SET schemaVersion = 83; + +COMMIT TRANSACTION; diff --git a/conf/evolutions/reversions/083-unique-layer-names.sql b/conf/evolutions/reversions/083-unique-layer-names.sql new file mode 100644 index 00000000000..873fcb90402 --- /dev/null +++ b/conf/evolutions/reversions/083-unique-layer-names.sql @@ -0,0 +1,14 @@ +START TRANSACTION; + +ALTER TABLE webknossos.annotation_layers +ALTER COLUMN name DROP NOT NULL; + +ALTER TABLE webknossos.annotation_layers +DROP CONSTRAINT annotation_layers_name__annotation_key; + +ALTER TABLE webknossos.annotation_layers +DROP CONSTRAINT annotation_layers_name_check; + +UPDATE webknossos.releaseInformation SET schemaVersion = 82; + +COMMIT TRANSACTION; diff --git a/tools/postgres/schema.sql b/tools/postgres/schema.sql index a60baa68589..5cb80042288 100644 --- a/tools/postgres/schema.sql +++ b/tools/postgres/schema.sql @@ -56,7 +56,8 @@ CREATE TABLE webknossos.annotation_layers( _annotation CHAR(24) NOT NULL, tracingId CHAR(36) NOT NULL UNIQUE, typ webknossos.ANNOTATION_LAYER_TYPE NOT NULL, - name VARCHAR(256) NOT NULL UNIQUE CHECK (name ~* '^[A-Za-z0-9\-_\.]+$'), + name VARCHAR(256) NOT NULL CHECK (name ~* '^[A-Za-z0-9\-_\.]+$'), + UNIQUE (name, _annotation), PRIMARY KEY (_annotation, tracingId) ); From 5db455829785f019181aea2d023e548dcb8cff60 Mon Sep 17 00:00:00 2001 From: Florian M Date: Mon, 11 Jul 2022 12:55:43 +0200 Subject: [PATCH 3/5] adapt backend to name being non-optional --- app/controllers/AnnotationController.scala | 7 +++-- app/controllers/AnnotationIOController.scala | 11 +++++--- app/controllers/LegacyApiController.scala | 4 +-- app/models/annotation/Annotation.scala | 2 +- app/models/annotation/AnnotationLayer.scala | 26 ++++++++++++++----- app/models/annotation/AnnotationMerger.scala | 18 ++++++++----- app/models/annotation/AnnotationService.scala | 9 ++++--- app/models/annotation/nml/NmlWriter.scala | 5 ++-- .../left-border-tabs/layer_settings_tab.tsx | 4 +-- 9 files changed, 55 insertions(+), 31 deletions(-) diff --git a/app/controllers/AnnotationController.scala b/app/controllers/AnnotationController.scala index 2c46fa652dd..3db9c09b831 100755 --- a/app/controllers/AnnotationController.scala +++ b/app/controllers/AnnotationController.scala @@ -33,7 +33,7 @@ import scala.concurrent.duration._ case class AnnotationLayerParameters(typ: AnnotationLayerType, fallbackLayerName: Option[String], resolutionRestrictions: Option[ResolutionRestrictions], - name: Option[String]) + name: String) object AnnotationLayerParameters { implicit val jsonFormat: OFormat[AnnotationLayerParameters] = Json.format[AnnotationLayerParameters] } @@ -262,7 +262,10 @@ class AnnotationController @Inject()( None, ObjectId.dummyId, ObjectId.dummyId, - List(AnnotationLayer(TracingIds.dummyTracingId, AnnotationLayerType.Skeleton)) + List( + AnnotationLayer(TracingIds.dummyTracingId, + AnnotationLayerType.Skeleton, + AnnotationLayer.defaultSkeletonLayerName)) ) json <- annotationService.publicWrites(annotation, request.identity) ?~> "annotation.write.failed" } yield JsonOk(json) diff --git a/app/controllers/AnnotationIOController.scala b/app/controllers/AnnotationIOController.scala index b5cfb8917dd..dc01f94ec75 100755 --- a/app/controllers/AnnotationIOController.scala +++ b/app/controllers/AnnotationIOController.scala @@ -144,7 +144,9 @@ Expects: if (volumeLayersGrouped.length > 1 && volumeLayersGrouped.exists(_.length > 1)) return Fox.failure("Cannot merge multiple annotations that each have multiple volume layers.") if (volumeLayersGrouped.length == 1) { // Just one annotation was uploaded, keep its layers separate - Fox.serialCombined(volumeLayersGrouped.toList.flatten) { uploadedVolumeLayer => + Fox.serialCombined(volumeLayersGrouped.toList.flatten.zipWithIndex) { volumeLayerWithIndex => + val uploadedVolumeLayer = volumeLayerWithIndex._1 + val idx = volumeLayerWithIndex._2 for { savedTracingId <- client.saveVolumeTracing(uploadedVolumeLayer.tracing, uploadedVolumeLayer.getDataZipFrom(otherFiles)) @@ -152,7 +154,7 @@ Expects: AnnotationLayer( savedTracingId, AnnotationLayerType.Volume, - uploadedVolumeLayer.name + uploadedVolumeLayer.name.getOrElse(AnnotationLayer.defaultVolumeLayerName + idx.toString) ) } } else { // Multiple annotations with volume layers (but at most one each) was uploaded merge those volume layers into one @@ -168,7 +170,7 @@ Expects: AnnotationLayer( mergedTracingId, AnnotationLayerType.Volume, - None + AnnotationLayer.defaultVolumeLayerName )) } } @@ -180,7 +182,8 @@ Expects: mergedTracingId <- tracingStoreClient.mergeSkeletonTracingsByContents( SkeletonTracings(skeletonTracings.map(t => SkeletonTracingOpt(Some(t)))), persistTracing = true) - } yield List(AnnotationLayer(mergedTracingId, AnnotationLayerType.Skeleton, None)) + } yield + List(AnnotationLayer(mergedTracingId, AnnotationLayerType.Skeleton, AnnotationLayer.defaultSkeletonLayerName)) } private def assertNonEmpty(parseSuccesses: List[NmlParseSuccess]) = diff --git a/app/controllers/LegacyApiController.scala b/app/controllers/LegacyApiController.scala index fe81f9e1f54..178f240a8ef 100644 --- a/app/controllers/LegacyApiController.scala +++ b/app/controllers/LegacyApiController.scala @@ -429,7 +429,7 @@ class LegacyApiController @Inject()(annotationController: AnnotationController, AnnotationLayerParameters(AnnotationLayerType.Skeleton, request.body.fallbackLayerName, request.body.resolutionRestrictions, - name = None)) + name = AnnotationLayer.defaultSkeletonLayerName)) val volumeParameters = if (request.body.typ == "skeleton") None else @@ -437,7 +437,7 @@ class LegacyApiController @Inject()(annotationController: AnnotationController, AnnotationLayerParameters(AnnotationLayerType.Volume, request.body.fallbackLayerName, request.body.resolutionRestrictions, - name = None)) + name = AnnotationLayer.defaultVolumeLayerName)) List(skeletonParameters, volumeParameters).flatten } diff --git a/app/models/annotation/Annotation.scala b/app/models/annotation/Annotation.scala index 65d83ddffef..4eb40e2797e 100755 --- a/app/models/annotation/Annotation.scala +++ b/app/models/annotation/Annotation.scala @@ -110,7 +110,7 @@ class AnnotationLayerDAO @Inject()(SQLClient: SQLClient)(implicit ec: ExecutionC def insertOneQuery(annotationId: ObjectId, a: AnnotationLayer): SqlAction[Int, NoStream, Effect] = sqlu"""insert into webknossos.annotation_layers(_annotation, tracingId, typ, name) - values($annotationId, ${a.tracingId}, '#${a.typ.toString}', ${a.name.map(sanitize)})""" + values($annotationId, ${a.tracingId}, '#${a.typ.toString}', ${a.name})""" def findAnnotationIdByTracingId(tracingId: String): Fox[ObjectId] = for { diff --git a/app/models/annotation/AnnotationLayer.scala b/app/models/annotation/AnnotationLayer.scala index c85e91fd756..25512636319 100644 --- a/app/models/annotation/AnnotationLayer.scala +++ b/app/models/annotation/AnnotationLayer.scala @@ -13,18 +13,28 @@ import scala.concurrent.ExecutionContext case class AnnotationLayer( tracingId: String, typ: AnnotationLayerType, - name: Option[String] = None + name: String ) {} object AnnotationLayer extends FoxImplicits { implicit val jsonFormat: OFormat[AnnotationLayer] = Json.format[AnnotationLayer] + val defaultSkeletonLayerName: String = "Skeleton" + val defaultVolumeLayerName: String = "Volume" + + def defaultNameForType(typ: AnnotationLayerType): String = + typ match { + case AnnotationLayerType.Skeleton => defaultSkeletonLayerName + case AnnotationLayerType.Volume => defaultVolumeLayerName + } + def layersFromIds(skeletonTracingIdOpt: Option[String], volumeTracingIdOpt: Option[String], assertNonEmpty: Boolean = true)(implicit ec: ExecutionContext): Fox[List[AnnotationLayer]] = { val annotationLayers: List[AnnotationLayer] = List( - skeletonTracingIdOpt.map(AnnotationLayer(_, AnnotationLayerType.Skeleton)), - volumeTracingIdOpt.map(AnnotationLayer(_, AnnotationLayerType.Volume))).flatten + skeletonTracingIdOpt.map(AnnotationLayer(_, AnnotationLayerType.Skeleton, defaultSkeletonLayerName)), + volumeTracingIdOpt.map(AnnotationLayer(_, AnnotationLayerType.Volume, defaultVolumeLayerName)) + ).flatten for { _ <- bool2Fox(!assertNonEmpty || annotationLayers.nonEmpty) ?~> "annotation.needsEitherSkeletonOrVolume" } yield annotationLayers @@ -32,7 +42,7 @@ object AnnotationLayer extends FoxImplicits { } case class FetchedAnnotationLayer(tracingId: String, - name: Option[String], + name: String, tracing: Either[SkeletonTracing, VolumeTracing], volumeDataOpt: Option[Array[Byte]] = None) { def typ: AnnotationLayerType = @@ -40,7 +50,7 @@ case class FetchedAnnotationLayer(tracingId: String, def volumeDataZipName(index: Int, isSingle: Boolean): String = { val indexLabel = if (isSingle) "" else s"_$index" - val nameLabel = name.map(n => s"_$n").getOrElse("") + val nameLabel = s"_$name" s"data$indexLabel$nameLabel.zip" } } @@ -74,8 +84,10 @@ object FetchedAnnotationLayer { _ <- bool2Fox(skeletonTracingIdOpt.isDefined == skeletonTracingOpt.isDefined) ?~> "annotation.mismatchingSkeletonIdsAndTracings" _ <- bool2Fox(volumeTracingIdOpt.isDefined == volumeTracingOpt.isDefined) ?~> "annotation.mismatchingVolumeIdsAndTracings" annotationLayers: List[FetchedAnnotationLayer] = List( - skeletonTracingIdOpt.map(FetchedAnnotationLayer(_, None, Left(skeletonTracingOpt.get))), - volumeTracingIdOpt.map(FetchedAnnotationLayer(_, None, Right(volumeTracingOpt.get))) + skeletonTracingIdOpt.map( + FetchedAnnotationLayer(_, AnnotationLayer.defaultSkeletonLayerName, Left(skeletonTracingOpt.get))), + volumeTracingIdOpt.map( + FetchedAnnotationLayer(_, AnnotationLayer.defaultVolumeLayerName, Right(volumeTracingOpt.get))) ).flatten _ <- bool2Fox(!assertNonEmpty || annotationLayers.nonEmpty) ?~> "annotation.needsEitherSkeletonOrVolume" } yield annotationLayers diff --git a/app/models/annotation/AnnotationMerger.scala b/app/models/annotation/AnnotationMerger.scala index 7c24836e14b..ca43a9f2cba 100644 --- a/app/models/annotation/AnnotationMerger.scala +++ b/app/models/annotation/AnnotationMerger.scala @@ -70,12 +70,18 @@ class AnnotationMerger @Inject()(dataSetDAO: DataSetDAO, tracingStoreService: Tr skeletonLayers.map(_.tracingId), persistTracing) mergedVolumeTracingId <- mergeVolumeTracings(tracingStoreClient, volumeLayers.map(_.tracingId), persistTracing) - mergedSkeletonName = allEqual(skeletonLayers.flatMap(_.name)) - mergedVolumeName = allEqual(volumeLayers.flatMap(_.name)) - mergedSkeletonLayer = mergedSkeletonTracingId.map(id => - AnnotationLayer(id, AnnotationLayerType.Skeleton, mergedSkeletonName)) - mergedVolumeLayer = mergedVolumeTracingId.map(id => - AnnotationLayer(id, AnnotationLayerType.Volume, mergedVolumeName)) + mergedSkeletonName = allEqual(skeletonLayers.map(_.name)) + mergedVolumeName = allEqual(volumeLayers.map(_.name)) + mergedSkeletonLayer = mergedSkeletonTracingId.map( + id => + AnnotationLayer(id, + AnnotationLayerType.Skeleton, + mergedSkeletonName.getOrElse(AnnotationLayer.defaultSkeletonLayerName))) + mergedVolumeLayer = mergedVolumeTracingId.map( + id => + AnnotationLayer(id, + AnnotationLayerType.Volume, + mergedVolumeName.getOrElse(AnnotationLayer.defaultVolumeLayerName))) } yield List(mergedSkeletonLayer, mergedVolumeLayer).flatten private def allEqual(str: List[String]): Option[String] = diff --git a/app/models/annotation/AnnotationService.scala b/app/models/annotation/AnnotationService.scala index 418fd092ac2..7d0b92b80d9 100755 --- a/app/models/annotation/AnnotationService.scala +++ b/app/models/annotation/AnnotationService.scala @@ -329,10 +329,11 @@ class AnnotationService @Inject()( case _ => Fox.failure("annotation.makeHybrid.alreadyHybrid") } usedFallbackLayerName = if (newAnnotationLayerType == AnnotationLayerType.Volume) fallbackLayerName else None - newAnnotationLayerParameters = AnnotationLayerParameters(newAnnotationLayerType, - usedFallbackLayerName, - Some(ResolutionRestrictions.empty), - None) + newAnnotationLayerParameters = AnnotationLayerParameters( + newAnnotationLayerType, + usedFallbackLayerName, + Some(ResolutionRestrictions.empty), + AnnotationLayer.defaultNameForType(newAnnotationLayerType)) _ <- addAnnotationLayer(annotation, organizationName, newAnnotationLayerParameters) ?~> "makeHybrid.createTracings.failed" } yield () diff --git a/app/models/annotation/nml/NmlWriter.scala b/app/models/annotation/nml/NmlWriter.scala index ae7ddd66d11..744fe4f8026 100644 --- a/app/models/annotation/nml/NmlWriter.scala +++ b/app/models/annotation/nml/NmlWriter.scala @@ -200,14 +200,13 @@ class NmlWriter @Inject()(implicit ec: ExecutionContext) extends FoxImplicits { volumeFilename: Option[String], skipVolumeData: Boolean)(implicit writer: XMLStreamWriter): Unit = if (skipVolumeData) { - val nameLabel = volumeLayer.name.map(n => f"named $n ").getOrElse("") writer.writeComment( - f"A volume layer $nameLabel(id = $index) was omitted here while downloading this annotation without volume data.") + f"A volume layer named ${volumeLayer.name} (id = $index) was omitted here while downloading this annotation without volume data.") } else { Xml.withinElementSync("volume") { writer.writeAttribute("id", index.toString) writer.writeAttribute("location", volumeFilename.getOrElse(volumeLayer.volumeDataZipName(index, isSingle))) - volumeLayer.name.foreach(n => writer.writeAttribute("name", n)) + writer.writeAttribute("name", volumeLayer.name) volumeLayer.tracing match { case Right(volumeTracing) => volumeTracing.fallbackLayer.foreach(writer.writeAttribute("fallbackLayer", _)) case _ => () diff --git a/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx b/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx index ba1d657c90f..ec8e0cdd76b 100644 --- a/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx +++ b/frontend/javascripts/oxalis/view/left-border-tabs/layer_settings_tab.tsx @@ -781,7 +781,7 @@ class DatasetSettings extends React.PureComponent { return ( {/* This div is necessary for the tooltip to be displayed */} @@ -804,7 +804,7 @@ class DatasetSettings extends React.PureComponent { wordWrap: "break-word", }} > - Skeletons + Skeleton {showSkeletons ? (
Date: Mon, 11 Jul 2022 13:22:24 +0200 Subject: [PATCH 4/5] fix backend test --- test/backend/NMLUnitTestSuite.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/backend/NMLUnitTestSuite.scala b/test/backend/NMLUnitTestSuite.scala index 3bd7c82a9b7..86e10d8ff15 100644 --- a/test/backend/NMLUnitTestSuite.scala +++ b/test/backend/NMLUnitTestSuite.scala @@ -4,7 +4,7 @@ import java.io.ByteArrayInputStream import com.scalableminds.webknossos.datastore.SkeletonTracing._ import models.annotation.nml.{NmlParser, NmlWriter} -import models.annotation.{FetchedAnnotationLayer, UploadedVolumeLayer} +import models.annotation.{AnnotationLayer, FetchedAnnotationLayer, UploadedVolumeLayer} import net.liftweb.common.{Box, Full} import org.scalatestplus.play.PlaySpec import play.api.i18n.{DefaultMessagesApi, Messages, MessagesProvider} @@ -23,7 +23,7 @@ class NMLUnitTestSuite extends PlaySpec { def writeAndParseTracing(skeletonTracing: SkeletonTracing) : Box[(Option[SkeletonTracing], List[UploadedVolumeLayer], String)] = { - val annotationLayers = List(FetchedAnnotationLayer("dummySkeletonTracingId", None, Left(skeletonTracing), None)) + val annotationLayers = List(FetchedAnnotationLayer("dummySkeletonTracingId", AnnotationLayer.defaultSkeletonLayerName, Left(skeletonTracing), None)) val nmlEnumarator = new NmlWriter().toNmlStream(annotationLayers, None, None, None, "testOrganization", None, None) val arrayFuture = Iteratee.flatten(nmlEnumarator |>> Iteratee.consume[Array[Byte]]()).run From eaa152c842013516554fd8aca85736502b461ef1 Mon Sep 17 00:00:00 2001 From: Florian M Date: Mon, 11 Jul 2022 14:04:58 +0200 Subject: [PATCH 5/5] adapt snapshots --- .../annotations.e2e.js.md | 7 ++++++ .../annotations.e2e.js.snap | Bin 11083 -> 11105 bytes .../backend-snapshot-tests/tasks.e2e.js.md | 2 ++ .../backend-snapshot-tests/tasks.e2e.js.snap | Bin 5097 -> 5114 bytes test/db/annotation_layers.csv | 20 +++++++++--------- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.md b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.md index 6ff422a8288..4b91d17c034 100644 --- a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.md +++ b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.md @@ -9,6 +9,7 @@ Generated by [AVA](https://avajs.dev). { annotationLayers: [ { + name: 'Skeleton', tracingId: 'ae417175-f7bb-4a34-8187-d9c3b50143ae', typ: 'Skeleton', }, @@ -127,6 +128,7 @@ Generated by [AVA](https://avajs.dev). { annotationLayers: [ { + name: 'Skeleton', tracingId: 'ae417175-f7bb-4a34-8187-d9c3b50143aa', typ: 'Skeleton', }, @@ -195,6 +197,7 @@ Generated by [AVA](https://avajs.dev). { annotationLayers: [ { + name: 'Skeleton', tracingId: 'tracingId', typ: 'Skeleton', }, @@ -365,6 +368,7 @@ Generated by [AVA](https://avajs.dev). { annotationLayers: [ { + name: 'Skeleton', tracingId: 'tracingId', typ: 'Skeleton', }, @@ -535,6 +539,7 @@ Generated by [AVA](https://avajs.dev). { annotationLayers: [ { + name: 'Skeleton', tracingId: 'tracingId', typ: 'Skeleton', }, @@ -658,6 +663,7 @@ Generated by [AVA](https://avajs.dev). { annotationLayers: [ { + name: 'Skeleton', tracingId: 'tracingId', typ: 'Skeleton', }, @@ -781,6 +787,7 @@ Generated by [AVA](https://avajs.dev). { annotationLayers: [ { + name: 'Skeleton', tracingId: 'tracingId', typ: 'Skeleton', }, diff --git a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.snap b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.snap index d30e47ca28fece1649a0016985fdc128cd9ccafd..626848e5ec9a2722310cf82e944107ab68c4fab9 100644 GIT binary patch literal 11105 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 literal 11083 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

j6YtQBxya@X&-pI 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 diff --git a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/tasks.e2e.js.md b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/tasks.e2e.js.md index 1ef8452833a..29f0e20184e 100644 --- a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/tasks.e2e.js.md +++ b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/tasks.e2e.js.md @@ -403,6 +403,7 @@ Generated by [AVA](https://avajs.dev). { annotationLayers: [ { + name: 'Skeleton', tracingId: '07fd0bd7-f7a6-46d1-aaa8-58a381da4d50', typ: 'Skeleton', }, @@ -687,6 +688,7 @@ Generated by [AVA](https://avajs.dev). { annotationLayers: [ { + name: 'Skeleton', tracingId: 'tracingId', typ: 'Skeleton', }, diff --git a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/tasks.e2e.js.snap b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/tasks.e2e.js.snap index d4b35d46a8c9a86149683115b5305248146b734f..9d058eb4bfc2a600bc12834ddf4167c587770d9d 100644 GIT binary patch literal 5114 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 literal 5097 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