Skip to content

Commit

Permalink
Merge pull request #961 from scientist-softserv/fixing_reshare_some_m…
Browse files Browse the repository at this point in the history
…ore_part_deux

🎁 Add loading splash screen and speed up redirect
  • Loading branch information
Kirk Wang authored Jan 19, 2024
2 parents 23c9bcc + c343fd5 commit 9bc5bd9
Show file tree
Hide file tree
Showing 17 changed files with 399 additions and 41 deletions.
35 changes: 25 additions & 10 deletions app/assets/stylesheets/single_signon.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,39 @@
}

.loader {
-webkit-animation: spin 2s linear infinite; // Safari
animation: spin 2s linear infinite;
border-radius: 50%;
border-top: 16px solid #0a1f61;
border: 16px solid #f3f3f3;
color: #f3f3f3;
font-size: 11px;
height: 120px;
margin: 55px auto 150px;
text-indent: -99999em;
border-radius: 50%;
border-top: 16px solid #284a75;
width: 120px;
height: 120px;
-webkit-animation: spin 2s linear infinite; /* Safari */
animation: spin 2s linear infinite;
}
// Safari

@-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}

@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

#splash {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 1);
z-index: 10000;
justify-content: center;
align-items: center;
flex-direction: column;

&.active {
display: flex !important;
}
}
50 changes: 50 additions & 0 deletions app/jobs/create_group_and_add_members_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true

##
# This class is responsible for assigning a conceptually "private" group to the
# members of of the {Cdl} work.
#
# The reason for creating this group is when a person gains access to the CDL
# (via the lending application) it is more performant to add the person to a
# group than it is to add the person directly to each of the underlying files.
class CreateGroupAndAddMembersJob < ApplicationJob
RETRY_MAX = 5

queue_as :default

def perform(cdl_id, retries = 0)
work = Cdl.where(id: cdl_id).first
return if work.nil?

page_count = work.file_sets.first.page_count.first.to_i
child_model = work.iiif_print_config.pdf_split_child_model
child_works_count = work.members.select { |member| member.is_a?(child_model) }.count

if page_count == child_works_count
group = Hyrax::Group.find_or_create_by!(name: work.id)
work.read_groups = [group.name]

work.members.each do |member|
assign_read_groups(member, group.name)
end

work.save
group.save
else
return if retries > RETRY_MAX

retries += 1
CreateGroupAndAddMembersJob.set(wait: 10.minutes).perform_later(cdl_id, retries)
end
end

private

def assign_read_groups(member, group_name)
member.read_groups = [group_name]
member.save
member.members.each do |sub_member|
assign_read_groups(sub_member, group_name)
end
end
end
9 changes: 9 additions & 0 deletions app/jobs/destroy_cdl_group_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

class DestroyCdlGroupJob < ApplicationJob
queue_as :default

def perform(id)
Hyrax::Group.find_by(name: id)&.destroy
end
end
13 changes: 13 additions & 0 deletions app/jobs/destroy_split_pages_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

class DestroySplitPagesJob < ApplicationJob
queue_as :default

def perform(id)
work = ActiveFedora::Base.where(id: id).first
return unless work&.is_child

work.members.each(&:destroy)
work.destroy
end
end
52 changes: 52 additions & 0 deletions app/jobs/iiif_print/create_relationships_job_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# frozen_string_literal: true

# OVERRIDE IIIF Print v1.0.0 to call CreateGroupAndAddMembersJob

module IiifPrint
module Jobs
module CreateRelationshipsJobDecorator
def perform(parent_id:, parent_model:, child_model:, retries: 0, **)
@parent_id = parent_id
@parent_model = parent_model
@child_model = child_model
@retries = retries + 1

@number_of_successes = 0
@number_of_failures = 0
@parent_record_members_added = false
@errors = []

