bas 1.8.0 → 1.9.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 +4 -4
- data/CHANGELOG.md +6 -0
- data/lib/bas/utils/operaton/base_operaton_client.rb +81 -0
- data/lib/bas/utils/operaton/external_task_client.rb +74 -100
- data/lib/bas/utils/operaton/process_client.rb +83 -0
- data/lib/bas/version.rb +1 -1
- metadata +17 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe416744cd3c3fb9f0902dfc6a3b1432b63425a00da2cea4828a69b0f7e4595a
|
4
|
+
data.tar.gz: 02701d0e8b83c71e0721ebae2a197c6f9204414d3a6f313052152bc2e6957863
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b63e685728abb1a3c80ab896aca20f9621a08b3f3defa9ca682912e9722359a3510b2bfd65154bedc3a0171f372ea9f1df63ae6305bbfc48b28f36477c8c2761
|
7
|
+
data.tar.gz: 7f73e57bc600b044a82deb51c97b30eab54bc5de48122f554d18614e08a273950fa705f5403accd6ff5899e54dbba5ccb3634cf0504769073a55d8d6a7b695bc
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
# 1.9.0 (15.07.2025)
|
4
|
+
- [feat: Implement client to perform process deployments and instance creation in Operaton via the REST API](https://github.com/kommitters/bas/pull/146)
|
5
|
+
|
6
|
+
# 1.8.1 (08.07.2025)
|
7
|
+
- [refactor: Refactor client for correct use of shared_storage](https://github.com/kommitters/bas/pull/144)
|
8
|
+
|
3
9
|
# 1.8.0 (07.07.2025)
|
4
10
|
- [Feat: Implement client to interact with Operaton's API-REST](https://github.com/kommitters/bas/pull/142)
|
5
11
|
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "faraday"
|
4
|
+
require "json"
|
5
|
+
|
6
|
+
module Utils
|
7
|
+
module Operaton
|
8
|
+
##
|
9
|
+
# BaseClient provides common HTTP methods and variable formatting logic
|
10
|
+
# shared by all Operaton API clients.
|
11
|
+
#
|
12
|
+
class BaseClient
|
13
|
+
def initialize(base_url:)
|
14
|
+
raise ArgumentError, "base_url is required" if base_url.to_s.strip.empty?
|
15
|
+
|
16
|
+
@base_url = base_url.chomp("/")
|
17
|
+
@conn = build_conn
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def build_conn
|
23
|
+
# Override to add multipart support for file uploads and URL encoding for form data
|
24
|
+
Faraday.new(url: @base_url) do |f|
|
25
|
+
f.request :json
|
26
|
+
f.response :json, content_type: /\bjson$/
|
27
|
+
f.adapter Faraday.default_adapter
|
28
|
+
f.options.timeout = 30
|
29
|
+
f.options.open_timeout = 10
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def full_url(path)
|
34
|
+
"#{@base_url}#{path.start_with?("/") ? path : "/#{path}"}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def get(path, params = {})
|
38
|
+
response = @conn.get(full_url(path), params)
|
39
|
+
handle_response(response)
|
40
|
+
end
|
41
|
+
|
42
|
+
def post(path, body = {}, headers = {})
|
43
|
+
response = @conn.post(full_url(path)) do |req|
|
44
|
+
req.headers.update(headers) if headers.any?
|
45
|
+
req.body = body
|
46
|
+
end
|
47
|
+
handle_response(response)
|
48
|
+
end
|
49
|
+
|
50
|
+
def handle_response(response)
|
51
|
+
unless response.success?
|
52
|
+
error_body = response.body.is_a?(Hash) ? response.body : { message: response.body }
|
53
|
+
raise "Operaton API Error #{response.status}: #{error_body["message"] || error_body}"
|
54
|
+
end
|
55
|
+
|
56
|
+
response.body
|
57
|
+
end
|
58
|
+
|
59
|
+
def format_variables(vars)
|
60
|
+
vars.transform_values do |value|
|
61
|
+
{
|
62
|
+
value: value,
|
63
|
+
type: ruby_type_to_operaton_type(value)
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def ruby_type_to_operaton_type(value)
|
69
|
+
case value
|
70
|
+
when nil then "Null"
|
71
|
+
when String then "String"
|
72
|
+
when Integer then "Integer"
|
73
|
+
when Float then "Double"
|
74
|
+
when TrueClass, FalseClass then "Boolean"
|
75
|
+
when Array, Hash then "Json"
|
76
|
+
else "Object"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -2,114 +2,88 @@
|
|
2
2
|
|
3
3
|
require "faraday"
|
4
4
|
require "json"
|
5
|
+
require_relative "base_operaton_client"
|
6
|
+
|
7
|
+
module Utils
|
8
|
+
module Operaton
|
9
|
+
##
|
10
|
+
# Client for interacting with Operaton's External Task API.
|
11
|
+
#
|
12
|
+
# This client provides methods to manage external task lifecycle including:
|
13
|
+
# - Fetching and locking tasks
|
14
|
+
# - Completing tasks with variables
|
15
|
+
# - Unlocking tasks
|
16
|
+
# - Reporting task failures
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# client = Utils::Operaton::ExternalTaskClient.new(base_url: "https://api.operaton.com", worker_id: "worker-123")
|
20
|
+
# tasks = client.fetch_and_lock("my-topic")
|
21
|
+
#
|
22
|
+
class ExternalTaskClient < BaseClient
|
23
|
+
def initialize(params)
|
24
|
+
@worker_id = params[:worker_id]
|
25
|
+
validate_params!(params)
|
26
|
+
super(base_url: params[:base_url])
|
27
|
+
end
|
5
28
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
# @example
|
18
|
-
# client = ExternalTaskClient.new(base_url: "https://api.operaton.com", worker_id: "worker-123")
|
19
|
-
# tasks = client.fetch_and_lock("my-topic")
|
20
|
-
class ExternalTaskClient
|
21
|
-
def initialize(base_url:, worker_id:)
|
22
|
-
raise ArgumentError, "base_url cannot be nil or empty" if base_url.nil? || base_url.empty?
|
23
|
-
raise ArgumentError, "worker_id cannot be nil or empty" if worker_id.nil? || worker_id.empty?
|
24
|
-
|
25
|
-
@base_url = base_url
|
26
|
-
@worker_id = worker_id
|
27
|
-
|
28
|
-
@conn = Faraday.new(url: base_url) do |f|
|
29
|
-
f.request :json
|
30
|
-
f.response :json, content_type: /\bjson$/
|
31
|
-
f.adapter Faraday.default_adapter
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def fetch_and_lock(topics_str, lock_duration: 10_000, max_tasks: 1, use_priority: true, variables: [])
|
36
|
-
post("/external-task/fetchAndLock",
|
37
|
-
workerId: @worker_id,
|
38
|
-
maxTasks: max_tasks,
|
39
|
-
usePriority: use_priority,
|
40
|
-
topics: build_topics_payload(topics_str, lock_duration, variables))
|
41
|
-
end
|
42
|
-
|
43
|
-
def complete(task_id, variables = {})
|
44
|
-
post("/external-task/#{task_id}/complete", workerId: @worker_id,
|
45
|
-
variables: format_variables(variables))
|
46
|
-
end
|
47
|
-
|
48
|
-
def get_variables(task_id)
|
49
|
-
get("/external-task/#{task_id}/variables")
|
50
|
-
end
|
51
|
-
|
52
|
-
def unlock(task_id)
|
53
|
-
post("/external-task/#{task_id}/unlock")
|
54
|
-
end
|
55
|
-
|
56
|
-
def report_failure(task_id, error_message:, error_details:, retries:, retry_timeout:)
|
57
|
-
post("/external-task/#{task_id}/failure",
|
58
|
-
workerId: @worker_id,
|
59
|
-
errorMessage: error_message,
|
60
|
-
errorDetails: error_details,
|
61
|
-
retries: retries,
|
62
|
-
retryTimeout: retry_timeout)
|
63
|
-
end
|
64
|
-
|
65
|
-
private
|
66
|
-
|
67
|
-
def build_topics_payload(topics_str, lock_duration, variables)
|
68
|
-
topic_names = topics_str.is_a?(Array) ? topics_str : topics_str.to_s.split(",")
|
69
|
-
topic_names.map do |name|
|
70
|
-
{
|
71
|
-
topicName: name.strip,
|
72
|
-
lockDuration: lock_duration,
|
73
|
-
variables: variables
|
74
|
-
}
|
75
|
-
end
|
76
|
-
end
|
29
|
+
def fetch_and_lock(topics_str, lock_duration: 10_000, max_tasks: 1, use_priority: true, variables: [])
|
30
|
+
post(
|
31
|
+
"/external-task/fetchAndLock",
|
32
|
+
{
|
33
|
+
workerId: @worker_id,
|
34
|
+
maxTasks: max_tasks,
|
35
|
+
usePriority: use_priority,
|
36
|
+
topics: build_topics_payload(topics_str, lock_duration, variables)
|
37
|
+
}
|
38
|
+
)
|
39
|
+
end
|
77
40
|
|
78
|
-
|
79
|
-
|
80
|
-
|
41
|
+
def complete(task_id, variables = {})
|
42
|
+
post(
|
43
|
+
"/external-task/#{task_id}/complete",
|
44
|
+
{
|
45
|
+
workerId: @worker_id,
|
46
|
+
variables: format_variables(variables)
|
47
|
+
}
|
48
|
+
)
|
49
|
+
end
|
81
50
|
|
82
|
-
|
83
|
-
|
84
|
-
|
51
|
+
def get_variables(task_id)
|
52
|
+
get("/external-task/#{task_id}/variables")
|
53
|
+
end
|
85
54
|
|
86
|
-
|
87
|
-
|
88
|
-
|
55
|
+
def unlock(task_id)
|
56
|
+
post("/external-task/#{task_id}/unlock")
|
57
|
+
end
|
89
58
|
|
90
|
-
|
91
|
-
|
59
|
+
def report_failure(task_id, error_message:, error_details:, retries:, retry_timeout:)
|
60
|
+
post(
|
61
|
+
"/external-task/#{task_id}/failure",
|
62
|
+
{
|
63
|
+
workerId: @worker_id,
|
64
|
+
errorMessage: error_message,
|
65
|
+
errorDetails: error_details,
|
66
|
+
retries: retries,
|
67
|
+
retryTimeout: retry_timeout
|
68
|
+
}
|
69
|
+
)
|
70
|
+
end
|
92
71
|
|
93
|
-
|
94
|
-
end
|
72
|
+
private
|
95
73
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
type: ruby_type_to_operaton_type(value)
|
101
|
-
}
|
102
|
-
end
|
103
|
-
end
|
74
|
+
def validate_params!(params)
|
75
|
+
raise ArgumentError, "base_url cannot be nil or empty" if params[:base_url].to_s.strip.empty?
|
76
|
+
raise ArgumentError, "worker_id cannot be nil or empty" if params[:worker_id].to_s.strip.empty?
|
77
|
+
end
|
104
78
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
79
|
+
def build_topics_payload(topics_str, lock_duration, variables)
|
80
|
+
topic_names = topics_str.is_a?(Array) ? topics_str : topics_str.to_s.split(",")
|
81
|
+
topic_names.map do |name|
|
82
|
+
{
|
83
|
+
topicName: name.strip,
|
84
|
+
lockDuration: lock_duration,
|
85
|
+
variables: variables
|
86
|
+
}
|
113
87
|
end
|
114
88
|
end
|
115
89
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "faraday"
|
4
|
+
require "json"
|
5
|
+
require "faraday/multipart"
|
6
|
+
require "logger"
|
7
|
+
require_relative "base_operaton_client"
|
8
|
+
|
9
|
+
module Utils
|
10
|
+
module Operaton
|
11
|
+
##
|
12
|
+
# Client for deploying BPMN processes and starting process instances in Operaton (Camunda 7 API compatible)
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# client = Utils::Operaton::ProcessClient.new(base_url: "https://api.operaton.com")
|
16
|
+
# tasks = client.deploy_process(file_path, deployment_name: deployment_name)
|
17
|
+
#
|
18
|
+
class ProcessClient < BaseClient
|
19
|
+
def initialize(base_url:)
|
20
|
+
@logger = defined?(Rails) ? Rails.logger : Logger.new($stdout)
|
21
|
+
super(base_url: base_url)
|
22
|
+
end
|
23
|
+
|
24
|
+
def deploy_process(file_path, deployment_name:)
|
25
|
+
raise "File not found: #{file_path}" unless File.exist?(file_path)
|
26
|
+
raise "File is not readable: #{file_path}" unless File.readable?(file_path)
|
27
|
+
|
28
|
+
@logger.info "📁 Attempting to read file: #{file_path}"
|
29
|
+
@logger.info "📦 Deployment name: #{deployment_name}"
|
30
|
+
|
31
|
+
payload = {
|
32
|
+
"deployment-name" => deployment_name,
|
33
|
+
"deploy-changed-only" => "true",
|
34
|
+
"data" => Faraday::Multipart::FilePart.new(file_path, "application/octet-stream", File.basename(file_path))
|
35
|
+
}
|
36
|
+
|
37
|
+
post("/deployment/create", payload)
|
38
|
+
end
|
39
|
+
|
40
|
+
def instance_with_business_key_exists?(process_key, business_key)
|
41
|
+
query_params = {
|
42
|
+
processDefinitionKey: process_key,
|
43
|
+
maxResults: 50
|
44
|
+
}
|
45
|
+
|
46
|
+
response = get("/history/process-instance", query_params)
|
47
|
+
response.any? { |instance| instance["businessKey"] == business_key }
|
48
|
+
end
|
49
|
+
|
50
|
+
def start_process_instance_by_key(process_key, business_key:, variables: {}, validate_business_key: true)
|
51
|
+
validate_uniqueness!(process_key, business_key) if validate_business_key
|
52
|
+
|
53
|
+
json_payload = {
|
54
|
+
businessKey: business_key,
|
55
|
+
variables: format_variables(variables)
|
56
|
+
}
|
57
|
+
|
58
|
+
post(
|
59
|
+
"/process-definition/key/#{process_key}/start",
|
60
|
+
JSON.generate(json_payload),
|
61
|
+
{ "Content-Type" => "application/json" }
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def build_conn
|
68
|
+
Faraday.new(url: @base_url) do |f|
|
69
|
+
f.request :multipart
|
70
|
+
f.request :url_encoded
|
71
|
+
f.response :json, content_type: /\bjson$/
|
72
|
+
f.adapter Faraday.default_adapter
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_uniqueness!(process_key, business_key)
|
77
|
+
return unless instance_with_business_key_exists?(process_key, business_key)
|
78
|
+
|
79
|
+
raise "There is already an instance for processing '#{process_key}' with business key '#{business_key}'"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/bas/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bas
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- kommitters Open Source
|
@@ -23,6 +23,20 @@ dependencies:
|
|
23
23
|
- - "~>"
|
24
24
|
- !ruby/object:Gem::Version
|
25
25
|
version: '8.0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: faraday-multipart
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
26
40
|
- !ruby/object:Gem::Dependency
|
27
41
|
name: httparty
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
@@ -98,7 +112,9 @@ files:
|
|
98
112
|
- lib/bas/utils/notion/update_db_page.rb
|
99
113
|
- lib/bas/utils/notion/update_db_state.rb
|
100
114
|
- lib/bas/utils/openai/run_assistant.rb
|
115
|
+
- lib/bas/utils/operaton/base_operaton_client.rb
|
101
116
|
- lib/bas/utils/operaton/external_task_client.rb
|
117
|
+
- lib/bas/utils/operaton/process_client.rb
|
102
118
|
- lib/bas/utils/postgres/request.rb
|
103
119
|
- lib/bas/version.rb
|
104
120
|
- renovate.json
|