# frozen_string_literal: true module Dor module Workflow class Client # Makes requests relating to a workflow class WorkflowRoutes def initialize(requestor:) @requestor = requestor end # Creates a workflow for a given object in the repository. If this particular workflow for this objects exists, # it will replace the old workflow. # Returns true on success. Caller must handle any exceptions. # # @param [String] druid The id of the object # @param [String] workflow_name The name of the workflow you want to create. This must correspond with a workflow # name that is known by the workflow service. # @param [String] lane_id adds laneId attribute to all process elements in the wf_xml workflow xml. Defaults to a value of 'default' # @param [Integer] version specifies the version so that workflow service doesn't need to query dor-services. # @return [Boolean] always true # def create_workflow_by_name(druid, workflow_name, version:, lane_id: 'default') params = { 'lane-id' => lane_id, 'version' => version } requestor.request "objects/#{druid}/workflows/#{workflow_name}", 'post', '', content_type: 'application/xml', params: params true end # Updates the status of one step in a workflow. # Returns true on success. Caller must handle any exceptions # # @param [String] repo The repository the object resides in. The service recoginzes "dor" and "sdr" at the moment # @param [String] druid The id of the object # @param [String] workflow The name of the workflow # @param [String] process The name of the process step # @param [String] status The status that you want to set -- using one of the values in VALID_STATUS # @param [Float] :elapsed The number of seconds it took to complete this step. Can have a decimal. Is set to 0 if not passed in. # @param [String] :lifecycle Bookeeping label for this particular workflow step. Examples are: 'registered', 'shelved' # @param [String] :note Any kind of string annotation that you want to attach to the workflow # @param [String] :current_status Setting this string tells the workflow service to compare the current status to this value. If the current value does not match this value, the update is not performed # @return [Boolean] always true # Http Call # == # The method does an HTTP PUT to the base URL. As an example: # # PUT "/objects/pid:123/workflows/GoogleScannedWF/convert" # " def update_status(druid:, workflow:, process:, status:, elapsed: 0, lifecycle: nil, note: nil, current_status: nil) raise ArgumentError, "Unknown status value #{status}" unless VALID_STATUS.include?(status) raise ArgumentError, "Unknown current_status value #{current_status}" if current_status && !VALID_STATUS.include?(current_status) xml = create_process_xml(name: process, status: status, elapsed: elapsed, lifecycle: lifecycle, note: note) uri = "objects/#{druid}/workflows/#{workflow}/#{process}" uri += "?current-status=#{current_status}" if current_status response = requestor.request(uri, 'put', xml, content_type: 'application/xml') Workflow::Response::Update.new(json: response) end # # Retrieves the process status of the given workflow for the given object identifier # @param [String] repo The repository the object resides in. Currently recoginzes "dor" and "sdr". # @param [String] druid The id of the object # @param [String] workflow The name of the workflow # @param [String] process The name of the process step # @return [String] status for repo-workflow-process-druid def workflow_status(druid:, workflow:, process:) workflow_md = fetch_workflow(druid: druid, workflow: workflow) doc = Nokogiri::XML(workflow_md) raise Dor::WorkflowException, "Unable to parse response:\n#{workflow_md}" if doc.root.nil? processes = doc.root.xpath("//process[@name='#{process}']") process = processes.max { |a, b| a.attr('version').to_i <=> b.attr('version').to_i } process&.attr('status') end # Updates the status of one step in a workflow to error. # Returns true on success. Caller must handle any exceptions # # @param [String] druid The id of the object # @param [String] workflow The name of the workflow # @param [String] process The name of the workflow step # @param [String] error_msg The error message. Ideally, this is a brief message describing the error # @param [String] error_text A slot to hold more information about the error, like a full stacktrace # @return [Workflow::Response::Update] # # Http Call # == # The method does an HTTP PUT to the base URL. # # PUT "/objects/pid:123/workflows/GoogleScannedWF/convert" # " def update_error_status(druid:, workflow:, process:, error_msg:, error_text: nil) xml = create_process_xml(name: process, status: 'error', errorMessage: error_msg, error_text: error_text) response = requestor.request "objects/#{druid}/workflows/#{workflow}/#{process}", 'put', xml, content_type: 'application/xml' Workflow::Response::Update.new(json: response) end # Retrieves all workflows for the given object # @param [String] pid The id of the object # @return [Workflow::Response::Workflows] def all_workflows(pid:) xml = requestor.request "objects/#{pid}/workflows" Workflow::Response::Workflows.new(xml: xml) end # Get workflow names into an array for given PID # This method only works when this gem is used in a project that is configured to connect to DOR # # @param [String] pid of druid # @return [Array] list of worklows # @example # client.workflows('druid:sr100hp0609') # => ["accessionWF", "assemblyWF", "disseminationWF"] def workflows(pid) xml_doc = Nokogiri::XML(fetch_workflow(druid: pid, workflow: '')) xml_doc.xpath('//workflow').collect { |workflow| workflow['id'] } end # @param [String] pid id of object # @param [String] workflow_name The name of the workflow # @return [Workflow::Response::Workflow] def workflow(pid:, workflow_name:) xml = fetch_workflow(druid: pid, workflow: workflow_name) Workflow::Response::Workflow.new(xml: xml) end # @param [String] pid id of object # @param [String] workflow_name The name of the workflow # @param [String] process The name of the workflow step # @return [Workflow::Response::Process] def process(pid:, workflow_name:, process:) workflow(pid: pid, workflow_name: workflow_name).process_for_recent_version(name: process) end # Deletes a workflow from a particular repository and druid. This is only used by Hydrus. # @param [String] druid The id of the object to delete the workflow from # @param [String] workflow The name of the workflow to be deleted # @param [Integer] version The version of the workflow to delete # @return [Boolean] always true def delete_workflow(druid:, workflow:, version:) qs_args = "?version=#{version}" requestor.request "/objects/#{druid}/workflows/#{workflow}#{qs_args}", 'delete' true end # Deletes all workflow steps for a particular druid # @param [String] pid The id of the object to delete the workflow from # @return [Boolean] always true def delete_all_workflows(pid:) requestor.request "objects/#{pid}/workflows", 'delete' true end private attr_reader :requestor def fetch_workflow(druid:, workflow:) raise ArgumentError, 'missing workflow' unless workflow requestor.request "objects/#{druid}/workflows/#{workflow}" end # @param [Hash] params # @return [String] def create_process_xml(params) builder = Nokogiri::XML::Builder.new do |xml| attrs = params.compact attrs = attrs.transform_keys { |k| k.to_s.camelize(:lower) } # camelize all the keys in the attrs hash xml.process(attrs) end builder.to_xml end end end end end