Skip to content

Commit f7c09a6

Browse files
authored
Update Bundle Naming Convention and Add Task Locking Endpoints (#1163)
* change 'reset -> update' task bundling naming convention * fix task status issues * remove task lock finct * add bundle locking endpoints * make bundling data more usefule * simplify bundle creation
1 parent 718d31e commit f7c09a6

File tree

7 files changed

+192
-80
lines changed

7 files changed

+192
-80
lines changed

app/org/maproulette/controllers/api/TaskController.scala

+84
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,90 @@ class TaskController @Inject() (
369369
}
370370
}
371371

372+
/**
373+
* Locks a bundle of tasks based on the provided task IDs.
374+
*
375+
* @param taskIds The IDs of the tasks to lock
376+
* @return
377+
*/
378+
def lockTaskBundleByIds(taskIds: List[Long]): Action[AnyContent] = Action.async {
379+
implicit request =>
380+
this.sessionManager.authenticatedRequest { implicit user =>
381+
val (lockedTasks, failedTasks) = taskIds.foldLeft((List.empty[Task], List.empty[Long])) {
382+
case ((locked, failed), taskId) =>
383+
this.dal.retrieveById(taskId) match {
384+
case Some(task) =>
385+
try {
386+
this.dal.lockItem(user, task)
387+
(task :: locked, failed)
388+
} catch {
389+
case _: LockedException => (locked, taskId :: failed)
390+
}
391+
case None => (locked, failed)
392+
}
393+
}
394+
395+
if (failedTasks.nonEmpty) {
396+
lockedTasks.foreach(task => this.dal.unlockItem(user, task))
397+
Ok(Json.toJson(failedTasks, false))
398+
} else {
399+
Ok(Json.toJson(lockedTasks, true))
400+
}
401+
}
402+
}
403+
404+
/**
405+
* Unlocks a bundle of tasks based on the provided task IDs.
406+
*
407+
* @param taskIds The IDs of the tasks to unlock
408+
* @return
409+
*/
410+
def unlockTaskBundleByIds(taskIds: List[Long]): Action[AnyContent] = Action.async {
411+
implicit request =>
412+
this.sessionManager.authenticatedRequest { implicit user =>
413+
val tasks = taskIds.flatMap { taskId =>
414+
this.dal.retrieveById(taskId) match {
415+
case Some(task) =>
416+
try {
417+
this.dal.unlockItem(user, task)
418+
Some(taskId)
419+
} catch {
420+
case _: Exception => None
421+
}
422+
case None => None
423+
}
424+
}
425+
426+
Ok(Json.toJson(tasks))
427+
}
428+
}
429+
430+
/**
431+
* Refreshes the locks on a bundle of tasks based on the provided task IDs.
432+
*
433+
* @param taskIds The IDs of the tasks to refresh locks
434+
* @return
435+
*/
436+
def refreshTaskLocksByIds(taskIds: List[Long]): Action[AnyContent] = Action.async {
437+
implicit request =>
438+
this.sessionManager.authenticatedRequest { implicit user =>
439+
val result = taskIds.map { taskId =>
440+
this.dal.retrieveById(taskId) match {
441+
case Some(task) =>
442+
try {
443+
this.dal.refreshItemLock(user, task)
444+
Some(taskId)
445+
} catch {
446+
case _: LockedException => None
447+
}
448+
case None => None
449+
}
450+
}.flatten
451+
452+
Ok(Json.toJson(result))
453+
}
454+
}
455+
372456
/**
373457
* Gets a random task(s) given the provided tags.
374458
*

app/org/maproulette/framework/controller/TaskBundleController.scala

+5-6
Original file line numberDiff line numberDiff line change
@@ -262,17 +262,17 @@ class TaskBundleController @Inject() (
262262
}
263263

264264
/**
265-
* Resets the bundle to the tasks provided, and unlock all tasks removed from current bundle
265+
* Sets the bundle to the tasks provided, and unlock all tasks removed from current bundle
266266
*
267267
* @param bundleId The id of the bundle
268268
* @param taskIds The task ids the bundle will reset to
269269
*/
270-
def resetTaskBundle(
270+
def updateTaskBundle(
271271
id: Long,
272272
taskIds: List[Long]
273273
): Action[AnyContent] = Action.async { implicit request =>
274274
this.sessionManager.authenticatedRequest { implicit user =>
275-
this.serviceManager.taskBundle.resetTaskBundle(user, id, taskIds)
275+
this.serviceManager.taskBundle.updateTaskBundle(user, id, taskIds)
276276
Ok(Json.toJson(this.serviceManager.taskBundle.getTaskBundle(user, id)))
277277
}
278278
}
@@ -286,11 +286,10 @@ class TaskBundleController @Inject() (
286286
*/
287287
def unbundleTasks(
288288
id: Long,
289-
taskIds: List[Long],
290-
preventTaskIdUnlocks: List[Long]
289+
taskIds: List[Long]
291290
): Action[AnyContent] = Action.async { implicit request =>
292291
this.sessionManager.authenticatedRequest { implicit user =>
293-
this.serviceManager.taskBundle.unbundleTasks(user, id, taskIds, preventTaskIdUnlocks)
292+
this.serviceManager.taskBundle.unbundleTasks(user, id, taskIds)
294293
Ok(Json.toJson(this.serviceManager.taskBundle.getTaskBundle(user, id)))
295294
}
296295
}

app/org/maproulette/framework/repository/TaskBundleRepository.scala

+32-58
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ class TaskBundleRepository @Inject() (
5151
taskIds: List[Long],
5252
verifyTasks: (List[Task]) => Unit
5353
): TaskBundle = {
54-
this.withMRTransaction { implicit c =>
54+
// First transaction: verify tasks and create bundle
55+
val bundleId = this.withMRTransaction { implicit c =>
5556
val lockedTasks = this.withListLocking(user, Some(TaskType())) { () =>
5657
this.taskDAL.retrieveListById(-1, 0)(taskIds)
5758
}
@@ -70,54 +71,35 @@ class TaskBundleRepository @Inject() (
7071
SQL"""INSERT INTO bundles (owner_id, name) VALUES (${user.id}, ${name})""".executeInsert()
7172

7273
rowId match {
73-
case Some(bundleId: Long) =>
74-
// Update the task object to bind it to the bundle
75-
SQL(s"""UPDATE tasks SET bundle_id = $bundleId
76-
WHERE id IN ({inList})""")
77-
.on(
78-
"inList" -> taskIds
79-
)
80-
.executeUpdate()
81-
82-
primaryId match {
83-
case Some(id) =>
84-
val sqlQuery = s"""UPDATE tasks SET is_bundle_primary = true WHERE id = $id"""
85-
SQL(sqlQuery).executeUpdate()
86-
case None => // Handle the case where primaryId is None
87-
}
88-
89-
val sqlInsertTaskBundles =
90-
s"""INSERT INTO task_bundles (task_id, bundle_id) VALUES ({taskId}, $bundleId)"""
91-
val parameters = lockedTasks.map(task => Seq[NamedParameter]("taskId" -> task.id))
92-
BatchSql(sqlInsertTaskBundles, parameters.head, parameters.tail: _*).execute()
93-
94-
// Lock each of the new tasks to indicate they are part of the bundle
95-
for (task <- lockedTasks) {
96-
try {
97-
this.lockItem(user, task)
98-
} catch {
99-
case e: Exception => this.logger.warn(e.getMessage)
100-
}
101-
taskRepository.cacheManager.cache.remove(task.id)
74+
case Some(id: Long) =>
75+
// Set primary task if specified
76+
primaryId.foreach { pid =>
77+
SQL"""UPDATE tasks SET is_bundle_primary = true WHERE id = $pid""".executeUpdate()
10278
}
103-
104-
TaskBundle(bundleId, user.id, lockedTasks.map(task => {
105-
task.id
106-
}), Some(lockedTasks))
107-
79+
id
10880
case None =>
10981
throw new Exception("Bundle creation failed")
11082
}
11183
}
84+
85+
// Second transaction: add tasks to bundle
86+
this.bundleTasks(user, bundleId, taskIds)
87+
88+
val lockedTasks = this.withListLocking(user, Some(TaskType())) { () =>
89+
this.taskDAL.retrieveListById(-1, 0)(taskIds)
90+
}
91+
92+
// Return the created bundle
93+
TaskBundle(bundleId, user.id, taskIds, Some(lockedTasks))
11294
}
11395

11496
/**
115-
* Resets the bundle to the tasks provided, and unlock all tasks removed from current bundle
97+
* Sets the bundle to the tasks provided, and unlock all tasks removed from current bundle
11698
*
11799
* @param bundleId The id of the bundle
118100
* @param taskIds The task ids the bundle will reset to
119101
*/
120-
def resetTaskBundle(
102+
def updateTaskBundle(
121103
user: User,
122104
bundleId: Long,
123105
taskIds: List[Long]
@@ -134,7 +116,7 @@ class TaskBundleRepository @Inject() (
134116
val tasksToRemove = currentTaskIds.filter(taskId => !taskIds.contains(taskId))
135117

136118
if (tasksToRemove.nonEmpty) {
137-
this.unbundleTasks(user, bundleId, tasksToRemove, List.empty)
119+
this.unbundleTasks(user, bundleId, tasksToRemove)
138120
}
139121

140122
// Filter for tasks that need to be added back to the bundle.
@@ -207,12 +189,6 @@ class TaskBundleRepository @Inject() (
207189
}
208190

209191
lockedTasks.foreach { task =>
210-
try {
211-
this.lockItem(user, task)
212-
} catch {
213-
case e: Exception =>
214-
this.logger.warn(e.getMessage)
215-
}
216192
taskRepository.cacheManager.cache.remove(task.id)
217193
}
218194
}
@@ -226,8 +202,7 @@ class TaskBundleRepository @Inject() (
226202
def unbundleTasks(
227203
user: User,
228204
bundleId: Long,
229-
taskIds: List[Long],
230-
preventTaskIdUnlocks: List[Long]
205+
taskIds: List[Long]
231206
): Unit = {
232207
this.withMRConnection { implicit c =>
233208
val tasks = this.retrieveTasks(
@@ -265,13 +240,6 @@ class TaskBundleRepository @Inject() (
265240
)
266241
.executeUpdate()
267242

268-
if (!preventTaskIdUnlocks.contains(task.id)) {
269-
try {
270-
this.unlockItem(user, task)
271-
} catch {
272-
case e: Exception => this.logger.warn(e.getMessage)
273-
}
274-
}
275243
taskRepository.cacheManager.cache.remove(task.id)
276244
case None => // do nothing
277245
}
@@ -306,13 +274,19 @@ class TaskBundleRepository @Inject() (
306274
.on("bundleId" -> bundleId)
307275
.executeUpdate()
308276

277+
// Update cache for each task
309278
tasks.foreach { task =>
310279
if (!task.isBundlePrimary.getOrElse(false)) {
311-
try {
312-
this.unlockItem(user, task)
313-
} catch {
314-
case e: Exception => this.logger.warn(e.getMessage)
315-
}
280+
SQL(
281+
"""UPDATE tasks
282+
SET status = {status}
283+
WHERE id = {taskId}
284+
"""
285+
).on(
286+
"taskId" -> task.id,
287+
"status" -> STATUS_CREATED
288+
)
289+
.executeUpdate()
316290
}
317291
taskRepository.cacheManager.cache.remove(task.id)
318292
}

app/org/maproulette/framework/service/TaskBundleService.scala

+5-6
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,12 @@ class TaskBundleService @Inject() (
9090
}
9191

9292
/**
93-
* Resets the bundle to the tasks provided, and unlock all tasks removed from current bundle
93+
* Sets the bundle to the tasks provided, and unlock all tasks removed from current bundle
9494
*
9595
* @param bundleId The id of the bundle
9696
* @param taskIds The task ids the bundle will reset to
9797
*/
98-
def resetTaskBundle(
98+
def updateTaskBundle(
9999
user: User,
100100
bundleId: Long,
101101
taskIds: List[Long]
@@ -108,7 +108,7 @@ class TaskBundleService @Inject() (
108108
)
109109
}
110110

111-
this.repository.resetTaskBundle(user, bundleId, taskIds)
111+
this.repository.updateTaskBundle(user, bundleId, taskIds)
112112
this.getTaskBundle(user, bundleId)
113113
}
114114

@@ -120,8 +120,7 @@ class TaskBundleService @Inject() (
120120
def unbundleTasks(
121121
user: User,
122122
bundleId: Long,
123-
taskIds: List[Long],
124-
preventTaskIdUnlocks: List[Long]
123+
taskIds: List[Long]
125124
)(): TaskBundle = {
126125
val bundle = this.getTaskBundle(user, bundleId)
127126

@@ -132,7 +131,7 @@ class TaskBundleService @Inject() (
132131
)
133132
}
134133

135-
this.repository.unbundleTasks(user, bundleId, taskIds, preventTaskIdUnlocks)
134+
this.repository.unbundleTasks(user, bundleId, taskIds)
136135
this.getTaskBundle(user, bundleId)
137136
}
138137

conf/v2_route/bundle.api

+4-8
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ POST /taskBundle @org.maproulette.framework.c
4747
POST /taskBundle/:id @org.maproulette.framework.controller.TaskBundleController.getTaskBundle(id:Long, lockTasks:Boolean ?= false)
4848
###
4949
# tags: [ Bundle ]
50-
# summary: Resets a Task Bundle
51-
# description: Resets the bundle to the tasks provided, and unlock all tasks removed from current bundle
50+
# summary: Updates a Task Bundle
51+
# description: Sets the bundle to the tasks provided, and unlock all tasks removed from current bundle
5252
# responses:
5353
# '200':
5454
# description: Ok with empty body
@@ -63,7 +63,7 @@ POST /taskBundle/:id @org.maproulette.framework.
6363
# in: query
6464
# description: The task ids the bundle will reset to
6565
###
66-
POST /taskBundle/:id/reset @org.maproulette.framework.controller.TaskBundleController.resetTaskBundle(id: Long, taskIds: List[Long])
66+
POST /taskBundle/:id/update @org.maproulette.framework.controller.TaskBundleController.updateTaskBundle(id: Long, taskIds: List[Long])
6767
###
6868
# tags: [ Bundle ]
6969
# summary: Deletes a Task Bundle
@@ -104,9 +104,5 @@ DELETE /taskBundle/:id @org.maproulette.framework.c
104104
# in: query
105105
# description: The list of task ids to remove from the bundle
106106
# required: true
107-
# - name: preventTaskIdUnlocks
108-
# in: query
109-
# description: The list of task ids to keep locked when removed from bundle
110-
# required: true
111107
###
112-
POST /taskBundle/:id/unbundle @org.maproulette.framework.controller.TaskBundleController.unbundleTasks(id:Long, taskIds:List[Long], preventTaskIdUnlocks:List[Long])
108+
POST /taskBundle/:id/unbundle @org.maproulette.framework.controller.TaskBundleController.unbundleTasks(id:Long, taskIds:List[Long])

0 commit comments

Comments
 (0)