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

Make isValidNewName route always return 200 #7550

Merged
merged 10 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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 MIGRATIONS.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
2 changes: 1 addition & 1 deletion app/RequestHandler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 10 additions & 4 deletions app/controllers/DatasetController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
15 changes: 15 additions & 0 deletions app/controllers/LegacyApiController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -29,11 +32,23 @@ 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)
*/
Expand Down
2 changes: 1 addition & 1 deletion conf/webknossos.latest.routes
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
7 changes: 7 additions & 0 deletions conf/webknossos.versioned.routes
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
19 changes: 9 additions & 10 deletions frontend/javascripts/admin/admin_rest_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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