camunda-workflow 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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