# Because we need our children in the correct order, we can't create any
# relationships until all child works have been created.
if completed_child_data
# add the members
add_children_to_parent
if @number_of_failures.zero? && @number_of_successes == @pending_children.count
# remove pending relationships upon valid completion
@pending_children.each(&:destroy)
elsif @number_of_failures.zero? && @number_of_successes > @pending_children.count
# remove pending relationships but raise error that too many relationships formed
@pending_children.each(&:destroy)
raise "CreateRelationshipsJob for parent id: #{@parent_id} " \
"added #{@number_of_successes} children, " \
"expected #{@pending_children} children."
else
# report failures & keep pending relationships
raise "CreateRelationshipsJob failed for parent id: #{@parent_id} " \
"had #{@number_of_successes} successes & #{@number_of_failures} failures, " \
"with errors: #{@errors}. Wanted #{@pending_children} children."
end

# OVERRIDE begin
CreateGroupAndAddMembersJob.set(wait: 2.minutes).perform_later(parent_id)
# OVERRIDE end
else
# if we aren't ready yet, reschedule the job and end this one normally
reschedule_job
end
end
end
end
end

IiifPrint::Jobs::CreateRelationshipsJob.prepend(IiifPrint::Jobs::CreateRelationshipsJobDecorator)
16 changes: 16 additions & 0 deletions app/models/cdl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ class Cdl < ActiveFedora::Base
include VideoEmbedBehavior

self.indexer = CdlIndexer

before_destroy :destroy_split_pages
after_destroy :destroy_cdl_group

validates :title, presence: { message: 'Your work must have a title.' }

property :additional_information, predicate: ::RDF::Vocab::DC.accessRights do |index|
Expand Down Expand Up @@ -50,4 +54,16 @@ class Cdl < ActiveFedora::Base
# including `include ::Hyrax::BasicMetadata`. All properties must
# be declared before their values can be ordered.
include OrderMetadataValues

private

def destroy_cdl_group
DestroyCdlGroupJob.perform_later(id)
end

def destroy_split_pages
members.each do |member|
DestroySplitPagesJob.perform_later(member.id)
end
end
end
2 changes: 2 additions & 0 deletions app/models/sushi.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal:true

require 'sushi/error'

module Sushi
mattr_accessor :info

Expand Down
46 changes: 26 additions & 20 deletions app/models/work_authorization.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# frozen_string_literal: true

# WorkAuthorization models users granted access to works. The instigator of the authorizations is
# WorkAuthorization models users granted access to groups of works. The instigator of the authorizations is
# outside the model. In the case of PALNI/PALCI there will be a Shibboleth/SAML authentication that
# indicates we should create a WorkAuthorization entry.
# indicates we should create a WorkAuthorization entry for a group of works.
#
# @note Transactions across data storage layers (e.g. postgres and fedora) are precarious. Fedora
# doesn't have proper transactions and there is not a clear concept of Postgres and Fedora
Expand All @@ -18,14 +18,20 @@ def initialize(user:, work:)
end
end

class GroupNotFoundError < StandardError
def initialize(user:, group:)
"Unable to authorize #{user.class} #{user.user_key.inspect} for group with name=#{group.name.inspect} because group does not exist."
end
end

belongs_to :user

# This will be a non-ActiveRecord resource
validates :work_pid, presence: true

