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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a27720481a463c1a4a6edaeda8d9c159d009a11546802f559fd4232be721a146
4
- data.tar.gz: fb829e6b146c295403118870324641d6e8e57ca3231259493aa686c4045d2b80
3
+ metadata.gz: fe416744cd3c3fb9f0902dfc6a3b1432b63425a00da2cea4828a69b0f7e4595a
4
+ data.tar.gz: 02701d0e8b83c71e0721ebae2a197c6f9204414d3a6f313052152bc2e6957863
5
5
  SHA512:
6
- metadata.gz: 8a76d3b14aaf04f243a26968e79726025156d491f47ecde2435b5f83c316d3275a61f5cda1de54105459e0d420678c5379194d59f7c7e3d21c8208c10305c730
7
- data.tar.gz: 34d41b30c0b3520edd259d325973b6b6a87467f7aac874ee17b630a292b68c57155c40157748a8a5c37c593e93be68dd9cee94136b55155d30324a6744a3d2db
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
- module Bas
7
- module Utils
8
- module Operaton
9
- # Client for interacting with Operaton's External Task API
10
- #
11
- # This client provides methods to manage external task lifecycle including:
12
- # - Fetching and locking tasks
13
- # - Completing tasks with variables
14
- # - Unlocking tasks
15
- # - Reporting task failures
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
- def full_url(path)
79
- "#{@base_url}#{path}"
80
- end
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
- def post(path, body = {})
83
- handle_response(@conn.post(full_url(path), body))
84
- end
51
+ def get_variables(task_id)
52
+ get("/external-task/#{task_id}/variables")
53
+ end
85
54
 
86
- def get(path, params = {})
87
- handle_response(@conn.get(full_url(path), params))
88
- end
55
+ def unlock(task_id)
56
+ post("/external-task/#{task_id}/unlock")
57
+ end
89
58
 
90
- def handle_response(response)
91
- raise "Operaton API Error #{response.status}: #{response.body}" unless response.success?
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
- response.body
94
- end
72
+ private
95
73
 
96
- def format_variables(vars)
97
- vars.transform_values do |value|
98
- {
99
- value: value,
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
- def ruby_type_to_operaton_type(value)
106
- case value
107
- when String then "String"
108
- when Integer then "Integer"
109
- when Float then "Double"
110
- when TrueClass, FalseClass then "Boolean"
111
- else "Object"
112
- end
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Bas
4
4
  # Gem version
5
- VERSION = "1.8.0"
5
+ VERSION = "1.9.0"
6
6
  end
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.8.0
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