Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 206: Conditional execution in saga #222

Merged
merged 3 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/super-linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ jobs:
VALIDATE_JAVASCRIPT_ES: true
VALIDATE_JAVASCRIPT_STANDARD: true
VALIDATE_MARKDOWN: true
FILTER_REGEX_EXCLUDE: .*test/.*.js
FILTER_REGEX_EXCLUDE: .*test/.*.js|e2e/.*\.js
Michaelpalacce marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
### Enhancements
* [docs] IAC-800 - Document TS Array functions behaviour and recommended typization approach.
* [vropkg] IAC-793 / Support for vRO custom interaction forms in the vRO package during parsing and serializing.
* [typescript] 206 / Conditional Execution in Saga.

### Fixes
* [artifact-manager] IAC-779 / Install vro package fails with 404 not found in case vro_server=vro-l-01a is used not FQDN
Expand Down
15 changes: 15 additions & 0 deletions docs/versions/latest/Release.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@ SQLDatabaseManager.getDatabase() function is removed in vRA 7.6 / Aria Automatio
[//]: # (Optional But higlhy recommended Specify *NONE* if missing)
[//]: # (#### Relevant Documentation:)

### Conditional execution in saga

This feature enables the conditional execution of tasks/workflows based on a conditional variable (saga state value).

#### Relevant Documentation
```yaml
tasks:
TestOne:
execute: testSagaTask
if: conditionalVariable # Newly introduced variable
TestTwo:
workflow: workflowId
if: conditionalVariable # Newly introduced variable
```

[//]: # (Improvements -> Bugfixes/hotfixes or general improvements)
## Improvements

Expand Down
7 changes: 7 additions & 0 deletions typescript/vrotsc/e2e/cases/saga/tasks/testSagaContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface Context {
conditionalVar?: boolean;
}

export default function (context: Context) {
context.conditionalVar = true;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
id: 3669e-e7ea-4486-8b91-e1e36ae7ce9
name : Test Conditional Execution Saga
path: sagas/saga-test-conditional-execution
imports:
- saga.test.saga.tasks
attributes:
conditionalVar:
type: boolean

tasks:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to add test for workflows as well, ideally separately, so we can figure out where the error is if it happens faster

Initialize Context:
execute: testSagaContext
Print First Saga:
execute: firstSaga
if: conditionalVar
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* @return {Any}
*/
(function () {
var exports = {};
exports.default = function (context) {
context.conditionalVar = true;
};
return exports;
});

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion typescript/vrotsc/e2e/expect/saga/types/tasks/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as rollbackFirstSaga from "./rollbackFirstSaga";
import * as rollbackSecondSaga from "./rollbackSecondSaga";
import * as rollbackThirdSaga from "./rollbackThirdSaga";
import * as secondSaga from "./secondSaga";
import * as testSagaContext from "./testSagaContext";
import * as testSagaTask from "./testSagaTask";
import * as thirdSaga from "./thirdSaga";
export { firstSaga, rollbackFirstSaga, rollbackSecondSaga, rollbackThirdSaga, secondSaga, testSagaTask, thirdSaga };
export { firstSaga, rollbackFirstSaga, rollbackSecondSaga, rollbackThirdSaga, secondSaga, testSagaContext, testSagaTask, thirdSaga };
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface Context {
conditionalVar?: boolean;
}
export default function (context: Context): void;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>Generated by vrotsc</comment>
<entry key="categoryPath">sagas.saga-test-conditional-execution</entry>
<entry key="name">Test Conditional Execution Saga</entry>
<entry key="type">Workflow</entry>
<entry key="id">3669e-e7ea-4486-8b91-e1e36ae7ce9</entry>
</properties>
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?xml version='1.0' encoding='UTF-8'?><workflow xmlns="http://vmware.com/vco/workflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://vmware.com/vco/workflow http://vmware.com/vco/workflow/Workflow-v4.xsd" root-name="item7" object-name="workflow:name=generic" id="3669e-e7ea-4486-8b91-e1e36ae7ce9" version="1.0.0" api-version="6.0.0" allowed-operations="evf" restartMode="1" resumeFromFailedMode="0"><display-name><![CDATA[Test Conditional Execution Saga]]></display-name><position y="45.90909090909091" x="105.0"/><input><param name="__sagaIn" type="Any"/><param name="__sagaRoll" type="boolean"/></input><output><param name="__sagaOut" type="Any"/></output><attrib name="__sagaState" type="Any" read-only="false"><value encoded="n"><![CDATA[__NULL__]]></value></attrib><attrib name="__sagaError" type="string" read-only="false"><value encoded="n"><![CDATA[]]></value></attrib><workflow-item name="item0" type="end" end-mode="0"><position y="45.40909090909091" x="1065.0"/></workflow-item><workflow-item name="item1" type="end" end-mode="0"><position y="190.86363636363635" x="265.0"/></workflow-item><workflow-item name="item2" throw-bind-name="__sagaError" type="end" end-mode="1"><position y="118.13636363636363" x="105.0"/></workflow-item><workflow-item name="item3" out-name="item0" type="task"><display-name><![CDATA[Finish]]></display-name><script encoded="false"><![CDATA[function getValue(ognl) {
return ognl.split(".").reduce(function(obj,name) { return (obj || {})[name] }, __sagaState);
}
__sagaOut = __sagaState;
]]></script><in-binding><bind name="__sagaState" type="Any" export-name="__sagaState"/></in-binding><out-binding><bind name="__sagaOut" type="Any" export-name="__sagaOut"/></out-binding><position y="55.40909090909091" x="865.0"/></workflow-item><workflow-item name="item4" out-name="item2" type="custom-condition" alt-out-name="item1"><display-name><![CDATA[Is Not Rollback?]]></display-name><script encoded="false"><![CDATA[return !__sagaRoll;]]></script><in-binding><bind name="__sagaRoll" type="boolean" export-name="__sagaRoll"/></in-binding><position y="118.13636363636363" x="225.0"/></workflow-item><workflow-item name="item5" out-name="item4" type="task"><display-name><![CDATA[Finish rollback]]></display-name><script encoded="false"><![CDATA[__sagaOut = __sagaState;]]></script><in-binding><bind name="__sagaState" type="Any" export-name="__sagaState"/></in-binding><out-binding><bind name="__sagaOut" type="Any" export-name="__sagaOut"/></out-binding><position y="128.13636363636363" x="385.0"/></workflow-item><workflow-item name="item6" out-name="item8" type="custom-condition" alt-out-name="item11"><display-name><![CDATA[Is Not Rollback?]]></display-name><script encoded="false"><![CDATA[return !__sagaRoll;]]></script><in-binding><bind name="__sagaRoll" type="boolean" export-name="__sagaRoll"/></in-binding><position y="45.40909090909091" x="385.0"/></workflow-item><workflow-item name="item7" out-name="item6" type="task"><display-name><![CDATA[Start]]></display-name><script encoded="false"><![CDATA[function notInternalName(name) {
return name.indexOf("__saga") !== 0;
}
__sagaState = __sagaIn || {};
var systemContext = System.getContext();
if (systemContext) {
(systemContext.parameterNames() || []).filter(notInternalName).forEach(function (name) { __sagaState[name] = systemContext.getParameter(name); });
}
if (workflow) {
var attributes = workflow.getAttributes();
(attributes.keys || []).filter(notInternalName).forEach(function (name) { __sagaState[name] = attributes.get(name); });
var inputParameters = workflow.getInputParameters();
(inputParameters.keys || []).filter(notInternalName).forEach(function (name) { __sagaState[name] = inputParameters.get(name); });
}
]]></script><in-binding><bind name="__sagaIn" type="Any" export-name="__sagaIn"/></in-binding><out-binding><bind name="__sagaState" type="Any" export-name="__sagaState"/></out-binding><position y="55.40909090909091" x="225.0"/></workflow-item><workflow-item name="item8" out-name="item10" catch-name="item5" throw-bind-name="__sagaError" type="task"><display-name><![CDATA[Initialize Context]]></display-name><script encoded="false"><![CDATA[System.log("Executing 'Initialize Context'...");
function requireModule() {
var actionName = "testSagaContext"
var imports = ["saga.test.saga.tasks"];
for (var i = 0; i < imports.length; i++) {
var moduleName = imports[i];
var mod = System.getModule(moduleName);
for (var j = 0; j < mod.actionDescriptions.length; j++) {
var action = mod.actionDescriptions[j];
if (action.name === actionName) {
var VROES = System.getModule("com.vmware.pscoe.library.ecmascript").VROES();
return VROES.require(moduleName + "." + actionName);
}
}
}
throw new Error("Unable to resolve action '" + actionName + "'");
}
var action = requireModule();
try {
(action.execute || action.default)(__sagaState);
}
catch (e) {
System.error(e);
throw e;
}
]]></script><in-binding><bind name="__sagaState" type="Any" export-name="__sagaState"/></in-binding><out-binding><bind name="__sagaState" type="Any" export-name="__sagaState"/></out-binding><position y="55.40909090909091" x="545.0"/></workflow-item><workflow-item name="item9" out-name="item5" type="task"><display-name><![CDATA[Initialize Context rollback]]></display-name><script encoded="false"><![CDATA[System.log("Rolling back 'Initialize Context'...");
]]></script><in-binding><bind name="__sagaState" type="Any" export-name="__sagaState"/></in-binding><out-binding><bind name="__sagaState" type="Any" export-name="__sagaState"/></out-binding><position y="128.13636363636363" x="545.0"/></workflow-item><workflow-item name="item10" out-name="item3" catch-name="item9" throw-bind-name="__sagaError" type="task"><display-name><![CDATA[Print First Saga]]></display-name><script encoded="false"><![CDATA[System.log("Executing 'Print First Saga'...");
function requireModule() {
var actionName = "firstSaga"
var imports = ["saga.test.saga.tasks"];
for (var i = 0; i < imports.length; i++) {
var moduleName = imports[i];
var mod = System.getModule(moduleName);
for (var j = 0; j < mod.actionDescriptions.length; j++) {
var action = mod.actionDescriptions[j];
if (action.name === actionName) {
var VROES = System.getModule("com.vmware.pscoe.library.ecmascript").VROES();
return VROES.require(moduleName + "." + actionName);
}
}
}
throw new Error("Unable to resolve action '" + actionName + "'");
}
var action = requireModule();
try {
if (__sagaState["conditionalVar"]) {
(action.execute || action.default)(__sagaState);
}
}
catch (e) {
System.error(e);
throw e;
}
]]></script><in-binding><bind name="__sagaState" type="Any" export-name="__sagaState"/></in-binding><out-binding><bind name="__sagaState" type="Any" export-name="__sagaState"/></out-binding><position y="55.40909090909091" x="705.0"/></workflow-item><workflow-item name="item11" out-name="item9" type="task"><display-name><![CDATA[Print First Saga rollback]]></display-name><script encoded="false"><![CDATA[System.log("Rolling back 'Print First Saga'...");
]]></script><in-binding><bind name="__sagaState" type="Any" export-name="__sagaState"/></in-binding><out-binding><bind name="__sagaState" type="Any" export-name="__sagaState"/></out-binding><position y="128.13636363636363" x="705.0"/></workflow-item><presentation><p-param name="__sagaIn"><p-qual kind="ognl" name="notVisible" type="boolean"><![CDATA[true]]></p-qual></p-param><p-param name="__sagaRoll"><p-qual kind="ognl" name="notVisible" type="boolean"><![CDATA[true]]></p-qual></p-param></presentation></workflow>
38 changes: 35 additions & 3 deletions typescript/vrotsc/src/compiler/transformers/scripts/saga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ namespace vrotsc {
execute: string;
rollback: string;
workflow: string;
if: string;
rollbackCurrentTask: boolean;
}

Expand Down Expand Up @@ -450,7 +451,7 @@ namespace vrotsc {
const rollbackItemId = nextItemId++;
const posX = 545 + index * 160;

if (task.workflow) {
if (task.workflow && !task.if) {
// Execute
items.push({
name: getItemName(executeItemId),
Expand Down Expand Up @@ -659,29 +660,41 @@ namespace vrotsc {
}

function buildExecuteScript(saga: SagaDescriptor, taskName: string, task: TaskDescriptor): string {
const builder = createStringBuilder();
let builder = createStringBuilder();
builder.append(`System.log("Executing '${taskName}'...");`).appendLine();
const conditionalVar = task.if;
if (task.execute) {
buildRequireModuleScript(builder, saga, task.execute);
builder.append(`var action = requireModule();`).appendLine();
builder.append(`try {`).appendLine();
builder.indent();
if (conditionalVar) {
builder.append(`if (${ATT_STATE}["${conditionalVar}"]) {`).appendLine();
builder.indent();
}
builder.append(`(action.execute || action.default)(${ATT_STATE});`).appendLine();
builder.unindent();
if (conditionalVar) {
builder.append(`}`).appendLine();
builder.unindent();
}
builder.append(`}`).appendLine();
builder.append(`catch (e) {`).appendLine();
builder.indent();
builder.append(`System.error(e);`).appendLine();
builder.append(`throw e;`).appendLine();
builder.unindent();
builder.append(`}`).appendLine();
} else if (task.workflow && conditionalVar) {
builder = buildWorkflowExecutor(builder, task.workflow, conditionalVar, false);
}
return builder.toString();
}

function buildRollbackScript(saga: SagaDescriptor, taskName: string, task: TaskDescriptor): string {
const builder = createStringBuilder();
let builder = createStringBuilder();
builder.append(`System.log("Rolling back '${taskName}'...");`).appendLine();
const conditionalVar = task.if;
if (task.rollback) {
buildRequireModuleScript(builder, saga, task.rollback);
builder.append(`try {`).appendLine();
Expand All @@ -695,6 +708,8 @@ namespace vrotsc {
builder.append(`System.error(e.toString() + ". Failed to rollback '${taskName}'.");`).appendLine();
builder.unindent();
builder.append(`}`).appendLine();
} else if (task.workflow && conditionalVar) {
builder = buildWorkflowExecutor(builder, task.workflow, conditionalVar, true);
}
return builder.toString();
}
Expand Down Expand Up @@ -732,6 +747,23 @@ namespace vrotsc {
builder.append(`}`).appendLine();
}

function buildWorkflowExecutor(builder: StringBuilder, workflowId: string, conditionalVar: string, rollback: boolean): StringBuilder {
builder.append(`if (${ATT_STATE}["${conditionalVar}"]) {`).appendLine();
builder.indent();
builder.append(`var __global = System.getContext() || (function () { return this; }).call(null);`).appendLine();
builder.append(`var VROES = __global.__VROES || (__global.__VROES = System.getModule("com.vmware.pscoe.library.ecmascript").VROES());`).appendLine();
builder.append(`var AsyncWorkflowExecutor_1 = VROES.importLazy("com.vmware.pscoe.library.ts.util/AsyncWorkflowExecutor");`).appendLine();
builder.append(`var props = new Properties();`).appendLine();
builder.append(`props.put("${PARAM_INPUT_DATA}", ${ATT_STATE});`).appendLine();
builder.append(`props.put("${PARAM_INPUT_ROLL}", ${rollback});`).appendLine();
builder.append(`var resultFuture = AsyncWorkflowExecutor_1._.AsyncWorkflowExecutor.execute("${workflowId}", props);`).appendLine();
builder.append(`var result = resultFuture.get();`).appendLine();
builder.append(`${ATT_STATE} = result.get("${ATT_STATE}");`).appendLine();
builder.unindent();
builder.append(`}`).appendLine();
return builder;
}

function buildWorkflowPresentation(workflow: WorkflowDescriptor, saga: SagaDescriptor): void {
workflow.presentation = workflow.presentation || {};
const params = workflow.presentation["p-param"] || (workflow.presentation["p-param"] = []);
Expand Down
Loading