Skip to content

Commit f28cda9

Browse files
authored
Add support for script with JOIN tasks (#265)
1 parent 498f0ea commit f28cda9

4 files changed

Lines changed: 113 additions & 2 deletions

File tree

‎examples/orkes/fork_join_script.py‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import json
2+
3+
from conductor.client.configuration.configuration import Configuration
4+
from conductor.client.http.models import StartWorkflowRequest, RerunWorkflowRequest, TaskResult, WorkflowRun, \
5+
WorkflowDef
6+
from conductor.client.http.models.task_result_status import TaskResultStatus
7+
from conductor.client.http.models.workflow_def import to_workflow_def
8+
from conductor.client.http.models.workflow_state_update import WorkflowStateUpdate
9+
from conductor.client.orkes_clients import OrkesClients
10+
from conductor.client.workflow.conductor_workflow import ConductorWorkflow
11+
from conductor.client.workflow.task.fork_task import ForkTask
12+
from conductor.client.workflow.task.http_task import HttpTask
13+
from conductor.client.workflow.task.join_task import JoinTask
14+
from conductor.client.workflow_client import WorkflowClient
15+
16+
17+
def main():
18+
api_config = Configuration()
19+
clients = OrkesClients(configuration=api_config)
20+
workflow_client = clients.get_workflow_client()
21+
executor = clients.get_workflow_executor()
22+
23+
workflow = ConductorWorkflow(name='fork_join_example', version=1, executor=executor)
24+
fork_size = 10
25+
tasks = []
26+
join_on = []
27+
for i in range(fork_size):
28+
http = HttpTask(task_ref_name=f"http_{i}",
29+
http_input={"uri": "https://orkes-api-tester.orkesconductor.com/api2"})
30+
http.optional = True
31+
tasks.append([http])
32+
join_on.append(f"http_{i}")
33+
34+
# HTTP tasks are marked as optional and the URL gives 404 error
35+
# the script below checks if the tasks are completed or completed with errors and completes the join task
36+
script = """
37+
(function(){
38+
let results = {};
39+
let pendingJoinsFound = false;
40+
if($.joinOn){
41+
$.joinOn.forEach((element)=>{
42+
if($[element] && $[element].status !== 'COMPLETED' && $[element] && $[element].status !== 'COMPLETED_WITH_ERRORS'){
43+
results[element] = $[element].status;
44+
pendingJoinsFound = true;
45+
}
46+
});
47+
if(pendingJoinsFound){
48+
return {
49+
"status":"IN_PROGRESS",
50+
"reasonForIncompletion":"Pending",
51+
"outputData":{
52+
"scriptResults": results
53+
}
54+
};
55+
}
56+
// To complete the Join - return true OR an object with status = 'COMPLETED' like above.
57+
return true;
58+
}
59+
})();
60+
"""
61+
join = JoinTask(task_ref_name='join', join_on_script=script, join_on=join_on)
62+
fork = ForkTask(task_ref_name="fork", forked_tasks=tasks)
63+
workflow >> fork >> join
64+
workflow_id = workflow.start_workflow_with_input()
65+
print(f'started workflow with id {workflow_id}')
66+
67+
68+
if __name__ == '__main__':
69+
main()

‎src/conductor/client/workflow/conductor_workflow.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,25 @@ def start_workflow(self, start_workflow_request: StartWorkflowRequest) -> str:
192192
start_workflow_request.version = self.version
193193
return self._executor.start_workflow(start_workflow_request)
194194

195+
def start_workflow_with_input(self, workflow_input: dict = {}, correlation_id=None, task_to_domain=None,
196+
priority=None, idempotency_key: str = None, idempotency_strategy: IdempotencyStrategy = IdempotencyStrategy.FAIL) -> str:
197+
"""
198+
Starts the workflow with given inputs and parameters and returns the id of the started workflow
199+
"""
200+
201+
start_workflow_request = StartWorkflowRequest()
202+
start_workflow_request.workflow_def = self.to_workflow_def()
203+
start_workflow_request.name = self.name
204+
start_workflow_request.version = self.version
205+
start_workflow_request.input = workflow_input
206+
start_workflow_request.correlation_id = correlation_id
207+
start_workflow_request.idempotency_key = idempotency_key
208+
start_workflow_request.idempotency_strategy = idempotency_strategy
209+
start_workflow_request.priority = priority
210+
start_workflow_request.task_to_domain =task_to_domain
211+
212+
return self._executor.start_workflow(start_workflow_request)
213+
195214
def execute(self, workflow_input: Any = {}, wait_until_task_ref: str = '', wait_for_seconds: int = 10,
196215
request_id: str = None,
197216
idempotency_key: str = None, idempotency_strategy : IdempotencyStrategy = IdempotencyStrategy.FAIL, task_to_domain: dict[str, str] = None) -> WorkflowRun:

‎src/conductor/client/workflow/task/join_task.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@
99

1010

1111
class JoinTask(TaskInterface):
12-
def __init__(self, task_ref_name: str, join_on: List[str] = None) -> Self:
12+
def __init__(self, task_ref_name: str, join_on: List[str] = None, join_on_script: str = None) -> Self:
1313
super().__init__(
1414
task_reference_name=task_ref_name,
1515
task_type=TaskType.JOIN
1616
)
1717
self._join_on = deepcopy(join_on)
18+
if join_on_script is not None:
19+
self.evaluator_type = 'js'
20+
self.expression = join_on_script
1821

1922
def to_workflow_task(self) -> WorkflowTask:
2023
workflow = super().to_workflow_task()

‎src/conductor/client/workflow/task/task.py‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ def __init__(self,
3434
self.input_parameters = input_parameters
3535
self._cache_key = cache_key
3636
self._cache_ttl_second = cache_ttl_second
37+
self._expression = None
38+
self._evaluator_type = None
3739

3840
@property
3941
def task_reference_name(self) -> str:
@@ -65,6 +67,22 @@ def name(self, name: str) -> None:
6567
raise Exception('invalid type')
6668
self._name = name
6769

70+
@property
71+
def expression(self) -> str:
72+
return self._expression
73+
74+
@expression.setter
75+
def expression(self, expression: str) -> None:
76+
self._expression = expression
77+
78+
@property
79+
def evaluator_type(self) -> str:
80+
return self._evaluator_type
81+
82+
@evaluator_type.setter
83+
def evaluator_type(self, evaluator_type: str) -> None:
84+
self._evaluator_type = evaluator_type
85+
6886
def cache(self, cache_key: str, cache_ttl_second: int):
6987
self._cache_key = cache_key
7088
self._cache_ttl_second = cache_ttl_second
@@ -122,7 +140,9 @@ def to_workflow_task(self) -> WorkflowTask:
122140
description=self._description,
123141
input_parameters=self._input_parameters,
124142
optional=self._optional,
125-
cache_config=cache_config
143+
cache_config=cache_config,
144+
expression=self._expression,
145+
evaluator_type=self._evaluator_type
126146
)
127147

128148
def output(self, json_path: str = None) -> str:

0 commit comments

Comments
 (0)