-
Notifications
You must be signed in to change notification settings - Fork 128
Mediated Deposit Workflow
In the Samvera community, there are a lot of folks who would like to be able to deposit an item and make sure it doesn't go live until the item and its metadata have been thoroughly reviewed. This is where deposit workflow comes in. In Hyrax, a user can now describe groups of steps they want to take to have an item "properly" reviewed. Hyrax uses what is called "workflows" to describe these groups of steps to review an item that was ingested.
In Hyrax, a workflow is a state machine generated pragmatically by utilizing work done in a gem called Sipity (https://github.com/ndlib/sipity). Each workflow can look different depending on who has designed it. By taking this info into account, it is now possible to generate any workflow a user would like by simply describing it in a JSON format.
The first step in introducing a workflow in Hyrax is to draw out a state machine. Pull up a whiteboard or piece of paper and diagram out what you think a workflow should look like. Here are a few tips about drawing out a state machine:
- Draw circles or squares to describe a step in that workflow. (metadata review step, digitization review step, etc.)
- Draw lines in between these circles or squares to describe how to transition from one state to another.
- Determine the roles that are required by the user who would be taking an action.
- Determine which roles can see items throughout the workflow.
- When a state change occurs, what other actions could/should be taken?
Here is a quick example of a state machine that describes a workflow:
Now that a workflow has been conceptually figured out, it is now time to generate a JSON expression of said workflow. This JSON file, should live in APP_ROOT/config/workflows/*.json
Sipity follows a very stringent format for this JSON expression of a state machine.
The first expression in the JSON is a "work_types" tag. This tells the program that a list of work types are coming up. These work types need to exist in Hyrax in order for the program to fully generate the workflow needed. To generate a work type, a user can run rails generator hyrax:work <NameOfWork>
in the command line. Note: This generator command will generate a default workflow, but it may not be the workflow you want.
Example:
A user whom wants to generate a GenericWork work type would run
rails generator hyrax:work GenericWork.
This will generate a generic_work_workflow.json file.
It will serve as your workflow JSON file
Next comes the "actions" tag which is a list of actions that can be taken in the workflow. This is expressed in the JSON file as an array of action names with other supporting information that describe the way to get from one state to the next, what state comes next, what actions can be taken during a state change, and the roles needed to make the state change.
Within each action is a "name" tag, a "transition_to" tag, a "from_states", and a "roles" tag. This describes the name of the action, where the state transitions to after an action taken, what state is prior to the current state, and the roles needed for making the change from the current state to the next. Below is a sample JSON file, which replicates the aforementioned state machine.
{
"workflow": [
{
"name": "example",
"actions": [{
"name": "approve", "transition_to": "approved",
"from_states": [{"names": ["under_review"], "roles": ["approving_work"]}]
}, {
"name": "submit_for_review", "transition_to": "approved",
"from_states": [{"names": ["new", "changes_required"], "roles": ["creating_deposit"]}]
}, {
"name": "request_changes", "transition_to": "changes_required",
"from_states": [{"names": ["under_review"], "roles": ["approving_work"]}]
}]
}
]
}
Thats a good question. Its quite simple! All you need to do now is run a simple command in the command line.
rails hyrax:workflow:load
and voila! Your workflow is now loaded into the system and is ready to be used. If you dont get yelled at by this rake task then you know that it worked. The verification of your workflow JSON file is very particular and will come back with errors if there is anything wrong in your workflow JSON file.
This is true. Luckily, there is a UI element that now exists in which an administrative user can add a role to a user within a particular workflow. This can be seen at /admin/workflow_roles/?
. For development purposes, if you would like to add all users to all roles, you can do that with the following command:
Hyrax::Workflow::PermissionGenerator.call(roles: Sipity::Role.all,
workflow: Sipity::Workflow.last,
agents: User.all)
Hyrax comes with a one step approval workflow. You can see the file in config/workflows/mediated_deposit_workflow.json
.
Make sure you’ve imported all of the files in the workflows directory:
rake hyrax:workflow:load
In order to use the mediated deposit workflow, you first need to create a new Admin Set and and assign it to use the workflow. You can do this either via the UI, or programatically.
Make sure you have an admin user. Look at your config/role_map.yml
file and ensure that the user you want to have admin privileges is defined under an admin section, like this:
development:
archivist:
- [email protected]
admin:
- [email protected]
Now, if you log in via the admin user, you should see an menu called “Administration” in the top left corner. Under it, you should see a menu item on the left called “Administrative Sets.” Under that menu, you can create a new administrative set. Give it a name and description and click save. Now you can assign it a workflow under the “Workflow” tab that will appear. You will also need to give it participants under the participants tab.
You will also need to use the workflow --> roles
menu to add users as approvers for the workflow.
Here is a console walk through of how to set up workflow programatically. This is a good idea if you have many workflows, or complex setup, that you need to re-create over and over again and don’t want to make a person do it manually.
a = AdminSet.new
a.title = ["Emory ETDs"]
a.save
Hyrax::AdminSetCreateService.call(admin_set: a, creating_user: User.first!)
# this will tell you what workflow was assigned. Probably the default.
a.active_workflow
# Get all the workflows available for this AdminSet's permission_template
available_workflows = a.permission_template.available_workflows
=> #<ActiveRecord::Associations::CollectionProxy [#<Sipity::Workflow id: 1, name: "default", label: "Default workflow", description: "A single submission step, default workflow", created_at: "2017-04-25 18:21:41", updated_at: "2017-04-25 18:21:43", permission_template_id: 1, active: true, allows_access_grant: true>, #<Sipity::Workflow id: 2, name: "one_step_mediated_deposit", label: "One-step mediated deposit workflow", description: "A single-step workflow for mediated deposit in whi...", created_at: "2017-04-25 18:21:41", updated_at: "2017-04-25 18:21:43", permission_template_id: 1, active: nil, allows_access_grant: false>]>
mediated_workflow = a.permission_template.available_workflows.where(name: "one_step_mediated_deposit").first
Sipity::Workflow.activate!(permission_template: a.permission_template, workflow_id: mediated_workflow.id)
# Now calling #active_workflow should give you the mediated deposit workflow
a.active_workflow
=> #<Sipity::Workflow id: 2, name: "one_step_mediated_deposit", label: "One-step mediated deposit workflow", description: "A single-step workflow for mediated deposit in whi...", created_at: "2017-04-25 18:21:41", updated_at: "2017-04-25 18:44:48", permission_template_id: 1, active: true, allows_access_grant: false>
The AdminSet will be visible on the “relationships” tab of the deposit form. If you only have one admin set, it will be set by default. In order for it to work, a non-admin user must deposit a work, and an admin user must approve it.
# First, you need a user
u = ::User.find_or_create_by(email: approver_email)
u.password = "123456"
u.save
# Then, you need to transform your User object into a Sipity::Agent
approving_agent = u.to_sipity_agent
workflow = a.active_workflow
# Get the Sipity::Role for approving
approval_role = Sipity::Role.find_by!(name: 'approving')
# Grant this one user the approving role for the EmoryETDs AdminSet
workflow.update_responsibilities(role: approval_role, agents: approving_agent)
Important
Calling #update_responsibilities
on a workflow will add any agents you pass in the agents parameter, but it will also REMOVE any agents who are not in the agents parameter.
One pattern being used successfully is to put your workflow setup into its own class (e.g., lib/workflow_setup.rb
) and give it a method (e.g., setup
) that will configure everything as expected. That way you can follow best practice and make sure you are writing tests for it. Then, put something like this in db/seeds.rb
:
require 'workflow_setup'
w = WorkflowSetup.new
w.setup
Putting this code in db/seeds.rb
means it will be called at the end of bin/setup
, and you can call it anytime via rake db:seed
Create a Sipity::Method to assign a reviewer based on some property of the deposited work (e.g. reviewers based on the work’s department).
Before getting into the specifics of how to do this, let’s do a quick overview of some key workflow concepts for this problem.
For this particular issue, there are three primary concepts that you may want to reference:
- Permissions are assigned at two levels:
- Sipity::WorkflowResponsibility - A person has permissions to all things using this workflow
- Sipity::EntitySpecificResponsibility - A person has permissions to only the work/entity
-
Sipity::Method - Something we
.call
when we take aSipity::Action
. - Hyrax::Workflow::PermissionGenerator - Responsible for assigning permissions. Note: This could be better named as Hyrax::Workflow::PermissionAssigner.
Referencing the default workflow's array of template workflows ([default workflow][template_workflows]
), there is a method object that is called when a user deposits a work: One method object, Hyrax::Workflow::GrantEditToDepositor, grants the user depositing rights for the deposit worked (eg. Sipity::EntitySpecificResponsibility
).
You’ll need to modify your workflow (an exercise left up to the reader) to include a method for the appropriate action. You may want to reference the JSON workflow schema for proper syntax.
Then create (and test) your method object.
module Hyrax::Workflow::AssignReviewerByDepartment
def self.call(target:, **)
reviewer = find_reviewer_for(department: target.department)
# This assigns database permissions, but does not grant permissions on the Fedora object.
Hyrax::Workflow::PermissionGenerator.call(entity: target, agent: reviewer, role: 'Reviewer')
# Do you want to update the Fedora object? Then you'll need to make adjustments.
end
def self.find_reviewer_for(department:)
# You do the work
end
end
# Make sure to test that your method conforms to the Hyrax workflow method interface
RSpec.describe Hyrax::Workflow::AssignReviewerByDepartment do
let(:workflow_method) { described_class }
it_behaves_like "a Hyrax workflow method"
# Don't forget to write specs for the business logic of this method
end