diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 1927b106e4a..72816cc3d62 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -19,6 +19,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released ### Fixed - Fixed sharing button for users who are currently visiting a dataset or annotation which was shared with them. [#6438](https://github.com/scalableminds/webknossos/pull/6438) +- Fixed the duplicate function for annotations with an editable mapping (a.k.a. supervoxel proofreading) layer. [#6446](https://github.com/scalableminds/webknossos/pull/6446) ### Removed diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingController.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingController.scala index e44ff9fc67f..f6377609a5d 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingController.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/controllers/VolumeTracingController.scala @@ -181,23 +181,28 @@ class VolumeTracingController @Inject()( boundingBox: Option[String]): Action[AnyContent] = Action.async { implicit request => log() { logTime(slackNotificationService.noticeSlowRequest) { - accessTokenService.validateAccess(UserAccessRequest.webknossos, urlOrHeaderToken(token, request)) { + val userToken = urlOrHeaderToken(token, request) + accessTokenService.validateAccess(UserAccessRequest.webknossos, userToken) { for { tracing <- tracingService.find(tracingId) ?~> Messages("tracing.notFound") - _ <- bool2Fox(!tracing.getMappingIsEditable) ?~> "Duplicate is not yet implemented for editable mapping annotations" dataSetBoundingBox = request.body.asJson.flatMap(_.validateOpt[BoundingBox].asOpt.flatten) resolutionRestrictions = ResolutionRestrictions(minResolution, maxResolution) editPositionParsed <- Fox.runOptional(editPosition)(Vec3Int.fromUriLiteral) editRotationParsed <- Fox.runOptional(editRotation)(Vec3Double.fromUriLiteral) boundingBoxParsed <- Fox.runOptional(boundingBox)(BoundingBox.fromLiteral) - (newId, newTracing) <- tracingService.duplicate(tracingId, - tracing, - fromTask.getOrElse(false), - dataSetBoundingBox, - resolutionRestrictions, - editPositionParsed, - editRotationParsed, - boundingBoxParsed) + newEditableMappingId <- Fox.runIf(tracing.mappingIsEditable.contains(true))( + editableMappingService.duplicate(tracing.mappingName, tracing, userToken)) + (newId, newTracing) <- tracingService.duplicate( + tracingId, + tracing, + fromTask.getOrElse(false), + dataSetBoundingBox, + resolutionRestrictions, + editPositionParsed, + editRotationParsed, + boundingBoxParsed, + newEditableMappingId + ) _ <- Fox.runIfOptionTrue(downsample)(tracingService.downsample(newId, newTracing)) } yield Ok(Json.toJson(newId)) } diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingService.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingService.scala index a969b593500..b5b48d0ee15 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingService.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingService.scala @@ -123,6 +123,7 @@ class EditableMappingService @Inject()( "mappingName" -> editableMappingId, "version" -> version, "tracingId" -> tracingId, + "baseMappingName" -> editableMapping.baseMappingName, "createdTimestamp" -> editableMapping.createdTimestamp ) @@ -147,6 +148,15 @@ class EditableMappingService @Inject()( emptyFallback = Some(-1L)) } yield versionOrMinusOne >= 0 + def duplicate(editableMappingIdOpt: Option[String], tracing: VolumeTracing, userToken: Option[String]): Fox[String] = + for { + editableMappingId <- editableMappingIdOpt ?~> "duplicate on editable mapping without id" + remoteFallbackLayer <- RemoteFallbackLayer.fromVolumeTracing(tracing) + editableMapping <- get(editableMappingId, remoteFallbackLayer, userToken) + newId = generateId + _ <- tracingDataStore.editableMappings.put(newId, 0L, toProtoBytes(editableMapping.toProto)) + } yield newId + def updateActionLog(editableMappingId: String): Fox[JsValue] = { def versionedTupleToJson(tuple: (Long, List[EditableMappingUpdateAction])): JsObject = Json.obj( diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala index feca9f0b4ba..89897251688 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala @@ -273,7 +273,8 @@ class VolumeTracingService @Inject()( resolutionRestrictions: ResolutionRestrictions, editPosition: Option[Vec3Int], editRotation: Option[Vec3Double], - boundingBox: Option[BoundingBox]): Fox[(String, VolumeTracing)] = { + boundingBox: Option[BoundingBox], + mappingName: Option[String]): Fox[(String, VolumeTracing)] = { val tracingWithBB = addBoundingBoxFromTaskIfRequired(sourceTracing, fromTask, dataSetBoundingBox) val tracingWithResolutionRestrictions = restrictMagList(tracingWithBB, resolutionRestrictions) val newTracing = tracingWithResolutionRestrictions.copy( @@ -281,6 +282,7 @@ class VolumeTracingService @Inject()( editPosition = editPosition.map(vec3IntToProto).getOrElse(tracingWithResolutionRestrictions.editPosition), editRotation = editRotation.map(vec3DoubleToProto).getOrElse(tracingWithResolutionRestrictions.editRotation), boundingBox = boundingBoxOptToProto(boundingBox).getOrElse(tracingWithResolutionRestrictions.boundingBox), + mappingName = mappingName.orElse(tracingWithResolutionRestrictions.mappingName), version = 0 ) for {