camunda-workflow 0.1.3 → 0.1.4

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: 258061e8801a974e302c84e184e90c60771aea045ac8ac51bd92afdcbfc4acc9
4
- data.tar.gz: 9aa801c96ff96951ce08f7ed35d69d36772b9122aa4ac51addf62979ad7b63d5
3
+ metadata.gz: ba961748beb590604ec47e0044a1ce7fa50f97d45bec52652a0e528011e4b2f3
4
+ data.tar.gz: 3886388d63f770a0fa49686489cbb5a82e3884df697d52cbb9c28582ac5dd867
5
5
  SHA512:
6
- metadata.gz: 8e77b9f038bad10d3e3606a4dc9ee3e594c050006dcaa0018733f41fc3f37d13db6392d3e76b15e1b88362b6623433e8495e664fa294e9197db849da7d71598e
7
- data.tar.gz: 3769a359d104c320094b44a33b4145caa7d36cf4b96b5054d6df452325973ea0ec4efc3903ef180488bf112e90c3840fa94ca95f72ca2db97ac515a646d7e546
6
+ metadata.gz: fc768ad2478ce728101e69d6c891e53e0045a0ea266a0c2d87d87109f0692cc9d7b3c554f4048c261d96d06e23c3c4f2b66a1054ceeb8f8804bcf3de1748bc05
7
+ data.tar.gz: de9541842d8c4158a7bf7553a527ffd25a67fdd7dfb6d1b1da8fa5fe713cb58f01c41f0f2dc1d91984b903aae95d77c91b98ea1120661fcb2dd1d37109ec2420
data/CHANGELOG.md CHANGED
@@ -1,15 +1,44 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased](https://github.com/amalagaura/camunda-workflow/tree/HEAD)
3
+ ## [v0.1.4](https://github.com/amalagaura/camunda-workflow/tree/v0.1.4) (2019-12-11)
4
4
 
5
- [Full Changelog](https://github.com/amalagaura/camunda-workflow/compare/fc9ab266909628118a892082abdff953f3bc7eca...HEAD)
5
+ [Full Changelog](https://github.com/amalagaura/camunda-workflow/compare/v0.1.3...v0.1.4)
6
+
7
+ **Closed issues:**
8
+
9
+ - add find\_by! [\#13](https://github.com/amalagaura/camunda-workflow/issues/13)
6
10
 
7
11
  **Merged pull requests:**
8
12
 
13
+ - Add find\_by! to all Camunda Her models [\#14](https://github.com/amalagaura/camunda-workflow/pull/14) ([amalagaura](https://github.com/amalagaura))
14
+ - Yarddoc documentation [\#12](https://github.com/amalagaura/camunda-workflow/pull/12) ([curatingbits](https://github.com/curatingbits))
15
+
16
+ ## [v0.1.3](https://github.com/amalagaura/camunda-workflow/tree/v0.1.3) (2019-12-06)
17
+
18
+ [Full Changelog](https://github.com/amalagaura/camunda-workflow/compare/v0.1.2...v0.1.3)
19
+
20
+ **Closed issues:**
21
+
22
+ - When there is a logic error in submitting a user task we are getting a MissingTask error instead [\#9](https://github.com/amalagaura/camunda-workflow/issues/9)
23
+ - If a class does not exist, the Poller loop crashes [\#7](https://github.com/amalagaura/camunda-workflow/issues/7)
24
+
25
+ **Merged pull requests:**
26
+
27
+ - Raise Submission errors if Camunda does not accept completion [\#11](https://github.com/amalagaura/camunda-workflow/pull/11) ([amalagaura](https://github.com/amalagaura))
28
+ - Allow Poller to not exit its loop when there is a missing class and report error [\#8](https://github.com/amalagaura/camunda-workflow/pull/8) ([amalagaura](https://github.com/amalagaura))
29
+
30
+ ## [v0.1.2](https://github.com/amalagaura/camunda-workflow/tree/v0.1.2) (2019-11-27)
31
+
32
+ [Full Changelog](https://github.com/amalagaura/camunda-workflow/compare/fc9ab266909628118a892082abdff953f3bc7eca...v0.1.2)
33
+
34
+ **Merged pull requests:**
35
+
36
+ - Add rubocop rspec [\#6](https://github.com/amalagaura/camunda-workflow/pull/6) ([amalagaura](https://github.com/amalagaura))
9
37
  - Correct find\_by\_business\_key\_and\_task\_definition\_key! [\#5](https://github.com/amalagaura/camunda-workflow/pull/5) ([amalagaura](https://github.com/amalagaura))
10
- - added authentication via templates for spring\_boot generator and Her [\#4](https://github.com/amalagaura/camunda-workflow/pull/4) ([curatingbits](https://github.com/curatingbits))
38
+ - added authentication via templates for spring\\_boot generator and Her [\#4](https://github.com/amalagaura/camunda-workflow/pull/4) ([curatingbits](https://github.com/curatingbits))
11
39
  - Refactor to return proper Her models after responses [\#3](https://github.com/amalagaura/camunda-workflow/pull/3) ([amalagaura](https://github.com/amalagaura))
12
40
  - Added tests for Camunda workflow [\#2](https://github.com/amalagaura/camunda-workflow/pull/2) ([curatingbits](https://github.com/curatingbits))
13
41
 
14
42
 
43
+
15
44
  \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
data/lib/camunda.rb CHANGED
@@ -3,19 +3,25 @@ require 'active_support/core_ext/object/blank.rb'
3
3
  require 'her'
4
4
  require 'faraday'
5
5
  require 'faraday_middleware'
6
-
6
+ # Top level module for camunda-workflow.
7
7
  module Camunda
8
+ # Camunda class
8
9
  class << self
10
+ # Allows setting the logger to a custom logger
9
11
  attr_writer :logger
10
-
12
+ # Default is output to the standard output stream.
13
+ # @return [Object] instance which is used for logging
11
14
  def logger
12
15
  @logger ||= Logger.new($stdout).tap do |log|
13
16
  log.progname = name
14
17
  end
15
18
  end
16
19
  end
17
-
20
+ ##
21
+ # Responsible for handling deserialization of variables.
18
22
  class Her::Middleware::SnakeCase < Faraday::Response::Middleware
23
+ # Check if variables are an Array or JSON and ensure variable names are transformed back from camelCase to snake_case.
24
+ # @param env [Array,Hash]
19
25
  def on_complete(env)
20
26
  return if env[:body].blank?
21
27
 
@@ -28,23 +34,35 @@ module Camunda
28
34
  env[:body] = JSON.generate(json)
29
35
  end
30
36
 
37
+ private
38
+
39
+ # Return a new hash with all keys converted by the block operation.
31
40
  def transform_hash!(hash)
32
41
  hash.deep_transform_keys!(&:underscore)
33
42
  end
34
43
  end
35
-
44
+ # Error when class corresponding to Camunda bpmn task does not exist.
36
45
  class MissingImplementationClass < StandardError
46
+ # Initializes message for MissingImplementationClass
37
47
  def initialize(class_name)
38
48
  super "Class to run a Camunda activity does not exist. Ensure there is a class with name: #{class_name} available."
39
49
  end
40
50
  end
41
-
51
+ # Error when deployment of process definition fails.
42
52
  class ProcessEngineException < StandardError
43
53
  end
44
-
54
+ # Error when BPMN process cannot be deployed.
45
55
  class BpmnError < StandardError
46
- attr_reader :error_code, :variables
56
+ # Camunda BPMN error code
57
+ # @return [String]
58
+ attr_reader :error_code
59
+ # variables to send to Camunda along with the error
60
+ # @return [Hash]
61
+ attr_reader :variables
47
62
 
63
+ # @param message [String]
64
+ # @param error_code [String]
65
+ # @param variables [Hash]
48
66
  def initialize(message:, error_code:, variables: {})
49
67
  super(message)
50
68
  @error_code = error_code
@@ -1,50 +1,71 @@
1
+ # Used to parse bpmn file during bpmn_classes generator to create Camunda job class based on process id
1
2
  class Camunda::BpmnXML
2
- attr_reader :doc
3
+ # @param io_or_string [IO,String] valid xml string for bpmn file
3
4
  def initialize(io_or_string)
4
5
  @doc = Nokogiri::XML(io_or_string)
5
6
  end
6
7
 
8
+ # Friendly name of this BPMN file is the module name
9
+ # @return [String] module name
7
10
  def to_s
8
11
  module_name
9
12
  end
10
13
 
14
+ # @return [String] Id (process definition key) of the BPMN process
15
+ # @example
16
+ # "CamundaWorkflow"
11
17
  def module_name
12
18
  @doc.xpath('/bpmn:definitions/bpmn:process').first['id']
13
19
  end
14
20
 
21
+ # creates a new instance of Camunda::BpmnXML::Task
15
22
  def external_tasks
16
23
  @doc.xpath('//*[@camunda:type="external"]').map do |task|
17
24
  Task.new(task)
18
25
  end
19
26
  end
20
27
 
28
+ # We may have tasks with different topics. Returns classes with topics which are the same as the BPMN process id
29
+ # @return [Array<String>] class names which should be implemented
30
+ # @example
31
+ # ["DoSomething"]
21
32
  def class_names_with_same_bpmn_id_as_topic
22
33
  tasks_with_same_bpmn_id_as_topic.map(&:class_name)
23
34
  end
24
35
 
36
+ # @return [Array<String>] array of modularized class names
37
+ # @example
38
+ # ["CamundaWorkflow::DoSomething"]
25
39
  def modularized_class_names
26
40
  class_names_with_same_bpmn_id_as_topic.map { |name| "#{module_name}::#{name}" }
27
41
  end
28
42
 
43
+ # @return [Array<String>] topics in this BPMN file
29
44
  def topics
30
45
  @doc.xpath('//*[@camunda:topic]').map { |node| node.attribute('topic').value }.uniq
31
46
  end
32
47
 
33
48
  private
34
49
 
50
+ # We may have tasks with different topics.
51
+ # @return [Array<Camunda::BpmnXML::Task>] tasks with topics which are the same as the BPMN process id
35
52
  def tasks_with_same_bpmn_id_as_topic
36
53
  external_tasks.select { |task| task.topic == module_name }
37
54
  end
38
55
 
56
+ # Wrapper for BPMN task XML node
39
57
  class Task
58
+ # Stores XML node about a task
40
59
  def initialize(task)
41
60
  @task = task
42
61
  end
43
62
 
63
+ # Extracts class name from Task XML node
44
64
  def class_name
45
65
  @task.attribute('id').value
46
66
  end
47
67
 
68
+ # Extracts topic name from Task XML node
48
69
  def topic
49
70
  @task.attribute('topic').value
50
71
  end
@@ -1,6 +1,21 @@
1
+ # Deployment is responsible for creating BPMN, DMN, and CMMN processes within Camunda. Before a process (or case, or decision)
2
+ # can be executed by the process engine, it has to be deployed. A deployment is a logical entity that groups multiple resources
3
+ # that are deployed together. Camunda offers an application called Modeler(https://camunda.com/download/modeler/) that allows you
4
+ # to create and edit BPMN diagrams and BPMN decision tables.
5
+ # @see https://docs.camunda.org/manual/7.4/user-guide/process-engine/deployments/
6
+ # @note You must supply the paths of the BPMN files as a param titled file_names to deploy the BPMN file
7
+ # and deploy BPMN, DMN, CMMN definitions in the Camunda engine.
1
8
  class Camunda::Deployment < Camunda::Model
2
9
  collection_path 'deployment'
3
- # Only supporting .create which uses a POST on deployment/create.
10
+ # Deploys a new process definition to Camunda and returns an instance of Camunda::ProcessDefinition.
11
+ # @note Only supporting .create which uses a POST on deployment/create.
12
+ # @example
13
+ # pd = Camunda::Deployment.create(file_names: ['bpmn/diagrams/sample.bpmn']).first
14
+ # @param files_names [Array<String>] file paths of the bpmn file for deployment
15
+ # @param tenant_id [String] supplied when a single Camunda installation should serve more than one tenant
16
+ # @param deployment_source [String] the source of where the deployment occurred.
17
+ # @param deployment_name [String] provide the name of the deployment, otherwise the deployment name will be the bpmn file names.
18
+ # @return [Camunda::ProcessDefinition]
4
19
  def self.create(file_names:, tenant_id: nil, deployment_source: 'Camunda Workflow Gem', deployment_name: nil)
5
20
  deployment_name ||= file_names.map { |file_name| File.basename(file_name) }.join(", ")
6
21
  tenant_id ||= Camunda::Workflow.configuration.tenant_id
@@ -11,12 +26,18 @@ class Camunda::Deployment < Camunda::Model
11
26
  deployed_process_definitions(response[:parsed_data][:data][:deployed_process_definitions])
12
27
  end
13
28
 
29
+ # Convenience method for dealing with files and IO that are to be uploaded
30
+ # @param file_names [Array<String>] local files paths to be uploaded
14
31
  def self.file_data(file_names)
15
32
  file_names.map do |file_name|
16
33
  [file_name, UploadIO.new(file_name, 'text/plain')]
17
34
  end.to_h
18
35
  end
19
36
 
37
+ # Returns a new instance of Camunda::ProcessDefinition according to definitions hash returned by Camunda
38
+ # @raise [ProcessEngineException] if process definition is nil
39
+ # @param definitions_hash [Hash]
40
+ # @return [Array<Camunda::ProcessDefinition>]
20
41
  def self.deployed_process_definitions(definitions_hash)
21
42
  # Currently only returning the process definitions. But this Deployment.create can create a DMN, CMMN also
22
43
  # It returns :deployed_process_definitions, :deployed_case_definitions, :deployed_decision_definitions,
@@ -1,22 +1,42 @@
1
1
  require 'active_support/core_ext/string/inflections.rb'
2
-
2
+ # External Tasks are the task entity for camunda. We can query the topic and lock the task. After the task
3
+ # is locked, the external task of the process instance can be worked and completed. Below is an excerpt from the Camunda
4
+ # documentation regarding the ExternalTask process.
5
+ # @see https://docs.camunda.org/manual/7.7/user-guide/process-engine/external-tasks/
6
+ # "When the process engine encounters a service task that is configured to be externally handled, it creates an external task
7
+ # instance and adds it to a list of external tasks(step 1). The task instance receives a topic that identifies the nature of
8
+ # the work to be performed. At a time in the future, an external worker may fetch and lock tasks for a specific set of
9
+ # topics (step 2). To prevent one task being fetched by multiple workers at the same time, a task has a timestamp-based lock
10
+ # that is set when the task is acquired. Only when the lock expires, another worker can fetch the task again. When an external
11
+ # worker has completed the desired work, it can signal the process engine to continue process execution after the service
12
+ # task (step 3)."
3
13
  class Camunda::ExternalTask < Camunda::Model
14
+ # Camunda engine doesn't searching on snake_case variables.
4
15
  include Camunda::VariableSerialization
16
+ # collection_path sets the path for Her in Camunda::Model
5
17
  collection_path 'external-task'
6
18
  custom_post :fetchAndLock, :unlock
7
19
 
20
+ # @note long_polling_duration is defaulted to 30 seconds in Camunda::Workflow.configuration.
21
+ # @return [Integer] default polling duration from Camunda::Workflow configuration
8
22
  def self.long_polling_duration
9
23
  Camunda::Workflow.configuration.long_polling_duration.in_milliseconds
10
24
  end
11
25
 
26
+ # @note Max polling tasks is defaulted to 2 in Camunda::Workflow.configuration.
27
+ # @return [Integer] sets the max polling tasks
12
28
  def self.max_polling_tasks
13
29
  Camunda::Workflow.configuration.max_polling_tasks
14
30
  end
15
31
 
32
+ # Default lock duration time is set to 14 days in Camunda::Workflow.configuration.
33
+ # @return [Integer] default lock duration time
16
34
  def self.lock_duration
17
35
  Camunda::Workflow.configuration.lock_duration.in_milliseconds
18
36
  end
19
37
 
38
+ # Reports a failure to Camunda process definition and creates an incident for a process instance.
39
+ # @param input_variables [Hash] process variables
20
40
  def failure(exception, input_variables={})
21
41
  variables_information = "Input variables are #{input_variables.inspect}\n\n" if input_variables.present?
22
42
  self.class.post_raw("#{collection_path}/#{id}/failure",
@@ -24,12 +44,17 @@ class Camunda::ExternalTask < Camunda::Model
24
44
  errorDetails: variables_information.to_s + exception.full_message)[:response]
25
45
  end
26
46
 
47
+ # Reports the error to Camunda and creates an incident for the process instance.
48
+ # @param bpmn_exception [Camunda::BpmnError]
27
49
  def bpmn_error(bpmn_exception)
28
50
  self.class.post_raw("#{collection_path}/#{id}/bpmnError",
29
51
  workerId: worker_id, variables: serialize_variables(bpmn_exception.variables),
30
52
  errorCode: bpmn_exception.error_code, errorMessage: bpmn_exception.message)[:response]
31
53
  end
32
54
 
55
+ # Completes the process instance of a fetched task
56
+ # @param variables [Hash] submitted when starting the process definition
57
+ # @raise [Camunda::ExternalTask::SubmissionError] if Camunda does not accept the task submission
33
58
  def complete(variables={})
34
59
  self.class.post_raw("#{collection_path}/#{id}/complete",
35
60
  workerId: worker_id, variables: serialize_variables(variables))[:response]
@@ -38,14 +63,20 @@ class Camunda::ExternalTask < Camunda::Model
38
63
  end
39
64
  end
40
65
 
66
+ # Returns the worker id for an external task
67
+ # @note default is set to '0' in Camunda::Workflow.configuration
68
+ # @return [String]
41
69
  def worker_id
42
70
  self.class.worker_id
43
71
  end
44
72
 
73
+ # Helper method for instances since collection_path is a class method
74
+ # @return [String]
45
75
  def collection_path
46
76
  self.class.collection_path
47
77
  end
48
78
 
79
+ # deserializes JSON attributes from variables returned by Camunda API
49
80
  def variables
50
81
  super.transform_values do |details|
51
82
  if details['type'] == 'Json'
@@ -56,14 +87,39 @@ class Camunda::ExternalTask < Camunda::Model
56
87
  end
57
88
  end
58
89
 
90
+ # Queues the task to run at a specific time via ActiveJob
91
+ # @example
92
+ # # Below will retrieve current running process instances with the definition key "CamundaWorkflow"
93
+ # # and queues the instances to be run at a specific time.
94
+ # task = Camunda::ExternalTask.fetch_and_lock("CamundaWorkflow")
95
+ # task.each(&:queue_task)
96
+ # @return [Camunda::ExternalTask]
59
97
  def queue_task
60
98
  task_class.perform_later(id, variables)
61
99
  end
62
100
 
101
+ # Runs the task using ActiveJob based on the classes created by bpmn_classes_generator. Before an external task
102
+ # can be run, you must #fetch_and_lock the task.
103
+ # @see fetch_and_lock
104
+ # @example
105
+ # # Below will retrieve current running process instances with the definition key "CamundaWorkflow"
106
+ # # and run the instances.
107
+ # task = Camunda::ExternalTask.fetch_and_lock("CamundaWorkflow")
108
+ # task.each(&:run_now)
109
+ # @return [Camunda::ExternalTask]
63
110
  def run_now
64
111
  task_class_name.safe_constantize.perform_now id, variables
65
112
  end
66
113
 
114
+ # Locking means that the task is reserved for this worker for a certain duration beginning with the time of fetching and
115
+ # prevents that another worker can fetch the same task while the lock is valid. Locking duration is set in the the
116
+ # Camunda::Workflow configuration. Before an external task can be completed, it must be locked.
117
+ # @example
118
+ # task = Camunda::ExternalTask.fetch_and_lock("CamundaWorkflow")
119
+ # @param topics [Array<String>] definition keys
120
+ # @param lock_duration [Integer]
121
+ # @param long_polling_duration [Integer]
122
+ # @return [Camunda::ExternalTask]
67
123
  def self.fetch_and_lock(topics, lock_duration: nil, long_polling_duration: nil)
68
124
  long_polling_duration ||= long_polling_duration()
69
125
  lock_duration ||= lock_duration()
@@ -74,16 +130,22 @@ class Camunda::ExternalTask < Camunda::Model
74
130
  topics: topic_details
75
131
  end
76
132
 
133
+ # Returns the class name which is supposed to implement this task
134
+ # @return [String] modularized class name of bpmn task implementation
77
135
  def task_class_name
78
136
  "#{process_definition_key}::#{activity_id}"
79
137
  end
80
138
 
139
+ # Checks to make sure an implementation class is available
140
+ # @return [Module] return class name
141
+ # @raise [Camunda::MissingImplementationClass] if the class does not exist
81
142
  def task_class
82
143
  task_class_name.safe_constantize.tap do |klass|
83
144
  raise Camunda::MissingImplementationClass, task_class_name unless klass
84
145
  end
85
146
  end
86
-
147
+ # If the BPMN file expects a variable and the variable isn't supplied with an SubmissionError will be raised
148
+ # indicating that the variable does not exist.
87
149
  class SubmissionError < StandardError
88
150
  end
89
151
  end
@@ -1,4 +1,16 @@
1
+ # Camunda::ExternalTaskJob module is included in the generated bpmn_classes for ActiveJob and handles
2
+ # the task completion or failure for a given worker that has been locked to be performed.
3
+ # @see Camunda::ExternalTask
1
4
  module Camunda::ExternalTaskJob
5
+ # Performs the external task for the process definition and processes completion or throws an error. The below example
6
+ # shows how to run a task based off of our generated classes from the bpmn_classes generator from the sample.bpmn file
7
+ # provided.
8
+ # @example
9
+ # task = Camunda::ExternalTask.fetch_and_lock('CamundaWorkflow').first
10
+ # CamundaWorkflow::DoSomething.new.perform(task.id, x: 'abcd')
11
+ # @param id [Integer] of the worker for the locked task
12
+ # @param input_variables [Hash]
13
+ # @raise [Camunda::ExternalTask::SubmissionError] if Camunda does not accept the submission of the task
2
14
  def perform(id, input_variables)
3
15
  output_variables = bpmn_perform(input_variables)
4
16
  output_variables = {} if output_variables.nil?
@@ -14,24 +26,35 @@ module Camunda::ExternalTaskJob
14
26
  report_failure id, e, input_variables
15
27
  end
16
28
 
29
+ # Reports completion for an external task with output variable set in bpmn_perform.
30
+ # @param id [Integer] of the worker
31
+ # @param variables [Hash] output variables declared in bpmn_perform
17
32
  def report_completion(id, variables)
18
33
  # Submit to Camunda using
19
34
  # POST /external-task/{id}/complete
20
35
  Camunda::ExternalTask.new(id: id).complete(variables)
21
36
  end
22
37
 
38
+ # Reports external task failure to the Camunda process definition and creates an incident report
39
+ # @param id [Integer] of the worker for the process instance
40
+ # @param exception [Object] the exception for the failed task
41
+ # @param input_variables [Hash] given to the process definition
23
42
  def report_failure(id, exception, input_variables)
24
43
  # Submit error state to Camunda using
25
44
  # POST /external-task/{id}/failure
26
45
  Camunda::ExternalTask.new(id: id).failure(exception, input_variables)
27
46
  end
28
47
 
48
+ # Reports an error if there is a problem with bpmn_perform
49
+ # @param id [Integer] of the process instance
50
+ # @param exception [Camunda::BpmnError] instance of Camunda::BpmnError
29
51
  def report_bpmn_error(id, exception)
30
52
  # Submit bpmn error state to Camunda using
31
53
  # POST /external-task/{id}/bpmnError
32
54
  Camunda::ExternalTask.new(id: id).bpmn_error(exception)
33
55
  end
34
56
 
57
+ # Default bpmn_perform which raises an error. Forces user to create their own implementation
35
58
  def bpmn_perform(_variables)
36
59
  raise StandardError, "Please define this method which takes a hash of variables and returns a hash of variables"
37
60
  end
@@ -1,3 +1,6 @@
1
+ # Allows for incidents to be reported to Camunda. Incidents are notable events
2
+ # that happen in the process engine. Such incidents usually indicate some kind of problem
3
+ # related to process execution. Incidents are reported on failures that occur.
1
4
  class Camunda::Incident < Camunda::Model
2
5
  collection_path 'incident'
3
6
  end
@@ -1,8 +1,9 @@
1
+ # RSpec matcher for topic names used for testing bpmn files
1
2
  RSpec::Matchers.define :have_topics do |topic_names|
2
3
  match { |bpmn_xml| topic_names.sort == bpmn_xml.topics.sort }
3
4
  failure_message { |bpmn_xml| "Expected #{topic_names}. Found #{bpmn_xml.topics.sort}" }
4
5
  end
5
-
6
+ # RSpec matcher used for testing bpmn files
6
7
  RSpec::Matchers.define :have_module do |module_name_expected|
7
8
  match { |bpmn_xml| module_name_expected == bpmn_xml.module_name }
8
9
  failure_message { |bpmn_xml| "ID of the BPMN process is #{bpmn_xml.module_name}. Expected #{module_name_expected}" }
data/lib/camunda/model.rb CHANGED
@@ -1,8 +1,11 @@
1
1
  require 'her/model'
2
+ # This class in the main element of Her. It defines which API models will be bound to.
2
3
  class Camunda::Model
3
4
  include Her::Model
4
5
 
6
+ # We use a lambda so that this is evaluated after Camunda::Workflow.configuration is set
5
7
  api = lambda do
8
+ # Configuration for Her and Faraday requests and responses
6
9
  Her::API.new(url: File.join(Camunda::Workflow.configuration.engine_url)) do |c|
7
10
  c.path_prefix = Camunda::Workflow.configuration.engine_route_prefix
8
11
  # Request
@@ -22,7 +25,23 @@ class Camunda::Model
22
25
 
23
26
  use_api api
24
27
 
28
+ # Returns result of find_by but raises an exception instead of returning nil
29
+ # @param params [Hash] query parameters
30
+ # @return [Camunda::Model]
31
+ # @raise [Camunda::Model::RecordNotFound] if query returns no results
32
+ def self.find_by!(params)
33
+ find_by(params).tap do |result|
34
+ raise Camunda::Model::RecordNotFound unless result
35
+ end
36
+ end
37
+
38
+ # Returns the worker id
39
+ # @note default worker id is set in Camunda::Workflow.configuration
40
+ # @return [String] id of worker
25
41
  def self.worker_id
26
42
  Camunda::Workflow.configuration.worker_id
27
43
  end
44
+
45
+ class RecordNotFound < StandardError
46
+ end
28
47
  end
@@ -1,4 +1,12 @@
1
+ # The poller will run as an infinite loop with long polling to fetch tasks, queue, and run them.
2
+ # Topic is the process definition key. Below will run the poller to fetch, lock, and run a task for the
3
+ # example process definition with an id of CamundaWorkflow.
4
+ # @example
5
+ # Camunda::Poller.fetch_and_execute %w[CamundaWorkflow]
1
6
  class Camunda::Poller
7
+ # @param topics [Array] process definition keys
8
+ # @param lock_duration [Integer] lock duration time, default time is set in Camunda::Workflow.configuration
9
+ # @param long_polling_duration [Integer] long polling time, default is set to Camunda::Workflow.configuration
2
10
  def self.fetch_and_execute(topics, lock_duration: nil, long_polling_duration: nil)
3
11
  loop do
4
12
  Camunda::ExternalTask
@@ -1,7 +1,22 @@
1
+ ##
2
+ # A process definition defines the structure of a process. The `key` of a process definition is the logical
3
+ # identifier of the process. It is used throughout the API, most prominently for starting a process instances.
4
+ # The key of a process definition is defined using the `id` property of the corresponding process element in the BPMN XML file.
5
+ # @see https://docs.camunda.org/manual/7.7/user-guide/process-engine/process-engine-concepts/
6
+ # @see Camunda::ProcessInstance
1
7
  class Camunda::ProcessDefinition < Camunda::Model
2
8
  include Camunda::VariableSerialization
3
9
  collection_path 'process-definition'
4
-
10
+ # Starts an individual process instance by key and supplies process variables to be included in the process instance. In
11
+ # the example below a business key is provided. A business key is a domain-specific identifier of a process instance,
12
+ # it makes querying for task more efficient. The business key is displayed prominently in applications like Camunda Cockpit.
13
+ # @see https://blog.camunda.com/post/2018/10/business-key/
14
+ # @example
15
+ # pd = Camunda::ProcessDefinition.start_by_key 'CamundaWorkflow', variables: { x: 'abcd' }, businessKey: 'WorkflowBusinessKey'
16
+ # @param key [String] process definition identifier
17
+ # @param hash [Hash] sets variables to be included with starting a process definition
18
+ # @return [Camunda::ProcessInstance]
19
+ # @raise [Camunda::ProcessEngineException] if submission was unsuccessful
5
20
  def self.start_by_key(key, hash={})
6
21
  hash[:variables] = serialize_variables(hash[:variables]) if hash[:variables]
7
22
  tenant_id = hash.delete(:tenant_id)
@@ -13,6 +28,15 @@ class Camunda::ProcessDefinition < Camunda::Model
13
28
  Camunda::ProcessInstance.new response[:parsed_data][:data]
14
29
  end
15
30
 
31
+ # Starts an individual process instance for a process definition. The below example shows how to start a process
32
+ # definition after deployment.
33
+ # @example
34
+ # pd = Camunda::Deployment.create(file_names: ['bpmn/diagrams/sample.bpmn']).first
35
+ # pd.start
36
+ # Starts the process instance by sending a request to the Camunda engine
37
+ # @param hash [Hash] defaults to {} if no variables are provided
38
+ # @return [Camunda::ProcessInstance]
39
+ # @raise [Camunda::ProcessEngineException] if submission was unsuccessful
16
40
  def start(hash={})
17
41
  hash[:variables] = serialize_variables(hash[:variables]) if hash[:variables]
18
42
  response = self.class.post_raw "process-definition/#{id}/start", hash
@@ -21,6 +45,7 @@ class Camunda::ProcessDefinition < Camunda::Model
21
45
  Camunda::ProcessInstance.new response[:parsed_data][:data]
22
46
  end
23
47
 
48
+ # Sets path to include tenant_id if a tenant_id is provided with a process definition on deployment.
24
49
  def self.start_path_for_key(key, tenant_id)
25
50
  path = "process-definition/key/#{key}"
26
51
  path << "/tenant-id/#{tenant_id}" if tenant_id
@@ -1,6 +1,11 @@
1
+ # A process instance is an individual execution of a process definition. The relation of the process instance to the process
2
+ # definition is the same as the relation between Class and Class instance in OOP. When a process definition is started,
3
+ # a process instance is created.
4
+ # @see https://docs.camunda.org/manual/7.4/user-guide/process-engine/process-engine-concepts/
5
+ # @see Camunda::ProcessDefinition
1
6
  class Camunda::ProcessInstance < Camunda::Model
2
7
  collection_path 'process-instance'
3
-
8
+ # GETs the process instance and deserializes the variables
4
9
  def variables
5
10
  response = self.class.get_raw "process-instance/#{id}/variables"
6
11
  deserialize_variables response[:parsed_data][:data]
@@ -8,6 +13,8 @@ class Camunda::ProcessInstance < Camunda::Model
8
13
 
9
14
  private
10
15
 
16
+ # Deserialize variables and convert variable names from CamelCase to snake_case.
17
+ # @param hash [Hash] Transforms a hash of Camunda serialized variables to a simple Ruby hash and snake_cases variable names
11
18
  def deserialize_variables(hash)
12
19
  hash.transform_values do |value_hash|
13
20
  case value_hash[:type]
@@ -1,7 +1,14 @@
1
+ ##
2
+ # Signal events are events which reference a named signal. Camunda::Signal is used to
3
+ # create a signal with variables.
4
+ # @example
5
+ # `Camunda::Signal.create name: 'Signal Name', variables: {foo: "bar"}`
1
6
  class Camunda::Signal < Camunda::Model
2
7
  include Camunda::VariableSerialization
3
8
  collection_path 'signal'
4
-
9
+ # Creates a signal within the process definition on the Camunda engine
10
+ # @param hash [Hash] variables that are sent to Camunda engine
11
+ # @return [{Symbol => Hash,Faraday::Response}]
5
12
  def self.create(hash={})
6
13
  hash[:variables] = serialize_variables(hash[:variables]) if hash[:variables]
7
14
  post_raw collection_path, hash
data/lib/camunda/task.rb CHANGED
@@ -1,16 +1,33 @@
1
+ # Finds tasks by business key and task definition and allows you to report a task complete and update process variables.
2
+ # If a business key isn't supplied when creating a process definition, you can still retrieve UserTasks by using
3
+ # the `.find_by` helper provided by Her.
4
+ # @see Camunda::ProcessDefinition
5
+ # @see https://github.com/remi/her
6
+ # @example
7
+ # Camunda::Task.find_by(taskDefinitionKey: 'UserTask')
8
+ # @example
9
+ # # You can get all tasks with the `.all.each` helper
10
+ # tasks = Camunda::Task.all.each
11
+ # # And then complete all tasks like so
12
+ # tasks.each(&:complete!)
1
13
  class Camunda::Task < Camunda::Model
2
14
  include Camunda::VariableSerialization
3
15
  collection_path 'task'
4
16
 
17
+ # @example
18
+ # user_task = Camunda::Task.find_by_business_key_and_task_definition_key!('WorkflowBusinessKey','UserTask')
19
+ # @param instance_business_key [String] the process instance business key
20
+ # @param task_key [String] id/key of the user task
21
+ # @return [Camunda::Task]
5
22
  def self.find_by_business_key_and_task_definition_key!(instance_business_key, task_key)
6
- find_by(processInstanceBusinessKey: instance_business_key, taskDefinitionKey: task_key).tap do |ct|
7
- unless ct
8
- raise MissingTask, "Could not find Camunda Task with processInstanceBusinessKey: #{instance_business_key} " \
9
- "and taskDefinitionKey #{task_key}"
10
- end
11
- end
23
+ find_by!(processInstanceBusinessKey: instance_business_key, taskDefinitionKey: task_key)
12
24
  end
13
25
 
26
+ # Complete a task and updates process variables.
27
+ # @example
28
+ # user_task = Camunda::Task.find_by_business_key_and_task_definition_key!('WorkflowBusinessKey','UserTask')
29
+ # user_task.complete!
30
+ # @param vars [Hash] variables to be submitted as part of task completion
14
31
  def complete!(vars={})
15
32
  self.class.post_raw("#{self.class.collection_path}/#{id}/complete", variables: serialize_variables(vars))[:response]
16
33
  .tap do |response|
@@ -18,9 +35,8 @@ class Camunda::Task < Camunda::Model
18
35
  end
19
36
  end
20
37
 
21
- class MissingTask < StandardError
22
- end
23
-
38
+ # Error class when the task cannot be submitted. For instance if the bpmn process expects a variable and the variable
39
+ # isn't supplied, Camunda will not accept the task
24
40
  class SubmissionError < StandardError
25
41
  end
26
42
  end
@@ -1,14 +1,21 @@
1
1
  require 'active_support/concern'
2
2
  module Camunda
3
+ # The VariableSerialization module adds information to variables so Camunda can parse them. It adds types annotations and
4
+ # serializes hashes and array to JSON. Camunda engine cannot search on snake_case variables so it changes variable names
5
+ # to camelCase.
6
+ # @see Camunda::ProcessDefinition
3
7
  module VariableSerialization
4
8
  extend ActiveSupport::Concern
5
-
9
+ # Wrapper for class level method
6
10
  def serialize_variables(variables)
7
11
  self.class.serialize_variables(variables)
8
12
  end
9
13
 
10
14
  class_methods do
11
15
  # rubocop:disable Metrics/MethodLength
16
+ # @param variables [Hash]
17
+ # @return {String,Symbol => {String,Symbol => Object}}
18
+ # @raise [ArgumentError] if a class instance is passed
12
19
  def serialize_variables(variables)
13
20
  hash = variables.transform_values do |value|
14
21
  case value
@@ -30,6 +37,9 @@ module Camunda
30
37
  end
31
38
  # rubocop:enable Metrics/MethodLength
32
39
 
40
+ # Transforms keys of a JSON like object (Array,Hash) from snake_case to CamelCase
41
+ # @param json [Array,Hash]
42
+ # @return [Hash] returns hash with camelCase keys
33
43
  def transform_json(json)
34
44
  if json.is_a?(Array)
35
45
  json.map { |element| transform_json(element) }
@@ -1,22 +1,65 @@
1
1
  module Camunda
2
+ ##
3
+ # Default configuration file for camunda-workflow. These defaults can be overridden using an
4
+ # initializer file within a Rails application.
5
+ # @example override default values
6
+ # Camunda::Workflow.configure do |config|
7
+ # config.engine_url = 'http://localhost:8080'
8
+ # config.engine_route_prefix = 'rest'
9
+ # end
2
10
  module Workflow
11
+ # Implements Configuration class and sets default instance variables. The default variables can be overridden by creating an
12
+ # initializer file within your rails application and setting the variables like in the example below.
13
+ # @note if HTTP Basic Auth is used with the Camunda engine, this is where you would set a camunda_user and camunda_password
14
+ # using the creditials from a user setup in Camunda Admin.
15
+ # @example
16
+ # 'Camunda::Workflow.configure do |config|
17
+ # config.engine_url = 'http://localhost:8080'
18
+ # config.engine_route_prefix = 'rest'
19
+ # end'
3
20
  def self.configure
4
21
  yield(configuration)
5
22
  end
6
23
 
24
+ # Access the Configuration class
25
+ # @return [Configuration]
7
26
  def self.configuration
8
27
  @configuration ||= Configuration.new
9
28
  end
10
-
29
+ # Default instance variables configurations for Her and camunda-workflow
11
30
  class Configuration
31
+ # Sets the deult engine url for Camunda REST Api
32
+ # @return [String] the url for Camunda deployment
12
33
  attr_accessor :engine_url
34
+ # Engine route prefix that determines the path for the REST Api
35
+ # Default route for Java spring app is `/rest`
36
+ # Default route for Camunda deployment is `/rest-engine`
37
+ # @return [String] the prefix for Camunda REST Api
13
38
  attr_accessor :engine_route_prefix
39
+ # Name of worker, defaults to '0'
40
+ # @return [String] name of worker
14
41
  attr_accessor :worker_id
42
+ # The default fetch_and_lock time duration when fetching a task
43
+ # @return [Integer] time in days to lock task
15
44
  attr_accessor :lock_duration
45
+ # Max polling tasks when using the command line to fetch and lock tasks
46
+ # @return [Integer] default is set to fetch and lock 2 tasks
16
47
  attr_accessor :max_polling_tasks
48
+ # With the aid of log polling, a request is suspended by the server if no external tasks are available.
49
+ # Long polling significantly reduces the number of request and enables using resources more
50
+ # efficiently on both the server and client.
51
+ # @return [Integer]
17
52
  attr_accessor :long_polling_duration
53
+ # The tenant identifier is specified on the deployment and is propagated to all data
54
+ # that is created from the deployment(e.g. process definitions, process instances, tacks).
55
+ # @return [String] name for tenant identifier
18
56
  attr_accessor :tenant_id
57
+ # When HTTP Basic Auth is turned on for Camunda, a user needs to be created in Camunda Admin
58
+ # and set in to be used in the configuration.
59
+ # @return [String] Camunda user name for HTTP Basic Auth
19
60
  attr_accessor :camunda_user
61
+ # Camunda password is supplied with the Camunda user to authenticate using HTTP Basic Auth.
62
+ # @return [String] Camunda password for HTTP Basic Auth
20
63
  attr_accessor :camunda_password
21
64
 
22
65
  def initialize
@@ -1,5 +1,5 @@
1
1
  module Camunda
2
2
  module Workflow
3
- VERSION = '0.1.3'.freeze
3
+ VERSION = '0.1.4'.freeze
4
4
  end
5
5
  end
@@ -1,15 +1,20 @@
1
1
  module Camunda
2
2
  module Generators
3
+ # Parses the BPMN file and creates task classes according to the ID of the process file and the ID of
4
+ # each task. It checks each task and only creates it if the topic name is the same as the process ID. This
5
+ # allows one to have some tasks be handled outside the Rails app. It confirms that the ID's are valid Ruby constant names.
3
6
  class BpmnClassesGenerator < Rails::Generators::Base
4
7
  source_root File.expand_path('templates', __dir__)
5
8
  argument :bpmn_file, type: :string
6
9
  class_option :model_path, type: :string, default: 'app/bpmn'
7
10
 
11
+ # @return [String] The id of the BPMN process
8
12
  def validate_module_name
9
13
  puts "The id of the BPMN process is: #{colored_module_name}. That will be your module name."
10
14
  validate_constant_name(module_name)
11
15
  end
12
16
 
17
+ # Creates a class name to be used for the task classes created to perform external tasks.
13
18
  def validate_class_names
14
19
  bpmn_xml.modularized_class_names.each do |class_name|
15
20
  validate_constant_name(class_name.demodulize, module_name)
@@ -19,10 +24,13 @@ module Camunda
19
24
  puts colorized_class_names.join("\n")
20
25
  end
21
26
 
27
+ # Creates module names to be used for the task classes to perform external tasks.
22
28
  def create_module
23
29
  template 'bpmn_module.rb.template', File.join(model_path, "#{module_name.underscore}.rb")
24
30
  end
25
31
 
32
+ # Creates the correct classes from the bpmn file to be used in the provided generator template for the classes
33
+ # needed to run external tasks.
26
34
  def create_classes
27
35
  bpmn_xml.class_names_with_same_bpmn_id_as_topic.each do |class_name|
28
36
  template 'bpmn_class.rb.template',
@@ -32,6 +40,7 @@ module Camunda
32
40
 
33
41
  private
34
42
 
43
+ # Validates constant names by attempting to create a Ruby constant
35
44
  def validate_constant_name(name, module_name=nil)
36
45
  top_level = module_name.nil? ? Module.new : module_name.constantize
37
46
  colorized_name = set_color name, :red
@@ -1,8 +1,12 @@
1
1
  module Camunda
2
+ # Generator to run and install camunda_job.rb that includes ExternalTaskJob to be used with task classes.
2
3
  module Generators
4
+ # Creates `app/jobs/camunda_job.rb`. A class which inherits from ApplicationJob and includes `ExternalTaskJob`.
5
+ # It can be changed to include Sidekiq::Worker instead.
6
+ # All of the BPMN worker classes will inherit from this class
3
7
  class InstallGenerator < Rails::Generators::Base
4
8
  source_root File.expand_path('templates', __dir__)
5
-
9
+ # Copies the camunda_job file to the Rails application.
6
10
  def copy_camunda_application_job
7
11
  copy_file 'camunda_job.rb', 'app/jobs/camunda_job.rb'
8
12
  end
@@ -1,3 +1,4 @@
1
+ # All of the BPMN worker classes will inherit from this class
1
2
  class CamundaJob < ApplicationJob
2
3
  # If using Sidekiq change to include Sidekiq::Worker instead of inheriting from ApplicationJob
3
4
  include Camunda::ExternalTaskJob
@@ -1,10 +1,13 @@
1
1
  module Camunda
2
2
  module Generators
3
+ # Creates a skeleton Java Spring Boot app, which also contains the minimal files to run unit tests on a BPMN file.
4
+ # This can be used to start a Camunda instance with a REST api. This can also be deployed to PCF by generating a
5
+ # Spring Boot jar and pushing it.
3
6
  class SpringBootGenerator < Rails::Generators::Base
4
7
  source_root File.expand_path('templates', __dir__)
5
8
  class_option :app_path, type: :string, default: 'bpmn/java_app'
6
9
  class_option :diagram_path, type: :string, default: 'bpmn/diagrams'
7
-
10
+ # Copys all spring boot files into a rails application and provides a Camunda engine for testing.
8
11
  def copy_java_app_files
9
12
  copy_file 'pom.xml', File.join(bpmn_app_path, 'pom.xml')
10
13
  copy_file 'camunda.cfg.xml', File.join(bpmn_app_path, 'src/test/resources/camunda.cfg.xml')
@@ -13,10 +16,12 @@ module Camunda
13
16
  copy_file 'Camunda.java', File.join(bpmn_app_path, 'src/main/java/camunda/Camunda.java')
14
17
  end
15
18
 
19
+ # Copys a sample bpmn file to help demonstrate the usage for camunda-workflow
16
20
  def link_resources_folder
17
21
  copy_file 'sample.bpmn', File.join(diagram_path, 'sample.bpmn'), ''
18
22
  end
19
23
 
24
+ # Add spring boot files to .gitignore
20
25
  def add_to_ignores
21
26
  %w[.gitignore .cfignore].each do |file|
22
27
  append_to_file file do
@@ -27,6 +32,7 @@ module Camunda
27
32
  end
28
33
  end
29
34
 
35
+ # Provides instruction regarding an error with EventedFileChecker listening on the entire Rails folder.
30
36
  def output_error_instructions
31
37
  puts <<~DOC
32
38
  If you get an error when starting your Rails app
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: camunda-workflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ankur Sethi