TSK-1965: create endpoints for all state transitions (#2024)

* TSK-1965: created endpoint for force claiming a task

* TSK-1965: created endpoint for force completing a task

* TSK-1965: restructured TaskController to match TaskService structure

* TSK-1965: structured TaskControllerIntTest

* TSK-1965: created endpoint for terminating a task

* TSK-1965: created endpoint for setting a task read
This commit is contained in:
norman-schmidt 2022-10-20 14:20:24 +02:00 committed by GitHub
parent e40623ce0f
commit b9b49f47a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 2302 additions and 1768 deletions

View File

@ -169,9 +169,9 @@ public interface TaskService {
* @param taskId the {@linkplain Task#getId() id} of the {@linkplain Task} to be claimed
* @return the claimed {@linkplain Task}
* @throws TaskNotFoundException if the {@linkplain Task} with taskId was not found
* @throws InvalidStateException if the {@linkplain Task#getState() state} of the {@linkplain
* Task} with taskId isn't {@linkplain TaskState#READY}
* @throws InvalidOwnerException if the {@linkplain Task} with taskId is claimed by someone else
* @throws InvalidStateException if the state of Task with taskId is in {@linkplain
* TaskState#END_STATES}
* @throws InvalidOwnerException cannot be thrown
* @throws NotAuthorizedException if the current user has no {@linkplain
* WorkbasketPermission#READ} for the {@linkplain Workbasket} the {@linkplain Task} is in
*/
@ -216,8 +216,7 @@ public interface TaskService {
* @throws TaskNotFoundException if the {@linkplain Task} with taskId was not found
* @throws InvalidStateException if the {@linkplain Task} is already in one of the {@linkplain
* TaskState#END_STATES}
* @throws InvalidOwnerException if forceCancel is false and the {@linkplain Task} is claimed by
* another user
* @throws InvalidOwnerException cannot be thrown
* @throws NotAuthorizedException if the current user has no {@linkplain
* WorkbasketPermission#READ} for the {@linkplain Workbasket} the {@linkplain Task} is in
*/
@ -326,7 +325,9 @@ public interface TaskService {
* @param taskId the {@linkplain Task#getId() id} of the {@linkplain Task} which should be
* completed
* @return the updated {@linkplain Task} after completion
* @throws InvalidStateException if the {@linkplain Task} with taskId wasn't claimed before
* @throws InvalidStateException if the {@linkplain Task#getState() state} of the {@linkplain
* Task} with taskId is with taskId is {@linkplain TaskState#TERMINATED} or {@linkplain
* TaskState#CANCELLED}
* @throws TaskNotFoundException if the {@linkplain Task} with taskId wasn't found
* @throws InvalidOwnerException if current user isn't the {@linkplain Task#getOwner() owner} of
* the {@linkplain Task} or {@linkplain TaskanaRole#ADMIN}
@ -389,13 +390,13 @@ public interface TaskService {
*
* <p>This is typically done by administration to correct any technical issue.
*
* @param taskId the {@linkplain Task#getId() id} of the {@linkplain Task} to cancel
* @param taskId the {@linkplain Task#getId() id} of the {@linkplain Task} to terminate
* @return the updated {@linkplain Task}
* @throws TaskNotFoundException if the {@linkplain Task} with taskId wasn't found
* @throws InvalidStateException if the {@linkplain Task} isn't in {@linkplain TaskState#READY} or
* {@linkplain TaskState#CLAIMED}
* @throws NotAuthorizedException if the current user isn't member of {@linkplain
* TaskanaRole#ADMIN} or {@linkplain TaskanaRole#BUSINESS_ADMIN}
* TaskanaRole#ADMIN} or {@linkplain TaskanaRole#TASK_ADMIN}
*/
Task terminateTask(String taskId)
throws TaskNotFoundException, InvalidStateException, NotAuthorizedException;
@ -710,8 +711,9 @@ public interface TaskService {
* @throws TaskNotFoundException if the given {@linkplain Task#getId() id} doesn't refer to an
* existing {@linkplain Task}
* @throws InvalidStateException if the {@linkplain Task#getState() state} of the referenced
* {@linkplain Task} isn't one of the {@linkplain TaskState#END_STATES} and forceDelete is
* false
* {@linkplain Task} isn't {@linkplain TaskState#TERMINATED} or {@linkplain
* TaskState#CANCELLED} and the Callback State of the Task is {@linkplain
* CallbackState#CALLBACK_PROCESSING_REQUIRED}
* @throws NotAuthorizedException if the current user isn't member of {@linkplain
* TaskanaRole#ADMIN}
*/

View File

@ -8,8 +8,7 @@ This is the REST documentation for http://taskana.pro)[TASKANA] - the world's fi
Whenever a parameter is an array type, several values can be passed by declaring that parameter multiple times.
Whenever a parameter is a complex type, the attributes of the value-object can be passed as a json.
For example, a complex parameter with the name "complex-query-param" and attributes "attribute1" and "attribute2"
would be specified in the following way: +
For example, a complex parameter with the name "complex-query-param" and attributes "attribute1" and "attribute2" would be specified in the following way: +
complex-query-param={"attribute1":"value1","attribute2":"value2"}
=== Hypermedia Support
@ -101,6 +100,7 @@ include::{snippets}/TaskControllerRestDocTest/getSpecificTaskDocTest/auto-sectio
include::{snippets}/TaskControllerRestDocTest/getAllTasksDocTest/auto-section.adoc[]
include::{snippets}/TaskControllerRestDocTest/updateTaskDocTest/auto-section.adoc[]
include::{snippets}/TaskControllerRestDocTest/claimTaskDocTest/auto-section.adoc[]
include::{snippets}/TaskControllerRestDocTest/forceClaimTaskDocTest/auto-section.adoc[]
include::{snippets}/TaskControllerRestDocTest/selectAndClaimTaskDocTest/auto-section.adoc[]
include::{snippets}/TaskControllerRestDocTest/cancelClaimTaskDocTest/auto-section.adoc[]
include::{snippets}/TaskControllerRestDocTest/forceCancelClaimTaskDocTest/auto-section.adoc[]
@ -109,9 +109,13 @@ include::{snippets}/TaskControllerRestDocTest/forceRequestReviewTaskDocTest/auto
include::{snippets}/TaskControllerRestDocTest/requestChangesTaskDocTest/auto-section.adoc[]
include::{snippets}/TaskControllerRestDocTest/forceRequestChangesTaskDocTest/auto-section.adoc[]
include::{snippets}/TaskControllerRestDocTest/completeTaskDocTest/auto-section.adoc[]
include::{snippets}/TaskControllerRestDocTest/forceCompleteTaskDocTest/auto-section.adoc[]
include::{snippets}/TaskControllerRestDocTest/cancelTaskDocTest/auto-section.adoc[]
include::{snippets}/TaskControllerRestDocTest/terminateTaskDocTest/auto-section.adoc[]
include::{snippets}/TaskControllerRestDocTest/transferTaskDocTest/auto-section.adoc[]
include::{snippets}/TaskControllerRestDocTest/setTaskReadDocTest/auto-section.adoc[]
include::{snippets}/TaskControllerRestDocTest/deleteTaskDocTest/auto-section.adoc[]
include::{snippets}/TaskControllerRestDocTest/forceDeleteTaskDocTest/auto-section.adoc[]
include::{snippets}/TaskControllerRestDocTest/deleteTasksDocTest/auto-section.adoc[]
== Task Comment Resource

View File

@ -43,6 +43,7 @@ public final class RestEndpoints {
// task endpoints
public static final String URL_TASKS = API_V1 + "tasks";
public static final String URL_TASKS_ID = API_V1 + "tasks/{taskId}";
public static final String URL_TASKS_ID_FORCE = API_V1 + "tasks/{taskId}/force";
public static final String URL_TASKS_ID_CLAIM = API_V1 + "tasks/{taskId}/claim";
public static final String URL_TASKS_ID_CLAIM_FORCE = API_V1 + "tasks/{taskId}/claim/force";
public static final String URL_TASKS_ID_SELECT_AND_CLAIM = API_V1 + "tasks/select-and-claim";
@ -54,9 +55,12 @@ public final class RestEndpoints {
public static final String URL_TASKS_ID_REQUEST_CHANGES_FORCE =
API_V1 + "tasks/{taskId}/request-changes/force";
public static final String URL_TASKS_ID_COMPLETE = API_V1 + "tasks/{taskId}/complete";
public static final String URL_TASKS_ID_COMPLETE_FORCE = API_V1 + "tasks/{taskId}/complete/force";
public static final String URL_TASKS_ID_CANCEL = API_V1 + "tasks/{taskId}/cancel";
public static final String URL_TASKS_ID_TERMINATE = API_V1 + "tasks/{taskId}/terminate";
public static final String URL_TASKS_ID_TRANSFER_WORKBASKET_ID =
API_V1 + "tasks/{taskId}/transfer/{workbasketId}";
public static final String URL_TASKS_ID_SET_READ = API_V1 + "tasks/{taskId}/set-read";
// task comment endpoints
public static final String URL_TASK_COMMENTS = API_V1 + "tasks/{taskId}/comments";

View File

@ -49,6 +49,7 @@ import pro.taskana.task.api.models.Task;
import pro.taskana.task.api.models.TaskSummary;
import pro.taskana.task.rest.assembler.TaskRepresentationModelAssembler;
import pro.taskana.task.rest.assembler.TaskSummaryRepresentationModelAssembler;
import pro.taskana.task.rest.models.IsReadRepresentationModel;
import pro.taskana.task.rest.models.TaskRepresentationModel;
import pro.taskana.task.rest.models.TaskSummaryCollectionRepresentationModel;
import pro.taskana.task.rest.models.TaskSummaryPagedRepresentationModel;
@ -73,6 +74,43 @@ public class TaskController {
this.taskSummaryRepresentationModelAssembler = taskSummaryRepresentationModelAssembler;
}
// region CREATE
/**
* This endpoint creates a persistent Task.
*
* @param taskRepresentationModel the Task which should be created.
* @return the created Task
* @throws WorkbasketNotFoundException if the referenced Workbasket does not exist
* @throws ClassificationNotFoundException if the referenced Classification does not exist
* @throws NotAuthorizedException if the current user is not authorized to append a Task to the
* referenced Workbasket
* @throws TaskAlreadyExistException if the requested Task already exists.
* @throws InvalidArgumentException if any input is semantically wrong.
* @throws AttachmentPersistenceException if an Attachment with ID will be added multiple times
* without using the task-methods
* @throws ObjectReferencePersistenceException if an ObjectReference with ID will be added
* multiple times without using the task-methods
* @title Create a new Task
*/
@PostMapping(path = RestEndpoints.URL_TASKS)
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<TaskRepresentationModel> createTask(
@RequestBody TaskRepresentationModel taskRepresentationModel)
throws WorkbasketNotFoundException, ClassificationNotFoundException, NotAuthorizedException,
TaskAlreadyExistException, InvalidArgumentException, AttachmentPersistenceException,
ObjectReferencePersistenceException {
Task fromResource = taskRepresentationModelAssembler.toEntityModel(taskRepresentationModel);
Task createdTask = taskService.createTask(fromResource);
return ResponseEntity.status(HttpStatus.CREATED)
.body(taskRepresentationModelAssembler.toModel(createdTask));
}
// endregion
// region READ
/**
* This endpoint retrieves a list of existing Tasks. Filters can be applied.
*
@ -116,50 +154,6 @@ public class TaskController {
return ResponseEntity.ok(pagedModels);
}
/**
* This endpoint deletes an aggregation of Tasks and returns the deleted Tasks. Filters can be
* applied.
*
* @title Delete multiple Tasks
* @param filterParameter the filter parameters
* @param filterCustomFields the filter parameters regarding TaskCustomFields
* @param filterCustomIntFields the filter parameters regarding TaskCustomIntFields
* @return the deleted task summaries
* @throws InvalidArgumentException TODO: this is never thrown
* @throws NotAuthorizedException if the current user is not authorized to delete the requested
* Tasks.
*/
@DeleteMapping(path = RestEndpoints.URL_TASKS)
@Transactional(readOnly = true, rollbackFor = Exception.class)
public ResponseEntity<TaskSummaryCollectionRepresentationModel> deleteTasks(
TaskQueryFilterParameter filterParameter,
TaskQueryFilterCustomFields filterCustomFields,
TaskQueryFilterCustomIntFields filterCustomIntFields)
throws InvalidArgumentException, NotAuthorizedException {
TaskQuery query = taskService.createTaskQuery();
filterParameter.apply(query);
filterCustomFields.apply(query);
filterCustomIntFields.apply(query);
List<TaskSummary> taskSummaries = query.list();
List<String> taskIdsToDelete =
taskSummaries.stream().map(TaskSummary::getId).collect(Collectors.toList());
BulkOperationResults<String, TaskanaException> result =
taskService.deleteTasks(taskIdsToDelete);
Set<String> failedIds = new HashSet<>(result.getFailedIds());
List<TaskSummary> successfullyDeletedTaskSummaries =
taskSummaries.stream()
.filter(not(summary -> failedIds.contains(summary.getId())))
.collect(Collectors.toList());
return ResponseEntity.ok(
taskSummaryRepresentationModelAssembler.toTaskanaCollectionModel(
successfullyDeletedTaskSummaries));
}
/**
* This endpoint retrieves a specific Task.
*
@ -178,6 +172,10 @@ public class TaskController {
return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(task));
}
// endregion
// region UPDATE
/**
* This endpoint claims a Task if possible.
*
@ -198,8 +196,108 @@ public class TaskController {
throws TaskNotFoundException, InvalidStateException, InvalidOwnerException,
NotAuthorizedException {
// TODO verify user
taskService.claim(taskId);
Task updatedTask = taskService.getTask(taskId);
Task updatedTask = taskService.claim(taskId);
return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(updatedTask));
}
/**
* This endpoint force claims a Task if possible even if it is already claimed by someone else.
*
* @param taskId the Id of the Task which should be force claimed
* @param userName TODO: this is currently not used
* @return the force claimed Task
* @throws TaskNotFoundException if the requested Task does not exist.
* @throws InvalidStateException if the state of Task with taskId is in an END_STATE.
* @throws InvalidOwnerException cannot be thrown.
* @throws NotAuthorizedException if the current user has no read permissions for the requested
* Task.
* @title Force claim a Task
*/
@PostMapping(path = RestEndpoints.URL_TASKS_ID_CLAIM_FORCE)
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<TaskRepresentationModel> forceClaimTask(
@PathVariable String taskId, @RequestBody(required = false) String userName)
throws TaskNotFoundException, InvalidStateException, InvalidOwnerException,
NotAuthorizedException {
// TODO verify user
Task updatedTask = taskService.forceClaim(taskId);
return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(updatedTask));
}
/**
* This endpoint selects the first Task returned by the Task Query and claims it.
*
* @param filterParameter the filter parameters
* @param filterCustomFields the filter parameters regarding TaskCustomFields
* @param filterCustomIntFields the filter parameters regarding TaskCustomIntFields
* @param sortParameter the sort parameters
* @return the claimed Task
* @throws InvalidOwnerException if the Task is already claimed by someone else
* @throws NotAuthorizedException if the current user has no read permission for the Workbasket
* the Task is in
* @title Select and claim a Task
*/
@PostMapping(path = RestEndpoints.URL_TASKS_ID_SELECT_AND_CLAIM)
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<TaskRepresentationModel> selectAndClaimTask(
TaskQueryFilterParameter filterParameter,
TaskQueryFilterCustomFields filterCustomFields,
TaskQueryFilterCustomIntFields filterCustomIntFields,
TaskQuerySortParameter sortParameter)
throws InvalidOwnerException, NotAuthorizedException {
TaskQuery query = taskService.createTaskQuery();
filterParameter.apply(query);
filterCustomFields.apply(query);
filterCustomIntFields.apply(query);
sortParameter.apply(query);
Task selectedAndClaimedTask = taskService.selectAndClaim(query);
return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(selectedAndClaimedTask));
}
/**
* This endpoint cancels the claim of an existing Task if it was claimed by the current user
* before.
*
* @param taskId the Id of the requested Task.
* @return the unclaimed Task.
* @throws TaskNotFoundException if the requested Task does not exist.
* @throws InvalidStateException if the Task is already in an end state.
* @throws InvalidOwnerException if the Task is claimed by a different user.
* @throws NotAuthorizedException if the current user has no read permission for the Workbasket
* the Task is in
* @title Cancel a claimed Task
*/
@DeleteMapping(path = RestEndpoints.URL_TASKS_ID_CLAIM)
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<TaskRepresentationModel> cancelClaimTask(@PathVariable String taskId)
throws TaskNotFoundException, InvalidStateException, InvalidOwnerException,
NotAuthorizedException {
Task updatedTask = taskService.cancelClaim(taskId);
return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(updatedTask));
}
/**
* This endpoint force cancels the claim of an existing Task.
*
* @param taskId the Id of the requested Task.
* @return the unclaimed Task.
* @throws TaskNotFoundException if the requested Task does not exist.
* @throws InvalidStateException if the Task is already in an end state.
* @throws InvalidOwnerException if the Task is claimed by a different user.
* @throws NotAuthorizedException if the current user has no read permission for the Workbasket
* the Task is in
* @title Force cancel a claimed Task
*/
@DeleteMapping(path = RestEndpoints.URL_TASKS_ID_CLAIM_FORCE)
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<TaskRepresentationModel> forceCancelClaimTask(@PathVariable String taskId)
throws TaskNotFoundException, InvalidStateException, InvalidOwnerException,
NotAuthorizedException {
Task updatedTask = taskService.forceCancelClaim(taskId);
return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(updatedTask));
}
@ -287,83 +385,6 @@ public class TaskController {
return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(task));
}
/**
* This endpoint selects the first Task returned by the Task Query and claims it.
*
* @param filterParameter the filter parameters
* @param filterCustomFields the filter parameters regarding TaskCustomFields
* @param filterCustomIntFields the filter parameters regarding TaskCustomIntFields
* @param sortParameter the sort parameters
* @return the claimed Task
* @throws InvalidOwnerException if the Task is already claimed by someone else
* @throws NotAuthorizedException if the current user has no read permission for the Workbasket
* the Task is in
* @title Select and claim a Task
*/
@PostMapping(path = RestEndpoints.URL_TASKS_ID_SELECT_AND_CLAIM)
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<TaskRepresentationModel> selectAndClaimTask(
TaskQueryFilterParameter filterParameter,
TaskQueryFilterCustomFields filterCustomFields,
TaskQueryFilterCustomIntFields filterCustomIntFields,
TaskQuerySortParameter sortParameter)
throws InvalidOwnerException, NotAuthorizedException {
TaskQuery query = taskService.createTaskQuery();
filterParameter.apply(query);
filterCustomFields.apply(query);
filterCustomIntFields.apply(query);
sortParameter.apply(query);
Task selectedAndClaimedTask = taskService.selectAndClaim(query);
return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(selectedAndClaimedTask));
}
/**
* This endpoint cancels the claim of an existing Task if it was claimed by the current user
* before.
*
* @param taskId the Id of the requested Task.
* @return the unclaimed Task.
* @throws TaskNotFoundException if the requested Task does not exist.
* @throws InvalidStateException if the Task is already in an end state.
* @throws InvalidOwnerException if the Task is claimed by a different user.
* @throws NotAuthorizedException if the current user has no read permission for the Workbasket
* the Task is in
* @title Cancel a claimed Task
*/
@DeleteMapping(path = RestEndpoints.URL_TASKS_ID_CLAIM)
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<TaskRepresentationModel> cancelClaimTask(@PathVariable String taskId)
throws TaskNotFoundException, InvalidStateException, InvalidOwnerException,
NotAuthorizedException {
Task updatedTask = taskService.cancelClaim(taskId);
return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(updatedTask));
}
/**
* This endpoint force cancels the claim of an existing Task.
*
* @param taskId the Id of the requested Task.
* @return the unclaimed Task.
* @throws TaskNotFoundException if the requested Task does not exist.
* @throws InvalidStateException if the Task is already in an end state.
* @throws InvalidOwnerException if the Task is claimed by a different user.
* @throws NotAuthorizedException if the current user has no read permission for the Workbasket
* the Task is in
* @title Force cancel a claimed Task
*/
@DeleteMapping(path = RestEndpoints.URL_TASKS_ID_CLAIM_FORCE)
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<TaskRepresentationModel> forceCancelClaimTask(@PathVariable String taskId)
throws TaskNotFoundException, InvalidStateException, InvalidOwnerException,
NotAuthorizedException {
Task updatedTask = taskService.forceCancelClaim(taskId);
return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(updatedTask));
}
/**
* This endpoint completes a Task.
*
@ -382,29 +403,32 @@ public class TaskController {
throws TaskNotFoundException, InvalidOwnerException, InvalidStateException,
NotAuthorizedException {
Task updatedTask = taskService.forceCompleteTask(taskId);
Task updatedTask = taskService.completeTask(taskId);
return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(updatedTask));
}
/**
* This endpoint deletes a Task.
* This endpoint force completes a Task.
*
* @title Delete a Task
* @param taskId the Id of the Task which should be deleted.
* @return the deleted Task.
* @param taskId Id of the requested Task to force complete.
* @return the force completed Task
* @throws TaskNotFoundException if the requested Task does not exist.
* @throws InvalidStateException TODO: this is never thrown
* @throws NotAuthorizedException if the current user is not authorized to delete the requested
* Task.
* @throws InvalidOwnerException cannot be thrown.
* @throws InvalidStateException if the state of the Task with taskId is TERMINATED or CANCELED
* @throws NotAuthorizedException if the current user has no read permission for the Workbasket
* the Task is in
* @title Force complete a Task
*/
@DeleteMapping(path = RestEndpoints.URL_TASKS_ID)
@PostMapping(path = RestEndpoints.URL_TASKS_ID_COMPLETE_FORCE)
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<TaskRepresentationModel> deleteTask(@PathVariable String taskId)
throws TaskNotFoundException, InvalidStateException, NotAuthorizedException {
taskService.forceDeleteTask(taskId);
public ResponseEntity<TaskRepresentationModel> forceCompleteTask(@PathVariable String taskId)
throws TaskNotFoundException, InvalidOwnerException, InvalidStateException,
NotAuthorizedException {
return ResponseEntity.noContent().build();
Task updatedTask = taskService.forceCompleteTask(taskId);
return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(updatedTask));
}
/**
@ -430,34 +454,23 @@ public class TaskController {
}
/**
* This endpoint creates a persistent Task.
* This endpoint terminates a Task. Termination is an administrative action to complete a Task.
*
* @param taskRepresentationModel the Task which should be created.
* @return the created Task
* @throws WorkbasketNotFoundException if the referenced Workbasket does not exist
* @throws ClassificationNotFoundException if the referenced Classification does not exist
* @throws NotAuthorizedException if the current user is not authorized to append a Task to the
* referenced Workbasket
* @throws TaskAlreadyExistException if the requested Task already exists.
* @throws InvalidArgumentException if any input is semantically wrong.
* @throws AttachmentPersistenceException if an Attachment with ID will be added multiple times
* without using the task-methods
* @throws ObjectReferencePersistenceException if an ObjectReference with ID will be added
* multiple times without using the task-methods
* @title Create a new Task
* @param taskId Id of the requested Task to terminate.
* @return the terminated Task
* @throws TaskNotFoundException if the requested Task does not exist.
* @throws InvalidStateException if the task is not in state READY or CLAIMED
* @throws NotAuthorizedException if the current user isn't an administrator (ADMIN/TASKADMIN)
* @title Terminate a Task
*/
@PostMapping(path = RestEndpoints.URL_TASKS)
@PostMapping(path = RestEndpoints.URL_TASKS_ID_TERMINATE)
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<TaskRepresentationModel> createTask(
@RequestBody TaskRepresentationModel taskRepresentationModel)
throws WorkbasketNotFoundException, ClassificationNotFoundException, NotAuthorizedException,
TaskAlreadyExistException, InvalidArgumentException, AttachmentPersistenceException,
ObjectReferencePersistenceException {
Task fromResource = taskRepresentationModelAssembler.toEntityModel(taskRepresentationModel);
Task createdTask = taskService.createTask(fromResource);
public ResponseEntity<TaskRepresentationModel> terminateTask(@PathVariable String taskId)
throws TaskNotFoundException, NotAuthorizedException, InvalidStateException {
return ResponseEntity.status(HttpStatus.CREATED)
.body(taskRepresentationModelAssembler.toModel(createdTask));
Task terminatedTask = taskService.terminateTask(taskId);
return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(terminatedTask));
}
/**
@ -527,6 +540,119 @@ public class TaskController {
return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(task));
}
/**
* This endpoint sets the 'isRead' property of a Task.
*
* @param taskId Id of the requested Task to set read or unread.
* @param isRead if true, the Task property isRead is set to true, else it's set to false
* @return the updated Task
* @throws TaskNotFoundException if the requested Task does not exist.
* @throws NotAuthorizedException if the current user has no read permission for the Workbasket
* the Task is in
* @title Set a Task read or unread
*/
@PostMapping(path = RestEndpoints.URL_TASKS_ID_SET_READ)
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<TaskRepresentationModel> setTaskRead(
@PathVariable String taskId, @RequestBody IsReadRepresentationModel isRead)
throws TaskNotFoundException, NotAuthorizedException {
Task updatedTask = taskService.setTaskRead(taskId, isRead.getIsRead());
return ResponseEntity.ok(taskRepresentationModelAssembler.toModel(updatedTask));
}
// endregion
// region DELETE
/**
* This endpoint deletes a Task.
*
* @title Delete a Task
* @param taskId the Id of the Task which should be deleted.
* @return the deleted Task.
* @throws TaskNotFoundException if the requested Task does not exist.
* @throws InvalidStateException If the Task is not in an END_STATE
* @throws NotAuthorizedException if the current user isn't an administrator (ADMIN)
*/
@DeleteMapping(path = RestEndpoints.URL_TASKS_ID)
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<TaskRepresentationModel> deleteTask(@PathVariable String taskId)
throws TaskNotFoundException, InvalidStateException, NotAuthorizedException {
taskService.deleteTask(taskId);
return ResponseEntity.noContent().build();
}
/**
* This endpoint force deletes a Task even if it's not completed.
*
* @title Force delete a Task
* @param taskId the Id of the Task which should be force deleted.
* @return the force deleted Task.
* @throws TaskNotFoundException if the requested Task does not exist.
* @throws InvalidStateException If the Task is not TERMINATED or CANCELLED and the Callback state
* of the Task is CALLBACK_PROCESSING_REQUIRED
* @throws NotAuthorizedException if the current user isn't an administrator (ADMIN) Task.
*/
@DeleteMapping(path = RestEndpoints.URL_TASKS_ID_FORCE)
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<TaskRepresentationModel> forceDeleteTask(@PathVariable String taskId)
throws TaskNotFoundException, InvalidStateException, NotAuthorizedException {
taskService.forceDeleteTask(taskId);
return ResponseEntity.noContent().build();
}
/**
* This endpoint deletes an aggregation of Tasks and returns the deleted Tasks. Filters can be
* applied.
*
* @title Delete multiple Tasks
* @param filterParameter the filter parameters
* @param filterCustomFields the filter parameters regarding TaskCustomFields
* @param filterCustomIntFields the filter parameters regarding TaskCustomIntFields
* @return the deleted task summaries
* @throws InvalidArgumentException TODO: this is never thrown
* @throws NotAuthorizedException if the current user is not authorized to delete the requested
* Tasks.
*/
@DeleteMapping(path = RestEndpoints.URL_TASKS)
@Transactional(readOnly = true, rollbackFor = Exception.class)
public ResponseEntity<TaskSummaryCollectionRepresentationModel> deleteTasks(
TaskQueryFilterParameter filterParameter,
TaskQueryFilterCustomFields filterCustomFields,
TaskQueryFilterCustomIntFields filterCustomIntFields)
throws InvalidArgumentException, NotAuthorizedException {
TaskQuery query = taskService.createTaskQuery();
filterParameter.apply(query);
filterCustomFields.apply(query);
filterCustomIntFields.apply(query);
List<TaskSummary> taskSummaries = query.list();
List<String> taskIdsToDelete =
taskSummaries.stream().map(TaskSummary::getId).collect(Collectors.toList());
BulkOperationResults<String, TaskanaException> result =
taskService.deleteTasks(taskIdsToDelete);
Set<String> failedIds = new HashSet<>(result.getFailedIds());
List<TaskSummary> successfullyDeletedTaskSummaries =
taskSummaries.stream()
.filter(not(summary -> failedIds.contains(summary.getId())))
.collect(Collectors.toList());
return ResponseEntity.ok(
taskSummaryRepresentationModelAssembler.toTaskanaCollectionModel(
successfullyDeletedTaskSummaries));
}
// endregion
// region TaskQuery
public enum TaskQuerySortBy implements QuerySortBy<TaskQuery> {
CLASSIFICATION_KEY(TaskQuery::orderByClassificationKey),
CLASSIFICATION_NAME(TaskQuery::orderByClassificationName),
@ -608,4 +734,7 @@ public class TaskController {
return super.getSortBy();
}
}
// endregion
}

View File

@ -0,0 +1,20 @@
package pro.taskana.task.rest.models;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.beans.ConstructorProperties;
public class IsReadRepresentationModel {
/** The value to set the Task property isRead. */
@JsonProperty("is-read")
private final boolean isRead;
@ConstructorProperties({"is-read"})
public IsReadRepresentationModel(boolean isRead) {
this.isRead = isRead;
}
public boolean getIsRead() {
return isRead;
}
}

View File

@ -19,6 +19,7 @@ import pro.taskana.task.api.TaskService;
import pro.taskana.task.api.models.Task;
import pro.taskana.task.internal.models.ObjectReferenceImpl;
import pro.taskana.task.rest.assembler.TaskRepresentationModelAssembler;
import pro.taskana.task.rest.models.IsReadRepresentationModel;
import pro.taskana.task.rest.models.TaskRepresentationModel;
@ExtendWith(JaasExtension.class)
@ -34,6 +35,21 @@ class TaskControllerRestDocTest extends BaseRestDocTest {
.andExpect(MockMvcResultMatchers.status().isOk());
}
@Test
void deleteTaskDocTest() throws Exception {
mockMvc
.perform(delete(RestEndpoints.URL_TASKS_ID, "TKI:000000000000000000000000000000000039"))
.andExpect(MockMvcResultMatchers.status().isNoContent());
}
@Test
void forceDeleteTaskDocTest() throws Exception {
mockMvc
.perform(
delete(RestEndpoints.URL_TASKS_ID_FORCE, "TKI:000000000000000000000000000000000005"))
.andExpect(MockMvcResultMatchers.status().isNoContent());
}
@Test
void deleteTasksDocTest() throws Exception {
mockMvc
@ -60,6 +76,15 @@ class TaskControllerRestDocTest extends BaseRestDocTest {
.andExpect(MockMvcResultMatchers.status().isOk());
}
@Test
void forceClaimTaskDocTest() throws Exception {
mockMvc
.perform(
post(
RestEndpoints.URL_TASKS_ID_CLAIM_FORCE, "TKI:000000000000000000000000000000000003"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
@Test
void cancelClaimTaskDocTest() throws Exception {
mockMvc
@ -139,6 +164,16 @@ class TaskControllerRestDocTest extends BaseRestDocTest {
.andExpect(MockMvcResultMatchers.status().isOk());
}
@Test
void forceCompleteTaskDocTest() throws Exception {
mockMvc
.perform(
post(
RestEndpoints.URL_TASKS_ID_COMPLETE_FORCE,
"TKI:000000000000000000000000000000000003"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
@Test
void cancelTaskDocTest() throws Exception {
mockMvc
@ -147,6 +182,26 @@ class TaskControllerRestDocTest extends BaseRestDocTest {
.andExpect(MockMvcResultMatchers.status().isOk());
}
@Test
void terminateTaskDocTest() throws Exception {
mockMvc
.perform(
post(RestEndpoints.URL_TASKS_ID_TERMINATE, "TKI:000000000000000000000000000000000000"))
.andExpect(MockMvcResultMatchers.status().isOk());
}
@Test
void setTaskReadDocTest() throws Exception {
IsReadRepresentationModel isRead = new IsReadRepresentationModel(true);
mockMvc
.perform(
post(RestEndpoints.URL_TASKS_ID_SET_READ, "TKI:000000000000000000000000000000000025")
.content(objectMapper.writeValueAsString(isRead)))
.andExpect(MockMvcResultMatchers.status().isOk());
}
@Test
void createTaskDocTest() throws Exception {
final Task task = taskService.newTask("WBI:100000000000000000000000000000000004");
@ -165,13 +220,6 @@ class TaskControllerRestDocTest extends BaseRestDocTest {
.andExpect(MockMvcResultMatchers.status().isCreated());
}
@Test
void deleteTaskDocTest() throws Exception {
mockMvc
.perform(delete(RestEndpoints.URL_TASKS_ID, "TKI:000000000000000000000000000000000001"))
.andExpect(MockMvcResultMatchers.status().isNoContent());
}
@Test
void transferTaskDocTest() throws Exception {
mockMvc