##
# When a :user signs in, we want to re-authorize works that are part of their latest
# authentication. We want to de-authorize access to any works that are not part of their recent
# When a :user signs in, we want to re-authorize the works' groups that are part of their latest
# authentication. We want to de-authorize access to any works' groups that are not part of their recent
# authentication.
#
# @param user [User]
Expand All @@ -46,8 +52,9 @@ def self.handle_signin_for!(user:, authorize_until: 1.day.from_now, work_pid: ni
pids.each do |pid|
begin
work = ActiveFedora::Base.where(id: pid).first
authorize!(user: user, work: work, expires_at: authorize_until)
rescue WorkNotFoundError
group = Hyrax::Group.find_by(name: pid)
authorize!(user: user, work: work, group: group, expires_at: authorize_until)
rescue WorkNotFoundError, GroupNotFoundError
Rails.logger.info("Unable to find work_pid of #{pid.inspect}.")
end
end
Expand Down Expand Up @@ -112,46 +119,45 @@ def self.extract_pids_from(scope:, with_regexp: REGEXP_TO_MATCH_PID)
# Grant the given :user permission to read the work associated with the given :work_pid.
#
# @param user [User]
# @param work_pid [String]
# @param work [ActiveFedora::Base]
# @param group [Hyrax::Group]
#
# @raise [WorkAuthorization::WorkNotFoundError] when the given :work_pid is not found.
# @raise [WorkAuthorization::GroupNotFoundError] when the given :group is not found.
#
# @see .revoke!
def self.authorize!(user:, work:, expires_at: 1.day.from_now)
def self.authorize!(user:, work:, group:, expires_at: 1.day.from_now)
raise WorkNotFoundError.new(user: user, work: work) unless work
raise GroupNotFoundError.new(user: user, group: group) unless group

transaction do
authorization = find_or_create_by!(user_id: user.id, work_pid: work.id)
authorization.update!(work_title: work.title, expires_at: expires_at)

work.set_read_users([user.user_key], [user.user_key])
work.save!
end
work.members.each do |member_work|
authorize!(user: user, work: member_work, expires_at: expires_at)
group.add_members_by_id(user.id)
end
end

##
# Remove permission for the given :user to read the work associated with the given :work_pid.
#
# @param user [User]
# @param work_pid [String]
# @param work [ActiveFedora::Base]
#
# @see .authorize!
def self.revoke!(user:, work:)
return unless work

# When we delete the authorizations, we want to ensure that we've tidied up the corresponding
# work's read users. If for some reason the ActiveFedora save fails, this the destruction of
# the authorizations will rollback. Meaning we still have a record of what we've authorized.OB
transaction do
where(user_id: user.id, work_pid: work.id).destroy_all
work.set_read_users([], [user.user_key])
work.save!
true
end
work.members.each do |member_work|
revoke!(user: user, work: member_work)
Rails.logger.info("Looking for a group with name=#{work.id.inspect}.")
group = Hyrax::Group.find_by(name: work.id)
return unless group

group.remove_members_by_id(user.id)
end
end

Expand Down
7 changes: 5 additions & 2 deletions app/views/devise/omniauth_callbacks/complete.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<h1 class='text-center'> Thank you for signing in. Your dashboard is loading</h1>
<div class="loader">Loading...</div>
<div class="text-center">
<h1>Thank you for signing in. Your dashboard is loading</h1>
<div class="loader center-block"></div>
</div>

<script>
Blacklight.onLoad(function() {
window.setTimeout(function(){
Expand Down
12 changes: 9 additions & 3 deletions app/views/single_signon/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
<div id="splash">
<h1>Please wait while we redirect you...</h1>
<div class="loader"></div>
</div>

<h1 class='text-center'>Select a Single Sign On Provider</h1>

<%- if devise_mapping.omniauthable? %>
Expand All @@ -16,7 +21,8 @@
<% end -%>

<% if @identity_providers.count == 1 %>
<script>
$('form.button_to').submit();
</script>
<script>
$('#splash').addClass('active');
$('form.button_to').submit();
</script>
<% end %>
19 changes: 19 additions & 0 deletions lib/tasks/create_group_and_add_members.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

# CREATE_GROUP_FOR=tenant rake hyku:cdl:create_group_and_add_members[id1.id2.id3]
# NOTE: we are using period separations because comma separates don't seem to work
# without the CREATE_GROUP_FOR env var, it will default to 'blc' tenant
namespace :hyku do
namespace :cdl do
desc 'Enqueue CreateGroupAndAddMembersJob for each provided ID'
task :create_group_and_add_members, [:ids] => :environment do |_, args|
tenant = ENV['CREATE_GROUPS_FOR'] || 'blc'
switch!(tenant)

ids = args[:ids].split('.').map(&:strip)
ids.each do |id|
CreateGroupAndAddMembersJob.perform_later(id)
end
end
end
end
Loading

0 comments on commit 9bc5bd9

Please sign in to comment.