flowable 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/CHANGELOG.md +83 -0
- data/LICENSE +21 -0
- data/README.md +872 -0
- data/bin/flowable +510 -0
- data/lib/flowable/flowable.rb +273 -0
- data/lib/flowable/resources/base.rb +44 -0
- data/lib/flowable/resources/bpmn_deployments.rb +90 -0
- data/lib/flowable/resources/bpmn_history.rb +228 -0
- data/lib/flowable/resources/case_definitions.rb +115 -0
- data/lib/flowable/resources/case_instances.rb +188 -0
- data/lib/flowable/resources/deployments.rb +87 -0
- data/lib/flowable/resources/executions.rb +134 -0
- data/lib/flowable/resources/history.rb +264 -0
- data/lib/flowable/resources/plan_item_instances.rb +131 -0
- data/lib/flowable/resources/process_definitions.rb +142 -0
- data/lib/flowable/resources/process_instances.rb +200 -0
- data/lib/flowable/resources/tasks.rb +281 -0
- data/lib/flowable/version.rb +37 -0
- data/lib/flowable/workflow.rb +444 -0
- data/lib/flowable.rb +9 -0
- data/lib/flowable_client/resources/bpmn_history.rb +228 -0
- data/lib/flowable_client/resources/process_definitions.rb +142 -0
- data/lib/flowable_client/resources/process_instances.rb +200 -0
- metadata +104 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'base64'
|
|
7
|
+
require 'date'
|
|
8
|
+
|
|
9
|
+
require_relative 'version'
|
|
10
|
+
|
|
11
|
+
module Flowable
|
|
12
|
+
class Error < StandardError; end
|
|
13
|
+
class NotFoundError < Error; end
|
|
14
|
+
class UnauthorizedError < Error; end
|
|
15
|
+
class ForbiddenError < Error; end
|
|
16
|
+
class BadRequestError < Error; end
|
|
17
|
+
class ConflictError < Error; end
|
|
18
|
+
|
|
19
|
+
class Client
|
|
20
|
+
CMMN_BASE_PATH = '/flowable-rest/cmmn-api'
|
|
21
|
+
BPMN_BASE_PATH = '/flowable-rest/service'
|
|
22
|
+
|
|
23
|
+
attr_reader :host
|
|
24
|
+
attr_reader :port
|
|
25
|
+
attr_reader :username
|
|
26
|
+
attr_reader :base_path
|
|
27
|
+
attr_reader :bpmn_base_path
|
|
28
|
+
|
|
29
|
+
def initialize(host: 'localhost', port: 8080, username:, password:, base_path: CMMN_BASE_PATH,
|
|
30
|
+
bpmn_base_path: BPMN_BASE_PATH, use_ssl: false)
|
|
31
|
+
@host = host
|
|
32
|
+
@port = port
|
|
33
|
+
@username = username
|
|
34
|
+
@password = password
|
|
35
|
+
@base_path = base_path
|
|
36
|
+
@bpmn_base_path = bpmn_base_path
|
|
37
|
+
@use_ssl = use_ssl
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# CMMN Resource accessors
|
|
41
|
+
def deployments
|
|
42
|
+
@deployments ||= Resources::Deployments.new(self)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def case_definitions
|
|
46
|
+
@case_definitions ||= Resources::CaseDefinitions.new(self)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def case_instances
|
|
50
|
+
@case_instances ||= Resources::CaseInstances.new(self)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def tasks
|
|
54
|
+
@tasks ||= Resources::Tasks.new(self)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def plan_item_instances
|
|
58
|
+
@plan_item_instances ||= Resources::PlanItemInstances.new(self)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def history
|
|
62
|
+
@history ||= Resources::History.new(self)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# BPMN Resource accessors
|
|
66
|
+
def bpmn_deployments
|
|
67
|
+
@bpmn_deployments ||= Resources::BpmnDeployments.new(self)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def process_definitions
|
|
71
|
+
@process_definitions ||= Resources::ProcessDefinitions.new(self)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def process_instances
|
|
75
|
+
@process_instances ||= Resources::ProcessInstances.new(self)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def executions
|
|
79
|
+
@executions ||= Resources::Executions.new(self)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def bpmn_history
|
|
83
|
+
@bpmn_history ||= Resources::BpmnHistory.new(self)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# HTTP methods
|
|
87
|
+
def get(path, params = {})
|
|
88
|
+
request(:get, path, params: params)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def post(path, body = nil)
|
|
92
|
+
request(:post, path, body: body)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def put(path, body = nil)
|
|
96
|
+
request(:put, path, body: body)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def delete(path, params = {})
|
|
100
|
+
request(:delete, path, params: params)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def post_multipart(path, file_path, additional_fields = {})
|
|
104
|
+
uri = build_uri(path)
|
|
105
|
+
boundary = "----Flowable#{rand(1_000_000)}"
|
|
106
|
+
|
|
107
|
+
body = build_multipart_body(file_path, additional_fields, boundary)
|
|
108
|
+
|
|
109
|
+
http = build_http(uri)
|
|
110
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
|
111
|
+
request['Authorization'] = auth_header
|
|
112
|
+
request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
|
|
113
|
+
request.body = body
|
|
114
|
+
|
|
115
|
+
handle_response(http.request(request))
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
def request(method, path, params: {}, body: nil)
|
|
121
|
+
uri = build_uri(path, params)
|
|
122
|
+
http = build_http(uri)
|
|
123
|
+
|
|
124
|
+
request = build_request(method, uri, body)
|
|
125
|
+
handle_response(http.request(request))
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def build_uri(path, params = {})
|
|
129
|
+
# Determine base path - BPMN resources use service path
|
|
130
|
+
effective_base_path = path.start_with?('service/') || path.start_with?('repository/') || path.start_with?('runtime/') || (path.start_with?('history/') && !path.include?('cmmn')) ? @bpmn_base_path : @base_path
|
|
131
|
+
|
|
132
|
+
# For BPMN paths, strip the 'service/' prefix if present since it's in base path
|
|
133
|
+
adjusted_path = path.start_with?('service/') ? path.sub('service/', '') : path
|
|
134
|
+
|
|
135
|
+
uri = URI::HTTP.build(
|
|
136
|
+
host: @host,
|
|
137
|
+
port: @port,
|
|
138
|
+
path: "#{effective_base_path}/#{adjusted_path}".gsub('//', '/')
|
|
139
|
+
)
|
|
140
|
+
uri.query = URI.encode_www_form(params) unless params.empty?
|
|
141
|
+
uri
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def build_http(uri)
|
|
145
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
146
|
+
http.use_ssl = @use_ssl
|
|
147
|
+
http.read_timeout = 30
|
|
148
|
+
http.open_timeout = 10
|
|
149
|
+
http
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def build_request(method, uri, body)
|
|
153
|
+
request_class = {
|
|
154
|
+
get: Net::HTTP::Get,
|
|
155
|
+
post: Net::HTTP::Post,
|
|
156
|
+
put: Net::HTTP::Put,
|
|
157
|
+
delete: Net::HTTP::Delete
|
|
158
|
+
}[method]
|
|
159
|
+
|
|
160
|
+
request = request_class.new(uri.request_uri)
|
|
161
|
+
request['Authorization'] = auth_header
|
|
162
|
+
request['Accept'] = 'application/json'
|
|
163
|
+
request['Content-Type'] = 'application/json'
|
|
164
|
+
|
|
165
|
+
if body
|
|
166
|
+
request.body = body.is_a?(String) ? body : body.to_json
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
request
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def auth_header
|
|
173
|
+
credentials = Base64.strict_encode64("#{@username}:#{@password}")
|
|
174
|
+
"Basic #{credentials}"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def handle_response(response)
|
|
178
|
+
case response.code.to_i
|
|
179
|
+
when 200, 201
|
|
180
|
+
parse_response(response)
|
|
181
|
+
when 204
|
|
182
|
+
true
|
|
183
|
+
when 400
|
|
184
|
+
raise BadRequestError, parse_error_message(response)
|
|
185
|
+
when 401
|
|
186
|
+
raise UnauthorizedError, 'Invalid credentials'
|
|
187
|
+
when 404
|
|
188
|
+
raise NotFoundError, parse_error_message(response)
|
|
189
|
+
when 409
|
|
190
|
+
raise ConflictError, parse_error_message(response)
|
|
191
|
+
else
|
|
192
|
+
raise Error, "HTTP #{response.code}: #{parse_error_message(response)}"
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def parse_response(response)
|
|
197
|
+
return nil if response.body.nil? || response.body.empty?
|
|
198
|
+
|
|
199
|
+
body = response.body
|
|
200
|
+
content_type = response['Content-Type'] || ''
|
|
201
|
+
|
|
202
|
+
# Flowable bug workaround: resourcedata endpoint returns XML with Content-Type: application/json
|
|
203
|
+
# Check if body starts with XML declaration and return raw body
|
|
204
|
+
return body if body.start_with?('<?xml')
|
|
205
|
+
|
|
206
|
+
if content_type.include?('application/json')
|
|
207
|
+
# Handle Flowable bug: when Jackson exceeds nesting limit (1000),
|
|
208
|
+
# it appends an error message to incomplete JSON instead of returning proper error
|
|
209
|
+
if body.include?('{"message":"Bad request","exception":"Could not write JSON: Document nesting depth')
|
|
210
|
+
idx = body.index('{"message":"Bad request"')
|
|
211
|
+
# idx is guaranteed to be non-nil here since we already checked body includes the pattern
|
|
212
|
+
if idx.positive?
|
|
213
|
+
# Try to extract valid JSON before the error
|
|
214
|
+
body = body[0...(idx - 1)]
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
JSON.parse(body, max_nesting: 1500)
|
|
219
|
+
else
|
|
220
|
+
body
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def parse_error_message(response)
|
|
225
|
+
return response.message if response.body.nil? || response.body.empty?
|
|
226
|
+
|
|
227
|
+
parsed = JSON.parse(response.body)
|
|
228
|
+
parsed['errorMessage'] || parsed['message'] || response.body
|
|
229
|
+
rescue JSON::ParserError
|
|
230
|
+
response.body
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def build_multipart_body(file_path, additional_fields, boundary)
|
|
234
|
+
body = []
|
|
235
|
+
|
|
236
|
+
# Add file
|
|
237
|
+
filename = File.basename(file_path)
|
|
238
|
+
file_content = File.binread(file_path)
|
|
239
|
+
|
|
240
|
+
body << "--#{boundary}"
|
|
241
|
+
body << "Content-Disposition: form-data; name=\"file\"; filename=\"#{filename}\""
|
|
242
|
+
body << 'Content-Type: application/octet-stream'
|
|
243
|
+
body << ''
|
|
244
|
+
body << file_content
|
|
245
|
+
|
|
246
|
+
# Add additional fields
|
|
247
|
+
additional_fields.each do |name, value|
|
|
248
|
+
body << "--#{boundary}"
|
|
249
|
+
body << "Content-Disposition: form-data; name=\"#{name}\""
|
|
250
|
+
body << ''
|
|
251
|
+
body << value.to_s
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
body << "--#{boundary}--"
|
|
255
|
+
body.join("\r\n")
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# High-level DSL
|
|
261
|
+
require_relative 'resources/base'
|
|
262
|
+
require_relative 'resources/deployments'
|
|
263
|
+
require_relative 'resources/case_definitions'
|
|
264
|
+
require_relative 'resources/case_instances'
|
|
265
|
+
require_relative 'resources/tasks'
|
|
266
|
+
require_relative 'resources/plan_item_instances'
|
|
267
|
+
require_relative 'resources/history'
|
|
268
|
+
require_relative 'resources/bpmn_deployments'
|
|
269
|
+
require_relative 'resources/process_definitions'
|
|
270
|
+
require_relative 'resources/process_instances'
|
|
271
|
+
require_relative 'resources/executions'
|
|
272
|
+
require_relative 'resources/bpmn_history'
|
|
273
|
+
require_relative 'workflow'
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flowable
|
|
4
|
+
module Resources
|
|
5
|
+
class Base
|
|
6
|
+
attr_reader :client
|
|
7
|
+
|
|
8
|
+
def initialize(client)
|
|
9
|
+
@client = client
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def paginate_params(options)
|
|
15
|
+
params = {}
|
|
16
|
+
params[:start] = options[:start] if options[:start]
|
|
17
|
+
params[:size] = options[:size] if options[:size]
|
|
18
|
+
params[:sort] = options[:sort] if options[:sort]
|
|
19
|
+
params[:order] = options[:order] if options[:order]
|
|
20
|
+
params
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def build_variables_array(variables)
|
|
24
|
+
return [] unless variables
|
|
25
|
+
|
|
26
|
+
variables.map do |name, value|
|
|
27
|
+
var = { name: name.to_s, value: value }
|
|
28
|
+
var[:type] = infer_type(value)
|
|
29
|
+
var
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def infer_type(value)
|
|
34
|
+
case value
|
|
35
|
+
when Integer then 'long'
|
|
36
|
+
when Float then 'double'
|
|
37
|
+
when TrueClass, FalseClass then 'boolean'
|
|
38
|
+
when Date, Time, DateTime then 'date'
|
|
39
|
+
else 'string'
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flowable
|
|
4
|
+
module Resources
|
|
5
|
+
class BpmnDeployments < Base
|
|
6
|
+
BASE_PATH = 'service/repository/deployments'
|
|
7
|
+
|
|
8
|
+
# List all BPMN deployments
|
|
9
|
+
# @param options [Hash] Query parameters
|
|
10
|
+
# @option options [String] :name Filter by exact name
|
|
11
|
+
# @option options [String] :nameLike Filter by name pattern (use % wildcard)
|
|
12
|
+
# @option options [String] :category Filter by category
|
|
13
|
+
# @option options [String] :tenantId Filter by tenant
|
|
14
|
+
# @option options [Integer] :start Pagination start (default: 0)
|
|
15
|
+
# @option options [Integer] :size Page size (default: 10)
|
|
16
|
+
# @option options [String] :sort Sort field (id/name/deployTime/tenantId)
|
|
17
|
+
# @option options [String] :order Sort order (asc/desc)
|
|
18
|
+
# @return [Hash] Paginated list of deployments
|
|
19
|
+
def list(**options)
|
|
20
|
+
params = paginate_params(options)
|
|
21
|
+
params[:name] = options[:name] if options[:name]
|
|
22
|
+
params[:nameLike] = options[:nameLike] if options[:nameLike]
|
|
23
|
+
params[:category] = options[:category] if options[:category]
|
|
24
|
+
params[:tenantId] = options[:tenantId] if options[:tenantId]
|
|
25
|
+
params[:withoutTenantId] = options[:withoutTenantId] if options[:withoutTenantId]
|
|
26
|
+
|
|
27
|
+
client.get(BASE_PATH, params)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Get a specific deployment
|
|
31
|
+
# @param deployment_id [String] The deployment ID
|
|
32
|
+
# @return [Hash] Deployment details
|
|
33
|
+
def get(deployment_id)
|
|
34
|
+
client.get("#{BASE_PATH}/#{deployment_id}")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Create a new deployment from a file
|
|
38
|
+
# @param file_path [String] Path to BPMN file (.bpmn, .bpmn20.xml, .bar, .zip)
|
|
39
|
+
# @param name [String] Optional deployment name
|
|
40
|
+
# @param tenant_id [String] Optional tenant ID
|
|
41
|
+
# @param category [String] Optional category
|
|
42
|
+
# @return [Hash] Created deployment
|
|
43
|
+
def create(file_path, name: nil, tenant_id: nil, category: nil)
|
|
44
|
+
additional_fields = {}
|
|
45
|
+
additional_fields[:deploymentName] = name if name
|
|
46
|
+
additional_fields[:tenantId] = tenant_id if tenant_id
|
|
47
|
+
additional_fields[:category] = category if category
|
|
48
|
+
|
|
49
|
+
client.post_multipart(BASE_PATH, file_path, additional_fields)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Delete a deployment
|
|
53
|
+
# @param deployment_id [String] The deployment ID
|
|
54
|
+
# @param cascade [Boolean] Also delete running process instances
|
|
55
|
+
# @return [Boolean] true if successful
|
|
56
|
+
def delete(deployment_id, cascade: false)
|
|
57
|
+
params = {}
|
|
58
|
+
params[:cascade] = true if cascade
|
|
59
|
+
client.delete("#{BASE_PATH}/#{deployment_id}", params)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# List resources in a deployment
|
|
63
|
+
# @param deployment_id [String] The deployment ID
|
|
64
|
+
# @return [Array<Hash>] List of resources
|
|
65
|
+
def resources(deployment_id)
|
|
66
|
+
client.get("#{BASE_PATH}/#{deployment_id}/resources")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Get a specific resource from a deployment
|
|
70
|
+
# @param deployment_id [String] The deployment ID
|
|
71
|
+
# @param resource_id [String] The resource ID (URL-encoded if contains /)
|
|
72
|
+
# @return [Hash] Resource details
|
|
73
|
+
def resource(deployment_id, resource_id)
|
|
74
|
+
encoded_resource_id = URI.encode_www_form_component(resource_id)
|
|
75
|
+
client.get("#{BASE_PATH}/#{deployment_id}/resources/#{encoded_resource_id}")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Get the content of a deployment resource
|
|
79
|
+
# @param deployment_id [String] The deployment ID
|
|
80
|
+
# @param resource_id [String] The resource ID
|
|
81
|
+
# @return [String] Raw resource content
|
|
82
|
+
def resource_data(deployment_id, resource_id)
|
|
83
|
+
encoded_resource_id = URI.encode_www_form_component(resource_id)
|
|
84
|
+
client.get("#{BASE_PATH}/#{deployment_id}/resourcedata/#{encoded_resource_id}")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
alias resource_content resource_data
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Flowable
|
|
4
|
+
module Resources
|
|
5
|
+
class BpmnHistory < Base
|
|
6
|
+
# --- Historic Process Instances ---
|
|
7
|
+
|
|
8
|
+
# List historic process instances
|
|
9
|
+
# @param options [Hash] Query parameters
|
|
10
|
+
# @option options [String] :processInstanceId Filter by process instance ID
|
|
11
|
+
# @option options [String] :processDefinitionKey Filter by definition key
|
|
12
|
+
# @option options [String] :processDefinitionId Filter by definition ID
|
|
13
|
+
# @option options [String] :businessKey Filter by business key
|
|
14
|
+
# @option options [String] :involvedUser Filter by involved user
|
|
15
|
+
# @option options [Boolean] :finished Only finished instances
|
|
16
|
+
# @option options [Boolean] :includeProcessVariables Include variables
|
|
17
|
+
# @option options [String] :tenantId Filter by tenant
|
|
18
|
+
# @return [Hash] Paginated list of historic process instances
|
|
19
|
+
def process_instances(**options)
|
|
20
|
+
params = paginate_params(options)
|
|
21
|
+
%i[processInstanceId processDefinitionKey processDefinitionId businessKey
|
|
22
|
+
involvedUser superProcessInstanceId startedBy tenantId tenantIdLike].each do |key|
|
|
23
|
+
params[key] = options[key] if options[key]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
%i[finished includeProcessVariables withoutTenantId].each do |key|
|
|
27
|
+
params[key] = options[key] if options.key?(key)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
%i[finishedAfter finishedBefore startedAfter startedBefore].each do |key|
|
|
31
|
+
params[key] = format_date(options[key]) if options[key]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
client.get('service/history/historic-process-instances', params)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Get a specific historic process instance
|
|
38
|
+
# @param process_instance_id [String] The process instance ID
|
|
39
|
+
# @return [Hash] Historic process instance details
|
|
40
|
+
def process_instance(process_instance_id)
|
|
41
|
+
client.get("service/history/historic-process-instances/#{process_instance_id}")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Delete a historic process instance
|
|
45
|
+
# @param process_instance_id [String] The process instance ID
|
|
46
|
+
# @return [Boolean] true if successful
|
|
47
|
+
def delete_process_instance(process_instance_id)
|
|
48
|
+
client.delete("service/history/historic-process-instances/#{process_instance_id}")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Query historic process instances with complex filters
|
|
52
|
+
# @param query [Hash] Query body
|
|
53
|
+
# @return [Hash] Paginated list of historic process instances
|
|
54
|
+
def query_process_instances(query)
|
|
55
|
+
client.post('service/query/historic-process-instances', query)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Get identity links for a historic process instance
|
|
59
|
+
# @param process_instance_id [String] The process instance ID
|
|
60
|
+
# @return [Array<Hash>] List of identity links
|
|
61
|
+
def process_instance_identity_links(process_instance_id)
|
|
62
|
+
client.get("service/history/historic-process-instances/#{process_instance_id}/identitylinks")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# --- Historic Activity Instances ---
|
|
66
|
+
|
|
67
|
+
# List historic activity instances
|
|
68
|
+
# @param options [Hash] Query parameters
|
|
69
|
+
# @option options [String] :activityId Filter by activity ID
|
|
70
|
+
# @option options [String] :activityName Filter by activity name
|
|
71
|
+
# @option options [String] :activityType Filter by type (userTask, serviceTask, etc.)
|
|
72
|
+
# @option options [String] :processInstanceId Filter by process instance
|
|
73
|
+
# @option options [String] :processDefinitionId Filter by process definition
|
|
74
|
+
# @option options [Boolean] :finished Only finished activities
|
|
75
|
+
# @return [Hash] Paginated list of historic activities
|
|
76
|
+
def activity_instances(**options)
|
|
77
|
+
params = paginate_params(options)
|
|
78
|
+
%i[activityId activityName activityType processInstanceId processDefinitionId
|
|
79
|
+
executionId taskAssignee tenantId tenantIdLike].each do |key|
|
|
80
|
+
params[key] = options[key] if options[key]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
params[:finished] = options[:finished] if options.key?(:finished)
|
|
84
|
+
params[:withoutTenantId] = options[:withoutTenantId] if options.key?(:withoutTenantId)
|
|
85
|
+
|
|
86
|
+
client.get('service/history/historic-activity-instances', params)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Query historic activities with complex filters
|
|
90
|
+
# @param query [Hash] Query body
|
|
91
|
+
# @return [Hash] Paginated list of historic activities
|
|
92
|
+
def query_activity_instances(query)
|
|
93
|
+
client.post('service/query/historic-activity-instances', query)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# --- Historic Task Instances ---
|
|
97
|
+
|
|
98
|
+
# List historic task instances
|
|
99
|
+
# @param options [Hash] Query parameters
|
|
100
|
+
# @option options [String] :taskId Filter by task ID
|
|
101
|
+
# @option options [String] :processInstanceId Filter by process instance
|
|
102
|
+
# @option options [String] :processDefinitionId Filter by process definition
|
|
103
|
+
# @option options [String] :taskName Filter by name
|
|
104
|
+
# @option options [String] :taskAssignee Filter by assignee
|
|
105
|
+
# @option options [Boolean] :finished Only finished tasks
|
|
106
|
+
# @return [Hash] Paginated list of historic tasks
|
|
107
|
+
def task_instances(**options)
|
|
108
|
+
params = paginate_params(options)
|
|
109
|
+
%i[taskId processInstanceId processDefinitionId processDefinitionKey
|
|
110
|
+
taskName taskNameLike taskDescription taskDescriptionLike
|
|
111
|
+
taskDefinitionKey taskDeleteReason taskDeleteReasonLike
|
|
112
|
+
taskAssignee taskAssigneeLike taskOwner taskOwnerLike
|
|
113
|
+
taskInvolvedUser taskPriority parentTaskId tenantId tenantIdLike].each do |key|
|
|
114
|
+
params[key] = options[key] if options[key]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
%i[finished processFinished withoutDueDate includeTaskLocalVariables withoutTenantId].each do |key|
|
|
118
|
+
params[key] = options[key] if options.key?(key)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
%i[dueDate dueDateAfter dueDateBefore taskCompletedOn taskCompletedAfter
|
|
122
|
+
taskCompletedBefore taskCreatedOn taskCreatedBefore taskCreatedAfter].each do |key|
|
|
123
|
+
params[key] = format_date(options[key]) if options[key]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
client.get('service/history/historic-task-instances', params)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Get a specific historic task
|
|
130
|
+
# @param task_id [String] The task ID
|
|
131
|
+
# @return [Hash] Historic task details
|
|
132
|
+
def task_instance(task_id)
|
|
133
|
+
client.get("service/history/historic-task-instances/#{task_id}")
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Delete a historic task
|
|
137
|
+
# @param task_id [String] The task ID
|
|
138
|
+
# @return [Boolean] true if successful
|
|
139
|
+
def delete_task_instance(task_id)
|
|
140
|
+
client.delete("service/history/historic-task-instances/#{task_id}")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Query historic tasks with complex filters
|
|
144
|
+
# @param query [Hash] Query body
|
|
145
|
+
# @return [Hash] Paginated list of historic tasks
|
|
146
|
+
def query_task_instances(query)
|
|
147
|
+
client.post('service/query/historic-task-instances', query)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# --- Historic Variable Instances ---
|
|
151
|
+
|
|
152
|
+
# List historic variable instances
|
|
153
|
+
# @param options [Hash] Query parameters
|
|
154
|
+
# @option options [String] :processInstanceId Filter by process instance
|
|
155
|
+
# @option options [String] :taskId Filter by task
|
|
156
|
+
# @option options [Boolean] :excludeTaskVariables Exclude task variables
|
|
157
|
+
# @option options [String] :variableName Filter by variable name
|
|
158
|
+
# @return [Hash] Paginated list of historic variables
|
|
159
|
+
def variable_instances(**options)
|
|
160
|
+
params = paginate_params(options)
|
|
161
|
+
%i[processInstanceId taskId variableName variableNameLike].each do |key|
|
|
162
|
+
params[key] = options[key] if options[key]
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
params[:excludeTaskVariables] = options[:excludeTaskVariables] if options.key?(:excludeTaskVariables)
|
|
166
|
+
|
|
167
|
+
client.get('service/history/historic-variable-instances', params)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Query historic variables with complex filters
|
|
171
|
+
# @param query [Hash] Query body
|
|
172
|
+
# @return [Hash] Paginated list of historic variables
|
|
173
|
+
def query_variable_instances(query)
|
|
174
|
+
client.post('service/query/historic-variable-instances', query)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# --- Historic Detail ---
|
|
178
|
+
|
|
179
|
+
# List historic details (variable updates, form properties)
|
|
180
|
+
# @param options [Hash] Query parameters
|
|
181
|
+
# @option options [String] :processInstanceId Filter by process instance
|
|
182
|
+
# @option options [String] :executionId Filter by execution
|
|
183
|
+
# @option options [String] :activityInstanceId Filter by activity instance
|
|
184
|
+
# @option options [String] :taskId Filter by task
|
|
185
|
+
# @option options [Boolean] :selectOnlyFormProperties Only form properties
|
|
186
|
+
# @option options [Boolean] :selectOnlyVariableUpdates Only variable updates
|
|
187
|
+
# @return [Hash] Paginated list of historic details
|
|
188
|
+
def details(**options)
|
|
189
|
+
params = paginate_params(options)
|
|
190
|
+
%i[processInstanceId executionId activityInstanceId taskId].each do |key|
|
|
191
|
+
params[key] = options[key] if options[key]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
if options.key?(:selectOnlyFormProperties)
|
|
195
|
+
params[:selectOnlyFormProperties] =
|
|
196
|
+
options[:selectOnlyFormProperties]
|
|
197
|
+
end
|
|
198
|
+
if options.key?(:selectOnlyVariableUpdates)
|
|
199
|
+
params[:selectOnlyVariableUpdates] =
|
|
200
|
+
options[:selectOnlyVariableUpdates]
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
client.get('service/history/historic-detail', params)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Query historic details with complex filters
|
|
207
|
+
# @param query [Hash] Query body
|
|
208
|
+
# @return [Hash] Paginated list of historic details
|
|
209
|
+
def query_details(query)
|
|
210
|
+
client.post('service/query/historic-detail', query)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Aliases for backward compatibility
|
|
214
|
+
alias tasks task_instances
|
|
215
|
+
alias activities activity_instances
|
|
216
|
+
alias variables variable_instances
|
|
217
|
+
|
|
218
|
+
private
|
|
219
|
+
|
|
220
|
+
def format_date(date)
|
|
221
|
+
return date if date.is_a?(String)
|
|
222
|
+
return date.iso8601 if date.respond_to?(:iso8601)
|
|
223
|
+
|
|
224
|
+
date.to_s
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|