From 2c4602045912e3b12ca35c3be9575ee53dd14898 Mon Sep 17 00:00:00 2001 From: James Reynaldi Date: Tue, 23 Jul 2024 16:35:00 +0200 Subject: [PATCH] Closes #2622: Extend OpenAPI with History and Routing Doc --- .../rest/TaskHistoryEventController.java | 50 ++- .../rest/TaskHistoryQueryFilterParameter.java | 328 ++++++++++++++++++ ...kHistoryEventPagedRepresentationModel.java | 2 + .../TaskHistoryEventRepresentationModel.java | 42 +++ .../example/boot/OpenApiConfiguration.java | 5 + .../routing/dmn/rest/DmnUploadController.java | 107 +++++- ...outingUploadResultRepresentationModel.java | 7 + 7 files changed, 537 insertions(+), 4 deletions(-) diff --git a/history/taskana-simplehistory-rest-spring/src/main/java/pro/taskana/simplehistory/rest/TaskHistoryEventController.java b/history/taskana-simplehistory-rest-spring/src/main/java/pro/taskana/simplehistory/rest/TaskHistoryEventController.java index f0b8c58991..b6f5f27fad 100644 --- a/history/taskana-simplehistory-rest-spring/src/main/java/pro/taskana/simplehistory/rest/TaskHistoryEventController.java +++ b/history/taskana-simplehistory-rest-spring/src/main/java/pro/taskana/simplehistory/rest/TaskHistoryEventController.java @@ -1,9 +1,15 @@ package pro.taskana.simplehistory.rest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import jakarta.servlet.http.HttpServletRequest; import java.beans.ConstructorProperties; import java.util.List; import java.util.function.BiConsumer; +import org.springdoc.core.annotations.ParameterObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.MediaTypes; import org.springframework.hateoas.config.EnableHypermediaSupport; @@ -57,13 +63,31 @@ public TaskHistoryEventController( * @param pagingParameter the paging parameters * @return the Task History Events with the given filter, sort and paging options. */ + @Operation( + summary = "Get a list of all Task History Events", + description = + "This endpoint retrieves a list of existing Task History Events. Filters can be applied.", + parameters = { + @Parameter(name = "page", example = "1"), + @Parameter(name = "page-size", example = "3") + }, + responses = { + @ApiResponse( + responseCode = "200", + description = "the Task History Events with the given filter, sort and paging options.", + content = { + @Content( + mediaType = MediaTypes.HAL_JSON_VALUE, + schema = @Schema(implementation = TaskHistoryEventPagedRepresentationModel.class)) + }) + }) @GetMapping(path = HistoryRestEndpoints.URL_HISTORY_EVENTS, produces = MediaTypes.HAL_JSON_VALUE) @Transactional(readOnly = true, rollbackFor = Exception.class) public ResponseEntity getTaskHistoryEvents( HttpServletRequest request, - TaskHistoryQueryFilterParameter filterParameter, - TaskHistoryQuerySortParameter sortParameter, - QueryPagingParameter pagingParameter) { + @ParameterObject TaskHistoryQueryFilterParameter filterParameter, + @ParameterObject TaskHistoryQuerySortParameter sortParameter, + @ParameterObject QueryPagingParameter pagingParameter) { QueryParamsValidator.validateParams( request, @@ -92,6 +116,26 @@ public ResponseEntity getTaskHistoryEv * @throws TaskanaHistoryEventNotFoundException If a Task History Event can't be found by the * provided historyEventId */ + @Operation( + summary = "Get a single Task History Event", + description = "This endpoint retrieves a single Task History Event.", + parameters = { + @Parameter( + name = "historyEventId", + description = "the Id of the requested Task History Event.", + example = "THI:000000000000000000000000000000000000", + required = true), + }, + responses = { + @ApiResponse( + responseCode = "200", + description = "the requested Task History Event", + content = { + @Content( + mediaType = MediaTypes.HAL_JSON_VALUE, + schema = @Schema(implementation = TaskHistoryEventRepresentationModel.class)) + }) + }) @GetMapping(path = HistoryRestEndpoints.URL_HISTORY_EVENTS_ID) @Transactional(readOnly = true, rollbackFor = Exception.class) public ResponseEntity getTaskHistoryEvent( diff --git a/history/taskana-simplehistory-rest-spring/src/main/java/pro/taskana/simplehistory/rest/TaskHistoryQueryFilterParameter.java b/history/taskana-simplehistory-rest-spring/src/main/java/pro/taskana/simplehistory/rest/TaskHistoryQueryFilterParameter.java index aae7e9cc6b..a37c7bf8f6 100644 --- a/history/taskana-simplehistory-rest-spring/src/main/java/pro/taskana/simplehistory/rest/TaskHistoryQueryFilterParameter.java +++ b/history/taskana-simplehistory-rest-spring/src/main/java/pro/taskana/simplehistory/rest/TaskHistoryQueryFilterParameter.java @@ -3,6 +3,7 @@ import static java.util.Optional.ofNullable; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; import java.beans.ConstructorProperties; import java.time.Instant; import pro.taskana.common.api.exceptions.InvalidArgumentException; @@ -11,8 +12,154 @@ import pro.taskana.spi.history.api.events.task.TaskHistoryCustomField; public class TaskHistoryQueryFilterParameter implements QueryParameter { + public String[] getEventType() { + return eventType; + } + + public String[] getEventTypeLike() { + return eventTypeLike; + } + + public String[] getUserId() { + return userId; + } + + public String[] getUserIdLike() { + return userIdLike; + } + + public Instant[] getCreated() { + return created; + } + + public String[] getDomain() { + return domain; + } + + public String[] getTaskId() { + return taskId; + } + + public String[] getTaskIdLike() { + return taskIdLike; + } + + public String[] getBusinessProcessId() { + return businessProcessId; + } + + public String[] getBusinessProcessIdLike() { + return businessProcessIdLike; + } + + public String[] getParentBusinessProcessId() { + return parentBusinessProcessId; + } + + public String[] getParentBusinessProcessIdLike() { + return parentBusinessProcessIdLike; + } + + public String[] getTaskClassificationKey() { + return taskClassificationKey; + } + + public String[] getTaskClassificationKeyLike() { + return taskClassificationKeyLike; + } + + public String[] getTaskClassificationCategory() { + return taskClassificationCategory; + } + + public String[] getTaskClassificationCategoryLike() { + return taskClassificationCategoryLike; + } + + public String[] getAttachmentClassificationKey() { + return attachmentClassificationKey; + } + + public String[] getAttachmentClassificationKeyLike() { + return attachmentClassificationKeyLike; + } + + public String[] getWorkbasketKey() { + return workbasketKey; + } + + public String[] getWorkbasketKeyLike() { + return workbasketKeyLike; + } + + public String[] getPorCompany() { + return porCompany; + } + + public String[] getPorCompanyLike() { + return porCompanyLike; + } + + public String[] getPorSystem() { + return porSystem; + } + + public String[] getPorSystemLike() { + return porSystemLike; + } + + public String[] getPorInstance() { + return porInstance; + } + + public String[] getPorInstanceLike() { + return porInstanceLike; + } + + public String[] getPorValue() { + return porValue; + } + + public String[] getPorValueLike() { + return porValueLike; + } + + public String[] getCustom1() { + return custom1; + } + + public String[] getCustom1Like() { + return custom1Like; + } + + public String[] getCustom2() { + return custom2; + } + + public String[] getCustom2Like() { + return custom2Like; + } + + public String[] getCustom3() { + return custom3; + } + + public String[] getCustom3Like() { + return custom3Like; + } + + public String[] getCustom4() { + return custom4; + } + + public String[] getCustom4Like() { + return custom4Like; + } /** Filter by the event type of the Task History Event. This is an exact match. */ + @Schema( + name = "event-type", + description = "Filter by the event type of the Task History Event. This is an exact match.") @JsonProperty("event-type") private final String[] eventType; @@ -21,10 +168,19 @@ public class TaskHistoryQueryFilterParameter implements QueryParameterThe format is ISO-8601. */ + @Schema( + name = "created", + description = + "Filter by a created time interval. The length of the provided values has to be even. To " + + "create an open interval you can either use 'null' or just leave it blank.

