From a67b5d47716e832b1ee0dfb4238e7a307f8d904c Mon Sep 17 00:00:00 2001 From: dwithana Date: Mon, 22 Jul 2024 14:43:27 -0400 Subject: [PATCH 01/17] Retain user entered playlist item title during playback --- app/assets/javascripts/player_listeners.js | 13 +++++++++---- app/assets/javascripts/ramp_utils.js | 19 ++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/player_listeners.js b/app/assets/javascripts/player_listeners.js index 1db0483b13..fb66f3fceb 100644 --- a/app/assets/javascripts/player_listeners.js +++ b/app/assets/javascripts/player_listeners.js @@ -20,6 +20,7 @@ let addToPlaylistListenersAdded = false; let firstLoad = true; let streamId = ''; let isMobile = false; +let isPlaying = false; /** * Bind action buttons on the page with player events and re-populate details @@ -69,17 +70,21 @@ function addActionButtonListeners(player, mediaObjectId, sectionIds) { } /* Add player event listeners to update UI components on the page */ - // Listen to 'timeupdate' event to udate add to playlist form when using while media is playing or manually seeking - player.player.on('timeupdate', () => { + // Listen to 'seeked' event to udate add to playlist form + player.player.on('seeked', () => { if (getActiveItem() != undefined) { activeTrack = getActiveItem(false); if (activeTrack != undefined) { streamId = activeTrack.streamId; } - disableEnableCurrentTrack(activeTrack, player.player.currentTime(), true, currentSectionLabel); + disableEnableCurrentTrack(activeTrack, player.player.currentTime(), isPlaying, currentSectionLabel); } }); + player.player.on('play', () => { isPlaying = true; }); + + player.player.on('pause', () => { isPlaying = false; }); + /* Disable action buttons tied to player related information on player's 'loadstart' event which functions parallel to the player's src changes. So, that the user doesn't interact with them get corrupted data @@ -273,7 +278,7 @@ function addToPlaylistListeners(sectionIds, mediaObjectId) { disableEnableCurrentTrack( activeTrack, currentTime, - false, + isPlaying, $('#playlist_item_title').val() || currentSectionLabel // Preserve user edits for the title when available ); $('#current-section-name').text(currentSectionLabel); diff --git a/app/assets/javascripts/ramp_utils.js b/app/assets/javascripts/ramp_utils.js index 30fbdefcc8..ea82abd688 100644 --- a/app/assets/javascripts/ramp_utils.js +++ b/app/assets/javascripts/ramp_utils.js @@ -111,7 +111,7 @@ function getTimelineScopes() { if (parent.length === 0) { let begin = 0; let end = activeItem.times.end; - scopes[0].times = { begin: 0, end: end } + scopes[0].times = { begin: 0, end: end }; } while (parent.length > 0) { let next = parent.closest('ul').closest('li'); @@ -209,10 +209,10 @@ function collapseMoreDetails() { * disable the option otherwise enable it. * @param {Object} activeTrack JSON object for the active timespans * @param {Number} currentTime player's playhead position - * @param {Boolean} isSeeked flag to indicate player 'seeked' event happened/not + * @param {Boolean} isPlaying flag to inidicate media is playing or not * @param {String} sectionTitle name of the current section */ -function disableEnableCurrentTrack(activeTrack, currentTime, isSeeked, sectionTitle) { +function disableEnableCurrentTrack(activeTrack, currentTime, isPlaying, sectionTitle) { // Return when add to playlist form is not visible let playlistForm = $('#add_to_playlist')[0]; if (!playlistForm) { @@ -222,7 +222,8 @@ function disableEnableCurrentTrack(activeTrack, currentTime, isSeeked, sectionTi if (activeTrack != undefined) { streamId = activeTrack.streamId; let { label, times, sectionLabel } = activeTrack; - let starttime = currentTime || times.begin; + // Update starttime when media is not playing + let starttime = isPlaying ? times.begin : currentTime || times.begin; $('#playlist_item_start').val(createTimestamp(starttime, true)); $('#playlist_item_end').val(createTimestamp(times.end, true)); title = `${sectionLabel} - ${label}`; @@ -245,9 +246,7 @@ function disableEnableCurrentTrack(activeTrack, currentTime, isSeeked, sectionTi $('#playlistitem_scope_track').prop('checked', false); } } - // Only change the title when user actively seeked to a different timestamp, - // persisting the user changes to the field unless the active track is changed - if (isSeeked && sectionTitle != undefined) { + if (sectionTitle != undefined) { $('#playlist_item_title').val(title); } } @@ -345,4 +344,10 @@ function resetAddToPlaylistForm() { function closeAlert() { $('#add_to_playlist_alert').slideUp(); $('#add_to_playlist_form_group').slideDown(); + // Set default selection in options list when alert is closed + if ($('#playlistitem_scope_track')[0].disabled) { + $('#playlistitem_scope_section').prop('checked', true); + } else { + $('#playlistitem_scope_track').prop('checked', true); + } } From 76f63de49a0ffb92f81cb1770f3edebe39c884f7 Mon Sep 17 00:00:00 2001 From: Chris Colvard Date: Tue, 23 Jul 2024 12:42:20 -0400 Subject: [PATCH 02/17] Speed up section list migration by optimizing solr queries Only return ids since we're only using ids. --- lib/tasks/avalon_migrations.rake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tasks/avalon_migrations.rake b/lib/tasks/avalon_migrations.rake index 3b5bd49cb6..ef68420c79 100644 --- a/lib/tasks/avalon_migrations.rake +++ b/lib/tasks/avalon_migrations.rake @@ -97,7 +97,7 @@ namespace :avalon do task media_object_section_list: :environment do error_ids = [] mo_count = MediaObject.count - ids_to_migrate = ActiveFedora::SolrService.query("has_model_ssim:MediaObject AND NOT section_list_ssim:[* TO *]", rows: mo_count).pluck("id") + ids_to_migrate = ActiveFedora::SolrService.query("has_model_ssim:MediaObject AND NOT section_list_ssim:[* TO *]", fl: [:id], rows: mo_count).pluck("id") puts "Migrating #{ids_to_migrate.size} out of #{mo_count} Media Objects." ids_to_migrate.each do |id| MediaObject.find(id).save!(validate: false) @@ -105,7 +105,7 @@ namespace :avalon do error_ids += [id] puts "Error migrating #{id}: #{error.message}" end - remaining_ids_count = ActiveFedora::SolrService.query("has_model_ssim:MediaObject AND NOT section_list_ssim:[* TO *]", rows: mo_count).size + remaining_ids_count = ActiveFedora::SolrService.query("has_model_ssim:MediaObject AND NOT section_list_ssim:[* TO *]", fl: [:id], rows: mo_count).size if error_ids.size > 0 puts "Migration finished running but #{error_ids.size} Media Objects failed to migrate. Try running the migration again." elsif remaining_ids_count > 0 From c0d5086b4238adbd8075e1ffe3783514e3080dd9 Mon Sep 17 00:00:00 2001 From: Chris Colvard Date: Tue, 23 Jul 2024 14:29:57 -0400 Subject: [PATCH 03/17] Send Referer header when fetching HLS for frame extraction The Referer header is used by the streaming server to do an auth callback. Without this header, the request fails with a 403 Forbidden. --- app/models/master_file.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/master_file.rb b/app/models/master_file.rb index 6f911d0e79..789471d64c 100644 --- a/app/models/master_file.rb +++ b/app/models/master_file.rb @@ -597,7 +597,7 @@ def create_frame_source_hls_temp_file(offset) # Fixes https://github.com/avalonmediasystem/avalon/issues/3474 target_location = File.basename(details[:location]).split('?')[0] target = File.join(Dir.tmpdir, target_location) - File.open(target,'wb') { |f| URI.open(details[:location]) { |io| f.write(io.read) } } + File.open(target,'wb') { |f| URI.open(details[:location], "Referer" => Rails.application.routes.url_helpers.root_url) { |io| f.write(io.read) } } return target, details[:offset] end From e27f03d61c280a6e17d3bc1af1f04b59067964f3 Mon Sep 17 00:00:00 2001 From: dwithana Date: Fri, 26 Jul 2024 16:20:55 -0400 Subject: [PATCH 04/17] New Ramp build to test latest changes --- package.json | 2 +- yarn.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 2dd7ca271c..6a15471a39 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "@babel/plugin-proposal-object-rest-spread": "^7.20.7", "@babel/preset-react": "^7.0.0", "@babel/runtime": "7", - "@samvera/ramp": "https://github.com/samvera-labs/ramp.git#e7d98e06ceceebe7e8ba59af29d1350a3a43acee", + "@samvera/ramp": "https://github.com/samvera-labs/ramp.git#921dcd5d452791df2021ae03f8e57f12c1066503", "babel-plugin-macros": "^3.1.0", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "buffer": "^6.0.3", diff --git a/yarn.lock b/yarn.lock index 92ed2d308c..3e40f2c0bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1477,9 +1477,9 @@ estree-walker "^2.0.2" picomatch "^2.3.1" -"@samvera/ramp@https://github.com/samvera-labs/ramp.git#e7d98e06ceceebe7e8ba59af29d1350a3a43acee": +"@samvera/ramp@https://github.com/samvera-labs/ramp.git#921dcd5d452791df2021ae03f8e57f12c1066503": version "3.1.2" - resolved "https://github.com/samvera-labs/ramp.git#e7d98e06ceceebe7e8ba59af29d1350a3a43acee" + resolved "https://github.com/samvera-labs/ramp.git#921dcd5d452791df2021ae03f8e57f12c1066503" dependencies: "@rollup/plugin-json" "^6.0.1" "@silvermine/videojs-quality-selector" "^1.3.1" @@ -5397,9 +5397,9 @@ postcss-value-parser@^4.2.0: integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== postcss@^8.3.11: - version "8.4.39" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.39.tgz#aa3c94998b61d3a9c259efa51db4b392e1bde0e3" - integrity sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw== + version "8.4.40" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.40.tgz#eb81f2a4dd7668ed869a6db25999e02e9ad909d8" + integrity sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q== dependencies: nanoid "^3.3.7" picocolors "^1.0.1" @@ -6563,9 +6563,9 @@ underscore@1.13.1: integrity sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g== underscore@^1.13.1: - version "1.13.6" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" - integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== + version "1.13.7" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.7.tgz#970e33963af9a7dda228f17ebe8399e5fbe63a10" + integrity sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g== unfetch@^4.2.0: version "4.2.0" From ff0ccb552128c6a00c1e486d1d0e0046b83abb05 Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Fri, 26 Jul 2024 16:43:07 -0400 Subject: [PATCH 05/17] Add setting for alternative rack tempfile directory --- config/application.rb | 3 +++ config/settings.yml | 5 +++++ lib/tempfile_factory.rb | 25 +++++++++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 lib/tempfile_factory.rb diff --git a/config/application.rb b/config/application.rb index 1ecb941c0e..415bfaea1d 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,4 +1,5 @@ require_relative 'boot' +require_relative '../lib/tempfile_factory' require 'rails/all' require 'resolv-replace' @@ -56,6 +57,8 @@ class Application < Rails::Application end end + config.middleware.use TempfileFactory + config.active_storage.service = (Settings&.active_storage&.service.presence || "local").to_sym end end diff --git a/config/settings.yml b/config/settings.yml index ec358e8a0c..53480e0efa 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -123,3 +123,8 @@ derivative: allow_download: true # Maximum size for uploaded files in bytes (default is disabled) #max_upload_size: 2147483648 # Use :none or comment out to disable limit +# Rack Multipart creates temporary files when processing multipart form data with a large payload. +# If the default system /tmp directory is storage constrained, you can define an alternative here. +# Leave commented out to use the system default. +# tempfile: +# location: '' \ No newline at end of file diff --git a/lib/tempfile_factory.rb b/lib/tempfile_factory.rb new file mode 100644 index 0000000000..bff44f481a --- /dev/null +++ b/lib/tempfile_factory.rb @@ -0,0 +1,25 @@ +# Middleware to override the default Rack::Multipart tempfile factory: +# https://www.rubydoc.info/gems/rack/Rack/Multipart/Parser#TEMPFILE_FACTORY-constant +class TempfileFactory + def initialize(app) + @app = app + return unless Settings.tempfile.present? + if Settings.tempfile&.location.present? && File.directory?(Settings.tempfile&.location) && File.writable?(Settings.tempfile&.location) + @tempfile_location = Settings.tempfile.location + else + logger = ActiveSupport::TaggedLogging.new(Logger.new(File.join(Rails.root, 'log', "#{Rails.env}.log"))) + logger.tagged('Rack::Multipart', 'Tempfile').warn "#{Settings.tempfile.location} is not a diretory or not writable. Falling back to #{Dir.tmpdir}." + end + end + + def call(env) + if @tempfile_location + env["rack.multipart.tempfile_factory"] = lambda { |filename, content_type| + extension = ::File.extname(filename.gsub("\0", '%00'))[0, 129] + Tempfile.new(["RackMultipart", extension], @tempfile_location) + } + end + + @app.call(env) + end +end \ No newline at end of file From ce70296279396b77eaf7916464b6b1182a11944f Mon Sep 17 00:00:00 2001 From: cjcolvar Date: Fri, 26 Jul 2024 17:21:04 -0400 Subject: [PATCH 06/17] Ensure that thumbnails show during migration --- app/helpers/application_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index ad555e556c..2f3e8e3c7f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -61,8 +61,8 @@ def lti_share_url_for(obj, _opts = {}) def image_for(document) master_file_id = document["section_id_ssim"].try :first - video_count = document["avalon_resource_type_ssim"].count{|m| m.start_with?('moving image') } rescue 0 - audio_count = document["avalon_resource_type_ssim"].count{|m| m.start_with?('sound recording') } rescue 0 + video_count = document["avalon_resource_type_ssim"].count{|m| m.downcase.start_with?('moving image') } rescue 0 + audio_count = document["avalon_resource_type_ssim"].count{|m| m.downcase.start_with?('sound recording') } rescue 0 if master_file_id if video_count > 0 From 5f0f09dc2195999b5cc24baf7ce72926aecd1b6a Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Mon, 29 Jul 2024 15:26:35 -0400 Subject: [PATCH 07/17] Fix links in footer --- app/assets/stylesheets/avalon/_footer.scss | 2 +- app/assets/stylesheets/avalon/_playlists.scss | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/avalon/_footer.scss b/app/assets/stylesheets/avalon/_footer.scss index 5313f9a3f9..413099019c 100644 --- a/app/assets/stylesheets/avalon/_footer.scss +++ b/app/assets/stylesheets/avalon/_footer.scss @@ -21,7 +21,7 @@ width: 100%; min-height: 2.5rem; color: #999999; - z-index: -1000; + z-index: 0; } footer { diff --git a/app/assets/stylesheets/avalon/_playlists.scss b/app/assets/stylesheets/avalon/_playlists.scss index f0d6e5468b..485fb18c1b 100644 --- a/app/assets/stylesheets/avalon/_playlists.scss +++ b/app/assets/stylesheets/avalon/_playlists.scss @@ -176,6 +176,7 @@ #Playlists_paginate { float: right; + z-index: 10; } /* Playlist copy modal */ From 2b55f5ea4d5371357a731e84e5a0851e52e4cc9b Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Tue, 30 Jul 2024 12:30:52 -0400 Subject: [PATCH 08/17] Add tests --- config/settings.yml | 2 +- spec/lib/tempfile_factory_spec.rb | 61 +++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 spec/lib/tempfile_factory_spec.rb diff --git a/config/settings.yml b/config/settings.yml index 53480e0efa..5b5b039022 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -127,4 +127,4 @@ derivative: # If the default system /tmp directory is storage constrained, you can define an alternative here. # Leave commented out to use the system default. # tempfile: -# location: '' \ No newline at end of file +# location: '/tmp' \ No newline at end of file diff --git a/spec/lib/tempfile_factory_spec.rb b/spec/lib/tempfile_factory_spec.rb new file mode 100644 index 0000000000..274903cd49 --- /dev/null +++ b/spec/lib/tempfile_factory_spec.rb @@ -0,0 +1,61 @@ +# Copyright 2011-2024, The Trustees of Indiana University and Northwestern +# University. Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# --- END LICENSE_HEADER BLOCK --- + +require 'rails_helper' + +describe TempfileFactory do + class MockApp + def call(env) + [200, {}, env] + end + end + + let(:file) { Rack::Test::UploadedFile.new(Rails.root.join('spec', 'fixtures', 'videoshort.mp4'), 'video/mp4')} + let(:env) { Rack::MockRequest.env_for('/', method: :post, params: file) } + let(:app) { MockApp.new } + subject { TempfileFactory.new(app) } + + context "when an alternate directory is defined" do + it "sets `env['rack.multipart.tempfile_factory']`" do + without_partial_double_verification do + allow(Settings.tempfile).to receive(:location).and_return(Rails.root.join('spec', 'fixtures').to_s) + subject.instance_variable_set(:@tempfile_location, Settings.tempfile.location) + status, headers, response = subject.call(env) + expect(response).to include 'rack.multipart.tempfile_factory' + end + end + end + + context "when an alternate directory is NOT defined" do + it "does not set `env['rack.multipart.tempfile_factory']`" do + without_partial_double_verification do + allow(Settings.tempfile).to receive(:location).and_return(nil) + subject.instance_variable_set(:@tempfile_location, Settings.tempfile.location) + status, headers, response = subject.call(env) + expect(response).to_not include 'rack.multipart.tempfile_factory' + end + end + end + + context "when there is a problem with the defined alternate directory" do + it "does not set `env['rack.multipart.tempfile_factory']`" do + without_partial_double_verification do + allow(Settings.tempfile).to receive(:location).and_return('does not exist') + subject.instance_variable_set(:@tempfile_loaction, Settings.tempfile.location) + status, headers, response = subject.call(env) + expect(response).to_not include 'rack.multipart.tempfile_factory' + end + end + end +end \ No newline at end of file From 971fc222d9a9150dd2700d9e13fd250cf442dcb7 Mon Sep 17 00:00:00 2001 From: dwithana Date: Tue, 30 Jul 2024 13:00:26 -0400 Subject: [PATCH 09/17] Add margin along x-axis to playlist description and tags display in mobile view --- app/javascript/components/PlaylistRamp.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/components/PlaylistRamp.jsx b/app/javascript/components/PlaylistRamp.jsx index 2efd8af22b..fb8e5927e9 100644 --- a/app/javascript/components/PlaylistRamp.jsx +++ b/app/javascript/components/PlaylistRamp.jsx @@ -223,7 +223,7 @@ const Ramp = ({ - + {comment && (

