Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HYC-1994 - Viewer Notification Workflow #1143

Merged
merged 74 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
f314c03
dataset deposited notif class
davidcam-src Jan 28, 2025
6c175c2
dataset notification in default workflow
davidcam-src Jan 29, 2025
8b616d4
test changes to workflows
davidcam-src Jan 29, 2025
a43dd79
notifications
davidcam-src Jan 30, 2025
06d0fe7
only use dataset deposited notifications
davidcam-src Jan 30, 2025
4b2b9da
viewer notifications
davidcam-src Feb 4, 2025
d2060de
Merge branch 'main' into hyc-1994
davidcam-src Feb 4, 2025
687a181
label, name
davidcam-src Feb 4, 2025
4736826
workflow update
davidcam-src Feb 4, 2025
1248387
deposited viewer notif test
davidcam-src Feb 4, 2025
61fefbe
remove deposited viewer notif class
davidcam-src Feb 5, 2025
863bc41
rolling back some stuff
davidcam-src Feb 6, 2025
0cacc4c
spacing
davidcam-src Feb 6, 2025
d2ed1c5
rollback
davidcam-src Feb 6, 2025
10e83df
reordering notifications
davidcam-src Feb 10, 2025
087260d
role registry override
davidcam-src Feb 11, 2025
7d084c6
syntax
davidcam-src Feb 11, 2025
5233cb8
remove comments
davidcam-src Feb 11, 2025
46b4d6b
modify roles_for_agent
davidcam-src Feb 12, 2025
532e9a0
viewer notification class
davidcam-src Feb 12, 2025
dd41fc5
update workflow
davidcam-src Feb 12, 2025
a19b828
wip viewer notifications class
davidcam-src Feb 12, 2025
740ace4
users to notify
davidcam-src Feb 13, 2025
ac05e57
duplicate user key
davidcam-src Feb 13, 2025
7ce499f
reintroduce logs, users in both group are notified as viewers
davidcam-src Feb 13, 2025
f8ea2dc
retrieve user ids for attached groups
davidcam-src Feb 13, 2025
63fdee3
rubocop, filter managers
davidcam-src Feb 14, 2025
57bbdf0
syntax
davidcam-src Feb 14, 2025
2cab368
remove old code
davidcam-src Feb 14, 2025
44cd26d
comments
davidcam-src Feb 14, 2025
daf6d0d
slightly more descriptive naming
davidcam-src Feb 14, 2025
26e9709
users assigned to both groups are notified as viewers
davidcam-src Feb 14, 2025
aa3caf4
wip query change
davidcam-src Feb 14, 2025
0019e65
log
davidcam-src Feb 14, 2025
2bdd6ee
new query
davidcam-src Feb 14, 2025
5a5774e
logic change
davidcam-src Feb 14, 2025
add3a26
small changes
davidcam-src Feb 14, 2025
4df8c72
comment removal
davidcam-src Feb 14, 2025
7dc6b38
fetch user info from db
davidcam-src Feb 14, 2025
682fd14
sql fix
davidcam-src Feb 15, 2025
b9493a1
logs
davidcam-src Feb 15, 2025
fdc29fb
syntax
davidcam-src Feb 15, 2025
819759a
change admin set query
davidcam-src Feb 15, 2025
2f51001
update query for users
davidcam-src Feb 15, 2025
224577e
fetch users from only_viewer_ids array
davidcam-src Feb 15, 2025
7e9d955
leverage helper method
davidcam-src Feb 15, 2025
5735a0d
remove recipients array
davidcam-src Feb 15, 2025
961f8f2
send if roles are equal
davidcam-src Feb 17, 2025
e08e18e
users and roles query
davidcam-src Feb 17, 2025
85a0fa6
work data by id
davidcam-src Feb 17, 2025
10f2a49
roll back changes, log
davidcam-src Feb 17, 2025
5aee8b5
extra query condition
davidcam-src Feb 17, 2025
77a88ac
rubocop
davidcam-src Feb 17, 2025
7b469a7
viewer notification tests
davidcam-src Feb 17, 2025
2483a6e
add cc in users to notify, notification test
davidcam-src Feb 17, 2025
4be0c0d
isolate test, unescape user names
davidcam-src Feb 18, 2025
9bc3384
removing comments, directly unescape user link
davidcam-src Feb 18, 2025
46c5fc4
tests
davidcam-src Feb 18, 2025
35b3f34
rubocop
davidcam-src Feb 18, 2025
fea4041
role registry tests
davidcam-src Feb 18, 2025
d98e034
role registry override change
davidcam-src Feb 18, 2025
f6ba6fd
rubocop, test changes
davidcam-src Feb 18, 2025
b56560b
remove comments
davidcam-src Feb 18, 2025
9ee0752
wip fetch work data by id tests
davidcam-src Feb 18, 2025
86767e1
finish fetch_work_data_by_id tests
davidcam-src Feb 18, 2025
e42477f
revert spacing change
davidcam-src Feb 18, 2025
316f72f
remove send notifications from override
davidcam-src Feb 18, 2025
7f052fd
revert build
davidcam-src Feb 18, 2025
af38446
avoid notifying users assigned view and manage an equal amount
davidcam-src Feb 18, 2025
2082d91
indent change, streamlining users to notif impl
davidcam-src Feb 24, 2025
49bf768
spacing
davidcam-src Feb 25, 2025
37fa54d
spacing
davidcam-src Feb 25, 2025
cace6ba
expand text, manager with viewing role
davidcam-src Feb 25, 2025
a053efe
viewer notif changes
davidcam-src Feb 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions app/helpers/work_utils_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,22 @@ def self.fetch_work_data_by_fileset_id(fileset_id)
# Set the admin set to an empty hash if the solr query returns nil
admin_set_name = work_data['admin_set_tesim']&.first
admin_set_data = admin_set_name ? ActiveFedora::SolrService.get("title_tesim:#{admin_set_name}", { :rows => 1, 'df' => 'title_tesim'})['response']['docs'].first : {}
Rails.logger.warn(self.generate_warning_message(admin_set_name, fileset_id)) if admin_set_data.blank?
Rails.logger.warn(self.generate_warning_message(admin_set_name, fileset_id, true)) if admin_set_data.blank?
{
work_id: work_data['id'],
work_type: work_data.dig('has_model_ssim', 0),
title: work_data['title_tesim']&.first,
admin_set_id: admin_set_data['id'],
admin_set_name: admin_set_name
}
end

