vcoworkflows-ruby2 0.2.3

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,42 @@
1
+ require_relative 'workflowservice'
2
+ require 'json'
3
+
4
+ # VcoWorkflows
5
+ module VcoWorkflows
6
+ # WorkflowExecutionLog is a simple object to contain the log for an
7
+ # execution of a workflow.
8
+ class WorkflowExecutionLog
9
+ # Log messages
10
+ # @return [String[]] Array of log message lines
11
+ attr_reader :messages
12
+
13
+ # Create an execution log object
14
+ # @param [String] log_json JSON document as string
15
+ # @return [VcoWorkflows::WorkflowExecutionLog]
16
+ def initialize(log_json)
17
+ @messages = {}
18
+ JSON.parse(log_json)['logs'].each do |log_entry|
19
+ messages[log_entry['entry']['time-stamp']] = log_entry['entry']
20
+ end
21
+ end
22
+
23
+ # rubocop:disable MethodLength, LineLength
24
+
25
+ # Stringify the log
26
+ # @return [String]
27
+ def to_s
28
+ message = ''
29
+ @messages.keys.sort.each do |timestamp|
30
+ message << Time.at(timestamp / 1000).to_s
31
+ message << " #{@messages[timestamp]['severity']}: #{@messages[timestamp]['user']}:"
32
+ message << " #{@messages[timestamp]['short-description']}"
33
+ unless @messages[timestamp]['short-description'].eql?(@messages[timestamp]['long-description'])
34
+ message << "; #{@messages[timestamp]['long-description']}"
35
+ end
36
+ message << "\n"
37
+ end
38
+ message
39
+ end
40
+ # rubocop:enable MethodLength, LineLength
41
+ end
42
+ end
@@ -0,0 +1,150 @@
1
+ require_relative 'constants'
2
+ require 'json'
3
+
4
+ module VcoWorkflows
5
+ # WorkflowParameter is an object wrapper for workflow input and output
6
+ # parameters.
7
+ class WorkflowParameter
8
+ # Parameter name
9
+ # @return [String] parameter name
10
+ attr_reader :name
11
+
12
+ # Parameter type
13
+ # @return [String] parameter type
14
+ attr_reader :type
15
+
16
+ # Parameter subtype (used when type is 'Array')
17
+ # @return [String] parameter subtype
18
+ attr_reader :subtype
19
+
20
+ # Parameter value
21
+ # @return [Object] parameter value
22
+ attr_reader :value
23
+
24
+ # rubocop:disable MethodLength
25
+
26
+ # Create a new workflow parameter object
27
+ # @param [String] name Name of the workflow parameter
28
+ # @param [String] type Data type of the parameter (according to vCO)
29
+ # @return [VcoWorkflows::WorkflowParameter]
30
+ def initialize(name = nil, type = nil, options = {})
31
+ # Merge provided options with our defaults
32
+ options = {
33
+ required: false,
34
+ value: nil
35
+ }.merge(options)
36
+
37
+ @name = name
38
+
39
+ case type
40
+ when %r{\/}
41
+ @type = type.gsub(%r{\/.*$}, '')
42
+ @subtype = type.gsub(%r{^.*\/}, '')
43
+ else
44
+ @type = type
45
+ @subtype = nil
46
+ end
47
+
48
+ @required = options[:required]
49
+
50
+ # If value is supposed to be an array but we dont' have a value yet,
51
+ # create an empty array. If it's not supposed to be an array, just
52
+ # set the value, even if it's still nil.
53
+ @value = if options[:value].nil?
54
+ @type.eql?('Array') ? [] : nil
55
+ else
56
+ set(options[:value])
57
+ end
58
+ end
59
+ # rubocop:enable MethodLength
60
+
61
+ # Set the parameter value
62
+ # @param [Object] value Value for the parameter
63
+ def set(value)
64
+ # Do some basic checking for Arrays.
65
+ unless value.nil?
66
+ case @type
67
+ when 'Array'
68
+ raise(IOError, ERR[:param_verify_failed]) unless value.is_a?(Array)
69
+ end
70
+ end
71
+ @value = value
72
+ end
73
+ # rubocop:enable CyclomaticComplexity
74
+
75
+ # Has a value been set for this parameter?
76
+ # @return [Boolean]
77
+ def set?
78
+ case value.class
79
+ when Array
80
+ value.empty? ? false : true
81
+ else
82
+ value.nil? ? false : true
83
+ end
84
+ end
85
+
86
+ # Set whether or not this WorkflowParameter is required
87
+ # @param [Boolean] required Set this parameter as required (if not specified)
88
+ def required(required = true)
89
+ @required = required
90
+ end
91
+ # rubocop:enable LineLength
92
+
93
+ # Determine whether or not this WorkflowParameter has been marked as
94
+ # required
95
+ # @return [Boolean]
96
+ def required?
97
+ @required
98
+ end
99
+
100
+ # rubocop:disable CyclomaticComplexity, PerceivedComplexity, MethodLength
101
+
102
+ # Return a string representation of the parameter
103
+ # @return [String] Pretty-formatted string
104
+ def to_s
105
+ string = @name.to_s
106
+ # If value is either nil or an empty array
107
+ if @value.nil? || @value.is_a?(Array) && @value.empty?
108
+ string << " (#{@type}"
109
+ string << "/#{@subtype}" if @subtype
110
+ string << ')'
111
+ string << ' [required]' if @required
112
+ elsif @type.eql?('Array')
113
+ string << ' ='
114
+ @value.each { |v| string << "\n - #{v}" }
115
+ else
116
+ string << " = #{@value}"
117
+ end
118
+ string << "\n"
119
+ end
120
+ # rubocop:enable CyclomaticComplexity, PerceivedComplexity, MethodLength
121
+
122
+ # Public
123
+ # Return a JSON document representation of this object
124
+ # @return [String] JSON representation
125
+ def to_json
126
+ as_struct.to_json
127
+ end
128
+
129
+ # rubocop:disable LineLength
130
+
131
+ # Hashify the parameter (primarily useful for converting to JSON or YAML)
132
+ # @return [Hash] Contents of this object as a hash
133
+ def as_struct
134
+ attributes = { type: @type, name: @name, scope: 'local' }
135
+
136
+ # If the value is an array, we need to build it in the somewhat silly
137
+ # manner that vCO requires it to be presented. Otherwise, just paste
138
+ # it on the end of the hash.
139
+ if @type.eql?('Array')
140
+ raise(IOError, ERR[:wtf]) unless @value.is_a?(Array)
141
+ attributes[:value] = { @type.downcase => { elements: [] } }
142
+ @value.each { |val| attributes[:value][@type.downcase][:elements] << { @subtype => { value: val } } }
143
+ else
144
+ attributes[:value] = { @type => { value: @value } }
145
+ end
146
+ attributes
147
+ end
148
+ # rubocop:enable LineLength
149
+ end
150
+ end
@@ -0,0 +1,62 @@
1
+ require_relative 'constants'
2
+ require_relative 'workflowservice'
3
+ require_relative 'workflow'
4
+ require_relative 'workflowtoken'
5
+ require_relative 'workflowparameter'
6
+ require 'json'
7
+
8
+ # VcoWorkflows
9
+ module VcoWorkflows
10
+ # WorkflowPresentation is a helper class for Workflow and is primarily used
11
+ # internally to apply additional constraints to WorkflowParameters. Currently
12
+ # WorkflowPresentation examines the presentation JSON from vCO to determine
13
+ # whether input parameters for the workflow are required or not.
14
+ class WorkflowPresentation
15
+ # Accessor for the data structure
16
+ # @return [Hash] parsed JSON
17
+ attr_reader :presentation_data
18
+
19
+ # Get the list of required parameters for the workflow
20
+ # @return [String[]] Array of strings (names of parameters)
21
+ attr_reader :required
22
+
23
+ # rubocop:disable MethodLength
24
+
25
+ # Create a new WorkflowPresentation
26
+ # @param [VcoWorkflows::WorkflowService] workflow_service workflow service to use
27
+ # @param [String] workflow_id workflow GUID
28
+ # @return [VcoWorkflows::WorkflowPresentation]
29
+ def initialize(workflow_service, workflow_id)
30
+ @required = []
31
+ @presentation_data = JSON.parse(workflow_service.get_presentation(workflow_id))
32
+
33
+ # Determine if there are any required input parameters
34
+ # We're parsing this because we specifically want to know if any of
35
+ # the input parameters are marked as required. This is very specifically
36
+ # in the array of hashes in:
37
+ # presentation_data[:steps][0][:step][:elements][0][:fields]
38
+ fields = @presentation_data['steps'][0]['step']['elements'][0]['fields']
39
+ fields.each do |attribute|
40
+ next unless attribute.key?('constraints')
41
+ attribute['constraints'].each do |const|
42
+ if const.key?('@type') && const['@type'].eql?('mandatory')
43
+ @required << attribute['id']
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ # String representation of the presentation
50
+ # @return [String]
51
+ def to_s
52
+ @presentation_data.to_s
53
+ end
54
+
55
+ # JSON document
56
+ # @return [String] JSON Document
57
+ def to_json
58
+ JSON.pretty_generate(@presentation_data)
59
+ end
60
+ end
61
+ # rubocop:enable ClassLength
62
+ end
@@ -0,0 +1,109 @@
1
+ require_relative 'constants'
2
+ require_relative 'vcosession'
3
+ require_relative 'workflow'
4
+ require_relative 'workflowexecutionlog'
5
+ require 'json'
6
+ require 'erb'
7
+
8
+ include ERB::Util
9
+
10
+ module VcoWorkflows
11
+ # WorkflowService is the object which acts as the interface to the vCO
12
+ # API, and is loosely modeled from the vCO API documentation.
13
+ class WorkflowService
14
+ # The VcoSession used by this service
15
+ # @return [VcoWorkflows::VcoSession]
16
+ attr_reader :session
17
+
18
+ # Create a new WorkflowService
19
+ # @param [VcoWorkflows::VcoSession] session Session object for the API endpoint
20
+ # @return [VcoWorkflows::WorkflowService]
21
+ def initialize(session)
22
+ @session = session
23
+ end
24
+ # rubocop:enable LineLength
25
+
26
+ # Get a workflow by GUID
27
+ # @param [String] id Workflow GUID
28
+ # @return [String] the JSON document of the requested workflow
29
+ def get_workflow_for_id(id)
30
+ @session.get("/workflows/#{id}").body
31
+ end
32
+
33
+ # Get the presentation for the given workflow GUID
34
+ # @param [String] workflow_id workflow GUID
35
+ # @return [String] JSON document representation of Workflow Presentation
36
+ def get_presentation(workflow_id)
37
+ @session.get("/workflows/#{workflow_id}/presentation/").body
38
+ end
39
+
40
+ # Get one workflow with a specified name.
41
+ # @param [String] name Name of the workflow
42
+ # @return [String] the JSON document of the requested workflow
43
+ def get_workflow_for_name(name)
44
+ path = "/workflows?conditions=name=#{url_encode(name)}"
45
+ response = JSON.parse(@session.get(path).body)
46
+
47
+ # barf if we got anything other than a single workflow
48
+ raise(IOError, ERR[:too_many_workflows]) if response['total'] > 1
49
+ raise(IOError, ERR[:no_workflow_found]) if response['total'].zero?
50
+
51
+ # yank out the workflow id and name from the result attributes
52
+ workflow_id = nil
53
+ response['link'][0]['attributes'].each do |a|
54
+ workflow_id = a['value'] if a['name'].eql?('id')
55
+ end
56
+
57
+ # Get the workflow by GUID
58
+ get_workflow_for_id(workflow_id)
59
+ end
60
+
61
+ # Get a WorkflowToken for the requested workflow_id and execution_id
62
+ # @param [String] workflow_id Workflow GUID
63
+ # @param [String] execution_id Execution GUID
64
+ # @return [String] JSON document for workflow token
65
+ def get_execution(workflow_id, execution_id)
66
+ path = "/workflows/#{workflow_id}/executions/#{execution_id}"
67
+ @session.get(path).body
68
+ end
69
+
70
+ # Get a list of executions for the given workflow GUID
71
+ # @param [String] workflow_id Workflow GUID
72
+ # @return [Hash] workflow executions, keyed by execution ID
73
+ def get_execution_list(workflow_id)
74
+ path = "/workflows/#{workflow_id}/executions/"
75
+ relations = JSON.parse(@session.get(path).body)['relations']
76
+ # The first two elements of the relations['link'] array are URLS,
77
+ # so scrap them. Everything else is an execution.
78
+ executions = {}
79
+ relations['link'].each do |link|
80
+ next unless link.key?('attributes')
81
+ attributes = {}
82
+ link['attributes'].each { |a| attributes[a['name']] = a['value'] }
83
+ executions[attributes['id']] = attributes
84
+ end
85
+ executions
86
+ end
87
+
88
+ # Get the log for a specific execution
89
+ # @param [String] workflow_id Workflow GUID
90
+ # @param [String] execution_id Workflow execution ID
91
+ # @return [String] JSON log document
92
+ def get_log(workflow_id, execution_id)
93
+ path = "/workflows/#{workflow_id}/executions/#{execution_id}/logs/"
94
+ @session.get(path).body
95
+ end
96
+
97
+ # Submit the given workflow for execution
98
+ # @param [String] id Workflow GUID for the workflow we want to execute
99
+ # @param [String] parameter_json JSON document of input parameters
100
+ # @return [String] Execution ID
101
+ def execute_workflow(id, parameter_json)
102
+ path = "/workflows/#{id}/executions/"
103
+ response = @session.post(path, parameter_json)
104
+ # Execution ID is the final component in the Location header URL, so
105
+ # chop off the front, then pull off any trailing /
106
+ response.headers[:location].gsub(%r{^.*/executions/}, '').gsub(%r{\/$}, '')
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,154 @@
1
+ require_relative 'constants'
2
+ require_relative 'workflow'
3
+ require_relative 'workflowservice'
4
+ require 'json'
5
+
6
+ module VcoWorkflows
7
+ # WorkflowToken is used for workflow execution results, and contains as much
8
+ # data on the given workflow execution instance as vCO can provide.
9
+ class WorkflowToken
10
+ # rubocop:disable LineLength
11
+
12
+ # Workflow execution ID
13
+ # @return [String] the execution id for this token
14
+ attr_reader :id
15
+
16
+ # Workflow ID
17
+ # @return [String] the GUID for this workflow
18
+ attr_reader :workflow_id
19
+
20
+ # Workflow name
21
+ # @return [String] name of the workflow
22
+ attr_reader :name
23
+
24
+ # Execution state
25
+ # @return [String] current state of the workflow execution
26
+ attr_reader :state
27
+
28
+ # Execution href
29
+ # @return [String] link to this execution via the REST API
30
+ attr_reader :href
31
+
32
+ # Execution start date
33
+ # @return [String] date and time the workflow execution started
34
+ attr_reader :start_date
35
+
36
+ # Execution end date
37
+ # @return [String] date and time the workflow execution ended
38
+ attr_reader :end_date
39
+
40
+ # Execution started by
41
+ # @return [String] vCO user who started this execution
42
+ attr_reader :started_by
43
+
44
+ # @return [String]
45
+ attr_reader :current_item_name
46
+
47
+ # @return [String]
48
+ attr_reader :current_item_state
49
+
50
+ # @return [String]
51
+ attr_reader :content_exception
52
+
53
+ # @return [String]
54
+ attr_reader :global_state
55
+
56
+ # Workflow execution input parameters
57
+ # @return [VcoWorkflows::WorkflowParameter{}] Hash of input parameters which were given when the workflow was executed
58
+ attr_reader :input_parameters
59
+
60
+ # Workflow execution output parameters
61
+ # @return [VcoWorkflows::WorkflowParameter{}] Hash of output parameters set by the workflow execution
62
+ attr_reader :output_parameters
63
+
64
+ # Source JSON
65
+ # @return [String] source JSON document returned by vCO for this execution
66
+ attr_reader :json_content
67
+
68
+ # rubocop:enable LineLength
69
+
70
+ # rubocop:disable CyclomaticComplexity, PerceivedComplexity, MethodLength, LineLength
71
+
72
+ # Create a new workflow token
73
+ # @param [VcoWorkflows::WorkflowService] workflow_service Workflow service to use
74
+ # @param [String] workflow_id GUID of the workflow
75
+ # @param [String] execution_id GUID of execution
76
+ # @return [VcoWorkflows::WorkflowToken]
77
+ def initialize(workflow_service, workflow_id, execution_id)
78
+ @service = workflow_service
79
+ @workflow_id = workflow_id
80
+ @json_content = @service.get_execution(workflow_id, execution_id)
81
+
82
+ token = JSON.parse(@json_content)
83
+ @id = token.key?('id') ? token['id'] : nil
84
+ @name = token.key?('name') ? token['name'] : nil
85
+ @state = token.key?('state') ? token['state'] : nil
86
+ @href = token.key?('href') ? token['href'] : nil
87
+ @start_date = token.key?('start-date') ? token['start-date'] : nil
88
+ @end_date = token.key?('end-date') ? token['end-date'] : nil
89
+ @started_by = token.key?('started-by') ? token['started-by'] : nil
90
+ @current_item_name = token.key?('current-item-display-name') ? token['current-item-display-name'] : nil
91
+ @current_item_state = token.key?('current-item-state') ? token['current-item-state'] : nil
92
+ @global_state = token.key?('global-state') ? token['global-state'] : nil
93
+ @content_exception = token.key?('content-exeption') ? token['content-exception'] : nil
94
+
95
+ if token.key?('input-parameters')
96
+ @input_parameters = VcoWorkflows::Workflow.parse_parameters(token['input-parameters'])
97
+ else
98
+ @input_parameters = {}
99
+ end
100
+
101
+ if token.key?('output-parameters')
102
+ @output_parameters = VcoWorkflows::Workflow.parse_parameters(token['output-parameters'])
103
+ else
104
+ @output_parameters = {}
105
+ end
106
+ end
107
+ # rubocop:enable CyclomaticComplexity, PerceivedComplexity, MethodLength, LineLength
108
+
109
+ # Is the workflow execution still alive?
110
+ # @return [Boolean]
111
+ def alive?
112
+ running? || waiting?
113
+ end
114
+
115
+ # Is the workflow actively running?
116
+ # @return [Boolean]
117
+ def running?
118
+ state.eql?('running')
119
+ end
120
+
121
+ # Is the workflow in a waiting state?
122
+ # @return [Boolean]
123
+ def waiting?
124
+ state.match(/waiting/).nil? ? false : true
125
+ end
126
+
127
+ # rubocop:disable MethodLength, LineLength
128
+
129
+ # Convert this object to a string representation
130
+ # @return [String]
131
+ def to_s
132
+ string = "Execution ID: #{@id}\n"
133
+ string << "Name: #{@name}\n"
134
+ string << "Workflow ID: #{@workflow_id}\n"
135
+ string << "State: #{@state}\n"
136
+ string << "Start Date: #{Time.at(@start_date / 1000)}\n"
137
+ string << "End Date: #{end_date.nil? ? '' : Time.at(@end_date / 1000)}\n"
138
+ string << "Started By: #{@started_by}\n"
139
+ string << "Content Exception: #{@content_exception}\n" unless @content_exception.nil?
140
+ string << "\nInput Parameters:\n"
141
+ @input_parameters.each_value { |wf_param| string << " #{wf_param}" if wf_param.set? } unless @input_parameters.empty?
142
+ string << "\nOutput Parameters:" << "\n"
143
+ @output_parameters.each_value { |wf_param| string << " #{wf_param}" } unless @output_parameters.empty?
144
+ string
145
+ end
146
+ # rubocop:enable MethodLength, LineLength
147
+
148
+ # Convert this object to a JSON document (string)
149
+ # @return [String] JSON representation of the workflow token
150
+ def to_json
151
+ JSON.pretty_generate(JSON.parse(@json_content))
152
+ end
153
+ end
154
+ end