diff --git a/DISCLAIMER b/DISCLAIMER-WIP similarity index 100% rename from DISCLAIMER rename to DISCLAIMER-WIP diff --git a/apps-integration-tests/integration-tests-data-index-service/integration-tests-data-index-service-common/src/test/java/org/kie/kogito/index/AbstractProcessDataIndexIT.java b/apps-integration-tests/integration-tests-data-index-service/integration-tests-data-index-service-common/src/test/java/org/kie/kogito/index/AbstractProcessDataIndexIT.java index e2fb7f7b72..0e12e6d410 100644 --- a/apps-integration-tests/integration-tests-data-index-service/integration-tests-data-index-service-common/src/test/java/org/kie/kogito/index/AbstractProcessDataIndexIT.java +++ b/apps-integration-tests/integration-tests-data-index-service/integration-tests-data-index-service-common/src/test/java/org/kie/kogito/index/AbstractProcessDataIndexIT.java @@ -30,7 +30,6 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicReference; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.testcontainers.shaded.com.fasterxml.jackson.databind.JsonNode; import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper; @@ -92,7 +91,6 @@ protected ValidatableResponse addCheck(ValidatableResponse res) { } @Test - @Disabled public void testProcessInstanceEvents() throws IOException { String pId = given() .contentType(ContentType.JSON) @@ -113,13 +111,13 @@ public void testProcessInstanceEvents() throws IOException { .contentType(ContentType.JSON) .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", pId) .when() - .get("/approvals/{processId}/tasks") + .get("/usertasks/instance") .then() .statusCode(200) + .log().body() .body("$.size()", is(1)) - .body("[0].name", is("firstLineApproval")) + .body("[0].taskName", is("firstLineApproval")) .body("[0].id", notNullValue()) .extract() .path("[0].id"); @@ -152,7 +150,7 @@ public void testProcessInstanceEvents() throws IOException { .body("data.Approvals[0].metadata.userTasks.size()", is(1)) .body("data.Approvals[0].metadata.userTasks[0].id", is(flTaskId)) .body("data.Approvals[0].metadata.userTasks[0].name", is("firstLineApproval")) - .body("data.Approvals[0].metadata.userTasks[0].state", is("Ready"))); + .body("data.Approvals[0].metadata.userTasks[0].state", is("Reserved"))); } await() @@ -187,7 +185,22 @@ public void testProcessInstanceEvents() throws IOException { .body("data.UserTaskInstances.size()", is(1)) .body("data.UserTaskInstances[0].id", is(flTaskId)) .body("data.UserTaskInstances[0].name", is("firstLineApproval")) - .body("data.UserTaskInstances[0].state", is("Ready"))); + .body("data.UserTaskInstances[0].state", is("Reserved"))); + + String workItemId = given() + .contentType(ContentType.JSON) + .queryParam("user", "admin") + .queryParam("group", "managers") + .pathParam("processId", pId) + .when() + .get("/approvals/{processId}/tasks") + .then() + .statusCode(200) + .body("$.size()", is(1)) + .body("[0].name", is("firstLineApproval")) + .body("[0].id", notNullValue()) + .extract() + .path("[0].id"); await() .atMost(TIMEOUT) @@ -196,7 +209,7 @@ public void testProcessInstanceEvents() throws IOException { .queryParam("user", "admin") .queryParam("group", "managers") .pathParam("processId", pId) - .pathParam("taskId", flTaskId) + .pathParam("taskId", workItemId) .body(singletonMap("approved", true)) .post("/approvals/{processId}/firstLineApproval/{taskId}") .then() @@ -324,6 +337,7 @@ public void testProcessGatewayAPI() throws IOException { .when().post("/graphql") .then() .statusCode(200) + .log().body() .body("data.UserTaskInstances[0].description", nullValue()) .body("data.UserTaskInstances[0].potentialGroups[0]", equalTo("managers")) .extract().path("data.UserTaskInstances[0].id"); @@ -335,6 +349,7 @@ public void testProcessGatewayAPI() throws IOException { .when().post("/graphql") .then() .statusCode(200) + .log().body() .body("errors", nullValue()) .extract().path("data.UserTaskInstances[0].schema"); checkExpectedTaskSchema(taskSchema); @@ -474,9 +489,8 @@ public void testProcessGatewayAPIComments(String taskId, String processInstanceI .when() .queryParam("user", "manager") .queryParam("group", "managers") - .pathParam("id", processInstanceId) .pathParam("taskId", taskId) - .get("/approvals/{id}/firstLineApproval/{taskId}/comments") + .get("/usertasks/instance/{taskId}/comments") .then() .statusCode(200) .body("size()", is(1)) @@ -585,9 +599,8 @@ public void testProcessGatewayAPIAttachments(String taskId, String processInstan .when() .queryParam("user", "manager") .queryParam("group", "managers") - .pathParam("id", processInstanceId) .pathParam("taskId", taskId) - .get("/approvals/{id}/firstLineApproval/{taskId}/attachments") + .get("/usertasks/instance/{taskId}/attachments") .then() .statusCode(200) .body("size()", is(1)) @@ -710,9 +723,8 @@ private void checkExpectedTaskSchema(String taskSchema) throws IOException { assertEquals("object", schemaJsonNode.at("/type").asText()); // Check Schema phases - assertEquals(4, schemaJsonNode.at("/phases").size()); + assertEquals(3, schemaJsonNode.at("/phases").size()); assertTrue(schemaJsonNode.get("phases").toString().contains("abort")); - assertTrue(schemaJsonNode.get("phases").toString().contains("claim")); assertTrue(schemaJsonNode.get("phases").toString().contains("skip")); assertTrue(schemaJsonNode.get("phases").toString().contains("complete")); diff --git a/apps-integration-tests/integration-tests-trusty-service/integration-tests-trusty-service-springboot/pom.xml b/apps-integration-tests/integration-tests-trusty-service/integration-tests-trusty-service-springboot/pom.xml index 1f01ab3b42..fd37954413 100644 --- a/apps-integration-tests/integration-tests-trusty-service/integration-tests-trusty-service-springboot/pom.xml +++ b/apps-integration-tests/integration-tests-trusty-service/integration-tests-trusty-service-springboot/pom.xml @@ -33,7 +33,7 @@ org.kie.kogito/integration-tests-trusty-service-springboot:${project.version} **/KogitoApplication.java - 2.8.0 + 3.3.1 eclipse-temurin:17-jre diff --git a/data-index/data-index-common/src/main/java/org/kie/kogito/index/api/KogitoRuntimeCommonClient.java b/data-index/data-index-common/src/main/java/org/kie/kogito/index/api/KogitoRuntimeCommonClient.java index 7e86049fc3..1df466aa4d 100644 --- a/data-index/data-index-common/src/main/java/org/kie/kogito/index/api/KogitoRuntimeCommonClient.java +++ b/data-index/data-index-common/src/main/java/org/kie/kogito/index/api/KogitoRuntimeCommonClient.java @@ -7,7 +7,7 @@ * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an @@ -118,8 +118,11 @@ public CompletableFuture sendDeleteClientRequest(WebClient webClient, String req protected void asyncHttpResponseTreatment(AsyncResult> res, CompletableFuture future, String logMessage) { if (res.succeeded() && (res.result().statusCode() == 200 || res.result().statusCode() == 201)) { - future.complete(res.result().bodyAsString() != null ? res.result().bodyAsString() : "Successfully performed: " + logMessage); + String jsonMessage = res.result().bodyAsString(); + LOGGER.trace("Result {}", jsonMessage); + future.complete(jsonMessage != null ? jsonMessage : "Successfully performed: " + logMessage); } else { + LOGGER.trace("Error {}", logMessage); future.completeExceptionally(new DataIndexServiceException(getErrorMessage(logMessage, res.result()))); } } diff --git a/data-index/data-index-common/src/main/java/org/kie/kogito/index/service/IndexingService.java b/data-index/data-index-common/src/main/java/org/kie/kogito/index/service/IndexingService.java index f3cd153d5b..5fbf4d6536 100644 --- a/data-index/data-index-common/src/main/java/org/kie/kogito/index/service/IndexingService.java +++ b/data-index/data-index-common/src/main/java/org/kie/kogito/index/service/IndexingService.java @@ -79,15 +79,8 @@ public class IndexingService { public void indexProcessInstanceEvent(ProcessInstanceDataEvent event) { ProcessInstanceStorage storage = manager.getProcessInstanceStorage(); if (event instanceof MultipleProcessInstanceDataEvent) { - for (ProcessInstanceDataEvent item : ((MultipleProcessInstanceDataEvent) event).getData()) - indexProccessInstanceEvent(storage, item); - } else { - indexProccessInstanceEvent(storage, event); - } - } - - private void indexProccessInstanceEvent(ProcessInstanceStorage storage, ProcessInstanceDataEvent event) { - if (event instanceof ProcessInstanceErrorDataEvent) { + storage.indexGroup(((MultipleProcessInstanceDataEvent) event)); + } else if (event instanceof ProcessInstanceErrorDataEvent) { storage.indexError((ProcessInstanceErrorDataEvent) event); } else if (event instanceof ProcessInstanceNodeDataEvent) { storage.indexNode((ProcessInstanceNodeDataEvent) event); @@ -112,16 +105,8 @@ public void indexProcessDefinition(ProcessDefinitionDataEvent definitionDataEven public void indexUserTaskInstanceEvent(UserTaskInstanceDataEvent event) { UserTaskInstanceStorage storage = manager.getUserTaskInstanceStorage(); if (event instanceof MultipleUserTaskInstanceDataEvent) { - for (UserTaskInstanceDataEvent item : ((MultipleUserTaskInstanceDataEvent) event).getData()) { - indexUserTaskInstanceEvent(storage, item); - } - } else { - indexUserTaskInstanceEvent(storage, event); - } - } - - private void indexUserTaskInstanceEvent(UserTaskInstanceStorage storage, UserTaskInstanceDataEvent event) { - if (event instanceof UserTaskInstanceAssignmentDataEvent) { + storage.indexGroup((MultipleUserTaskInstanceDataEvent) event); + } else if (event instanceof UserTaskInstanceAssignmentDataEvent) { storage.indexAssignment((UserTaskInstanceAssignmentDataEvent) event); } else if (event instanceof UserTaskInstanceAttachmentDataEvent) { storage.indexAttachment((UserTaskInstanceAttachmentDataEvent) event); diff --git a/data-index/data-index-graphql/src/main/resources/basic.schema.graphqls b/data-index/data-index-graphql/src/main/resources/basic.schema.graphqls index 4e6f30b764..741c0d4e89 100644 --- a/data-index/data-index-graphql/src/main/resources/basic.schema.graphqls +++ b/data-index/data-index-graphql/src/main/resources/basic.schema.graphqls @@ -367,6 +367,7 @@ type UserTaskInstance { endpoint: String comments: [Comment!] attachments: [Attachment!] + externalReferenceId : String } input UserTaskInstanceArgument { diff --git a/data-index/data-index-service/data-index-service-common/src/main/java/org/kie/kogito/index/service/api/KogitoRuntimeClientImpl.java b/data-index/data-index-service/data-index-service-common/src/main/java/org/kie/kogito/index/service/api/KogitoRuntimeClientImpl.java index beabe661a9..badbe77b24 100644 --- a/data-index/data-index-service/data-index-service-common/src/main/java/org/kie/kogito/index/service/api/KogitoRuntimeClientImpl.java +++ b/data-index/data-index-service/data-index-service-common/src/main/java/org/kie/kogito/index/service/api/KogitoRuntimeClientImpl.java @@ -28,11 +28,13 @@ import org.kie.kogito.index.model.ProcessInstance; import org.kie.kogito.index.model.UserTaskInstance; import org.kie.kogito.index.service.DataIndexServiceException; +import org.kie.kogito.usertask.model.CommentInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.vertx.core.AsyncResult; import io.vertx.core.buffer.Buffer; +import io.vertx.core.json.Json; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpRequest; import io.vertx.ext.web.client.HttpResponse; @@ -58,15 +60,15 @@ class KogitoRuntimeClientImpl extends KogitoRuntimeCommonClient implements Kogit public static final String CANCEL_NODE_INSTANCE_PATH = "/management/processes/%s/instances/%s/nodeInstances/%s"; // nodeInstance Id public static final String GET_TASK_SCHEMA_PATH = "/%s/%s/%s/%s/schema"; - public static final String UPDATE_USER_TASK_INSTANCE_PATH = "/management/processes/%s/instances/%s/tasks/%s"; + public static final String UPDATE_USER_TASK_INSTANCE_PATH = "/management/usertasks/%s"; - public static final String CREATE_USER_TASK_INSTANCE_COMMENT_PATH = "/%s/%s/%s/%s/comments"; - public static final String UPDATE_USER_TASK_INSTANCE_COMMENT_PATH = "/%s/%s/%s/%s/comments/%s"; - public static final String DELETE_USER_TASK_INSTANCE_COMMENT_PATH = "/%s/%s/%s/%s/comments/%s"; + public static final String CREATE_USER_TASK_INSTANCE_COMMENT_PATH = "/usertasks/instance/%s/comments"; + public static final String UPDATE_USER_TASK_INSTANCE_COMMENT_PATH = "/usertasks/instance/%s/comments/%s"; + public static final String DELETE_USER_TASK_INSTANCE_COMMENT_PATH = "/usertasks/instance/%s/comments/%s"; - public static final String CREATE_USER_TASK_INSTANCE_ATTACHMENT_PATH = "/%s/%s/%s/%s/attachments"; - public static final String UPDATE_USER_TASK_INSTANCE_ATTACHMENT_PATH = "/%s/%s/%s/%s/attachments/%s"; - public static final String DELETE_USER_TASK_INSTANCE_ATTACHMENT_PATH = "/%s/%s/%s/%s/attachments/%s"; + public static final String CREATE_USER_TASK_INSTANCE_ATTACHMENT_PATH = "/usertasks/instance/%s/attachments"; + public static final String UPDATE_USER_TASK_INSTANCE_ATTACHMENT_PATH = "/usertasks/instance/%s/attachments/%s"; + public static final String DELETE_USER_TASK_INSTANCE_ATTACHMENT_PATH = "/usertasks/instance/%s/attachments/%s"; private static final Logger LOGGER = LoggerFactory.getLogger(KogitoRuntimeClientImpl.class); @@ -137,7 +139,8 @@ public CompletableFuture cancelNodeInstance(String serviceURL, ProcessIn @Override public CompletableFuture getUserTaskSchema(String serviceURL, UserTaskInstance userTaskInstance, String user, List groups) { String requestURI = format(GET_TASK_SCHEMA_PATH, userTaskInstance.getProcessId(), userTaskInstance.getProcessInstanceId(), - userTaskInstance.getName(), userTaskInstance.getId()) + "?" + getUserGroupsURIParameter(user, groups); + userTaskInstance.getName(), userTaskInstance.getExternalReferenceId()) + "?" + getUserGroupsURIParameter(user, groups); + LOGGER.trace("get user task schema service {} and URI {}", serviceURL, requestURI); return sendGetClientRequest(getWebClient(serviceURL), requestURI, "Get User Task schema for task:" + userTaskInstance.getName() + " with id: " + userTaskInstance.getId(), null); @@ -146,44 +149,40 @@ public CompletableFuture getUserTaskSchema(String serviceURL, UserTaskIn @Override public CompletableFuture updateUserTaskInstance(String serviceURL, UserTaskInstance userTaskInstance, String user, List groups, Map taskInfo) { - String requestURI = format(UPDATE_USER_TASK_INSTANCE_PATH, userTaskInstance.getProcessId(), userTaskInstance.getProcessInstanceId(), - userTaskInstance.getId()) + "?" + getUserGroupsURIParameter(user, groups); + String requestURI = format(UPDATE_USER_TASK_INSTANCE_PATH, userTaskInstance.getId()) + "?" + getUserGroupsURIParameter(user, groups); + LOGGER.trace("Send request to {}", requestURI); return sendPatchClientRequest(getWebClient(serviceURL), requestURI, - "Update user task instance:" + userTaskInstance.getName() + " with id: " + userTaskInstance.getId(), + "Update user task instance: " + userTaskInstance.getName() + " with id: " + userTaskInstance.getId(), new JsonObject(taskInfo)); } @Override public CompletableFuture createUserTaskInstanceComment(String serviceURL, UserTaskInstance userTaskInstance, String user, List groups, String commentInfo) { - String requestURI = format(CREATE_USER_TASK_INSTANCE_COMMENT_PATH, userTaskInstance.getProcessId(), userTaskInstance.getProcessInstanceId(), userTaskInstance.getName(), - userTaskInstance.getId()) + "?" + getUserGroupsURIParameter(user, groups); + String requestURI = format(CREATE_USER_TASK_INSTANCE_COMMENT_PATH, userTaskInstance.getId()) + "?" + getUserGroupsURIParameter(user, groups); return sendPostWithBodyClientRequest(getWebClient(serviceURL), requestURI, "Adding comment to UserTask:" + userTaskInstance.getName() + " with id: " + userTaskInstance.getId(), - commentInfo, MediaType.TEXT_PLAIN); + Json.encode(new CommentInfo(commentInfo)), MediaType.APPLICATION_JSON); } @Override public CompletableFuture updateUserTaskInstanceComment(String serviceURL, UserTaskInstance userTaskInstance, String user, List groups, String commentId, String commentInfo) { - String requestURI = format(UPDATE_USER_TASK_INSTANCE_COMMENT_PATH, userTaskInstance.getProcessId(), userTaskInstance.getProcessInstanceId(), userTaskInstance.getName(), - userTaskInstance.getId(), commentId) + "?" + getUserGroupsURIParameter(user, groups); + String requestURI = format(UPDATE_USER_TASK_INSTANCE_COMMENT_PATH, userTaskInstance.getId(), commentId) + "?" + getUserGroupsURIParameter(user, groups); return sendPutClientRequest(getWebClient(serviceURL), requestURI, "Update UserTask: " + userTaskInstance.getName() + " comment:" + commentId + " with taskid: " + userTaskInstance.getId(), - commentInfo, MediaType.TEXT_PLAIN); + Json.encode(new CommentInfo(commentInfo)), MediaType.APPLICATION_JSON); } @Override public CompletableFuture deleteUserTaskInstanceComment(String serviceURL, UserTaskInstance userTaskInstance, String user, List groups, String commentId) { - String requestURI = format(DELETE_USER_TASK_INSTANCE_COMMENT_PATH, userTaskInstance.getProcessId(), userTaskInstance.getProcessInstanceId(), - userTaskInstance.getName(), userTaskInstance.getId(), commentId) + "?" + getUserGroupsURIParameter(user, groups); + String requestURI = format(DELETE_USER_TASK_INSTANCE_COMMENT_PATH, userTaskInstance.getId(), commentId) + "?" + getUserGroupsURIParameter(user, groups); return sendDeleteClientRequest(getWebClient(serviceURL), requestURI, "Delete comment : " + commentId + "of Task: " + userTaskInstance.getName() + " with taskid: " + userTaskInstance.getId()); } @Override public CompletableFuture createUserTaskInstanceAttachment(String serviceURL, UserTaskInstance userTaskInstance, String user, List groups, String name, String uri) { - String requestURI = format(CREATE_USER_TASK_INSTANCE_ATTACHMENT_PATH, userTaskInstance.getProcessId(), userTaskInstance.getProcessInstanceId(), userTaskInstance.getName(), - userTaskInstance.getId()) + "?" + getUserGroupsURIParameter(user, groups); + String requestURI = format(CREATE_USER_TASK_INSTANCE_ATTACHMENT_PATH, userTaskInstance.getId()) + "?" + getUserGroupsURIParameter(user, groups); return sendPostWithBodyClientRequest(getWebClient(serviceURL), requestURI, "Adding attachment to UserTask:" + userTaskInstance.getName() + " with id: " + userTaskInstance.getId(), "{ \"name\": \"" + name + "\", \"uri\": \"" + uri + "\" }", MediaType.APPLICATION_JSON); @@ -192,8 +191,7 @@ public CompletableFuture createUserTaskInstanceAttachment(String service @Override public CompletableFuture updateUserTaskInstanceAttachment(String serviceURL, UserTaskInstance userTaskInstance, String user, List groups, String attachmentId, String name, String uri) { - String requestURI = format(UPDATE_USER_TASK_INSTANCE_ATTACHMENT_PATH, userTaskInstance.getProcessId(), userTaskInstance.getProcessInstanceId(), userTaskInstance.getName(), - userTaskInstance.getId(), attachmentId) + "?" + getUserGroupsURIParameter(user, groups); + String requestURI = format(UPDATE_USER_TASK_INSTANCE_ATTACHMENT_PATH, userTaskInstance.getId(), attachmentId) + "?" + getUserGroupsURIParameter(user, groups); return sendJSONPutClientRequest(getWebClient(serviceURL), requestURI, "Update UserTask: " + userTaskInstance.getName() + " attachment:" + attachmentId + @@ -203,8 +201,7 @@ public CompletableFuture updateUserTaskInstanceAttachment(String service @Override public CompletableFuture deleteUserTaskInstanceAttachment(String serviceURL, UserTaskInstance userTaskInstance, String user, List groups, String attachmentId) { - String requestURI = format(DELETE_USER_TASK_INSTANCE_ATTACHMENT_PATH, userTaskInstance.getProcessId(), userTaskInstance.getProcessInstanceId(), - userTaskInstance.getName(), userTaskInstance.getId(), attachmentId) + "?" + getUserGroupsURIParameter(user, groups); + String requestURI = format(DELETE_USER_TASK_INSTANCE_ATTACHMENT_PATH, userTaskInstance.getId(), attachmentId) + "?" + getUserGroupsURIParameter(user, groups); return sendDeleteClientRequest(getWebClient(serviceURL), requestURI, "Delete attachment : " + attachmentId + "of Task: " + userTaskInstance.getName() + " with taskid: " + userTaskInstance.getId()); } @@ -225,10 +222,10 @@ protected CompletableFuture sendPostWithBodyClientRequest(WebClient webClient, S .putHeader("Authorization", getAuthHeader()) .putHeader("Content-Type", contentType); if (MediaType.APPLICATION_JSON.equals(contentType)) { - LOGGER.debug("Sending Json Body: {} POST to URI {}", body, requestURI); + LOGGER.trace("Sending Json Body: {} POST to URI {}", body, requestURI); request.sendJson(new JsonObject(body), res -> asyncHttpResponseTreatment(res, future, logMessage)); } else { - LOGGER.debug("Sending Buffer(Body): {} POST to URI {}", body, requestURI); + LOGGER.trace("Sending Buffer(Body): {} POST to URI {}", body, requestURI); request.sendBuffer(Buffer.buffer(body), res -> asyncHttpResponseTreatment(res, future, logMessage)); } return future; @@ -253,10 +250,10 @@ protected CompletableFuture sendPutClientRequest(WebClient webClient, String req .putHeader("Authorization", getAuthHeader()) .putHeader("Content-Type", contentType); if (MediaType.APPLICATION_JSON.equals(contentType)) { - LOGGER.debug("Sending Json Body: {} PUT to URI {}", body, requestURI); + LOGGER.info("Sending Json Body: {} PUT to URI {}", body, requestURI); request.sendJson(new JsonObject(body), res -> asyncHttpResponseTreatment(res, future, logMessage)); } else { - LOGGER.debug("Sending Buffer(Body): {} PUT to URI {}", body, requestURI); + LOGGER.info("Sending Buffer(Body): {} PUT to URI {}", body, requestURI); request.sendBuffer(Buffer.buffer(body), res -> asyncHttpResponseTreatment(res, future, logMessage)); } return future; diff --git a/data-index/data-index-service/data-index-service-common/src/test/java/org/kie/kogito/index/service/AbstractIndexingServiceIT.java b/data-index/data-index-service/data-index-service-common/src/test/java/org/kie/kogito/index/service/AbstractIndexingServiceIT.java index 9843238378..dd24ab69e9 100644 --- a/data-index/data-index-service/data-index-service-common/src/test/java/org/kie/kogito/index/service/AbstractIndexingServiceIT.java +++ b/data-index/data-index-service/data-index-service-common/src/test/java/org/kie/kogito/index/service/AbstractIndexingServiceIT.java @@ -477,7 +477,7 @@ protected void validateUserTaskInstance(String query, UserTaskInstanceStateDataE .body("data.UserTaskInstances[0].lastUpdate", anything()) .body("data.UserTaskInstances[0].endpoint", is(event.getSource().toString() + "/" + event.getData().getProcessInstanceId() + "/" + event.getData().getUserTaskName() + "/" - + event.getData().getUserTaskInstanceId()))); + + event.getData().getExternalReferenceId()))); } } diff --git a/data-index/data-index-service/data-index-service-common/src/test/java/org/kie/kogito/index/service/api/KogitoRuntimeClientTest.java b/data-index/data-index-service/data-index-service-common/src/test/java/org/kie/kogito/index/service/api/KogitoRuntimeClientTest.java index 62fbd15702..28acb2caf3 100644 --- a/data-index/data-index-service/data-index-service-common/src/test/java/org/kie/kogito/index/service/api/KogitoRuntimeClientTest.java +++ b/data-index/data-index-service/data-index-service-common/src/test/java/org/kie/kogito/index/service/api/KogitoRuntimeClientTest.java @@ -35,6 +35,7 @@ import org.kie.kogito.index.model.UserTaskInstance; import org.kie.kogito.index.service.DataIndexServiceException; import org.kie.kogito.index.test.TestUtils; +import org.kie.kogito.usertask.model.CommentInfo; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -44,6 +45,7 @@ import io.vertx.core.AsyncResult; import io.vertx.core.Handler; import io.vertx.core.Vertx; +import io.vertx.core.json.Json; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.client.HttpRequest; import io.vertx.ext.web.client.HttpResponse; @@ -58,12 +60,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.kie.kogito.index.service.api.KogitoRuntimeClientImpl.ABORT_PROCESS_INSTANCE_PATH; -import static org.kie.kogito.index.service.api.KogitoRuntimeClientImpl.CANCEL_JOB_PATH; import static org.kie.kogito.index.service.api.KogitoRuntimeClientImpl.CANCEL_NODE_INSTANCE_PATH; import static org.kie.kogito.index.service.api.KogitoRuntimeClientImpl.GET_PROCESS_INSTANCE_DIAGRAM_PATH; import static org.kie.kogito.index.service.api.KogitoRuntimeClientImpl.GET_PROCESS_INSTANCE_NODE_DEFINITIONS_PATH; import static org.kie.kogito.index.service.api.KogitoRuntimeClientImpl.GET_PROCESS_INSTANCE_SOURCE_PATH; -import static org.kie.kogito.index.service.api.KogitoRuntimeClientImpl.RESCHEDULE_JOB_PATH; import static org.kie.kogito.index.service.api.KogitoRuntimeClientImpl.RETRIGGER_NODE_INSTANCE_PATH; import static org.kie.kogito.index.service.api.KogitoRuntimeClientImpl.RETRY_PROCESS_INSTANCE_PATH; import static org.kie.kogito.index.service.api.KogitoRuntimeClientImpl.SKIP_PROCESS_INSTANCE_PATH; @@ -72,6 +72,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -401,8 +402,9 @@ public void testGetUserTaskSchema() { UserTaskInstance taskInstance = createUserTaskInstance(PROCESS_INSTANCE_ID, TASK_ID, "InProgress"); client.getUserTaskSchema(SERVICE_URL, taskInstance, "jdoe", Collections.singletonList("managers")); - verify(client).sendGetClientRequest(webClientMock, "/travels/" + PROCESS_INSTANCE_ID + "/TaskName/" + TASK_ID + "/schema?user=jdoe&group=managers", - "Get User Task schema for task:TaskName with id: " + taskInstance.getId(), null); + verify(client).sendGetClientRequest(eq(webClientMock), + eq("/travels/" + PROCESS_INSTANCE_ID + "/TaskName/" + taskInstance.getExternalReferenceId() + "/schema?user=jdoe&group=managers"), + eq("Get User Task schema for task:TaskName with id: " + taskInstance.getId()), isNull()); ArgumentCaptor handlerCaptor = ArgumentCaptor.forClass(Handler.class); verify(httpRequestMock).send(handlerCaptor.capture()); verify(httpRequestMock).putHeader(eq("Authorization"), eq("Bearer " + AUTHORIZED_TOKEN)); @@ -421,8 +423,8 @@ public void testUpdateUserTaskInstance() { client.updateUserTaskInstance(SERVICE_URL, taskInstance, "jdoe", Collections.singletonList("managers"), taskInfo); ArgumentCaptor jsonCaptor = ArgumentCaptor.forClass(JsonObject.class); verify(client).sendPatchClientRequest(eq(webClientMock), - eq("/management/processes/travels/instances/" + PROCESS_INSTANCE_ID + "/tasks/" + TASK_ID + "?user=jdoe&group=managers"), - eq("Update user task instance:" + taskInstance.getName() + " with id: " + taskInstance.getId()), + eq("/management/usertasks/" + TASK_ID + "?user=jdoe&group=managers"), + eq("Update user task instance: " + taskInstance.getName() + " with id: " + taskInstance.getId()), jsonCaptor.capture()); assertThat(jsonCaptor.getValue().getString("description")).isEqualTo("NewDescription"); ArgumentCaptor handlerCaptor = ArgumentCaptor.forClass(Handler.class); @@ -443,10 +445,11 @@ public void testCreateUserTaskInstanceComment() { client.createUserTaskInstanceComment(SERVICE_URL, taskInstance, "jdoe", Collections.singletonList("managers"), commentInfo); verify(client).sendPostWithBodyClientRequest(eq(webClientMock), - eq("/travels/" + PROCESS_INSTANCE_ID + "/" + taskInstance.getName() + "/" + TASK_ID + "/comments?user=jdoe&group=managers"), - eq("Adding comment to UserTask:" + taskInstance.getName() + " with id: " + taskInstance.getId()), eq(commentInfo), eq("text/plain")); + eq("/usertasks/instance/" + TASK_ID + "/comments?user=jdoe&group=managers"), + eq("Adding comment to UserTask:" + taskInstance.getName() + " with id: " + taskInstance.getId()), + eq(Json.encode(new CommentInfo(commentInfo))), eq("application/json")); ArgumentCaptor handlerCaptor = ArgumentCaptor.forClass(Handler.class); - verify(httpRequestMock).sendBuffer(any(), handlerCaptor.capture()); + verify(httpRequestMock).sendJson(any(), handlerCaptor.capture()); checkResponseHandling(handlerCaptor.getValue()); } @@ -462,7 +465,7 @@ public void testCreateUserTaskInstanceAttachment() { client.createUserTaskInstanceAttachment(SERVICE_URL, taskInstance, "jdoe", Collections.singletonList("managers"), attachmentName, attachmentUri); verify(client).sendPostWithBodyClientRequest(eq(webClientMock), - eq("/travels/" + PROCESS_INSTANCE_ID + "/" + taskInstance.getName() + "/" + TASK_ID + "/attachments?user=jdoe&group=managers"), + eq("/usertasks/instance/" + TASK_ID + "/attachments?user=jdoe&group=managers"), eq("Adding attachment to UserTask:" + taskInstance.getName() + " with id: " + taskInstance.getId()), eq("{ \"name\": \"" + attachmentName + "\", \"uri\": \"" + attachmentUri + "\" }"), eq("application/json")); ArgumentCaptor handlerCaptor = ArgumentCaptor.forClass(Handler.class); @@ -484,12 +487,12 @@ public void testUpdateUserTaskInstanceComment() { client.updateUserTaskInstanceComment(SERVICE_URL, taskInstance, "jdoe", Collections.singletonList("managers"), commentId, commentInfo); verify(client).sendPutClientRequest(eq(webClientMock), - eq("/travels/" + PROCESS_INSTANCE_ID + "/" + taskInstance.getName() + "/" + TASK_ID + "/comments/" + commentId + "?user=jdoe&group=managers"), + eq("/usertasks/instance/" + TASK_ID + "/comments/" + commentId + "?user=jdoe&group=managers"), eq("Update UserTask: " + taskInstance.getName() + " comment:" + commentId + " with taskid: " + taskInstance.getId()), - eq(commentInfo), eq("text/plain")); + eq(Json.encode(new CommentInfo(commentInfo))), eq("application/json")); ArgumentCaptor handlerCaptor = ArgumentCaptor.forClass(Handler.class); - verify(httpRequestMock).sendBuffer(any(), handlerCaptor.capture()); + verify(httpRequestMock).sendJson(any(), handlerCaptor.capture()); checkResponseHandling(handlerCaptor.getValue()); } @@ -503,7 +506,7 @@ public void testDeleteTaskInstanceComment() { client.deleteUserTaskInstanceComment(SERVICE_URL, taskInstance, "jdoe", Collections.singletonList("managers"), commentId); verify(client).sendDeleteClientRequest(eq(webClientMock), - eq("/travels/" + PROCESS_INSTANCE_ID + "/" + taskInstance.getName() + "/" + TASK_ID + "/comments/" + commentId + "?user=jdoe&group=managers"), + eq("/usertasks/instance/" + TASK_ID + "/comments/" + commentId + "?user=jdoe&group=managers"), eq("Delete comment : " + commentId + "of Task: " + taskInstance.getName() + " with taskid: " + taskInstance.getId())); ArgumentCaptor handlerCaptor = ArgumentCaptor.forClass(Handler.class); verify(httpRequestMock).send(handlerCaptor.capture()); @@ -524,7 +527,7 @@ public void testUpdateUserTaskInstanceAttachment() { client.updateUserTaskInstanceAttachment(SERVICE_URL, taskInstance, "jdoe", Collections.singletonList("managers"), attachmentId, attachmentName, attachmentContent); verify(client).sendJSONPutClientRequest(eq(webClientMock), - eq("/travels/" + PROCESS_INSTANCE_ID + "/" + taskInstance.getName() + "/" + TASK_ID + "/attachments/" + attachmentId + "?user=jdoe&group=managers"), + eq("/usertasks/instance/" + TASK_ID + "/attachments/" + attachmentId + "?user=jdoe&group=managers"), eq("Update UserTask: " + taskInstance.getName() + " attachment:" + attachmentId + " with taskid: " + taskInstance.getId() + "with: " + attachmentName + " and info:" + attachmentContent), @@ -546,7 +549,7 @@ public void testDeleteTaskInstanceAttachment() { client.deleteUserTaskInstanceAttachment(SERVICE_URL, taskInstance, "jdoe", Collections.singletonList("managers"), attachmentId); verify(client).sendDeleteClientRequest(eq(webClientMock), - eq("/travels/" + PROCESS_INSTANCE_ID + "/" + taskInstance.getName() + "/" + TASK_ID + "/attachments/" + attachmentId + "?user=jdoe&group=managers"), + eq("/usertasks/instance/" + TASK_ID + "/attachments/" + attachmentId + "?user=jdoe&group=managers"), eq("Delete attachment : " + attachmentId + "of Task: " + taskInstance.getName() + " with taskid: " + taskInstance.getId())); ArgumentCaptor handlerCaptor = ArgumentCaptor.forClass(Handler.class); verify(httpRequestMock).send(handlerCaptor.capture()); diff --git a/data-index/data-index-service/data-index-service-inmemory/src/main/resources/application.properties b/data-index/data-index-service/data-index-service-inmemory/src/main/resources/application.properties index e6eb7e8ccd..8f28492088 100644 --- a/data-index/data-index-service/data-index-service-inmemory/src/main/resources/application.properties +++ b/data-index/data-index-service/data-index-service-inmemory/src/main/resources/application.properties @@ -34,6 +34,7 @@ quarkus.flyway.locations=classpath:kie-flyway/db/data-index/postgresql quarkus.hibernate-orm.jdbc.timezone=UTC quarkus.hibernate-orm.database.generation=update quarkus.hibernate-orm.database.generation.halt-on-error=true +quarkus.hibernate-orm.physical-naming-strategy=org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy #Container image quarkus.container-image.build=${quarkus.build.image:true} diff --git a/data-index/data-index-storage/data-index-storage-api/src/main/java/org/kie/kogito/index/model/UserTaskInstance.java b/data-index/data-index-storage/data-index-storage-api/src/main/java/org/kie/kogito/index/model/UserTaskInstance.java index 8572edcaf8..b87d73777f 100644 --- a/data-index/data-index-storage/data-index-storage-api/src/main/java/org/kie/kogito/index/model/UserTaskInstance.java +++ b/data-index/data-index-storage/data-index-storage-api/src/main/java/org/kie/kogito/index/model/UserTaskInstance.java @@ -28,6 +28,7 @@ public class UserTaskInstance extends UserTaskInstanceMeta { private ObjectNode inputs; private ObjectNode outputs; private String endpoint; + private String externalReferenceId; public String getProcessId() { return processId; @@ -94,4 +95,12 @@ public ObjectNode getOutputs() { public void setOutputs(ObjectNode outputs) { this.outputs = outputs; } + + public String getExternalReferenceId() { + return externalReferenceId; + } + + public void setExternalReferenceId(String externalReferenceId) { + this.externalReferenceId = externalReferenceId; + } } diff --git a/data-index/data-index-storage/data-index-storage-api/src/main/java/org/kie/kogito/index/storage/ProcessInstanceStorage.java b/data-index/data-index-storage/data-index-storage-api/src/main/java/org/kie/kogito/index/storage/ProcessInstanceStorage.java index 753caf66bc..674cc17bd4 100644 --- a/data-index/data-index-storage/data-index-storage-api/src/main/java/org/kie/kogito/index/storage/ProcessInstanceStorage.java +++ b/data-index/data-index-storage/data-index-storage-api/src/main/java/org/kie/kogito/index/storage/ProcessInstanceStorage.java @@ -18,6 +18,7 @@ */ package org.kie.kogito.index.storage; +import org.kie.kogito.event.process.MultipleProcessInstanceDataEvent; import org.kie.kogito.event.process.ProcessInstanceErrorDataEvent; import org.kie.kogito.event.process.ProcessInstanceNodeDataEvent; import org.kie.kogito.event.process.ProcessInstanceSLADataEvent; @@ -28,6 +29,8 @@ public interface ProcessInstanceStorage extends StorageFetcher { + void indexGroup(MultipleProcessInstanceDataEvent event); + void indexError(ProcessInstanceErrorDataEvent event); void indexNode(ProcessInstanceNodeDataEvent event); diff --git a/data-index/data-index-storage/data-index-storage-api/src/main/java/org/kie/kogito/index/storage/UserTaskInstanceStorage.java b/data-index/data-index-storage/data-index-storage-api/src/main/java/org/kie/kogito/index/storage/UserTaskInstanceStorage.java index f315ae4f73..58c586fd62 100644 --- a/data-index/data-index-storage/data-index-storage-api/src/main/java/org/kie/kogito/index/storage/UserTaskInstanceStorage.java +++ b/data-index/data-index-storage/data-index-storage-api/src/main/java/org/kie/kogito/index/storage/UserTaskInstanceStorage.java @@ -18,6 +18,7 @@ */ package org.kie.kogito.index.storage; +import org.kie.kogito.event.usertask.MultipleUserTaskInstanceDataEvent; import org.kie.kogito.event.usertask.UserTaskInstanceAssignmentDataEvent; import org.kie.kogito.event.usertask.UserTaskInstanceAttachmentDataEvent; import org.kie.kogito.event.usertask.UserTaskInstanceCommentDataEvent; @@ -40,4 +41,6 @@ public interface UserTaskInstanceStorage extends StorageFetcher event : events.getData()) { + if (event instanceof ProcessInstanceErrorDataEvent) { + index(event, errorMerger); + } else if (event instanceof ProcessInstanceNodeDataEvent) { + index(event, nodeMerger); + } else if (event instanceof ProcessInstanceSLADataEvent) { + index(event, slaMerger); + } else if (event instanceof ProcessInstanceStateDataEvent) { + index(event, stateMerger); + } else if (event instanceof ProcessInstanceVariableDataEvent) { + index(event, variableMerger); + } + } + } + private > void index(T event, ProcessInstanceEventMerger merger) { ProcessInstance processInstance = storage.get(event.getKogitoProcessInstanceId()); if (processInstance == null) { diff --git a/data-index/data-index-storage/data-index-storage-common/src/main/java/org/kie/kogito/index/storage/ModelUserTaskInstanceStorage.java b/data-index/data-index-storage/data-index-storage-common/src/main/java/org/kie/kogito/index/storage/ModelUserTaskInstanceStorage.java index 20f98a396c..e76104c9e5 100644 --- a/data-index/data-index-storage/data-index-storage-common/src/main/java/org/kie/kogito/index/storage/ModelUserTaskInstanceStorage.java +++ b/data-index/data-index-storage/data-index-storage-common/src/main/java/org/kie/kogito/index/storage/ModelUserTaskInstanceStorage.java @@ -20,6 +20,7 @@ import java.util.ArrayList; +import org.kie.kogito.event.usertask.MultipleUserTaskInstanceDataEvent; import org.kie.kogito.event.usertask.UserTaskInstanceAssignmentDataEvent; import org.kie.kogito.event.usertask.UserTaskInstanceAttachmentDataEvent; import org.kie.kogito.event.usertask.UserTaskInstanceCommentDataEvent; @@ -53,7 +54,6 @@ public ModelUserTaskInstanceStorage(Storage storage) { @Override public void indexAssignment(UserTaskInstanceAssignmentDataEvent event) { index(event, assignmentMerger); - } @Override @@ -76,13 +76,30 @@ public void indexState(UserTaskInstanceStateDataEvent event) { @Override public void indexVariable(UserTaskInstanceVariableDataEvent event) { index(event, variableMerger); - } @Override public void indexComment(UserTaskInstanceCommentDataEvent event) { index(event, commentMerger); + } + @Override + public void indexGroup(MultipleUserTaskInstanceDataEvent events) { + for (UserTaskInstanceDataEvent event : events.getData()) { + if (event instanceof UserTaskInstanceAssignmentDataEvent) { + index((UserTaskInstanceAssignmentDataEvent) event, assignmentMerger); + } else if (event instanceof UserTaskInstanceAttachmentDataEvent) { + index((UserTaskInstanceAttachmentDataEvent) event, attachmentMerger); + } else if (event instanceof UserTaskInstanceDeadlineDataEvent) { + index((UserTaskInstanceDeadlineDataEvent) event, deadlineMerger); + } else if (event instanceof UserTaskInstanceStateDataEvent) { + index((UserTaskInstanceStateDataEvent) event, stateMerger); + } else if (event instanceof UserTaskInstanceCommentDataEvent) { + index((UserTaskInstanceCommentDataEvent) event, commentMerger); + } else if (event instanceof UserTaskInstanceVariableDataEvent) { + index((UserTaskInstanceVariableDataEvent) event, variableMerger); + } + } } private > void index(T event, UserTaskInstanceEventMerger merger) { diff --git a/data-index/data-index-storage/data-index-storage-common/src/main/java/org/kie/kogito/index/storage/merger/UserTaskInstanceStateEventMerger.java b/data-index/data-index-storage/data-index-storage-common/src/main/java/org/kie/kogito/index/storage/merger/UserTaskInstanceStateEventMerger.java index 8cf9d9fd75..8e813f07e5 100644 --- a/data-index/data-index-storage/data-index-storage-common/src/main/java/org/kie/kogito/index/storage/merger/UserTaskInstanceStateEventMerger.java +++ b/data-index/data-index-storage/data-index-storage-common/src/main/java/org/kie/kogito/index/storage/merger/UserTaskInstanceStateEventMerger.java @@ -58,9 +58,10 @@ public UserTaskInstance merge(UserTaskInstance task, UserTaskInstanceDataEvent(), Comment.class)); ut.setAttachments(reader.readCollection(ATTACHMENTS, new ArrayList<>(), Attachment.class)); + ut.setExternalReferenceId(reader.readString(EXTERNAL_REFERENCE_ID)); return ut; } @@ -118,6 +120,7 @@ public void writeTo(ProtoStreamWriter writer, UserTaskInstance ut) throws IOExce writer.writeString(ENDPOINT, ut.getEndpoint()); writer.writeCollection(COMMENTS, ut.getComments(), Comment.class); writer.writeCollection(ATTACHMENTS, ut.getAttachments(), Attachment.class); + writer.writeString(EXTERNAL_REFERENCE_ID, ut.getExternalReferenceId()); } @Override diff --git a/data-index/data-index-storage/data-index-storage-infinispan/src/test/java/org/kie/kogito/index/infinispan/protostream/UserTaskInstanceMarshallerTest.java b/data-index/data-index-storage/data-index-storage-infinispan/src/test/java/org/kie/kogito/index/infinispan/protostream/UserTaskInstanceMarshallerTest.java index 6c19ae9b54..07f98b1ac7 100644 --- a/data-index/data-index-storage/data-index-storage-infinispan/src/test/java/org/kie/kogito/index/infinispan/protostream/UserTaskInstanceMarshallerTest.java +++ b/data-index/data-index-storage/data-index-storage-infinispan/src/test/java/org/kie/kogito/index/infinispan/protostream/UserTaskInstanceMarshallerTest.java @@ -47,6 +47,7 @@ import static org.kie.kogito.index.infinispan.protostream.UserTaskInstanceMarshaller.DESCRIPTION; import static org.kie.kogito.index.infinispan.protostream.UserTaskInstanceMarshaller.ENDPOINT; import static org.kie.kogito.index.infinispan.protostream.UserTaskInstanceMarshaller.EXCLUDED_USERS; +import static org.kie.kogito.index.infinispan.protostream.UserTaskInstanceMarshaller.EXTERNAL_REFERENCE_ID; import static org.kie.kogito.index.infinispan.protostream.UserTaskInstanceMarshaller.ID; import static org.kie.kogito.index.infinispan.protostream.UserTaskInstanceMarshaller.INPUTS; import static org.kie.kogito.index.infinispan.protostream.UserTaskInstanceMarshaller.LAST_UPDATE; @@ -100,6 +101,7 @@ static void setup() { TASK.setReferenceName("referenceName"); TASK.setLastUpdate(ZonedDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.MILLIS)); TASK.setEndpoint("endpoint"); + TASK.setExternalReferenceId("externalReferenceId"); TASK.setComments(List.of(Comment.builder() .id("attId") .content("Text comment") @@ -144,6 +146,7 @@ void testReadFrom() throws IOException { when(reader.readString(ENDPOINT)).thenReturn(TASK.getEndpoint()); when(reader.readCollection(eq(COMMENTS), any(), eq(Comment.class))).thenReturn(TASK.getComments()); when(reader.readCollection(eq(ATTACHMENTS), any(), eq(Attachment.class))).thenReturn(TASK.getAttachments()); + when(reader.readString(EXTERNAL_REFERENCE_ID)).thenReturn(TASK.getExternalReferenceId()); UserTaskInstance task = marshaller.readFrom(reader); @@ -174,6 +177,7 @@ void testReadFrom() throws IOException { inOrder.verify(reader).readString(ENDPOINT); inOrder.verify(reader).readCollection(COMMENTS, new ArrayList<>(), Comment.class); inOrder.verify(reader).readCollection(ATTACHMENTS, new ArrayList<>(), Attachment.class); + inOrder.verify(reader).readString(EXTERNAL_REFERENCE_ID); verifyNoMoreInteractions(reader); } @@ -209,6 +213,7 @@ void testWriteTo() throws IOException { inOrder.verify(writer).writeString(ENDPOINT, TASK.getEndpoint()); inOrder.verify(writer).writeCollection(COMMENTS, TASK.getComments(), Comment.class); inOrder.verify(writer).writeCollection(ATTACHMENTS, TASK.getAttachments(), Attachment.class); + inOrder.verify(writer).writeString(EXTERNAL_REFERENCE_ID, TASK.getExternalReferenceId()); verifyNoMoreInteractions(writer); } diff --git a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/model/UserTaskInstanceEntity.java b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/model/UserTaskInstanceEntity.java index 0021a0eb20..314174ed1a 100644 --- a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/model/UserTaskInstanceEntity.java +++ b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/model/UserTaskInstanceEntity.java @@ -91,6 +91,16 @@ public class UserTaskInstanceEntity extends AbstractEntity { @OneToMany(cascade = CascadeType.ALL, mappedBy = "userTask", orphanRemoval = true, fetch = FetchType.LAZY) private List attachments; + private String externalReferenceId; + + public String getExternalReferenceId() { + return externalReferenceId; + } + + public void setExternalReferenceId(String externalReferenceId) { + this.externalReferenceId = externalReferenceId; + } + @Override public String getId() { return id; diff --git a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/ProcessInstanceEntityStorage.java b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/ProcessInstanceEntityStorage.java index 386aabcf78..8796f3204a 100644 --- a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/ProcessInstanceEntityStorage.java +++ b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/ProcessInstanceEntityStorage.java @@ -20,9 +20,12 @@ import java.time.ZonedDateTime; import java.util.ArrayList; -import java.util.Date; +import java.util.HashMap; +import java.util.Map; import java.util.Set; +import org.kie.kogito.event.process.MultipleProcessInstanceDataEvent; +import org.kie.kogito.event.process.ProcessInstanceDataEvent; import org.kie.kogito.event.process.ProcessInstanceErrorDataEvent; import org.kie.kogito.event.process.ProcessInstanceErrorEventBody; import org.kie.kogito.event.process.ProcessInstanceNodeDataEvent; @@ -64,43 +67,51 @@ public ProcessInstanceEntityStorage(ProcessInstanceEntityRepository repository, super(repository, ProcessInstanceEntity.class, mapper::mapToModel); } + @Override + @Transactional + public void indexGroup(MultipleProcessInstanceDataEvent events) { + Map piMap = new HashMap<>(); + for (ProcessInstanceDataEvent event : events.getData()) { + indexEvent(piMap.computeIfAbsent(event.getKogitoProcessInstanceId(), id -> findOrInit(event)), event); + } + } + @Override @Transactional public void indexError(ProcessInstanceErrorDataEvent event) { - indexError(event.getData()); + indexError(findOrInit(event), event.getData()); } @Override @Transactional public void indexNode(ProcessInstanceNodeDataEvent event) { - indexNode(event.getData()); + indexNode(findOrInit(event), event.getData()); } @Override @Transactional public void indexSLA(ProcessInstanceSLADataEvent event) { - indexSLA(event.getData()); - + indexSla(findOrInit(event), event.getData()); } @Override @Transactional public void indexState(ProcessInstanceStateDataEvent event) { - indexState(event.getData(), event.getKogitoAddons() == null ? Set.of() : Set.of(event.getKogitoAddons().split(",")), event.getSource() == null ? null : event.getSource().toString()); + indexState(findOrInit(event), event); } @Override @Transactional public void indexVariable(ProcessInstanceVariableDataEvent event) { - indexVariable(event.getData()); + indexVariable(findOrInit(event), event.getData()); } - private ProcessInstanceEntity findOrInit(String processId, String processInstanceId, Date date) { - return repository.findByIdOptional(processInstanceId).orElseGet(() -> { + private ProcessInstanceEntity findOrInit(ProcessInstanceDataEvent event) { + return repository.findByIdOptional(event.getKogitoProcessInstanceId()).orElseGet(() -> { ProcessInstanceEntity pi = new ProcessInstanceEntity(); - pi.setProcessId(processId); - pi.setId(processInstanceId); - pi.setLastUpdate(toZonedDateTime(date)); + pi.setProcessId(event.getKogitoProcessId()); + pi.setId(event.getKogitoProcessInstanceId()); + pi.setLastUpdate(toZonedDateTime(event.getTime())); pi.setNodes(new ArrayList<>()); pi.setMilestones(new ArrayList<>()); repository.persist(pi); @@ -108,8 +119,21 @@ private ProcessInstanceEntity findOrInit(String processId, String processInstanc }); } - private void indexError(ProcessInstanceErrorEventBody error) { - ProcessInstanceEntity pi = findOrInit(error.getProcessId(), error.getProcessInstanceId(), error.getEventDate()); + private void indexEvent(ProcessInstanceEntity pi, ProcessInstanceDataEvent event) { + if (event instanceof ProcessInstanceErrorDataEvent) { + indexError(pi, ((ProcessInstanceErrorDataEvent) event).getData()); + } else if (event instanceof ProcessInstanceNodeDataEvent) { + indexNode(pi, ((ProcessInstanceNodeDataEvent) event).getData()); + } else if (event instanceof ProcessInstanceSLADataEvent) { + indexSla(pi, ((ProcessInstanceSLADataEvent) event).getData()); + } else if (event instanceof ProcessInstanceStateDataEvent) { + indexState(pi, (ProcessInstanceStateDataEvent) event); + } else if (event instanceof ProcessInstanceVariableDataEvent) { + indexVariable(pi, ((ProcessInstanceVariableDataEvent) event).getData()); + } + } + + private void indexError(ProcessInstanceEntity pi, ProcessInstanceErrorEventBody error) { ProcessInstanceErrorEntity errorEntity = pi.getError(); if (errorEntity == null) { errorEntity = new ProcessInstanceErrorEntity(); @@ -118,16 +142,13 @@ private void indexError(ProcessInstanceErrorEventBody error) { errorEntity.setMessage(error.getErrorMessage()); errorEntity.setNodeDefinitionId(error.getNodeDefinitionId()); pi.setState(CommonUtils.ERROR_STATE); - repository.flush(); } - private void indexNode(ProcessInstanceNodeEventBody data) { - ProcessInstanceEntity pi = findOrInit(data.getProcessId(), data.getProcessInstanceId(), data.getEventDate()); + private void indexNode(ProcessInstanceEntity pi, ProcessInstanceNodeEventBody data) { pi.getNodes().stream().filter(n -> n.getId().equals(data.getNodeInstanceId())).findAny().ifPresentOrElse(n -> updateNode(n, data), () -> createNode(pi, data)); if ("MilestoneNode".equals(data.getNodeType())) { pi.getMilestones().stream().filter(n -> n.getId().equals(data.getNodeInstanceId())).findAny().ifPresentOrElse(n -> updateMilestone(n, data), () -> createMilestone(pi, data)); } - repository.flush(); } private MilestoneEntity createMilestone(ProcessInstanceEntity pi, ProcessInstanceNodeEventBody data) { @@ -146,9 +167,10 @@ private MilestoneEntity updateMilestone(MilestoneEntity milestone, ProcessInstan private NodeInstanceEntity createNode(ProcessInstanceEntity pi, ProcessInstanceNodeEventBody data) { NodeInstanceEntity node = new NodeInstanceEntity(); - pi.getNodes().add(node); node.setProcessInstance(pi); - return updateNode(node, data); + updateNode(node, data); + pi.getNodes().add(node); + return node; } private NodeInstanceEntity updateNode(NodeInstanceEntity nodeInstance, ProcessInstanceNodeEventBody body) { @@ -159,7 +181,6 @@ private NodeInstanceEntity updateNode(NodeInstanceEntity nodeInstance, ProcessIn nodeInstance.setType(body.getNodeType()); ZonedDateTime eventDate = toZonedDateTime(body.getEventDate()); switch (body.getEventType()) { - case EVENT_TYPE_ENTER: nodeInstance.setEnter(eventDate); break; @@ -174,13 +195,12 @@ private NodeInstanceEntity updateNode(NodeInstanceEntity nodeInstance, ProcessIn return nodeInstance; } - private void indexSLA(ProcessInstanceSLAEventBody data) { - findOrInit(data.getProcessId(), data.getProcessInstanceId(), data.getEventDate()); - repository.flush(); + private void indexState(ProcessInstanceEntity pi, ProcessInstanceStateDataEvent event) { + indexState(pi, event.getData(), (event.getKogitoAddons() == null || event.getKogitoAddons().isEmpty()) ? Set.of() : Set.of(event.getKogitoAddons().split(",")), + event.getSource() == null ? null : event.getSource().toString()); } - private void indexState(ProcessInstanceStateEventBody data, Set addons, String endpoint) { - ProcessInstanceEntity pi = findOrInit(data.getProcessId(), data.getProcessInstanceId(), data.getEventDate()); + private void indexState(ProcessInstanceEntity pi, ProcessInstanceStateEventBody data, Set addons, String endpoint) { pi.setVersion(data.getProcessVersion()); pi.setProcessName(data.getProcessName()); pi.setRootProcessInstanceId(data.getRootProcessInstanceId()); @@ -199,13 +219,13 @@ private void indexState(ProcessInstanceStateEventBody data, Set addons, pi.setLastUpdate(toZonedDateTime(data.getEventDate())); pi.setAddons(addons); pi.setEndpoint(endpoint); - repository.flush(); } - private void indexVariable(ProcessInstanceVariableEventBody data) { - ProcessInstanceEntity pi = findOrInit(data.getProcessId(), data.getProcessInstanceId(), data.getEventDate()); + private void indexVariable(ProcessInstanceEntity pi, ProcessInstanceVariableEventBody data) { pi.setVariables(JsonUtils.mergeVariable(data.getVariableName(), data.getVariableValue(), pi.getVariables())); - repository.flush(); } + private void indexSla(ProcessInstanceEntity orInit, ProcessInstanceSLAEventBody data) { + // SLA does nothing for now + } } diff --git a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/UserTaskInstanceEntityStorage.java b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/UserTaskInstanceEntityStorage.java index 1e2c127a36..5204db6574 100644 --- a/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/UserTaskInstanceEntityStorage.java +++ b/data-index/data-index-storage/data-index-storage-jpa-common/src/main/java/org/kie/kogito/index/jpa/storage/UserTaskInstanceEntityStorage.java @@ -19,15 +19,19 @@ package org.kie.kogito.index.jpa.storage; import java.net.URI; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; +import org.kie.kogito.event.usertask.MultipleUserTaskInstanceDataEvent; import org.kie.kogito.event.usertask.UserTaskInstanceAssignmentDataEvent; import org.kie.kogito.event.usertask.UserTaskInstanceAssignmentEventBody; import org.kie.kogito.event.usertask.UserTaskInstanceAttachmentDataEvent; import org.kie.kogito.event.usertask.UserTaskInstanceAttachmentEventBody; import org.kie.kogito.event.usertask.UserTaskInstanceCommentDataEvent; import org.kie.kogito.event.usertask.UserTaskInstanceCommentEventBody; +import org.kie.kogito.event.usertask.UserTaskInstanceDataEvent; import org.kie.kogito.event.usertask.UserTaskInstanceDeadlineDataEvent; import org.kie.kogito.event.usertask.UserTaskInstanceStateDataEvent; import org.kie.kogito.event.usertask.UserTaskInstanceStateEventBody; @@ -66,20 +70,69 @@ public UserTaskInstanceEntityStorage(UserTaskInstanceEntityRepository repository super(repository, UserTaskInstanceEntity.class, mapper::mapToModel); } - private UserTaskInstanceEntity findOrInit(String taskId) { - return repository.findByIdOptional(taskId).orElseGet(() -> { - UserTaskInstanceEntity ut = new UserTaskInstanceEntity(); - ut.setId(taskId); - repository.persist(ut); - return ut; - }); + @Override + @Transactional + public void indexGroup(MultipleUserTaskInstanceDataEvent events) { + Map taskMap = new HashMap<>(); + for (UserTaskInstanceDataEvent event : events.getData()) { + indexEvent(taskMap.computeIfAbsent(event.getKogitoUserTaskInstanceId(), id -> findOrInit(id)), event); + } } @Override @Transactional public void indexAssignment(UserTaskInstanceAssignmentDataEvent event) { + indexAssignment(findOrInit(event), event); + } + + @Override + @Transactional + public void indexAttachment(UserTaskInstanceAttachmentDataEvent event) { + indexAttachment(findOrInit(event), event); + } + + @Override + @Transactional + public void indexDeadline(UserTaskInstanceDeadlineDataEvent event) { + indexDeadline(findOrInit(event), event); + } + + @Override + @Transactional + public void indexState(UserTaskInstanceStateDataEvent event) { + indexState(findOrInit(event), event); + } + + @Override + @Transactional + public void indexComment(UserTaskInstanceCommentDataEvent event) { + indexComment(findOrInit(event), event); + } + + @Override + @Transactional + public void indexVariable(UserTaskInstanceVariableDataEvent event) { + indexVariable(findOrInit(event), event); + } + + private void indexEvent(UserTaskInstanceEntity task, UserTaskInstanceDataEvent event) { + if (event instanceof UserTaskInstanceAssignmentDataEvent) { + indexAssignment(task, (UserTaskInstanceAssignmentDataEvent) event); + } else if (event instanceof UserTaskInstanceAttachmentDataEvent) { + indexAttachment(task, (UserTaskInstanceAttachmentDataEvent) event); + } else if (event instanceof UserTaskInstanceDeadlineDataEvent) { + indexDeadline(task, (UserTaskInstanceDeadlineDataEvent) event); + } else if (event instanceof UserTaskInstanceStateDataEvent) { + indexState(task, (UserTaskInstanceStateDataEvent) event); + } else if (event instanceof UserTaskInstanceCommentDataEvent) { + indexComment(task, (UserTaskInstanceCommentDataEvent) event); + } else if (event instanceof UserTaskInstanceVariableDataEvent) { + indexVariable(task, (UserTaskInstanceVariableDataEvent) event); + } + } + + private void indexAssignment(UserTaskInstanceEntity userTaskInstance, UserTaskInstanceAssignmentDataEvent event) { UserTaskInstanceAssignmentEventBody body = event.getData(); - UserTaskInstanceEntity userTaskInstance = findOrInit(event.getKogitoUserTaskInstanceId()); switch (body.getAssignmentType()) { case "USER_OWNERS": userTaskInstance.setPotentialUsers(new HashSet<>(body.getUsers())); @@ -97,13 +150,9 @@ public void indexAssignment(UserTaskInstanceAssignmentDataEvent event) { userTaskInstance.setAdminUsers(new HashSet<>(body.getUsers())); break; } - repository.flush(); } - @Override - @Transactional - public void indexAttachment(UserTaskInstanceAttachmentDataEvent event) { - UserTaskInstanceEntity userTaskInstance = findOrInit(event.getKogitoUserTaskInstanceId()); + private void indexAttachment(UserTaskInstanceEntity userTaskInstance, UserTaskInstanceAttachmentDataEvent event) { UserTaskInstanceAttachmentEventBody body = event.getData(); List attachments = userTaskInstance.getAttachments(); switch (body.getEventType()) { @@ -127,17 +176,12 @@ public void indexAttachment(UserTaskInstanceAttachmentDataEvent event) { } } - @Override - @Transactional - public void indexDeadline(UserTaskInstanceDeadlineDataEvent event) { - findOrInit(event.getKogitoUserTaskInstanceId()); + private void indexDeadline(UserTaskInstanceEntity userTaskInstance, UserTaskInstanceDeadlineDataEvent event) { + // deadlines ignored for now } - @Override - @Transactional - public void indexState(UserTaskInstanceStateDataEvent event) { + private void indexState(UserTaskInstanceEntity task, UserTaskInstanceStateDataEvent event) { UserTaskInstanceStateEventBody body = event.getData(); - UserTaskInstanceEntity task = findOrInit(event.getKogitoUserTaskInstanceId()); task.setProcessInstanceId(body.getProcessInstanceId()); task.setProcessId(event.getKogitoProcessId()); task.setRootProcessId(event.getKogitoRootProcessId()); @@ -153,9 +197,10 @@ public void indexState(UserTaskInstanceStateDataEvent event) { } task.setActualOwner(event.getData().getActualOwner()); task.setEndpoint( - event.getSource() == null ? null : getEndpoint(event.getSource(), event.getData().getProcessInstanceId(), event.getData().getUserTaskName(), event.getData().getUserTaskInstanceId())); + event.getSource() == null ? null : getEndpoint(event.getSource(), event.getData().getProcessInstanceId(), event.getData().getUserTaskName(), event.getData().getExternalReferenceId())); task.setLastUpdate(toZonedDateTime(event.getData().getEventDate())); task.setReferenceName(event.getData().getUserTaskReferenceName()); + task.setExternalReferenceId(body.getExternalReferenceId()); } private String getEndpoint(URI source, String pId, String taskName, String taskId) { @@ -163,11 +208,8 @@ private String getEndpoint(URI source, String pId, String taskName, String taskI return source.toString() + format("/%s/%s/%s", pId, name, taskId); } - @Override - @Transactional - public void indexComment(UserTaskInstanceCommentDataEvent event) { + private void indexComment(UserTaskInstanceEntity userTaskInstance, UserTaskInstanceCommentDataEvent event) { UserTaskInstanceCommentEventBody body = event.getData(); - UserTaskInstanceEntity userTaskInstance = findOrInit(event.getKogitoUserTaskInstanceId()); List comments = userTaskInstance.getComments(); switch (body.getEventType()) { case UserTaskInstanceCommentEventBody.EVENT_TYPE_ADDED: @@ -190,10 +232,7 @@ public void indexComment(UserTaskInstanceCommentDataEvent event) { } } - @Override - @Transactional - public void indexVariable(UserTaskInstanceVariableDataEvent event) { - UserTaskInstanceEntity userTaskInstance = findOrInit(event.getKogitoUserTaskInstanceId()); + private void indexVariable(UserTaskInstanceEntity userTaskInstance, UserTaskInstanceVariableDataEvent event) { UserTaskInstanceVariableEventBody body = event.getData(); if (body.getVariableType().equals("INPUT")) { ObjectNode objectNode = userTaskInstance.getInputs(); @@ -211,4 +250,17 @@ public void indexVariable(UserTaskInstanceVariableDataEvent event) { userTaskInstance.setOutputs(objectNode); } } + + private UserTaskInstanceEntity findOrInit(UserTaskInstanceDataEvent event) { + return findOrInit(event.getKogitoUserTaskInstanceId()); + } + + private UserTaskInstanceEntity findOrInit(String taskId) { + return repository.findByIdOptional(taskId).orElseGet(() -> { + UserTaskInstanceEntity ut = new UserTaskInstanceEntity(); + ut.setId(taskId); + repository.persist(ut); + return ut; + }); + } } diff --git a/data-index/data-index-storage/data-index-storage-jpa-common/src/test/java/org/kie/kogito/index/jpa/mapper/AbstractUserTaskInstanceEntityMapperIT.java b/data-index/data-index-storage/data-index-storage-jpa-common/src/test/java/org/kie/kogito/index/jpa/mapper/AbstractUserTaskInstanceEntityMapperIT.java index 6d4dd31e24..f9e8b2f7c4 100644 --- a/data-index/data-index-storage/data-index-storage-jpa-common/src/test/java/org/kie/kogito/index/jpa/mapper/AbstractUserTaskInstanceEntityMapperIT.java +++ b/data-index/data-index-storage/data-index-storage-jpa-common/src/test/java/org/kie/kogito/index/jpa/mapper/AbstractUserTaskInstanceEntityMapperIT.java @@ -71,6 +71,7 @@ void setup() { String processId = "testProcessId"; String rootProcessId = "testRootProcessId"; String rootProcessInstanceId = "testRootProcessInstanceId"; + String externalReferenceId = "testExternalReferenceId"; Map object = new HashMap<>(); object.put("test", "testValue"); ObjectNode inputs = jsonMapper.createObjectNode().put("testInput", "testValue"); @@ -124,6 +125,7 @@ void setup() { userTaskInstance.setOutputs(outputs); userTaskInstance.setComments(singletonList(comment)); userTaskInstance.setAttachments(singletonList(attachment)); + userTaskInstance.setExternalReferenceId(externalReferenceId); userTaskInstanceEntity.setId(testId); userTaskInstanceEntity.setDescription(description); @@ -148,6 +150,7 @@ void setup() { userTaskInstanceEntity.setOutputs(outputs); userTaskInstanceEntity.setComments(singletonList(commentEntity)); userTaskInstanceEntity.setAttachments(singletonList(attachmentEntity)); + userTaskInstanceEntity.setExternalReferenceId(externalReferenceId); } @Test diff --git a/data-index/data-index-storage/data-index-storage-jpa/src/main/resources/kie-flyway/db/data-index/ansi/V1.45.0.5__add_external_reference_id.sql b/data-index/data-index-storage/data-index-storage-jpa/src/main/resources/kie-flyway/db/data-index/ansi/V1.45.0.5__add_external_reference_id.sql new file mode 100644 index 0000000000..918b9369c6 --- /dev/null +++ b/data-index/data-index-storage/data-index-storage-jpa/src/main/resources/kie-flyway/db/data-index/ansi/V1.45.0.5__add_external_reference_id.sql @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +alter table tasks add external_reference_id character varying(4000) diff --git a/data-index/data-index-storage/data-index-storage-mongodb/src/main/java/org/kie/kogito/index/mongodb/model/UserTaskInstanceEntity.java b/data-index/data-index-storage/data-index-storage-mongodb/src/main/java/org/kie/kogito/index/mongodb/model/UserTaskInstanceEntity.java index a027ad3843..c97c753dc0 100644 --- a/data-index/data-index-storage/data-index-storage-mongodb/src/main/java/org/kie/kogito/index/mongodb/model/UserTaskInstanceEntity.java +++ b/data-index/data-index-storage/data-index-storage-mongodb/src/main/java/org/kie/kogito/index/mongodb/model/UserTaskInstanceEntity.java @@ -76,6 +76,8 @@ public class UserTaskInstanceEntity { List attachments; + private String externalReferenceId; + public String getId() { return id; } @@ -268,6 +270,14 @@ public void setAttachments(List attachments) { this.attachments = attachments; } + public void setExternalReferenceId(String externalReferenceId) { + this.externalReferenceId = externalReferenceId; + } + + public String getExternalReferenceId() { + return externalReferenceId; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -435,4 +445,5 @@ public int hashCode() { return Objects.hash(id); } } + } diff --git a/data-index/data-index-storage/data-index-storage-mongodb/src/main/java/org/kie/kogito/index/mongodb/model/UserTaskInstanceEntityMapper.java b/data-index/data-index-storage/data-index-storage-mongodb/src/main/java/org/kie/kogito/index/mongodb/model/UserTaskInstanceEntityMapper.java index b7479af604..1a8b9add5d 100644 --- a/data-index/data-index-storage/data-index-storage-mongodb/src/main/java/org/kie/kogito/index/mongodb/model/UserTaskInstanceEntityMapper.java +++ b/data-index/data-index-storage/data-index-storage-mongodb/src/main/java/org/kie/kogito/index/mongodb/model/UserTaskInstanceEntityMapper.java @@ -79,6 +79,7 @@ public UserTaskInstanceEntity mapToEntity(String key, UserTaskInstance instance) entity.setEndpoint(instance.getEndpoint()); entity.setComments(Optional.ofNullable(instance.getComments()).map(comments -> comments.stream().map(this::fromComment).collect(toList())).orElse(null)); entity.setAttachments(Optional.ofNullable(instance.getAttachments()).map(attachments -> attachments.stream().map(this::fromAttachment).collect(toList())).orElse(null)); + entity.setExternalReferenceId(instance.getExternalReferenceId()); return entity; } @@ -113,6 +114,7 @@ public UserTaskInstance mapToModel(UserTaskInstanceEntity entity) { instance.setEndpoint(entity.getEndpoint()); instance.setComments(Optional.ofNullable(entity.getComments()).map(comments -> comments.stream().map(this::toComment).collect(toList())).orElse(null)); instance.setAttachments(Optional.ofNullable(entity.getAttachments()).map(attachments -> attachments.stream().map(this::toAttachment).collect(toList())).orElse(null)); + instance.setExternalReferenceId(entity.getExternalReferenceId()); return instance; } diff --git a/data-index/data-index-storage/data-index-storage-mongodb/src/test/java/org/kie/kogito/index/mongodb/model/UserTaskInstanceEntityMapperTest.java b/data-index/data-index-storage/data-index-storage-mongodb/src/test/java/org/kie/kogito/index/mongodb/model/UserTaskInstanceEntityMapperTest.java index 9340704383..013ba7a041 100644 --- a/data-index/data-index-storage/data-index-storage-mongodb/src/test/java/org/kie/kogito/index/mongodb/model/UserTaskInstanceEntityMapperTest.java +++ b/data-index/data-index-storage/data-index-storage-mongodb/src/test/java/org/kie/kogito/index/mongodb/model/UserTaskInstanceEntityMapperTest.java @@ -64,6 +64,7 @@ static void setup() { String processId = "testProcessId"; String rootProcessId = "testRootProcessId"; String rootProcessInstanceId = "testRootProcessInstanceId"; + String externalReferenceId = "testExternalReferenceId"; ObjectNode object = MAPPER.createObjectNode(); object.put("test", "testValue"); ObjectNode inputs = object; @@ -114,6 +115,7 @@ static void setup() { userTaskInstance.setOutputs(outputs); userTaskInstance.setComments(List.of(comment)); userTaskInstance.setAttachments(List.of(attachment)); + userTaskInstance.setExternalReferenceId(externalReferenceId); userTaskInstanceEntity = new UserTaskInstanceEntity(); userTaskInstanceEntity.setId(testId); @@ -139,6 +141,7 @@ static void setup() { userTaskInstanceEntity.setOutputs(jsonNodeToDocument(outputs)); userTaskInstanceEntity.setComments(List.of(commentEntity)); userTaskInstanceEntity.setAttachments(List.of(attachmentEntity)); + userTaskInstanceEntity.setExternalReferenceId(externalReferenceId); } @Test diff --git a/data-index/data-index-storage/data-index-storage-postgresql/src/main/resources/kie-flyway/db/data-index/postgresql/V1.45.0.5__add_external_reference_id.sql b/data-index/data-index-storage/data-index-storage-postgresql/src/main/resources/kie-flyway/db/data-index/postgresql/V1.45.0.5__add_external_reference_id.sql new file mode 100644 index 0000000000..908ee2b249 --- /dev/null +++ b/data-index/data-index-storage/data-index-storage-postgresql/src/main/resources/kie-flyway/db/data-index/postgresql/V1.45.0.5__add_external_reference_id.sql @@ -0,0 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +alter table tasks add external_reference_id varchar(4000); diff --git a/data-index/data-index-storage/data-index-storage-protobuf/src/main/resources/META-INF/kogito-index.proto b/data-index/data-index-storage/data-index-storage-protobuf/src/main/resources/META-INF/kogito-index.proto index 18f01a6014..5c23eecefa 100644 --- a/data-index/data-index-storage/data-index-storage-protobuf/src/main/resources/META-INF/kogito-index.proto +++ b/data-index/data-index-storage/data-index-storage-protobuf/src/main/resources/META-INF/kogito-index.proto @@ -237,6 +237,7 @@ message UserTaskInstance { repeated Comment comments = 23; /* @Field(store = Store.YES) */ repeated Attachment attachments = 24; + optional string externalReferenceId = 25; } /* @Indexed */ @@ -275,6 +276,7 @@ message UserTaskInstanceMeta { repeated Comment comments = 17; /* @Field(store = Store.YES) */ repeated Attachment attachments = 18; + optional string externalReferenceId = 25; } /* @Indexed */ diff --git a/data-index/data-index-test-utils/src/main/java/org/kie/kogito/index/test/TestUtils.java b/data-index/data-index-test-utils/src/main/java/org/kie/kogito/index/test/TestUtils.java index 68946a056c..600d52824e 100644 --- a/data-index/data-index-test-utils/src/main/java/org/kie/kogito/index/test/TestUtils.java +++ b/data-index/data-index-test-utils/src/main/java/org/kie/kogito/index/test/TestUtils.java @@ -286,6 +286,7 @@ public static UserTaskInstanceStateDataEvent getUserTaskCloudEvent(String taskId .actualOwner(actualOwner) .eventDate(new Date()) .processInstanceId(processInstanceId) + .externalReferenceId("testExternalReferenceId") .build(); UserTaskInstanceStateDataEvent event = new UserTaskInstanceStateDataEvent(URI.create("http://localhost:8080/" + processId).toString(), null, null, body.metaData(), body); event.setKogitoProcessId(processId); @@ -422,6 +423,7 @@ public static UserTaskInstance getUserTaskInstance(String taskId, String process task.setPotentialGroups(singleton("potentialGroup")); task.setComments(List.of(Comment.builder().id("commentId" + taskId).content("Comment 1").updatedBy("kogito").build())); task.setAttachments(List.of(Attachment.builder().id("attachmentId" + taskId).content("http://linltodoc.com/1").name("doc1").updatedBy("kogito").build())); + task.setExternalReferenceId("testExternalReferenceId"); return task; } } diff --git a/jitexecutor/jitexecutor-dmn/src/main/java/org/kie/kogito/jitexecutor/dmn/DMNEvaluator.java b/jitexecutor/jitexecutor-dmn/src/main/java/org/kie/kogito/jitexecutor/dmn/DMNEvaluator.java index 979f4159bd..92ec93303f 100644 --- a/jitexecutor/jitexecutor-dmn/src/main/java/org/kie/kogito/jitexecutor/dmn/DMNEvaluator.java +++ b/jitexecutor/jitexecutor-dmn/src/main/java/org/kie/kogito/jitexecutor/dmn/DMNEvaluator.java @@ -22,7 +22,9 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Optional; import org.kie.api.io.Resource; import org.kie.dmn.api.core.DMNContext; @@ -36,6 +38,7 @@ import org.kie.internal.io.ResourceFactory; import org.kie.kogito.jitexecutor.common.requests.MultipleResourcesPayload; import org.kie.kogito.jitexecutor.common.requests.ResourceWithURI; +import org.kie.kogito.jitexecutor.dmn.responses.JITDMNResult; import org.kie.kogito.jitexecutor.dmn.utils.ResolveByKey; public class DMNEvaluator { @@ -47,6 +50,7 @@ public static DMNEvaluator fromXML(String modelXML) { Resource modelResource = ResourceFactory.newReaderResource(new StringReader(modelXML), "UTF-8"); DMNRuntime dmnRuntime = DMNRuntimeBuilder.fromDefaults().buildConfiguration() .fromResources(Collections.singletonList(modelResource)).getOrElseThrow(RuntimeException::new); + dmnRuntime.addListener(new JITDMNListener()); DMNModel dmnModel = dmnRuntime.getModels().get(0); return new DMNEvaluator(dmnModel, dmnRuntime); } @@ -73,9 +77,16 @@ public Collection getAllDMNModels() { return dmnRuntime.getModels(); } - public DMNResult evaluate(Map context) { - DMNContext dmnContext = new DynamicDMNContextBuilder(dmnRuntime.newContext(), dmnModel).populateContextWith(context); - return dmnRuntime.evaluateAll(dmnModel, dmnContext); + public JITDMNResult evaluate(Map context) { + DMNContext dmnContext = + new DynamicDMNContextBuilder(dmnRuntime.newContext(), dmnModel).populateContextWith(context); + DMNResult dmnResult = dmnRuntime.evaluateAll(dmnModel, dmnContext); + Optional> evaluationHitIds = dmnRuntime.getListeners().stream() + .filter(JITDMNListener.class::isInstance) + .findFirst() + .map(JITDMNListener.class::cast) + .map(JITDMNListener::getEvaluationHitIds); + return new JITDMNResult(getNamespace(), getName(), dmnResult, evaluationHitIds.orElse(Collections.emptyList())); } public static DMNEvaluator fromMultiple(MultipleResourcesPayload payload) { diff --git a/jitexecutor/jitexecutor-dmn/src/main/java/org/kie/kogito/jitexecutor/dmn/JITDMNListener.java b/jitexecutor/jitexecutor-dmn/src/main/java/org/kie/kogito/jitexecutor/dmn/JITDMNListener.java new file mode 100644 index 0000000000..9f753e7548 --- /dev/null +++ b/jitexecutor/jitexecutor-dmn/src/main/java/org/kie/kogito/jitexecutor/dmn/JITDMNListener.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.jitexecutor.dmn; + +import java.util.ArrayList; +import java.util.List; + +import org.kie.dmn.api.core.event.AfterConditionalEvaluationEvent; +import org.kie.dmn.api.core.event.AfterEvaluateAllEvent; +import org.kie.dmn.api.core.event.AfterEvaluateBKMEvent; +import org.kie.dmn.api.core.event.AfterEvaluateContextEntryEvent; +import org.kie.dmn.api.core.event.AfterEvaluateDecisionEvent; +import org.kie.dmn.api.core.event.AfterEvaluateDecisionServiceEvent; +import org.kie.dmn.api.core.event.AfterEvaluateDecisionTableEvent; +import org.kie.dmn.api.core.event.AfterInvokeBKMEvent; +import org.kie.dmn.api.core.event.DMNEvent; +import org.kie.dmn.api.core.event.DMNRuntimeEventListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JITDMNListener implements DMNRuntimeEventListener { + + private final List evaluationHitIds = new ArrayList<>(); + + private static final Logger LOGGER = LoggerFactory.getLogger(JITDMNListener.class); + + @Override + public void afterEvaluateDecisionTable(AfterEvaluateDecisionTableEvent event) { + logEvent(event); + evaluationHitIds.addAll(event.getSelectedIds()); + } + + @Override + public void afterEvaluateDecision(AfterEvaluateDecisionEvent event) { + logEvent(event); + } + + @Override + public void afterEvaluateBKM(AfterEvaluateBKMEvent event) { + logEvent(event); + } + + @Override + public void afterEvaluateContextEntry(AfterEvaluateContextEntryEvent event) { + logEvent(event); + } + + @Override + public void afterEvaluateDecisionService(AfterEvaluateDecisionServiceEvent event) { + logEvent(event); + } + + @Override + public void afterInvokeBKM(AfterInvokeBKMEvent event) { + logEvent(event); + } + + @Override + public void afterEvaluateAll(AfterEvaluateAllEvent event) { + logEvent(event); + } + + @Override + public void afterConditionalEvaluation(AfterConditionalEvaluationEvent event) { + logEvent(event); + evaluationHitIds.add(event.getExecutedId()); + } + + public List getEvaluationHitIds() { + return evaluationHitIds; + } + + private void logEvent(DMNEvent toLog) { + LOGGER.info("{} event {}", toLog.getClass().getSimpleName(), toLog); + } + + private void logEvent(AfterConditionalEvaluationEvent toLog) { + LOGGER.info("{} event {}", toLog.getClass().getSimpleName(), toLog); + } + +} diff --git a/jitexecutor/jitexecutor-dmn/src/main/java/org/kie/kogito/jitexecutor/dmn/JITDMNServiceImpl.java b/jitexecutor/jitexecutor-dmn/src/main/java/org/kie/kogito/jitexecutor/dmn/JITDMNServiceImpl.java index 320f1a9609..bb0b6513c8 100644 --- a/jitexecutor/jitexecutor-dmn/src/main/java/org/kie/kogito/jitexecutor/dmn/JITDMNServiceImpl.java +++ b/jitexecutor/jitexecutor-dmn/src/main/java/org/kie/kogito/jitexecutor/dmn/JITDMNServiceImpl.java @@ -76,8 +76,7 @@ public JITDMNServiceImpl(int explainabilityLimeSampleSize, int explainabilityLim @Override public JITDMNResult evaluateModel(String modelXML, Map context) { DMNEvaluator dmnEvaluator = DMNEvaluator.fromXML(modelXML); - DMNResult dmnResult = dmnEvaluator.evaluate(context); - return new JITDMNResult(dmnEvaluator.getNamespace(), dmnEvaluator.getName(), dmnResult); + return dmnEvaluator.evaluate(context); } @Override diff --git a/jitexecutor/jitexecutor-dmn/src/main/java/org/kie/kogito/jitexecutor/dmn/responses/JITDMNResult.java b/jitexecutor/jitexecutor-dmn/src/main/java/org/kie/kogito/jitexecutor/dmn/responses/JITDMNResult.java index bfaea16420..4f43a123f6 100644 --- a/jitexecutor/jitexecutor-dmn/src/main/java/org/kie/kogito/jitexecutor/dmn/responses/JITDMNResult.java +++ b/jitexecutor/jitexecutor-dmn/src/main/java/org/kie/kogito/jitexecutor/dmn/responses/JITDMNResult.java @@ -21,6 +21,7 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -49,16 +50,23 @@ public class JITDMNResult implements Serializable, private Map decisionResults = new HashMap<>(); + private List evaluationHitIds; + public JITDMNResult() { // Intentionally blank. } public JITDMNResult(String namespace, String modelName, org.kie.dmn.api.core.DMNResult dmnResult) { + this(namespace, modelName, dmnResult, Collections.emptyList()); + } + + public JITDMNResult(String namespace, String modelName, org.kie.dmn.api.core.DMNResult dmnResult, List evaluationHitIds) { this.namespace = namespace; this.modelName = modelName; this.setDmnContext(dmnResult.getContext().getAll()); this.setMessages(dmnResult.getMessages()); this.setDecisionResults(dmnResult.getDecisionResults()); + this.evaluationHitIds = evaluationHitIds; } public String getNamespace() { @@ -102,6 +110,14 @@ public void setDecisionResults(List decisionResults } } + public List getEvaluationHitIds() { + return evaluationHitIds; + } + + public void setEvaluationHitIds(List evaluationHitIds) { + this.evaluationHitIds = evaluationHitIds; + } + @JsonIgnore @Override public DMNContext getContext() { @@ -151,6 +167,7 @@ public String toString() { .append(", dmnContext=").append(dmnContext) .append(", messages=").append(messages) .append(", decisionResults=").append(decisionResults) + .append(", evaluationHitIds=").append(evaluationHitIds) .append("]").toString(); } } diff --git a/jitexecutor/jitexecutor-dmn/src/test/java/org/kie/kogito/jitexecutor/dmn/JITDMNServiceImplTest.java b/jitexecutor/jitexecutor-dmn/src/test/java/org/kie/kogito/jitexecutor/dmn/JITDMNServiceImplTest.java index 3169dce379..e99957ef0c 100644 --- a/jitexecutor/jitexecutor-dmn/src/test/java/org/kie/kogito/jitexecutor/dmn/JITDMNServiceImplTest.java +++ b/jitexecutor/jitexecutor-dmn/src/test/java/org/kie/kogito/jitexecutor/dmn/JITDMNServiceImplTest.java @@ -19,8 +19,11 @@ package org.kie.kogito.jitexecutor.dmn; import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.jupiter.api.Assertions; @@ -43,7 +46,7 @@ public static void setup() throws IOException { } @Test - public void testModelEvaluation() { + void testModelEvaluation() { Map context = new HashMap<>(); context.put("FICO Score", 800); context.put("DTI Ratio", .1); @@ -57,7 +60,141 @@ public void testModelEvaluation() { } @Test - public void testExplainability() throws IOException { + void testDecisionTableModelEvaluation() throws IOException { + String decisionTableModel = getModelFromIoUtils("valid_models/DMNv1_x/LoanEligibility.dmn"); + Map client = new HashMap<>(); + client.put("age", 43); + client.put("salary", 1950); + client.put("existing payments", 100); + + Map loan = new HashMap<>(); + loan.put("duration", 15); + loan.put("installment", 180); + Map context = new HashMap<>(); + + context.put("Client", client); + context.put("Loan", loan); + context.put("SupremeDirector", "No"); + context.put("Bribe", 10); + JITDMNResult dmnResult = jitdmnService.evaluateModel(decisionTableModel, context); + + Assertions.assertEquals("LoanEligibility", dmnResult.getModelName()); + Assertions.assertEquals("https://github.com/kiegroup/kogito-examples/dmn-quarkus-listener-example", dmnResult.getNamespace()); + Assertions.assertTrue(dmnResult.getMessages().isEmpty()); + Assertions.assertEquals("Yes", dmnResult.getDecisionResultByName("Eligibility").getResult()); + } + + @Test + void testEvaluationHitIds() throws IOException { + final String thenElementId = "_6481FF12-61B5-451C-B775-4143D9B6CD6B"; + final String elseElementId = "_2CD02CB2-6B56-45C4-B461-405E89D45633"; + final String ruleId0 = "_1578BD9E-2BF9-4BFC-8956-1A736959C937"; + final String ruleId1 = "_31CD7AA3-A806-4E7E-B512-821F82043620"; + final String ruleId3 = "_2545E1A8-93D3-4C8A-A0ED-8AD8B10A58F9"; + final String ruleId4 = "_510A50DA-D5A4-4F06-B0BE-7F8F2AA83740"; + String decisionTableModel = getModelFromIoUtils("valid_models/DMNv1_5/RiskScore_Simple.dmn"); + Map context = new HashMap<>(); + context.put("Credit Score", "Poor"); + context.put("DTI", 33); + JITDMNResult dmnResult = jitdmnService.evaluateModel(decisionTableModel, context); + + Assertions.assertEquals("DMN_A77074C1-21FE-4F7E-9753-F84661569AFC", dmnResult.getModelName()); + Assertions.assertTrue(dmnResult.getMessages().isEmpty()); + Assertions.assertEquals(BigDecimal.valueOf(50), dmnResult.getDecisionResultByName("Risk Score").getResult()); + List evaluationHitIds = dmnResult.getEvaluationHitIds(); + Assertions.assertNotNull(evaluationHitIds); + Assertions.assertEquals(3, evaluationHitIds.size()); + Assertions.assertTrue(evaluationHitIds.contains(elseElementId)); + Assertions.assertTrue(evaluationHitIds.contains(ruleId0)); + Assertions.assertTrue(evaluationHitIds.contains(ruleId3)); + + context = new HashMap<>(); + context.put("Credit Score", "Excellent"); + context.put("DTI", 10); + dmnResult = jitdmnService.evaluateModel(decisionTableModel, context); + + Assertions.assertTrue(dmnResult.getMessages().isEmpty()); + Assertions.assertEquals(BigDecimal.valueOf(20), dmnResult.getDecisionResultByName("Risk Score").getResult()); + evaluationHitIds = dmnResult.getEvaluationHitIds(); + Assertions.assertNotNull(evaluationHitIds); + Assertions.assertEquals(3, evaluationHitIds.size()); + Assertions.assertTrue(evaluationHitIds.contains(thenElementId)); + Assertions.assertTrue(evaluationHitIds.contains(ruleId1)); + Assertions.assertTrue(evaluationHitIds.contains(ruleId4)); + } + + @Test + void testConditionalWithNestedDecisionTableFromRiskScoreEvaluation() throws IOException { + final String thenElementId = "_6481FF12-61B5-451C-B775-4143D9B6CD6B"; + final String thenRuleId0 = "_D1753442-03F0-414B-94F8-6A86182DF6EB"; + final String thenRuleId4 = "_E787BA51-E31D-449B-A432-50BE7466A15E"; + final String elseElementId = "_2CD02CB2-6B56-45C4-B461-405E89D45633"; + final String elseRuleId2 = "_945A5471-9F91-4751-9D96-74978F6FB12B"; + final String elseRuleId5 = "_654BBFBC-9B84-4BD8-9D0B-13E8DD1B9F5D"; + String decisionTableModel = getModelFromIoUtils("valid_models/DMNv1_5/RiskScore_Conditional.dmn"); + + Map context = new HashMap<>(); + context.put("Credit Score", "Poor"); + context.put("DTI", 33); + context.put("World Region", "Asia"); + JITDMNResult dmnResult = jitdmnService.evaluateModel(decisionTableModel, context); + + Assertions.assertTrue(dmnResult.getMessages().isEmpty()); + Assertions.assertEquals(BigDecimal.valueOf(50), dmnResult.getDecisionResultByName("Risk Score").getResult()); + List evaluationHitIds = dmnResult.getEvaluationHitIds(); + Assertions.assertNotNull(evaluationHitIds); + Assertions.assertEquals(3, evaluationHitIds.size()); + Assertions.assertTrue(evaluationHitIds.contains(thenElementId)); + Assertions.assertTrue(evaluationHitIds.contains(thenRuleId0)); + Assertions.assertTrue(evaluationHitIds.contains(thenRuleId4)); + + context = new HashMap<>(); + context.put("Credit Score", "Excellent"); + context.put("DTI", 10); + context.put("World Region", "Europe"); + dmnResult = jitdmnService.evaluateModel(decisionTableModel, context); + + Assertions.assertTrue(dmnResult.getMessages().isEmpty()); + Assertions.assertEquals(BigDecimal.valueOf(30), dmnResult.getDecisionResultByName("Risk Score").getResult()); + evaluationHitIds = dmnResult.getEvaluationHitIds(); + Assertions.assertNotNull(evaluationHitIds); + Assertions.assertEquals(3, evaluationHitIds.size()); + Assertions.assertTrue(evaluationHitIds.contains(elseElementId)); + Assertions.assertTrue(evaluationHitIds.contains(elseRuleId2)); + Assertions.assertTrue(evaluationHitIds.contains(elseRuleId5)); + } + + @Test + void testMultipleHitRulesEvaluation() throws IOException { + final String rule0 = "_E5C380DA-AF7B-4401-9804-C58296EC09DD"; + final String rule1 = "_DFD65E8B-5648-4BFD-840F-8C76B8DDBD1A"; + final String rule2 = "_E80EE7F7-1C0C-4050-B560-F33611F16B05"; + String decisionTableModel = getModelFromIoUtils("valid_models/DMNv1_5/MultipleHitRules.dmn"); + + final List numbers = new ArrayList<>(); + numbers.add(BigDecimal.valueOf(10)); + numbers.add(BigDecimal.valueOf(2)); + numbers.add(BigDecimal.valueOf(1)); + final Map context = new HashMap<>(); + context.put("Numbers", numbers); + final JITDMNResult dmnResult = jitdmnService.evaluateModel(decisionTableModel, context); + + final List expectedStatistcs = new ArrayList<>(); + expectedStatistcs.add(BigDecimal.valueOf(6)); + expectedStatistcs.add(BigDecimal.valueOf(3)); + expectedStatistcs.add(BigDecimal.valueOf(1)); + Assertions.assertTrue(dmnResult.getMessages().isEmpty()); + Assertions.assertEquals(expectedStatistcs, dmnResult.getDecisionResultByName("Statistics").getResult()); + final List evaluationHitIds = dmnResult.getEvaluationHitIds(); + Assertions.assertNotNull(evaluationHitIds); + Assertions.assertEquals(6, evaluationHitIds.size()); + Assertions.assertEquals(3, evaluationHitIds.stream().filter(rule0::equals).count()); + Assertions.assertEquals(2, evaluationHitIds.stream().filter(rule1::equals).count()); + Assertions.assertEquals(1, evaluationHitIds.stream().filter(rule2::equals).count()); + } + + @Test + void testExplainability() throws IOException { String allTypesModel = getModelFromIoUtils("valid_models/DMNv1_x/allTypes.dmn"); Map context = new HashMap<>(); diff --git a/jitexecutor/jitexecutor-dmn/src/test/java/org/kie/kogito/jitexecutor/dmn/api/JITDMNResourceTest.java b/jitexecutor/jitexecutor-dmn/src/test/java/org/kie/kogito/jitexecutor/dmn/api/JITDMNResourceTest.java index d728d112c3..6c29603088 100644 --- a/jitexecutor/jitexecutor-dmn/src/test/java/org/kie/kogito/jitexecutor/dmn/api/JITDMNResourceTest.java +++ b/jitexecutor/jitexecutor-dmn/src/test/java/org/kie/kogito/jitexecutor/dmn/api/JITDMNResourceTest.java @@ -22,10 +22,17 @@ import java.util.HashMap; import java.util.Map; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.kie.kogito.jitexecutor.dmn.requests.JITDMNPayload; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; + import io.quarkus.test.junit.QuarkusTest; import io.restassured.http.ContentType; @@ -38,15 +45,25 @@ public class JITDMNResourceTest { private static String model; private static String modelWithExtensionElements; + private static String modelWithEvaluationHitIds; + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private static final String EVALUATION_HIT_IDS_FIELD_NAME = "evaluationHitIds"; + + static { + MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } @BeforeAll public static void setup() throws IOException { model = getModelFromIoUtils("invalid_models/DMNv1_x/test.dmn"); modelWithExtensionElements = getModelFromIoUtils("valid_models/DMNv1_x/testWithExtensionElements.dmn"); + modelWithEvaluationHitIds = getModelFromIoUtils("valid_models/DMNv1_5/RiskScore_Simple.dmn"); } @Test - public void testjitEndpoint() { + void testjitEndpoint() { JITDMNPayload jitdmnpayload = new JITDMNPayload(model, buildContext()); given() .contentType(ContentType.JSON) @@ -58,7 +75,7 @@ public void testjitEndpoint() { } @Test - public void testjitdmnResultEndpoint() { + void testjitdmnResultEndpoint() { JITDMNPayload jitdmnpayload = new JITDMNPayload(model, buildContext()); given() .contentType(ContentType.JSON) @@ -70,7 +87,34 @@ public void testjitdmnResultEndpoint() { } @Test - public void testjitExplainabilityEndpoint() { + void testjitdmnResultEndpointWithEvaluationHitIds() throws JsonProcessingException { + JITDMNPayload jitdmnpayload = new JITDMNPayload(modelWithEvaluationHitIds, buildRiskScoreContext()); + final String elseElementId = "_2CD02CB2-6B56-45C4-B461-405E89D45633"; + final String ruleId0 = "_1578BD9E-2BF9-4BFC-8956-1A736959C937"; + final String ruleId3 = "_2545E1A8-93D3-4C8A-A0ED-8AD8B10A58F9"; + String response = given().contentType(ContentType.JSON) + .body(jitdmnpayload) + .when().post("/jitdmn/dmnresult") + .then() + .statusCode(200) + .body(containsString("Risk Score"), + containsString("Loan Pre-Qualification"), + containsString(EVALUATION_HIT_IDS_FIELD_NAME), + containsString(elseElementId), + containsString(ruleId0), + containsString(ruleId3)) + .extract() + .asString(); + JsonNode retrieved = MAPPER.readTree(response); + ArrayNode evaluationHitIdsNode = (ArrayNode) retrieved.get(EVALUATION_HIT_IDS_FIELD_NAME); + Assertions.assertThat(evaluationHitIdsNode).hasSize(3) + .anyMatch(node -> node.asText().equals(elseElementId)) + .anyMatch(node -> node.asText().equals(ruleId0)) + .anyMatch(node -> node.asText().equals(ruleId3)); + } + + @Test + void testjitExplainabilityEndpoint() { JITDMNPayload jitdmnpayload = new JITDMNPayload(model, buildContext()); given() .contentType(ContentType.JSON) @@ -78,11 +122,12 @@ public void testjitExplainabilityEndpoint() { .when().post("/jitdmn/evaluateAndExplain") .then() .statusCode(200) - .body(containsString("dmnResult"), containsString("saliencies"), containsString("xls2dmn"), containsString("featureName")); + .body(containsString("dmnResult"), containsString("saliencies"), containsString("xls2dmn"), + containsString("featureName")); } @Test - public void testjitdmnWithExtensionElements() { + void testjitdmnWithExtensionElements() { Map context = new HashMap<>(); context.put("m", 1); context.put("n", 2); @@ -97,6 +142,13 @@ public void testjitdmnWithExtensionElements() { .body(containsString("m"), containsString("n"), containsString("sum")); } + private Map buildRiskScoreContext() { + Map context = new HashMap<>(); + context.put("Credit Score", "Poor"); + context.put("DTI", 33); + return context; + } + private Map buildContext() { Map context = new HashMap<>(); context.put("FICO Score", 800); diff --git a/jitexecutor/jitexecutor-runner/src/main/resources/application.properties b/jitexecutor/jitexecutor-runner/src/main/resources/application.properties index 47e4b5dc93..0dfac610c5 100644 --- a/jitexecutor/jitexecutor-runner/src/main/resources/application.properties +++ b/jitexecutor/jitexecutor-runner/src/main/resources/application.properties @@ -22,6 +22,6 @@ quarkus.http.cors=true quarkus.swagger-ui.always-include=true quarkus.native.additional-build-args=\ - --initialize-at-run-time=com.thoughtworks.xstream.converters.extended.DynamicProxyConverter$Reflections\\,org.kie.dmn.core.compiler.execmodelbased.AbstractModelEvaluator\\,org.kie.dmn.core.pmml.DMNKiePMMLTrustyInvocationEvaluator,\ + --initialize-at-run-time=org.apache.hc.client5.http.impl.auth.NTLMEngineImpl\\,com.thoughtworks.xstream.converters.extended.DynamicProxyConverter$Reflections\\,org.kie.dmn.core.compiler.execmodelbased.AbstractModelEvaluator\\,org.kie.dmn.core.pmml.DMNKiePMMLTrustyInvocationEvaluator,\ -H:IncludeResourceBundles=com.sun.org.apache.xerces.internal.impl.xpath.regex.message,\ --allow-incomplete-classpath \ No newline at end of file