From 6e766f906f3fdd9a49d87603f2e311aba453ab9e Mon Sep 17 00:00:00 2001 From: Youri K Date: Thu, 14 Mar 2019 18:10:41 +0100 Subject: [PATCH 1/3] add count header for paginated routes and updated docs #3666 --- app/controllers/ProjectController.scala | 3 ++- app/controllers/UserController.scala | 12 ++++++++---- app/models/annotation/Annotation.scala | 17 +++++++++++++++++ app/models/task/Task.scala | 9 +++++++++ docs/rest_api.md | 6 +++++- 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/app/controllers/ProjectController.scala b/app/controllers/ProjectController.scala index f923a4e5cb3..eb91c05a1f6 100644 --- a/app/controllers/ProjectController.scala +++ b/app/controllers/ProjectController.scala @@ -127,9 +127,10 @@ class ProjectController @Inject()(projectService: ProjectService, _ <- Fox.assertTrue(userService.isTeamManagerOrAdminOf(request.identity, project._team)) ?~> "notAllowed" ~> FORBIDDEN tasks <- taskDAO.findAllByProject(project._id, limit.getOrElse(Int.MaxValue), pageNumber.getOrElse(0))( GlobalAccessContext) + taskCount <- taskDAO.countAllByProject(project._id)(GlobalAccessContext) js <- Fox.serialCombined(tasks)(task => taskService.publicWrites(task)) } yield { - Ok(Json.toJson(js)) + Ok(Json.toJson(js)).withHeaders("X-Total-Count" -> taskCount.toString) } } diff --git a/app/controllers/UserController.scala b/app/controllers/UserController.scala index 4099fba7d82..b566b695629 100755 --- a/app/controllers/UserController.scala +++ b/app/controllers/UserController.scala @@ -60,9 +60,10 @@ class UserController @Inject()(userService: UserService, AnnotationType.Explorational, limit.getOrElse(defaultAnnotationLimit), pageNumber.getOrElse(0)) + annotationCount <- annotationDAO.countAllFor(request.identity._id, isFinished, AnnotationType.Explorational) jsonList <- Fox.serialCombined(annotations)(a => annotationService.compactWrites(a)) } yield { - Ok(Json.toJson(jsonList)) + Ok(Json.toJson(jsonList)).withHeaders("X-Total-Count" -> annotationCount.toString) } } @@ -74,9 +75,10 @@ class UserController @Inject()(userService: UserService, AnnotationType.Task, limit.getOrElse(defaultAnnotationLimit), pageNumber.getOrElse(0)) + annotationCount <- annotationDAO.countAllFor(request.identity._id, isFinished, AnnotationType.Task) jsonList <- Fox.serialCombined(annotations)(a => annotationService.publicWrites(a, Some(request.identity))) } yield { - Ok(Json.toJson(jsonList)) + Ok(Json.toJson(jsonList)).withHeaders("X-Total-Count" -> annotationCount.toString) } } @@ -143,9 +145,10 @@ class UserController @Inject()(userService: UserService, AnnotationType.Explorational, limit.getOrElse(defaultAnnotationLimit), pageNumber.getOrElse(0)) + annotationCount <- annotationDAO.countAllFor(userIdValidated, isFinished, AnnotationType.Explorational) jsonList <- Fox.serialCombined(annotations)(a => annotationService.compactWrites(a)) } yield { - Ok(Json.toJson(jsonList)) + Ok(Json.toJson(jsonList)).withHeaders("X-Total-Count" -> annotationCount.toString) } } @@ -160,9 +163,10 @@ class UserController @Inject()(userService: UserService, AnnotationType.Task, limit.getOrElse(defaultAnnotationLimit), pageNumber.getOrElse(0)) + annotationCount <- annotationDAO.countAllFor(userIdValidated, isFinished, AnnotationType.Task) jsonList <- Fox.serialCombined(annotations)(a => annotationService.publicWrites(a, Some(request.identity))) } yield { - Ok(Json.toJson(jsonList)) + Ok(Json.toJson(jsonList)).withHeaders("X-Total-Count" -> annotationCount.toString) } } diff --git a/app/models/annotation/Annotation.scala b/app/models/annotation/Annotation.scala index c9d3ebe6fbe..29f28e8025b 100755 --- a/app/models/annotation/Annotation.scala +++ b/app/models/annotation/Annotation.scala @@ -128,6 +128,23 @@ class AnnotationDAO @Inject()(sqlClient: SQLClient)(implicit ec: ExecutionContex } yield parsed } + def countAllFor(userId: ObjectId, isFinished: Option[Boolean], annotationType: AnnotationType)( + implicit ctx: DBAccessContext) = { + val stateQuery = isFinished match { + case Some(true) => s"state = '${AnnotationState.Finished.toString}'" + case Some(false) => s"state = '${AnnotationState.Active.toString}'" + case None => s"state != '${AnnotationState.Cancelled.toString}'" + } + for { + accessQuery <- readAccessQuery + r <- run( + sql"""select count(*) from #${existingCollectionName} + where _user = ${userId.id} and typ = '#${annotationType.toString}' and #${stateQuery} and #${accessQuery}""" + .as[Int]) + parsed <- r.headOption + } yield parsed + } + // hint: does not use access query (because they dont support prefixes yet). use only after separate access check def findAllFinishedForProject(projectId: ObjectId)(implicit ctx: DBAccessContext): Fox[List[Annotation]] = for { diff --git a/app/models/task/Task.scala b/app/models/task/Task.scala index d969eef7220..0f5465a1f74 100755 --- a/app/models/task/Task.scala +++ b/app/models/task/Task.scala @@ -166,6 +166,15 @@ class TaskDAO @Inject()(sqlClient: SQLClient, projectDAO: ProjectDAO)(implicit e parsed <- Fox.combined(r.toList.map(parse)) } yield parsed + def countAllByProject(projectId: ObjectId)(implicit ctx: DBAccessContext) = + for { + accessQuery <- readAccessQuery + r <- run( + sql"""select count(*) from #${existingCollectionName} where _project = ${projectId.id} and #${accessQuery}""" + .as[Int]) + parsed <- r.headOption + } yield parsed + private def findNextTaskQ(userId: ObjectId, teamIds: List[ObjectId]) = s""" select ${columnsWithPrefix("webknossos.tasks_.")} diff --git a/docs/rest_api.md b/docs/rest_api.md index 73798459ac0..b5db7355090 100644 --- a/docs/rest_api.md +++ b/docs/rest_api.md @@ -63,6 +63,7 @@ List your own task annotations #### Returns - JSON list of objects containing annotation information about your own task annotations, also including task and task type information + - total count of task annotations in the HTTP header `X-Total-Count` --- @@ -81,6 +82,7 @@ List your own explorative annotations #### Returns - JSON list of objects containing annotation information about your own explorative annotations + - total count of explorative annotations in the HTTP header `X-Total-Count` @@ -112,7 +114,7 @@ List the task annotations of a user #### Returns - JSON list of objects containing annotation information about the task annotations of the user, also including task and task type information - + - total count of task annotations in the HTTP header `X-Total-Count` --- ### `GET /users/:id/annotations` @@ -131,6 +133,7 @@ List the explorative annotations of a uaser #### Returns - JSON list of objects containing annotation information about the explorative annotations of the user + - total count of explorative annotations in the HTTP header `X-Total-Count` @@ -531,6 +534,7 @@ List all tasks of a project #### Returns - JSON list of objects containing task information + - total count of tasks in the HTTP header `X-Total-Count` #### Note - For smoother backwards compatibility, the limit defaults to infinity. However, to ease server load and improve response time, we suggest using a limit of 1000 From 7d372ebc7903f596158c7b0fe0c2be6c8e83cb2d Mon Sep 17 00:00:00 2001 From: Youri K Date: Thu, 14 Mar 2019 18:16:05 +0100 Subject: [PATCH 2/3] update changelog #3899 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ae9b3e0d0d..4486bb13d68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,8 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.md). - Added a shortcut (Q) and button in the actions dropdown to screenshot the tracing views. The screenshots will contain everything that is visible in the tracing views, so feel free to disable the crosshairs in the settings or toggle the tree visibility using the (1) and (2) shortcuts before triggering the screenshot. [#3834](https://github.com/scalableminds/webknossos/pull/3834) - Neuroglancer precomputed datasets can now be added to webKnossos using the webknossos-connect (wk-connect) service. To setup a wk-connect datastore follow the instructions in the [Readme](https://github.com/scalableminds/webknossos-connect). Afterwards, datasets can be added through "Add Dataset" - "Add Dataset via wk-connect". [#3843](https://github.com/scalableminds/webknossos/pull/3843) - The dataset settings within the tracing view allow to select between different loading strategies now ("best quality first" and "progressive quality"). Additionally, the rendering can use different magnifications as a fallback (instead of only one magnification). [#3801](https://github.com/scalableminds/webknossos/pull/3801) -- The mapping selection dropbown is now sorted alphabetically. [#3864](https://github.com/scalableminds/webknossos/pull/3864) +- The mapping selection dropdown is now sorted alphabetically. [#3864](https://github.com/scalableminds/webknossos/pull/3864) +- Paginated routes now send a `X-Total-Count` HTTP header which shows how many entries were found in total. [#3899](https://github.com/scalableminds/webknossos/pull/3899) ### Changed - Improved the flight mode performance for tracings with very large trees (>80.000 nodes). [#3880](https://github.com/scalableminds/webknossos/pull/3880) From e759c4b2d849191026e1f7c3dde80265befcb6cf Mon Sep 17 00:00:00 2001 From: Youri K Date: Wed, 20 Mar 2019 13:31:29 +0100 Subject: [PATCH 3/3] implement pr feedback --- app/controllers/ProjectController.scala | 15 +++- app/controllers/UserController.scala | 75 +++++++++++++------ app/models/annotation/Annotation.scala | 19 +++-- conf/webknossos.latest.routes | 10 +-- docs/rest_api.md | 21 ++++-- .../scalableminds/util/tools/Converter.scala | 5 ++ 6 files changed, 101 insertions(+), 44 deletions(-) diff --git a/app/controllers/ProjectController.scala b/app/controllers/ProjectController.scala index eb91c05a1f6..1de88693cda 100644 --- a/app/controllers/ProjectController.scala +++ b/app/controllers/ProjectController.scala @@ -10,6 +10,7 @@ import net.liftweb.common.Empty import oxalis.security.WkEnv import com.mohiva.play.silhouette.api.Silhouette import com.mohiva.play.silhouette.api.actions.{SecuredRequest, UserAwareRequest} +import com.scalableminds.util.tools.DefaultConverters.BoolToOption import play.api.i18n.{Messages, MessagesApi} import play.api.libs.json.Json import utils.ObjectId @@ -120,17 +121,25 @@ class ProjectController @Inject()(projectService: ProjectService, Ok(js) } - def tasksForProject(projectName: String, limit: Option[Int] = None, pageNumber: Option[Int] = None) = + def tasksForProject(projectName: String, + limit: Option[Int] = None, + pageNumber: Option[Int] = None, + includeTotalCount: Option[Boolean]) = sil.SecuredAction.async { implicit request => for { project <- projectDAO.findOneByName(projectName) ?~> Messages("project.notFound", projectName) ~> NOT_FOUND _ <- Fox.assertTrue(userService.isTeamManagerOrAdminOf(request.identity, project._team)) ?~> "notAllowed" ~> FORBIDDEN tasks <- taskDAO.findAllByProject(project._id, limit.getOrElse(Int.MaxValue), pageNumber.getOrElse(0))( GlobalAccessContext) - taskCount <- taskDAO.countAllByProject(project._id)(GlobalAccessContext) + taskCount <- Fox.runOptional(includeTotalCount.flatMap(BoolToOption.convert))(_ => + taskDAO.countAllByProject(project._id)(GlobalAccessContext)) js <- Fox.serialCombined(tasks)(task => taskService.publicWrites(task)) } yield { - Ok(Json.toJson(js)).withHeaders("X-Total-Count" -> taskCount.toString) + val result = Ok(Json.toJson(js)) + taskCount match { + case Some(count) => result.withHeaders("X-Total-Count" -> count.toString) + case None => result + } } } diff --git a/app/controllers/UserController.scala b/app/controllers/UserController.scala index b566b695629..749a59b1f60 100755 --- a/app/controllers/UserController.scala +++ b/app/controllers/UserController.scala @@ -52,7 +52,10 @@ class UserController @Inject()(userService: UserService, } } - def annotations(isFinished: Option[Boolean], limit: Option[Int], pageNumber: Option[Int] = None) = + def annotations(isFinished: Option[Boolean], + limit: Option[Int], + pageNumber: Option[Int] = None, + includeTotalCount: Option[Boolean] = None) = sil.SecuredAction.async { implicit request => for { annotations <- annotationDAO.findAllFor(request.identity._id, @@ -60,26 +63,38 @@ class UserController @Inject()(userService: UserService, AnnotationType.Explorational, limit.getOrElse(defaultAnnotationLimit), pageNumber.getOrElse(0)) - annotationCount <- annotationDAO.countAllFor(request.identity._id, isFinished, AnnotationType.Explorational) + annotationCount <- Fox.runOptional(includeTotalCount.flatMap(BoolToOption.convert))(_ => + annotationDAO.countAllFor(request.identity._id, isFinished, AnnotationType.Explorational)) jsonList <- Fox.serialCombined(annotations)(a => annotationService.compactWrites(a)) } yield { - Ok(Json.toJson(jsonList)).withHeaders("X-Total-Count" -> annotationCount.toString) + val result = Ok(Json.toJson(jsonList)) + annotationCount match { + case Some(count) => result.withHeaders("X-Total-Count" -> count.toString) + case None => result + } } } - def tasks(isFinished: Option[Boolean], limit: Option[Int], pageNumber: Option[Int] = None) = sil.SecuredAction.async { - implicit request => - for { - annotations <- annotationDAO.findAllFor(request.identity._id, - isFinished, - AnnotationType.Task, - limit.getOrElse(defaultAnnotationLimit), - pageNumber.getOrElse(0)) - annotationCount <- annotationDAO.countAllFor(request.identity._id, isFinished, AnnotationType.Task) - jsonList <- Fox.serialCombined(annotations)(a => annotationService.publicWrites(a, Some(request.identity))) - } yield { - Ok(Json.toJson(jsonList)).withHeaders("X-Total-Count" -> annotationCount.toString) + def tasks(isFinished: Option[Boolean], + limit: Option[Int], + pageNumber: Option[Int] = None, + includeTotalCount: Option[Boolean] = None) = sil.SecuredAction.async { implicit request => + for { + annotations <- annotationDAO.findAllFor(request.identity._id, + isFinished, + AnnotationType.Task, + limit.getOrElse(defaultAnnotationLimit), + pageNumber.getOrElse(0)) + annotationCount <- Fox.runOptional(includeTotalCount.flatMap(BoolToOption.convert))(_ => + annotationDAO.countAllFor(request.identity._id, isFinished, AnnotationType.Task)) + jsonList <- Fox.serialCombined(annotations)(a => annotationService.publicWrites(a, Some(request.identity))) + } yield { + val result = Ok(Json.toJson(jsonList)) + annotationCount match { + case Some(count) => result.withHeaders("X-Total-Count" -> count.toString) + case None => result } + } } def userLoggedTime(userId: String) = sil.SecuredAction.async { implicit request => @@ -134,7 +149,11 @@ class UserController @Inject()(userService: UserService, .map(loggedTime => Ok(Json.toJson(loggedTime))) } - def userAnnotations(userId: String, isFinished: Option[Boolean], limit: Option[Int], pageNumber: Option[Int] = None) = + def userAnnotations(userId: String, + isFinished: Option[Boolean], + limit: Option[Int], + pageNumber: Option[Int] = None, + includeTotalCount: Option[Boolean] = None) = sil.SecuredAction.async { implicit request => for { userIdValidated <- ObjectId.parse(userId) ?~> "user.id.invalid" @@ -145,14 +164,23 @@ class UserController @Inject()(userService: UserService, AnnotationType.Explorational, limit.getOrElse(defaultAnnotationLimit), pageNumber.getOrElse(0)) - annotationCount <- annotationDAO.countAllFor(userIdValidated, isFinished, AnnotationType.Explorational) + annotationCount <- Fox.runOptional(includeTotalCount.flatMap(BoolToOption.convert))(_ => + annotationDAO.countAllFor(userIdValidated, isFinished, AnnotationType.Explorational)) jsonList <- Fox.serialCombined(annotations)(a => annotationService.compactWrites(a)) } yield { - Ok(Json.toJson(jsonList)).withHeaders("X-Total-Count" -> annotationCount.toString) + val result = Ok(Json.toJson(jsonList)) + annotationCount match { + case Some(count) => result.withHeaders("X-Total-Count" -> count.toString) + case None => result + } } } - def userTasks(userId: String, isFinished: Option[Boolean], limit: Option[Int], pageNumber: Option[Int] = None) = + def userTasks(userId: String, + isFinished: Option[Boolean], + limit: Option[Int], + pageNumber: Option[Int] = None, + includeTotalCount: Option[Boolean] = None) = sil.SecuredAction.async { implicit request => for { userIdValidated <- ObjectId.parse(userId) ?~> "user.id.invalid" @@ -163,10 +191,15 @@ class UserController @Inject()(userService: UserService, AnnotationType.Task, limit.getOrElse(defaultAnnotationLimit), pageNumber.getOrElse(0)) - annotationCount <- annotationDAO.countAllFor(userIdValidated, isFinished, AnnotationType.Task) + annotationCount <- Fox.runOptional(includeTotalCount.flatMap(BoolToOption.convert))(_ => + annotationDAO.countAllFor(userIdValidated, isFinished, AnnotationType.Task)) jsonList <- Fox.serialCombined(annotations)(a => annotationService.publicWrites(a, Some(request.identity))) } yield { - Ok(Json.toJson(jsonList)).withHeaders("X-Total-Count" -> annotationCount.toString) + val result = Ok(Json.toJson(jsonList)) + annotationCount match { + case Some(count) => result.withHeaders("X-Total-Count" -> count.toString) + case None => result + } } } diff --git a/app/models/annotation/Annotation.scala b/app/models/annotation/Annotation.scala index 29f28e8025b..6a358b27476 100755 --- a/app/models/annotation/Annotation.scala +++ b/app/models/annotation/Annotation.scala @@ -109,16 +109,19 @@ class AnnotationDAO @Inject()(sqlClient: SQLClient)(implicit ec: ExecutionContex parsed <- parse(r) ?~> ("SQLDAO Error: Could not parse database row for object " + id + " in " + collectionName) } yield parsed + private def getStateQuery(isFinished: Option[Boolean]) = + isFinished match { + case Some(true) => s"state = '${AnnotationState.Finished.toString}'" + case Some(false) => s"state = '${AnnotationState.Active.toString}'" + case None => s"state != '${AnnotationState.Cancelled.toString}'" + } + def findAllFor(userId: ObjectId, isFinished: Option[Boolean], annotationType: AnnotationType, limit: Int, pageNumber: Int = 0)(implicit ctx: DBAccessContext): Fox[List[Annotation]] = { - val stateQuery = isFinished match { - case Some(true) => s"state = '${AnnotationState.Finished.toString}'" - case Some(false) => s"state = '${AnnotationState.Active.toString}'" - case None => s"state != '${AnnotationState.Cancelled.toString}'" - } + val stateQuery = getStateQuery(isFinished) for { accessQuery <- readAccessQuery r <- run(sql"""select #${columns} from #${existingCollectionName} @@ -130,11 +133,7 @@ class AnnotationDAO @Inject()(sqlClient: SQLClient)(implicit ec: ExecutionContex def countAllFor(userId: ObjectId, isFinished: Option[Boolean], annotationType: AnnotationType)( implicit ctx: DBAccessContext) = { - val stateQuery = isFinished match { - case Some(true) => s"state = '${AnnotationState.Finished.toString}'" - case Some(false) => s"state = '${AnnotationState.Active.toString}'" - case None => s"state != '${AnnotationState.Cancelled.toString}'" - } + val stateQuery = getStateQuery(isFinished) for { accessQuery <- readAccessQuery r <- run( diff --git a/conf/webknossos.latest.routes b/conf/webknossos.latest.routes index ed7b3ecf8fd..b2e18149e45 100644 --- a/conf/webknossos.latest.routes +++ b/conf/webknossos.latest.routes @@ -38,16 +38,16 @@ GET /user/tasks/peek GET /users controllers.UserController.list GET /user controllers.UserController.current -GET /user/tasks controllers.UserController.tasks(isFinished: Option[Boolean], limit: Option[Int], pageNumber: Option[Int]) -GET /user/annotations controllers.UserController.annotations(isFinished: Option[Boolean], limit: Option[Int], pageNumber: Option[Int]) +GET /user/tasks controllers.UserController.tasks(isFinished: Option[Boolean], limit: Option[Int], pageNumber: Option[Int], includeTotalCount: Option[Boolean]) +GET /user/annotations controllers.UserController.annotations(isFinished: Option[Boolean], limit: Option[Int], pageNumber: Option[Int], includeTotalCount: Option[Boolean]) GET /user/loggedTime controllers.UserController.loggedTime GET /users/:id controllers.UserController.user(id: String) PATCH /users/:id controllers.UserController.update(id: String) PUT /users/:id/taskTypeId controllers.UserController.updateLastTaskTypeId(id: String) -GET /users/:id/tasks controllers.UserController.userTasks(id: String, isFinished: Option[Boolean], limit: Option[Int], pageNumber: Option[Int]) +GET /users/:id/tasks controllers.UserController.userTasks(id: String, isFinished: Option[Boolean], limit: Option[Int], pageNumber: Option[Int], includeTotalCount: Option[Boolean]) GET /users/:id/loggedTime controllers.UserController.userLoggedTime(id: String) POST /users/loggedTime controllers.UserController.usersLoggedTime -GET /users/:id/annotations controllers.UserController.userAnnotations(id: String, isFinished: Option[Boolean], limit: Option[Int], pageNumber: Option[Int]) +GET /users/:id/annotations controllers.UserController.userAnnotations(id: String, isFinished: Option[Boolean], limit: Option[Int], pageNumber: Option[Int], includeTotalCount: Option[Boolean]) # Team GET /teams controllers.TeamController.list @@ -148,7 +148,7 @@ POST /projects GET /projects/:name controllers.ProjectController.read(name: String) DELETE /projects/:name controllers.ProjectController.delete(name: String) PUT /projects/:name controllers.ProjectController.update(name: String) -GET /projects/:name/tasks controllers.ProjectController.tasksForProject(name: String, limit: Option[Int], pageNumber: Option[Int]) +GET /projects/:name/tasks controllers.ProjectController.tasksForProject(name: String, limit: Option[Int], pageNumber: Option[Int], includeTotalCount: Option[Boolean]) PATCH /projects/:name/incrementEachTasksInstances controllers.ProjectController.incrementEachTasksInstances(name: String, delta: Option[Long]) PATCH /projects/:name/pause controllers.ProjectController.pause(name: String) PATCH /projects/:name/resume controllers.ProjectController.resume(name: String) diff --git a/docs/rest_api.md b/docs/rest_api.md index b5db7355090..745faa40965 100644 --- a/docs/rest_api.md +++ b/docs/rest_api.md @@ -60,10 +60,12 @@ List your own task annotations - return only the first x results (defaults to 1000) - Optional GET parameter `pageNumber=[INT]` - return the results starting at offset `limit` * `pageNumber` (defaults to 0) + - Optional GET parameter `includeTotalCount=[BOOLEAN]` + - if true returns the total count of entries (defaults to false) #### Returns - JSON list of objects containing annotation information about your own task annotations, also including task and task type information - - total count of task annotations in the HTTP header `X-Total-Count` + - total count of task annotations in the HTTP header `X-Total-Count` if parameter is set --- @@ -79,10 +81,12 @@ List your own explorative annotations - return only the first x results (defaults to 1000) - Optional GET parameter `pageNumber=[INT]` - return the results starting at offset `limit` * `pageNumber` (defaults to 0) + - Optional GET parameter `includeTotalCount=[BOOLEAN]` + - if true returns the total count of entries (defaults to false) #### Returns - JSON list of objects containing annotation information about your own explorative annotations - - total count of explorative annotations in the HTTP header `X-Total-Count` + - total count of explorative annotations in the HTTP header `X-Total-Count` if parameter is set @@ -111,10 +115,13 @@ List the task annotations of a user - return only the first x results (defaults to 1000) - Optional GET parameter `pageNumber=[INT]` - return the results starting at offset `limit` * `pageNumber` (defaults to 0) + - Optional GET parameter `includeTotalCount=[BOOLEAN]` + - if true returns the total count of entries (defaults to false) #### Returns - JSON list of objects containing annotation information about the task annotations of the user, also including task and task type information - - total count of task annotations in the HTTP header `X-Total-Count` + - total count of task annotations in the HTTP header `X-Total-Count` if parameter is set + - total count of task annotations in the HTTP header `X-Total-Count` if parameter is set --- ### `GET /users/:id/annotations` @@ -130,10 +137,12 @@ List the explorative annotations of a uaser - return only the first x results (defaults to 1000) - Optional GET parameter `pageNumber=[INT]` - return the results starting at offset `limit` * `pageNumber` (defaults to 0) + - Optional GET parameter `includeTotalCount=[BOOLEAN]` + - if true returns the total count of entries (defaults to false) #### Returns - JSON list of objects containing annotation information about the explorative annotations of the user - - total count of explorative annotations in the HTTP header `X-Total-Count` + - total count of explorative annotations in the HTTP header `X-Total-Count` if parameter is set @@ -531,10 +540,12 @@ List all tasks of a project - return only the first x results (defaults to infinity) - Optional GET parameter `pageNumber=[INT]` - return the results starting at offset `limit` * `pageNumber` (defaults to 0) + - Optional GET parameter `includeTotalCount=[BOOLEAN]` + - if true returns the total count of entries (defaults to false) #### Returns - JSON list of objects containing task information - - total count of tasks in the HTTP header `X-Total-Count` + - total count of tasks in the HTTP header `X-Total-Count` if parameter is set #### Note - For smoother backwards compatibility, the limit defaults to infinity. However, to ease server load and improve response time, we suggest using a limit of 1000 diff --git a/util/src/main/scala/com/scalableminds/util/tools/Converter.scala b/util/src/main/scala/com/scalableminds/util/tools/Converter.scala index a2e7d0df56a..218ff00d962 100644 --- a/util/src/main/scala/com/scalableminds/util/tools/Converter.scala +++ b/util/src/main/scala/com/scalableminds/util/tools/Converter.scala @@ -46,6 +46,11 @@ object DefaultConverters { } } + implicit object BoolToOption extends Converter[Boolean, Unit] { + override def convert(a: Boolean): Option[Unit] = + if (a) Some(()) else None + } + implicit object IntArrayToByteArrayConverter extends ArrayConverter[Array[Int], Array[Byte]] { def convert(a: Array[Int], bytesPerElement: Int): Array[Byte] = a.flatMap { value =>