orkestr 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +832 -0
- data/Rakefile +6 -0
- data/app/assets/builds/orkestr/orkestr-editor.js +72 -0
- data/app/assets/stylesheets/orkestr/application.css +15 -0
- data/app/assets/stylesheets/orkestr/theme.css +62 -0
- data/app/controllers/orkestr/api/base_controller.rb +45 -0
- data/app/controllers/orkestr/api/executions_controller.rb +50 -0
- data/app/controllers/orkestr/api/human_tasks_controller.rb +48 -0
- data/app/controllers/orkestr/api/registry_controller.rb +40 -0
- data/app/controllers/orkestr/api/workflows_controller.rb +53 -0
- data/app/controllers/orkestr/application_controller.rb +4 -0
- data/app/controllers/orkestr/human_tasks_controller.rb +30 -0
- data/app/controllers/orkestr/ui_controller.rb +8 -0
- data/app/controllers/orkestr/webhooks_controller.rb +35 -0
- data/app/helpers/orkestr/application_helper.rb +4 -0
- data/app/helpers/orkestr/ui_helper.rb +34 -0
- data/app/javascript/orkestr-ui/index.html +19 -0
- data/app/javascript/orkestr-ui/package-lock.json +2050 -0
- data/app/javascript/orkestr-ui/package.json +23 -0
- data/app/javascript/orkestr-ui/src/OrkestrApp.tsx +152 -0
- data/app/javascript/orkestr-ui/src/api/client.ts +59 -0
- data/app/javascript/orkestr-ui/src/api/executions.ts +23 -0
- data/app/javascript/orkestr-ui/src/api/humanTasks.ts +32 -0
- data/app/javascript/orkestr-ui/src/api/index.ts +5 -0
- data/app/javascript/orkestr-ui/src/api/registry.ts +10 -0
- data/app/javascript/orkestr-ui/src/api/workflows.ts +33 -0
- data/app/javascript/orkestr-ui/src/components/Editor/ActionsBuilder.tsx +213 -0
- data/app/javascript/orkestr-ui/src/components/Editor/CustomNode.tsx +31 -0
- data/app/javascript/orkestr-ui/src/components/Editor/EditorToolbar.tsx +153 -0
- data/app/javascript/orkestr-ui/src/components/Editor/FormSchemaBuilder.tsx +390 -0
- data/app/javascript/orkestr-ui/src/components/Editor/NodeConfigPanel.tsx +274 -0
- data/app/javascript/orkestr-ui/src/components/Editor/NodePalette.tsx +43 -0
- data/app/javascript/orkestr-ui/src/components/Editor/RunDialog.tsx +52 -0
- data/app/javascript/orkestr-ui/src/components/Editor/WorkflowEditor.tsx +299 -0
- data/app/javascript/orkestr-ui/src/components/Executions/ExecutionDetail.tsx +155 -0
- data/app/javascript/orkestr-ui/src/components/Executions/ExecutionList.tsx +74 -0
- data/app/javascript/orkestr-ui/src/components/HumanTasks/TaskForm.tsx +216 -0
- data/app/javascript/orkestr-ui/src/components/HumanTasks/TaskFormEmbed.tsx +117 -0
- data/app/javascript/orkestr-ui/src/components/HumanTasks/TaskList.tsx +110 -0
- data/app/javascript/orkestr-ui/src/components/Workflows/NewWorkflowDialog.tsx +64 -0
- data/app/javascript/orkestr-ui/src/components/Workflows/WorkflowList.tsx +94 -0
- data/app/javascript/orkestr-ui/src/components/shared/EntryConditionsEditor.tsx +138 -0
- data/app/javascript/orkestr-ui/src/components/shared/ExpressionEditor.tsx +206 -0
- data/app/javascript/orkestr-ui/src/components/shared/HumanTaskFormRenderer.tsx +321 -0
- data/app/javascript/orkestr-ui/src/components/shared/JsonSchemaForm.tsx +376 -0
- data/app/javascript/orkestr-ui/src/components/shared/Loading.tsx +5 -0
- data/app/javascript/orkestr-ui/src/components/shared/StatusBadge.tsx +9 -0
- data/app/javascript/orkestr-ui/src/fieldRegistry.ts +74 -0
- data/app/javascript/orkestr-ui/src/hooks/useApi.ts +30 -0
- data/app/javascript/orkestr-ui/src/hooks/useRegistry.ts +35 -0
- data/app/javascript/orkestr-ui/src/main.tsx +75 -0
- data/app/javascript/orkestr-ui/src/styles/editor.css +445 -0
- data/app/javascript/orkestr-ui/src/styles/index.css +478 -0
- data/app/javascript/orkestr-ui/src/types/execution.ts +37 -0
- data/app/javascript/orkestr-ui/src/types/humanTask.ts +30 -0
- data/app/javascript/orkestr-ui/src/types/index.ts +4 -0
- data/app/javascript/orkestr-ui/src/types/registry.ts +22 -0
- data/app/javascript/orkestr-ui/src/types/workflow.ts +64 -0
- data/app/javascript/orkestr-ui/src/vite-env.d.ts +6 -0
- data/app/javascript/orkestr-ui/tsconfig.json +21 -0
- data/app/javascript/orkestr-ui/tsconfig.tsbuildinfo +1 -0
- data/app/javascript/orkestr-ui/vite.config.ts +30 -0
- data/app/jobs/orkestr/application_job.rb +4 -0
- data/app/jobs/orkestr/execute_workflow_job.rb +10 -0
- data/app/jobs/orkestr/resume_execution_job.rb +15 -0
- data/app/mailers/orkestr/application_mailer.rb +6 -0
- data/app/models/concerns/orkestr/assignable.rb +28 -0
- data/app/models/concerns/orkestr/contextualizable.rb +9 -0
- data/app/models/orkestr/application_record.rb +6 -0
- data/app/models/orkestr/assignee.rb +40 -0
- data/app/models/orkestr/context.rb +42 -0
- data/app/models/orkestr/edge.rb +58 -0
- data/app/models/orkestr/execution.rb +45 -0
- data/app/models/orkestr/execution_log.rb +38 -0
- data/app/models/orkestr/human_task.rb +63 -0
- data/app/models/orkestr/node.rb +48 -0
- data/app/models/orkestr/node_execution.rb +59 -0
- data/app/models/orkestr/workflow.rb +39 -0
- data/app/orkestr_nodes/action/node.rb +77 -0
- data/app/orkestr_nodes/condition/node.rb +67 -0
- data/app/orkestr_nodes/http_request/node.rb +88 -0
- data/app/orkestr_nodes/human_action/node.rb +103 -0
- data/app/orkestr_nodes/transform/node.rb +48 -0
- data/app/orkestr_nodes/wait/node.rb +18 -0
- data/app/orkestr_triggers/manual/trigger.rb +12 -0
- data/app/orkestr_triggers/scheduled/trigger.rb +26 -0
- data/app/orkestr_triggers/webhook/trigger.rb +23 -0
- data/app/serializers/orkestr/assignee_serializer.rb +30 -0
- data/app/serializers/orkestr/context_serializer.rb +30 -0
- data/app/serializers/orkestr/edge_serializer.rb +33 -0
- data/app/serializers/orkestr/execution_collection_serializer.rb +7 -0
- data/app/serializers/orkestr/execution_log_serializer.rb +31 -0
- data/app/serializers/orkestr/execution_serializer.rb +37 -0
- data/app/serializers/orkestr/human_task_serializer.rb +46 -0
- data/app/serializers/orkestr/node_execution_serializer.rb +43 -0
- data/app/serializers/orkestr/node_serializer.rb +29 -0
- data/app/serializers/orkestr/workflow_collection_serializer.rb +11 -0
- data/app/serializers/orkestr/workflow_serializer.rb +30 -0
- data/app/services/orkestr/entry_condition_evaluator.rb +68 -0
- data/app/services/orkestr/execution_service/complete.rb +64 -0
- data/app/services/orkestr/execution_service/join_resolver.rb +56 -0
- data/app/services/orkestr/execution_service/node_runner.rb +56 -0
- data/app/services/orkestr/execution_service/runner.rb +162 -0
- data/app/services/orkestr/execution_service/start.rb +90 -0
- data/app/services/orkestr/expression_resolver.rb +72 -0
- data/app/services/orkestr/human_task_service/complete.rb +62 -0
- data/app/services/orkestr/workflow_service/duplicate.rb +30 -0
- data/app/services/orkestr/workflow_service/export.rb +26 -0
- data/app/services/orkestr/workflow_service/import.rb +29 -0
- data/app/services/orkestr/workflow_synchronizer.rb +102 -0
- data/app/views/layouts/orkestr/application.html.erb +17 -0
- data/app/views/layouts/orkestr/ui.html.erb +18 -0
- data/app/views/orkestr/human_tasks/show.html.erb +17 -0
- data/app/views/orkestr/ui/index.html.erb +8 -0
- data/config/routes.rb +27 -0
- data/db/migrate/20260308204133_enable_pgcrypto_extension.rb +5 -0
- data/db/migrate/20260308204558_create_orkestr_workflows.rb +12 -0
- data/db/migrate/20260308204703_create_orkestr_nodes.rb +12 -0
- data/db/migrate/20260308204807_create_orkestr_edges.rb +12 -0
- data/db/migrate/20260308204931_create_orkestr_executions.rb +13 -0
- data/db/migrate/20260308205023_create_orkestr_node_executions.rb +16 -0
- data/db/migrate/20260308205119_add_react_flow_id_to_nodes.rb +6 -0
- data/db/migrate/20260308205123_add_react_flow_id_to_edges.rb +6 -0
- data/db/migrate/20260308205745_add_workflow_to_executions.rb +5 -0
- data/db/migrate/20260308205940_make_executable_nullable_on_executions.rb +6 -0
- data/db/migrate/20260308220730_create_orkestr_human_tasks.rb +15 -0
- data/db/migrate/20260308220900_create_orkestr_assignees.rb +13 -0
- data/db/migrate/20260308234115_add_unique_index_to_node_executions.rb +7 -0
- data/db/migrate/20260309075336_create_orkestr_contexts.rb +10 -0
- data/db/migrate/20260309075343_replace_executable_with_context_on_executions.rb +11 -0
- data/db/migrate/20260309080416_add_status_key_deadline_to_orkestr_assignees.rb +7 -0
- data/db/migrate/20260309082815_add_status_and_workflow_to_orkestr_contexts.rb +7 -0
- data/db/migrate/20260309082816_add_status_default_context_and_reuse_context_to_orkestr_workflows.rb +7 -0
- data/db/migrate/20260309083328_create_orkestr_execution_logs.rb +14 -0
- data/db/migrate/20260310223204_replace_node_executions_unique_index_for_cycle_support.rb +18 -0
- data/lib/orkestr/configuration.rb +16 -0
- data/lib/orkestr/engine.rb +48 -0
- data/lib/orkestr/nodes/base.rb +74 -0
- data/lib/orkestr/nodes/loader.rb +32 -0
- data/lib/orkestr/nodes/registry.rb +34 -0
- data/lib/orkestr/nodes/schema_dsl.rb +73 -0
- data/lib/orkestr/triggers/base.rb +49 -0
- data/lib/orkestr/triggers/loader.rb +29 -0
- data/lib/orkestr/triggers/registry.rb +34 -0
- data/lib/orkestr/triggers/schema_dsl.rb +45 -0
- data/lib/orkestr/version.rb +3 -0
- data/lib/orkestr.rb +27 -0
- data/lib/tasks/annotate_rb.rake +10 -0
- data/lib/tasks/orkestr_tasks.rake +19 -0
- metadata +251 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
require "net/http"
|
|
2
|
+
require "uri"
|
|
3
|
+
|
|
4
|
+
module Orkestr
|
|
5
|
+
module Nodes
|
|
6
|
+
class HttpRequest < Base
|
|
7
|
+
node_id "http_request"
|
|
8
|
+
label "HTTP Request"
|
|
9
|
+
category "integration"
|
|
10
|
+
|
|
11
|
+
config_schema do
|
|
12
|
+
string :method, enum: %w[GET POST PUT DELETE], description: "HTTP method"
|
|
13
|
+
string :url, description: "Target URL (supports {{variable}} interpolation)"
|
|
14
|
+
object :headers, required: false, description: "HTTP headers" do
|
|
15
|
+
# dynamic keys
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
input_schema do
|
|
20
|
+
string :body, required: false, description: "Request body (supports {{variable}} interpolation)"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
output_schema do
|
|
24
|
+
number :status_code, description: "HTTP response status code"
|
|
25
|
+
string :response_body, description: "Response body"
|
|
26
|
+
object :response_headers, required: false, description: "Response headers" do
|
|
27
|
+
# dynamic keys
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
TIMEOUT = 30
|
|
32
|
+
|
|
33
|
+
def execute(context, input)
|
|
34
|
+
url = interpolate(config[:url].to_s, context)
|
|
35
|
+
method = (config[:method] || "GET").upcase
|
|
36
|
+
headers = build_headers(config[:headers] || {}, context)
|
|
37
|
+
body = input[:body] ? interpolate(input[:body].to_s, context) : nil
|
|
38
|
+
|
|
39
|
+
perform_request(method, url, headers, body)
|
|
40
|
+
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
|
41
|
+
raise ExecutionError, "HTTP timeout: #{e.message}"
|
|
42
|
+
rescue SocketError, Errno::ECONNREFUSED => e
|
|
43
|
+
raise ExecutionError, "Network error: #{e.message}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class ExecutionError < StandardError; end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def perform_request(method, url, headers, body)
|
|
51
|
+
uri = URI.parse(url)
|
|
52
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
53
|
+
http.use_ssl = uri.scheme == "https"
|
|
54
|
+
http.open_timeout = TIMEOUT
|
|
55
|
+
http.read_timeout = TIMEOUT
|
|
56
|
+
|
|
57
|
+
request = build_request(method, uri, headers, body)
|
|
58
|
+
response = http.request(request)
|
|
59
|
+
|
|
60
|
+
{
|
|
61
|
+
status_code: response.code.to_i,
|
|
62
|
+
response_body: response.body,
|
|
63
|
+
response_headers: response.each_header.to_h
|
|
64
|
+
}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def build_request(method, uri, headers, body)
|
|
68
|
+
request_class = {
|
|
69
|
+
"GET" => Net::HTTP::Get,
|
|
70
|
+
"POST" => Net::HTTP::Post,
|
|
71
|
+
"PUT" => Net::HTTP::Put,
|
|
72
|
+
"DELETE" => Net::HTTP::Delete
|
|
73
|
+
}.fetch(method)
|
|
74
|
+
|
|
75
|
+
request = request_class.new(uri)
|
|
76
|
+
headers.each { |k, v| request[k] = v }
|
|
77
|
+
request.body = body if body
|
|
78
|
+
request
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def build_headers(headers_config, context)
|
|
82
|
+
headers_config.each_with_object({}) do |(key, value), hash|
|
|
83
|
+
hash[key.to_s] = interpolate(value.to_s, context)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
module Orkestr
|
|
2
|
+
module Nodes
|
|
3
|
+
class HumanAction < Base
|
|
4
|
+
node_id "human_action"
|
|
5
|
+
label "Human Action"
|
|
6
|
+
category "human"
|
|
7
|
+
|
|
8
|
+
# Assignees: array of { type, id } objects.
|
|
9
|
+
# Resolved by priority: input > context > config
|
|
10
|
+
config_schema do
|
|
11
|
+
string :description, required: false, description: "Description of the task"
|
|
12
|
+
array :assignees, required: false, description: "Array of {type, id} objects" do
|
|
13
|
+
object do
|
|
14
|
+
string :type, description: "Polymorphic type (e.g. User, Group)"
|
|
15
|
+
string :id, description: "Record ID"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
string :assignee_key, required: false, description: "Role key for assignees (e.g. reviewer, approver)"
|
|
19
|
+
string :deadline, required: false, description: "ISO 8601 deadline for the task"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def execute(context, input)
|
|
23
|
+
human_task = create_human_task
|
|
24
|
+
create_assignees(human_task, context, input)
|
|
25
|
+
node_execution.update!(status: "waiting")
|
|
26
|
+
{}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def create_human_task
|
|
32
|
+
form_schema = node_execution.node.config_json.dig("form_schema") || {}
|
|
33
|
+
Orkestr::HumanTask.create!(
|
|
34
|
+
node_execution: node_execution,
|
|
35
|
+
schema_json: form_schema,
|
|
36
|
+
status: "pending"
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def create_assignees(human_task, context, input)
|
|
41
|
+
assignees = resolve_assignees(context, input)
|
|
42
|
+
assignees = exclude_already_responded(assignees)
|
|
43
|
+
return if assignees.empty?
|
|
44
|
+
|
|
45
|
+
deadline = resolve_deadline(context, input)
|
|
46
|
+
|
|
47
|
+
assignees.each do |assignee|
|
|
48
|
+
human_task.assignees.create!(
|
|
49
|
+
assignable_type: assignee[:type],
|
|
50
|
+
assignable_id: assignee[:id].to_s,
|
|
51
|
+
key: config[:assignee_key],
|
|
52
|
+
deadline: deadline
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Priority: input > context > config
|
|
58
|
+
def resolve_assignees(context, input)
|
|
59
|
+
raw = (input || {}).deep_symbolize_keys[:assignees] ||
|
|
60
|
+
(context || {}).deep_symbolize_keys[:assignees] ||
|
|
61
|
+
config[:assignees]
|
|
62
|
+
return [] if raw.blank?
|
|
63
|
+
|
|
64
|
+
Array(raw).filter_map do |entry|
|
|
65
|
+
entry = entry.deep_symbolize_keys if entry.respond_to?(:deep_symbolize_keys)
|
|
66
|
+
next unless entry[:type].present? && entry[:id].present?
|
|
67
|
+
|
|
68
|
+
{ type: entry[:type].to_s, id: entry[:id].to_s }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# In cycle workflows, exclude assignees who already completed a task
|
|
73
|
+
# for this same node in this execution.
|
|
74
|
+
def exclude_already_responded(assignees)
|
|
75
|
+
previous = execution.node_executions
|
|
76
|
+
.where(node_id: node_execution.node_id)
|
|
77
|
+
.where.not(id: node_execution.id)
|
|
78
|
+
.where(status: "completed")
|
|
79
|
+
|
|
80
|
+
return assignees unless previous.exists?
|
|
81
|
+
|
|
82
|
+
responded_ids = Orkestr::Assignee
|
|
83
|
+
.where(status: "completed")
|
|
84
|
+
.joins(:human_task)
|
|
85
|
+
.where(orkestr_human_tasks: { node_execution_id: previous.select(:id) })
|
|
86
|
+
.pluck(:assignable_id)
|
|
87
|
+
.map(&:to_s)
|
|
88
|
+
.to_set
|
|
89
|
+
|
|
90
|
+
assignees.reject { |a| responded_ids.include?(a[:id].to_s) }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def resolve_deadline(context, input)
|
|
94
|
+
raw = (input || {}).deep_symbolize_keys[:deadline] ||
|
|
95
|
+
(context || {}).deep_symbolize_keys[:deadline] ||
|
|
96
|
+
config[:deadline]
|
|
97
|
+
raw.present? ? Time.zone.parse(raw.to_s) : nil
|
|
98
|
+
rescue ArgumentError
|
|
99
|
+
nil
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Orkestr
|
|
2
|
+
module Nodes
|
|
3
|
+
class Transform < Base
|
|
4
|
+
node_id "transform"
|
|
5
|
+
label "Transform"
|
|
6
|
+
category "data"
|
|
7
|
+
|
|
8
|
+
config_schema do
|
|
9
|
+
array :mappings do
|
|
10
|
+
object do
|
|
11
|
+
string :source, description: "Source path (dot notation) or template with {{variable}}"
|
|
12
|
+
string :target, description: "Target key name"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
input_schema do
|
|
18
|
+
object :context, description: "Workflow context" do
|
|
19
|
+
# dynamic keys
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def execute(context, _input)
|
|
24
|
+
mappings = config[:mappings] || []
|
|
25
|
+
result = {}
|
|
26
|
+
|
|
27
|
+
mappings.each do |mapping|
|
|
28
|
+
source = mapping[:source].to_s
|
|
29
|
+
target = mapping[:target].to_s
|
|
30
|
+
|
|
31
|
+
result[target] = if template?(source)
|
|
32
|
+
interpolate(source, context)
|
|
33
|
+
else
|
|
34
|
+
dig_value(context, source)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
result
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def template?(source)
|
|
44
|
+
source.include?("{{") && source.include?("}}")
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Orkestr
|
|
2
|
+
module Nodes
|
|
3
|
+
class Wait < Base
|
|
4
|
+
node_id "wait"
|
|
5
|
+
label "Wait"
|
|
6
|
+
category "flow"
|
|
7
|
+
|
|
8
|
+
config_schema do
|
|
9
|
+
string :description, required: false, description: "Description of what is being waited for"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def execute(_context, _input)
|
|
13
|
+
node_execution.update!(status: "waiting")
|
|
14
|
+
{}
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require "fugit"
|
|
2
|
+
|
|
3
|
+
module Orkestr
|
|
4
|
+
module Triggers
|
|
5
|
+
class Scheduled < Base
|
|
6
|
+
trigger_id "scheduled"
|
|
7
|
+
label "Scheduled Trigger"
|
|
8
|
+
|
|
9
|
+
config_schema do
|
|
10
|
+
string :cron, description: "Cron expression (e.g. '0 * * * *')"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def fire(context = {})
|
|
14
|
+
start_execution(context)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def should_fire?(time)
|
|
18
|
+
cron = Fugit::Cron.parse(trigger_config[:cron])
|
|
19
|
+
return false unless cron
|
|
20
|
+
|
|
21
|
+
truncated = Time.at(time.to_i - (time.to_i % 60)).utc
|
|
22
|
+
cron.match?(truncated)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Orkestr
|
|
2
|
+
module Triggers
|
|
3
|
+
class Webhook < Base
|
|
4
|
+
trigger_id "webhook"
|
|
5
|
+
label "Webhook Trigger"
|
|
6
|
+
|
|
7
|
+
config_schema do
|
|
8
|
+
string :secret, required: false, description: "Shared secret for signature verification"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def fire(context = {})
|
|
12
|
+
start_execution(context, async: true)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def valid_secret?(provided_secret)
|
|
16
|
+
expected = trigger_config[:secret]
|
|
17
|
+
return true if expected.blank?
|
|
18
|
+
|
|
19
|
+
ActiveSupport::SecurityUtils.secure_compare(expected.to_s, provided_secret.to_s)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# == Schema Information
|
|
2
|
+
#
|
|
3
|
+
# Table name: orkestr_assignees
|
|
4
|
+
#
|
|
5
|
+
# id :uuid not null, primary key
|
|
6
|
+
# assignable_type :string not null
|
|
7
|
+
# deadline :datetime
|
|
8
|
+
# key :string
|
|
9
|
+
# status :string default("pending"), not null
|
|
10
|
+
# created_at :datetime not null
|
|
11
|
+
# updated_at :datetime not null
|
|
12
|
+
# assignable_id :string not null
|
|
13
|
+
# human_task_id :uuid not null
|
|
14
|
+
#
|
|
15
|
+
# Indexes
|
|
16
|
+
#
|
|
17
|
+
# index_orkestr_assignees_on_assignable_type_and_assignable_id (assignable_type,assignable_id)
|
|
18
|
+
# index_orkestr_assignees_on_human_task_id (human_task_id)
|
|
19
|
+
#
|
|
20
|
+
# Foreign Keys
|
|
21
|
+
#
|
|
22
|
+
# fk_rails_... (human_task_id => orkestr_human_tasks.id)
|
|
23
|
+
#
|
|
24
|
+
module Orkestr
|
|
25
|
+
class AssigneeSerializer
|
|
26
|
+
include Alba::Resource
|
|
27
|
+
|
|
28
|
+
attributes :id, :assignable_type, :assignable_id, :key, :status, :deadline, :created_at
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# == Schema Information
|
|
2
|
+
#
|
|
3
|
+
# Table name: orkestr_contexts
|
|
4
|
+
#
|
|
5
|
+
# id :uuid not null, primary key
|
|
6
|
+
# contextualizable_type :string
|
|
7
|
+
# data :jsonb not null
|
|
8
|
+
# status :string default("pending"), not null
|
|
9
|
+
# created_at :datetime not null
|
|
10
|
+
# updated_at :datetime not null
|
|
11
|
+
# contextualizable_id :uuid
|
|
12
|
+
# workflow_id :uuid
|
|
13
|
+
#
|
|
14
|
+
# Indexes
|
|
15
|
+
#
|
|
16
|
+
# index_orkestr_contexts_on_contextualizable (contextualizable_type,contextualizable_id)
|
|
17
|
+
# index_orkestr_contexts_on_workflow_id (workflow_id)
|
|
18
|
+
#
|
|
19
|
+
# Foreign Keys
|
|
20
|
+
#
|
|
21
|
+
# fk_rails_... (workflow_id => orkestr_workflows.id)
|
|
22
|
+
#
|
|
23
|
+
module Orkestr
|
|
24
|
+
class ContextSerializer
|
|
25
|
+
include Alba::Resource
|
|
26
|
+
|
|
27
|
+
attributes :id, :contextualizable_type, :contextualizable_id,
|
|
28
|
+
:workflow_id, :status, :data, :created_at, :updated_at
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# == Schema Information
|
|
2
|
+
#
|
|
3
|
+
# Table name: orkestr_edges
|
|
4
|
+
#
|
|
5
|
+
# id :uuid not null, primary key
|
|
6
|
+
# source_handle :string
|
|
7
|
+
# created_at :datetime not null
|
|
8
|
+
# updated_at :datetime not null
|
|
9
|
+
# react_flow_id :string
|
|
10
|
+
# source_node_id :uuid not null
|
|
11
|
+
# target_node_id :uuid not null
|
|
12
|
+
# workflow_id :uuid not null
|
|
13
|
+
#
|
|
14
|
+
# Indexes
|
|
15
|
+
#
|
|
16
|
+
# index_orkestr_edges_on_react_flow_id (react_flow_id)
|
|
17
|
+
# index_orkestr_edges_on_source_node_id (source_node_id)
|
|
18
|
+
# index_orkestr_edges_on_target_node_id (target_node_id)
|
|
19
|
+
# index_orkestr_edges_on_workflow_id (workflow_id)
|
|
20
|
+
#
|
|
21
|
+
# Foreign Keys
|
|
22
|
+
#
|
|
23
|
+
# fk_rails_... (source_node_id => orkestr_nodes.id)
|
|
24
|
+
# fk_rails_... (target_node_id => orkestr_nodes.id)
|
|
25
|
+
# fk_rails_... (workflow_id => orkestr_workflows.id)
|
|
26
|
+
#
|
|
27
|
+
module Orkestr
|
|
28
|
+
class EdgeSerializer
|
|
29
|
+
include Alba::Resource
|
|
30
|
+
|
|
31
|
+
attributes :id, :source_node_id, :target_node_id, :source_handle, :react_flow_id, :created_at, :updated_at
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# == Schema Information
|
|
2
|
+
#
|
|
3
|
+
# Table name: orkestr_execution_logs
|
|
4
|
+
#
|
|
5
|
+
# id :uuid not null, primary key
|
|
6
|
+
# level :string default("info"), not null
|
|
7
|
+
# message :text not null
|
|
8
|
+
# metadata :jsonb not null
|
|
9
|
+
# created_at :datetime not null
|
|
10
|
+
# updated_at :datetime not null
|
|
11
|
+
# execution_id :uuid not null
|
|
12
|
+
# node_execution_id :uuid
|
|
13
|
+
#
|
|
14
|
+
# Indexes
|
|
15
|
+
#
|
|
16
|
+
# index_orkestr_execution_logs_on_execution_id (execution_id)
|
|
17
|
+
# index_orkestr_execution_logs_on_node_execution_id (node_execution_id)
|
|
18
|
+
#
|
|
19
|
+
# Foreign Keys
|
|
20
|
+
#
|
|
21
|
+
# fk_rails_... (execution_id => orkestr_executions.id)
|
|
22
|
+
# fk_rails_... (node_execution_id => orkestr_node_executions.id)
|
|
23
|
+
#
|
|
24
|
+
module Orkestr
|
|
25
|
+
class ExecutionLogSerializer
|
|
26
|
+
include Alba::Resource
|
|
27
|
+
|
|
28
|
+
attributes :id, :execution_id, :node_execution_id,
|
|
29
|
+
:level, :message, :metadata, :created_at
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# == Schema Information
|
|
2
|
+
#
|
|
3
|
+
# Table name: orkestr_executions
|
|
4
|
+
#
|
|
5
|
+
# id :uuid not null, primary key
|
|
6
|
+
# finished_at :datetime
|
|
7
|
+
# started_at :datetime
|
|
8
|
+
# status :string default("pending"), not null
|
|
9
|
+
# created_at :datetime not null
|
|
10
|
+
# updated_at :datetime not null
|
|
11
|
+
# context_id :uuid
|
|
12
|
+
# workflow_id :uuid not null
|
|
13
|
+
#
|
|
14
|
+
# Indexes
|
|
15
|
+
#
|
|
16
|
+
# index_orkestr_executions_on_context_id (context_id)
|
|
17
|
+
# index_orkestr_executions_on_workflow_id (workflow_id)
|
|
18
|
+
#
|
|
19
|
+
# Foreign Keys
|
|
20
|
+
#
|
|
21
|
+
# fk_rails_... (context_id => orkestr_contexts.id)
|
|
22
|
+
# fk_rails_... (workflow_id => orkestr_workflows.id)
|
|
23
|
+
#
|
|
24
|
+
module Orkestr
|
|
25
|
+
class ExecutionSerializer
|
|
26
|
+
include Alba::Resource
|
|
27
|
+
|
|
28
|
+
attributes :id, :workflow_id, :status, :context_id,
|
|
29
|
+
:started_at, :finished_at, :created_at, :updated_at
|
|
30
|
+
|
|
31
|
+
attribute :context_data do |execution|
|
|
32
|
+
execution.context&.data || {}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
many :node_executions, resource: NodeExecutionSerializer
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# == Schema Information
|
|
2
|
+
#
|
|
3
|
+
# Table name: orkestr_human_tasks
|
|
4
|
+
#
|
|
5
|
+
# id :uuid not null, primary key
|
|
6
|
+
# completed_at :datetime
|
|
7
|
+
# response_json :json
|
|
8
|
+
# schema_json :json not null
|
|
9
|
+
# status :string default("pending"), not null
|
|
10
|
+
# created_at :datetime not null
|
|
11
|
+
# updated_at :datetime not null
|
|
12
|
+
# execution_id :uuid not null
|
|
13
|
+
# node_execution_id :uuid not null
|
|
14
|
+
# node_id :uuid not null
|
|
15
|
+
#
|
|
16
|
+
# Indexes
|
|
17
|
+
#
|
|
18
|
+
# index_orkestr_human_tasks_on_execution_id (execution_id)
|
|
19
|
+
# index_orkestr_human_tasks_on_node_execution_id (node_execution_id)
|
|
20
|
+
# index_orkestr_human_tasks_on_node_id (node_id)
|
|
21
|
+
#
|
|
22
|
+
# Foreign Keys
|
|
23
|
+
#
|
|
24
|
+
# fk_rails_... (execution_id => orkestr_executions.id)
|
|
25
|
+
# fk_rails_... (node_execution_id => orkestr_node_executions.id)
|
|
26
|
+
# fk_rails_... (node_id => orkestr_nodes.id)
|
|
27
|
+
#
|
|
28
|
+
module Orkestr
|
|
29
|
+
class HumanTaskSerializer
|
|
30
|
+
include Alba::Resource
|
|
31
|
+
|
|
32
|
+
attributes :id, :execution_id, :node_execution_id, :node_id,
|
|
33
|
+
:status, :schema_json, :response_json,
|
|
34
|
+
:completed_at, :created_at, :updated_at
|
|
35
|
+
|
|
36
|
+
attribute :description do |task|
|
|
37
|
+
task.node&.config_json&.dig("description")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
attribute :actions do |task|
|
|
41
|
+
task.node&.config_json&.dig("actions")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
many :assignees, resource: AssigneeSerializer
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# == Schema Information
|
|
2
|
+
#
|
|
3
|
+
# Table name: orkestr_node_executions
|
|
4
|
+
#
|
|
5
|
+
# id :uuid not null, primary key
|
|
6
|
+
# attempt :integer default(1), not null
|
|
7
|
+
# finished_at :datetime
|
|
8
|
+
# input_json :json not null
|
|
9
|
+
# output_json :json not null
|
|
10
|
+
# started_at :datetime
|
|
11
|
+
# status :string default("pending"), not null
|
|
12
|
+
# created_at :datetime not null
|
|
13
|
+
# updated_at :datetime not null
|
|
14
|
+
# execution_id :uuid not null
|
|
15
|
+
# node_id :uuid not null
|
|
16
|
+
#
|
|
17
|
+
# Indexes
|
|
18
|
+
#
|
|
19
|
+
# index_orkestr_node_executions_on_execution_id (execution_id)
|
|
20
|
+
# index_orkestr_node_executions_on_node_id (node_id)
|
|
21
|
+
# index_orkestr_node_executions_unique_active (execution_id,node_id) UNIQUE WHERE ((status)::text = ANY (ARRAY[('pending'::character varying)::text, ('running'::character varying)::text, ('waiting'::character varying)::text]))
|
|
22
|
+
#
|
|
23
|
+
# Foreign Keys
|
|
24
|
+
#
|
|
25
|
+
# fk_rails_... (execution_id => orkestr_executions.id)
|
|
26
|
+
# fk_rails_... (node_id => orkestr_nodes.id)
|
|
27
|
+
#
|
|
28
|
+
module Orkestr
|
|
29
|
+
class NodeExecutionSerializer
|
|
30
|
+
include Alba::Resource
|
|
31
|
+
|
|
32
|
+
attributes :id, :node_id, :status, :input_json, :output_json, :attempt,
|
|
33
|
+
:started_at, :finished_at, :created_at, :updated_at
|
|
34
|
+
|
|
35
|
+
attribute :node_type do |ne|
|
|
36
|
+
ne.node.node_type
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
attribute :duration do |ne|
|
|
40
|
+
ne.duration
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# == Schema Information
|
|
2
|
+
#
|
|
3
|
+
# Table name: orkestr_nodes
|
|
4
|
+
#
|
|
5
|
+
# id :uuid not null, primary key
|
|
6
|
+
# conditions_json :json not null
|
|
7
|
+
# config_json :json not null
|
|
8
|
+
# node_type :string not null
|
|
9
|
+
# created_at :datetime not null
|
|
10
|
+
# updated_at :datetime not null
|
|
11
|
+
# react_flow_id :string
|
|
12
|
+
# workflow_id :uuid not null
|
|
13
|
+
#
|
|
14
|
+
# Indexes
|
|
15
|
+
#
|
|
16
|
+
# index_orkestr_nodes_on_react_flow_id (react_flow_id)
|
|
17
|
+
# index_orkestr_nodes_on_workflow_id (workflow_id)
|
|
18
|
+
#
|
|
19
|
+
# Foreign Keys
|
|
20
|
+
#
|
|
21
|
+
# fk_rails_... (workflow_id => orkestr_workflows.id)
|
|
22
|
+
#
|
|
23
|
+
module Orkestr
|
|
24
|
+
class NodeSerializer
|
|
25
|
+
include Alba::Resource
|
|
26
|
+
|
|
27
|
+
attributes :id, :node_type, :react_flow_id, :config_json, :conditions_json, :created_at, :updated_at
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# == Schema Information
|
|
2
|
+
#
|
|
3
|
+
# Table name: orkestr_workflows
|
|
4
|
+
#
|
|
5
|
+
# id :uuid not null, primary key
|
|
6
|
+
# default_context :jsonb not null
|
|
7
|
+
# graph_json :json not null
|
|
8
|
+
# name :string not null
|
|
9
|
+
# reuse_context :boolean default(FALSE), not null
|
|
10
|
+
# status :string default("draft"), not null
|
|
11
|
+
# trigger_config :json not null
|
|
12
|
+
# trigger_type :string
|
|
13
|
+
# created_at :datetime not null
|
|
14
|
+
# updated_at :datetime not null
|
|
15
|
+
#
|
|
16
|
+
module Orkestr
|
|
17
|
+
class WorkflowSerializer
|
|
18
|
+
include Alba::Resource
|
|
19
|
+
|
|
20
|
+
attributes :id, :name, :status, :trigger_type, :trigger_config, :graph_json,
|
|
21
|
+
:default_context, :reuse_context, :created_at, :updated_at
|
|
22
|
+
|
|
23
|
+
many :nodes, resource: NodeSerializer
|
|
24
|
+
many :edges, resource: EdgeSerializer
|
|
25
|
+
|
|
26
|
+
attribute :executions_count do |workflow|
|
|
27
|
+
workflow.executions.size
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|