dor-workflow-service 1.7.6 → 1.7.7
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 +13 -5
- data/.rubocop_todo.yml +5 -54
- data/.travis.yml +2 -0
- data/Gemfile +1 -1
- data/Rakefile +1 -1
- data/dor-workflow-service.gemspec +18 -17
- data/lib/dor-workflow-service.rb +1 -1
- data/lib/dor/services/workflow_service.rb +134 -94
- data/lib/dor/workflow_version.rb +1 -1
- data/spec/plumbing_spec.rb +18 -0
- data/spec/spec_helper.rb +0 -3
- data/spec/workflow_service_spec.rb +162 -153
- metadata +48 -32
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NGMyMjcyOTg4YjEzYzAwYjFiMTI1NjMzYzNmYjk1YjlhZDVmNTZhYw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
YzA4M2RjMDc0MWFmOGU2NDBkZjUxZDU3M2M4MTgzOWQ5YmMyY2IxMw==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
N2Y3Y2JlN2Q0MzYwZWQ5MWE4NDZiNzM1MWY2NzJkYTM3OWMzNzUwMzRjZGFj
|
10
|
+
Y2FmNjA3ZjY3YjFjODliMDhkMTYzNzIzMmUzZjhlMzkzYzcxMWU3MDEzZDVk
|
11
|
+
Y2Q0OGM1OWNmYzIwNmJlODk0OWQ4ZWE2YmY5M2Q5OTNlY2IxMzU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YTUzYTVkOTQ4Y2NmMTc0MjRjMzM2NzdkOWRkMDM2ZjY2OTkzNTcyYWZmY2I3
|
14
|
+
YTE1NzhjOWUyNzY2ODc4ZmFhNzlmZTZiYjVmN2VmYjgxM2YxOTVjNzVhM2Y2
|
15
|
+
ZmRhYTIzMTgzYWE1MGIzMjlkZTc2YWUzNWYwYTkyZGJkYTg4NzQ=
|
data/.rubocop_todo.yml
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# This configuration was generated by `rubocop --auto-gen-config`
|
2
|
-
# on 2015-
|
2
|
+
# on 2015-08-28 15:56:06 -0700 using RuboCop version 0.32.1.
|
3
3
|
# The point is for the user to remove these configuration records
|
4
4
|
# one by one as the offenses are removed from the code base.
|
5
5
|
# Note that changes in the inspected code, or installation of new
|
@@ -60,12 +60,6 @@ Style/ClassVars:
|
|
60
60
|
Style/Documentation:
|
61
61
|
Enabled: false
|
62
62
|
|
63
|
-
# Offense count: 7
|
64
|
-
# Cop supports --auto-correct.
|
65
|
-
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
66
|
-
Style/EmptyLinesAroundBlockBody:
|
67
|
-
Enabled: false
|
68
|
-
|
69
63
|
# Offense count: 2
|
70
64
|
# Cop supports --auto-correct.
|
71
65
|
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
@@ -94,20 +88,15 @@ Style/HashSyntax:
|
|
94
88
|
Style/LeadingCommentSpace:
|
95
89
|
Enabled: false
|
96
90
|
|
97
|
-
# Offense count: 1
|
98
|
-
# Cop supports --auto-correct.
|
99
|
-
Style/MethodCallParentheses:
|
100
|
-
Enabled: false
|
101
|
-
|
102
91
|
# Offense count: 2
|
103
92
|
# Cop supports --auto-correct.
|
104
|
-
|
105
|
-
Style/MethodDefParentheses:
|
93
|
+
Style/MultilineBlockLayout:
|
106
94
|
Enabled: false
|
107
95
|
|
108
|
-
# Offense count:
|
96
|
+
# Offense count: 6
|
109
97
|
# Cop supports --auto-correct.
|
110
|
-
|
98
|
+
# Configuration parameters: AllowSafeAssignment.
|
99
|
+
Style/ParenthesesAroundCondition:
|
111
100
|
Enabled: false
|
112
101
|
|
113
102
|
# Offense count: 10
|
@@ -121,39 +110,12 @@ Style/PercentLiteralDelimiters:
|
|
121
110
|
Style/RaiseArgs:
|
122
111
|
Enabled: false
|
123
112
|
|
124
|
-
# Offense count: 10
|
125
|
-
# Cop supports --auto-correct.
|
126
|
-
# Configuration parameters: AllowMultipleReturnValues.
|
127
|
-
Style/RedundantReturn:
|
128
|
-
Enabled: false
|
129
|
-
|
130
|
-
# Offense count: 3
|
131
|
-
# Cop supports --auto-correct.
|
132
|
-
Style/RedundantSelf:
|
133
|
-
Enabled: false
|
134
|
-
|
135
113
|
# Offense count: 5
|
136
114
|
# Cop supports --auto-correct.
|
137
115
|
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
138
116
|
Style/SignalException:
|
139
117
|
Enabled: false
|
140
118
|
|
141
|
-
# Offense count: 14
|
142
|
-
# Cop supports --auto-correct.
|
143
|
-
Style/SpaceAfterComma:
|
144
|
-
Enabled: false
|
145
|
-
|
146
|
-
# Offense count: 6
|
147
|
-
# Cop supports --auto-correct.
|
148
|
-
Style/SpaceAfterControlKeyword:
|
149
|
-
Enabled: false
|
150
|
-
|
151
|
-
# Offense count: 7
|
152
|
-
# Cop supports --auto-correct.
|
153
|
-
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
154
|
-
Style/SpaceAroundEqualsInParameterDefault:
|
155
|
-
Enabled: false
|
156
|
-
|
157
119
|
# Offense count: 11
|
158
120
|
# Cop supports --auto-correct.
|
159
121
|
# Configuration parameters: MultiSpaceAllowedForOperators.
|
@@ -188,17 +150,6 @@ Style/SpaceInsideHashLiteralBraces:
|
|
188
150
|
Style/SpaceInsideParens:
|
189
151
|
Enabled: false
|
190
152
|
|
191
|
-
# Offense count: 1
|
192
|
-
# Cop supports --auto-correct.
|
193
|
-
Style/SpecialGlobalVars:
|
194
|
-
Enabled: false
|
195
|
-
|
196
|
-
# Offense count: 128
|
197
|
-
# Cop supports --auto-correct.
|
198
|
-
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
199
|
-
Style/StringLiterals:
|
200
|
-
Enabled: false
|
201
|
-
|
202
153
|
# Offense count: 1
|
203
154
|
# Cop supports --auto-correct.
|
204
155
|
# Configuration parameters: IgnoredMethods.
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -4,27 +4,28 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
require 'dor/workflow_version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |gem|
|
7
|
-
gem.name =
|
7
|
+
gem.name = 'dor-workflow-service'
|
8
8
|
gem.version = Dor::Workflow::Service::VERSION
|
9
|
-
gem.authors = [
|
10
|
-
gem.email = [
|
11
|
-
gem.description =
|
12
|
-
gem.summary =
|
13
|
-
gem.homepage =
|
9
|
+
gem.authors = ['Willy Mene', 'Darren Hardy']
|
10
|
+
gem.email = ['wmene@stanford.edu']
|
11
|
+
gem.description = 'Enables Ruby manipulation of the DOR Workflow Service via its REST API'
|
12
|
+
gem.summary = 'Provides convenience methods to work with the DOR Workflow Service'
|
13
|
+
gem.homepage = 'https://consul.stanford.edu/display/DOR/DOR+services#DORservices-initializeworkflow'
|
14
14
|
|
15
|
-
gem.files = `git ls-files`.split(
|
15
|
+
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
16
16
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
17
|
gem.test_files = gem.files.grep(%r{^(spec)/})
|
18
|
-
gem.require_paths = [
|
18
|
+
gem.require_paths = ['lib']
|
19
19
|
|
20
|
-
gem.add_dependency
|
21
|
-
gem.add_dependency
|
22
|
-
gem.add_dependency
|
23
|
-
gem.add_dependency
|
20
|
+
gem.add_dependency 'activesupport', '>= 3.2.1', '< 5'
|
21
|
+
gem.add_dependency 'nokogiri', '~> 1.6.0'
|
22
|
+
gem.add_dependency 'rest-client', '~> 1.7'
|
23
|
+
gem.add_dependency 'retries'
|
24
|
+
gem.add_dependency 'confstruct', '>= 0.2.7', '< 2'
|
24
25
|
|
25
|
-
gem.add_development_dependency
|
26
|
-
gem.add_development_dependency
|
27
|
-
gem.add_development_dependency
|
28
|
-
gem.add_development_dependency
|
29
|
-
gem.add_development_dependency
|
26
|
+
gem.add_development_dependency 'rake'
|
27
|
+
gem.add_development_dependency 'rspec', '~> 3.3'
|
28
|
+
gem.add_development_dependency 'yard'
|
29
|
+
gem.add_development_dependency 'redcarpet'
|
30
|
+
gem.add_development_dependency 'equivalent-xml', '~> 0.5.1'
|
30
31
|
end
|
data/lib/dor-workflow-service.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
require
|
1
|
+
require 'dor/workflow_version'
|
2
2
|
require 'dor/services/workflow_service'
|
@@ -2,13 +2,24 @@ require 'rest-client'
|
|
2
2
|
require 'active_support'
|
3
3
|
require 'active_support/core_ext'
|
4
4
|
require 'nokogiri'
|
5
|
+
require 'retries'
|
5
6
|
|
6
7
|
module Dor
|
7
8
|
|
8
|
-
#
|
9
|
-
|
9
|
+
# TODO: major version revision: change pattern of usage to be normal non-singleton class
|
10
|
+
# TODO: convert @@class_vars to regular attributes
|
11
|
+
# TODO: create normal initalize method, deprecate configure
|
12
|
+
# TODO: hardcoded 'true' returns are dumb, instead return the response object where possible
|
13
|
+
# TODO: VALID_STATUS should be just another attribute w/ default
|
14
|
+
# TODO: allow constructor/initalizer to receive RestClient::Resource object(s), not just URLs (solves SSL/proxy config problem)
|
15
|
+
# TODO: allow constructor/initalizer to receive logger
|
16
|
+
|
17
|
+
# Create and update workflows
|
18
|
+
class WorkflowService
|
10
19
|
class << self
|
11
20
|
|
21
|
+
@@handler = nil
|
22
|
+
@@logger = nil
|
12
23
|
@@resource = nil
|
13
24
|
@@dor_services_url = nil
|
14
25
|
|
@@ -32,9 +43,12 @@ module Dor
|
|
32
43
|
def create_workflow(repo, druid, workflow_name, wf_xml, opts = {:create_ds => true})
|
33
44
|
lane_id = opts.fetch(:lane_id, 'default')
|
34
45
|
xml = add_lane_id_to_workflow_xml(lane_id, wf_xml)
|
35
|
-
|
36
|
-
|
37
|
-
|
46
|
+
workflow_resource_method "#{repo}/objects/#{druid}/workflows/#{workflow_name}", 'put', xml,
|
47
|
+
{
|
48
|
+
:content_type => 'application/xml',
|
49
|
+
:params => { 'create-ds' => opts[:create_ds] }
|
50
|
+
}
|
51
|
+
true
|
38
52
|
end
|
39
53
|
|
40
54
|
# Updates the status of one step in a workflow.
|
@@ -60,19 +74,19 @@ module Dor
|
|
60
74
|
# <process name=\"convert\" status=\"completed\" />"
|
61
75
|
def update_workflow_status(repo, druid, workflow, process, status, opts = {})
|
62
76
|
raise ArgumentError, "Unknown status value #{status}" unless VALID_STATUS.include?(status.downcase)
|
63
|
-
opts = {:elapsed => 0, :lifecycle => nil, :note => nil}.merge!(opts)
|
77
|
+
opts = { :elapsed => 0, :lifecycle => nil, :note => nil }.merge!(opts)
|
64
78
|
opts[:elapsed] = opts[:elapsed].to_s
|
65
79
|
current_status = opts.delete(:current_status)
|
66
|
-
xml = create_process_xml({:name => process, :status => status.downcase}.merge!(opts))
|
80
|
+
xml = create_process_xml({ :name => process, :status => status.downcase }.merge!(opts))
|
67
81
|
uri = "#{repo}/objects/#{druid}/workflows/#{workflow}/#{process}"
|
68
82
|
uri << "?current-status=#{current_status.downcase}" if current_status
|
69
|
-
|
70
|
-
|
83
|
+
workflow_resource_method(uri, 'put', xml, { :content_type => 'application/xml' })
|
84
|
+
true
|
71
85
|
end
|
72
86
|
|
73
87
|
#
|
74
88
|
# Retrieves the process status of the given workflow for the given object identifier
|
75
|
-
# @param [String] repo The repository the object resides in.
|
89
|
+
# @param [String] repo The repository the object resides in. Currently recoginzes "dor" and "sdr".
|
76
90
|
# @param [String] druid The id of the object
|
77
91
|
# @param [String] workflow The name of the workflow
|
78
92
|
# @param [String] process The name of the process step
|
@@ -80,35 +94,34 @@ module Dor
|
|
80
94
|
def get_workflow_status(repo, druid, workflow, process)
|
81
95
|
workflow_md = get_workflow_xml(repo, druid, workflow)
|
82
96
|
doc = Nokogiri::XML(workflow_md)
|
83
|
-
raise Exception.new("Unable to parse response:\n#{workflow_md}") if
|
84
|
-
|
97
|
+
raise Exception.new("Unable to parse response:\n#{workflow_md}") if doc.root.nil?
|
85
98
|
status = doc.root.at_xpath("//process[@name='#{process}']/@status")
|
86
|
-
status=status.content if status
|
87
|
-
|
99
|
+
status = status.content if status
|
100
|
+
status
|
88
101
|
end
|
89
102
|
|
90
103
|
#
|
91
104
|
# Retrieves the raw XML for the given workflow
|
92
|
-
# @param [String] repo The repository the object resides in.
|
105
|
+
# @param [String] repo The repository the object resides in. Currently recoginzes "dor" and "sdr".
|
93
106
|
# @param [String] druid The id of the object
|
94
107
|
# @param [String] workflow The name of the workflow
|
95
108
|
# @return [String] XML of the workflow
|
96
109
|
def get_workflow_xml(repo, druid, workflow)
|
97
|
-
|
110
|
+
workflow_resource_method "#{repo}/objects/#{druid}/workflows/#{workflow}"
|
98
111
|
end
|
99
112
|
|
100
113
|
# Get workflow names into an array for given PID
|
101
114
|
# This method only works when this gem is used in a project that is configured to connect to DOR
|
102
115
|
#
|
103
116
|
# @param [String] pid of druid
|
104
|
-
# @param [String] repo repository for the object
|
117
|
+
# @param [String] repo repository for the object
|
105
118
|
# @return [Array<String>] list of worklows
|
106
119
|
# @example
|
107
120
|
# Dor::WorkflowService.get_workflows('druid:sr100hp0609')
|
108
121
|
# => ["accessionWF", "assemblyWF", "disseminationWF"]
|
109
|
-
def get_workflows(pid, repo='dor')
|
110
|
-
xml_doc=Nokogiri::XML(get_workflow_xml(repo,pid,''))
|
111
|
-
|
122
|
+
def get_workflows(pid, repo = 'dor')
|
123
|
+
xml_doc = Nokogiri::XML(get_workflow_xml(repo, pid, ''))
|
124
|
+
xml_doc.xpath('//workflow').collect {|workflow| workflow['id']}
|
112
125
|
end
|
113
126
|
|
114
127
|
# Get active workflow names into an array for given PID
|
@@ -121,7 +134,7 @@ module Dor
|
|
121
134
|
# Dor::WorkflowService.get_workflows('dor', 'druid:sr100hp0609')
|
122
135
|
# => ["accessionWF", "assemblyWF", "disseminationWF"]
|
123
136
|
def get_active_workflows(repo, pid)
|
124
|
-
doc = Nokogiri::XML(get_workflow_xml(repo,pid,''))
|
137
|
+
doc = Nokogiri::XML(get_workflow_xml(repo, pid, ''))
|
125
138
|
doc.xpath( %(//workflow[not(process/@archived)]/@id ) ).map {|n| n.value}
|
126
139
|
end
|
127
140
|
|
@@ -145,8 +158,8 @@ module Dor
|
|
145
158
|
def update_workflow_error_status(repo, druid, workflow, process, error_msg, opts = {})
|
146
159
|
opts = {:error_text => nil}.merge!(opts)
|
147
160
|
xml = create_process_xml({:name => process, :status => 'error', :errorMessage => error_msg}.merge!(opts))
|
148
|
-
|
149
|
-
|
161
|
+
workflow_resource_method "#{repo}/objects/#{druid}/workflows/#{workflow}/#{process}", 'put', xml, {:content_type => 'application/xml'}
|
162
|
+
true
|
150
163
|
end
|
151
164
|
|
152
165
|
# Deletes a workflow from a particular repository and druid
|
@@ -155,8 +168,8 @@ module Dor
|
|
155
168
|
# @param [String] workflow The name of the workflow to be deleted
|
156
169
|
# @return [Boolean] always true
|
157
170
|
def delete_workflow(repo, druid, workflow)
|
158
|
-
|
159
|
-
|
171
|
+
workflow_resource_method "#{repo}/objects/#{druid}/workflows/#{workflow}", 'delete'
|
172
|
+
true
|
160
173
|
end
|
161
174
|
|
162
175
|
# Returns the Date for a requested milestone from workflow lifecycle
|
@@ -171,10 +184,9 @@ module Dor
|
|
171
184
|
# <milestone date="2010-06-15T16:08:58-0700">released</milestone>
|
172
185
|
# </lifecycle>
|
173
186
|
def get_lifecycle(repo, druid, milestone)
|
174
|
-
doc =
|
187
|
+
doc = query_lifecycle(repo, druid)
|
175
188
|
milestone = doc.at_xpath("//lifecycle/milestone[text() = '#{milestone}']")
|
176
189
|
return Time.parse(milestone['date']) if milestone
|
177
|
-
|
178
190
|
nil
|
179
191
|
end
|
180
192
|
|
@@ -190,17 +202,16 @@ module Dor
|
|
190
202
|
# <milestone date="2010-06-15T16:08:58-0700">released</milestone>
|
191
203
|
# </lifecycle>
|
192
204
|
def get_active_lifecycle(repo, druid, milestone)
|
193
|
-
doc =
|
205
|
+
doc = query_lifecycle(repo, druid, true)
|
194
206
|
milestone = doc.at_xpath("//lifecycle/milestone[text() = '#{milestone}']")
|
195
207
|
return Time.parse(milestone['date']) if milestone
|
196
|
-
|
197
208
|
nil
|
198
209
|
end
|
199
210
|
|
200
211
|
# @return [Hash]
|
201
212
|
def get_milestones(repo, druid)
|
202
|
-
doc =
|
203
|
-
doc.xpath(
|
213
|
+
doc = query_lifecycle(repo, druid)
|
214
|
+
doc.xpath('//lifecycle/milestone').collect do |node|
|
204
215
|
{ :milestone => node.text, :at => Time.parse(node['date']), :version => node['version'] }
|
205
216
|
end
|
206
217
|
end
|
@@ -208,16 +219,13 @@ module Dor
|
|
208
219
|
# Converts repo-workflow-step into repo:workflow:step
|
209
220
|
# @param [String] default_repository
|
210
221
|
# @param [String] default_workflow
|
211
|
-
# @param [String] step if contains colon :, then
|
212
|
-
# the value for workflow and/or workflow/repository.
|
213
|
-
# for example, jp2-create, or assemblyWF:jp2-create,
|
214
|
-
# or dor:assemblyWF:jp2-create
|
222
|
+
# @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'
|
215
223
|
# @return [String] repo:workflow:step
|
216
224
|
# @example
|
217
225
|
# dor:assemblyWF:jp2-create
|
218
226
|
def qualify_step(default_repository, default_workflow, step)
|
219
|
-
current = step.split(/:/,3)
|
220
|
-
current.unshift(default_workflow)
|
227
|
+
current = step.split(/:/, 3)
|
228
|
+
current.unshift(default_workflow) if current.length < 3
|
221
229
|
current.unshift(default_repository) if current.length < 3
|
222
230
|
current.join(':')
|
223
231
|
end
|
@@ -256,24 +264,21 @@ module Dor
|
|
256
264
|
# "druid:py156ps0477",
|
257
265
|
# }
|
258
266
|
#
|
259
|
-
def get_objects_for_workstep
|
260
|
-
waiting_param = qualify_step(options[:default_repository],options[:default_workflow],waiting)
|
267
|
+
def get_objects_for_workstep(completed, waiting, lane_id = 'default', options = {})
|
268
|
+
waiting_param = qualify_step(options[:default_repository], options[:default_workflow], waiting)
|
261
269
|
uri_string = "workflow_queue?waiting=#{waiting_param}"
|
262
|
-
if
|
270
|
+
if completed
|
263
271
|
Array(completed).each do |step|
|
264
|
-
completed_param = qualify_step(options[:default_repository],options[:default_workflow],step)
|
272
|
+
completed_param = qualify_step(options[:default_repository], options[:default_workflow], step)
|
265
273
|
uri_string << "&completed=#{completed_param}"
|
266
274
|
end
|
267
275
|
end
|
268
276
|
|
269
|
-
if options[:limit] && options[:limit].to_i > 0
|
270
|
-
uri_string << "&limit=#{options[:limit].to_i}"
|
271
|
-
end
|
272
|
-
|
277
|
+
uri_string << "&limit=#{options[:limit].to_i}" if options[:limit] && options[:limit].to_i > 0
|
273
278
|
uri_string << "&lane-id=#{lane_id}"
|
274
279
|
|
275
|
-
workflow_resource.options[:timeout] = 5 * 60 unless
|
276
|
-
resp =
|
280
|
+
workflow_resource.options[:timeout] = 5 * 60 unless workflow_resource.options.include?(:timeout)
|
281
|
+
resp = workflow_resource_method uri_string
|
277
282
|
#
|
278
283
|
# response looks like:
|
279
284
|
# <objects count="2">
|
@@ -299,10 +304,9 @@ module Dor
|
|
299
304
|
# Dor::WorkflowService.get_errored_objects_for_workstep('accessionWF','content-metadata')
|
300
305
|
# => {"druid:qd556jq0580"=>"druid:qd556jq0580 - Item error; caused by
|
301
306
|
# #<Rubydora::FedoraInvalidRequest: Error modifying datastream contentMetadata for druid:qd556jq0580. See logger for details>"}
|
302
|
-
def get_errored_objects_for_workstep
|
307
|
+
def get_errored_objects_for_workstep(workflow, step, repository = 'dor')
|
308
|
+
resp = workflow_resource_method "workflow_queue?repository=#{repository}&workflow=#{workflow}&error=#{step}"
|
303
309
|
result = {}
|
304
|
-
uri_string = "workflow_queue?repository=#{repository}&workflow=#{workflow}&error=#{step}"
|
305
|
-
resp = workflow_resource[uri_string].get
|
306
310
|
Nokogiri::XML(resp).xpath('//object').collect do |node|
|
307
311
|
result.merge!(node['id'] => node['errorMessage'])
|
308
312
|
end
|
@@ -316,7 +320,7 @@ module Dor
|
|
316
320
|
# @param [String] repository -- optional, default=dor
|
317
321
|
#
|
318
322
|
# @return [Integer] Number of objects with this repository:workflow:step that have a status of 'error'
|
319
|
-
def count_errored_for_workstep(workflow, step, repository='dor')
|
323
|
+
def count_errored_for_workstep(workflow, step, repository = 'dor')
|
320
324
|
count_objects_in_step(workflow, step, repository, 'error')
|
321
325
|
end
|
322
326
|
|
@@ -327,7 +331,7 @@ module Dor
|
|
327
331
|
# @param [String] repository -- optional, default=dor
|
328
332
|
#
|
329
333
|
# @return [Integer] Number of objects with this repository:workflow:step that have a status of 'queued'
|
330
|
-
def count_queued_for_workstep(workflow, step, repository='dor')
|
334
|
+
def count_queued_for_workstep(workflow, step, repository = 'dor')
|
331
335
|
count_objects_in_step(workflow, step, repository, 'queued')
|
332
336
|
end
|
333
337
|
|
@@ -342,8 +346,7 @@ module Dor
|
|
342
346
|
# :workflow, :step, :druid, :lane_id
|
343
347
|
def get_stale_queued_workflows(repository, opts = {})
|
344
348
|
uri_string = build_queued_uri(repository, opts)
|
345
|
-
|
346
|
-
parse_queued_workflows_response xml
|
349
|
+
parse_queued_workflows_response workflow_resource_method(uri_string)
|
347
350
|
end
|
348
351
|
|
349
352
|
# 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
|
@@ -353,31 +356,31 @@ module Dor
|
|
353
356
|
# meaning you will get all queued workflows
|
354
357
|
# @return [Integer] number of stale, queued steps if the :count_only option was set to true
|
355
358
|
def count_stale_queued_workflows(repository, opts = {})
|
356
|
-
uri_string = build_queued_uri(repository, opts)
|
357
|
-
|
358
|
-
|
359
|
-
doc = Nokogiri::XML(xml)
|
360
|
-
return doc.at_xpath('/objects/@count').value.to_i
|
359
|
+
uri_string = build_queued_uri(repository, opts) + '&count-only=true'
|
360
|
+
doc = Nokogiri::XML(workflow_resource_method uri_string)
|
361
|
+
doc.at_xpath('/objects/@count').value.to_i
|
361
362
|
end
|
362
363
|
|
364
|
+
# @param [Hash] params
|
363
365
|
# @return [String]
|
364
366
|
def create_process_xml(params)
|
365
367
|
builder = Nokogiri::XML::Builder.new do |xml|
|
366
|
-
attrs = params.reject { |k,v| v.nil? }
|
367
|
-
attrs = Hash[ attrs.map {|k,v| [k.to_s.camelize(:lower), v]}] # camelize all the keys in the attrs hash
|
368
|
+
attrs = params.reject { |k, v| v.nil? }
|
369
|
+
attrs = Hash[ attrs.map {|k, v| [k.to_s.camelize(:lower), v]}] # camelize all the keys in the attrs hash
|
368
370
|
xml.process(attrs)
|
369
371
|
end
|
370
|
-
|
372
|
+
builder.to_xml
|
371
373
|
end
|
372
374
|
|
373
375
|
# @return [Nokogiri::XML::Document]
|
374
376
|
def query_lifecycle(repo, druid, active_only = false)
|
375
377
|
req = "#{repo}/objects/#{druid}/lifecycle"
|
376
378
|
req << '?active-only=true' if active_only
|
377
|
-
|
378
|
-
return Nokogiri::XML(lifecycle_xml)
|
379
|
+
Nokogiri::XML(workflow_resource_method req)
|
379
380
|
end
|
380
381
|
|
382
|
+
# @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
|
383
|
+
# @param [String] druid The id of the object to archive the workflows from
|
381
384
|
def archive_active_workflow(repo, druid)
|
382
385
|
workflows = get_active_workflows(repo, druid)
|
383
386
|
workflows.each do |wf|
|
@@ -385,12 +388,13 @@ module Dor
|
|
385
388
|
end
|
386
389
|
end
|
387
390
|
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
+
# @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment
|
392
|
+
# @param [String] druid The id of the object to delete the workflow from
|
393
|
+
def archive_workflow(repo, druid, wf_name, version_num = nil)
|
394
|
+
raise 'Please call Dor::WorkflowService.configure(workflow_service_url, :dor_services_url => DOR_SERVIES_URL) once before archiving workflow' if @@dor_services_url.nil?
|
391
395
|
dor_services = RestClient::Resource.new(@@dor_services_url)
|
392
396
|
url = "/v1/objects/#{druid}/workflows/#{wf_name}/archive"
|
393
|
-
url << "/#{version_num}" if
|
397
|
+
url << "/#{version_num}" if version_num
|
394
398
|
dor_services[url].post ''
|
395
399
|
end
|
396
400
|
|
@@ -404,9 +408,9 @@ module Dor
|
|
404
408
|
# @param [Boolean] create_accession_wf Option to create accessionWF when closing a version. Defaults to true
|
405
409
|
def close_version(repo, druid, create_accession_wf = true)
|
406
410
|
uri = "#{repo}/objects/#{druid}/versionClose"
|
407
|
-
uri <<
|
408
|
-
|
409
|
-
|
411
|
+
uri << '?create-accession=false' unless create_accession_wf
|
412
|
+
workflow_resource_method(uri, 'post', '')
|
413
|
+
true
|
410
414
|
end
|
411
415
|
|
412
416
|
# Returns all the distinct laneIds for a given workflow step
|
@@ -417,57 +421,74 @@ module Dor
|
|
417
421
|
# @return [Array<String>] all of the distinct laneIds. Array will be empty if no lane ids were found
|
418
422
|
def get_lane_ids(repo, workflow, process)
|
419
423
|
uri = "workflow_queue/lane_ids?step=#{repo}:#{workflow}:#{process}"
|
420
|
-
doc = Nokogiri::XML(
|
424
|
+
doc = Nokogiri::XML(workflow_resource_method uri)
|
421
425
|
nodes = doc.xpath('/lanes/lane')
|
422
|
-
nodes.map {|n| n['id']}
|
426
|
+
nodes.map { |n| n['id'] }
|
423
427
|
end
|
424
428
|
|
425
|
-
|
429
|
+
### MIMICKING ATTRIBUTE READER
|
430
|
+
# @return [RestClient::Resource] the REST client resource created during configure()
|
426
431
|
def workflow_resource
|
427
|
-
raise
|
432
|
+
raise 'Please call Dor::WorkflowService.configure(url) once before calling any WorkflowService methods' if @@resource.nil?
|
428
433
|
@@resource
|
429
434
|
end
|
430
435
|
|
436
|
+
# Among other things, a distinct method helps tests mock default logger
|
437
|
+
# @param [String, IO] logdev The log device. This is a filename (String) or IO object (typically STDOUT, STDERR, or an open file).
|
438
|
+
# @param [String, Integer] shift_age Number of old log files to keep, or frequency of rotation (daily, weekly or monthly).
|
439
|
+
# @return [Logger] default logger object
|
440
|
+
def default_logger(logdev = 'workflow_service.log', shift_age = 'weekly')
|
441
|
+
Logger.new(logdev, shift_age)
|
442
|
+
end
|
443
|
+
|
444
|
+
def workflow_service_exceptions_to_catch
|
445
|
+
[RestClient::Exception]
|
446
|
+
end
|
447
|
+
|
431
448
|
# Configure the workflow service
|
432
|
-
#
|
449
|
+
# TODO: replace with initialize
|
433
450
|
# @param [String] url points to the workflow service
|
434
451
|
# @param [Hash] opts optional params
|
435
|
-
# @option opts [
|
452
|
+
# @option opts [Logger] :logger defaults writing to workflow_service.log with weekly rotation
|
453
|
+
# @option opts [String] :dor_services_url uri to the DOR REST service
|
436
454
|
# @option opts [Integer] :timeout number of seconds for RestClient timeout
|
437
455
|
# @option opts [String] :client_cert_file path to an SSL client certificate (deprecated)
|
438
456
|
# @option opts [String] :client_key_file path to an SSL key file (deprecated)
|
439
457
|
# @option opts [String] :client_key_pass password for the key file (deprecated)
|
440
458
|
# @return [RestClient::Resource] the REST client resource
|
441
|
-
def configure(url, opts={})
|
459
|
+
def configure(url, opts = {})
|
442
460
|
params = {}
|
443
|
-
params[:timeout]
|
461
|
+
params[:timeout] = opts[:timeout] if opts[:timeout]
|
462
|
+
@@logger = opts[:logger] || default_logger
|
444
463
|
@@dor_services_url = opts[:dor_services_url] if opts[:dor_services_url]
|
445
|
-
#params[:ssl_client_cert] = OpenSSL::X509::Certificate.new(File.read(opts[:client_cert_file])) if opts[:client_cert_file]
|
446
|
-
#params[:ssl_client_key] = OpenSSL::PKey::RSA.new(File.read(opts[:client_key_file]), opts[:client_key_pass]) if opts[:client_key_file]
|
464
|
+
# params[:ssl_client_cert] = OpenSSL::X509::Certificate.new(File.read(opts[:client_cert_file])) if opts[:client_cert_file]
|
465
|
+
# params[:ssl_client_key] = OpenSSL::PKey::RSA.new(File.read(opts[:client_key_file]), opts[:client_key_pass]) if opts[:client_key_file]
|
466
|
+
@@handler = Proc.new do |exception, attempt_number, total_delay|
|
467
|
+
@@logger.warn "[Attempt #{attempt_number}] #{exception.class}: #{exception.message}; #{total_delay} seconds elapsed."
|
468
|
+
end
|
447
469
|
@@resource = RestClient::Resource.new(url, params)
|
448
470
|
end
|
449
471
|
|
472
|
+
|
450
473
|
protected
|
451
474
|
|
452
475
|
def build_queued_uri(repository, opts = {})
|
453
476
|
uri_string = "workflow_queue/all_queued?repository=#{repository}"
|
454
477
|
uri_string << "&hours-ago=#{opts[:hours_ago]}" if opts[:hours_ago]
|
455
|
-
uri_string << "&limit=#{opts[:limit]}"
|
478
|
+
uri_string << "&limit=#{opts[:limit]}" if opts[:limit]
|
456
479
|
uri_string
|
457
480
|
end
|
458
481
|
|
459
482
|
def parse_queued_workflows_response(xml)
|
460
|
-
res = []
|
461
483
|
doc = Nokogiri::XML(xml)
|
462
|
-
doc.xpath('/workflows/workflow').
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
484
|
+
doc.xpath('/workflows/workflow').collect do |wf_node|
|
485
|
+
{
|
486
|
+
:workflow => wf_node['name'],
|
487
|
+
:step => wf_node['process'],
|
488
|
+
:druid => wf_node['druid'],
|
489
|
+
:lane_id => wf_node['laneId']
|
490
|
+
}
|
469
491
|
end
|
470
|
-
res
|
471
492
|
end
|
472
493
|
|
473
494
|
# Adds laneId attributes to each process of workflow xml
|
@@ -482,13 +503,32 @@ module Dor
|
|
482
503
|
end
|
483
504
|
|
484
505
|
def count_objects_in_step(workflow, step, type, repo)
|
485
|
-
|
486
|
-
resp = @workflow_resource[uri_string].get
|
506
|
+
resp = workflow_resource_method "workflow_queue?repository=#{repo}&workflow=#{workflow}&#{type}=#{step}"
|
487
507
|
node = Nokogiri::XML(resp).at_xpath('/objects')
|
488
|
-
raise
|
508
|
+
raise 'Unable to determine count from response' if node.nil?
|
489
509
|
node['count'].to_i
|
490
510
|
end
|
491
511
|
|
512
|
+
# calls workflow_resource[uri_string]."#{meth}" with variable number of optional arguments
|
513
|
+
# The point of this is to wrap ALL remote calls with consistent error handling and logging
|
514
|
+
# @param [String] uri_string resource to request
|
515
|
+
# @param [String] meth REST method to use on resource (get, put, post, delete, etc.)
|
516
|
+
# @param [String] payload body for (e.g. put) request
|
517
|
+
# @param [Hash] opts addtional headers options
|
518
|
+
# @return [Object] response from method
|
519
|
+
def workflow_resource_method(uri_string, meth = 'get', payload = '', opts = {})
|
520
|
+
with_retries(:max_tries => 2, :handler => @@handler, :rescue => workflow_service_exceptions_to_catch) do |attempt|
|
521
|
+
@@logger.info "[Attempt #{attempt}] #{meth} #{workflow_resource.url}/#{uri_string}"
|
522
|
+
if %w[get delete].include?(meth)
|
523
|
+
workflow_resource[uri_string].send(meth, opts)
|
524
|
+
elsif opts.size == 0 # the right number of args allows existing test expect/with statements to continue working
|
525
|
+
workflow_resource[uri_string].send(meth, payload)
|
526
|
+
else
|
527
|
+
workflow_resource[uri_string].send(meth, payload, opts)
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
492
532
|
end
|
493
533
|
end
|
494
534
|
end
|