dor-workflow-client 3.0.0.rc1
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 +7 -0
- data/.gitignore +21 -0
- data/.rspec +1 -0
- data/.rubocop.yml +11 -0
- data/.rubocop_todo.yml +21 -0
- data/.travis.yml +10 -0
- data/.yardopts +1 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +35 -0
- data/Rakefile +16 -0
- data/bin/console +14 -0
- data/dor-workflow-client.gemspec +36 -0
- data/lib/dor/models/response/process.rb +55 -0
- data/lib/dor/models/response/update.rb +20 -0
- data/lib/dor/models/response/workflow.rb +54 -0
- data/lib/dor/workflow/client/connection_factory.rb +75 -0
- data/lib/dor/workflow/client/lifecycle_routes.rb +71 -0
- data/lib/dor/workflow/client/queues.rb +208 -0
- data/lib/dor/workflow/client/requestor.rb +48 -0
- data/lib/dor/workflow/client/version.rb +9 -0
- data/lib/dor/workflow/client/version_routes.rb +33 -0
- data/lib/dor/workflow/client/workflow_routes.rb +192 -0
- data/lib/dor/workflow/client.rb +77 -0
- data/lib/dor/workflow_exception.rb +6 -0
- data/spec/models/response/workflow_spec.rb +142 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/workflow/client/connection_factory_spec.rb +25 -0
- data/spec/workflow/client/lifecycle_routes_spec.rb +27 -0
- data/spec/workflow/client/requestor_spec.rb +33 -0
- data/spec/workflow/client/workflow_routes_spec.rb +53 -0
- data/spec/workflow/client_spec.rb +633 -0
- metadata +309 -0
@@ -0,0 +1,208 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
module Workflow
|
5
|
+
class Client
|
6
|
+
# Makes requests relating to the workflow queues
|
7
|
+
class Queues
|
8
|
+
def initialize(requestor:)
|
9
|
+
@requestor = requestor
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns all the distinct laneIds for a given workflow step
|
13
|
+
#
|
14
|
+
# @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
|
15
|
+
# @param [String] workflow name
|
16
|
+
# @param [String] process name
|
17
|
+
# @return [Array<String>] all of the distinct laneIds. Array will be empty if no lane ids were found
|
18
|
+
def lane_ids(repo, workflow, process)
|
19
|
+
uri = "workflow_queue/lane_ids?step=#{repo}:#{workflow}:#{process}"
|
20
|
+
doc = Nokogiri::XML(requestor.request(uri))
|
21
|
+
nodes = doc.xpath('/lanes/lane')
|
22
|
+
nodes.map { |n| n['id'] }
|
23
|
+
end
|
24
|
+
|
25
|
+
# Gets all of the workflow steps that have a status of 'queued' that have a last-updated timestamp older than the number of hours passed in
|
26
|
+
# This will enable re-queueing of jobs that have been lost by the job manager
|
27
|
+
# @param [String] repository name of the repository you want to query, like 'dor' or 'sdr'
|
28
|
+
# @param [Hash] opts optional values for query
|
29
|
+
# @option opts [Integer] :hours_ago steps older than this value will be returned by the query. If not passed in, the service defaults to 0 hours,
|
30
|
+
# meaning you will get all queued workflows
|
31
|
+
# @option opts [Integer] :limit sets the maximum number of workflow steps that can be returned. Defaults to no limit
|
32
|
+
# @return [Array[Hash]] each Hash represents a workflow step. It will have the following keys:
|
33
|
+
# :workflow, :step, :druid, :lane_id
|
34
|
+
def stale_queued_workflows(repository, opts = {})
|
35
|
+
uri_string = build_queued_uri(repository, opts)
|
36
|
+
parse_queued_workflows_response requestor.request(uri_string)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns a count of workflow steps that have a status of 'queued' that have a last-updated timestamp older than the number of hours passed in
|
40
|
+
# @param [String] repository name of the repository you want to query, like 'dor' or 'sdr'
|
41
|
+
# @param [Hash] opts optional values for query
|
42
|
+
# @option opts [Integer] :hours_ago steps older than this value will be returned by the query. If not passed in, the service defaults to 0 hours,
|
43
|
+
# meaning you will get all queued workflows
|
44
|
+
# @return [Integer] number of stale, queued steps if the :count_only option was set to true
|
45
|
+
def count_stale_queued_workflows(repository, opts = {})
|
46
|
+
uri_string = build_queued_uri(repository, opts) + '&count-only=true'
|
47
|
+
doc = Nokogiri::XML(requestor.request(uri_string))
|
48
|
+
doc.at_xpath('/objects/@count').value.to_i
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns a list of druids from the workflow service that meet the criteria
|
52
|
+
# of the passed in completed and waiting params
|
53
|
+
#
|
54
|
+
# @param [Array<String>, String] completed An array or single String of the completed steps, should use the qualified format: `repository:workflow:step-name`
|
55
|
+
# @param [String] waiting name of the waiting step
|
56
|
+
# @param [String] repository default repository to use if it isn't passed in the qualified-step-name
|
57
|
+
# @param [String] workflow default workflow to use if it isn't passed in the qualified-step-name
|
58
|
+
# @param [String] lane_id issue a query for a specific lane_id for the waiting step
|
59
|
+
# @param [Hash] options
|
60
|
+
# @param options [String] :default_repository repository to query for if not using the qualified format
|
61
|
+
# @param options [String] :default_workflow workflow to query for if not using the qualified format
|
62
|
+
# @option options [Integer] :limit maximum number of druids to return (nil for no limit)
|
63
|
+
# @return [Array<String>] Array of druids
|
64
|
+
#
|
65
|
+
# @example
|
66
|
+
# objects_for_workstep(...)
|
67
|
+
# => [
|
68
|
+
# "druid:py156ps0477",
|
69
|
+
# "druid:tt628cb6479",
|
70
|
+
# "druid:ct021wp7863"
|
71
|
+
# ]
|
72
|
+
#
|
73
|
+
# @example
|
74
|
+
# objects_for_workstep(..., "lane1")
|
75
|
+
# => {
|
76
|
+
# "druid:py156ps0477",
|
77
|
+
# "druid:tt628cb6479",
|
78
|
+
# }
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# objects_for_workstep(..., "lane1", limit: 1)
|
82
|
+
# => {
|
83
|
+
# "druid:py156ps0477",
|
84
|
+
# }
|
85
|
+
#
|
86
|
+
def objects_for_workstep(completed, waiting, lane_id = 'default', options = {})
|
87
|
+
waiting_param = qualify_step(options[:default_repository], options[:default_workflow], waiting)
|
88
|
+
uri_string = "workflow_queue?waiting=#{waiting_param}"
|
89
|
+
if completed
|
90
|
+
Array(completed).each do |step|
|
91
|
+
completed_param = qualify_step(options[:default_repository], options[:default_workflow], step)
|
92
|
+
uri_string += "&completed=#{completed_param}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
uri_string += "&limit=#{options[:limit].to_i}" if options[:limit]&.to_i&.positive?
|
97
|
+
uri_string += "&lane-id=#{lane_id}"
|
98
|
+
|
99
|
+
resp = requestor.request uri_string
|
100
|
+
#
|
101
|
+
# response looks like:
|
102
|
+
# <objects count="2">
|
103
|
+
# <object id="druid:ab123de4567"/>
|
104
|
+
# <object id="druid:ab123de9012"/>
|
105
|
+
# </objects>
|
106
|
+
#
|
107
|
+
# convert into:
|
108
|
+
# ['druid:ab123de4567', 'druid:ab123de9012']
|
109
|
+
#
|
110
|
+
result = Nokogiri::XML(resp).xpath('//object[@id]')
|
111
|
+
result.map { |n| n[:id] }
|
112
|
+
end
|
113
|
+
|
114
|
+
# Get a list of druids that have errored out in a particular workflow and step
|
115
|
+
#
|
116
|
+
# @param [String] workflow name
|
117
|
+
# @param [String] step name
|
118
|
+
# @param [String] repository -- optional, default=dor
|
119
|
+
#
|
120
|
+
# @return [Hash] hash of results, with key has a druid, and value as the error message
|
121
|
+
# @example
|
122
|
+
# client.errored_objects_for_workstep('accessionWF','content-metadata')
|
123
|
+
# => {"druid:qd556jq0580"=>"druid:qd556jq0580 - Item error; caused by
|
124
|
+
# #<Rubydora::FedoraInvalidRequest: Error modifying datastream contentMetadata for druid:qd556jq0580. See logger for details>"}
|
125
|
+
def errored_objects_for_workstep(workflow, step, repository = 'dor')
|
126
|
+
resp = requestor.request "workflow_queue?repository=#{repository}&workflow=#{workflow}&error=#{step}"
|
127
|
+
result = {}
|
128
|
+
Nokogiri::XML(resp).xpath('//object').collect do |node|
|
129
|
+
result.merge!(node['id'] => node['errorMessage'])
|
130
|
+
end
|
131
|
+
result
|
132
|
+
end
|
133
|
+
|
134
|
+
# Used by preservation robots stats reporter
|
135
|
+
def count_objects_in_step(workflow, step, type, repo)
|
136
|
+
resp = requestor.request "workflow_queue?repository=#{repo}&workflow=#{workflow}&#{type}=#{step}"
|
137
|
+
extract_object_count(resp)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns the number of objects that have a status of 'error' in a particular workflow and step
|
141
|
+
#
|
142
|
+
# @param [String] workflow name
|
143
|
+
# @param [String] step name
|
144
|
+
# @param [String] repository -- optional, default=dor
|
145
|
+
#
|
146
|
+
# @return [Integer] Number of objects with this repository:workflow:step that have a status of 'error'
|
147
|
+
def count_errored_for_workstep(workflow, step, repository = 'dor')
|
148
|
+
count_objects_in_step(workflow, step, 'error', repository)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Returns the number of objects that have a status of 'queued' in a particular workflow and step
|
152
|
+
#
|
153
|
+
# @param [String] workflow name
|
154
|
+
# @param [String] step name
|
155
|
+
# @param [String] repository -- optional, default=dor
|
156
|
+
#
|
157
|
+
# @return [Integer] Number of objects with this repository:workflow:step that have a status of 'queued'
|
158
|
+
def count_queued_for_workstep(workflow, step, repository = 'dor')
|
159
|
+
count_objects_in_step(workflow, step, 'queued', repository)
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
attr_reader :requestor
|
165
|
+
|
166
|
+
def build_queued_uri(repository, opts = {})
|
167
|
+
uri_string = "workflow_queue/all_queued?repository=#{repository}"
|
168
|
+
uri_string += "&hours-ago=#{opts[:hours_ago]}" if opts[:hours_ago]
|
169
|
+
uri_string += "&limit=#{opts[:limit]}" if opts[:limit]
|
170
|
+
uri_string
|
171
|
+
end
|
172
|
+
|
173
|
+
# Converts repo-workflow-step into repo:workflow:step
|
174
|
+
# @param [String] default_repository
|
175
|
+
# @param [String] default_workflow
|
176
|
+
# @param [String] step if contains colon :, then the value for workflow and/or workflow/repository. For example: 'jp2-create', 'assemblyWF:jp2-create' or 'dor:assemblyWF:jp2-create'
|
177
|
+
# @return [String] repo:workflow:step
|
178
|
+
# @example
|
179
|
+
# dor:assemblyWF:jp2-create
|
180
|
+
def qualify_step(default_repository, default_workflow, step)
|
181
|
+
current = step.split(/:/, 3)
|
182
|
+
current.unshift(default_workflow) if current.length < 3
|
183
|
+
current.unshift(default_repository) if current.length < 3
|
184
|
+
current.join(':')
|
185
|
+
end
|
186
|
+
|
187
|
+
def parse_queued_workflows_response(xml)
|
188
|
+
doc = Nokogiri::XML(xml)
|
189
|
+
doc.xpath('/workflows/workflow').collect do |wf_node|
|
190
|
+
{
|
191
|
+
workflow: wf_node['name'],
|
192
|
+
step: wf_node['process'],
|
193
|
+
druid: wf_node['druid'],
|
194
|
+
lane_id: wf_node['laneId']
|
195
|
+
}
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def extract_object_count(resp)
|
200
|
+
node = Nokogiri::XML(resp).at_xpath('/objects')
|
201
|
+
raise Dor::WorkflowException, 'Unable to determine count from response' if node.nil?
|
202
|
+
|
203
|
+
node['count'].to_i
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
module Workflow
|
5
|
+
class Client
|
6
|
+
# Makes requests to the workflow service and retries them if necessary.
|
7
|
+
class Requestor
|
8
|
+
def initialize(connection:)
|
9
|
+
@connection = connection
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :connection
|
13
|
+
|
14
|
+
# calls workflow_resource[uri_string]."#{meth}" with variable number of optional arguments
|
15
|
+
# The point of this is to wrap ALL remote calls with consistent error handling and logging
|
16
|
+
# @param [String] uri_string resource to request
|
17
|
+
# @param [String] meth REST method to use on resource (get, put, post, delete, etc.)
|
18
|
+
# @param [String] payload body for (e.g. put) request
|
19
|
+
# @param [Hash] opts addtional headers options
|
20
|
+
# @return [Object] response from method
|
21
|
+
def request(uri_string, meth = 'get', payload = '', opts = {})
|
22
|
+
response = send_workflow_resource_request(uri_string, meth, payload, opts)
|
23
|
+
response.body
|
24
|
+
rescue Faraday::Error => e
|
25
|
+
msg = "Failed to retrieve resource: #{meth} #{base_url}/#{uri_string}"
|
26
|
+
msg += " (HTTP status #{e.response[:status]})" if e.respond_to?(:response) && e.response
|
27
|
+
raise Dor::WorkflowException, msg
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
##
|
33
|
+
# Get the configured URL for the connection
|
34
|
+
def base_url
|
35
|
+
connection.url_prefix
|
36
|
+
end
|
37
|
+
|
38
|
+
def send_workflow_resource_request(uri_string, meth = 'get', payload = '', opts = {})
|
39
|
+
connection.public_send(meth, uri_string) do |req|
|
40
|
+
req.body = payload unless meth == 'delete'
|
41
|
+
req.params.update opts[:params] if opts[:params]
|
42
|
+
req.headers.update opts.except(:params)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
module Workflow
|
5
|
+
class Client
|
6
|
+
# Makes requests relating to versions
|
7
|
+
class VersionRoutes
|
8
|
+
def initialize(requestor:)
|
9
|
+
@requestor = requestor
|
10
|
+
end
|
11
|
+
|
12
|
+
# Calls the versionClose endpoint of the workflow service:
|
13
|
+
#
|
14
|
+
# - completes the versioningWF:submit-version and versioningWF:start-accession steps
|
15
|
+
# - initiates accesssionWF
|
16
|
+
#
|
17
|
+
# @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
|
18
|
+
# @param [String] druid The id of the object to delete the workflow from
|
19
|
+
# @param [Boolean] create_accession_wf Option to create accessionWF when closing a version. Defaults to true
|
20
|
+
def close_version(repo, druid, create_accession_wf = true)
|
21
|
+
uri = "#{repo}/objects/#{druid}/versionClose"
|
22
|
+
uri += '?create-accession=false' unless create_accession_wf
|
23
|
+
requestor.request(uri, 'post', '')
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :requestor
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
module Workflow
|
5
|
+
class Client
|
6
|
+
# Makes requests relating to a workflow
|
7
|
+
class WorkflowRoutes
|
8
|
+
def initialize(requestor:)
|
9
|
+
@requestor = requestor
|
10
|
+
end
|
11
|
+
|
12
|
+
# Creates a workflow for a given object in the repository. If this particular workflow for this objects exists,
|
13
|
+
# it will replace the old workflow with wf_xml passed to this method. You have the option of creating a datastream or not.
|
14
|
+
# Returns true on success. Caller must handle any exceptions
|
15
|
+
#
|
16
|
+
# @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
|
17
|
+
# @param [String] druid The id of the object
|
18
|
+
# @param [String] workflow_name The name of the workflow you want to create
|
19
|
+
# @param [String] wf_xml The xml that represents the workflow
|
20
|
+
# @param [Hash] opts optional params
|
21
|
+
# @option opts [Boolean] :create_ds if true, a workflow datastream will be created in Fedora. Set to false if you do not want a datastream to be created
|
22
|
+
# If you do not pass in an <b>opts</b> Hash, then :create_ds is set to true by default
|
23
|
+
# @option opts [String] :lane_id adds laneId attribute to all process elements in the wf_xml workflow xml. Defaults to a value of 'default'
|
24
|
+
# @return [Boolean] always true
|
25
|
+
#
|
26
|
+
def create_workflow(repo, druid, workflow_name, wf_xml, opts = { create_ds: true })
|
27
|
+
lane_id = opts.fetch(:lane_id, 'default')
|
28
|
+
xml = add_lane_id_to_workflow_xml(lane_id, wf_xml)
|
29
|
+
_status = requestor.request "#{repo}/objects/#{druid}/workflows/#{workflow_name}", 'put', xml,
|
30
|
+
content_type: 'application/xml',
|
31
|
+
params: { 'create-ds' => opts[:create_ds] }
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
# Updates the status of one step in a workflow.
|
36
|
+
# Returns true on success. Caller must handle any exceptions
|
37
|
+
#
|
38
|
+
# @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
|
39
|
+
# @param [String] druid The id of the object
|
40
|
+
# @param [String] workflow The name of the workflow
|
41
|
+
# @param [String] process The name of the process step
|
42
|
+
# @param [String] status The status that you want to set -- using one of the values in VALID_STATUS
|
43
|
+
# @param [Hash] opts optional values for the workflow step
|
44
|
+
# @option opts [Float] :elapsed The number of seconds it took to complete this step. Can have a decimal. Is set to 0 if not passed in.
|
45
|
+
# @option opts [String] :lifecycle Bookeeping label for this particular workflow step. Examples are: 'registered', 'shelved'
|
46
|
+
# @option opts [String] :note Any kind of string annotation that you want to attach to the workflow
|
47
|
+
# @option opts [String] :lane_id Id of processing lane used by the job manager. Can convey priority or name of an applicaiton specific processing lane (e.g. 'high', 'critical', 'hydrus')
|
48
|
+
# @option opts [String] :current_status Setting this string tells the workflow service to compare the current status to this value. If the current value does not match this value, the update is not performed
|
49
|
+
# @return [Boolean] always true
|
50
|
+
# Http Call
|
51
|
+
# ==
|
52
|
+
# The method does an HTTP PUT to the URL defined in `Dor::WF_URI`. As an example:
|
53
|
+
#
|
54
|
+
# PUT "/dor/objects/pid:123/workflows/GoogleScannedWF/convert"
|
55
|
+
# <process name=\"convert\" status=\"completed\" />"
|
56
|
+
def update_workflow_status(repo, druid, workflow, process, status, opts = {})
|
57
|
+
raise ArgumentError, "Unknown status value #{status}" unless VALID_STATUS.include?(status.downcase)
|
58
|
+
|
59
|
+
opts = { elapsed: 0, lifecycle: nil, note: nil }.merge!(opts)
|
60
|
+
opts[:elapsed] = opts[:elapsed].to_s
|
61
|
+
current_status = opts.delete(:current_status)
|
62
|
+
xml = create_process_xml({ name: process, status: status.downcase }.merge!(opts))
|
63
|
+
uri = "#{repo}/objects/#{druid}/workflows/#{workflow}/#{process}"
|
64
|
+
uri += "?current-status=#{current_status.downcase}" if current_status
|
65
|
+
response = requestor.request(uri, 'put', xml, content_type: 'application/xml')
|
66
|
+
|
67
|
+
Workflow::Response::Update.new(json: response)
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Retrieves the process status of the given workflow for the given object identifier
|
72
|
+
# @param [String] repo The repository the object resides in. Currently recoginzes "dor" and "sdr".
|
73
|
+
# @param [String] druid The id of the object
|
74
|
+
# @param [String] workflow The name of the workflow
|
75
|
+
# @param [String] process The name of the process step
|
76
|
+
# @return [String] status for repo-workflow-process-druid
|
77
|
+
def workflow_status(repo, druid, workflow, process)
|
78
|
+
workflow_md = workflow_xml(repo, druid, workflow)
|
79
|
+
doc = Nokogiri::XML(workflow_md)
|
80
|
+
raise Dor::WorkflowException, "Unable to parse response:\n#{workflow_md}" if doc.root.nil?
|
81
|
+
|
82
|
+
processes = doc.root.xpath("//process[@name='#{process}']")
|
83
|
+
process = processes.max { |a, b| a.attr('version').to_i <=> b.attr('version').to_i }
|
84
|
+
process&.attr('status')
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Retrieves the raw XML for the given workflow
|
89
|
+
# @param [String] repo The repository the object resides in. Currently recoginzes "dor" and "sdr".
|
90
|
+
# @param [String] druid The id of the object
|
91
|
+
# @param [String] workflow The name of the workflow
|
92
|
+
# @return [String] XML of the workflow
|
93
|
+
def workflow_xml(repo, druid, workflow)
|
94
|
+
raise ArgumentError, 'missing workflow' unless workflow
|
95
|
+
|
96
|
+
requestor.request "#{repo}/objects/#{druid}/workflows/#{workflow}"
|
97
|
+
end
|
98
|
+
|
99
|
+
# Updates the status of one step in a workflow to error.
|
100
|
+
# Returns true on success. Caller must handle any exceptions
|
101
|
+
#
|
102
|
+
# @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
|
103
|
+
# @param [String] druid The id of the object
|
104
|
+
# @param [String] workflow The name of the workflow
|
105
|
+
# @param [String] error_msg The error message. Ideally, this is a brief message describing the error
|
106
|
+
# @param [Hash] opts optional values for the workflow step
|
107
|
+
# @option opts [String] :error_text A slot to hold more information about the error, like a full stacktrace
|
108
|
+
# @return [Boolean] always true
|
109
|
+
#
|
110
|
+
# Http Call
|
111
|
+
# ==
|
112
|
+
# The method does an HTTP PUT to the URL defined in `Dor::WF_URI`.
|
113
|
+
#
|
114
|
+
# PUT "/dor/objects/pid:123/workflows/GoogleScannedWF/convert"
|
115
|
+
# <process name=\"convert\" status=\"error\" />"
|
116
|
+
def update_workflow_error_status(repo, druid, workflow, process, error_msg, opts = {})
|
117
|
+
opts = { error_text: nil }.merge!(opts)
|
118
|
+
xml = create_process_xml({ name: process, status: 'error', errorMessage: error_msg }.merge!(opts))
|
119
|
+
requestor.request "#{repo}/objects/#{druid}/workflows/#{workflow}/#{process}", 'put', xml, content_type: 'application/xml'
|
120
|
+
true
|
121
|
+
end
|
122
|
+
|
123
|
+
#
|
124
|
+
# Retrieves the raw XML for all the workflows for the the given object
|
125
|
+
# @param [String] druid The id of the object
|
126
|
+
# @return [String] XML of the workflow
|
127
|
+
def all_workflows_xml(druid)
|
128
|
+
requestor.request "objects/#{druid}/workflows"
|
129
|
+
end
|
130
|
+
|
131
|
+
# Get workflow names into an array for given PID
|
132
|
+
# This method only works when this gem is used in a project that is configured to connect to DOR
|
133
|
+
#
|
134
|
+
# @param [String] pid of druid
|
135
|
+
# @param [String] repo repository for the object
|
136
|
+
# @return [Array<String>] list of worklows
|
137
|
+
# @example
|
138
|
+
# client.workflows('druid:sr100hp0609')
|
139
|
+
# => ["accessionWF", "assemblyWF", "disseminationWF"]
|
140
|
+
def workflows(pid, repo = 'dor')
|
141
|
+
xml_doc = Nokogiri::XML(workflow_xml(repo, pid, ''))
|
142
|
+
xml_doc.xpath('//workflow').collect { |workflow| workflow['id'] }
|
143
|
+
end
|
144
|
+
|
145
|
+
# @param [String] repo repository of the object
|
146
|
+
# @param [String] pid id of object
|
147
|
+
# @param [String] workflow_name The name of the workflow
|
148
|
+
# @return [Workflow::Response::Workflow]
|
149
|
+
def workflow(repo: 'dor', pid:, workflow_name:)
|
150
|
+
xml = workflow_xml(repo, pid, workflow_name)
|
151
|
+
Workflow::Response::Workflow.new(xml: xml)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Deletes a workflow from a particular repository and druid
|
155
|
+
# @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
|
156
|
+
# @param [String] druid The id of the object to delete the workflow from
|
157
|
+
# @param [String] workflow The name of the workflow to be deleted
|
158
|
+
# @return [Boolean] always true
|
159
|
+
def delete_workflow(repo, druid, workflow)
|
160
|
+
requestor.request "#{repo}/objects/#{druid}/workflows/#{workflow}", 'delete'
|
161
|
+
true
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
attr_reader :requestor
|
167
|
+
|
168
|
+
# Adds laneId attributes to each process of workflow xml
|
169
|
+
#
|
170
|
+
# @param [String] lane_id to add to each process element
|
171
|
+
# @param [String] wf_xml the workflow xml
|
172
|
+
# @return [String] wf_xml with lane_id attributes
|
173
|
+
def add_lane_id_to_workflow_xml(lane_id, wf_xml)
|
174
|
+
doc = Nokogiri::XML(wf_xml)
|
175
|
+
doc.xpath('/workflow/process').each { |proc| proc['laneId'] = lane_id }
|
176
|
+
doc.to_xml
|
177
|
+
end
|
178
|
+
|
179
|
+
# @param [Hash] params
|
180
|
+
# @return [String]
|
181
|
+
def create_process_xml(params)
|
182
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
183
|
+
attrs = params.reject { |_k, v| v.nil? }
|
184
|
+
attrs = Hash[attrs.map { |k, v| [k.to_s.camelize(:lower), v] }] # camelize all the keys in the attrs hash
|
185
|
+
xml.process(attrs)
|
186
|
+
end
|
187
|
+
builder.to_xml
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext'
|
5
|
+
require 'nokogiri'
|
6
|
+
require 'dor/workflow_exception'
|
7
|
+
require 'dor/models/response/workflow'
|
8
|
+
require 'dor/models/response/update'
|
9
|
+
|
10
|
+
require 'dor/workflow/client/connection_factory'
|
11
|
+
require 'dor/workflow/client/lifecycle_routes'
|
12
|
+
require 'dor/workflow/client/queues'
|
13
|
+
require 'dor/workflow/client/requestor'
|
14
|
+
require 'dor/workflow/client/version_routes'
|
15
|
+
require 'dor/workflow/client/workflow_routes'
|
16
|
+
|
17
|
+
module Dor
|
18
|
+
module Workflow
|
19
|
+
# TODO: VALID_STATUS should be just another attribute w/ default
|
20
|
+
#
|
21
|
+
# Create and update workflows
|
22
|
+
class Client
|
23
|
+
# From Workflow Service's admin/Process.java
|
24
|
+
VALID_STATUS = %w[waiting completed error queued skipped hold].freeze
|
25
|
+
|
26
|
+
attr_accessor :requestor
|
27
|
+
|
28
|
+
# Configure the workflow service
|
29
|
+
# @param [String] :url points to the workflow service
|
30
|
+
# @param [Logger] :logger defaults writing to workflow_service.log with weekly rotation
|
31
|
+
# @param [Integer] :timeout number of seconds for HTTP timeout
|
32
|
+
# @param [Faraday::Connection] :connection the REST client resource
|
33
|
+
def initialize(url: nil, logger: default_logger, timeout: nil, connection: nil)
|
34
|
+
raise ArgumentError, 'You must provide either a connection or a url' if !url && !connection
|
35
|
+
|
36
|
+
@requestor = Requestor.new(connection: connection || ConnectionFactory.build_connection(url, timeout: timeout, logger: logger))
|
37
|
+
end
|
38
|
+
|
39
|
+
delegate :create_workflow, :update_workflow_status, :workflow_status, :workflow_xml,
|
40
|
+
:update_workflow_error_status, :all_workflows_xml, :workflows,
|
41
|
+
:workflow, :delete_workflow, to: :workflow_routes
|
42
|
+
|
43
|
+
delegate :lifecycle, :active_lifecycle, :milestones, to: :lifecycle_routes
|
44
|
+
|
45
|
+
delegate :lane_ids, :stale_queued_workflows, :count_stale_queued_workflows,
|
46
|
+
:objects_for_workstep, :errored_objects_for_workstep, :count_objects_in_step,
|
47
|
+
:count_errored_for_workstep, :count_queued_for_workstep,
|
48
|
+
to: :queues
|
49
|
+
|
50
|
+
delegate :close_version, to: :version_routes
|
51
|
+
|
52
|
+
def queues
|
53
|
+
@queues ||= Queues.new(requestor: requestor)
|
54
|
+
end
|
55
|
+
|
56
|
+
def workflow_routes
|
57
|
+
@workflow_routes ||= WorkflowRoutes.new(requestor: requestor)
|
58
|
+
end
|
59
|
+
|
60
|
+
def lifecycle_routes
|
61
|
+
@lifecycle_routes ||= LifecycleRoutes.new(requestor: requestor)
|
62
|
+
end
|
63
|
+
|
64
|
+
def version_routes
|
65
|
+
@version_routes ||= VersionRoutes.new(requestor: requestor)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Among other things, a distinct method helps tests mock default logger
|
71
|
+
# @return [Logger] default logger object
|
72
|
+
def default_logger
|
73
|
+
Logger.new('workflow_service.log', 'weekly')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|