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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d25cb073f436c9dd92ecc738e8279216a84088e61d9452a4f7c81c9889e3138
4
- data.tar.gz: 2d7db7153e6227ac47e93952ae44600fc0d7f1187127f96521bc4cdcbcd7b7a2
3
+ metadata.gz: 565b25455d31d878a49e800e34b6b3b096322762300a7042ae677ed650ff21a5
4
+ data.tar.gz: 8c9f6acc0a76f4ac7da4b3190a3e1d4e5e349ccc9678d487f23fa3ee237e3d65
5
5
  SHA512:
6
- metadata.gz: 59aee4af11f92713f68e2a114af9db4be1cb684fd2d06660463a57c3bf9762dccfffad527c441abfe4cf8ac6a607f010676765b1d01b2792c955216a62b23f12
7
- data.tar.gz: 903bc3b83d1c664cd9b198e0a1985ad0e9727bb8fb50502e74faa265c7dbce3dd103e3fbc35e92a4af4617437dbcd2c70abf4dc8a6ff1248606e73a5388e70e9
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.12.0)
4
+ dor-services-client (15.13.0)
5
5
  activesupport (>= 7.0.0)
6
- cocina-models (~> 0.104.0)
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.0)
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
 
@@ -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.0'
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
@@ -3,7 +3,7 @@
3
3
  module Dor
4
4
  module Services
5
5
  class Client
6
- VERSION = '15.12.0'
6
+ VERSION = '15.13.0'
7
7
  end
8
8
  end
9
9
  end
@@ -10,6 +10,7 @@ require 'active_support/json'
10
10
  require 'cocina/models'
11
11
  require 'faraday'
12
12
  require 'faraday/retry'
13
+ require 'nokogiri'
13
14
  require 'singleton'
14
15
  require 'zeitwerk'
15
16
 
@@ -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.12.0
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-24 00:00:00.000000000 Z
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.0
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.0
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: