dor-workflow-service 1.7.6 → 1.7.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|