dor-services-client 15.12.0 → 15.13.0
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 +4 -4
- data/Gemfile.lock +4 -3
- data/README.md +12 -0
- data/dor-services-client.gemspec +2 -1
- data/lib/dor/services/client/milestones.rb +69 -0
- data/lib/dor/services/client/object.rb +12 -0
- data/lib/dor/services/client/object_workflow.rb +49 -0
- data/lib/dor/services/client/object_workflows.rb +32 -0
- data/lib/dor/services/client/version.rb +1 -1
- data/lib/dor/services/client.rb +1 -0
- data/lib/dor/services/response/process.rb +66 -0
- data/lib/dor/services/response/workflow.rb +87 -0
- data/lib/dor/services/response/workflows.rb +38 -0
- metadata +24 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 565b25455d31d878a49e800e34b6b3b096322762300a7042ae677ed650ff21a5
|
4
|
+
data.tar.gz: 8c9f6acc0a76f4ac7da4b3190a3e1d4e5e349ccc9678d487f23fa3ee237e3d65
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94e719800898ca73d2028dd144a184e7ca98b5958b421846a8380bd176b7f5eef64e921e0cc3f973ed62b7607e29e801dfa64fc771d19d995cb7d00dec01f84a
|
7
|
+
data.tar.gz: 337a076d2a17c2eb7201fc7f038f53018582f26c22fdbbfd61caf05268e382e8911e6aafaa8f20aaa9c64aafcf3681fc84ce0efbb2dc31174b89c9c5ad10ec23
|
data/Gemfile.lock
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dor-services-client (15.
|
4
|
+
dor-services-client (15.13.0)
|
5
5
|
activesupport (>= 7.0.0)
|
6
|
-
cocina-models (~> 0.104.
|
6
|
+
cocina-models (~> 0.104.1)
|
7
7
|
deprecation
|
8
8
|
faraday (~> 2.0)
|
9
9
|
faraday-retry
|
10
|
+
nokogiri
|
10
11
|
zeitwerk (~> 2.1)
|
11
12
|
|
12
13
|
GEM
|
@@ -33,7 +34,7 @@ GEM
|
|
33
34
|
benchmark (0.4.1)
|
34
35
|
bigdecimal (3.2.2)
|
35
36
|
byebug (12.0.0)
|
36
|
-
cocina-models (0.104.
|
37
|
+
cocina-models (0.104.1)
|
37
38
|
activesupport
|
38
39
|
deprecation
|
39
40
|
dry-struct (~> 1.0)
|
data/README.md
CHANGED
@@ -152,6 +152,18 @@ object_client.workspace.create(source: object_path_string)
|
|
152
152
|
# Reindex
|
153
153
|
object_client.reindex
|
154
154
|
|
155
|
+
# List workflows
|
156
|
+
object_client.workflows
|
157
|
+
# Find workflow
|
158
|
+
object_client.workflow('accessionWF').find
|
159
|
+
# Create workflow
|
160
|
+
object_client.workflow('etdSubmitWF').create(version: 2)
|
161
|
+
|
162
|
+
# List milestones
|
163
|
+
object_client.milestones.list
|
164
|
+
# Get the date for a milestone
|
165
|
+
object_client.milestones.date(milestone_name: 'published')
|
166
|
+
|
155
167
|
# Search for administrative tags:
|
156
168
|
Dor::Services::Client.administrative_tags.search(q: 'Project')
|
157
169
|
|
data/dor-services-client.gemspec
CHANGED
@@ -25,10 +25,11 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.required_ruby_version = '>= 3.0', '< 4'
|
26
26
|
|
27
27
|
spec.add_dependency 'activesupport', '>= 7.0.0'
|
28
|
-
spec.add_dependency 'cocina-models', '~> 0.104.
|
28
|
+
spec.add_dependency 'cocina-models', '~> 0.104.1'
|
29
29
|
spec.add_dependency 'deprecation', '>= 0'
|
30
30
|
spec.add_dependency 'faraday', '~> 2.0'
|
31
31
|
spec.add_dependency 'faraday-retry'
|
32
|
+
spec.add_dependency 'nokogiri'
|
32
33
|
spec.add_dependency 'zeitwerk', '~> 2.1'
|
33
34
|
|
34
35
|
spec.add_development_dependency 'bundler'
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
module Services
|
5
|
+
class Client
|
6
|
+
# API calls around milestones
|
7
|
+
class Milestones < VersionedService
|
8
|
+
# @param object_identifier [String] the druid for the object
|
9
|
+
def initialize(connection:, version:, object_identifier:)
|
10
|
+
super(connection: connection, version: version)
|
11
|
+
@object_identifier = object_identifier
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the Date for a requested milestone from workflow lifecycle
|
15
|
+
#
|
16
|
+
# @param [String] druid object id
|
17
|
+
# @param [String] milestone_name the name of the milestone being queried for
|
18
|
+
# @param [Number] version (nil) the version to query for
|
19
|
+
# @param [Boolean] active_only (false) if true, return only lifecycle steps for versions that have all processes complete
|
20
|
+
# @return [Time] when the milestone was achieved. Returns nil if the milestone does not exist
|
21
|
+
def date(milestone_name:, version: nil, active_only: false)
|
22
|
+
filter_milestone(query_lifecycle(version: version, active_only: active_only), milestone_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Array<Hash>]
|
26
|
+
def list
|
27
|
+
doc = query_lifecycle(active_only: false)
|
28
|
+
doc.xpath('//lifecycle/milestone').collect do |node|
|
29
|
+
{ milestone: node.text, at: Time.parse(node['date']), version: node['version'] }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :object_identifier
|
36
|
+
|
37
|
+
def filter_milestone(lifecycle_doc, milestone_name)
|
38
|
+
milestone = lifecycle_doc.at_xpath("//lifecycle/milestone[text() = '#{milestone_name}']")
|
39
|
+
return unless milestone
|
40
|
+
|
41
|
+
Time.parse(milestone['date'])
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param [String] druid object id
|
45
|
+
# @param [Boolean] active_only (false) if true, return only lifecycle steps for versions that have all processes complete
|
46
|
+
# @param [Number] version the version to query for
|
47
|
+
# @return [Nokogiri::XML::Document]
|
48
|
+
# @example An example lifecycle xml from the workflow service.
|
49
|
+
# <lifecycle objectId="druid:ct011cv6501">
|
50
|
+
# <milestone date="2010-04-27T11:34:17-0700">registered</milestone>
|
51
|
+
# <milestone date="2010-04-29T10:12:51-0700">inprocess</milestone>
|
52
|
+
# <milestone date="2010-06-15T16:08:58-0700">released</milestone>
|
53
|
+
# </lifecycle>
|
54
|
+
#
|
55
|
+
def query_lifecycle(active_only:, version: nil)
|
56
|
+
resp = connection.get do |req|
|
57
|
+
req.url "#{api_version}/objects/#{object_identifier}/lifecycles"
|
58
|
+
req.headers['Accept'] = 'application/xml'
|
59
|
+
req.params['version'] = version if version
|
60
|
+
req.params['active-only'] = 'true' if active_only
|
61
|
+
end
|
62
|
+
raise_exception_based_on_response!(resp) unless resp.success?
|
63
|
+
|
64
|
+
Nokogiri::XML(resp.body)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -46,6 +46,18 @@ module Dor
|
|
46
46
|
@accession ||= Accession.new(**parent_params.merge(params))
|
47
47
|
end
|
48
48
|
|
49
|
+
def milestones
|
50
|
+
@milestones ||= Milestones.new(**parent_params)
|
51
|
+
end
|
52
|
+
|
53
|
+
def workflows
|
54
|
+
@workflows ||= ObjectWorkflows.new(**parent_params).list
|
55
|
+
end
|
56
|
+
|
57
|
+
def workflow(workflow_name)
|
58
|
+
@workflow ||= ObjectWorkflow.new(**parent_params.merge({ workflow_name: workflow_name }))
|
59
|
+
end
|
60
|
+
|
49
61
|
# Retrieves the Cocina model
|
50
62
|
# @param [boolean] validate validate the response object
|
51
63
|
# @raise [NotFoundResponse] when the response is a 404 (object not found)
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
module Services
|
5
|
+
class Client
|
6
|
+
# API calls around workflow for an object.
|
7
|
+
class ObjectWorkflow < VersionedService
|
8
|
+
# @param object_identifier [String] the druid for the object
|
9
|
+
# @param [String] workflow_name The name of the workflow
|
10
|
+
def initialize(connection:, version:, object_identifier:, workflow_name:)
|
11
|
+
super(connection: connection, version: version)
|
12
|
+
@object_identifier = object_identifier
|
13
|
+
@workflow_name = workflow_name
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [Workflow::Response::Workflow]
|
17
|
+
def find
|
18
|
+
resp = connection.get do |req|
|
19
|
+
req.url "#{api_version}/objects/#{object_identifier}/workflows/#{workflow_name}"
|
20
|
+
req.headers['Accept'] = 'application/xml'
|
21
|
+
end
|
22
|
+
raise_exception_based_on_response!(resp) unless resp.success?
|
23
|
+
|
24
|
+
Dor::Services::Response::Workflow.new(xml: Nokogiri::XML(resp.body))
|
25
|
+
end
|
26
|
+
|
27
|
+
# Creates a workflow for a given object in the repository. If this particular workflow for this objects exists,
|
28
|
+
# it will replace the old workflow.
|
29
|
+
# @param [Integer] version
|
30
|
+
# @param [String] lane_id adds laneId attribute to all process elements in the wf_xml workflow xml. Defaults to a value of 'default'
|
31
|
+
# @param [Hash] context optional context to be included in the workflow (same for all processes for a given druid/version pair)
|
32
|
+
def create(version:, lane_id: 'default', context: nil) # rubocop:disable Metrics/AbcSize
|
33
|
+
resp = connection.post do |req|
|
34
|
+
req.url "#{api_version}/objects/#{object_identifier}/workflows/#{workflow_name}"
|
35
|
+
req.params['version'] = version
|
36
|
+
req.params['lane-id'] = lane_id
|
37
|
+
req.headers['Content-Type'] = 'application/json'
|
38
|
+
req.body = { context: context }.to_json if context
|
39
|
+
end
|
40
|
+
raise_exception_based_on_response!(resp) unless resp.success?
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
attr_reader :object_identifier, :workflow_name
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
module Services
|
5
|
+
class Client
|
6
|
+
# API calls around workflows for an object.
|
7
|
+
class ObjectWorkflows < VersionedService
|
8
|
+
# @param object_identifier [String] the druid for the object
|
9
|
+
def initialize(connection:, version:, object_identifier:)
|
10
|
+
super(connection: connection, version: version)
|
11
|
+
@object_identifier = object_identifier
|
12
|
+
end
|
13
|
+
|
14
|
+
# Retrieves all workflows for the given object
|
15
|
+
# @return [Dor::Services::Response::Workflows]
|
16
|
+
def list
|
17
|
+
resp = connection.get do |req|
|
18
|
+
req.url "#{api_version}/objects/#{object_identifier}/workflows"
|
19
|
+
req.headers['Accept'] = 'application/xml'
|
20
|
+
end
|
21
|
+
raise_exception_based_on_response!(resp) unless resp.success?
|
22
|
+
|
23
|
+
Dor::Services::Response::Workflows.new(xml: Nokogiri::XML(resp.body))
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :object_identifier
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/dor/services/client.rb
CHANGED
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
module Services
|
5
|
+
module Response
|
6
|
+
# Represents the status of an object doing a workflow process
|
7
|
+
class Process
|
8
|
+
# @params [Workflow] parent
|
9
|
+
# @params [Hash] attributes
|
10
|
+
def initialize(parent:, **attributes)
|
11
|
+
@parent = parent
|
12
|
+
@attributes = attributes
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
@attributes[:name].presence
|
17
|
+
end
|
18
|
+
|
19
|
+
def status
|
20
|
+
@attributes[:status].presence
|
21
|
+
end
|
22
|
+
|
23
|
+
def datetime
|
24
|
+
@attributes[:datetime].presence
|
25
|
+
end
|
26
|
+
|
27
|
+
def elapsed
|
28
|
+
@attributes[:elapsed].presence
|
29
|
+
end
|
30
|
+
|
31
|
+
def attempts
|
32
|
+
@attributes[:attempts].presence
|
33
|
+
end
|
34
|
+
|
35
|
+
def lifecycle
|
36
|
+
@attributes[:lifecycle].presence
|
37
|
+
end
|
38
|
+
|
39
|
+
def note
|
40
|
+
@attributes[:note].presence
|
41
|
+
end
|
42
|
+
|
43
|
+
def error_message
|
44
|
+
@attributes[:errorMessage].presence
|
45
|
+
end
|
46
|
+
|
47
|
+
def lane_id
|
48
|
+
@attributes[:laneId].presence
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [Hash] the context for the process (or empty hash if none present)
|
52
|
+
def context
|
53
|
+
return {} unless @attributes[:context].present?
|
54
|
+
|
55
|
+
JSON.parse(@attributes[:context])
|
56
|
+
end
|
57
|
+
|
58
|
+
delegate :pid, :workflow_name, to: :parent
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
attr_reader :parent
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
module Services
|
5
|
+
module Response
|
6
|
+
# The response from asking the server about a workflow for an item
|
7
|
+
class Workflow
|
8
|
+
def initialize(xml:)
|
9
|
+
@xml = xml
|
10
|
+
end
|
11
|
+
|
12
|
+
def pid
|
13
|
+
workflow['objectId']
|
14
|
+
end
|
15
|
+
|
16
|
+
def workflow_name
|
17
|
+
workflow['id']
|
18
|
+
end
|
19
|
+
|
20
|
+
# Check if there are any processes for the provided version.
|
21
|
+
# @param [Integer] version the version we are checking for.
|
22
|
+
def active_for?(version:)
|
23
|
+
result = ng_xml.at_xpath("/workflow/process[@version=#{version}]")
|
24
|
+
result ? true : false
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns the process, for the most recent version that matches the given name:
|
28
|
+
def process_for_recent_version(name:)
|
29
|
+
nodes = process_nodes_for(name: name)
|
30
|
+
node = nodes.max { |a, b| a.attr('version').to_i <=> b.attr('version').to_i }
|
31
|
+
to_process(node)
|
32
|
+
end
|
33
|
+
|
34
|
+
def empty?
|
35
|
+
ng_xml.xpath('/workflow/process').empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
# Check if all processes are skipped or complete for the provided version.
|
39
|
+
# @param [Integer] version the version we are checking for.
|
40
|
+
def complete_for?(version:)
|
41
|
+
# ng_xml.xpath("/workflow/process[@version=#{version}]/@status").map(&:value).all? { |p| %w[skipped completed].include?(p) }
|
42
|
+
incomplete_processes_for(version: version).empty?
|
43
|
+
end
|
44
|
+
|
45
|
+
def complete?
|
46
|
+
complete_for?(version: version)
|
47
|
+
end
|
48
|
+
|
49
|
+
def incomplete_processes_for(version:)
|
50
|
+
process_nodes = ng_xml.xpath("/workflow/process[@version=#{version}]")
|
51
|
+
incomplete_process_nodes = process_nodes.reject { |process_node| %w[skipped completed].include?(process_node.attr('status')) }
|
52
|
+
incomplete_process_nodes.map { |process_node| to_process(process_node) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def incomplete_processes
|
56
|
+
incomplete_processes_for(version: version)
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_reader :xml
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# Return the max version in this workflow document
|
64
|
+
def version
|
65
|
+
ng_xml.xpath('/workflow/process/@version').map { |attr| attr.value.to_i }.max
|
66
|
+
end
|
67
|
+
|
68
|
+
def workflow
|
69
|
+
ng_xml.at_xpath('workflow')
|
70
|
+
end
|
71
|
+
|
72
|
+
def process_nodes_for(name:)
|
73
|
+
ng_xml.xpath("/workflow/process[@name = '#{name}']")
|
74
|
+
end
|
75
|
+
|
76
|
+
def ng_xml
|
77
|
+
@ng_xml ||= Nokogiri::XML(@xml)
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_process(node)
|
81
|
+
attributes = node ? node.attributes.to_h { |k, v| [k.to_sym, v.value] } : {}
|
82
|
+
Process.new(parent: self, **attributes)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dor
|
4
|
+
module Services
|
5
|
+
module Response
|
6
|
+
# The response from asking the server about all workflows for an item
|
7
|
+
class Workflows
|
8
|
+
def initialize(xml:)
|
9
|
+
@xml = xml
|
10
|
+
end
|
11
|
+
|
12
|
+
def pid
|
13
|
+
ng_xml.at_xpath('/workflows/@objectId').text
|
14
|
+
end
|
15
|
+
|
16
|
+
def workflows
|
17
|
+
@workflows ||= ng_xml.xpath('/workflows/workflow').map do |node|
|
18
|
+
Workflow.new(xml: node.to_xml)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Array<String>] returns a list of errors for any process for the current version
|
23
|
+
def errors_for(version:)
|
24
|
+
ng_xml.xpath("//workflow/process[@version='#{version}' and @status='error']/@errorMessage")
|
25
|
+
.map(&:text)
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :xml
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def ng_xml
|
33
|
+
@ng_xml ||= Nokogiri::XML(@xml)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dor-services-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 15.
|
4
|
+
version: 15.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Coyne
|
8
8
|
- Michael Giarlo
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-06-
|
11
|
+
date: 2025-06-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.104.
|
33
|
+
version: 0.104.1
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.104.
|
40
|
+
version: 0.104.1
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: deprecation
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: nokogiri
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: zeitwerk
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -237,9 +251,12 @@ files:
|
|
237
251
|
- lib/dor/services/client/error_faraday_middleware.rb
|
238
252
|
- lib/dor/services/client/events.rb
|
239
253
|
- lib/dor/services/client/members.rb
|
254
|
+
- lib/dor/services/client/milestones.rb
|
240
255
|
- lib/dor/services/client/mutate.rb
|
241
256
|
- lib/dor/services/client/object.rb
|
242
257
|
- lib/dor/services/client/object_version.rb
|
258
|
+
- lib/dor/services/client/object_workflow.rb
|
259
|
+
- lib/dor/services/client/object_workflows.rb
|
243
260
|
- lib/dor/services/client/objects.rb
|
244
261
|
- lib/dor/services/client/release_tag.rb
|
245
262
|
- lib/dor/services/client/release_tags.rb
|
@@ -250,6 +267,9 @@ files:
|
|
250
267
|
- lib/dor/services/client/virtual_objects.rb
|
251
268
|
- lib/dor/services/client/workflows.rb
|
252
269
|
- lib/dor/services/client/workspace.rb
|
270
|
+
- lib/dor/services/response/process.rb
|
271
|
+
- lib/dor/services/response/workflow.rb
|
272
|
+
- lib/dor/services/response/workflows.rb
|
253
273
|
homepage: https://github.com/sul-dlss/dor-services-client
|
254
274
|
licenses: []
|
255
275
|
metadata:
|