dor-workflow-client 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5f6d5ba580a5c98372eed49a683d9db35e1e83fbbf91350e401ab7dccbd93c38
4
+ data.tar.gz: 7899acc1174d4b43e56ae9e4a1823e9302c2219f0623b159014f4c1d9e130214
5
+ SHA512:
6
+ metadata.gz: b5c2081451447bf2732c7450562e20cad324d50f5c88f5733579a7c200ad646aa11eb89ae24d1c8535f761c81049b82394dcce4b7003d1d31880bc307474dcb0
7
+ data.tar.gz: a92d0a03be85f00934f03d5f3e68e5796c9c4779c4db8f8c368faf541fdb93a336d0c40deebc879b7e424ac667f5896ee6d656f840a669ffe6490ffbbb6dc5d9
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .rvmrc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ .ruby-version
20
+ .ruby-gemset
21
+
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.rubocop.yml ADDED
@@ -0,0 +1,11 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ AllCops:
4
+ TargetRubyVersion: 2.3
5
+ # Configuration parameters: AllowURI, URISchemes.
6
+ Metrics/LineLength:
7
+ Max: 300
8
+
9
+ Metrics/BlockLength:
10
+ Exclude:
11
+ - spec/**/*_spec.rb
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,21 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2019-04-06 08:08:20 -0500 using RuboCop version 0.63.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 3
10
+ Metrics/AbcSize:
11
+ Max: 23
12
+
13
+ # Offense count: 1
14
+ # Configuration parameters: CountComments, ExcludedMethods.
15
+ Metrics/MethodLength:
16
+ Max: 13
17
+
18
+ # Offense count: 2
19
+ # Configuration parameters: CountKeywordArgs.
20
+ Metrics/ParameterLists:
21
+ Max: 6
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ notifications:
3
+ email: false
4
+
5
+ rvm:
6
+ - 2.5.3
7
+
8
+ env:
9
+ - "RAILS_VERSION=4.2.11.1"
10
+ - "RAILS_VERSION=5.2.3"
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup markdown
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in dor-workflow-service.gemspec
6
+ gemspec
7
+
8
+ gem 'activesupport', ENV['RAILS_VERSION'] if ENV['RAILS_VERSION']
9
+
10
+ group :development, :test do
11
+ gem 'byebug'
12
+ gem 'simplecov'
13
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Willy Mene
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ [![Build Status](https://travis-ci.org/sul-dlss/dor-workflow-service.svg?branch=master)](https://travis-ci.org/sul-dlss/dor-workflow-service)
2
+
3
+ # dor-workflow-client gem
4
+
5
+ A Ruby client to work with the DOR Workflow REST Service. The REST API is defined here:
6
+ https://consul.stanford.edu/display/DOR/DOR+services#DORservices-initializeworkflow
7
+
8
+ ## Usage
9
+
10
+ You should initialize a `Dor::Workflow::Client` object in your application configuration, i.e. in a bootup or startup method like:
11
+
12
+ ```ruby
13
+ wfs = Dor::Workflow::Client.new(url: 'https://test-server.edu/workflow/')
14
+ ```
15
+
16
+ Consumers of recent versions of the [dor-services](https://github.com/sul-dlss/dor-services) gem can access the configured `Dor::Workflow::Client` object via `Dor::Config`.
17
+
18
+ ## Underlying Clients
19
+
20
+ This gem currently uses the [Faraday](https://github.com/lostisland/faraday) HTTP client to access the back-end service. The clients be accessed directly from your `Dor::Workflow::Client` object:
21
+
22
+ ```ruby
23
+ wfs.connection # the Faraday object
24
+ ```
25
+
26
+ Or for advanced configurations, ONE of them (not both) can be passed to the constructor instead of the raw URL string:
27
+
28
+ ```ruby
29
+ conn = Faraday.new(:url => 'http://sushi.com') do |faraday|
30
+ faraday.request :url_encoded # form-encode POST params
31
+ faraday.response :logger # log requests to STDOUT
32
+ faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
33
+ end
34
+ wfs = Dor::Workflow::Client.new(connection: conn)
35
+ ```
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'bundler/gem_tasks'
5
+
6
+ require 'rspec/core/rake_task'
7
+ require 'rubocop/rake_task'
8
+
9
+ RuboCop::RakeTask.new
10
+ RSpec::Core::RakeTask.new(:spec)
11
+
12
+ desc 'Run linter and tests'
13
+ task default: %i[rubocop spec]
14
+
15
+ require 'yard'
16
+ YARD::Rake::YardocTask.new
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #! /usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'rubygems'
5
+ require 'irb'
6
+
7
+ project_root = File.expand_path(File.dirname(__FILE__) + '/..')
8
+
9
+ # Load config for current environment.
10
+ $LOAD_PATH.unshift(project_root + '/lib')
11
+
12
+ require 'dor-workflow-service'
13
+
14
+ IRB.start
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'dor/workflow/client/version'
6
+
7
+ Gem::Specification.new do |gem|
8
+ gem.name = 'dor-workflow-client'
9
+ gem.version = Dor::Workflow::Client::VERSION
10
+ gem.authors = ['Willy Mene', 'Darren Hardy']
11
+ gem.email = ['wmene@stanford.edu']
12
+ gem.description = 'Enables Ruby manipulation of the DOR Workflow Service via its REST API'
13
+ gem.summary = 'Provides convenience methods to work with the DOR Workflow Service'
14
+ gem.homepage = 'https://consul.stanford.edu/display/DOR/DOR+services#DORservices-initializeworkflow'
15
+
16
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(spec)/})
19
+ gem.require_paths = ['lib']
20
+
21
+ gem.add_dependency 'activesupport', '>= 3.2.1', '< 6'
22
+ gem.add_dependency 'confstruct', '>= 0.2.7', '< 2'
23
+ gem.add_dependency 'faraday', '~> 0.9', '>= 0.9.2'
24
+ gem.add_dependency 'faraday_middleware'
25
+ gem.add_dependency 'net-http-persistent', '>= 2.9.4', '< 4.a'
26
+ gem.add_dependency 'nokogiri', '~> 1.6'
27
+
28
+ gem.add_development_dependency 'equivalent-xml', '~> 0.5', '>= 0.5.1'
29
+ gem.add_development_dependency 'rake'
30
+ gem.add_development_dependency 'redcarpet'
31
+ gem.add_development_dependency 'rspec', '~> 3.3'
32
+ gem.add_development_dependency 'rubocop', '~> 0.63.1'
33
+ gem.add_development_dependency 'simplecov'
34
+ gem.add_development_dependency 'webmock'
35
+ gem.add_development_dependency 'yard'
36
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dor
4
+ module Workflow
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
+ delegate :pid, :workflow_name, to: :parent
48
+
49
+ private
50
+
51
+ attr_reader :parent
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dor/models/response/process'
4
+
5
+ module Dor
6
+ module Workflow
7
+ module Response
8
+ # The response from telling the server to update a workflow step.
9
+ class Update
10
+ def initialize(json:)
11
+ @json = JSON.parse(json)
12
+ end
13
+
14
+ def next_steps
15
+ @json[:next_steps]
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dor/models/response/process'
4
+
5
+ module Dor
6
+ module Workflow
7
+ module Response
8
+ # The response form asking the server about a workflow for an item
9
+ class Workflow
10
+ def initialize(xml:)
11
+ @xml = xml
12
+ end
13
+
14
+ def pid
15
+ ng_xml.at_xpath('/workflow/@objectId').text
16
+ end
17
+
18
+ def workflow_name
19
+ ng_xml.at_xpath('/workflow/@id').text
20
+ end
21
+
22
+ # @param [Integer] version the version we are checking for.
23
+ def active_for?(version:)
24
+ result = ng_xml.at_xpath("/workflow/process[@version=#{version}]")
25
+ result ? true : false
26
+ end
27
+
28
+ # Returns the process, for the most recent version that matches the given name:
29
+ def process_for_recent_version(name:)
30
+ nodes = process_nodes_for(name: name)
31
+ node = nodes.max { |a, b| a.attr('version').to_i <=> b.attr('version').to_i }
32
+ attributes = node ? Hash[node.attributes.collect { |k, v| [k.to_sym, v.value] }] : {}
33
+ Process.new(parent: self, **attributes)
34
+ end
35
+
36
+ def empty?
37
+ ng_xml.xpath('/workflow/process').empty?
38
+ end
39
+
40
+ attr_reader :xml
41
+
42
+ private
43
+
44
+ def process_nodes_for(name:)
45
+ ng_xml.xpath("/workflow/process[@name = '#{name}']")
46
+ end
47
+
48
+ def ng_xml
49
+ @ng_xml ||= Nokogiri::XML(@xml)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+
6
+ module Dor
7
+ module Workflow
8
+ class Client
9
+ # Builds Faraday connections that follow redirects
10
+ class ConnectionFactory
11
+ def self.build_connection(url, timeout:, logger:)
12
+ new(url, timeout: timeout, logger: logger).build_connection
13
+ end
14
+
15
+ def initialize(url, timeout:, logger:)
16
+ @url = url
17
+ @timeout = timeout
18
+ @logger = logger
19
+ end
20
+
21
+ # rubocop:disable Metrics/MethodLength
22
+ def build_connection
23
+ Faraday.new(url: url) do |faraday|
24
+ faraday.use Faraday::Response::RaiseError # raise exceptions on 40x, 50x responses
25
+ faraday.use FaradayMiddleware::FollowRedirects, limit: 3
26
+ faraday.options.params_encoder = Faraday::FlatParamsEncoder
27
+ if timeout
28
+ faraday.options.timeout = timeout
29
+ faraday.options.open_timeout = timeout
30
+ end
31
+ faraday.headers[:user_agent] = user_agent
32
+ faraday.request :retry,
33
+ max: 2,
34
+ interval: 5.0,
35
+ interval_randomness: 0.01,
36
+ backoff_factor: 2.0,
37
+ methods: retriable_methods,
38
+ exceptions: retriable_exceptions,
39
+ retry_block: retry_block,
40
+ retry_statuses: retry_statuses
41
+ faraday.adapter Faraday.default_adapter # Last middleware must be the adapter
42
+ end
43
+ end
44
+ # rubocop:enable Metrics/MethodLength
45
+
46
+ def user_agent
47
+ "dor-workflow-service #{VERSION}"
48
+ end
49
+
50
+ private
51
+
52
+ attr_reader :logger, :timeout, :url
53
+
54
+ def retriable_methods
55
+ Faraday::Request::Retry::IDEMPOTENT_METHODS + [:post]
56
+ end
57
+
58
+ def retriable_exceptions
59
+ Faraday::Request::Retry::DEFAULT_EXCEPTIONS + [Faraday::ConnectionFailed]
60
+ end
61
+
62
+ def retry_block
63
+ lambda do |env, _opts, retries, exception|
64
+ logger.warn "retrying connection (#{retries} remaining) to #{env.url}: (#{exception.class}) " \
65
+ "#{exception.message} #{env.status}"
66
+ end
67
+ end
68
+
69
+ def retry_statuses
70
+ [429, 500, 502, 503, 504, 599]
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dor
4
+ module Workflow
5
+ class Client
6
+ # Makes requests relating to a lifecycle
7
+ class LifecycleRoutes
8
+ def initialize(requestor:)
9
+ @requestor = requestor
10
+ end
11
+
12
+ # Returns the Date for a requested milestone from workflow lifecycle
13
+ # @param [String] repo repository name
14
+ # @param [String] druid object id
15
+ # @param [String] milestone name of the milestone being queried for
16
+ # @return [Time] when the milestone was achieved. Returns nil if the milestone does not exist
17
+ # @example An example lifecycle xml from the workflow service.
18
+ # <lifecycle objectId="druid:ct011cv6501">
19
+ # <milestone date="2010-04-27T11:34:17-0700">registered</milestone>
20
+ # <milestone date="2010-04-29T10:12:51-0700">inprocess</milestone>
21
+ # <milestone date="2010-06-15T16:08:58-0700">released</milestone>
22
+ # </lifecycle>
23
+ def lifecycle(repo, druid, milestone)
24
+ doc = query_lifecycle(repo, druid)
25
+ milestone = doc.at_xpath("//lifecycle/milestone[text() = '#{milestone}']")
26
+ return Time.parse(milestone['date']) if milestone
27
+
28
+ nil
29
+ end
30
+
31
+ # Returns the Date for a requested milestone ONLY FROM THE ACTIVE workflow table
32
+ # @param [String] repo repository name
33
+ # @param [String] druid object id
34
+ # @param [String] milestone name of the milestone being queried for
35
+ # @return [Time] when the milestone was achieved. Returns nil if the milestone does not exist
36
+ # @example An example lifecycle xml from the workflow service.
37
+ # <lifecycle objectId="druid:ct011cv6501">
38
+ # <milestone date="2010-04-27T11:34:17-0700">registered</milestone>
39
+ # <milestone date="2010-04-29T10:12:51-0700">inprocess</milestone>
40
+ # <milestone date="2010-06-15T16:08:58-0700">released</milestone>
41
+ # </lifecycle>
42
+ def active_lifecycle(repo, druid, milestone)
43
+ doc = query_lifecycle(repo, druid, true)
44
+ milestone = doc.at_xpath("//lifecycle/milestone[text() = '#{milestone}']")
45
+ return Time.parse(milestone['date']) if milestone
46
+
47
+ nil
48
+ end
49
+
50
+ # @return [Hash]
51
+ def milestones(repo, druid)
52
+ doc = query_lifecycle(repo, druid)
53
+ doc.xpath('//lifecycle/milestone').collect do |node|
54
+ { milestone: node.text, at: Time.parse(node['date']), version: node['version'] }
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ # @return [Nokogiri::XML::Document]
61
+ def query_lifecycle(repo, druid, active_only = false)
62
+ req = "#{repo}/objects/#{druid}/lifecycle"
63
+ req += '?active-only=true' if active_only
64
+ Nokogiri::XML(requestor.request(req))
65
+ end
66
+
67
+ attr_reader :requestor
68
+ end
69
+ end
70
+ end
71
+ end