{comment_label}

@@ -251,7 +251,7 @@ const Ramp = ({ {playlist_item_ids?.length > 0 && ( -

Playlist Items

+

Playlist Items

)} From 0841be6b4ccfbf8be0b76e02272f93a2946d1ae2 Mon Sep 17 00:00:00 2001 From: cjcolvar Date: Tue, 30 Jul 2024 15:02:43 -0400 Subject: [PATCH 10/17] Use current_ability instead of rebuilding ability which involves solr query --- app/controllers/catalog_controller.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/controllers/catalog_controller.rb b/app/controllers/catalog_controller.rb index 948342619a..3371bbb4db 100644 --- a/app/controllers/catalog_controller.rb +++ b/app/controllers/catalog_controller.rb @@ -89,18 +89,18 @@ class CatalogController < ApplicationController config.add_facet_field 'unit_ssim', label: 'Unit', limit: 5 config.add_facet_field 'language_ssim', label: 'Language', limit: 5 # Hide these facets if not a Collection Manager - config.add_facet_field 'workflow_published_sim', label: 'Published', limit: 5, if: Proc.new {|context, config, opts| Ability.new(context.current_user, context.user_session).can? :create, MediaObject}, group: "workflow" - config.add_facet_field 'avalon_uploader_ssi', label: 'Created by', limit: 5, if: Proc.new {|context, config, opts| Ability.new(context.current_user, context.user_session).can? :create, MediaObject}, group: "workflow" - config.add_facet_field 'read_access_group_ssim', label: 'Item access', if: Proc.new {|context, config, opts| Ability.new(context.current_user, context.user_session).can? :create, MediaObject}, group: "workflow", query: { + config.add_facet_field 'workflow_published_sim', label: 'Published', limit: 5, if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow" + config.add_facet_field 'avalon_uploader_ssi', label: 'Created by', limit: 5, if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow" + config.add_facet_field 'read_access_group_ssim', label: 'Item access', if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow", query: { public: { label: "Public", fq: "has_model_ssim:MediaObject AND read_access_group_ssim:#{Hydra::AccessControls::AccessRight::PERMISSION_TEXT_VALUE_PUBLIC}" }, restricted: { label: "Authenticated", fq: "has_model_ssim:MediaObject AND read_access_group_ssim:#{Hydra::AccessControls::AccessRight::PERMISSION_TEXT_VALUE_AUTHENTICATED}" }, private: { label: "Private", fq: "has_model_ssim:MediaObject AND NOT read_access_group_ssim:#{Hydra::AccessControls::AccessRight::PERMISSION_TEXT_VALUE_PUBLIC} AND NOT read_access_group_ssim:#{Hydra::AccessControls::AccessRight::PERMISSION_TEXT_VALUE_AUTHENTICATED}" } } - config.add_facet_field 'read_access_virtual_group_ssim', label: 'External Group', limit: 5, if: Proc.new {|context, config, opts| Ability.new(context.current_user, context.user_session).can? :create, MediaObject}, group: "workflow", helper_method: :vgroup_display - config.add_facet_field 'date_digitized_ssim', label: 'Date Digitized', limit: 5, if: Proc.new {|context, config, opts| Ability.new(context.current_user, context.user_session).can? :create, MediaObject}, group: "workflow"#, partial: 'blacklight/hierarchy/facet_hierarchy' - config.add_facet_field 'date_ingested_ssim', label: 'Date Ingested', limit: 5, if: Proc.new {|context, config, opts| Ability.new(context.current_user, context.user_session).can? :create, MediaObject}, group: "workflow" - config.add_facet_field 'has_captions_bsi', label: 'Has Captions', if: Proc.new {|context, config, opts| Ability.new(context.current_user, context.user_session).can? :create, MediaObject}, group: "workflow", helper_method: :display_has_caption_or_transcript - config.add_facet_field 'has_transcripts_bsi', label: 'Has Transcripts', if: Proc.new {|context, config, opts| Ability.new(context.current_user, context.user_session).can? :create, MediaObject}, group: "workflow", helper_method: :display_has_caption_or_transcript + config.add_facet_field 'read_access_virtual_group_ssim', label: 'External Group', limit: 5, if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow", helper_method: :vgroup_display + config.add_facet_field 'date_digitized_ssim', label: 'Date Digitized', limit: 5, if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow"#, partial: 'blacklight/hierarchy/facet_hierarchy' + config.add_facet_field 'date_ingested_ssim', label: 'Date Ingested', limit: 5, if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow" + config.add_facet_field 'has_captions_bsi', label: 'Has Captions', if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow", helper_method: :display_has_caption_or_transcript + config.add_facet_field 'has_transcripts_bsi', label: 'Has Transcripts', if: Proc.new {|context, config, opts| context.current_ability.can? :create, MediaObject}, group: "workflow", helper_method: :display_has_caption_or_transcript # Have BL send all facet field names to Solr, which has been the default # previously. Simply remove these lines if you'd rather use Solr request From 9a79d5a29e230719bfa87f3cfedf9603f864e73f Mon Sep 17 00:00:00 2001 From: cjcolvar Date: Tue, 30 Jul 2024 15:03:35 -0400 Subject: [PATCH 11/17] Use solr doc to build proxy instead of fetching a new proxy from solr --- app/helpers/blacklight/local_blacklight_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/blacklight/local_blacklight_helper.rb b/app/helpers/blacklight/local_blacklight_helper.rb index ecc2e18ca6..c11b38074e 100644 --- a/app/helpers/blacklight/local_blacklight_helper.rb +++ b/app/helpers/blacklight/local_blacklight_helper.rb @@ -26,7 +26,7 @@ def facet_group_names end def url_for_document doc, options = {} - SpeedyAF::Base.find(doc[:id]) + SpeedyAF::Base.for(doc.to_h.with_indifferent_access) end def contributor_index_display args From 788be765a76434d6ebe58f08d2a88434919a8bc7 Mon Sep 17 00:00:00 2001 From: dwithana Date: Tue, 30 Jul 2024 15:43:59 -0400 Subject: [PATCH 12/17] Ramp 3.2.0 bump --- package.json | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 6a15471a39..4f5aa1441a 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "@babel/plugin-proposal-object-rest-spread": "^7.20.7", "@babel/preset-react": "^7.0.0", "@babel/runtime": "7", - "@samvera/ramp": "https://github.com/samvera-labs/ramp.git#921dcd5d452791df2021ae03f8e57f12c1066503", + "@samvera/ramp": "^3.2.0", "babel-plugin-macros": "^3.1.0", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "buffer": "^6.0.3", diff --git a/yarn.lock b/yarn.lock index 3e40f2c0bd..c90b3fa9c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1477,9 +1477,10 @@ estree-walker "^2.0.2" picomatch "^2.3.1" -"@samvera/ramp@https://github.com/samvera-labs/ramp.git#921dcd5d452791df2021ae03f8e57f12c1066503": - version "3.1.2" - resolved "https://github.com/samvera-labs/ramp.git#921dcd5d452791df2021ae03f8e57f12c1066503" +"@samvera/ramp@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@samvera/ramp/-/ramp-3.2.0.tgz#c281a6a2970e1bf6def1e2cabc5822ca22409261" + integrity sha512-lJUvPQETvYHWKW8GnO4XmP3D/JUZNNXELm9bDlOEv0PaQIs28FkSozbh/gQlNWh6DCBWUtV6qnG0ZlkNIH+qwQ== dependencies: "@rollup/plugin-json" "^6.0.1" "@silvermine/videojs-quality-selector" "^1.3.1" From 95b6200d44d08f872a0a171b2944035c9e46b27b Mon Sep 17 00:00:00 2001 From: cjcolvar Date: Wed, 31 Jul 2024 11:05:13 -0400 Subject: [PATCH 13/17] Testing improvements Co-authored-by: Mason Ballengee --- lib/tempfile_factory.rb | 11 ++++-- spec/lib/tempfile_factory_spec.rb | 58 ++++++++++++++++++++----------- 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/lib/tempfile_factory.rb b/lib/tempfile_factory.rb index bff44f481a..f1efde5caa 100644 --- a/lib/tempfile_factory.rb +++ b/lib/tempfile_factory.rb @@ -7,8 +7,7 @@ def initialize(app) if Settings.tempfile&.location.present? && File.directory?(Settings.tempfile&.location) && File.writable?(Settings.tempfile&.location) @tempfile_location = Settings.tempfile.location else - logger = ActiveSupport::TaggedLogging.new(Logger.new(File.join(Rails.root, 'log', "#{Rails.env}.log"))) - logger.tagged('Rack::Multipart', 'Tempfile').warn "#{Settings.tempfile.location} is not a diretory or not writable. Falling back to #{Dir.tmpdir}." + logger.warn("[Rack::Multipart] [Tempfile] #{Settings.tempfile.location} is not a diretory or not writable. Falling back to #{Dir.tmpdir}.") end end @@ -22,4 +21,10 @@ def call(env) @app.call(env) end -end \ No newline at end of file + + private + + def logger + @logger ||= Logger.new(File.join(Rails.root, 'log', "#{Rails.env}.log")) + end +end diff --git a/spec/lib/tempfile_factory_spec.rb b/spec/lib/tempfile_factory_spec.rb index 274903cd49..2360592055 100644 --- a/spec/lib/tempfile_factory_spec.rb +++ b/spec/lib/tempfile_factory_spec.rb @@ -21,41 +21,59 @@ def call(env) end end - let(:file) { Rack::Test::UploadedFile.new(Rails.root.join('spec', 'fixtures', 'videoshort.mp4'), 'video/mp4')} + let(:file) { fixture_file_upload("videoshort.mp4", "video/mp4") } let(:env) { Rack::MockRequest.env_for('/', method: :post, params: file) } let(:app) { MockApp.new } subject { TempfileFactory.new(app) } + around do |example| + @old_config = Settings.dig(:tempfile, :location) + if tempfile_config.present? + Settings.tempfile ||= Config::Options.new + Settings.tempfile.location = tempfile_config + else + Settings.tempfile = nil + end + example.run + if @old_config.present? + Settings.tempfile ||= Config::Options.new + Settings.tempfile.location = @old_config + else + Settings.tempfile = nil + end + end + context "when an alternate directory is defined" do + let(:tempfile_config) { Rails.root.join('spec', 'fixtures').to_s } + it "sets `env['rack.multipart.tempfile_factory']`" do - without_partial_double_verification do - allow(Settings.tempfile).to receive(:location).and_return(Rails.root.join('spec', 'fixtures').to_s) - subject.instance_variable_set(:@tempfile_location, Settings.tempfile.location) - status, headers, response = subject.call(env) - expect(response).to include 'rack.multipart.tempfile_factory' - end + status, headers, response = subject.call(env) + expect(response).to include 'rack.multipart.tempfile_factory' + expect(response['rack.multipart.tempfile_factory'].call("videoshort.mp4", "video/mp4").path).to start_with(tempfile_config) end end context "when an alternate directory is NOT defined" do + let(:tempfile_config) { nil } + it "does not set `env['rack.multipart.tempfile_factory']`" do - without_partial_double_verification do - allow(Settings.tempfile).to receive(:location).and_return(nil) - subject.instance_variable_set(:@tempfile_location, Settings.tempfile.location) - status, headers, response = subject.call(env) - expect(response).to_not include 'rack.multipart.tempfile_factory' - end + status, headers, response = subject.call(env) + expect(response).to_not include 'rack.multipart.tempfile_factory' end end context "when there is a problem with the defined alternate directory" do + let(:tempfile_config) { 'does not exist' } + let(:logger) { double() } + + before do + allow_any_instance_of(TempfileFactory).to receive(:logger).and_return(logger) + end + it "does not set `env['rack.multipart.tempfile_factory']`" do - without_partial_double_verification do - allow(Settings.tempfile).to receive(:location).and_return('does not exist') - subject.instance_variable_set(:@tempfile_loaction, Settings.tempfile.location) - status, headers, response = subject.call(env) - expect(response).to_not include 'rack.multipart.tempfile_factory' - end + expect(logger).to receive(:warn).with(match("Falling back")) + status, headers, response = subject.call(env) + expect(response).to_not include 'rack.multipart.tempfile_factory' end end -end \ No newline at end of file +end From dd32dd9317524f4bd45fe08e1387a38fd7137a43 Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Wed, 31 Jul 2024 11:48:45 -0400 Subject: [PATCH 14/17] Move TempfileFactory to start of middleware stack Co-authored-by: Chris Colvard --- config/application.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/application.rb b/config/application.rb index 415bfaea1d..475af9a286 100644 --- a/config/application.rb +++ b/config/application.rb @@ -57,7 +57,7 @@ class Application < Rails::Application end end - config.middleware.use TempfileFactory + config.middleware.insert_before 0, TempfileFactory config.active_storage.service = (Settings&.active_storage&.service.presence || "local").to_sym end From 830a5dd76727a3f0d01641a35e7924cc3b7aa6e5 Mon Sep 17 00:00:00 2001 From: Chris Colvard Date: Wed, 31 Jul 2024 15:02:14 -0400 Subject: [PATCH 15/17] Ensure single value for statement of responsibility matching solr field This fixes an edge case where bib import may bring in multiple values. --- app/models/mods_behaviors.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/mods_behaviors.rb b/app/models/mods_behaviors.rb index 9d0d0d6edf..4ba32746ba 100644 --- a/app/models/mods_behaviors.rb +++ b/app/models/mods_behaviors.rb @@ -82,7 +82,7 @@ def to_solr(solr_doc = Hash.new, opts = {}) solr_doc['other_identifier_sim'] = gather_terms(self.find_by_terms(:other_identifier)) solr_doc['bibliographic_id_ssi'] = self.bibliographic_id.first solr_doc['bibliographic_id_source_ssi'] = self.bibliographic_id.source.first - solr_doc['statement_of_responsibility_ssi'] = gather_terms(self.find_by_terms(:statement_of_responsibility)) + solr_doc['statement_of_responsibility_ssi'] = gather_terms(self.find_by_terms(:statement_of_responsibility)).first solr_doc['record_identifier_ssim'] = gather_terms(self.find_by_terms(:record_identifier)) # Extract 4-digit year for creation date facet in Hydra and pub_date facet in Blacklight From 50615eca2ccf480349ffdddb66a53b4c3b9959d6 Mon Sep 17 00:00:00 2001 From: Chris Colvard Date: Wed, 31 Jul 2024 15:39:50 -0400 Subject: [PATCH 16/17] Get rid of nil ids when migrating to section lists This fixes an edge case where ordered_master_file_ids contain nils. --- app/models/media_object.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/media_object.rb b/app/models/media_object.rb index e2030cd738..c1eb6aa305 100644 --- a/app/models/media_object.rb +++ b/app/models/media_object.rb @@ -177,7 +177,7 @@ def section_ids return @section_ids if @section_ids # Do migration - self.section_ids = self.ordered_master_file_ids if self.section_list.nil? + self.section_ids = self.ordered_master_file_ids.compact if self.section_list.nil? return [] if self.section_list.nil? @section_ids = JSON.parse(self.section_list) From d5c56e3c701ea74451b44a1323f2bb9c0049625e Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Thu, 1 Aug 2024 15:37:33 -0400 Subject: [PATCH 17/17] Skip transcript search if there are curly brackets in query --- app/models/search_builder.rb | 1 + spec/controllers/catalog_controller_spec.rb | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/app/models/search_builder.rb b/app/models/search_builder.rb index 014ba8bf84..6b02d50579 100644 --- a/app/models/search_builder.rb +++ b/app/models/search_builder.rb @@ -51,6 +51,7 @@ def search_section_transcripts(solr_parameters) return unless solr_parameters[:q].present? && SupplementalFile.with_tag('transcript').any? && !(blacklight_params[:controller] == 'bookmarks') terms = solr_parameters[:q].split + return if terms.any? { |term| term.match?(/[\{\}]/) } term_subquery = terms.map { |term| "transcript_tsim:#{RSolr.solr_escape(term)}" }.join(" OR ") solr_parameters[:defType] = "lucene" solr_parameters[:q] = "({!edismax v=\"#{RSolr.solr_escape(solr_parameters[:q])}\"}) {!join to=id from=isPartOf_ssim}{!join to=id from=isPartOf_ssim}#{term_subquery}" diff --git a/spec/controllers/catalog_controller_spec.rb b/spec/controllers/catalog_controller_spec.rb index 296d584b24..4ad1437206 100644 --- a/spec/controllers/catalog_controller_spec.rb +++ b/spec/controllers/catalog_controller_spec.rb @@ -308,6 +308,22 @@ expect(assigns(:response).documents.count).to eq 2 expect(assigns(:response).documents.map(&:id)).to match_array [private_media_object.id, public_media_object.id] end + context "when there are transcripts present" do + let!(:transcript) { FactoryBot.create(:supplemental_file, :with_transcript_file, :with_transcript_tag, parent_id: private_media_object.id) } + + before do + private_media_object.supplemental_files = [transcript] + private_media_object.save + end + + it "should not error when search includes curly brackets" do + request.headers['Avalon-Api-Key'] = 'secret_token' + get 'index', params: { q: '{8}', format: 'atom' } + expect(response).to be_successful + expect(response).to render_template('catalog/index') + expect(response.content_type).to eq "application/atom+xml; charset=utf-8" + end + end end context "without api key" do it "should not show results" do