From 46ee7937502830895b73c21151e2eed95c77b869 Mon Sep 17 00:00:00 2001 From: Florian M Date: Thu, 20 Jul 2023 14:42:20 +0200 Subject: [PATCH 01/13] WIP: Use New Task Terminology in Code --- tools/postgres/schema.sql | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/postgres/schema.sql b/tools/postgres/schema.sql index 9c54adc227..59f2c7bac2 100644 --- a/tools/postgres/schema.sql +++ b/tools/postgres/schema.sql @@ -266,7 +266,7 @@ CREATE TABLE webknossos.tasks( neededExperience_domain VARCHAR(256) NOT NULL CHECK (neededExperience_domain ~* '^.{2,}$'), neededExperience_value INT NOT NULL, totalInstances BIGINT NOT NULL, - openInstances BIGINT NOT NULL, + pendingInstances BIGINT NOT NULL, tracingTime BIGINT, boundingBox webknossos.BOUNDING_BOX, editPosition webknossos.VECTOR3 NOT NULL, @@ -274,7 +274,7 @@ CREATE TABLE webknossos.tasks( creationInfo VARCHAR(512), created TIMESTAMPTZ NOT NULL DEFAULT NOW(), isDeleted BOOLEAN NOT NULL DEFAULT false, - CONSTRAINT openInstancesLargeEnoughCheck CHECK (openInstances >= 0) + CONSTRAINT pendingInstancesLargeEnoughCheck CHECK (pendingInstances >= 0) ); CREATE TABLE webknossos.experienceDomains( @@ -780,7 +780,7 @@ $$ LANGUAGE plpgsql; CREATE FUNCTION webknossos.onUpdateTask() RETURNS trigger AS $$ BEGIN IF NEW.totalInstances <> OLD.totalInstances THEN - UPDATE webknossos.tasks SET openInstances = openInstances + (NEW.totalInstances - OLD.totalInstances) WHERE _id = NEW._id; + UPDATE webknossos.tasks SET pendingInstances = pendingInstances + (NEW.totalInstances - OLD.totalInstances) WHERE _id = NEW._id; END IF; RETURN NULL; END; @@ -794,7 +794,7 @@ FOR EACH ROW EXECUTE PROCEDURE webknossos.onUpdateTask(); CREATE FUNCTION webknossos.onInsertAnnotation() RETURNS trigger AS $$ BEGIN IF (NEW.typ = 'Task') AND (NEW.isDeleted = false) AND (NEW.state != 'Cancelled') THEN - UPDATE webknossos.tasks SET openInstances = openInstances - 1 WHERE _id = NEW._task; + UPDATE webknossos.tasks SET pendingInstances = pendingInstances - 1 WHERE _id = NEW._task; END IF; RETURN NULL; END; @@ -813,11 +813,11 @@ CREATE OR REPLACE FUNCTION webknossos.onUpdateAnnotation() RETURNS trigger AS $$ END IF; IF (webknossos.countsAsTaskInstance(OLD) AND NOT webknossos.countsAsTaskInstance(NEW)) THEN - UPDATE webknossos.tasks SET openInstances = openInstances + 1 WHERE _id = NEW._task; + UPDATE webknossos.tasks SET pendingInstances = pendingInstances + 1 WHERE _id = NEW._task; END IF; IF (NOT webknossos.countsAsTaskInstance(OLD) AND webknossos.countsAsTaskInstance(NEW)) THEN - UPDATE webknossos.tasks SET openInstances = openInstances - 1 WHERE _id = NEW._task; + UPDATE webknossos.tasks SET pendingInstances = pendingInstances - 1 WHERE _id = NEW._task; END IF; RETURN NULL; END; @@ -831,7 +831,7 @@ FOR EACH ROW EXECUTE PROCEDURE webknossos.onUpdateAnnotation(); CREATE FUNCTION webknossos.onDeleteAnnotation() RETURNS trigger AS $$ BEGIN IF (OLD.typ = 'Task') AND (OLD.isDeleted = false) AND (OLD.state != 'Cancelled') THEN - UPDATE webknossos.tasks SET openInstances = openInstances + 1 WHERE _id = OLD._task; + UPDATE webknossos.tasks SET pendingInstances = pendingInstances + 1 WHERE _id = OLD._task; END IF; RETURN NULL; END; From d6d79b6ac4e74098aaaeba1713200b0e003ad5b9 Mon Sep 17 00:00:00 2001 From: Florian M Date: Thu, 20 Jul 2023 15:02:22 +0200 Subject: [PATCH 02/13] update more spots --- app/controllers/ProjectController.scala | 6 ++-- app/controllers/ReportController.scala | 36 ++++++++++++-------- app/controllers/StatisticsController.scala | 2 +- app/controllers/TaskController.scala | 2 +- app/models/task/Task.scala | 26 +++++++------- app/models/task/TaskCreationParameters.scala | 2 +- app/models/task/TaskCreationService.scala | 6 ++-- app/models/task/TaskService.scala | 2 +- 8 files changed, 45 insertions(+), 37 deletions(-) diff --git a/app/controllers/ProjectController.scala b/app/controllers/ProjectController.scala index ef87b060fa..9fade6fecc 100644 --- a/app/controllers/ProjectController.scala +++ b/app/controllers/ProjectController.scala @@ -42,7 +42,7 @@ class ProjectController @Inject()(projectService: ProjectService, def listWithStatus: Action[AnyContent] = sil.SecuredAction.async { implicit request => for { projects <- projectDAO.findAll ?~> "project.list.failed" - allCounts <- taskDAO.countOpenInstancesAndTimeByProject + allCounts <- taskDAO.countPendingInstancesAndTimeByProject js <- Fox.serialCombined(projects) { project => for { openInstancesAndTime <- Fox.successful(allCounts.getOrElse(project._id, (0L, 0L))) @@ -165,7 +165,7 @@ Expects: taskTypeIdValidated <- ObjectId.fromString(taskTypeId) _ <- taskTypeDAO.findOne(taskTypeIdValidated) ?~> "taskType.notFound" ~> NOT_FOUND projects <- projectDAO.findAllWithTaskType(taskTypeId) ?~> "project.list.failed" - allCounts <- taskDAO.countOpenInstancesAndTimeByProject + allCounts <- taskDAO.countPendingInstancesAndTimeByProject js <- Fox.serialCombined(projects) { project => for { openInstancesAndTime <- Fox.successful(allCounts.getOrElse(project._id, (0L, 0L))) @@ -217,7 +217,7 @@ Expects: projectIdValidated <- ObjectId.fromString(id) project <- projectDAO.findOne(projectIdValidated) ?~> "project.notFound" ~> NOT_FOUND _ <- taskDAO.incrementTotalInstancesOfAllWithProject(project._id, delta.getOrElse(1L)) - openInstancesAndTime <- taskDAO.countOpenInstancesAndTimeForProject(project._id) + openInstancesAndTime <- taskDAO.countPendingInstancesAndTimeForProject(project._id) js <- projectService.publicWritesWithStatus(project, openInstancesAndTime._1, openInstancesAndTime._2) } yield Ok(js) } diff --git a/app/controllers/ReportController.scala b/app/controllers/ReportController.scala index 705e28cac7..dd0ccce38b 100644 --- a/app/controllers/ReportController.scala +++ b/app/controllers/ReportController.scala @@ -14,15 +14,20 @@ import utils.sql.{SimpleSQLDAO, SqlClient} import javax.inject.Inject import scala.concurrent.ExecutionContext -case class OpenTasksEntry(id: String, user: String, totalAssignments: Int, assignmentsByProjects: Map[String, Int]) -object OpenTasksEntry { implicit val jsonFormat: OFormat[OpenTasksEntry] = Json.format[OpenTasksEntry] } +case class AvailableTaskCountsEntry(id: String, + user: String, + totalAvailableTasks: Int, + availableTasksByProject: Map[String, Int]) +object AvailableTaskCountsEntry { + implicit val jsonFormat: OFormat[AvailableTaskCountsEntry] = Json.format[AvailableTaskCountsEntry] +} case class ProjectProgressEntry(projectName: String, paused: Boolean, priority: Long, totalTasks: Int, totalInstances: Int, - openInstances: Int, + pendingInstances: Int, finishedInstances: Int, activeInstances: Int, billedMilliseconds: Long) @@ -70,7 +75,7 @@ class ReportDAO @Inject()(sqlClient: SqlClient, annotationDAO: AnnotationDAO)(im p.priority priority, count(t._id) totalTasks, sum(t.totalInstances) totalInstances, - sum(t.openInstances) openInstances, + sum(t.pendingInstances) pendingInstances, sum(t.tracingTime) tracingTime from filteredProjects p @@ -87,17 +92,17 @@ class ReportDAO @Inject()(sqlClient: SqlClient, annotationDAO: AnnotationDAO)(im ) - select s1.projectName, s1.paused, s1.priority, s1.totalTasks, s1.totalInstances, s1.openInstances, (s1.totalInstances - s1.openInstances - s2.activeInstances) finishedInstances, s2.activeInstances, s1.tracingTime + select s1.projectName, s1.paused, s1.priority, s1.totalTasks, s1.totalInstances, s1.pendingInstances, (s1.totalInstances - s1.pendingInstances - s2.activeInstances) finishedInstances, s2.activeInstances, s1.tracingTime from s1 join s2 on s1._id = s2._id join projectModifiedTimes pmt on s1._id = pmt._id - where (not (s1.paused and s1.totalInstances = s1.openInstances)) and ((s1.openInstances > 0 and not s1.paused) or s2.activeInstances > 0 or pmt.modified > NOW() - INTERVAL '30 days') + where (not (s1.paused and s1.totalInstances = s1.pendingInstances)) and ((s1.pendingInstances > 0 and not s1.paused) or s2.activeInstances > 0 or pmt.modified > NOW() - INTERVAL '30 days') """.as[(String, Boolean, Long, Int, Int, Int, Int, Int, Long)]) } yield { r.toList.map(row => ProjectProgressEntry(row._1, row._2, row._3, row._4, row._5, row._6, row._7, row._8, row._9)) } - def getAssignmentsByProjectsFor(userId: ObjectId): Fox[Map[String, Int]] = + def getAvailableTaskCountsByProjectsFor(userId: ObjectId): Fox[Map[String, Int]] = for { r <- run(q""" select p._id, p.name, t.neededExperience_domain, t.neededExperience_value, count(t._id) @@ -110,14 +115,14 @@ class ReportDAO @Inject()(sqlClient: SqlClient, annotationDAO: AnnotationDAO)(im as ue on t.neededExperience_domain = ue.domain and t.neededExperience_value <= ue.value join webknossos.projects_ p on t._project = p._id left join (select _task from webknossos.annotations_ where _user = $userId and typ = ${AnnotationType.Task}) as userAnnotations ON t._id = userAnnotations._task - where t.openInstances > 0 + where t.pendingInstances > 0 and userAnnotations._task is null and not p.paused group by p._id, p.name, t.neededExperience_domain, t.neededExperience_value """.as[(String, String, String, Int, Int)]) } yield { val formattedList = r.toList.map(row => (row._2 + "/" + row._3 + ": " + row._4, row._5)) - formattedList.toMap.filter(_ match { case (_: String, openTaskCount: Int) => openTaskCount > 0 }) + formattedList.toMap.filter(_ match { case (_: String, availableTasksCount: Int) => availableTasksCount > 0 }) } } @@ -138,23 +143,26 @@ class ReportController @Inject()(reportDAO: ReportDAO, } yield Ok(Json.toJson(entries)) } - def openTasksOverview(teamId: String): Action[AnyContent] = sil.SecuredAction.async { implicit request => + def availableTasksOverview(teamId: String): Action[AnyContent] = sil.SecuredAction.async { implicit request => for { teamIdValidated <- ObjectId.fromString(teamId) team <- teamDAO.findOne(teamIdValidated) ?~> "team.notFound" ~> NOT_FOUND users <- userDAO.findAllByTeams(List(team._id)) nonUnlistedUsers = users.filter(!_.isUnlisted) nonAdminUsers <- Fox.filterNot(nonUnlistedUsers)(u => userService.isTeamManagerOrAdminOf(u, teamIdValidated)) - entries: List[OpenTasksEntry] <- getAllAvailableTaskCountsAndProjects(nonAdminUsers) + entries: List[AvailableTaskCountsEntry] <- getAvailableTaskCountsAndProjects(nonAdminUsers) } yield Ok(Json.toJson(entries)) } - private def getAllAvailableTaskCountsAndProjects(users: Seq[User]): Fox[List[OpenTasksEntry]] = { + private def getAvailableTaskCountsAndProjects(users: Seq[User]): Fox[List[AvailableTaskCountsEntry]] = { val foxes = users.map { user => for { - assignmentCountsByProject <- reportDAO.getAssignmentsByProjectsFor(user._id) + pendingTaskCountsByProject <- reportDAO.getAvailableTaskCountsByProjectsFor(user._id) } yield { - OpenTasksEntry(user._id.toString, user.name, assignmentCountsByProject.values.sum, assignmentCountsByProject) + AvailableTaskCountsEntry(user._id.toString, + user.name, + pendingTaskCountsByProject.values.sum, + pendingTaskCountsByProject) } } Fox.combined(foxes.toList) diff --git a/app/controllers/StatisticsController.scala b/app/controllers/StatisticsController.scala index 28456dcb17..67da1eae05 100644 --- a/app/controllers/StatisticsController.scala +++ b/app/controllers/StatisticsController.scala @@ -55,7 +55,7 @@ class StatisticsController @Inject()(timeSpanService: TimeSpanService, numberOfUsers <- userDAO.countAllForOrganization(organizationId) numberOfDatasets <- dataSetDAO.countAllForOrganization(organizationId) numberOfAnnotations <- annotationDAO.countAllForOrganization(organizationId) - numberOfAssignments <- taskDAO.countAllOpenInstancesForOrganization(organizationId) + numberOfAssignments <- taskDAO.countAllPendingInstancesForOrganization(organizationId) } yield { Ok( Json.obj( diff --git a/app/controllers/TaskController.scala b/app/controllers/TaskController.scala index e4ff75d099..e72de12b43 100755 --- a/app/controllers/TaskController.scala +++ b/app/controllers/TaskController.scala @@ -141,7 +141,7 @@ Expects: project <- projectDAO.findOne(task._project) _ <- Fox .assertTrue(userService.isTeamManagerOrAdminOf(request.identity, project._team)) ?~> "notAllowed" ~> FORBIDDEN - _ <- taskDAO.updateTotalInstances(task._id, task.totalInstances + params.openInstances - task.openInstances) + _ <- taskDAO.updateTotalInstances(task._id, task.totalInstances + params.pendingInstances - task.pendingInstances) updatedTask <- taskDAO.findOne(taskIdValidated) json <- taskService.publicWrites(updatedTask) } yield JsonOk(json, Messages("task.editSuccess")) diff --git a/app/models/task/Task.scala b/app/models/task/Task.scala index e6d58c5025..3960c7abb6 100755 --- a/app/models/task/Task.scala +++ b/app/models/task/Task.scala @@ -25,7 +25,7 @@ case class Task( _taskType: ObjectId, neededExperience: Experience, totalInstances: Long, - openInstances: Long, + pendingInstances: Long, tracingTime: Option[Long], boundingBox: Option[BoundingBox], editPosition: Vec3Int, @@ -54,7 +54,7 @@ class TaskDAO @Inject()(sqlClient: SqlClient, projectDAO: ProjectDAO)(implicit e ObjectId(r._Tasktype), Experience(r.neededexperienceDomain, r.neededexperienceValue), r.totalinstances, - r.openinstances, + r.pendinginstances, r.tracingtime, r.boundingbox.map(b => parseArrayLiteral(b).map(_.toInt)).flatMap(BoundingBox.fromSQL), editPosition, @@ -123,7 +123,7 @@ class TaskDAO @Inject()(sqlClient: SqlClient, projectDAO: ProjectDAO)(implicit e left join ( select _task from webknossos.annotations_ where _user = $userId and typ = ${AnnotationType.Task} and not ($isTeamManagerOrAdmin and state = ${AnnotationState.Cancelled}) ) as userAnnotations ON webknossos.tasks_._id = userAnnotations._task - where webknossos.tasks_.openInstances > 0 + where webknossos.tasks_.pendingInstances > 0 and webknossos.projects_._team in ${SqlToken.tupleFromList(teamIds)} and userAnnotations._task is null and not webknossos.projects_.paused @@ -133,7 +133,7 @@ class TaskDAO @Inject()(sqlClient: SqlClient, projectDAO: ProjectDAO)(implicit e private def findNextTaskByIdQ(taskId: ObjectId) = q"""select $columns from $existingCollectionName - where _id = $taskId and openInstances > 0 + where _id = $taskId and pendingInstances > 0 limit 1 """ @@ -156,7 +156,7 @@ class TaskDAO @Inject()(sqlClient: SqlClient, projectDAO: ProjectDAO)(implicit e _ <- run( insertAnnotationQ.withTransactionIsolation(Serializable), retryCount = 50, - retryIfErrorContains = List(transactionSerializationError, "Negative openInstances for Task") + retryIfErrorContains = List(transactionSerializationError, "Negative pendingInstances for Task") ) r <- run(findTaskOfInsertedAnnotationQ(annotationId)) parsed <- parseFirst(r, "task assignment query") @@ -187,7 +187,7 @@ class TaskDAO @Inject()(sqlClient: SqlClient, projectDAO: ProjectDAO)(implicit e _ <- run( insertAnnotationQ.withTransactionIsolation(Serializable), retryCount = 50, - retryIfErrorContains = List(transactionSerializationError, "Negative openInstances for Task") + retryIfErrorContains = List(transactionSerializationError, "Negative pendingInstances for Task") ) r <- run(findTaskOfInsertedAnnotationQ(annotationId)) parsed <- parseFirst(r, "task assignment query") @@ -247,26 +247,26 @@ class TaskDAO @Inject()(sqlClient: SqlClient, projectDAO: ProjectDAO)(implicit e } yield parsed } - def countAllOpenInstancesForOrganization(organizationId: ObjectId): Fox[Long] = + def countAllPendingInstancesForOrganization(organizationId: ObjectId): Fox[Long] = for { - result <- run(q"""SELECT SUM(t.openInstances) + result <- run(q"""SELECT SUM(t.pendingInstances) FROM webknossos.tasks_ t JOIN webknossos.projects_ p ON t._project = p._id WHERE $organizationId in (select _organization from webknossos.users_ where _id = p._owner)""".as[Long]) firstResult <- result.headOption } yield firstResult - def countOpenInstancesAndTimeForProject(projectId: ObjectId): Fox[(Long, Long)] = + def countPendingInstancesAndTimeForProject(projectId: ObjectId): Fox[(Long, Long)] = for { - result <- run(q"""select sum(openInstances), sum(tracingtime) + result <- run(q"""select sum(pendingInstances), sum(tracingtime) from webknossos.tasks_ where _project = $projectId group by _project""".as[(Long, Option[Long])]) firstResult <- result.headOption } yield (firstResult._1, firstResult._2.getOrElse(0L)) - def countOpenInstancesAndTimeByProject: Fox[Map[ObjectId, (Long, Long)]] = + def countPendingInstancesAndTimeByProject: Fox[Map[ObjectId, (Long, Long)]] = for { - rowsRaw <- run(q"""select _project, sum(openInstances), sum(tracingtime) + rowsRaw <- run(q"""select _project, sum(pendingInstances), sum(tracingtime) from webknossos.tasks_ group by _project""".as[(String, Long, Option[Long])]) } yield rowsRaw.toList.map(r => (ObjectId(r._1), (r._2, r._3.getOrElse(0L)))).toMap @@ -281,7 +281,7 @@ class TaskDAO @Inject()(sqlClient: SqlClient, projectDAO: ProjectDAO)(implicit e for { _ <- run(q"""insert into webknossos.tasks(_id, _project, _script, _taskType, neededExperience_domain, neededExperience_value, - totalInstances, openInstances, tracingTime, boundingBox, + totalInstances, pendingInstances, tracingTime, boundingBox, editPosition, editRotation, creationInfo, created, isDeleted) values(${t._id}, ${t._project}, ${t._script}, ${t._taskType}, diff --git a/app/models/task/TaskCreationParameters.scala b/app/models/task/TaskCreationParameters.scala index c7fa691ba8..aae0d9016a 100644 --- a/app/models/task/TaskCreationParameters.scala +++ b/app/models/task/TaskCreationParameters.scala @@ -6,7 +6,7 @@ import play.api.libs.json.{Format, Json} case class TaskParameters(taskTypeId: String, neededExperience: Experience, - openInstances: Int, + pendingInstances: Int, projectName: String, scriptId: Option[String], boundingBox: Option[BoundingBox], diff --git a/app/models/task/TaskCreationService.scala b/app/models/task/TaskCreationService.scala index 39072b2cde..35daab5cc4 100644 --- a/app/models/task/TaskCreationService.scala +++ b/app/models/task/TaskCreationService.scala @@ -110,7 +110,7 @@ class TaskCreationService @Inject()(taskTypeService: TaskTypeService, annotations <- annotationDAO.findAllByTaskIdAndType(taskId, AnnotationType.Task) } yield { val nonCancelledTaskAnnotations = annotations.filter(_.state != AnnotationState.Cancelled) - if (task.totalInstances == 1 && task.openInstances == 0 && + if (task.totalInstances == 1 && task.pendingInstances == 0 && nonCancelledTaskAnnotations.nonEmpty && nonCancelledTaskAnnotations.head.state == AnnotationState.Finished) Fox.successful(nonCancelledTaskAnnotations.head) @@ -539,8 +539,8 @@ class TaskCreationService @Inject()(taskTypeService: TaskTypeService, params.scriptId.map(ObjectId(_)), taskTypeIdValidated, params.neededExperience.trim, - params.openInstances, //all instances are open at this time - params.openInstances, + params.pendingInstances, //all instances are open at this time + params.pendingInstances, tracingTime = None, boundingBox = params.boundingBox.flatMap { box => if (box.isEmpty) None else Some(box) diff --git a/app/models/task/TaskService.scala b/app/models/task/TaskService.scala index 5f6f46deb0..9464151d9f 100644 --- a/app/models/task/TaskService.scala +++ b/app/models/task/TaskService.scala @@ -85,6 +85,6 @@ class TaskService @Inject()(conf: WkConf, def statusOf(task: Task)(implicit ctx: DBAccessContext): Fox[CompletionStatus] = for { activeCount <- annotationDAO.countActiveByTask(task._id, AnnotationType.Task).getOrElse(0) - } yield CompletionStatus(task.openInstances, activeCount, task.totalInstances - (activeCount + task.openInstances)) + } yield CompletionStatus(task.pendingInstances, activeCount, task.totalInstances - (activeCount + task.pendingInstances)) } From 84c5a8b96b4113fd3afbd5774c1eb364c1fdc71d Mon Sep 17 00:00:00 2001 From: Florian M Date: Thu, 20 Jul 2023 15:13:00 +0200 Subject: [PATCH 03/13] remaining usages in backend --- app/controllers/ProjectController.scala | 12 ++++++------ app/controllers/TaskController.scala | 5 +++-- app/models/task/TaskCreationParameters.scala | 2 +- app/models/task/TaskCreationService.scala | 2 +- conf/webknossos.latest.routes | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/controllers/ProjectController.scala b/app/controllers/ProjectController.scala index 9fade6fecc..9b13c8318f 100644 --- a/app/controllers/ProjectController.scala +++ b/app/controllers/ProjectController.scala @@ -45,8 +45,8 @@ class ProjectController @Inject()(projectService: ProjectService, allCounts <- taskDAO.countPendingInstancesAndTimeByProject js <- Fox.serialCombined(projects) { project => for { - openInstancesAndTime <- Fox.successful(allCounts.getOrElse(project._id, (0L, 0L))) - r <- projectService.publicWritesWithStatus(project, openInstancesAndTime._1, openInstancesAndTime._2) + pendingInstancesAndTime <- Fox.successful(allCounts.getOrElse(project._id, (0L, 0L))) + r <- projectService.publicWritesWithStatus(project, pendingInstancesAndTime._1, pendingInstancesAndTime._2) } yield r } } yield Ok(Json.toJson(js)) @@ -168,8 +168,8 @@ Expects: allCounts <- taskDAO.countPendingInstancesAndTimeByProject js <- Fox.serialCombined(projects) { project => for { - openInstancesAndTime <- Fox.successful(allCounts.getOrElse(project._id, (0L, 0L))) - r <- projectService.publicWritesWithStatus(project, openInstancesAndTime._1, openInstancesAndTime._2) + pendingInstancesAndTime <- Fox.successful(allCounts.getOrElse(project._id, (0L, 0L))) + r <- projectService.publicWritesWithStatus(project, pendingInstancesAndTime._1, pendingInstancesAndTime._2) } yield r } } yield { @@ -217,8 +217,8 @@ Expects: projectIdValidated <- ObjectId.fromString(id) project <- projectDAO.findOne(projectIdValidated) ?~> "project.notFound" ~> NOT_FOUND _ <- taskDAO.incrementTotalInstancesOfAllWithProject(project._id, delta.getOrElse(1L)) - openInstancesAndTime <- taskDAO.countPendingInstancesAndTimeForProject(project._id) - js <- projectService.publicWritesWithStatus(project, openInstancesAndTime._1, openInstancesAndTime._2) + pendingInstancesAndTime <- taskDAO.countPendingInstancesAndTimeForProject(project._id) + js <- projectService.publicWritesWithStatus(project, pendingInstancesAndTime._1, pendingInstancesAndTime._2) } yield Ok(js) } diff --git a/app/controllers/TaskController.scala b/app/controllers/TaskController.scala index e72de12b43..a3d0595fc8 100755 --- a/app/controllers/TaskController.scala +++ b/app/controllers/TaskController.scala @@ -83,7 +83,7 @@ Expects: - As Form data: - taskTypeId (string) id of the task type to be used for the new tasks - neededExperience (Experience) experience domain and level that selects which users can get the new tasks - - openInstances (int) if greater than one, multiple instances of the task will be given to users to annotate + - pendingInstances (int) if greater than one, multiple instances of the task will be given to users to annotate - projectName (string) name of the project the task should be part of - scriptId (string, optional) id of a user script that should be loaded for the annotators of the new tasks - boundingBox (BoundingBox, optional) limit the bounding box where the annotators should be active @@ -141,7 +141,8 @@ Expects: project <- projectDAO.findOne(task._project) _ <- Fox .assertTrue(userService.isTeamManagerOrAdminOf(request.identity, project._team)) ?~> "notAllowed" ~> FORBIDDEN - _ <- taskDAO.updateTotalInstances(task._id, task.totalInstances + params.pendingInstances - task.pendingInstances) + _ <- taskDAO.updateTotalInstances(task._id, + task.totalInstances + params.pendingInstances - task.pendingInstances) updatedTask <- taskDAO.findOne(taskIdValidated) json <- taskService.publicWrites(updatedTask) } yield JsonOk(json, Messages("task.editSuccess")) diff --git a/app/models/task/TaskCreationParameters.scala b/app/models/task/TaskCreationParameters.scala index aae0d9016a..df03381d24 100644 --- a/app/models/task/TaskCreationParameters.scala +++ b/app/models/task/TaskCreationParameters.scala @@ -23,7 +23,7 @@ object TaskParameters { case class NmlTaskParameters(taskTypeId: String, neededExperience: Experience, - openInstances: Int, + pendingInstances: Int, projectName: String, scriptId: Option[String], boundingBox: Option[BoundingBox]) diff --git a/app/models/task/TaskCreationService.scala b/app/models/task/TaskCreationService.scala index 35daab5cc4..0b44cd3c3d 100644 --- a/app/models/task/TaskCreationService.scala +++ b/app/models/task/TaskCreationService.scala @@ -264,7 +264,7 @@ class TaskCreationService @Inject()(taskTypeService: TaskTypeService, TaskParameters( nmlFormParams.taskTypeId, nmlFormParams.neededExperience, - nmlFormParams.openInstances, + nmlFormParams.pendingInstances, nmlFormParams.projectName, nmlFormParams.scriptId, bbox, diff --git a/conf/webknossos.latest.routes b/conf/webknossos.latest.routes index 5b8430ce1f..5646c0ccfa 100644 --- a/conf/webknossos.latest.routes +++ b/conf/webknossos.latest.routes @@ -63,7 +63,7 @@ GET /users/:id/annotations GET /teams controllers.TeamController.list(isEditable: Option[Boolean]) POST /teams controllers.TeamController.create DELETE /teams/:id controllers.TeamController.delete(id: String) -GET /teams/:id/openTasksOverview controllers.ReportController.openTasksOverview(id: String) +GET /teams/:id/availableTasksOverview controllers.ReportController.availableTasksOverview(id: String) GET /teams/:id/progressOverview controllers.ReportController.projectProgressOverview(id: String) # DataSets From 78c31c73ad37393ae503b934cc97a4ffdc39373f Mon Sep 17 00:00:00 2001 From: Florian M Date: Thu, 20 Jul 2023 16:01:03 +0200 Subject: [PATCH 04/13] more usages, some frontend --- app/models/task/CompletionStatus.scala | 11 ----------- app/models/task/TaskService.scala | 8 ++++---- app/models/task/TaskStatus.scala | 11 +++++++++++ .../admin/statistic/project_progress_report_view.tsx | 6 +++--- .../javascripts/admin/task/task_create_bulk_view.tsx | 8 ++++---- .../javascripts/admin/task/task_create_form_view.tsx | 8 ++++---- frontend/javascripts/admin/task/task_list_view.tsx | 4 ++-- .../test/backend-snapshot-tests/tasks.e2e.ts | 10 +++++----- frontend/javascripts/types/api_flow_types.ts | 4 ++-- 9 files changed, 35 insertions(+), 35 deletions(-) delete mode 100755 app/models/task/CompletionStatus.scala create mode 100755 app/models/task/TaskStatus.scala diff --git a/app/models/task/CompletionStatus.scala b/app/models/task/CompletionStatus.scala deleted file mode 100755 index 82e2802458..0000000000 --- a/app/models/task/CompletionStatus.scala +++ /dev/null @@ -1,11 +0,0 @@ -package models.task - -import play.api.libs.json.{Json, OFormat} - -case class CompletionStatus(open: Long, active: Long, finished: Long) { - def total: Long = open + active + finished -} - -object CompletionStatus { - implicit val jsonFormat: OFormat[CompletionStatus] = Json.format[CompletionStatus] -} diff --git a/app/models/task/TaskService.scala b/app/models/task/TaskService.scala index 9464151d9f..555daa3a14 100644 --- a/app/models/task/TaskService.scala +++ b/app/models/task/TaskService.scala @@ -31,7 +31,7 @@ class TaskService @Inject()(conf: WkConf, for { annotationBase <- annotationBaseFor(task._id) dataSet <- dataSetDAO.findOne(annotationBase._dataSet) - status <- statusOf(task).getOrElse(CompletionStatus(-1, -1, -1)) + status <- statusOf(task).getOrElse(TaskStatus(-1, -1, -1)) taskType <- taskTypeDAO.findOne(task._taskType)(GlobalAccessContext) taskTypeJs <- taskTypeService.publicWrites(taskType) scriptInfo <- task._script.toFox.flatMap(sid => scriptDAO.findOne(sid)).futureBox @@ -77,14 +77,14 @@ class TaskService @Inject()(conf: WkConf, result <- annotationDAO.countActiveAnnotationsFor(user._id, AnnotationType.Task, teamManagerTeamIds) } yield result - def annotationBaseFor(taskId: ObjectId)(implicit ctx: DBAccessContext): Fox[Annotation] = + private def annotationBaseFor(taskId: ObjectId)(implicit ctx: DBAccessContext): Fox[Annotation] = (for { list <- annotationDAO.findAllByTaskIdAndType(taskId, AnnotationType.TracingBase) } yield list.headOption.toFox).flatten - def statusOf(task: Task)(implicit ctx: DBAccessContext): Fox[CompletionStatus] = + private def statusOf(task: Task)(implicit ctx: DBAccessContext): Fox[TaskStatus] = for { activeCount <- annotationDAO.countActiveByTask(task._id, AnnotationType.Task).getOrElse(0) - } yield CompletionStatus(task.pendingInstances, activeCount, task.totalInstances - (activeCount + task.pendingInstances)) + } yield TaskStatus(task.pendingInstances, activeCount, task.totalInstances - (activeCount + task.pendingInstances)) } diff --git a/app/models/task/TaskStatus.scala b/app/models/task/TaskStatus.scala new file mode 100755 index 0000000000..907321120e --- /dev/null +++ b/app/models/task/TaskStatus.scala @@ -0,0 +1,11 @@ +package models.task + +import play.api.libs.json.{Json, OFormat} + +case class TaskStatus(pending: Long, active: Long, finished: Long) { + def total: Long = pending + active + finished +} + +object TaskStatus { + implicit val jsonFormat: OFormat[TaskStatus] = Json.format[TaskStatus] +} diff --git a/frontend/javascripts/admin/statistic/project_progress_report_view.tsx b/frontend/javascripts/admin/statistic/project_progress_report_view.tsx index 3599401a46..63308dfc82 100644 --- a/frontend/javascripts/admin/statistic/project_progress_report_view.tsx +++ b/frontend/javascripts/admin/statistic/project_progress_report_view.tsx @@ -212,7 +212,7 @@ class ProjectProgressReportView extends React.PureComponent<{}, State> { // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'. b={item.activeInstances} // @ts-expect-error ts-migrate(2571) FIXME: Object is of type 'unknown'. - c={item.openInstances} + c={item.pendingInstances} /> ), })} @@ -244,8 +244,8 @@ class ProjectProgressReportView extends React.PureComponent<{}, State> { }} /> } - dataIndex="openInstances" - sorter={Utils.compareBy(typeHint, (project) => project.openInstances)} + dataIndex="pendingInstances" + sorter={Utils.compareBy(typeHint, (project) => project.pendingInstances)} render={() => ({ props: { colSpan: 0, diff --git a/frontend/javascripts/admin/task/task_create_bulk_view.tsx b/frontend/javascripts/admin/task/task_create_bulk_view.tsx index 126008f389..8adc1fa21d 100644 --- a/frontend/javascripts/admin/task/task_create_bulk_view.tsx +++ b/frontend/javascripts/admin/task/task_create_bulk_view.tsx @@ -23,7 +23,7 @@ export type NewTask = { }; readonly projectName: string; readonly scriptId: string | null | undefined; - readonly openInstances: number; + readonly pendingInstances: number; readonly taskTypeId: string; readonly csvFile?: File; readonly nmlFiles?: File; @@ -73,7 +73,7 @@ function TaskCreateBulkView() { !_.isString(task.projectName) || task.editPosition.some(Number.isNaN) || task.editRotation.some(Number.isNaN) || - Number.isNaN(task.openInstances) || + Number.isNaN(task.pendingInstances) || Number.isNaN(task.neededExperience.value) || // Bounding Box is optional and can be null (boundingBox != null ? boundingBox.topLeft.some(Number.isNaN) || @@ -120,7 +120,7 @@ function TaskCreateBulkView() { const rotX = parseInt(words[7]); const rotY = parseInt(words[8]); const rotZ = parseInt(words[9]); - const openInstances = parseInt(words[10]); + const pendingInstances = parseInt(words[10]); const boundingBoxX = parseInt(words[11]); const boundingBoxY = parseInt(words[12]); const boundingBoxZ = parseInt(words[13]); @@ -152,7 +152,7 @@ function TaskCreateBulkView() { dataSet, taskTypeId, scriptId, - openInstances, + pendingInstances, boundingBox, projectName, neededExperience: { diff --git a/frontend/javascripts/admin/task/task_create_form_view.tsx b/frontend/javascripts/admin/task/task_create_form_view.tsx index 141b309ac3..a7fa53cfa3 100644 --- a/frontend/javascripts/admin/task/task_create_form_view.tsx +++ b/frontend/javascripts/admin/task/task_create_form_view.tsx @@ -97,7 +97,7 @@ export function taskToText(task: APITask) { const [editPositionAsString, editRotationAsString] = [editPosition, editRotation].map((vector3) => vector3.join(","), ); - const totalNumberOfInstances = status.open + status.active + status.finished; + const totalNumberOfInstances = status.pending + status.active + status.finished; const boundingBoxAsString = boundingBoxVec6 ? boundingBoxVec6.join(",") : "0,0,0,0,0,0"; const scriptId = script ? `${script.id}` : ""; const creationInfoOrEmpty = creationInfo || ""; @@ -337,7 +337,7 @@ class TaskCreateFormView extends React.PureComponent { taskTypeId: task.type.id, boundingBox: task.boundingBox ? task.boundingBoxVec6 : null, scriptId: task.script ? task.script.id : null, - openInstances: task.status.open, + pendingInstances: task.status.pending, }); const validFormValues = _.omitBy(defaultValues, _.isNull); @@ -490,7 +490,7 @@ class TaskCreateFormView extends React.PureComponent { if ( taskResponse?.dataSet != null && _.isEqual(taskResponse.status, { - open: 0, + pending: 0, active: 0, finished: 1, }) @@ -688,7 +688,7 @@ class TaskCreateFormView extends React.PureComponent { {
- {status.open} + {status.pending}
@@ -482,7 +482,7 @@ class TaskListView extends React.PureComponent { Edit
- {task.status.open > 0 ? ( + {task.status.pending > 0 ? (
diff --git a/frontend/javascripts/test/backend-snapshot-tests/tasks.e2e.ts b/frontend/javascripts/test/backend-snapshot-tests/tasks.e2e.ts index 7b119078cf..0a4dec80ee 100644 --- a/frontend/javascripts/test/backend-snapshot-tests/tasks.e2e.ts +++ b/frontend/javascripts/test/backend-snapshot-tests/tasks.e2e.ts @@ -54,16 +54,16 @@ test.serial("updateTask()", async (t) => { taskTypeId: taskBase.type.id, boundingBox: taskBase.boundingBox ? taskBase.boundingBoxVec6 : null, scriptId: taskBase.script ? taskBase.script.id : null, - openInstances: taskBase.status.open, + pendingInstances: taskBase.status.pending, }), _.isNull, ); // @ts-expect-error ts-migrate(2533) FIXME: Object is possibly 'null' or 'undefined'. - const newTask = { ...task, openInstances: task.openInstances + 10 }; + const newTask = { ...task, pendingInstances: task.pendingInstances + 10 }; // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string | number | string[] | API... Remove this comment to see the full error message const updatedTask = await api.updateTask(task.id, newTask); - t.deepEqual(updatedTask.status.open, newTask.openInstances); + t.deepEqual(updatedTask.status.pending, newTask.pendingInstances); t.snapshot(updatedTask, { id: "tasks-updatedTask", }); @@ -71,7 +71,7 @@ test.serial("updateTask()", async (t) => { // @ts-expect-error ts-migrate(2345) FIXME: Argument of type 'string | number | string[] | API... Remove this comment to see the full error message const revertedTask = await api.updateTask(task.id, task); // @ts-expect-error ts-migrate(2533) FIXME: Object is possibly 'null' or 'undefined'. - t.is(revertedTask.status.open, task.status.open); + t.is(revertedTask.status.pending, task.status.pending); }); test.serial("transferTask()", async (t) => { const taskAnnotationId = "58135c402faeb34e0081c068"; @@ -93,7 +93,7 @@ const newTask = { }, projectName: "Test_Project4", scriptId: null, - openInstances: 3, + pendingInstances: 3, taskTypeId: "570b9f4c2a7c0e4c008da6ee", }; test.serial("createTasks() and deleteTask()", async (t) => { diff --git a/frontend/javascripts/types/api_flow_types.ts b/frontend/javascripts/types/api_flow_types.ts index 6d156fda53..2bc49c8d83 100644 --- a/frontend/javascripts/types/api_flow_types.ts +++ b/frontend/javascripts/types/api_flow_types.ts @@ -320,7 +320,7 @@ export type APITaskType = { readonly tracingType: TracingType; }; export type TaskStatus = { - readonly open: number; + readonly pending: number; readonly active: number; readonly finished: number; }; @@ -504,7 +504,7 @@ export type APIProjectProgressReport = { readonly paused: boolean; readonly totalTasks: number; readonly totalInstances: number; - readonly openInstances: number; + readonly pendingInstances: number; readonly activeInstances: number; readonly finishedInstances: number; readonly priority: number; From 44fcb919a3660a257c70ee3de53e3b333afd540b Mon Sep 17 00:00:00 2001 From: Florian M Date: Thu, 20 Jul 2023 16:25:34 +0200 Subject: [PATCH 05/13] more usages --- app/controllers/ReportController.scala | 4 +- app/models/project/Project.scala | 4 +- conf/webknossos.latest.routes | 6 +-- frontend/javascripts/admin/admin_rest_api.ts | 24 ++++----- .../admin/project/project_list_view.tsx | 46 +++++++++--------- .../statistic/open_tasks_report_view.tsx | 10 ++-- .../admin/statistic/statistic_view.tsx | 8 +-- .../backend-snapshot-tests/projects.e2e.ts | 8 +-- .../timetracking.e2e.ts | 10 ++-- .../fixtures/tasktracing_server_objects.ts | 2 +- .../annotations.e2e.js.md | 4 +- .../annotations.e2e.js.snap | Bin 11679 -> 11681 bytes .../backend-snapshot-tests/projects.e2e.js.md | 12 ++--- .../projects.e2e.js.snap | Bin 2619 -> 2573 bytes .../backend-snapshot-tests/tasks.e2e.js.md | 22 ++++----- .../backend-snapshot-tests/tasks.e2e.js.snap | Bin 5097 -> 5100 bytes .../timetracking.e2e.js.md | 8 +-- .../timetracking.e2e.js.snap | Bin 1972 -> 1977 bytes frontend/javascripts/types/api_flow_types.ts | 10 ++-- 19 files changed, 89 insertions(+), 89 deletions(-) diff --git a/app/controllers/ReportController.scala b/app/controllers/ReportController.scala index dd0ccce38b..aaab847c3f 100644 --- a/app/controllers/ReportController.scala +++ b/app/controllers/ReportController.scala @@ -135,7 +135,7 @@ class ReportController @Inject()(reportDAO: ReportDAO, extends Controller with FoxImplicits { - def projectProgressOverview(teamId: String): Action[AnyContent] = sil.SecuredAction.async { implicit request => + def projectProgressReport(teamId: String): Action[AnyContent] = sil.SecuredAction.async { implicit request => for { teamIdValidated <- ObjectId.fromString(teamId) _ <- teamDAO.findOne(teamIdValidated) ?~> "team.notFound" ~> NOT_FOUND @@ -143,7 +143,7 @@ class ReportController @Inject()(reportDAO: ReportDAO, } yield Ok(Json.toJson(entries)) } - def availableTasksOverview(teamId: String): Action[AnyContent] = sil.SecuredAction.async { implicit request => + def availableTasksReport(teamId: String): Action[AnyContent] = sil.SecuredAction.async { implicit request => for { teamIdValidated <- ObjectId.fromString(teamId) team <- teamDAO.findOne(teamIdValidated) ?~> "team.notFound" ~> NOT_FOUND diff --git a/app/models/project/Project.scala b/app/models/project/Project.scala index 01de61982b..c9faa003fe 100755 --- a/app/models/project/Project.scala +++ b/app/models/project/Project.scala @@ -220,12 +220,12 @@ class ProjectService @Inject()(projectDAO: ProjectDAO, teamDAO: TeamDAO, userSer ) } - def publicWritesWithStatus(project: Project, openTaskInstances: Long, tracingTime: Long)( + def publicWritesWithStatus(project: Project, pendingInstances: Long, tracingTime: Long)( implicit ctx: DBAccessContext): Fox[JsObject] = for { projectJson <- publicWrites(project) } yield { - projectJson ++ Json.obj("numberOfOpenAssignments" -> JsNumber(openTaskInstances), "tracingTime" -> tracingTime) + projectJson ++ Json.obj("pendingInstances" -> JsNumber(pendingInstances), "tracingTime" -> tracingTime) } } diff --git a/conf/webknossos.latest.routes b/conf/webknossos.latest.routes index 5646c0ccfa..6b6298a48d 100644 --- a/conf/webknossos.latest.routes +++ b/conf/webknossos.latest.routes @@ -63,8 +63,8 @@ GET /users/:id/annotations GET /teams controllers.TeamController.list(isEditable: Option[Boolean]) POST /teams controllers.TeamController.create DELETE /teams/:id controllers.TeamController.delete(id: String) -GET /teams/:id/availableTasksOverview controllers.ReportController.availableTasksOverview(id: String) -GET /teams/:id/progressOverview controllers.ReportController.projectProgressOverview(id: String) +GET /teams/:id/availableTasksReport controllers.ReportController.availableTasksReport(id: String) +GET /teams/:id/projectProgressReport controllers.ReportController.projectProgressReport(id: String) # DataSets POST /datasets/:organizationName/:dataSetName/createExplorational controllers.AnnotationController.createExplorational(organizationName: String, dataSetName: String) @@ -208,7 +208,7 @@ DELETE /scripts/:id # Projects GET /projects controllers.ProjectController.list -GET /projects/assignments controllers.ProjectController.listWithStatus +GET /projects/withStatus controllers.ProjectController.listWithStatus POST /projects controllers.ProjectController.create GET /projects/byName/:name controllers.ProjectController.readByName(name: String) GET /projects/:id controllers.ProjectController.read(id: String) diff --git a/frontend/javascripts/admin/admin_rest_api.ts b/frontend/javascripts/admin/admin_rest_api.ts index c6ee50f24e..46f6ad21f1 100644 --- a/frontend/javascripts/admin/admin_rest_api.ts +++ b/frontend/javascripts/admin/admin_rest_api.ts @@ -24,13 +24,13 @@ import type { APIMapping, APIMaybeUnimportedDataset, APIMeshFile, - APIOpenTasksReport, + APIAvailableTasksReport, APIOrganization, APIProject, APIProjectCreator, APIProjectProgressReport, APIProjectUpdater, - APIProjectWithAssignments, + APIProjectWithStatus, APIPublication, APIResolutionRestrictions, APIScript, @@ -334,7 +334,7 @@ export function deleteTeam(teamId: string): Promise { } // ### Projects -function transformProject(response: T): T { +function transformProject(response: T): T { return Object.assign({}, response, { expectedTime: Utils.millisecondsToMinutes(response.expectedTime), }); @@ -345,14 +345,14 @@ export async function getProjects(): Promise> { assertResponseLimit(responses); return responses.map(transformProject); } -export async function getProjectsWithOpenAssignments(): Promise> { - const responses = await Request.receiveJSON("/api/projects/assignments"); +export async function getProjectsWithStatus(): Promise> { + const responses = await Request.receiveJSON("/api/projects/withStatus"); assertResponseLimit(responses); return responses.map(transformProject); } export async function getProjectsForTaskType( taskTypeId: string, -): Promise> { +): Promise> { const responses = await Request.receiveJSON(`/api/taskTypes/${taskTypeId}/projects`); assertResponseLimit(responses); return responses.map(transformProject); @@ -364,7 +364,7 @@ export async function getProject(projectId: string): Promise { export async function increaseProjectTaskInstances( projectId: string, delta: number = 1, -): Promise { +): Promise { const project = await Request.receiveJSON( `/api/projects/${projectId}/incrementEachTasksInstances?delta=${delta}`, { @@ -1879,17 +1879,17 @@ export async function getProjectProgressReport( teamId: string, showErrorToast: boolean = true, ): Promise> { - const progressData = await Request.receiveJSON(`/api/teams/${teamId}/progressOverview`, { + const progressData = await Request.receiveJSON(`/api/teams/${teamId}/projectProgressReport`, { showErrorToast, }); assertResponseLimit(progressData); return progressData; } -export async function getOpenTasksReport(teamId: string): Promise> { - const openTasksData = await Request.receiveJSON(`/api/teams/${teamId}/openTasksOverview`); - assertResponseLimit(openTasksData); - return openTasksData; +export async function getAvailableTasksReport(teamId: string): Promise> { + const availableTasksData = await Request.receiveJSON(`/api/teams/${teamId}/availableTasksReport`); + assertResponseLimit(availableTasksData); + return availableTasksData; } // ### Organizations diff --git a/frontend/javascripts/admin/project/project_list_view.tsx b/frontend/javascripts/admin/project/project_list_view.tsx index 62c26058a3..925607a815 100644 --- a/frontend/javascripts/admin/project/project_list_view.tsx +++ b/frontend/javascripts/admin/project/project_list_view.tsx @@ -19,7 +19,7 @@ import * as React from "react"; import _ from "lodash"; import { AsyncLink } from "components/async_clickables"; import type { - APIProjectWithAssignments, + APIProjectWithStatus, APIProject, APIUser, APIUserBase, @@ -27,7 +27,7 @@ import type { import type { OxalisState } from "oxalis/store"; import { enforceActiveUser } from "oxalis/model/accessors/user_accessor"; import { - getProjectsWithOpenAssignments, + getProjectsWithStatus, getProjectsForTaskType, increaseProjectTaskInstances, deleteProject, @@ -57,10 +57,10 @@ type Props = OwnProps & StateProps; type State = { isLoading: boolean; - projects: Array; + projects: Array; searchQuery: string; isTransferTasksVisible: boolean; - selectedProject: APIProjectWithAssignments | null | undefined; + selectedProject: APIProjectWithStatus | null | undefined; taskTypeName: string | null | undefined; }; const persistence = new Persistence>( @@ -113,7 +113,7 @@ class ProjectListView extends React.PureComponent { ]); taskTypeName = taskType.summary; } else { - projects = await getProjectsWithOpenAssignments(); + projects = await getProjectsWithStatus(); } this.setState({ @@ -129,7 +129,7 @@ class ProjectListView extends React.PureComponent { }); }; - deleteProject = (project: APIProjectWithAssignments) => { + deleteProject = (project: APIProjectWithStatus) => { Modal.confirm({ title: messages["project.delete"], onOk: async () => { @@ -154,12 +154,12 @@ class ProjectListView extends React.PureComponent { }; mergeProjectWithUpdated = ( - oldProject: APIProjectWithAssignments, + oldProject: APIProjectWithStatus, updatedProject: APIProject, - ): APIProjectWithAssignments => ({ ...oldProject, ...updatedProject }); + ): APIProjectWithStatus => ({ ...oldProject, ...updatedProject }); pauseResumeProject = async ( - project: APIProjectWithAssignments, + project: APIProjectWithStatus, APICall: (arg0: string) => Promise, ) => { const updatedProject = await APICall(project.id); @@ -170,7 +170,7 @@ class ProjectListView extends React.PureComponent { })); }; - increaseProjectTaskInstances = async (project: APIProjectWithAssignments) => { + increaseProjectTaskInstances = async (project: APIProjectWithStatus) => { Modal.confirm({ title: messages["project.increase_instances"], onOk: async () => { @@ -193,7 +193,7 @@ class ProjectListView extends React.PureComponent { }); }; - showActiveUsersModal = async (project: APIProjectWithAssignments) => { + showActiveUsersModal = async (project: APIProjectWithStatus) => { this.setState({ selectedProject: project, isTransferTasksVisible: true, @@ -241,10 +241,10 @@ class ProjectListView extends React.PureComponent { }, ]; - const typeHint: Array = []; + const typeHint: Array = []; const filteredProjects = Utils.filterWithSearchQueryAND( this.state.projects, - ["name", "team", "priority", "owner", "numberOfOpenAssignments", "tracingTime"], + ["name", "team", "priority", "owner", "pendingInstances", "tracingTime"], this.state.searchQuery, ); @@ -306,11 +306,11 @@ class ProjectListView extends React.PureComponent { /> project.numberOfOpenAssignments)} + dataIndex="pendingInstances" + key="pendingInstances" + sorter={Utils.compareBy(typeHint, (project) => project.pendingInstances)} filters={greaterThanZeroFilters} - onFilter={(value, project: APIProjectWithAssignments) => { + onFilter={(value, project: APIProjectWithStatus) => { if (value === "0") { return project.tracingTime === 0; } @@ -330,7 +330,7 @@ class ProjectListView extends React.PureComponent { }) } filters={greaterThanZeroFilters} - onFilter={(value, project: APIProjectWithAssignments) => { + onFilter={(value, project: APIProjectWithStatus) => { if (value === "0") { return project.tracingTime === 0; } @@ -349,7 +349,7 @@ class ProjectListView extends React.PureComponent { })), "text", )} - onFilter={(value, project: APIProjectWithAssignments) => value === project.team} + onFilter={(value, project: APIProjectWithStatus) => value === project.team} filterMultiple /> { })), "text", )} - onFilter={(value, project: APIProjectWithAssignments) => value === project.owner.id} + onFilter={(value, project: APIProjectWithStatus) => value === project.owner.id} filterMultiple /> { dataIndex="priority" key="priority" sorter={Utils.compareBy(typeHint, (project) => project.priority)} - render={(priority, project: APIProjectWithAssignments) => + render={(priority, project: APIProjectWithStatus) => `${priority} ${project.paused ? "(paused)" : ""}` } filters={[ @@ -395,7 +395,7 @@ class ProjectListView extends React.PureComponent { value: "paused", }, ]} - onFilter={(_value, project: APIProjectWithAssignments) => project.paused} + onFilter={(_value, project: APIProjectWithStatus) => project.paused} /> { key="actions" fixed="right" width={200} - render={(__, project: APIProjectWithAssignments) => ( + render={(__, project: APIProjectWithStatus) => ( ; + data: Array; isLoading: boolean; }; @@ -102,7 +102,7 @@ class OpenTasksReportView extends React.PureComponent<{}, State> { + render={(_text, item: APIAvailableTasksReport) => Object.keys(item.assignmentsByProjects).map((key) => { const [projectName, experience] = key.split("/"); return ( diff --git a/frontend/javascripts/admin/statistic/statistic_view.tsx b/frontend/javascripts/admin/statistic/statistic_view.tsx index 01b7c1344c..4f73bde302 100644 --- a/frontend/javascripts/admin/statistic/statistic_view.tsx +++ b/frontend/javascripts/admin/statistic/statistic_view.tsx @@ -19,7 +19,7 @@ type State = { numberOfUsers: number; numberOfDatasets: number; numberOfAnnotations: number; - numberOfOpenAssignments: number; + pendingInstances: number; tracingTimes: TimeEntry[]; }; timeEntries: Array<{ @@ -51,7 +51,7 @@ class StatisticView extends React.PureComponent<{}, State> { numberOfUsers: 0, numberOfDatasets: 0, numberOfAnnotations: 0, - numberOfOpenAssignments: 0, + pendingInstances: 0, tracingTimes: [], }, }; @@ -199,8 +199,8 @@ class StatisticView extends React.PureComponent<{}, State> { {this.state.achievements.numberOfAnnotations}
  • -
    Number of Open Assignments
    - {this.state.achievements.numberOfOpenAssignments} +
    Number of Pending Task Instances
    + {this.state.achievements.pendingInstances}
  • diff --git a/frontend/javascripts/test/backend-snapshot-tests/projects.e2e.ts b/frontend/javascripts/test/backend-snapshot-tests/projects.e2e.ts index f89d53be17..68f47e9d22 100644 --- a/frontend/javascripts/test/backend-snapshot-tests/projects.e2e.ts +++ b/frontend/javascripts/test/backend-snapshot-tests/projects.e2e.ts @@ -26,11 +26,11 @@ test.serial("getProjects()", async (t) => { id: "projects-getProjects()", }); }); -test.serial("getProjectsWithOpenAssignments()", async (t) => { - const projects = _.sortBy(await api.getProjectsWithOpenAssignments(), (p) => p.name); +test.serial("getProjectsWithStatus()", async (t) => { + const projects = _.sortBy(await api.getProjectsWithStatus(), (p) => p.name); t.snapshot(replaceVolatileValues(projects), { - id: "projects-getProjectsWithOpenAssignments()", + id: "projects-getProjectsWithStatus()", }); }); test.serial("getProject(projectId: string)", async (t) => { @@ -54,7 +54,7 @@ test.serial("createProject and deleteProject", async (t) => { priority: 1, paused: false, expectedTime: 1, - numberOfOpenAssignments: 1, + pendingInstances: 1, isBlacklistedFromReport: true, }; const createdProject = await api.createProject(newProject); diff --git a/frontend/javascripts/test/backend-snapshot-tests/timetracking.e2e.ts b/frontend/javascripts/test/backend-snapshot-tests/timetracking.e2e.ts index 30b2e5ca86..3019acb545 100644 --- a/frontend/javascripts/test/backend-snapshot-tests/timetracking.e2e.ts +++ b/frontend/javascripts/test/backend-snapshot-tests/timetracking.e2e.ts @@ -72,12 +72,12 @@ test("getProjectProgressReport", async (t) => { }); }); -test("getOpenTasksReport", async (t) => { - const openTasksReport = await api.getOpenTasksReport(firstTeam.id); - writeTypeCheckingFile(openTasksReport, "open-tasks", "APIOpenTasksReport", { +test("getAvailableTasksReport", async (t) => { + const availableTasksReport = await api.getAvailableTasksReport(firstTeam.id); + writeTypeCheckingFile(availableTasksReport, "available-tasks", "APIAvailableTasksReport", { isArray: true, }); - t.snapshot(openTasksReport, { - id: "timetracking-openTasksReport", + t.snapshot(availableTasksReport, { + id: "timetracking-availableTasksReport", }); }); diff --git a/frontend/javascripts/test/fixtures/tasktracing_server_objects.ts b/frontend/javascripts/test/fixtures/tasktracing_server_objects.ts index 248ebcf074..ccd8602250 100644 --- a/frontend/javascripts/test/fixtures/tasktracing_server_objects.ts +++ b/frontend/javascripts/test/fixtures/tasktracing_server_objects.ts @@ -94,7 +94,7 @@ export const annotation: APIAnnotation = { }, created: 1529066010230, status: { - open: 0, + pending: 0, active: 1, finished: 0, }, diff --git a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.md b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.md index e83431b392..4be860867e 100644 --- a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.md +++ b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.md @@ -347,7 +347,7 @@ Generated by [AVA](https://avajs.dev). status: { active: 1, finished: -1, - open: 10, + pending: 10, }, team: 'team_X2', tracingTime: 'tracingTime', @@ -517,7 +517,7 @@ Generated by [AVA](https://avajs.dev). status: { active: 2, finished: -2, - open: 10, + pending: 10, }, team: 'team_X2', tracingTime: 'tracingTime', diff --git a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.snap b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/annotations.e2e.js.snap index 270c0bbce66db354650c8796bb0f8c373645a1f8..444456b0b157b77c2bde213bbd0417041d981d17 100644 GIT binary patch literal 11681 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 literal 11679 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 literal 2619 zcmV-B3dHq6RzVF00000000B+ zoDXmm)g8xwdwZ8$?sES~FbD-AmMRW}kOY$07Kl($Do`-P#6SsqcN@5vy9>7$At?k( zP_b0#I1RP5rYT^h)QVC?J2)*UQ;l``*9ny{Y>+C3oyVqKW~+=RYcUx74Qd|D92h z^DR!v{n_HkxY=;cDc%i$y6|_Je?5z7DonU$xgM2BwrxsS#{A_nYL7}rCFFPk&EO+OXcd%30>$_Du^`7>fntN}#KTsz4eEy&i z^!R;c-s(_78q$^S-q3?{SnKZ2&*8jIbt)-+da677RJV)1UWVDorDU0i@<0W64r~JZ zz!%^SGtsxrjKAjDB|yqFNJ_v;umv0d=j5-Y65S8VQkf!BR7E@i$wIIh90nIaZwpaA zm<8%BOc54U5o;i60*Ao=KtC%{A(#P{f|smJ5f0VD!^kwCLG%iq%1HO1p95t@VvRr;I^R1L}Pe2N`@1La5H#;-tlRZ+A8#RQ+m5uFLeCqT2YP|kxDfY;- zd!+F~P>{snB*7=l5d*OcGFA%FKUM#LzotkT`G2vt8~O48lRDf+rVNyEwWP{Y7gy{O|2O-?20P#{wI<5 zp;G>AZ!P7xJyJ@BBUB20*n>zu0zBYZ2V>3+tVhtl2FaV?Ah^iNlT`9=^dcGr#sF6@ zMxLyaKMl#VU?XT|~Bl^T0~*GH3>0g1&K%d$k2o^nz#vA9F!83)ybq12uf~%7l--GFisAt||V+ zN7fXd@sTx!0~`GekOxZiOfTtGo^v52OTllzyR2Nj1o<42)4*gP%3{ywlQc)$0~U21 z*apsk4A_+{@O|(&XzEFedb4guU^?weG<#}>&2)eXzz=>3HiEt2Jm{m@(@xpb;j!)M zOk_JjC79nad%84YPhV@Zr=PajQyZ*ZZ;%6~$G4}okSqZ&fbH?^=}|~d0V8bbP|cod z_EfW{nmyI*sb)_T%AV@7@R~<)Z6&9SwaL^r){I_Jl2EZS+;kE?sbNBybnGw-ji~xa z-5c@xDqA-ntM2GICsn&#sJ!)kX4oEP&KIQ2favuz@x zJn*|7J>i&rlD!>!9UkPGW56DS2N~uQ=MSrD=S7;k)r+tlb_2YK>_IZXBv1{W1FwSp z;43gd^CF$Q?GxLJ$R1=a2!KT$^CHV5UgXU-FLJESi^v|NA1DAb<9iX=gFFS+ft~Ta zi0nbm0V_O6j^;&f#;W{k=f*y;?zTBM-qW2HRaa=Z@g~&@vzZPif&f?r{s`U&Euf!f zQafc*b7Gs+GGvRu4{AGRQlE^N)Yscg>X9~++6xA+4;Tp^i*HgFK~fKzz>fGP^*AJF zfH_uRw`NjrkV(D%2L)bldr;sCMIIEG7wPCFeXxTd}4M88}5!~FYeA}zwrU=SQ7sNzxV}k0Q?8^Ng)~qO2A^U zJ|%8M`c_D`g9~7wiRe3^6f6Ymz%EnVi1be&ISTrjiN=B1pbj*Fo#0<=T)a9Wolhl7 z0b{@%unepPJHRPmXM@_ci1cf&?eDQ@(biXQqfoSUvDQX$+o-1NZFJkrwZ<52gl*{_ zWPboY0>>=Tdm~ju`Z)+LFd|h%I%g$HwnmFo5$O&HGQeG6C?mc~M0&opbxT%mjcmyp ztV~4u4r_Eo`uWKFP$`2VaqUyv;@X$kB8LE*kbDE|2Q9X^yZXL%qD=5zAlTzZq(2GC z3a|m}W##IK^s|t($T9~}J{#|@j!2&Yi2&-rT2`)(NdFrod%+1{VdLG^5$Pi!$p^(? z0V`KWr2iU{wcxMd2rJhj(zS^6uTNFC=&)#a3}z|C>um5e_yfSQpISgBM>GYLgZl8D zg@4HZT!cNWQr}@n^oNH#`40fnLt!GiL^_0sUSxFS;bHlX#e(pG<)LsWk$fVV8~zU` zi@WFU#ci`PT^;l8g>Eadx5v9{5n7w<;lp!Se@Ra1iTZ=0IQJ)d%t~uPg^{|iGpJCt dme+y`6MfI61r=(GwoWeE{tqID`)2P=00125^kM)2 diff --git a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/tasks.e2e.js.md b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/tasks.e2e.js.md index 0bef2bf5f5..7f937a048e 100644 --- a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/tasks.e2e.js.md +++ b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/tasks.e2e.js.md @@ -35,7 +35,7 @@ Generated by [AVA](https://avajs.dev). status: { active: 1, finished: -1, - open: 10, + pending: 10, }, team: 'team_X1', tracingTime: 0, @@ -89,7 +89,7 @@ Generated by [AVA](https://avajs.dev). status: { active: 0, finished: 0, - open: 10, + pending: 10, }, team: 'team_X2', tracingTime: 0, @@ -143,7 +143,7 @@ Generated by [AVA](https://avajs.dev). status: { active: 0, finished: 1, - open: 9, + pending: 9, }, team: 'team_X1', tracingTime: 0, @@ -202,7 +202,7 @@ Generated by [AVA](https://avajs.dev). status: { active: 0, finished: 1, - open: 9, + pending: 9, }, team: 'team_X1', tracingTime: 0, @@ -256,7 +256,7 @@ Generated by [AVA](https://avajs.dev). status: { active: 1, finished: -1, - open: 10, + pending: 10, }, team: 'team_X1', tracingTime: 0, @@ -313,7 +313,7 @@ Generated by [AVA](https://avajs.dev). status: { active: 0, finished: 1, - open: 9, + pending: 9, }, team: 'team_X1', tracingTime: null, @@ -370,7 +370,7 @@ Generated by [AVA](https://avajs.dev). status: { active: 0, finished: 1, - open: 9, + pending: 9, }, team: 'team_X1', tracingTime: 0, @@ -500,7 +500,7 @@ Generated by [AVA](https://avajs.dev). status: { active: 1, finished: -1, - open: 10, + pending: 10, }, team: 'team_X1', tracingTime: null, @@ -599,7 +599,7 @@ Generated by [AVA](https://avajs.dev). status: { active: 0, finished: 1, - open: 19, + pending: 19, }, team: 'team_X1', tracingTime: 0, @@ -655,7 +655,7 @@ Generated by [AVA](https://avajs.dev). status: { active: 0, finished: 0, - open: 3, + pending: 3, }, team: 'team_X1', tracingTime: 'tracingTime', @@ -784,7 +784,7 @@ Generated by [AVA](https://avajs.dev). status: { active: 1, finished: 0, - open: 2, + pending: 2, }, team: 'team_X1', tracingTime: 'tracingTime', diff --git a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/tasks.e2e.js.snap b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/tasks.e2e.js.snap index 86bbbb6b5f803a923308a0d4763fc863b6d6fc93..c221fbff422461b22cc16a748bde9171e4a8a4d8 100644 GIT binary patch literal 5100 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 OlYawUj?gNpeEy!yYR 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 Lv}b%W6EXk*F{Z8~ literal 1972 zcmV;l2TS-tRzVV+>KDA2Wmg~AHIp%q zzB7T4V>=)HWLIeVo|cA{T*uA>yBYK7e2x$=sWtm#>KK0u-Y0{bz?~ogvS29VAHvkM z77A(?!}MB_NQ5v!!qgC^o?#xcg24L7cxO!WgkgG2vlUmycun&O!}J*)2DZZ7&X`W5 z1?0e6;Cpb9myjF0cIW=-i8)%b#CZD5#>Pf7L>?}kU=`>A!+nI@44wvC!LL3lgtsh2 za}6P*!7X4h*Z__K-cLv#Eca6(d}Sdv!etBi6T~o}I(QB21I=}WwAN7}YRW>)f=d>> z4)%kF03p|cZm<#j6re)*PYn@96v9N{<7qNR8a;U1)2Uv2geb8Im1PsWDkWJhrn9UV z*R_~NwF#=ZT)w21()rxUrZOjpNlxK)RZ9r5B+JTNk`+@_(@;ktZ=6nhPR(k)2g7Yz zvD7}p`h=%i)RxrZ+*3{xJ;fzewtA9qzOpI`VvK56Z?r3Qb!hYjDtatwU!ZZ8DK)O^ zRNKbBw#CkDRxNb(e$b@NZD-?BtbKC2BcD+VQ?yK`NHzAA()})NMjLxQJB{O_k|N$l zc?Jy6VoA+*m`8m6UtaZ$YGVefli=!fu5_BTziHq)@vctiN~fN6k%8;PxjLOIofbYD z9Jo%3tJAsCX?(h-jp>&TddB0OcS`@POs}Q4QB5B2o#0il0~{r1PvOF5F4({@V~_{- zp4>3CSWM5#Wwl(XIJwIfhrU)%zp<69u}#@yo3h4MD<4~Ybuz6dgIm{5=uzw}YK1;6 zjepvwTs%47HO2S}pE;TAH+Bs(?esYV9t7QB33v&-0akz9#GIipry6F!G@midplR+h z%zD#oGKMTHZh~)Wxuo~>CaZKS)z{Q8eSq4S066^;fFr2r8d9{uy z{F`uj7aRnmkUUQX^T9@N9E_!t=kgT(Cb--Ro&uY}Z{SMYa_Qg=us!IQ!XJUlpWrIo zkIe?l!7dOE5pp9ahR&YCBgAsJtN>quhA<%$!GmBeI1ENLP$6o|LQI0owcuf}4jckw zB81!xmVwO?Dnwmbh#%qd3%I0_keOf!*aDa#gj@|~4WU8=%0euG%TllvcrGAB0CU0H zpa+D8QXzt6A$YjRAOqe8dq86oZco7?u(pW`QC}8fFI)~9Zo_a_2Ft(YHd%?@#8$d=-A;M)L#=>PhxDUJnwgKNrLMDMnz*{4!5Dit{ z+9f1DrAw?5WAUdYj8~oGgi5wfbK%rEYvEL6YuAbYAniR8JK$#q8OnD|j(vqJJfFWvX(` zW23y}{6p5kKS&iS39_Y9O0ZRO@0tdxQho2vBq@#}U2&)?kvM89(F5R$RPBlk zQBiTu@=h$>Td_(~GND*1$pXtkB~FjKDh*a8nRQ>mQDNiB4Y&?; zgD-)<4xW z@cb>mQY$LIauxg!6lMkI<9!iW1NK_UC>aZ;fFxK7jM5>8Vyysmx)C*5N; + readonly totalAvailableTasks: number; + readonly availableTasksByProject: Record; }; export type APIOrganization = { readonly id: string; From 7d90dfb5f746f78f6c2651000bef877182b3789c Mon Sep 17 00:00:00 2001 From: Florian M Date: Thu, 20 Jul 2023 17:27:09 +0200 Subject: [PATCH 06/13] more usages --- app/controllers/ReportController.scala | 8 ++++---- ..._view.tsx => available_tasks_report_view.tsx} | 16 ++++++++-------- frontend/javascripts/router.tsx | 6 +++--- frontend/javascripts/types/api_flow_types.ts | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) rename frontend/javascripts/admin/statistic/{open_tasks_report_view.tsx => available_tasks_report_view.tsx} (89%) diff --git a/app/controllers/ReportController.scala b/app/controllers/ReportController.scala index aaab847c3f..a96b0986c1 100644 --- a/app/controllers/ReportController.scala +++ b/app/controllers/ReportController.scala @@ -17,7 +17,7 @@ import scala.concurrent.ExecutionContext case class AvailableTaskCountsEntry(id: String, user: String, totalAvailableTasks: Int, - availableTasksByProject: Map[String, Int]) + availableTasksByProjects: Map[String, Int]) object AvailableTaskCountsEntry { implicit val jsonFormat: OFormat[AvailableTaskCountsEntry] = Json.format[AvailableTaskCountsEntry] } @@ -157,12 +157,12 @@ class ReportController @Inject()(reportDAO: ReportDAO, private def getAvailableTaskCountsAndProjects(users: Seq[User]): Fox[List[AvailableTaskCountsEntry]] = { val foxes = users.map { user => for { - pendingTaskCountsByProject <- reportDAO.getAvailableTaskCountsByProjectsFor(user._id) + pendingTaskCountsByProjects <- reportDAO.getAvailableTaskCountsByProjectsFor(user._id) } yield { AvailableTaskCountsEntry(user._id.toString, user.name, - pendingTaskCountsByProject.values.sum, - pendingTaskCountsByProject) + pendingTaskCountsByProjects.values.sum, + pendingTaskCountsByProjects) } } Fox.combined(foxes.toList) diff --git a/frontend/javascripts/admin/statistic/open_tasks_report_view.tsx b/frontend/javascripts/admin/statistic/available_tasks_report_view.tsx similarity index 89% rename from frontend/javascripts/admin/statistic/open_tasks_report_view.tsx rename to frontend/javascripts/admin/statistic/available_tasks_report_view.tsx index 32532e873c..9f7ac6628f 100644 --- a/frontend/javascripts/admin/statistic/open_tasks_report_view.tsx +++ b/frontend/javascripts/admin/statistic/available_tasks_report_view.tsx @@ -14,7 +14,7 @@ type State = { isLoading: boolean; }; -class OpenTasksReportView extends React.PureComponent<{}, State> { +class AvailableTasksReportView extends React.PureComponent<{}, State> { state: State = { data: [], isLoading: false, @@ -30,7 +30,7 @@ class OpenTasksReportView extends React.PureComponent<{}, State> { this.setState({ isLoading: true, }); - const progressData = await getOpenTasksReport(teamId); + const progressData = await getAvailableTasksReport(teamId); this.setState({ data: progressData, }); @@ -94,30 +94,30 @@ class OpenTasksReportView extends React.PureComponent<{}, State> { /> task.totalAssignments)} + sorter={Utils.compareBy(typeHint, (task) => task.totalAvailableTasks)} width={150} /> - Object.keys(item.assignmentsByProjects).map((key) => { + Object.keys(item.availableTasksByProjects).map((key) => { const [projectName, experience] = key.split("/"); return (
    - There are potentially {item.assignmentsByProjects[key]} tasks from the + There are potentially {item.availableTasksByProjects[key]} tasks from the project {projectName} available for automatic assignment for this user because they match the configured assignment criteria; especially the required experience level {experience}. } > - {projectName}: {item.assignmentsByProjects[key]} + {projectName}: {item.availableTasksByProjects[key]} {experience}
    @@ -132,4 +132,4 @@ class OpenTasksReportView extends React.PureComponent<{}, State> { } } -export default OpenTasksReportView; +export default AvailableTasksReportView; diff --git a/frontend/javascripts/router.tsx b/frontend/javascripts/router.tsx index 320faf1528..4f73655458 100644 --- a/frontend/javascripts/router.tsx +++ b/frontend/javascripts/router.tsx @@ -20,7 +20,7 @@ import ProjectCreateView from "admin/project/project_create_view"; import ProjectListView from "admin/project/project_list_view"; import ScriptCreateView from "admin/scripts/script_create_view"; import ScriptListView from "admin/scripts/script_list_view"; -import OpenTasksReportView from "admin/statistic/open_tasks_report_view"; +import AvailableTasksReportView from "admin/statistic/available_tasks_report_view"; import ProjectProgressReportView from "admin/statistic/project_progress_report_view"; import StatisticView from "admin/statistic/statistic_view"; import TaskCreateFormView from "admin/task/task_create_form_view"; @@ -323,8 +323,8 @@ class ReactRouter extends React.Component { ; + readonly availableTasksByProjects: Record; }; export type APIOrganization = { readonly id: string; From 77d6d74572d825978c9f6a0176d98c695823732a Mon Sep 17 00:00:00 2001 From: Florian M Date: Mon, 24 Jul 2023 11:08:41 +0200 Subject: [PATCH 07/13] sql evolutions --- conf/evolutions/105-task-terminology.sql | 91 +++++++++++++++++++ .../reversions/105-task-terminology.sql | 89 ++++++++++++++++++ tools/postgres/schema.sql | 2 +- 3 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 conf/evolutions/105-task-terminology.sql create mode 100644 conf/evolutions/reversions/105-task-terminology.sql diff --git a/conf/evolutions/105-task-terminology.sql b/conf/evolutions/105-task-terminology.sql new file mode 100644 index 0000000000..14766d6f62 --- /dev/null +++ b/conf/evolutions/105-task-terminology.sql @@ -0,0 +1,91 @@ +START TRANSACTION; + +do $$ begin ASSERT (select schemaVersion from webknossos.releaseInformation) = 104, 'Previous schema version mismatch'; end; $$ LANGUAGE plpgsql; + +DROP VIEW webknossos.tasks_; + +DROP TRIGGER onUpdateAnnotationTrigger ON webknossos.annotations; +DROP TRIGGER onInsertAnnotationTrigger ON webknossos.annotations; +DROP TRIGGER onDeleteAnnotationTrigger ON webknossos.annotations; +DROP TRIGGER onUpdateTaskTrigger ON webknossos.tasks; + +DROP FUNCTION webknossos.onUpdateAnnotation; +DROP FUNCTION webknossos.onInsertAnnotation; +DROP FUNCTION webknossos.onDeleteAnnotation; +DROP FUNCTION webknossos.onUpdateTask; + +ALTER TABLE webknossos.tasks DROP CONSTRAINT openInstancesLargeEnoughCheck; +ALTER TABLE webknossos.tasks RENAME COLUMN openInstances TO pendingInstances; +ALTER TABLE webknossos.tasks ADD CONSTRAINT pendingInstancesLargeEnoughCheck CHECK (pendingInstances >= 0); + +CREATE VIEW webknossos.tasks_ AS SELECT * FROM webknossos.tasks WHERE NOT isDeleted; + +CREATE FUNCTION webknossos.onUpdateTask() RETURNS trigger AS $$ + BEGIN + IF NEW.totalInstances <> OLD.totalInstances THEN + UPDATE webknossos.tasks SET pendingInstances = pendingInstances + (NEW.totalInstances - OLD.totalInstances) WHERE _id = NEW._id; + END IF; + RETURN NULL; + END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER onUpdateTaskTrigger +AFTER UPDATE ON webknossos.tasks +FOR EACH ROW EXECUTE PROCEDURE webknossos.onUpdateTask(); + + +CREATE FUNCTION webknossos.onInsertAnnotation() RETURNS trigger AS $$ + BEGIN + IF (NEW.typ = 'Task') AND (NEW.isDeleted = false) AND (NEW.state != 'Cancelled') THEN + UPDATE webknossos.tasks SET pendingInstances = pendingInstances - 1 WHERE _id = NEW._task; + END IF; + RETURN NULL; + END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER onInsertAnnotationTrigger +AFTER INSERT ON webknossos.annotations +FOR EACH ROW EXECUTE PROCEDURE webknossos.onInsertAnnotation(); + + + +CREATE OR REPLACE FUNCTION webknossos.onUpdateAnnotation() RETURNS trigger AS $$ + BEGIN + IF (NEW._task != OLD._task) OR (NEW.typ != OLD.typ) THEN + RAISE EXCEPTION 'annotation columns _task and typ are immutable'; + END IF; + IF (webknossos.countsAsTaskInstance(OLD) AND NOT webknossos.countsAsTaskInstance(NEW)) + THEN + UPDATE webknossos.tasks SET pendingInstances = pendingInstances + 1 WHERE _id = NEW._task; + END IF; + IF (NOT webknossos.countsAsTaskInstance(OLD) AND webknossos.countsAsTaskInstance(NEW)) + THEN + UPDATE webknossos.tasks SET pendingInstances = pendingInstances - 1 WHERE _id = NEW._task; + END IF; + RETURN NULL; + END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER onUpdateAnnotationTrigger +AFTER UPDATE ON webknossos.annotations +FOR EACH ROW EXECUTE PROCEDURE webknossos.onUpdateAnnotation(); + + +CREATE FUNCTION webknossos.onDeleteAnnotation() RETURNS trigger AS $$ + BEGIN + IF (OLD.typ = 'Task') AND (OLD.isDeleted = false) AND (OLD.state != 'Cancelled') THEN + UPDATE webknossos.tasks SET pendingInstances = pendingInstances + 1 WHERE _id = OLD._task; + END IF; + RETURN NULL; + END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER onDeleteAnnotationTrigger +AFTER DELETE ON webknossos.annotations +FOR EACH ROW EXECUTE PROCEDURE webknossos.onDeleteAnnotation(); + + + +UPDATE webknossos.releaseInformation SET schemaVersion = 105; + +COMMIT TRANSACTION; diff --git a/conf/evolutions/reversions/105-task-terminology.sql b/conf/evolutions/reversions/105-task-terminology.sql new file mode 100644 index 0000000000..1931aa3d35 --- /dev/null +++ b/conf/evolutions/reversions/105-task-terminology.sql @@ -0,0 +1,89 @@ +START TRANSACTION; + +do $$ begin ASSERT (select schemaVersion from webknossos.releaseInformation) = 105, 'Previous schema version mismatch'; end; $$ LANGUAGE plpgsql; + +DROP VIEW webknossos.tasks_; + +DROP TRIGGER onUpdateAnnotationTrigger ON webknossos.annotations; +DROP TRIGGER onInsertAnnotationTrigger ON webknossos.annotations; +DROP TRIGGER onDeleteAnnotationTrigger ON webknossos.annotations; +DROP TRIGGER onUpdateTaskTrigger ON webknossos.tasks; + +DROP FUNCTION webknossos.onUpdateAnnotation; +DROP FUNCTION webknossos.onInsertAnnotation; +DROP FUNCTION webknossos.onDeleteAnnotation; +DROP FUNCTION webknossos.onUpdateTask; + +ALTER TABLE webknossos.tasks DROP CONSTRAINT pendingInstancesLargeEnoughCheck; +ALTER TABLE webknossos.tasks RENAME COLUMN pendingInstances TO openInstances; +ALTER TABLE webknossos.tasks ADD CONSTRAINT openInstancesLargeEnoughCheck CHECK (openInstances >= 0); + +CREATE VIEW webknossos.tasks_ AS SELECT * FROM webknossos.tasks WHERE NOT isDeleted; +CREATE FUNCTION webknossos.onUpdateTask() RETURNS trigger AS $$ + BEGIN + IF NEW.totalInstances <> OLD.totalInstances THEN + UPDATE webknossos.tasks SET openInstances = openInstances + (NEW.totalInstances - OLD.totalInstances) WHERE _id = NEW._id; + END IF; + RETURN NULL; + END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER onUpdateTaskTrigger +AFTER UPDATE ON webknossos.tasks +FOR EACH ROW EXECUTE PROCEDURE webknossos.onUpdateTask(); + + +CREATE FUNCTION webknossos.onInsertAnnotation() RETURNS trigger AS $$ + BEGIN + IF (NEW.typ = 'Task') AND (NEW.isDeleted = false) AND (NEW.state != 'Cancelled') THEN + UPDATE webknossos.tasks SET openInstances = openInstances - 1 WHERE _id = NEW._task; + END IF; + RETURN NULL; + END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER onInsertAnnotationTrigger +AFTER INSERT ON webknossos.annotations +FOR EACH ROW EXECUTE PROCEDURE webknossos.onInsertAnnotation(); + + + +CREATE OR REPLACE FUNCTION webknossos.onUpdateAnnotation() RETURNS trigger AS $$ + BEGIN + IF (NEW._task != OLD._task) OR (NEW.typ != OLD.typ) THEN + RAISE EXCEPTION 'annotation columns _task and typ are immutable'; + END IF; + IF (webknossos.countsAsTaskInstance(OLD) AND NOT webknossos.countsAsTaskInstance(NEW)) + THEN + UPDATE webknossos.tasks SET openInstances = openInstances + 1 WHERE _id = NEW._task; + END IF; + IF (NOT webknossos.countsAsTaskInstance(OLD) AND webknossos.countsAsTaskInstance(NEW)) + THEN + UPDATE webknossos.tasks SET openInstances = openInstances - 1 WHERE _id = NEW._task; + END IF; + RETURN NULL; + END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER onUpdateAnnotationTrigger +AFTER UPDATE ON webknossos.annotations +FOR EACH ROW EXECUTE PROCEDURE webknossos.onUpdateAnnotation(); + + +CREATE FUNCTION webknossos.onDeleteAnnotation() RETURNS trigger AS $$ + BEGIN + IF (OLD.typ = 'Task') AND (OLD.isDeleted = false) AND (OLD.state != 'Cancelled') THEN + UPDATE webknossos.tasks SET openInstances = openInstances + 1 WHERE _id = OLD._task; + END IF; + RETURN NULL; + END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER onDeleteAnnotationTrigger +AFTER DELETE ON webknossos.annotations +FOR EACH ROW EXECUTE PROCEDURE webknossos.onDeleteAnnotation(); + + +UPDATE webknossos.releaseInformation SET schemaVersion = 104; + +COMMIT TRANSACTION; diff --git a/tools/postgres/schema.sql b/tools/postgres/schema.sql index 59f2c7bac2..b8b5ad581f 100644 --- a/tools/postgres/schema.sql +++ b/tools/postgres/schema.sql @@ -20,7 +20,7 @@ CREATE TABLE webknossos.releaseInformation ( schemaVersion BIGINT NOT NULL ); -INSERT INTO webknossos.releaseInformation(schemaVersion) values(104); +INSERT INTO webknossos.releaseInformation(schemaVersion) values(105); COMMIT TRANSACTION; From 3853b319cd4640b94a3105b80a156bb43ea93396 Mon Sep 17 00:00:00 2001 From: Florian M Date: Mon, 24 Jul 2023 11:22:15 +0200 Subject: [PATCH 08/13] format --- frontend/javascripts/admin/admin_rest_api.ts | 4 +++- frontend/javascripts/admin/project/project_list_view.tsx | 7 +------ .../admin/statistic/available_tasks_report_view.tsx | 8 ++++---- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/frontend/javascripts/admin/admin_rest_api.ts b/frontend/javascripts/admin/admin_rest_api.ts index 46f6ad21f1..d99a7ee5f5 100644 --- a/frontend/javascripts/admin/admin_rest_api.ts +++ b/frontend/javascripts/admin/admin_rest_api.ts @@ -1886,7 +1886,9 @@ export async function getProjectProgressReport( return progressData; } -export async function getAvailableTasksReport(teamId: string): Promise> { +export async function getAvailableTasksReport( + teamId: string, +): Promise> { const availableTasksData = await Request.receiveJSON(`/api/teams/${teamId}/availableTasksReport`); assertResponseLimit(availableTasksData); return availableTasksData; diff --git a/frontend/javascripts/admin/project/project_list_view.tsx b/frontend/javascripts/admin/project/project_list_view.tsx index 925607a815..5f0636e884 100644 --- a/frontend/javascripts/admin/project/project_list_view.tsx +++ b/frontend/javascripts/admin/project/project_list_view.tsx @@ -18,12 +18,7 @@ import { connect } from "react-redux"; import * as React from "react"; import _ from "lodash"; import { AsyncLink } from "components/async_clickables"; -import type { - APIProjectWithStatus, - APIProject, - APIUser, - APIUserBase, -} from "types/api_flow_types"; +import type { APIProjectWithStatus, APIProject, APIUser, APIUserBase } from "types/api_flow_types"; import type { OxalisState } from "oxalis/store"; import { enforceActiveUser } from "oxalis/model/accessors/user_accessor"; import { diff --git a/frontend/javascripts/admin/statistic/available_tasks_report_view.tsx b/frontend/javascripts/admin/statistic/available_tasks_report_view.tsx index 9f7ac6628f..862991d769 100644 --- a/frontend/javascripts/admin/statistic/available_tasks_report_view.tsx +++ b/frontend/javascripts/admin/statistic/available_tasks_report_view.tsx @@ -110,10 +110,10 @@ class AvailableTasksReportView extends React.PureComponent<{}, State> { - There are potentially {item.availableTasksByProjects[key]} tasks from the - project {projectName} available for automatic assignment for this - user because they match the configured assignment criteria; especially - the required experience level {experience}. + There are potentially {item.availableTasksByProjects[key]} tasks from + the project {projectName} available for automatic assignment for + this user because they match the configured assignment criteria; + especially the required experience level {experience}.
    } > From a768658a99b9af4ddcec44b7748593bcd41a776b Mon Sep 17 00:00:00 2001 From: Florian M Date: Mon, 24 Jul 2023 11:51:20 +0200 Subject: [PATCH 09/13] update snapshots --- .../timetracking.e2e.js.md | 2 +- .../timetracking.e2e.js.snap | Bin 1977 -> 1976 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/timetracking.e2e.js.md b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/timetracking.e2e.js.md index 32ce7a52ca..1cef324e5a 100644 --- a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/timetracking.e2e.js.md +++ b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/timetracking.e2e.js.md @@ -269,7 +269,7 @@ Generated by [AVA](https://avajs.dev). [ { - availableTasksByProject: {}, + availableTasksByProjects: {}, id: '770b9f4d2a7c0e4d008da6ef', totalAvailableTasks: 0, user: 'user_C BoyC', diff --git a/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/timetracking.e2e.js.snap b/frontend/javascripts/test/snapshots/public-test/test-bundle/test/backend-snapshot-tests/timetracking.e2e.js.snap index 1b65a6dac269225688603f4ed18a47c5b211f0ea..d12ba9878fb3c59bffa463ffa30735f9a7e80a9a 100644 GIT binary patch literal 1976 zcmV;p2S@lpRzVmFm_MifE$ZG$<%YqEo^jCMwZrK=gYb8`~*x+szO(cgd&sx7*+O zo!j5H=bYc|Jxd88Rpf#Ro92GI(z|}f*vq=Qc0RFzA;w=gNXVSob3a%cUwrWC^Sd{G zKKXk;W1gK|OUUwFp;^z|)%DGqt@mxssY`BT%(G{YBc$i`eNAJz>D$ zXV+au$l=fKeYZO_b9Yny8m{fLeY+U*>~f9}FR3>BWNH|H6ONO>HQ)x20IgsI;~&OU zHRtnc2gCGQ;c%ERLBdoKrj}vuvVy>R%Q!NodDJjHrrC^7#&}Kh0mJke9R_y5{Dd)` zNE66{m%(@794{f$ymsgQnTgxAWRdaonGFpMW{4bI+QBQJ2aNO)axHim>;S*`s1V+g z5TmM)J}?_B2b;m~fcF!U1FQU02wzEvEpXWm{sd7ZR0l7BufeDqLYix+5LG21=EJ2G zya@J!`T!wQKquG&4hEd$O)Y zD-`3itao^-MRidvEEsT+=qV>y=ee5TjJPTBBXDqfMhfprS{U_6IbsGNs0J zood_A*S65!+N$O|df#Z&7R1?@6pc?xx8*WwezKOy6sX3&V!Gc)o7KWjWoK|)L{h}- zD9?c5StzQlZRQ!D|EK3YV_KNO>Lj>2ohqFs?rj{rPQ0trsnV(Ewea9|;#{3hl}<|^ z4Gvx>#ntIl=`s_BfH$Y?D{MqyHitq=WiJ^j)vS?NvL=}lSb)#&t&DB|0bX&w;Y$^fBr*;zwfADl8?XJ-88 zzJgm|`U>uaOkcsh=~%&i#4fmDUV*~euGl*b({GyF4YS%bYrMo9r!c1*X23KbG0dQ8 zb{l4`X*L=u3z^014KQy8kAsiFUqD0&eFsH6& zfVH3pgho&yf+Zn%xX2&_)`H!jp%FK!U>VrZNQI~^39$z*`wh1!%nKDqcU?h|I?v*U!`oZ^&9Xb&e@hG-1i4XtqqOE7Qm0 zob%WSFFF65wf_%Nfl7jGsgx3ImE60g!Kzf-yD~|NqevJ3SdmB^HI(QCxFS_Le3+Tr(|HkGhy{yeq((xZY(@l>% z*e>ip*o2GpTt0rRhj?qdWfU%U8i%p}@nh+sY@14zwQb5(@bukKl`N|)aTPp$w_26= z`=P7g>AML_Eo&2&tKffNdsgs%9G8LhV2@RdlCfY4(81H-ZN_oSR)D(Pu$nBU7i!b8 zxa4e7vv*V(y$^63o=9ghT51Mf(*-S=%cf4S_iC7)&ZY~uX(=iq?dNY(+Y6c{qNjnD zs!hwL(%Jd+RgIwhYTEMYTs~dwu-~Wm4c6LU*=x(ZO{zDyn9;p9(qqW<>bL)l@%#hX KyULanG5`QR)6Ovf literal 1977 zcmV;q2S)foRzV21Qk6@igr!$jmlm&wdBB-lIgVt#~1C?&Ov|Vsb6kL>t zf-j|myK4xV+2q$bJ3aT@ z>HT`|J<~ah2_aSFoUt2k_;!W&Fhm0h>HYdT|| zed%IC4t#d^yIrBFJDTd(aBZLM-p-h3AK(b_l4`S0riSr1;W!>#1#Sch&9N56~bE* zVt5rHr-5t0Qt&qT1Mq%Aa$u#O3gIgWu?a3)z3?EOUG1BP48&9Wt>=B~GCRCP9@T!z#wW!Xr zVocYf8r3GKX0y4XT1@A%hnvcrASOA5*HtYcM3XElb4gZAQB6Z_`J8b%@vPda^_;A0 z(F(=*H0vFnYEfNO3$yxNBzlTTs%-Tn;d*6N6vQaiuGVN*>}b>I52)zTr2PSnt4yge zU8mYM^tLUux3;SJj-EFfwOMgCCPm}p(`~tonxCL$G6kx!ubA%h(WbSqSFlq!E+Q%7 zb(Cko@GKP7);9Bu&;Rq&o>48#Ky?yaosO4I<90O;TqoYu>3HeX{YrS?I&rQ}$4jTh z4+RIVlj7=hymT6yu4-ZWq=TNZIPy;EpOxt~^)#x=;n)sVfi7@>96N=pGjqXuhB+H~ zV9&)3SLdkdjGD-3Ef|c#_zqhi>N9xyjILyjZpt3rlr_2@$G%)9oBO1LlHGunar_o(1c`M({q^2EGPAI1PB6 zIpDwa=Gy|(n{Urydh_j#hx6@&cD@bsQsmWk!rp0^e$(7ynAN6P<0a-ag*n+U1E%?) zVFpdJ%P?zAv(Xr`keR#Q2=f;32>2NM1w>@fv%xyBqslRlKLi&avS%3-z#6a%jKFYS z59U=n=JC(LEcphDD@gct*tvEVlF6!;kUMiMd(+y$N= zNrkAd@cu3#@hM$ml_-lpEn&Rs94C~sf0}Ej&RJ`xB3oOpOs}0s+&@Eo_s;=tpY*m{ z!S<=q`N6TbPcbG`ynV6}nU6VcpQ%-^$yzySjww?#VacRuwoI%m)8J$hgh)(qmWiBF ziA+MOzf3GW)~-yI&L$}$ikzcNv%lO>woIzROG?aK^o&0E3F`jo+>7%8AX!$CMtfi8uSSpqGONpz} z(I=CrL`2?s&AVbvP5CmZiI^o5QU^;W$(3nvGV##}FFLPz9jd1SnWFY=(iOZIGSNR3 zxiVF_=CKi8a{e`I&!40Ml?2&RDJ9q{xpz$iRjIaTXOa|0kuKOqCzHTMI976$Y0-hdg|cO0d08^%5i7`SlovFLgXJptAE?d>F2HdKcp2=pl2I}mOawZ39K6XmR%`{R%MGi^VtS5t zWfqs5O=@;UmC<`Yx04d-Oh!vh!E3spC3D%-QEIP-=;>^_FjGrW5otevo7!H`ED=2o zv{Y?cHkHoKps#8uD8zHjPd*f Lv}b%W6EXk*F{Z8~ From 7a53c7bafdd070b54b7194622dc83806840873e7 Mon Sep 17 00:00:00 2001 From: Florian M Date: Mon, 24 Jul 2023 13:28:44 +0200 Subject: [PATCH 10/13] fix swagger.json route --- app/RequestHandler.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/RequestHandler.scala b/app/RequestHandler.scala index 6397f64850..09f1b09c11 100644 --- a/app/RequestHandler.scala +++ b/app/RequestHandler.scala @@ -30,7 +30,7 @@ class RequestHandler @Inject()(webCommands: WebCommands, with LazyLogging { override def routeRequest(request: RequestHeader): Option[Handler] = - if (request.uri.matches("^(/api/|/data/|/tracings/|/swagger/|/\\.well-known/).*$")) { + if (request.uri.matches("^(/api/|/data/|/tracings/|/swagger|/\\.well-known/).*$")) { super.routeRequest(request) } else if (request.uri.matches("^(/assets/).*$")) { val path = request.path.replaceFirst("^(/assets/)", "") From dcba0e6e72948fc3bbf208378151d36eb630908e Mon Sep 17 00:00:00 2001 From: Florian M Date: Tue, 25 Jul 2023 12:59:32 +0200 Subject: [PATCH 11/13] changelog, migration guide --- CHANGELOG.unreleased.md | 1 + MIGRATIONS.unreleased.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 579465dd90..46fdecbd79 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -42,3 +42,4 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released - Removed the "Globalize Floodfill" feature that could extend partial floodfills across an entire dataset. Please use the fill tool multiple times instead or make use of the proofreading tool when correcting large structures. [#7173](https://github.com/scalableminds/webknossos/pull/7173) ### Breaking Changes +- The task and project api have changed. Make sure to update to the latest webknossos python library version when interacting with task and projects via the python library. Compare [webknossos-libs#930](https://github.com/scalableminds/webknossos-libs/pull/930). [#7220](https://github.com/scalableminds/webknossos/pull/7220) diff --git a/MIGRATIONS.unreleased.md b/MIGRATIONS.unreleased.md index 7e18540b5e..34e8a23b81 100644 --- a/MIGRATIONS.unreleased.md +++ b/MIGRATIONS.unreleased.md @@ -8,6 +8,8 @@ User-facing changes are documented in the [changelog](CHANGELOG.released.md). ## Unreleased [Commits](https://github.com/scalableminds/webknossos/compare/23.07.0...HEAD) +- When interacting with webknossos via the python library, make sure you update to the latest version, as the task and project api have changed. Compare [webknossos-libs#930](https://github.com/scalableminds/webknossos-libs/pull/930). [#7220](https://github.com/scalableminds/webknossos/pull/7220) + ### Postgres Evolutions: - [103-thin-plane-splines.sql](conf/evolutions/103-thin-plane-splines.sql) - [104-thumbnails.sql](conf/evolutions/104-thumbnails.sql) From bf5f67232ea26a40200c544a4f4a54d252ef8273 Mon Sep 17 00:00:00 2001 From: Florian M Date: Thu, 27 Jul 2023 12:37:34 +0200 Subject: [PATCH 12/13] schema version number --- MIGRATIONS.unreleased.md | 1 + .../{105-task-terminology.sql => 106-task-terminology.sql} | 4 ++-- .../{105-task-terminology.sql => 106-task-terminology.sql} | 4 ++-- tools/postgres/schema.sql | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) rename conf/evolutions/{105-task-terminology.sql => 106-task-terminology.sql} (96%) rename conf/evolutions/reversions/{105-task-terminology.sql => 106-task-terminology.sql} (96%) diff --git a/MIGRATIONS.unreleased.md b/MIGRATIONS.unreleased.md index f016d9c3d4..9adb9ea3df 100644 --- a/MIGRATIONS.unreleased.md +++ b/MIGRATIONS.unreleased.md @@ -18,3 +18,4 @@ UPDATE webknossos.multiUsers SET isEmailVerified = false; ### Postgres Evolutions: - [105-verify-email.sql](conf/evolutions/105-verify-email.sql) +- [106-task-terminology.sql](conf/evolutions/106-task-terminology.sql) diff --git a/conf/evolutions/105-task-terminology.sql b/conf/evolutions/106-task-terminology.sql similarity index 96% rename from conf/evolutions/105-task-terminology.sql rename to conf/evolutions/106-task-terminology.sql index 14766d6f62..31b7971523 100644 --- a/conf/evolutions/105-task-terminology.sql +++ b/conf/evolutions/106-task-terminology.sql @@ -1,6 +1,6 @@ START TRANSACTION; -do $$ begin ASSERT (select schemaVersion from webknossos.releaseInformation) = 104, 'Previous schema version mismatch'; end; $$ LANGUAGE plpgsql; +do $$ begin ASSERT (select schemaVersion from webknossos.releaseInformation) = 105, 'Previous schema version mismatch'; end; $$ LANGUAGE plpgsql; DROP VIEW webknossos.tasks_; @@ -86,6 +86,6 @@ FOR EACH ROW EXECUTE PROCEDURE webknossos.onDeleteAnnotation(); -UPDATE webknossos.releaseInformation SET schemaVersion = 105; +UPDATE webknossos.releaseInformation SET schemaVersion = 106; COMMIT TRANSACTION; diff --git a/conf/evolutions/reversions/105-task-terminology.sql b/conf/evolutions/reversions/106-task-terminology.sql similarity index 96% rename from conf/evolutions/reversions/105-task-terminology.sql rename to conf/evolutions/reversions/106-task-terminology.sql index 1931aa3d35..4af811587c 100644 --- a/conf/evolutions/reversions/105-task-terminology.sql +++ b/conf/evolutions/reversions/106-task-terminology.sql @@ -1,6 +1,6 @@ START TRANSACTION; -do $$ begin ASSERT (select schemaVersion from webknossos.releaseInformation) = 105, 'Previous schema version mismatch'; end; $$ LANGUAGE plpgsql; +do $$ begin ASSERT (select schemaVersion from webknossos.releaseInformation) = 106, 'Previous schema version mismatch'; end; $$ LANGUAGE plpgsql; DROP VIEW webknossos.tasks_; @@ -84,6 +84,6 @@ AFTER DELETE ON webknossos.annotations FOR EACH ROW EXECUTE PROCEDURE webknossos.onDeleteAnnotation(); -UPDATE webknossos.releaseInformation SET schemaVersion = 104; +UPDATE webknossos.releaseInformation SET schemaVersion = 105; COMMIT TRANSACTION; diff --git a/tools/postgres/schema.sql b/tools/postgres/schema.sql index d1ed5f91c5..f3867205e1 100644 --- a/tools/postgres/schema.sql +++ b/tools/postgres/schema.sql @@ -20,7 +20,7 @@ CREATE TABLE webknossos.releaseInformation ( schemaVersion BIGINT NOT NULL ); -INSERT INTO webknossos.releaseInformation(schemaVersion) values(105); +INSERT INTO webknossos.releaseInformation(schemaVersion) values(106); COMMIT TRANSACTION; From 1670db04ad9d65d08573b37d622e9af17d51373b Mon Sep 17 00:00:00 2001 From: Florian M Date: Thu, 27 Jul 2023 12:43:49 +0200 Subject: [PATCH 13/13] add note on available vs pending, fix navbar link, add redirect for old uri --- .../admin/statistic/available_tasks_report_view.tsx | 5 +++++ frontend/javascripts/navbar.tsx | 4 ++-- frontend/javascripts/router.tsx | 4 ++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/frontend/javascripts/admin/statistic/available_tasks_report_view.tsx b/frontend/javascripts/admin/statistic/available_tasks_report_view.tsx index 862991d769..7268957ac9 100644 --- a/frontend/javascripts/admin/statistic/available_tasks_report_view.tsx +++ b/frontend/javascripts/admin/statistic/available_tasks_report_view.tsx @@ -14,6 +14,11 @@ type State = { isLoading: boolean; }; +/* + * Note that the phrasing “available” tasks is chosen here over “pending” to + * emphasize that tasks are still available for individual users. + * From the project viewpoint they are tasks with pending instances. + */ class AvailableTasksReportView extends React.PureComponent<{}, State> { state: State = { data: [], diff --git a/frontend/javascripts/navbar.tsx b/frontend/javascripts/navbar.tsx index 4e2c0f60bc..22eab75538 100644 --- a/frontend/javascripts/navbar.tsx +++ b/frontend/javascripts/navbar.tsx @@ -276,10 +276,10 @@ function getStatisticsSubMenu(collapse: boolean): SubMenuType { ), }, { - key: "/reports/openTasks", + key: "/reports/availableTasks", label: ( - Available Task Assignments + Available Task Assignments ), }, diff --git a/frontend/javascripts/router.tsx b/frontend/javascripts/router.tsx index 5017a52cd7..983ac54aea 100644 --- a/frontend/javascripts/router.tsx +++ b/frontend/javascripts/router.tsx @@ -321,6 +321,10 @@ class ReactRouter extends React.Component { component={ProjectProgressReportView} exact /> + } + />