diff --git a/docs/reference.md b/docs/reference.md index 045bfba6..746d2ec5 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -718,6 +718,7 @@ Key | Type | Required | [to](#to) | Map | No | [content](#send-message-content) | String/Object | Yes | [attachments](#attachments) | List | No | +[data](#data) | String | No | Output | Type | ----|----| @@ -871,6 +872,87 @@ encoded urls are accepted. Path to the file to be attached to the message. The path is relative to the workflows folder. +#### data + +A [structured object](https://docs.developers.symphony.com/building-bots-on-symphony/messages/overview-of-messageml/entities/structured-objects) +can be sent as part of a message in this field. It must be a json string. + + +Example: + +```yaml +activities: + - send-message: + id: echo + on: + message-received: + content: /echo + content: + ${text(event.source.message.message)} + data: "{ + \"object001\": + { + \"type\": \"org.symphonyoss.fin.security\", + \"version\": \"1.0\", + \"id\": + [ + { + \"type\": \"org.symphonyoss.fin.security.id.ticker\", + \"value\": \"IBM\" + }, + { + \"type\": \"org.symphonyoss.fin.security.id.isin\", + \"value\": \"US0378331005\" + }, + { + \"type\": \"org.symphonyoss.fin.security.id.cusip\", + \"value\": \"037833100\" + } + ] + } + }" +``` + +One could also use [Utility function](#utility-functions) to escape the data json string, the same example +above can be done like + +Example + +```yaml +variables: + data: { + "object001": + { + "type": "org.symphonyoss.fin.security", + "version": "1.0", + "id": + [ + { + "type": "org.symphonyoss.fin.security.id.ticker", + "value": "IBM" + }, + { + "type": "org.symphonyoss.fin.security.id.isin", + "value": "US0378331005" + }, + { + "type": "org.symphonyoss.fin.security.id.cusip", + "value": "037833100" + } + ] + } + } +activities: + - send-message: + id: echo + on: + message-received: + content: /echo + content: + ${text(event.source.message.message)} + data: ${escape(variables.data)} +``` + ### update-message Update an existing message into a stream. Returns the new updated message. diff --git a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/api/v1/dto/WorkflowActivitiesView.java b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/api/v1/dto/WorkflowActivitiesView.java index 3024dd65..7351f490 100644 --- a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/api/v1/dto/WorkflowActivitiesView.java +++ b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/api/v1/dto/WorkflowActivitiesView.java @@ -4,10 +4,12 @@ import lombok.Data; import java.util.List; +import java.util.Map; @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class WorkflowActivitiesView { private List activities; private VariableView globalVariables; + private Map error; } diff --git a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/api/v1/dto/WorkflowDefinitionView.java b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/api/v1/dto/WorkflowDefinitionView.java index 08127c3f..5c4dd9d8 100644 --- a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/api/v1/dto/WorkflowDefinitionView.java +++ b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/api/v1/dto/WorkflowDefinitionView.java @@ -10,6 +10,6 @@ @Builder public class WorkflowDefinitionView { private String workflowId; - private List> variables; + private Map variables; private List flowNodes; } diff --git a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/WorkflowDirectGraph.java b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/WorkflowDirectGraph.java index 2e1e49b1..985779d7 100644 --- a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/WorkflowDirectGraph.java +++ b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/WorkflowDirectGraph.java @@ -9,6 +9,7 @@ import org.springframework.cache.annotation.Cacheable; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -44,6 +45,9 @@ public class WorkflowDirectGraph { @Getter private final List startEvents = new ArrayList<>(); + @Getter + private final Map variables = new HashMap<>(); + public void addParent(String id, String parent) { parents.computeIfAbsent(id, k -> new HashSet<>()).add(parent); } diff --git a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/WorkflowDirectGraphBuilder.java b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/WorkflowDirectGraphBuilder.java index a37d33d8..3fdf077b 100644 --- a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/WorkflowDirectGraphBuilder.java +++ b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/WorkflowDirectGraphBuilder.java @@ -55,6 +55,7 @@ public WorkflowDirectGraph build() { String activityId = computeParallelJoinGateway(directGraph, activity); computeEvents(i, activityId, activities, directGraph); } + directGraph.getVariables().putAll(workflow.getVariables()); return directGraph; } diff --git a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/camunda/CamundaExecutor.java b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/camunda/CamundaExecutor.java index 4cf137af..5fce766f 100644 --- a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/camunda/CamundaExecutor.java +++ b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/camunda/CamundaExecutor.java @@ -106,7 +106,8 @@ public void execute(DelegateExecution execution) throws Exception { // escape break line and new lin characters String activityAsJsonString = ((String) execution.getVariable(ACTIVITY)).replaceAll("(\\r|\\n|\\r\\n)+", "\\\\n"); - Object activity = OBJECT_MAPPER.readValue(activityAsJsonString, Class.forName(type.getTypeName())); + BaseActivity activity = + (BaseActivity) OBJECT_MAPPER.readValue(activityAsJsonString, Class.forName(type.getTypeName())); EventHolder event = (EventHolder) execution.getVariable(ActivityExecutorContext.EVENT); @@ -114,16 +115,29 @@ public void execute(DelegateExecution execution) throws Exception { setMdc(execution); auditTrailLogger.execute(execution, activity.getClass().getSimpleName()); executor.execute( - new CamundaActivityExecutorContext(execution, (BaseActivity) activity, event, resourceLoader, bdk)); + new CamundaActivityExecutorContext(execution, activity, event, resourceLoader, bdk)); } catch (Exception e) { - log.error(String.format("Activity %s from workflow %s failed", - execution.getActivityInstanceId(), execution.getProcessInstanceId()), e); + log.error(String.format("Activity from workflow %s failed", execution.getProcessDefinitionId()), e); + logErrorVariables(execution, activity, e); throw new BpmnError("FAILURE", e); } finally { clearMdc(); } } + private static void logErrorVariables(DelegateExecution execution, BaseActivity activity, Exception e) { + Map innerMap = new HashMap<>(); + innerMap.put("message", e.getCause() == null ? e.getMessage() : e.getCause().getMessage()); + innerMap.put("activityInstId", execution.getActivityInstanceId()); + innerMap.put("activityId", activity.getId()); + ObjectValue objectValue = Variables.objectValue(innerMap) + .serializationDataFormat(Variables.SerializationDataFormats.JSON) + .create(); + execution.getProcessEngineServices() + .getRuntimeService() + .setVariable(execution.getId(), ActivityExecutorContext.ERROR, objectValue); + } + private void setMdc(DelegateExecution execution) { MDC.put(MDC_PROCESS_ID, execution.getProcessInstanceId()); MDC.put(MDC_ACTIVITY_ID, execution.getActivityInstanceId()); diff --git a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/camunda/monitoring/repository/VariableCmdaApiQueryRepository.java b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/camunda/monitoring/repository/VariableCmdaApiQueryRepository.java index 988c179b..84e62000 100644 --- a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/camunda/monitoring/repository/VariableCmdaApiQueryRepository.java +++ b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/camunda/monitoring/repository/VariableCmdaApiQueryRepository.java @@ -28,11 +28,11 @@ public VariableCmdaApiQueryRepository(RepositoryService repositoryService, } @Override - public VariablesDomain findGlobalVarsByWorkflowInstanceId(String id) { + public VariablesDomain findVarsByWorkflowInstanceIdAndVarName(String id, String name) { HistoricVariableInstanceEntity variables = (HistoricVariableInstanceEntity) historyService.createHistoricVariableInstanceQuery() .processInstanceId(id) - .variableName("variables") + .variableName(name) .singleResult(); VariablesDomain variablesDomain = new VariablesDomain(); diff --git a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/executor/message/SendMessageExecutor.java b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/executor/message/SendMessageExecutor.java index 32226148..446ea634 100644 --- a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/executor/message/SendMessageExecutor.java +++ b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/engine/executor/message/SendMessageExecutor.java @@ -21,6 +21,7 @@ import com.symphony.bdk.workflow.swadl.v1.activity.message.SendMessage; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import java.io.ByteArrayInputStream; @@ -141,6 +142,9 @@ private String createOrGetStreamId(List userIds, StreamService streamServi private Message buildMessage(ActivityExecutorContext execution) throws IOException { Message.MessageBuilder builder = Message.builder().content(extractContent(execution)); + if (StringUtils.isNotBlank(execution.getActivity().getData())) { + builder.data(execution.getActivity().getData()); + } if (execution.getActivity().getAttachments() != null) { for (SendMessage.Attachment attachment : execution.getActivity().getAttachments()) { this.handleFileAttachment(builder, attachment, execution); diff --git a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/monitoring/repository/VariableQueryRepository.java b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/monitoring/repository/VariableQueryRepository.java index 4b86322f..b3481f84 100644 --- a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/monitoring/repository/VariableQueryRepository.java +++ b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/monitoring/repository/VariableQueryRepository.java @@ -6,7 +6,7 @@ import java.util.List; public interface VariableQueryRepository extends QueryRepository { - VariablesDomain findGlobalVarsByWorkflowInstanceId(String id); + VariablesDomain findVarsByWorkflowInstanceIdAndVarName(String id, String name); List findGlobalVarsHistoryByWorkflowInstId(String id, Instant occurredBefore, Instant occurredAfter); } diff --git a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/monitoring/service/MonitoringService.java b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/monitoring/service/MonitoringService.java index 3f756f02..b860019d 100644 --- a/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/monitoring/service/MonitoringService.java +++ b/workflow-bot-app/src/main/java/com/symphony/bdk/workflow/monitoring/service/MonitoringService.java @@ -14,6 +14,7 @@ import com.symphony.bdk.workflow.engine.WorkflowDirectGraph; import com.symphony.bdk.workflow.engine.WorkflowNode; import com.symphony.bdk.workflow.engine.camunda.WorkflowDirectGraphCachingService; +import com.symphony.bdk.workflow.engine.executor.ActivityExecutorContext; import com.symphony.bdk.workflow.monitoring.repository.ActivityQueryRepository; import com.symphony.bdk.workflow.monitoring.repository.VariableQueryRepository; import com.symphony.bdk.workflow.monitoring.repository.WorkflowInstQueryRepository; @@ -27,7 +28,6 @@ import java.time.Instant; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; @@ -86,19 +86,20 @@ public WorkflowActivitiesView listWorkflowInstanceActivities(String workflowId, activity -> activity.setType(TaskTypeEnum.findByAbbr(activityIdToTypeMap.get(activity.getActivityId())))); } - VariablesDomain globalVariables = this.variableQueryRepository.findGlobalVarsByWorkflowInstanceId(instanceId); + VariablesDomain globalVariables = this.variableQueryRepository.findVarsByWorkflowInstanceIdAndVarName(instanceId, + ActivityExecutorContext.VARIABLES); + VariablesDomain error = + this.variableQueryRepository.findVarsByWorkflowInstanceIdAndVarName(instanceId, ActivityExecutorContext.ERROR); WorkflowActivitiesView result = new WorkflowActivitiesView(); result.setActivities(activities); result.setGlobalVariables(new VariableView(globalVariables)); + if (!error.getOutputs().isEmpty()) { + result.setError(error.getOutputs()); + } return result; } public WorkflowDefinitionView getWorkflowDefinition(String workflowId) { - WorkflowDefinitionView.WorkflowDefinitionViewBuilder builder = WorkflowDefinitionView.builder() - .workflowId(workflowId) - .flowNodes(new ArrayList<>()) - .variables(Collections.emptyList()); - WorkflowDirectGraph directGraph = this.workflowDirectGraphCachingService.getDirectGraph(workflowId); ArrayList activities = new ArrayList<>(); @@ -127,9 +128,10 @@ public WorkflowDefinitionView getWorkflowDefinition(String workflowId) { activities.add(taskDefinitionViewBuilder.build()); }); - - builder.flowNodes(activities); - return builder.build(); + return WorkflowDefinitionView.builder() + .workflowId(workflowId) + .flowNodes(activities) + .variables(directGraph.getVariables()).build(); } public List listWorkflowInstanceGlobalVars(String workflowId, String instanceId, Instant updatedBefore, diff --git a/workflow-bot-app/src/main/resources/application-local.yaml b/workflow-bot-app/src/main/resources/application-local.yaml index 1329c555..e9e8b288 100644 --- a/workflow-bot-app/src/main/resources/application-local.yaml +++ b/workflow-bot-app/src/main/resources/application-local.yaml @@ -3,15 +3,19 @@ wdk: workflows: path: ./workflows properties: - monitoring-token: ${wdk.monitoring.token:} # The default value is an empty String + monitoring-token: token # The default value is an empty String # BDK configuration for local development bdk: - host: xxx + host: develop.symphony.com + app: + appId: appsoufiane + privateKey: + path: /Users/yinan.liu/Projects/certs/appSoufianneprivatekey.pem bot: - username: xxx + username: wdkbot privateKey: - path: xxx + path: /Users/yinan.liu/Projects/certs/privatekey.pem logging: level: diff --git a/workflow-bot-app/src/main/resources/application.yaml b/workflow-bot-app/src/main/resources/application.yaml index b5d22ec9..dd59992f 100644 --- a/workflow-bot-app/src/main/resources/application.yaml +++ b/workflow-bot-app/src/main/resources/application.yaml @@ -39,7 +39,7 @@ spring: username: sa password: driver-class-name: org.h2.Driver - url: jdbc:h2:mem:process_engine;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + url: jdbc:h2:file:~/.data/process_engine;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;AUTO_SERVER=TRUE mvc: pathmatch: matching-strategy: ant_path_matcher diff --git a/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/ErrorIntegrationTest.java b/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/ErrorIntegrationTest.java index c550909e..49012a86 100644 --- a/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/ErrorIntegrationTest.java +++ b/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/ErrorIntegrationTest.java @@ -5,6 +5,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -13,8 +15,11 @@ import com.symphony.bdk.workflow.swadl.SwadlParser; import com.symphony.bdk.workflow.swadl.v1.Workflow; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; +import java.util.Map; + class ErrorIntegrationTest extends IntegrationTest { @Test @@ -138,4 +143,22 @@ void onActivityFailedRetry() throws Exception { assertThat(workflow).executed("fallback", "failing"); } + @Test + void sendMessageOnMessage_withException_errorVarNotEmpty() throws Exception { + final Workflow workflow = + SwadlParser.fromYaml(getClass().getResourceAsStream("/message/send-message-on-message.swadl.yaml")); + + RuntimeException toBeThrown = mock(RuntimeException.class); + when(toBeThrown.getMessage()).thenReturn("Exception"); + doThrow(toBeThrown).when(messageService).send(anyString(), any(Message.class)); + + engine.deploy(workflow); + engine.onEvent(messageReceived("/message")); + + sleepToTimeout(500); + Map errors = getVariable(lastProcess().get(), "error"); + Assertions.assertThat(errors.get("message")).isEqualTo("Exception"); + Assertions.assertThat(errors.get("activityId")).isEqualTo("sendMessage1"); + } + } diff --git a/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/IntegrationTest.java b/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/IntegrationTest.java index a583748c..c53c76ea 100644 --- a/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/IntegrationTest.java +++ b/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/IntegrationTest.java @@ -45,6 +45,7 @@ import org.camunda.bpm.engine.RepositoryService; import org.camunda.bpm.engine.history.HistoricActivityInstance; import org.camunda.bpm.engine.history.HistoricProcessInstance; +import org.camunda.bpm.engine.history.HistoricVariableInstance; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; @@ -58,6 +59,7 @@ import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -265,6 +267,18 @@ public static Boolean processIsCompleted(String processId) { return false; } + public static Map getVariable(String processId, String name) { + HistoricVariableInstance var = historyService.createHistoricVariableInstanceQuery() + .processInstanceId(processId) + .variableName(name) + .singleResult(); + if (var == null) { + return Collections.emptyMap(); + } else { + return new HashMap<>((Map) var.getValue()); + } + } + public static Optional lastProcess() { List processes = historyService.createHistoricProcessInstanceQuery() .orderByProcessInstanceStartTime().desc() diff --git a/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/MonitoringApiIntegrationTest.java b/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/MonitoringApiIntegrationTest.java index 14770378..3c160138 100644 --- a/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/MonitoringApiIntegrationTest.java +++ b/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/MonitoringApiIntegrationTest.java @@ -2,6 +2,7 @@ import static io.restassured.RestAssured.given; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.MAP; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; @@ -11,6 +12,7 @@ import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.when; import com.symphony.bdk.core.service.message.model.Message; @@ -405,6 +407,51 @@ void listInstanceActivities() throws Exception { engine.undeploy(workflow.getId()); } + @Test + void listInstanceActivities_withError() throws Exception { + final Workflow workflow = + SwadlParser.fromYaml(getClass().getResourceAsStream("/monitoring/testing-workflow-1.swadl.yaml")); + + when(messageService.send(anyString(), any(Message.class))).thenThrow(new RuntimeException("Unauthorized")); + + engine.undeploy(workflow.getId()); // clean any old running instance + engine.deploy(workflow); + engine.onEvent(messageReceived("/testingWorkflow1")); + + // Wait for the workflow to get executed + Thread.sleep(2000); + + String processDefinition = this.getLastProcessInstanceId("testingWorkflow1"); + + given() + .header(X_MONITORING_TOKEN_HEADER_KEY, X_MONITORING_TOKEN_HEADER_VALUE) + .contentType(ContentType.JSON) + .when() + .get(String.format(LIST_WORKFLOW_INSTANCE_ACTIVITIES_PATH, "testingWorkflow1", processDefinition)) + .then() + .assertThat() + .statusCode(HttpStatus.OK.value()) + + .body("activities[0].workflowId", equalTo("testingWorkflow1")) + .body("activities[0].instanceId", not(empty())) + .body("activities[0].activityId", equalTo("testingWorkflow1SendMsg1")) + .body("activities[0].type", equalTo("SEND_MESSAGE_ACTIVITY")) + .body("activities[0].startDate", not(empty())) + .body("activities[0].endDate", not(empty())) + .body("activities[0].duration", not(empty())) + .body("activities[0].outputs.message", not(empty())) + .body("activities[0].outputs.msgId", not(empty())) + + .body("globalVariables.outputs", equalTo(Collections.EMPTY_MAP)) + .body("globalVariables.revision", equalTo(0)) + .body("globalVariables.updateTime", not(empty())) + .body("error.activityId",equalTo("testingWorkflow1SendMsg1")) + .body("error.message",equalTo("Unauthorized")) + .body("error.activityInstId",not(empty())); + + engine.undeploy(workflow.getId()); + } + @Test void listInstanceActivities_startedBeforeFilter() throws Exception { final Workflow workflow = @@ -842,7 +889,7 @@ void listWorkflowActivitiesDefinitions() throws Exception { assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); assertThat(response.body().jsonPath().getString("workflowId")).isEqualTo("testingWorkflow1"); - assertThat(response.body().jsonPath().getList("variables")).isEmpty(); + assertThat(response.body().jsonPath().getMap("variables")).isEmpty(); assertThat(taskDefinitionViews) .hasSameSizeAs(expectedTaskDefinitions) diff --git a/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/SendMessageIntegrationTest.java b/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/SendMessageIntegrationTest.java index c294e73f..1c274dfa 100644 --- a/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/SendMessageIntegrationTest.java +++ b/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/SendMessageIntegrationTest.java @@ -54,7 +54,10 @@ void sendMessageOnMessage() throws Exception { engine.deploy(workflow); engine.onEvent(messageReceived("/message")); - verify(messageService, timeout(5000)).send(anyString(), any(Message.class)); + ArgumentCaptor captor = ArgumentCaptor.forClass(Message.class); + verify(messageService, timeout(5000)).send(anyString(), captor.capture()); + Message sent = captor.getValue(); + assertThat(sent.getData()).contains("id", "123456"); assertThat(workflow).isExecuted() .hasOutput(String.format(OUTPUTS_MSG_KEY, "sendMessage1"), message) diff --git a/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/api/v1/WorkflowsApiControllerTest.java b/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/api/v1/WorkflowsApiControllerTest.java index 139d0c7a..c1557018 100644 --- a/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/api/v1/WorkflowsApiControllerTest.java +++ b/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/api/v1/WorkflowsApiControllerTest.java @@ -278,7 +278,7 @@ void getWorkflowDefinitions() throws Exception { WorkflowDefinitionView workflowDefinitionView = WorkflowDefinitionView.builder() .workflowId(workflowId) - .variables(Collections.emptyList()) + .variables(Collections.emptyMap()) .flowNodes(Arrays.asList(activity0, event, activity1)) .build(); diff --git a/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/engine/WorkflowDirectGraphBuilderTest.java b/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/engine/WorkflowDirectGraphBuilderTest.java index f4ace3f1..18b72f19 100644 --- a/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/engine/WorkflowDirectGraphBuilderTest.java +++ b/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/engine/WorkflowDirectGraphBuilderTest.java @@ -55,6 +55,8 @@ void buildWorkflowDirectGraph_connectionFlow() throws Exception { WorkflowDirectGraph directGraph = workflowDirectGraphBuilder.build(); assertThat(directGraph.getDictionary()).hasSize(8); assertThat(directGraph.getStartEvents()).hasSize(1); + assertThat(directGraph.getVariables()).hasSize(1); + assertThat(directGraph.getVariables()).containsKey("administrator"); } @Test @@ -66,5 +68,6 @@ void buildWorkflowDirectGraph_allOfFlow() throws Exception { assertThat(directGraph.getDictionary()).hasSize(8); assertThat(directGraph.readChildren("scriptTrue").getGateway()).isEqualTo(WorkflowDirectGraph.Gateway.PARALLEL); assertThat(directGraph.getStartEvents()).hasSize(1); + assertThat(directGraph.getVariables()).containsKey("allOf"); } } diff --git a/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/engine/camunda/monitoring/repository/VariableCmdaApiQueryRepositoryTest.java b/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/engine/camunda/monitoring/repository/VariableCmdaApiQueryRepositoryTest.java index 19ac0eb8..81d16296 100644 --- a/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/engine/camunda/monitoring/repository/VariableCmdaApiQueryRepositoryTest.java +++ b/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/engine/camunda/monitoring/repository/VariableCmdaApiQueryRepositoryTest.java @@ -39,7 +39,7 @@ void findGlobalByWorkflowInstanceId() { when(variables.getRevision()).thenReturn(1); when(variables.getCreateTime()).thenReturn(new Date()); // when - VariablesDomain global = queryRepository.findGlobalVarsByWorkflowInstanceId("id"); + VariablesDomain global = queryRepository.findVarsByWorkflowInstanceIdAndVarName("id", "variables"); // then assertThat(global.getOutputs()).hasSize(1); assertThat(global.getOutputs().get("key")).isEqualTo("value"); diff --git a/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/monitoring/service/MonitoringServiceTest.java b/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/monitoring/service/MonitoringServiceTest.java index f8c84912..99360473 100644 --- a/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/monitoring/service/MonitoringServiceTest.java +++ b/workflow-bot-app/src/test/java/com/symphony/bdk/workflow/monitoring/service/MonitoringServiceTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.BDDAssertions.then; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; @@ -32,11 +31,11 @@ import com.symphony.bdk.workflow.swadl.v1.activity.message.SendMessage; import org.assertj.core.util.Maps; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -63,10 +62,6 @@ class MonitoringServiceTest { @InjectMocks MonitoringService service; - @BeforeEach - void setUp() { - } - @Test void listAllWorkflows() { when(workflowQueryRepository.findAll()).thenReturn(Collections.emptyList()); @@ -74,7 +69,7 @@ void listAllWorkflows() { // when List workflowViews = service.listAllWorkflows(); //then - then(workflowViews).isEmpty(); + assertThat(workflowViews).isEmpty(); } @Test @@ -84,7 +79,7 @@ void listWorkflowInstances() { // when List workflowViews = service.listWorkflowInstances("id", null); //then - then(workflowViews).isEmpty(); + assertThat(workflowViews).isEmpty(); } @ParameterizedTest @@ -96,7 +91,7 @@ void listWorkflowInstances_completedFilter(String status) { // when List workflowViews = service.listWorkflowInstances("id", status); //then - then(workflowViews).isEmpty(); + assertThat(workflowViews).isEmpty(); } @Test @@ -108,8 +103,9 @@ void listWorkflowInstancesBadStatus() { "Workflow instance status bad_status is not known. Allowed values [Completed, Pending, Failed]")); } - @Test - void listWorkflowInstanceActivities() { + @ParameterizedTest + @ValueSource(strings = {"", "errors"}) + void listWorkflowInstanceActivities(String errors) { // given ActivityInstanceView view1 = ActivityInstanceView.builder() .instanceId("instance") @@ -167,22 +163,37 @@ void listWorkflowInstanceActivities() { vars.setRevision(2); vars.setUpdateTime(Instant.now()); vars.setOutputs(Maps.newHashMap("key", "value")); - when(variableQueryRepository.findGlobalVarsByWorkflowInstanceId(anyString())).thenReturn(vars); + when(variableQueryRepository.findVarsByWorkflowInstanceIdAndVarName(anyString(), eq("variables"))).thenReturn(vars); + if ("errors".equals(errors)) { + VariablesDomain err = new VariablesDomain(); + err.setRevision(0); + err.setUpdateTime(Instant.now()); + err.setOutputs(Maps.newHashMap("error", "exception")); + when(variableQueryRepository.findVarsByWorkflowInstanceIdAndVarName(anyString(), eq("error"))).thenReturn(err); + } else { + when(variableQueryRepository.findVarsByWorkflowInstanceIdAndVarName(anyString(), eq("error"))).thenReturn( + new VariablesDomain()); + } // when WorkflowActivitiesView workflowInstanceActivities = service.listWorkflowInstanceActivities("workflow", "instance", new WorkflowInstLifeCycleFilter(null, null, null, null)); // then - then(workflowInstanceActivities.getActivities()).hasSize(2); - then(workflowInstanceActivities.getActivities().get(0).getOutputs()).hasSize(1); - then(workflowInstanceActivities.getActivities().get(0).getWorkflowId()).isEqualTo("workflow"); - then(workflowInstanceActivities.getActivities().get(0).getInstanceId()).isEqualTo("instance"); - then(workflowInstanceActivities.getActivities().get(0).getActivityId()).isEqualTo("activity1"); - then(workflowInstanceActivities.getActivities().get(0).getStartDate()).isNotNull(); - then(workflowInstanceActivities.getActivities().get(0).getEndDate()).isNotNull(); - then(workflowInstanceActivities.getGlobalVariables().getRevision()).isEqualTo(2); - then(workflowInstanceActivities.getGlobalVariables().getUpdateTime()).isNotNull(); + assertThat(workflowInstanceActivities.getActivities()).hasSize(2); + assertThat(workflowInstanceActivities.getActivities().get(0).getOutputs()).hasSize(1); + assertThat(workflowInstanceActivities.getActivities().get(0).getWorkflowId()).isEqualTo("workflow"); + assertThat(workflowInstanceActivities.getActivities().get(0).getInstanceId()).isEqualTo("instance"); + assertThat(workflowInstanceActivities.getActivities().get(0).getActivityId()).isEqualTo("activity1"); + assertThat(workflowInstanceActivities.getActivities().get(0).getStartDate()).isNotNull(); + assertThat(workflowInstanceActivities.getActivities().get(0).getEndDate()).isNotNull(); + assertThat(workflowInstanceActivities.getGlobalVariables().getRevision()).isEqualTo(2); + assertThat(workflowInstanceActivities.getGlobalVariables().getUpdateTime()).isNotNull(); + if ("errors".equals(errors)) { + assertThat(workflowInstanceActivities.getError()).hasSize(1); + } else { + assertThat(workflowInstanceActivities.getError()).isNull(); + } } @Test @@ -220,6 +231,7 @@ void getWorkflowDefinition() { directGraph.registerToDictionary("activity2", activity2); directGraph.addParent("activity2", "activity1"); directGraph.getChildren("activity1").addChild("activity2"); + directGraph.getVariables().put("variable", "value"); when(workflowDirectGraphCachingService.getDirectGraph("workflow")).thenReturn(directGraph); @@ -227,11 +239,13 @@ void getWorkflowDefinition() { WorkflowDefinitionView definitionView = service.getWorkflowDefinition("workflow"); // then - then(definitionView.getWorkflowId()).isEqualTo("workflow"); - then(definitionView.getFlowNodes()).hasSize(2); - then(definitionView.getFlowNodes().get(0).getChildren()).hasSize(1); - then(definitionView.getFlowNodes().get(1).getParents()).hasSize(1); - then(definitionView.getFlowNodes().get(0).getType()).isEqualTo(TaskTypeEnum.SEND_MESSAGE_ACTIVITY); + assertThat(definitionView.getWorkflowId()).isEqualTo("workflow"); + assertThat(definitionView.getVariables()).hasSize(1); + assertThat(definitionView.getVariables()).containsKey("variable"); + assertThat(definitionView.getFlowNodes()).hasSize(2); + assertThat(definitionView.getFlowNodes().get(0).getChildren()).hasSize(1); + assertThat(definitionView.getFlowNodes().get(1).getParents()).hasSize(1); + assertThat(definitionView.getFlowNodes().get(0).getType()).isEqualTo(TaskTypeEnum.SEND_MESSAGE_ACTIVITY); } @Test @@ -255,9 +269,9 @@ void listWorkflowInstanceGlobalVars() { List variableViews = service.listWorkflowInstanceGlobalVars("workflow", "instance", null, null); // then - then(variableViews).hasSize(1); - then(variableViews.get(0).getUpdateTime()).isNotNull(); - then(variableViews.get(0).getRevision()).isEqualTo(1); - then(variableViews.get(0).getOutputs()).hasSize(1); + assertThat(variableViews).hasSize(1); + assertThat(variableViews.get(0).getUpdateTime()).isNotNull(); + assertThat(variableViews.get(0).getRevision()).isEqualTo(1); + assertThat(variableViews.get(0).getOutputs()).hasSize(1); } } diff --git a/workflow-bot-app/src/test/resources/message/send-message-on-message.swadl.yaml b/workflow-bot-app/src/test/resources/message/send-message-on-message.swadl.yaml index 69b3529f..30c5985b 100644 --- a/workflow-bot-app/src/test/resources/message/send-message-on-message.swadl.yaml +++ b/workflow-bot-app/src/test/resources/message/send-message-on-message.swadl.yaml @@ -8,3 +8,4 @@ activities: to: stream-id: "123" content: Hello! + data: "{ \"id\": \"123456\"}" diff --git a/workflow-language/src/main/java/com/symphony/bdk/workflow/engine/executor/ActivityExecutorContext.java b/workflow-language/src/main/java/com/symphony/bdk/workflow/engine/executor/ActivityExecutorContext.java index 2a7a462b..21f9a248 100644 --- a/workflow-language/src/main/java/com/symphony/bdk/workflow/engine/executor/ActivityExecutorContext.java +++ b/workflow-language/src/main/java/com/symphony/bdk/workflow/engine/executor/ActivityExecutorContext.java @@ -13,6 +13,8 @@ public interface ActivityExecutorContext { */ String OUTPUTS = "outputs"; + String ERROR = "error"; + /** * ${variables.myVariable} */ diff --git a/workflow-language/src/main/java/com/symphony/bdk/workflow/swadl/v1/activity/message/SendMessage.java b/workflow-language/src/main/java/com/symphony/bdk/workflow/swadl/v1/activity/message/SendMessage.java index 4bcd10b1..0dc99d2b 100644 --- a/workflow-language/src/main/java/com/symphony/bdk/workflow/swadl/v1/activity/message/SendMessage.java +++ b/workflow-language/src/main/java/com/symphony/bdk/workflow/swadl/v1/activity/message/SendMessage.java @@ -17,6 +17,7 @@ public class SendMessage extends OboActivity { @Nullable private String content; @Nullable private To to; @Nullable private List attachments; + @Nullable private String data; @SuppressWarnings("unchecked") public void setContent(Object content) { diff --git a/workflow-language/src/main/resources/swadl-schema-1.0.json b/workflow-language/src/main/resources/swadl-schema-1.0.json index 4272466f..4ec2bb79 100644 --- a/workflow-language/src/main/resources/swadl-schema-1.0.json +++ b/workflow-language/src/main/resources/swadl-schema-1.0.json @@ -1701,6 +1701,10 @@ "content": { "$ref": "#/definitions/content-inner" }, + "data": { + "description": "Message data, which is a Json string and sent along with message.", + "type": "string" + }, "attachments": { "description": "One or more attachments to be sent along with the message.", "type": "array",