bas 1.8.1 → 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: 83923ab43148fb334e5809f95b2c4387b6bffccb36a7b034f9f6067ad9a75783
4
- data.tar.gz: 6d78bf8adf6e1644c85fc77c6ff3b60f21953fac861aa1dfc99210422dc84dba
3
+ metadata.gz: fe416744cd3c3fb9f0902dfc6a3b1432b63425a00da2cea4828a69b0f7e4595a
4
+ data.tar.gz: 02701d0e8b83c71e0721ebae2a197c6f9204414d3a6f313052152bc2e6957863
5
5
  SHA512:
6
- metadata.gz: 792b8feddf38fd47f80d1b3443e4a019e2e0fdae196aa02fe6c5949492ffc586abec3a64ba4fcedd0cd53994da2bdaff81008a0d9ffe0c356e7d68650f83b84f
7
- data.tar.gz: 73b45576f90b0d47448d78fec58f80ceca36304aa1b52e9c72813c7f102a0cdee71b80d0e1528e8d7f135af618ab2732d48e6e833746b7a46d2824d7a75a8265
6
+ metadata.gz: b63e685728abb1a3c80ab896aca20f9621a08b3f3defa9ca682912e9722359a3510b2bfd65154bedc3a0171f372ea9f1df63ae6305bbfc48b28f36477c8c2761
7
+ data.tar.gz: 7f73e57bc600b044a82deb51c97b30eab54bc5de48122f554d18614e08a273950fa705f5403accd6ff5899e54dbba5ccb3634cf0504769073a55d8d6a7b695bc
data/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
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
+
3
6
  # 1.8.1 (08.07.2025)
4
7
  - [refactor: Refactor client for correct use of shared_storage](https://github.com/kommitters/bas/pull/144)
5
8
 
@@ -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,10 +2,12 @@
2
2
 
3
3
  require "faraday"
4
4
  require "json"
5
+ require_relative "base_operaton_client"
5
6
 
6
7
  module Utils
7
8
  module Operaton
8
- # Client for interacting with Operaton's External Task API
9
+ ##
10
+ # Client for interacting with Operaton's External Task API.
9
11
  #
10
12
  # This client provides methods to manage external task lifecycle including:
11
13
  # - Fetching and locking tasks
@@ -14,42 +16,35 @@ module Utils
14
16
  # - Reporting task failures
15
17
  #
16
18
  # @example
17
- # client = Utils::Operaton::ExternalTaskClient.execute(base_url: "https://api.operaton.com",
18
- # worker_id: "worker-123")
19
+ # client = Utils::Operaton::ExternalTaskClient.new(base_url: "https://api.operaton.com", worker_id: "worker-123")
19
20
  # tasks = client.fetch_and_lock("my-topic")
20
- class ExternalTaskClient
21
- def self.execute(params)
22
- new(params)
23
- end
24
-
21
+ #
22
+ class ExternalTaskClient < BaseClient
25
23
  def initialize(params)
26
- validate_params!(params)
27
-
28
- @base_url = params[:base_url]
29
24
  @worker_id = params[:worker_id]
30
-
31
- @conn = Faraday.new(url: @base_url) do |f|
32
- f.request :json
33
- f.response :json, content_type: /\bjson$/
34
- f.adapter Faraday.default_adapter
35
- end
25
+ validate_params!(params)
26
+ super(base_url: params[:base_url])
36
27
  end
37
28
 
38
29
  def fetch_and_lock(topics_str, lock_duration: 10_000, max_tasks: 1, use_priority: true, variables: [])
39
30
  post(
40
31
  "/external-task/fetchAndLock",
41
- workerId: @worker_id,
42
- maxTasks: max_tasks,
43
- usePriority: use_priority,
44
- topics: build_topics_payload(topics_str, lock_duration, variables)
32
+ {
33
+ workerId: @worker_id,
34
+ maxTasks: max_tasks,
35
+ usePriority: use_priority,
36
+ topics: build_topics_payload(topics_str, lock_duration, variables)
37
+ }
45
38
  )
46
39
  end
47
40
 
48
41
  def complete(task_id, variables = {})
49
42
  post(
50
43
  "/external-task/#{task_id}/complete",
51
- workerId: @worker_id,
52
- variables: format_variables(variables)
44
+ {
45
+ workerId: @worker_id,
46
+ variables: format_variables(variables)
47
+ }
53
48
  )
54
49
  end
55
50
 
@@ -64,11 +59,13 @@ module Utils
64
59
  def report_failure(task_id, error_message:, error_details:, retries:, retry_timeout:)
65
60
  post(
66
61
  "/external-task/#{task_id}/failure",
67
- workerId: @worker_id,
68
- errorMessage: error_message,
69
- errorDetails: error_details,
70
- retries: retries,
71
- retryTimeout: retry_timeout
62
+ {
63
+ workerId: @worker_id,
64
+ errorMessage: error_message,
65
+ errorDetails: error_details,
66
+ retries: retries,
67
+ retryTimeout: retry_timeout
68
+ }
72
69
  )
73
70
  end
74
71
 
@@ -89,43 +86,6 @@ module Utils
89
86
  }
90
87
  end
91
88
  end
92
-
93
- def full_url(path)
94
- "#{@base_url}#{path}"
95
- end
96
-
97
- def post(path, body = {})
98
- handle_response(@conn.post(full_url(path), body))
99
- end
100
-
101
- def get(path, params = {})
102
- handle_response(@conn.get(full_url(path), params))
103
- end
104
-
105
- def handle_response(response)
106
- raise "Operaton API Error #{response.status}: #{response.body}" unless response.success?
107
-
108
- response.body
109
- end
110
-
111
- def format_variables(vars)
112
- vars.transform_values do |value|
113
- {
114
- value: value,
115
- type: ruby_type_to_operaton_type(value)
116
- }
117
- end
118
- end
119
-
120
- def ruby_type_to_operaton_type(value)
121
- case value
122
- when String then "String"
123
- when Integer then "Integer"
124
- when Float then "Double"
125
- when TrueClass, FalseClass then "Boolean"
126
- else "Object"
127
- end
128
- end
129
89
  end
130
90
  end
131
91
  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.1"
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.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