The " + + "format is ISO-8601.") private final Instant[] created; /** Filter by the domain of the Task History Event. This is an exact match. */ + @Schema( + name = "domain", + description = "Filter by the domain of the Task History Event. This is an exact match.") private final String[] domain; /** Filter by the task id of the Task History Event. This is an exact match. */ + @Schema( + name = "task-id", + description = "Filter by the task id of the Task History Event. This is an exact match.") @JsonProperty("task-id") private final String[] taskId; @@ -56,10 +230,20 @@ public class TaskHistoryQueryFilterParameter implements QueryParameter getContent() { diff --git a/history/taskana-simplehistory-rest-spring/src/main/java/pro/taskana/simplehistory/rest/models/TaskHistoryEventRepresentationModel.java b/history/taskana-simplehistory-rest-spring/src/main/java/pro/taskana/simplehistory/rest/models/TaskHistoryEventRepresentationModel.java index 68c47d0f24..da7ea571b1 100644 --- a/history/taskana-simplehistory-rest-spring/src/main/java/pro/taskana/simplehistory/rest/models/TaskHistoryEventRepresentationModel.java +++ b/history/taskana-simplehistory-rest-spring/src/main/java/pro/taskana/simplehistory/rest/models/TaskHistoryEventRepresentationModel.java @@ -1,5 +1,6 @@ package pro.taskana.simplehistory.rest.models; +import io.swagger.v3.oas.annotations.media.Schema; import java.time.Instant; import org.springframework.hateoas.RepresentationModel; import pro.taskana.spi.history.api.events.task.TaskHistoryEvent; @@ -9,60 +10,101 @@ public class TaskHistoryEventRepresentationModel extends RepresentationModel { /** Unique Id. */ + @Schema(name = "taskHistoryId", description = "Unique Id.") private String taskHistoryId; /** The Id of the business process. */ + @Schema(name = "businessProcessId", description = "The Id of the business process.") private String businessProcessId; /** The Id of the parent business process. */ + @Schema(name = "parentBusinessProcessId", description = "The Id of the parent business process.") private String parentBusinessProcessId; /** The Id of the task. */ + @Schema(name = "taskId", description = "The Id of the task.") private String taskId; /** The type of the event. */ + @Schema(name = "eventType", description = "The type of the event.") private String eventType; /** * The time of event creation. * *