def self.fetch_work_data_by_id(work_id)
work_data = ActiveFedora::SolrService.get("id:#{work_id}", rows: 1)['response']['docs'].first || {}
Rails.logger.warn("No work found associated with work id: #{work_id}") if work_data.blank?
admin_set_name = work_data['admin_set_tesim']&.first
admin_set_data = admin_set_name ? ActiveFedora::SolrService.get("title_tesim:#{admin_set_name} AND has_model_ssim:(\"AdminSet\")", { :rows => 1, 'df' => 'title_tesim'})['response']['docs'].first : {}
Rails.logger.warn(self.generate_warning_message(admin_set_name, work_id, false)) if admin_set_data.blank?
{
work_id: work_data['id'],
work_type: work_data.dig('has_model_ssim', 0),
Expand All @@ -19,9 +34,9 @@ def self.fetch_work_data_by_fileset_id(fileset_id)

private_class_method

def self.generate_warning_message(admin_set_name, fileset_id)
def self.generate_warning_message(admin_set_name, id, is_fileset_id)
if admin_set_name.blank?
return "Could not find an admin set, the work with fileset id: #{fileset_id} has no admin set name."
return "Could not find an admin set, the work with #{is_fileset_id ? 'fileset' : 'work'} id: #{id} has no admin set name."
else
return "No admin set found with title_tesim: #{admin_set_name}."
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true
# [hyc-override] Uncomment line in roles_for_agent
# https://github.com/samvera/hyrax/blob/hyrax-v4.0.0/app/forms/hyrax/forms/permission_template_form.rb
Hyrax::Forms::PermissionTemplateForm.class_eval do
def roles_for_agent
roles = []
grants_as_collection.each do |grant|
case grant[:access]
when Hyrax::PermissionTemplateAccess::DEPOSIT
roles << Sipity::Role.find_by(name: Hyrax::RoleRegistry::DEPOSITING)
when Hyrax::PermissionTemplateAccess::MANAGE
roles += Sipity::Role.where(name: Hyrax::RoleRegistry.new.role_names)
when Hyrax::PermissionTemplateAccess::VIEW
roles << Sipity::Role.find_by(name: Hyrax::RoleRegistry::VIEWING)
end
end
roles.uniq
end
end
25 changes: 25 additions & 0 deletions app/overrides/lib/hyrax/role_registry_override.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true
# [hyc-override] https://github.com/samvera/hyrax/blob/hyrax-v4.0.0/lib/hyrax/role_registry.rb

module Hyrax
class RoleRegistry
MANAGING = 'managing'
APPROVING = 'approving'
DEPOSITING = 'depositing'
# [hyc-override] Added Viewing role
VIEWING = 'viewing'

# Override the MAGIC_ROLES constant
MAGIC_ROLES = {
MANAGING => 'Grants access to management tasks',
APPROVING => 'Grants access to approval tasks',
DEPOSITING => 'Grants access to depositing tasks',
VIEWING => 'Grants access to viewing tasks'
}.freeze

# Override initialize to ensure the correct roles are loaded
def initialize
@roles = MAGIC_ROLES.dup
end
end
end
65 changes: 65 additions & 0 deletions app/services/hyrax/workflow/deposited_viewer_notification.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# frozen_string_literal: true
module Hyrax
module Workflow
# This notification service was created to exclusively notify viewers in the viewer-specific workflow.
# Using the DepositedManagerNotificaiton class for this would send deposit notifications to managers as well.
class DepositedViewerNotification < AbstractNotification
private
def subject
I18n.t('hyrax.notifications.workflow.deposited_manager.subject')
end

def message
I18n.t('hyrax.notifications.workflow.deposited_manager.message', title: title, link: (link_to work_id, document_path))
end

# Modified version of the users_to_notify method to only notify users that are exclusively viewers, since managers are assigned all roles they would get viewer notifications as well.
def users_to_notify
work_data = WorkUtilsHelper.fetch_work_data_by_id(@work_id)
return if work_data[:admin_set_id].blank?
admin_set_id = work_data[:admin_set_id]
admin_set_name = work_data[:admin_set_name]

# Query for users within groups assigned to the admin set
groups_and_roles_query = ActiveRecord::Base.connection.execute("SELECT u.id AS user_id, u.email, r.name AS group_name, pta.access AS admin_set_role
FROM users u
JOIN roles_users ru ON u.id = ru.user_id
JOIN roles r ON ru.role_id = r.id
JOIN permission_template_accesses pta ON pta.agent_id = r.name AND pta.agent_type = 'group'
WHERE pta.permission_template_id = (
SELECT id FROM permission_templates WHERE source_id = '#{admin_set_id}'
)")
# Query for users assigned admin set permissions directly
users_and_roles_query = ActiveRecord::Base.connection.execute("SELECT u.id, u.email, pta.access AS admin_set_role
FROM users u
JOIN permission_template_accesses pta ON u.uid = pta.agent_id
WHERE pta.permission_template_id = (
SELECT id FROM permission_templates WHERE source_id = '#{admin_set_id}'
)
AND pta.agent_type = 'user';"
)
groups_and_roles = groups_and_roles_query.map { |row| row.symbolize_keys }
users_and_roles = users_and_roles_query.map { |row| row.symbolize_keys }

# # Map [user_id, group_name, admin_set_role] -> [user_id, {role_name => count}]
user_role_map = groups_and_roles.each_with_object({}) do |query_result, h|
user_id = query_result[:user_id].to_i
h[user_id] ||= { 'view' => 0, 'manage' => 0 }
h[user_id][query_result[:admin_set_role]] += 1
end

users_and_roles.each do |query_result|
user_id = query_result[:id].to_i
user_role_map[user_id] ||= { 'view' => 0, 'manage' => 0 }
user_role_map[user_id][query_result[:admin_set_role]] += 1
end

# Filter users to those that have view but no manager
only_viewers = user_role_map.select { |user_id, role_counts| role_counts['view'] > 0 && role_counts['manage'] == 0 }
viewer_ids = only_viewers.keys.map(&:to_i)
# Fetch users directly from the database
::User.where(id: viewer_ids).to_a + recipients['cc']
end
end
end
end
95 changes: 95 additions & 0 deletions config/workflows/viewer_default_workflow.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{
"workflows": [
{
"name": "default_email_viewer",
"label": "Default workflow - viewer emails",
"description": "A single submission step, default workflow with viewer emails",
"allows_access_grant": true,
"actions": [
{
"name": "deposit",
"from_states": [],
"transition_to": "deposited",
"methods": [
"Hyrax::Workflow::GrantEditToDepositor",
"Hyrax::Workflow::ActivateObject"
],
"notifications": [
{
"notification_type": "email",
"name": "Hyrax::Workflow::DepositedNotification",
"to": []
},
{
"notification_type": "email",
"name": "Hyrax::Workflow::DepositedViewerNotification",
"to": ["viewing"]
}
]
}, {
"name": "withdraw",
"from_states": [{"names": ["deposited"], "roles": ["deleting"]}],
"transition_to": "withdrawn",
"notifications": [
{
"notification_type": "email",
"name": "Hyrax::Workflow::WithdrawalNotification",
"to": ["depositing", "deleting"]
}
],
"_comment" : "MetadataOnlyRecord must be called before RevokeEditFromDepositor",
"methods": [
"Hyrax::Workflow::MetadataOnlyRecord",
"Hyrax::Workflow::RevokeEditFromDepositor"
]
}, {
"name": "request_deletion",
"_comment" : "This action is suppressed from normal rendering with other review/approval actions",
"from_states": [{"names": ["deposited"], "roles": ["deleting", "depositing"]}],
"transition_to": "pending_deletion",
"notifications": [
{
"notification_type": "email",
"name": "Hyrax::Workflow::PendingDeletionNotification",
"to": []
}
],
"methods": [
"Hyrax::Workflow::MetadataOnlyRecord",
"Hyrax::Workflow::RevokeEditFromDepositor"
]
}, {
"name": "approve_deletion",
"from_states": [{"names": ["deposited", "pending_deletion"], "roles": ["deleting"]}],
"transition_to": "withdrawn",
"notifications": [
{
"notification_type": "email",
"name": "Hyrax::Workflow::DeletionApprovalNotification",
"to": ["depositing"]
}
],
"methods": [
"Hyrax::Workflow::MetadataOnlyRecord",
"Hyrax::Workflow::RevokeEditFromDepositor"
]
}, {
"name": "republish",
"from_states": [{"names": ["pending_deletion"], "roles": ["deleting"]}],
"transition_to": "deposited",
"notifications": [
{
"notification_type": "email",
"name": "Hyrax::Workflow::DeletionRequestRejectionNotification",
"to": ["depositing"]
}
],
"methods": [
"Hyrax::Workflow::GrantEditToDepositor",
"Hyrax::Workflow::ActivateObject"
]
}
]
}
]
}
1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ services:
- hycdev
solr:
image: solr:9
user: "8983:8983"
volumes:
- ./solr/config:/opt/solr/server/solr/configsets/hy-c/conf
- solr-data:/var/solr/data
Expand Down
45 changes: 45 additions & 0 deletions spec/forms/hyrax/forms/permission_template_form_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true
require 'rails_helper'

RSpec.describe Hyrax::Forms::PermissionTemplateForm do
describe '#roles_for_agent' do
let(:permission_template) { FactoryBot.create(:permission_template) }
let(:permission_template_form) { described_class.new(permission_template) }
let(:all_permissions) { [
{ access: Hyrax::PermissionTemplateAccess::DEPOSIT },
{ access: Hyrax::PermissionTemplateAccess::MANAGE },
{ access: Hyrax::PermissionTemplateAccess::VIEW },
]
}
let(:managing_role) { Sipity::Role.find_by(name: Hyrax::RoleRegistry::MANAGING) }
let(:depositing_role) { Sipity::Role.find_by(name: Hyrax::RoleRegistry::DEPOSITING) }
let(:viewing_role) { Sipity::Role.find_by(name: Hyrax::RoleRegistry::VIEWING) }


before do
Sipity::Role.create!(name: 'managing', description: 'Grants access to management tasks')
Sipity::Role.create!(name: 'depositing', description: 'Grants access to depositing tasks')
Sipity::Role.create!(name: 'viewing', description: 'Grants access to viewing tasks')
end

it 'returns all roles when manage access is granted' do
allow(permission_template_form).to receive(:grants_as_collection).and_return([{ access: Hyrax::PermissionTemplateAccess::MANAGE }])
expect(permission_template_form.roles_for_agent).to include(managing_role, depositing_role, viewing_role)
end

it 'returns the depositing role when deposit access is granted' do
allow(permission_template_form).to receive(:grants_as_collection).and_return([{ access: Hyrax::PermissionTemplateAccess::DEPOSIT }])
expect(permission_template_form.roles_for_agent).to eq [depositing_role]
end

it 'returns the viewing role when view access is granted' do
allow(permission_template_form).to receive(:grants_as_collection).and_return([{ access: Hyrax::PermissionTemplateAccess::VIEW }])
expect(permission_template_form.roles_for_agent).to eq [viewing_role]
end

it 'applies all roles when all permissions are present' do
allow(permission_template_form).to receive(:grants_as_collection).and_return(all_permissions)
expect(permission_template_form.roles_for_agent).to include(managing_role, depositing_role, viewing_role)
end
end
end
47 changes: 47 additions & 0 deletions spec/helpers/hyrax/work_utils_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,51 @@
end
end
end

describe '#fetch_work_data_by_id' do
it 'fetches the work data correctly' do
mock_record_id = mock_records[0][0]['id']
allow(ActiveFedora::SolrService).to receive(:get).with("id:#{mock_record_id}", rows: 1).and_return('response' => { 'docs' => mock_records[0] })
allow(ActiveFedora::SolrService).to receive(:get).with("title_tesim:#{admin_set_name} AND has_model_ssim:(\"AdminSet\")", {'df'=>'title_tesim', :rows=>1}).and_return('response' => { 'docs' => mock_admin_set })
result = WorkUtilsHelper.fetch_work_data_by_id(mock_record_id)
expect(result).to eq(expected_work_data)
end

it 'logs appropriate messages for missing values' do
# Mock the solr response to simulate a work with missing values, if it somehow makes it past the initial nil check
mock_record_id = mock_records[0][0]['id']
allow(ActiveFedora::SolrService).to receive(:get).with("id:#{mock_record_id}", rows: 1).and_return('response' => { 'docs' => [] })
allow(Rails.logger).to receive(:warn)
result = WorkUtilsHelper.fetch_work_data_by_id(mock_record_id)
expect(Rails.logger).to have_received(:warn).with("No work found associated with work id: #{mock_record_id}")
expect(Rails.logger).to have_received(:warn).with("Could not find an admin set, the work with work id: #{mock_record_id} has no admin set name.")
expect(result[:work_id]).to be_nil
expect(result[:work_type]).to be_nil
expect(result[:title]).to be_nil
expect(result[:admin_set_id]).to be_nil
end

context 'when admin set is not found' do
it 'logs an appropriate message if the work doesnt have an admin set title' do
mock_record_id = mock_records[1][0]['id']
# Using the mock record without an admin set title
allow(ActiveFedora::SolrService).to receive(:get).with("id:#{mock_record_id}", rows: 1).and_return('response' => { 'docs' => mock_records[1] })
allow(Rails.logger).to receive(:warn)
result = WorkUtilsHelper.fetch_work_data_by_id(mock_record_id)
expect(Rails.logger).to have_received(:warn).with("Could not find an admin set, the work with work id: #{mock_record_id} has no admin set name.")
expect(result[:admin_set_id]).to be_nil
end

it 'logs an appropriate message if the query for an admin set returns nothing' do
mock_record_id = mock_records[1][0]['id']
# Using the mock record with an admin set title
allow(ActiveFedora::SolrService).to receive(:get).with("id:#{mock_record_id}", rows: 1).and_return('response' => { 'docs' => mock_records[0] })
allow(ActiveFedora::SolrService).to receive(:get).with("title_tesim:#{admin_set_name} AND has_model_ssim:(\"AdminSet\")", {'df'=>'title_tesim', :rows=>1}).and_return('response' => { 'docs' => [{}] })
allow(Rails.logger).to receive(:warn)
result = WorkUtilsHelper.fetch_work_data_by_id(mock_record_id)
expect(Rails.logger).to have_received(:warn).with("No admin set found with title_tesim: #{admin_set_name}.")
expect(result[:admin_set_id]).to be_nil
end
end
end
end
17 changes: 17 additions & 0 deletions spec/lib/hyrax/role_registry_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true
require 'rails_helper'

RSpec.describe Hyrax::RoleRegistry do
describe '#initialize' do
it 'initializes with roles' do
expected_roles = {
'managing' => 'Grants access to management tasks',
'approving' => 'Grants access to approval tasks',
'depositing' => 'Grants access to depositing tasks',
'viewing' => 'Grants access to viewing tasks'
}
role_registry = described_class.new
expect(role_registry.instance_variable_get(:@roles)).to eq(expected_roles)
end
end
end
6 changes: 5 additions & 1 deletion spec/models/proxy_deposit_request_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true
require 'rails_helper'
require 'cgi'
require 'nokogiri'

RSpec.describe ProxyDepositRequest, type: :model do
include ActionView::Helpers::UrlHelper
Expand Down Expand Up @@ -38,7 +40,9 @@
user_link = link_to(sender.name, Hyrax::Engine.routes.url_helpers.user_path(sender))
transfer_link = link_to('transfer requests', Hyrax::Engine.routes.url_helpers.transfers_path)
work_link = link_to work.title.first, "#{ENV['HYRAX_HOST']}/concern/#{work.class.to_s.underscore}s/#{work.id}"
expect(receiver.mailbox.inbox.last.last_message.body).to include(user_link + ' wants to transfer ownership of ' + work_link + ' to you. To accept this transfer request, go to the Carolina Digital Repository (CDR) ' + transfer_link)
# Unescape user link for names with single quotes
expected_message = CGI.unescapeHTML(user_link.to_s) + ' wants to transfer ownership of ' + work_link + ' to you. To accept this transfer request, go to the Carolina Digital Repository (CDR) ' + transfer_link
expect(receiver.mailbox.inbox.last.last_message.body).to include(expected_message)
proxy_request = receiver.proxy_deposit_requests.first
expect(proxy_request.work_id).to eq(work_id)
expect(proxy_request.sending_user).to eq(sender)
Expand Down
Loading