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.
@@ -0,0 +1,444 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../flowable'
4
+
5
+ module Flowable
6
+ # High-level DSL for workflow management
7
+ # Provides a more intuitive API for common workflow operations
8
+ module Workflow
9
+ class Case
10
+ attr_reader :client
11
+ attr_reader :case_key
12
+ attr_reader :instance
13
+
14
+ def initialize(client, case_key)
15
+ @client = client
16
+ @case_key = case_key
17
+ @instance = nil
18
+ @task_handlers = {}
19
+ @milestone_callbacks = {}
20
+ end
21
+
22
+ # Start a new case instance
23
+ # @param variables [Hash] Initial variables
24
+ # @param business_key [String] Business key
25
+ # @return [self]
26
+ def start(variables: {}, business_key: nil)
27
+ @instance = client.case_instances.start_by_key(
28
+ case_key,
29
+ variables: variables,
30
+ business_key: business_key,
31
+ return_variables: true
32
+ )
33
+ self
34
+ end
35
+
36
+ # Load an existing case instance
37
+ # @param case_instance_id [String] Case instance ID
38
+ # @return [self]
39
+ def load(case_instance_id)
40
+ @instance = client.case_instances.get(case_instance_id)
41
+ self
42
+ end
43
+
44
+ # Find case by business key
45
+ # @param business_key [String] Business key
46
+ # @return [self, nil]
47
+ def find_by_business_key(business_key)
48
+ result = client.case_instances.list(
49
+ caseDefinitionKey: case_key,
50
+ businessKey: business_key,
51
+ size: 1
52
+ )
53
+ return nil if result['data'].empty?
54
+
55
+ @instance = result['data'].first
56
+ self
57
+ end
58
+
59
+ # Get case instance ID
60
+ def id
61
+ instance&.dig('id')
62
+ end
63
+
64
+ # Get case state
65
+ def state
66
+ refresh! if instance
67
+ instance&.dig('state')
68
+ end
69
+
70
+ # Check if case is active
71
+ def active?
72
+ state == 'active'
73
+ end
74
+
75
+ # Check if case is completed
76
+ def completed?
77
+ instance&.dig('ended') == true || instance&.dig('completed') == true
78
+ end
79
+
80
+ # Refresh case instance data from server
81
+ def refresh!
82
+ @instance = client.case_instances.get(id) if id
83
+ self
84
+ end
85
+
86
+ # Get all variables
87
+ def variables
88
+ return {} unless id
89
+
90
+ client.case_instances.variables(id).each_with_object({}) do |var, hash|
91
+ hash[var['name'].to_sym] = var['value']
92
+ end
93
+ end
94
+
95
+ # Get a single variable
96
+ def [](name)
97
+ variables[name.to_sym]
98
+ end
99
+
100
+ # Set variables
101
+ def []=(name, value)
102
+ set(name => value)
103
+ end
104
+
105
+ # Set multiple variables
106
+ def set(vars)
107
+ client.case_instances.set_variables(id, vars)
108
+ self
109
+ end
110
+
111
+ # Get stage overview
112
+ def stages
113
+ return [] unless id
114
+
115
+ client.case_instances.stage_overview(id).map do |stage|
116
+ Stage.new(stage)
117
+ end
118
+ end
119
+
120
+ # Get current stage
121
+ def current_stage
122
+ stages.find(&:current?)
123
+ end
124
+
125
+ # Get all tasks for this case
126
+ def tasks
127
+ return [] unless id
128
+
129
+ result = client.tasks.list(caseInstanceId: id)
130
+ result['data'].map { |t| Task.new(client, t) }
131
+ end
132
+
133
+ # Get pending tasks (unassigned or assigned to current user)
134
+ def pending_tasks
135
+ tasks.reject(&:completed?)
136
+ end
137
+
138
+ # Find task by name
139
+ def task(name)
140
+ tasks.find { |t| t.name == name }
141
+ end
142
+
143
+ # Wait for a task to appear (polling)
144
+ # @param name [String] Task name
145
+ # @param timeout [Integer] Timeout in seconds
146
+ # @param interval [Integer] Poll interval in seconds
147
+ # @yield [Task] Block to execute when task is found
148
+ def wait_for_task(name, timeout: 30, interval: 1)
149
+ start_time = Time.now
150
+ loop do
151
+ task = self.task(name)
152
+ if task
153
+ yield task if block_given?
154
+ return task
155
+ end
156
+
157
+ if Time.now - start_time > timeout
158
+ raise Error, "Timeout waiting for task '#{name}'"
159
+ end
160
+
161
+ sleep interval
162
+ refresh!
163
+ end
164
+ end
165
+
166
+ # Register a task handler
167
+ # @param task_name [String] Task name to handle
168
+ # @yield [Task] Block to execute when task is available
169
+ def on_task(task_name, &block)
170
+ @task_handlers[task_name] = block
171
+ self
172
+ end
173
+
174
+ # Process all pending tasks with registered handlers
175
+ def process_tasks!
176
+ pending_tasks.each do |task|
177
+ handler = @task_handlers[task.name]
178
+ handler&.call(task)
179
+ end
180
+ self
181
+ end
182
+
183
+ # Delete the case instance
184
+ def delete!
185
+ client.case_instances.delete(id) if id
186
+ @instance = nil
187
+ end
188
+
189
+ # Get identity links
190
+ def involved_users
191
+ return [] unless id
192
+
193
+ client.case_instances.identity_links(id)
194
+ end
195
+
196
+ # Add involved user
197
+ def involve(user_id, type: 'participant')
198
+ client.case_instances.add_involved_user(id, user_id, type: type)
199
+ self
200
+ end
201
+ end
202
+
203
+ class Process
204
+ attr_reader :client
205
+ attr_reader :process_key
206
+ attr_reader :instance
207
+
208
+ def initialize(client, process_key)
209
+ @client = client
210
+ @process_key = process_key
211
+ @instance = nil
212
+ end
213
+
214
+ # Start a new process instance
215
+ def start(variables: {}, business_key: nil)
216
+ @instance = client.process_instances.start_by_key(
217
+ process_key,
218
+ variables: variables,
219
+ business_key: business_key,
220
+ return_variables: true
221
+ )
222
+ self
223
+ end
224
+
225
+ # Load an existing process instance
226
+ def load(process_instance_id)
227
+ @instance = client.process_instances.get(process_instance_id)
228
+ self
229
+ end
230
+
231
+ def id
232
+ instance&.dig('id')
233
+ end
234
+
235
+ def ended?
236
+ instance&.dig('ended') == true
237
+ end
238
+
239
+ def suspended?
240
+ instance&.dig('suspended') == true
241
+ end
242
+
243
+ def refresh!
244
+ @instance = client.process_instances.get(id) if id
245
+ self
246
+ end
247
+
248
+ def variables
249
+ return {} unless id
250
+
251
+ client.process_instances.variables(id).each_with_object({}) do |var, hash|
252
+ hash[var['name'].to_sym] = var['value']
253
+ end
254
+ end
255
+
256
+ def set(vars)
257
+ client.process_instances.set_variables(id, vars)
258
+ self
259
+ end
260
+
261
+ def suspend!
262
+ client.process_instances.suspend(id)
263
+ refresh!
264
+ end
265
+
266
+ def activate!
267
+ client.process_instances.activate(id)
268
+ refresh!
269
+ end
270
+
271
+ def delete!(reason: nil)
272
+ client.process_instances.delete(id, delete_reason: reason)
273
+ @instance = nil
274
+ end
275
+ end
276
+
277
+ class Task
278
+ attr_reader :client
279
+ attr_reader :data
280
+
281
+ def initialize(client, data)
282
+ @client = client
283
+ @data = data
284
+ end
285
+
286
+ def id
287
+ data['id']
288
+ end
289
+
290
+ def name
291
+ data['name']
292
+ end
293
+
294
+ def description
295
+ data['description']
296
+ end
297
+
298
+ def assignee
299
+ data['assignee']
300
+ end
301
+
302
+ def owner
303
+ data['owner']
304
+ end
305
+
306
+ def priority
307
+ data['priority']
308
+ end
309
+
310
+ def due_date
311
+ data['dueDate']
312
+ end
313
+
314
+ def created_at
315
+ data['createTime']
316
+ end
317
+
318
+ def case_instance_id
319
+ data['caseInstanceId']
320
+ end
321
+
322
+ def process_instance_id
323
+ data['processInstanceId']
324
+ end
325
+
326
+ def completed?
327
+ !data['endTime'].nil? && !data['endTime'].to_s.empty?
328
+ end
329
+
330
+ def assigned?
331
+ !assignee.nil?
332
+ end
333
+
334
+ # Claim the task
335
+ def claim(user)
336
+ client.tasks.claim(id, user)
337
+ @data['assignee'] = user
338
+ self
339
+ end
340
+
341
+ # Unclaim the task
342
+ def unclaim
343
+ client.tasks.unclaim(id)
344
+ @data['assignee'] = nil
345
+ self
346
+ end
347
+
348
+ # Complete the task
349
+ def complete(variables: {}, outcome: nil)
350
+ client.tasks.complete(id, variables: variables, outcome: outcome)
351
+ self
352
+ end
353
+
354
+ # Delegate to another user
355
+ def delegate_to(user)
356
+ client.tasks.delegate(id, user)
357
+ self
358
+ end
359
+
360
+ # Resolve delegated task
361
+ def resolve
362
+ client.tasks.resolve(id)
363
+ self
364
+ end
365
+
366
+ # Get task variables
367
+ def variables(scope: nil)
368
+ client.tasks.variables(id, scope: scope).each_with_object({}) do |var, hash|
369
+ hash[var['name'].to_sym] = var['value']
370
+ end
371
+ end
372
+
373
+ # Set task variables
374
+ def set(vars, scope: 'local')
375
+ vars.each do |name, value|
376
+ client.tasks.update_variable(id, name.to_s, value, scope: scope)
377
+ end
378
+ self
379
+ end
380
+
381
+ # Update task properties
382
+ def update(attrs)
383
+ client.tasks.update(id, **attrs)
384
+ attrs.each { |k, v| @data[k.to_s] = v }
385
+ self
386
+ end
387
+
388
+ # Add candidate user
389
+ def add_candidate(user_id)
390
+ client.tasks.add_user_identity_link(id, user_id, type: 'candidate')
391
+ self
392
+ end
393
+
394
+ # Add candidate group
395
+ def add_candidate_group(group_id)
396
+ client.tasks.add_group_identity_link(id, group_id, type: 'candidate')
397
+ self
398
+ end
399
+ end
400
+
401
+ class Stage
402
+ attr_reader :data
403
+
404
+ def initialize(data)
405
+ @data = data
406
+ end
407
+
408
+ def id
409
+ data['id']
410
+ end
411
+
412
+ def name
413
+ data['name']
414
+ end
415
+
416
+ def current?
417
+ data['current'] == true
418
+ end
419
+
420
+ def ended?
421
+ data['ended'] == true
422
+ end
423
+
424
+ def end_time
425
+ data['endTime']
426
+ end
427
+ end
428
+
429
+ # Factory methods for workflow DSL
430
+ module ClassMethods
431
+ def case_workflow(case_key)
432
+ Case.new(self, case_key)
433
+ end
434
+
435
+ def process_workflow(process_key)
436
+ Process.new(self, process_key)
437
+ end
438
+ end
439
+ end
440
+
441
+ class Client
442
+ include Workflow::ClassMethods
443
+ end
444
+ end
data/lib/flowable.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Top-level require for the gem
4
+ require_relative 'flowable/version'
5
+ require_relative 'flowable/flowable'
6
+
7
+ module Flowable
8
+ # Re-export top-level constants if needed
9
+ end
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowableClient
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