vcoworkflows-ruby2 0.2.3

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