The format is ISO-8601. */ + @Schema(name = "created", description = "The time of event creation.

The format is ISO-8601.") private Instant created; /** The Id of the user. */ + @Schema(name = "userId", description = "The Id of the user.") private String userId; /** The long name of the user. */ + @Schema(name = "userLongName", description = "The long name of the user.") private String userLongName; /** Domain. */ + @Schema(name = "domain", description = "Domain.") private String domain; /** The key of the Workbasket. */ + @Schema(name = "workbasketKey", description = "The key of the Workbasket.") private String workbasketKey; /** The company the referenced primary object belongs to. */ + @Schema( + name = "porCompany", + description = "The company the referenced primary object belongs to.") private String porCompany; /** The type of the referenced primary object (contract, claim, policy, customer, ...). */ + @Schema( + name = "porType", + description = + "The type of the referenced primary object (contract, claim, policy, customer, ...).") private String porType; /** The (kind of) system, the referenced primary object resides in (e.g. SAP, MySystem A, ...). */ + @Schema( + name = "porSystem", + description = + "The (kind of) system, the referenced primary object resides in (e.g. SAP, MySystem A, " + + "...).") private String porSystem; /** The instance of the system where the referenced primary object is located. */ + @Schema( + name = "porInstance", + description = "The instance of the system where the referenced primary object is located.") private String porInstance; /** The value of the primary object reference. */ + @Schema(name = "porValue", description = "The value of the primary object reference.") private String porValue; /** The long name of the task owner. */ + @Schema(name = "taskOwnerLongName", description = "The long name of the task owner.") private String taskOwnerLongName; /** The key of the task's classification. */ + @Schema(name = "taskClassificationKey", description = "The key of the task's classification.") private String taskClassificationKey; /** The category of the task's classification. */ + @Schema( + name = "taskClassificationCategory", + description = "The category of the task's classification.") private String taskClassificationCategory; /** The classification key of the task's attachment. */ + @Schema( + name = "attachmentClassificationKey", + description = "The classification key of the task's attachment.") private String attachmentClassificationKey; /** The old value. */ + @Schema(name = "oldValue", description = "The old value.") private String oldValue; /** The new value. */ + @Schema(name = "newValue", description = "The new value.") private String newValue; /** A custom property with name "1". */ + @Schema(name = "custom1", description = "A custom property with name '1'.") private String custom1; /** A custom property with name "2". */ + @Schema(name = "custom2", description = "A custom property with name '2'.") private String custom2; /** A custom property with name "3". */ + @Schema(name = "custom3", description = "A custom property with name '3'.") private String custom3; /** A custom property with name "4". */ + @Schema(name = "custom4", description = "A custom property with name '4'.") private String custom4; /** details of changes within the task. */ + @Schema(name = "details", description = "details of changes within the task.") private String details; public String getTaskHistoryId() { diff --git a/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/OpenApiConfiguration.java b/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/OpenApiConfiguration.java index 40dda5e708..eac26a6606 100644 --- a/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/OpenApiConfiguration.java +++ b/rest/taskana-rest-spring-example-boot/src/main/java/pro/taskana/example/boot/OpenApiConfiguration.java @@ -167,6 +167,11 @@ + "workbasketKey, domain" + "" + "" + + "**404 NOT_FOUND**" + + "HISTORY_EVENT_NOT_FOUND" + + "historyEventId" + + "" + + "" + "**409 CONFLICT**" + "ATTACHMENT_ALREADY_EXISTS" + "attachmentId, taskId" diff --git a/routing/taskana-routing-rest/src/main/java/pro/taskana/routing/dmn/rest/DmnUploadController.java b/routing/taskana-routing-rest/src/main/java/pro/taskana/routing/dmn/rest/DmnUploadController.java index 86ea3cf13b..1fc415d015 100644 --- a/routing/taskana-routing-rest/src/main/java/pro/taskana/routing/dmn/rest/DmnUploadController.java +++ b/routing/taskana-routing-rest/src/main/java/pro/taskana/routing/dmn/rest/DmnUploadController.java @@ -1,9 +1,16 @@ package pro.taskana.routing.dmn.rest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import java.io.IOException; import org.camunda.bpm.model.dmn.DmnModelInstance; import org.camunda.bpm.model.dmn.instance.Rule; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.hateoas.MediaTypes; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -33,7 +40,93 @@ public DmnUploadController(DmnConverterService dmnConverterService) { * file * @throws IOException if there is an I/O problem with the provided excel file */ - @PutMapping(RoutingRestEndpoints.URL_ROUTING_RULES_DEFAULT) + @Operation( + summary = + "This endpoint converts an excel file to a DMN table and saves it on the filesystem.", + requestBody = + @RequestBody( + description = + "the excel file containing the routing rules

To try the request, copy the " + + "following to an excel file and save it in .xlsx format with any name, then" + + " upload the file" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "
InputInputOutputOutput
BKNClassificationKeyworkbasketKeydomain
JUELjavascriptExpressionExpression
task.primaryObjRef.valuetask.classificationSummary.key + task.noteworkbasketKeydomain
FIRSTstringstringstringstring
16260203GPK_KSCDOMAIN_AVIP-Team
2matches(cellInput,\"11048|12012|12013|12513|12523|12619|12910
" + + "|12911|12912|12913|12914|12915|12916|12917|12918|12919|12920
" + + "|12921|12922|12923|12924|12925|12926|12927|12928|12929|12930
" + + "|12931|12932|12933|12934|13082|13093|17999|19012\")
GPK_KSCDOMAIN_ASecond-Level Team 1
312345678GPK_KSCDOMAIN_A
", + required = true, + content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE)), + responses = { + @ApiResponse( + responseCode = "200", + description = "the result of the upload", + content = { + @Content( + mediaType = MediaTypes.HAL_JSON_VALUE, + schema = @Schema(implementation = RoutingUploadResultRepresentationModel.class)) + }) + }) + @PutMapping( + path = RoutingRestEndpoints.URL_ROUTING_RULES_DEFAULT, + consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity convertAndUpload( @RequestParam("excelRoutingFile") MultipartFile excelRoutingFile) throws IOException, NotAuthorizedException { @@ -55,6 +148,18 @@ public ResponseEntity convertAndUpload( * * @return true, when the taskana-routing-rest is enabled, otherwise false */ + @Operation( + summary = "This endpoint checks if the taskana-routing-rest is in use.", + responses = { + @ApiResponse( + responseCode = "200", + description = "true, when the taskana-routing-rest is enabled, otherwise false", + content = { + @Content( + mediaType = MediaTypes.HAL_JSON_VALUE, + schema = @Schema(implementation = Boolean.class)) + }) + }) @GetMapping(path = RoutingRestEndpoints.ROUTING_REST_ENABLED) public ResponseEntity getIsRoutingRestEnabled() { return ResponseEntity.ok(true); diff --git a/routing/taskana-routing-rest/src/main/java/pro/taskana/routing/dmn/rest/RoutingUploadResultRepresentationModel.java b/routing/taskana-routing-rest/src/main/java/pro/taskana/routing/dmn/rest/RoutingUploadResultRepresentationModel.java index aa48419179..289ab84004 100644 --- a/routing/taskana-routing-rest/src/main/java/pro/taskana/routing/dmn/rest/RoutingUploadResultRepresentationModel.java +++ b/routing/taskana-routing-rest/src/main/java/pro/taskana/routing/dmn/rest/RoutingUploadResultRepresentationModel.java @@ -1,5 +1,6 @@ package pro.taskana.routing.dmn.rest; +import io.swagger.v3.oas.annotations.media.Schema; import org.springframework.hateoas.RepresentationModel; /** Model class for a routing upload result. */ @@ -7,9 +8,15 @@ public class RoutingUploadResultRepresentationModel extends RepresentationModel { /** The total amount of imported rows from the provided excel sheet. */ + @Schema( + name = "amountOfImportedRows", + description = "The total amount of imported rows from the provided excel sheet.") protected int amountOfImportedRows; /** A human readable String that contains the amount of imported rows. */ + @Schema( + name = "result", + description = "A human readable String that contains the amount of imported rows.") protected String result; public int getAmountOfImportedRows() {