From 733ee276d20a00b25101d9fad26cb65be6ec1e7a Mon Sep 17 00:00:00 2001 From: frcroth Date: Mon, 15 Jan 2024 13:41:17 +0100 Subject: [PATCH 1/7] Add new isValidNewName route and increment api version --- app/RequestHandler.scala | 2 +- app/controllers/DatasetController.scala | 16 +++++++++++----- app/controllers/LegacyApiController.scala | 17 +++++++++++++++++ conf/webknossos.latest.routes | 2 +- conf/webknossos.versioned.routes | 7 +++++++ .../scalableminds/util/tools/BoxImplicits.scala | 5 +++++ 6 files changed, 42 insertions(+), 7 deletions(-) diff --git a/app/RequestHandler.scala b/app/RequestHandler.scala index c73d49b9c19..05f9b9a9121 100644 --- a/app/RequestHandler.scala +++ b/app/RequestHandler.scala @@ -66,7 +66,7 @@ class RequestHandler @Inject()(webCommands: WebCommands, assets.at(path = "/public", file = path) } - private def CURRENT_API_VERSION = 5 + private def CURRENT_API_VERSION = 6 private def apiVersionIsTooNew(request: RequestHeader): Boolean = "^/api/v(\\d+).*$".r.findFirstMatchIn(request.uri) match { diff --git a/app/controllers/DatasetController.scala b/app/controllers/DatasetController.scala index 9a176386e1d..9d05da8a06d 100755 --- a/app/controllers/DatasetController.scala +++ b/app/controllers/DatasetController.scala @@ -16,7 +16,7 @@ import models.dataset.explore.{ import models.organization.OrganizationDAO import models.team.{TeamDAO, TeamService} import models.user.{User, UserDAO, UserService} -import net.liftweb.common.{Box, Empty, Failure, Full} +import net.liftweb.common.{Box, Empty, EmptyBox, Failure, Full} import play.api.i18n.{Messages, MessagesProvider} import play.api.libs.functional.syntax._ import play.api.libs.json._ @@ -373,14 +373,20 @@ class DatasetController @Inject()(userService: UserService, Future.successful(JsonBadRequest(Messages("dataset.type.invalid", typ))) } - def assertValidNewName(organizationName: String, dataSetName: String): Action[AnyContent] = + def isValidNewName(organizationName: String, dataSetName: String): Action[AnyContent] = sil.SecuredAction.async { implicit request => for { organization <- organizationDAO.findOneByName(organizationName) _ <- bool2Fox(organization._id == request.identity._organization) ~> FORBIDDEN - _ <- datasetService.assertValidDatasetName(dataSetName) - _ <- datasetService.assertNewDatasetName(dataSetName, organization._id) ?~> "dataset.name.alreadyTaken" - } yield Ok + validName <- datasetService.assertValidDatasetName(dataSetName).futureBox + nameAlreadyExists <- (datasetService.assertNewDatasetName(dataSetName, organization._id) ?~> "dataset.name.alreadyTaken").futureBox + errors = combineErrors(List(validName, nameAlreadyExists)) + valid = validName.isDefined && nameAlreadyExists.isDefined + } yield + errors match { + case Some(e) => Ok(Json.obj("isValid" -> valid, "errors" -> e.map(Messages(_)))) + case None => Ok(Json.obj("isValid" -> valid)) + } } def getOrganizationForDataset(dataSetName: String): Action[AnyContent] = sil.UserAwareAction.async { diff --git a/app/controllers/LegacyApiController.scala b/app/controllers/LegacyApiController.scala index 7be9a016fd2..ed9f4ffa755 100644 --- a/app/controllers/LegacyApiController.scala +++ b/app/controllers/LegacyApiController.scala @@ -5,6 +5,9 @@ import play.silhouette.api.actions.{SecuredRequest, UserAwareRequest} import com.scalableminds.util.tools.Fox import com.scalableminds.webknossos.datastore.models.annotation.{AnnotationLayer, AnnotationLayerType} import com.scalableminds.webknossos.tracingstore.tracings.volume.ResolutionRestrictions +import models.dataset.DatasetService +import models.organization.OrganizationDAO + import javax.inject.Inject import models.project.ProjectDAO import models.task.{TaskDAO, TaskService} @@ -29,11 +32,25 @@ class LegacyApiController @Inject()(annotationController: AnnotationController, userController: UserController, projectController: ProjectController, projectDAO: ProjectDAO, + organizationDAO: OrganizationDAO, + datasetService: DatasetService, taskDAO: TaskDAO, taskService: TaskService, sil: Silhouette[WkEnv])(implicit ec: ExecutionContext, bodyParsers: PlayBodyParsers) extends Controller { + + def assertValidNewNameV5(organizationName: String, dataSetName: String): Action[AnyContent] = + sil.SecuredAction.async { implicit request => + for { + organization <- organizationDAO.findOneByName(organizationName) + _ <- bool2Fox(organization._id == request.identity._organization) ~> FORBIDDEN + _ <- datasetService.assertValidDatasetName(dataSetName) + _ <- datasetService.assertNewDatasetName(dataSetName, organization._id) ?~> "dataset.name.alreadyTaken" + } yield Ok + } + + /* to provide v4 - replace new annotation layers by old tracing ids (changed in v5) */ diff --git a/conf/webknossos.latest.routes b/conf/webknossos.latest.routes index 8c202ca4ac5..d87c9e06123 100644 --- a/conf/webknossos.latest.routes +++ b/conf/webknossos.latest.routes @@ -91,8 +91,8 @@ PATCH /datasets/:organizationName/:dataSetName/teams GET /datasets/:organizationName/:dataSetName/layers/:layer/thumbnail controllers.DatasetController.thumbnail(organizationName: String, dataSetName: String, layer: String, w: Option[Int], h: Option[Int], mappingName: Option[String], sharingToken: Option[String]) POST /datasets/:organizationName/:dataSetName/layers/:layer/segmentAnythingEmbedding controllers.DatasetController.segmentAnythingEmbedding(organizationName: String, dataSetName: String, layer: String, intensityMin: Option[Float], intensityMax: Option[Float]) PUT /datasets/:organizationName/:dataSetName/clearThumbnailCache controllers.DatasetController.removeFromThumbnailCache(organizationName: String, dataSetName: String) -GET /datasets/:organizationName/:dataSetName/isValidNewName controllers.DatasetController.assertValidNewName(organizationName: String, dataSetName: String) GET /datasets/:organizationName/:dataSetName controllers.DatasetController.read(organizationName: String, dataSetName: String, sharingToken: Option[String]) +GET /datasets/:organizationName/:dataSetName/isValidNewName controllers.DatasetController.isValidNewName(organizationName: String, dataSetName: String) # Folders GET /folders/root controllers.FolderController.getRoot() diff --git a/conf/webknossos.versioned.routes b/conf/webknossos.versioned.routes index e2fb81c18fc..cba7e31e78b 100644 --- a/conf/webknossos.versioned.routes +++ b/conf/webknossos.versioned.routes @@ -3,13 +3,20 @@ # example: assume, the features route has changed, introducing v2. The older v1 needs to be provided in the legacyApiController # version log: + # changed in v6: isValidName always returns Ok, with a JSON object containing possible errors and key "isValid" # new in v5: annotation layers (changed annotation json result, changed createExplorational params) # new in v4: project queried by id, not name (changed route parameters) # new in v3: annotation info and finish request now take timestamp # new in v2: annotation json constains visibility enum instead of booleans +-> /v6/ webknossos.latest.Routes + + +GET /v5/datasets/:organizationName/:dataSetName/isValidNewName controllers.LegacyApiController.assertValidNewNameV5(organizationName: String, dataSetName: String) + -> /v5/ webknossos.latest.Routes + # support changes to v5 GET /v4/annotations/:typ/:id/info controllers.LegacyApiController.annotationInfoV4(typ: String, id: String, timestamp: Long) PATCH /v4/annotations/:typ/:id/finish controllers.LegacyApiController.annotationFinishV4(typ: String, id: String, timestamp: Long) diff --git a/util/src/main/scala/com/scalableminds/util/tools/BoxImplicits.scala b/util/src/main/scala/com/scalableminds/util/tools/BoxImplicits.scala index a4642d2fcf5..cddb29e7c24 100644 --- a/util/src/main/scala/com/scalableminds/util/tools/BoxImplicits.scala +++ b/util/src/main/scala/com/scalableminds/util/tools/BoxImplicits.scala @@ -18,6 +18,11 @@ trait BoxImplicits { } def bool2Box(in: Boolean): Box[Unit] = if (in) Full(()) else Empty + + implicit def combineErrors(boxes: List[Box[_]]): Option[List[String]] = { + val failures = boxes.collect { case f: Failure => f } + if (failures.isEmpty) None else Some(failures.map(_.msg)) + } } object BoxImplicits extends BoxImplicits From 51e95c652d741c582820f0b79dae28d20eb53e56 Mon Sep 17 00:00:00 2001 From: frcroth Date: Mon, 15 Jan 2024 13:48:10 +0100 Subject: [PATCH 2/7] Add rudimentary frontend --- frontend/javascripts/admin/admin_rest_api.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/frontend/javascripts/admin/admin_rest_api.ts b/frontend/javascripts/admin/admin_rest_api.ts index 02bc6153e18..adea8f6247f 100644 --- a/frontend/javascripts/admin/admin_rest_api.ts +++ b/frontend/javascripts/admin/admin_rest_api.ts @@ -1646,17 +1646,16 @@ export async function isDatasetNameValid( return "The dataset name must not be empty."; } - try { - await Request.receiveJSON( - `/api/datasets/${datasetId.owningOrganization}/${datasetId.name}/isValidNewName`, - { - showErrorToast: false, - }, - ); + let response = await Request.receiveJSON( + `/api/datasets/${datasetId.owningOrganization}/${datasetId.name}/isValidNewName`, + { + showErrorToast: false, + }, + ); + if (response.isValid) { return null; - } catch (ex) { - // @ts-expect-error ts-migrate(7006) FIXME: Parameter 'msg' implicitly has an 'any' type. - return ex.messages.map((msg) => Object.values(msg)[0]).join(". "); + } else { + return response.errors[0]; } } From 849f25a6036ecfba5aaf472ba9d6d10c6f65c5fa Mon Sep 17 00:00:00 2001 From: frcroth Date: Mon, 15 Jan 2024 13:54:55 +0100 Subject: [PATCH 3/7] Update migration guide --- MIGRATIONS.unreleased.md | 1 + 1 file changed, 1 insertion(+) diff --git a/MIGRATIONS.unreleased.md b/MIGRATIONS.unreleased.md index 14576519a36..8c46c6251e7 100644 --- a/MIGRATIONS.unreleased.md +++ b/MIGRATIONS.unreleased.md @@ -11,6 +11,7 @@ User-facing changes are documented in the [changelog](CHANGELOG.released.md). - If your setup contains webknossos-workers, postgres evolution 110 introduces the column `supportedJobCommands`. This needs to be filled in manually for your workers. Currently available job commands are `compute_mesh_file`, `compute_segment_index_file`, `convert_to_wkw`, `export_tiff`, `find_largest_segment_id`, `infer_nuclei`, `infer_neurons`, `materialize_volume_annotation`, `render_animation`. [#7463](https://github.com/scalableminds/webknossos/pull/7463) - If your setup contains webknossos-workers, postgres evolution 110 introduces the columns `maxParallelHighPriorityJobs` and `maxParallelLowPriorityJobs`. Make sure to set those values to match what you want for your deployment. [#7463](https://github.com/scalableminds/webknossos/pull/7463) - If your setup contains webknossos-workers, you may want to add the new available worker job `compute_segment_index_file` to the `supportedJobCommands` column of one or more of your workers. [#7493](https://github.com/scalableminds/webknossos/pull/7493) +- The WEBKNOSSOS api version has changed to 6. The `isValidNewName` route for datasets now returns 200 regardless of whether the name is valid or not. The body contains a JSON object with the key "isValid". [#7550](https://github.com/scalableminds/webknossos/pull/7550) ### Postgres Evolutions: From a371a7be60e58aa4c9d325066cf20e6c4245ead6 Mon Sep 17 00:00:00 2001 From: frcroth Date: Mon, 15 Jan 2024 13:56:31 +0100 Subject: [PATCH 4/7] Fix backend lint --- app/controllers/DatasetController.scala | 2 +- app/controllers/LegacyApiController.scala | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/controllers/DatasetController.scala b/app/controllers/DatasetController.scala index 9d05da8a06d..d0f2e9f6a77 100755 --- a/app/controllers/DatasetController.scala +++ b/app/controllers/DatasetController.scala @@ -16,7 +16,7 @@ import models.dataset.explore.{ import models.organization.OrganizationDAO import models.team.{TeamDAO, TeamService} import models.user.{User, UserDAO, UserService} -import net.liftweb.common.{Box, Empty, EmptyBox, Failure, Full} +import net.liftweb.common.{Box, Empty, Failure, Full} import play.api.i18n.{Messages, MessagesProvider} import play.api.libs.functional.syntax._ import play.api.libs.json._ diff --git a/app/controllers/LegacyApiController.scala b/app/controllers/LegacyApiController.scala index ed9f4ffa755..3affee1220d 100644 --- a/app/controllers/LegacyApiController.scala +++ b/app/controllers/LegacyApiController.scala @@ -39,7 +39,6 @@ class LegacyApiController @Inject()(annotationController: AnnotationController, sil: Silhouette[WkEnv])(implicit ec: ExecutionContext, bodyParsers: PlayBodyParsers) extends Controller { - def assertValidNewNameV5(organizationName: String, dataSetName: String): Action[AnyContent] = sil.SecuredAction.async { implicit request => for { @@ -50,7 +49,6 @@ class LegacyApiController @Inject()(annotationController: AnnotationController, } yield Ok } - /* to provide v4 - replace new annotation layers by old tracing ids (changed in v5) */ From f9d8b1c080673769fd1f1302f0a5f1397d7ca415 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Tue, 16 Jan 2024 15:49:06 +0100 Subject: [PATCH 5/7] small clean up in isDatasetNameValid --- frontend/javascripts/admin/admin_rest_api.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frontend/javascripts/admin/admin_rest_api.ts b/frontend/javascripts/admin/admin_rest_api.ts index adea8f6247f..b5e7bcbd7de 100644 --- a/frontend/javascripts/admin/admin_rest_api.ts +++ b/frontend/javascripts/admin/admin_rest_api.ts @@ -1646,11 +1646,8 @@ export async function isDatasetNameValid( return "The dataset name must not be empty."; } - let response = await Request.receiveJSON( + const response = await Request.receiveJSON( `/api/datasets/${datasetId.owningOrganization}/${datasetId.name}/isValidNewName`, - { - showErrorToast: false, - }, ); if (response.isValid) { return null; From 3e2f34f3bcf75a6084edbb78ed580e2ff639ba9a Mon Sep 17 00:00:00 2001 From: frcroth Date: Wed, 17 Jan 2024 09:20:08 +0100 Subject: [PATCH 6/7] Update changelog --- CHANGELOG.unreleased.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 9fe943692bd..ebc9b7cff4c 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -30,6 +30,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released - Removed Swagger/OpenAPI json description of the HTTP API. [#7494](https://github.com/scalableminds/webknossos/pull/7494) - Updated antd UI library from version 4.24.8 to 4.24.15. [#7505](https://github.com/scalableminds/webknossos/pull/7505) - Changed the default dataset search mode to also search in subfolders. [#7539](https://github.com/scalableminds/webknossos/pull/7539) +- Updated the isValidName route in the API to return 200 for valid and invalid datasets. With this, the API version was bumped up to 6. [#7550](https://github.com/scalableminds/webknossos/pull/7550) ### Fixed - Datasets with annotations can now be deleted. The concerning annotations can no longer be viewed but still be downloaded. [#7429](https://github.com/scalableminds/webknossos/pull/7429) From 74937ab9ec09e91139ca042b16ffd434950c8c51 Mon Sep 17 00:00:00 2001 From: frcroth Date: Wed, 17 Jan 2024 09:20:08 +0100 Subject: [PATCH 7/7] Update changelog --- CHANGELOG.unreleased.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index ebc9b7cff4c..0f69f4d1ee8 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -30,7 +30,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released - Removed Swagger/OpenAPI json description of the HTTP API. [#7494](https://github.com/scalableminds/webknossos/pull/7494) - Updated antd UI library from version 4.24.8 to 4.24.15. [#7505](https://github.com/scalableminds/webknossos/pull/7505) - Changed the default dataset search mode to also search in subfolders. [#7539](https://github.com/scalableminds/webknossos/pull/7539) -- Updated the isValidName route in the API to return 200 for valid and invalid datasets. With this, the API version was bumped up to 6. [#7550](https://github.com/scalableminds/webknossos/pull/7550) +- Updated the isValidName route in the API to return 200 for valid and invalid names. With this, the API version was bumped up to 6. [#7550](https://github.com/scalableminds/webknossos/pull/7550) ### Fixed - Datasets with annotations can now be deleted. The concerning annotations can no longer be viewed but still be downloaded. [#7429](https://github.com/scalableminds/webknossos/pull/7429)