Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to lock explorative annotations #7801

Merged
merged 26 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
75ddb36
add backend route to update isBlockedByUser state of an annotation
MichaelBuessemeyer May 14, 2024
aec1293
WIP: restrict frontend edits on locked annotations [ci skip]
MichaelBuessemeyer May 14, 2024
d25b004
WIP: blocking edit action in tracing view whn isLockedByUser is activ…
MichaelBuessemeyer May 14, 2024
47b5b0c
add evolution file
MichaelBuessemeyer May 15, 2024
0a48511
improve tooltip messages in locked annotation
MichaelBuessemeyer May 15, 2024
be6a02e
fix backend formatting
MichaelBuessemeyer May 15, 2024
0a65e05
Add some allowUpdate checks to other routes updating an annotation wh…
MichaelBuessemeyer May 16, 2024
85d31ab
fix annotations.csv for e2e tests [ci skip]
MichaelBuessemeyer May 16, 2024
2007f5e
fix/update e2e tests
MichaelBuessemeyer May 16, 2024
81a01c3
Merge branch 'master' of github.com:scalableminds/webknossos into add…
MichaelBuessemeyer May 23, 2024
70935a4
add migration entry
MichaelBuessemeyer May 23, 2024
51993d6
remove locked tag from annotation when unlocked in annotation view
MichaelBuessemeyer May 23, 2024
61fbf22
add changelog entry
MichaelBuessemeyer May 23, 2024
1980d35
clean up and remove allowUpdateAndIsNotLocked function (added in this…
MichaelBuessemeyer May 24, 2024
730c6d3
remove outdated comment
MichaelBuessemeyer May 24, 2024
d77d856
remove migration reason
MichaelBuessemeyer May 28, 2024
4b91dd3
apply pr feedback
MichaelBuessemeyer May 28, 2024
5950da0
refactor explorative annotations table to use column prop instead of …
MichaelBuessemeyer May 28, 2024
6e593ba
replace locked tag with disabled text in id column hinting at a locke…
MichaelBuessemeyer May 28, 2024
f3c4893
Update frontend/javascripts/oxalis/model/reducers/reducer_helpers.ts
MichaelBuessemeyer May 28, 2024
79cd978
update snapshots
MichaelBuessemeyer May 28, 2024
95535b8
Merge branch 'master' into add-locked-state-to-annotation
MichaelBuessemeyer May 29, 2024
c20baf6
make it possible to lock an annotation from annotation view
MichaelBuessemeyer May 30, 2024
581099d
add revision
MichaelBuessemeyer May 30, 2024
073f941
add hint for unlocking for owners to navbar locked tag
MichaelBuessemeyer May 30, 2024
c05b825
Merge branch 'master' of github.com:scalableminds/webknossos into add…
MichaelBuessemeyer May 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released

### Added
- Within the proofreading tool, the user can now interact with the super voxels of a mesh in the 3D viewport. For example, this allows to merge or cut super voxels from another. As before, the proofreading tool requires an agglomerate file. [#7742](https://github.com/scalableminds/webknossos/pull/7742)
- Added the option to lock an explorative anntations for the owner. Locked annotation cannot be annotated by any user. [#7801](https://github.com/scalableminds/webknossos/pull/7801)
- Minor improvements for the timetracking overview (faster data loading, styling). [#7789](https://github.com/scalableminds/webknossos/pull/7789)
- Updated several backend dependencies for optimized stability and performance. [#7782](https://github.com/scalableminds/webknossos/pull/7782)
- Voxelytics workflows can be searched by name and hash. [#7790](https://github.com/scalableminds/webknossos/pull/7790)
Expand Down
3 changes: 3 additions & 0 deletions MIGRATIONS.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ User-facing changes are documented in the [changelog](CHANGELOG.released.md).
## Unreleased
[Commits](https://github.com/scalableminds/webknossos/compare/24.05.0...HEAD)

- Edited the annotations table to add the option to locked explorative annotations. Migration 114 needs to be executed to update the database schema.
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved

### Postgres Evolutions:
- [114-annotation-locked-by-user.sql](conf/evolutions/114-annotation-locked-by-user.sql)
33 changes: 20 additions & 13 deletions app/controllers/AnnotationController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,26 @@ class AnnotationController @Inject()(
} yield JsonOk(json, Messages("annotation.reopened"))
}

def editLockedState(typ: String, id: String, isLockedByUser: Boolean): Action[AnyContent] = sil.SecuredAction.async {
implicit request =>
for {
annotation <- provider.provideAnnotation(typ, id, request.identity)
_ <- bool2Fox(annotation._user == request.identity._id) ?~> "annotation.isLockedByUser.notAllowed"
_ <- bool2Fox(annotation.typ == AnnotationType.Explorational) ?~> "annotation.isLockedByUser.explorationalsOnly"
_ = logger.info(
s"Locking annotation $id, new locked state will be ${isLockedByUser.toString}, access context: ${request.identity.toStringAnonymous}")
MichaelBuessemeyer marked this conversation as resolved.
Show resolved Hide resolved
_ <- annotationDAO.updateLockedState(annotation._id, isLockedByUser) ?~> "annotation.invalid"
updatedAnnotation <- provider.provideAnnotation(typ, id, request.identity) ~> NOT_FOUND
json <- annotationService.publicWrites(updatedAnnotation, Some(request.identity)) ?~> "annotation.write.failed"
} yield JsonOk(json, Messages("annotation.IsLockedByUser.success"))
}

def addAnnotationLayer(typ: String, id: String): Action[AnnotationLayerParameters] =
sil.SecuredAction.async(validateJson[AnnotationLayerParameters]) { implicit request =>
for {
_ <- bool2Fox(AnnotationType.Explorational.toString == typ) ?~> "annotation.addLayer.explorationalsOnly"
restrictions <- provider.restrictionsFor(typ, id) ?~> "restrictions.notFound" ~> NOT_FOUND
_ <- restrictions.allowUpdate(request.identity) ?~> "notAllowed" ~> FORBIDDEN
annotation <- provider.provideAnnotation(typ, id, request.identity)
newLayerName = request.body.name.getOrElse(AnnotationLayer.defaultNameForType(request.body.typ))
_ <- bool2Fox(!annotation.annotationLayers.exists(_.name == newLayerName)) ?~> "annotation.addLayer.nameInUse"
Expand Down Expand Up @@ -281,6 +297,8 @@ class AnnotationController @Inject()(
sil.SecuredAction.async { implicit request =>
for {
_ <- bool2Fox(AnnotationType.Explorational.toString == typ) ?~> "annotation.addLayer.explorationalsOnly"
restrictions <- provider.restrictionsFor(typ, id) ?~> "restrictions.notFound" ~> NOT_FOUND
_ <- restrictions.allowUpdate(request.identity) ?~> "notAllowed" ~> FORBIDDEN
annotation <- provider.provideAnnotation(typ, id, request.identity)
organization <- organizationDAO.findOne(request.identity._organization)
_ <- annotationService.makeAnnotationHybrid(annotation, organization.name, fallbackLayerName) ?~> "annotation.makeHybrid.failed"
Expand All @@ -301,6 +319,8 @@ class AnnotationController @Inject()(
implicit request =>
for {
_ <- bool2Fox(AnnotationType.Explorational.toString == typ) ?~> "annotation.downsample.explorationalsOnly"
restrictions <- provider.restrictionsFor(typ, id) ?~> "restrictions.notFound" ~> NOT_FOUND
_ <- restrictions.allowUpdate(request.identity) ?~> "notAllowed" ~> FORBIDDEN
annotation <- provider.provideAnnotation(typ, id, request.identity)
annotationLayer <- annotation.annotationLayers
.find(_.tracingId == tracingId)
Expand All @@ -319,19 +339,6 @@ class AnnotationController @Inject()(
} yield result
}

def addSegmentIndex(id: String, tracingId: String): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
for {
annotation <- provider.provideAnnotation(id, request.identity)
_ <- bool2Fox(AnnotationType.Explorational == annotation.typ) ?~> "annotation.addSegmentIndex.explorationalsOnly"
annotationLayer <- annotation.annotationLayers
.find(_.tracingId == tracingId)
.toFox ?~> "annotation.addSegmentIndex.layerNotFound"
_ <- annotationService.addSegmentIndex(annotation, annotationLayer) ?~> "annotation.addSegmentIndex.failed"
updated <- provider.provideAnnotation(id, request.identity)
json <- annotationService.publicWrites(updated, Some(request.identity)) ?~> "annotation.write.failed"
} yield JsonOk(json)
}

Comment on lines -322 to -334
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was unused

def addSegmentIndicesToAll(parallelBatchCount: Int,
dryRun: Boolean,
skipTracings: Option[String]): Action[AnyContent] =
Expand Down
116 changes: 57 additions & 59 deletions app/models/annotation/Annotation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import models.annotation.AnnotationType.AnnotationType
import play.api.libs.json._
import slick.jdbc.GetResult._
import slick.jdbc.PostgresProfile.api._
import slick.jdbc.GetResult
import slick.jdbc.TransactionIsolation.Serializable
import slick.lifted.Rep
import slick.sql.SqlAction
Expand All @@ -33,6 +34,7 @@ case class Annotation(
name: String = "",
viewConfiguration: Option[JsObject] = None,
state: AnnotationState.Value = Active,
isLockedByUser: Boolean = false,
tags: Set[String] = Set.empty,
tracingTime: Option[Long] = None,
typ: AnnotationType.Value = AnnotationType.Explorational,
Expand Down Expand Up @@ -84,6 +86,7 @@ case class AnnotationCompactInfo(id: ObjectId,
modified: Instant,
tags: Set[String],
state: AnnotationState.Value = Active,
isLockedByUser: Boolean,
dataSetName: String,
visibility: AnnotationVisibility.Value = AnnotationVisibility.Internal,
tracingTime: Option[Long] = None,
Expand All @@ -93,10 +96,6 @@ case class AnnotationCompactInfo(id: ObjectId,
annotationLayerTypes: Seq[String],
annotationLayerStatistics: Seq[JsObject])

object AnnotationCompactInfo {
implicit val jsonFormat: Format[AnnotationCompactInfo] = Json.format[AnnotationCompactInfo]
}

Comment on lines -96 to -99
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was not needed

class AnnotationLayerDAO @Inject()(SQLClient: SqlClient)(implicit ec: ExecutionContext)
extends SimpleSQLDAO(SQLClient) {

Expand Down Expand Up @@ -218,6 +217,7 @@ class AnnotationDAO @Inject()(sqlClient: SqlClient, annotationLayerDAO: Annotati
r.name,
viewconfigurationOpt,
state,
r.islockedbyuser,
parseArrayLiteral(r.tags).toSet,
r.tracingtime,
typ,
Expand Down Expand Up @@ -322,6 +322,43 @@ class AnnotationDAO @Inject()(sqlClient: SqlClient, annotationLayerDAO: Annotati
} yield parsed
}

// Necessary since a tuple can only have 22 elements
implicit def GetResultAnnotationCompactInfo: GetResult[AnnotationCompactInfo] = GetResult { prs =>
import prs._

val id = <<[ObjectId]
val name = <<[String]
val description = <<[String]
val ownerId = <<[ObjectId]
val ownerFirstName = <<[String]
val ownerLastName = <<[String]
val othersMayEdit = <<[Boolean]
val teamIds = parseArrayLiteral(<<[String]).map(ObjectId(_))
val teamNames = parseArrayLiteral(<<[String])
val teamOrganizationIds = parseArrayLiteral(<<[String]).map(ObjectId(_))
val modified = <<[Instant]
val tags = parseArrayLiteral(<<[String]).toSet
val state = AnnotationState.fromString(<<[String]).getOrElse(AnnotationState.Active)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we’re here, could you try out if this and the other enums also work without the String detour, with <<[AnnotationState] (also changing the type in the case class)? In theory, the enums have automatic adapters both to SQL and to JSON.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i tried bug failed 🙈:

I changed to

 val state = <<[AnnotationState]
    val isLockedByOwner = <<[Boolean]
    val dataSetName = <<[String]
    val typ = <<[AnnotationType]
    val visibility = <<[AnnotationVisibility]

But scala could not find the matching GetResult "converters". Thus, I added them as implicit params:

  implicit def GetResultAnnotationCompactInfo(implicit
                                                e0: GetResult[AnnotationState],
                                                e1: GetResult[AnnotationType],
                                                e2: GetResult[AnnotationVisibility]
                                             ): GetResult[AnnotationCompactInfo] = GetResult { prs =>

But then the call run(query.as[AnnotationCompactInfo]) claims it needs to define the implicit params GetResult[AnnotationState]

=> I'd say no to the question / suggestion or we need to define the GetResults somewhere. But I honestly don't know how to do that 🙈

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, thanks for experimenting! Then let’s do this another time. Feel free to go via the string route as is :)

val isLockedByUser = <<[Boolean]
val dataSetName = <<[String]
val typ = AnnotationType.fromString(<<[String]).getOrElse(AnnotationType.Explorational)
val visibility = AnnotationVisibility.fromString(<<[String]).getOrElse(AnnotationVisibility.Internal)
val tracingTime = Option(<<[Long])
val organizationName = <<[String]
val tracingIds = parseArrayLiteral(<<[String])
val annotationLayerNames = parseArrayLiteral(<<[String])
val annotationLayerTypes = parseArrayLiteral(<<[String])
val annotationLayerStatistics =
parseArrayLiteral(<<[String]).map(layerStats => Json.parse(layerStats).validate[JsObject].getOrElse(Json.obj()))

// format: off
AnnotationCompactInfo(id, typ, name,description,ownerId,ownerFirstName,ownerLastName, othersMayEdit,teamIds,
teamNames,teamOrganizationIds,modified,tags,state,isLockedByUser,dataSetName,visibility,tracingTime,
organizationName,tracingIds,annotationLayerNames,annotationLayerTypes,annotationLayerStatistics
)
// format: on
}

def findAllListableExplorationals(
isFinished: Option[Boolean],
forUser: Option[ObjectId],
Expand Down Expand Up @@ -366,6 +403,7 @@ class AnnotationDAO @Inject()(sqlClient: SqlClient, annotationLayerDAO: Annotati
a.modified,
a.tags,
a.state,
a.isLockedByUser,
d.name,
a.typ,
a.visibility,
Expand All @@ -384,67 +422,15 @@ class AnnotationDAO @Inject()(sqlClient: SqlClient, annotationLayerDAO: Annotati
WHERE $stateQuery AND $accessQuery AND $userQuery AND $typQuery
GROUP BY
a._id, a.name, a.description, a._user, a.othersmayedit, a.modified,
a.tags, a.state, a.typ, a.visibility, a.tracingtime,
a.tags, a.state, a.islockedbyuser, a.typ, a.visibility, a.tracingtime,
u.firstname, u.lastname,
teams_agg.team_ids, teams_agg.team_names, teams_agg.team_organization_ids,
d.name, o.name
ORDER BY a._id DESC
LIMIT $limit
OFFSET ${pageNumber * limit}"""
rows <- run(
query.as[
(ObjectId,
String,
String,
ObjectId,
String,
String,
Boolean,
String,
String,
String,
Instant,
String,
String,
String,
String,
String,
Long,
String,
String,
String,
String,
String)])
} yield
rows.toList.map(
r => {
AnnotationCompactInfo(
id = r._1,
name = r._2,
description = r._3,
ownerId = r._4,
ownerFirstName = r._5,
ownerLastName = r._6,
othersMayEdit = r._7,
teamIds = parseArrayLiteral(r._8).map(ObjectId(_)),
teamNames = parseArrayLiteral(r._9),
teamOrganizationIds = parseArrayLiteral(r._10).map(ObjectId(_)),
modified = r._11,
tags = parseArrayLiteral(r._12).toSet,
state = AnnotationState.fromString(r._13).getOrElse(AnnotationState.Active),
dataSetName = r._14,
typ = AnnotationType.fromString(r._15).getOrElse(AnnotationType.Explorational),
visibility = AnnotationVisibility.fromString(r._16).getOrElse(AnnotationVisibility.Internal),
tracingTime = Option(r._17),
organizationName = r._18,
tracingIds = parseArrayLiteral(r._19),
annotationLayerNames = parseArrayLiteral(r._20),
annotationLayerTypes = parseArrayLiteral(r._21),
annotationLayerStatistics = parseArrayLiteral(r._22).map(layerStats =>
Json.parse(layerStats).validate[JsObject].getOrElse(Json.obj()))
)
}
)
rows <- run(query.as[AnnotationCompactInfo])
} yield rows.toList

def countAllListableExplorationals(isFinished: Option[Boolean])(implicit ctx: DBAccessContext): Fox[Long] = {
val stateQuery = getStateQuery(isFinished)
Expand Down Expand Up @@ -692,6 +678,18 @@ class AnnotationDAO @Inject()(sqlClient: SqlClient, annotationLayerDAO: Annotati
_ = logger.info(s"Updated state of Annotation $id to $state, access context: ${ctx.toStringAnonymous}")
} yield ()

def updateLockedState(id: ObjectId, isLocked: Boolean)(implicit ctx: DBAccessContext): Fox[Unit] =
for {
_ <- assertUpdateAccess(id) ?~> "FAILED: AnnotationSQLDAO.assertUpdateAccess"
query = q"UPDATE webknossos.annotations SET isLockedByUser = $isLocked WHERE _id = $id".asUpdate
_ <- run(
query.withTransactionIsolation(Serializable),
retryCount = 50,
retryIfErrorContains = List(transactionSerializationError)) ?~> "FAILED: run in AnnotationSQLDAO.updateState"
_ = logger.info(
s"Updated isLockedByUser of Annotation $id to $isLocked, access context: ${ctx.toStringAnonymous}")
} yield ()

def updateDescription(id: ObjectId, description: String)(implicit ctx: DBAccessContext): Fox[Unit] =
for {
_ <- assertUpdateAccess(id)
Expand Down
6 changes: 3 additions & 3 deletions app/models/annotation/AnnotationRestrictions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ class AnnotationRestrictionDefaults @Inject()(userService: UserService)(implicit
accessAllowed <- allowAccess(user)
} yield
user.exists { user =>
(annotation._user == user._id || accessAllowed && annotation.othersMayEdit) && !(annotation.state == Finished)
(annotation._user == user._id || accessAllowed && annotation.othersMayEdit) && !(annotation.state == Finished) && !annotation.isLockedByUser
}

override def allowFinish(userOption: Option[User]): Fox[Boolean] =
(for {
user <- option2Fox(userOption)
isTeamManagerOrAdminOfTeam <- userService.isTeamManagerOrAdminOf(user, annotation._team)
} yield {
(annotation._user == user._id || isTeamManagerOrAdminOfTeam) && !(annotation.state == Finished)
(annotation._user == user._id || isTeamManagerOrAdminOfTeam) && !(annotation.state == Finished) && !annotation.isLockedByUser
}).orElse(Fox.successful(false))

/* used in backend only to allow repeatable finish calls */
Expand All @@ -87,7 +87,7 @@ class AnnotationRestrictionDefaults @Inject()(userService: UserService)(implicit
user <- option2Fox(userOption)
isTeamManagerOrAdminOfTeam <- userService.isTeamManagerOrAdminOf(user, annotation._team)
} yield {
annotation._user == user._id || isTeamManagerOrAdminOfTeam
(annotation._user == user._id || isTeamManagerOrAdminOfTeam) && !annotation.isLockedByUser
}).orElse(Fox.successful(false))
}

Expand Down
11 changes: 2 additions & 9 deletions app/models/annotation/AnnotationService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -436,15 +436,6 @@ class AnnotationService @Inject()(
_ <- annotationLayersDAO.replaceTracingId(annotation._id, volumeAnnotationLayer.tracingId, newVolumeTracingId)
} yield ()

def addSegmentIndex(annotation: Annotation, volumeAnnotationLayer: AnnotationLayer)(
implicit ctx: DBAccessContext): Fox[Unit] =
for {
dataset <- datasetDAO.findOne(annotation._dataset) ?~> "dataset.notFoundForAnnotation"
_ <- bool2Fox(volumeAnnotationLayer.typ == AnnotationLayerType.Volume) ?~> "annotation.segmentIndex.volumeOnly"
rpcClient <- tracingStoreService.clientFor(dataset)
_ <- rpcClient.addSegmentIndex(volumeAnnotationLayer.tracingId, dryRun = false)
} yield ()

// WARNING: needs to be repeatable, might be called multiple times for an annotation
def finish(annotation: Annotation, user: User, restrictions: AnnotationRestrictions)(
implicit ctx: DBAccessContext): Fox[String] = {
Expand Down Expand Up @@ -908,6 +899,7 @@ class AnnotationService @Inject()(
Json.obj(
"modified" -> annotation.modified,
"state" -> annotation.state,
"isLockedByUser" -> annotation.isLockedByUser,
"id" -> annotation.id,
"name" -> annotation.name,
"description" -> annotation.description,
Expand Down Expand Up @@ -1010,6 +1002,7 @@ class AnnotationService @Inject()(
"description" -> annotationInfo.description,
"typ" -> annotationInfo.typ,
"stats" -> Json.obj(), // included for legacy parsers
"isLockedByUser" -> annotationInfo.isLockedByUser,
"annotationLayers" -> annotationLayerJson,
"dataSetName" -> annotationInfo.dataSetName,
"organization" -> annotationInfo.organizationName,
Expand Down
11 changes: 11 additions & 0 deletions conf/evolutions/114-annotation-locked-by-user.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
START TRANSACTION;

do $$ begin ASSERT (select schemaVersion from webknossos.releaseInformation) = 113, 'Previous schema version mismatch'; end; $$ LANGUAGE plpgsql;

DROP VIEW webknossos.annotations_;
ALTER TABLE webknossos.annotations ADD isLockedByUser BOOLEAN NOT NULL DEFAULT FALSE;
CREATE VIEW webknossos.annotations_ as SELECT * FROM webknossos.annotations WHERE NOT isDeleted;

UPDATE webknossos.releaseInformation SET schemaVersion = 114;

COMMIT TRANSACTION;
5 changes: 5 additions & 0 deletions conf/messages
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@ annotation.reopen.tooLate=The annotation cannot be reopened anymore, since it ha
annotation.reopen.notAllowed=You are not allowed to reopen this annotation.
annotation.reopen.notFinished=The requested annotation is not finished.
annotation.reopen.failed=Failed to reopen the annotation.
annotation.reopen.locked=This annotation is locked by the owner and therefore cannot be reopened.
annotation.isLockedByUser.notAllowed=Only the owner of this annotation is allowed to change the locked state of an annotation.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not even an admin can do so?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is what the current implementation does, yes. We should discuss it. Would you prefer if admins were allowed to lock/unlock other people’s annotations? I would vote for it. cc @normanrz

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you prefer if admins were allowed to lock/unlock other people’s annotations?

I think, I'm against that. For the same reason that only the owner can mutate an annotation.. If we should decide to allow admins this, then, the interface should make it very clear that one is locking/unlocking somebody else's annotation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine by me too. Restricting it to the owner certainly is easier for the moment

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, if a user leaves the team, an admin would need to copy the annotation to make it editable again?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, if a user leaves the team, an admin would need to copy the annotation to make it editable again?

In your described scenario -> yes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After we agreed upon this, this pr should (and its potential changes) this pr should be ready for the 2nd review round

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just talked with @fm3 about this pending decision: -> Should we decided that admins can also unlock/lock an annotation, this can be done as a follow-up pr :)

annotation.isLockedByUser.explorationalsOnly=Only explorational annotations can be locked.
annotation.IsLockedByUser.failed=Changing the isLockedByUser state of the annotation failed.
annotation.IsLockedByUser.success=The locking state of the annotation was successfully updated.
annotation.sandbox.skeletonOnly=Sandbox annotations are currently available as skeleton only.
annotation.multiLayers.skeleton.notImplemented=This feature is not implemented for annotations with more than one skeleton layer
annotation.multiLayers.volume.notImplemented=This feature is not implemented for annotations with more than one volume layer
Expand Down
Loading