From a1570e6d693726edb2deafd2561b9aea5af263a7 Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Wed, 21 Feb 2024 10:22:49 -0500 Subject: [PATCH 01/33] Support positional URLs for playlist items --- Gemfile | 2 +- Gemfile.lock | 12 ++- app/controllers/playlists_controller.rb | 5 +- app/javascript/components/PlaylistRamp.jsx | 17 +++- app/models/iiif_playlist_canvas_presenter.rb | 13 +++- app/views/playlists/show.html.erb | 3 +- spec/controllers/playlists_controller_spec.rb | 77 +++++++++++-------- .../iiif_playlist_canvas_presenter_spec.rb | 14 +++- 8 files changed, 98 insertions(+), 45 deletions(-) diff --git a/Gemfile b/Gemfile index 72503fe418..c4a93d60a5 100644 --- a/Gemfile +++ b/Gemfile @@ -53,7 +53,7 @@ gem 'avalon-about', git: 'https://github.com/avalonmediasystem/avalon-about.git' #gem 'bootstrap-sass', '< 3.4.1' # Pin to less than 3.4.1 due to change in behavior with popovers gem 'bootstrap-toggle-rails' gem 'bootstrap_form' -gem 'iiif_manifest', '>= 1.4.0' +gem 'iiif_manifest', git: 'https://github.com/samvera/iiif_manifest.git', branch: 'canvas-homepage' gem 'rack-cors', require: 'rack/cors' gem 'rails_same_site_cookie' gem 'recaptcha', require: 'recaptcha/rails' diff --git a/Gemfile.lock b/Gemfile.lock index fc57de7f85..df5c2a3dc7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -65,6 +65,14 @@ GIT ims-lti omniauth +GIT + remote: https://github.com/samvera/iiif_manifest.git + revision: e7aea3ab9614f645ccdb760f78954a7ef2cd893c + branch: canvas-homepage + specs: + iiif_manifest (1.4.0) + activesupport (>= 4) + GEM remote: https://rubygems.org/ specs: @@ -494,8 +502,6 @@ GEM i18n (1.14.1) concurrent-ruby (~> 1.0) iconv (1.0.8) - iiif_manifest (1.4.0) - activesupport (>= 4) ims-lti (1.1.13) builder oauth (>= 0.4.5, < 0.6) @@ -1037,7 +1043,7 @@ DEPENDENCIES httpx hydra-head (~> 12.0) iconv (~> 1.0.6) - iiif_manifest (>= 1.4.0) + iiif_manifest! ims-lti (~> 1.1.13) jbuilder (~> 2.0) jquery-datatables diff --git a/app/controllers/playlists_controller.rb b/app/controllers/playlists_controller.rb index 6b1baed16c..6084394829 100644 --- a/app/controllers/playlists_controller.rb +++ b/app/controllers/playlists_controller.rb @@ -260,10 +260,11 @@ def manifest # Condense secure_streams into single call using master_files stream_info_hash = secure_stream_infos(master_files, media_objects) - canvas_presenters = @playlist.items.collect do |item| + canvas_presenters = @playlist.items.collect.with_index do |item, i| master_file = master_files.find { |mf| mf.id == item.clip.master_file_id } cannot_read_item = master_file.nil? || cannot_read_hash[master_file.media_object_id] - IiifPlaylistCanvasPresenter.new(playlist_item: item, stream_info: stream_info_hash[master_file&.id], cannot_read_item: cannot_read_item, master_file: master_file) + position = i + 1 + IiifPlaylistCanvasPresenter.new(playlist_item: item, stream_info: stream_info_hash[master_file&.id], cannot_read_item: cannot_read_item, position: position, master_file: master_file) end can_edit_playlist = can? :edit, @playlist diff --git a/app/javascript/components/PlaylistRamp.jsx b/app/javascript/components/PlaylistRamp.jsx index 7ba3870a35..78fe26ebe6 100644 --- a/app/javascript/components/PlaylistRamp.jsx +++ b/app/javascript/components/PlaylistRamp.jsx @@ -39,8 +39,9 @@ const ExpandCollapseArrow = () => { }; const Ramp = ({ - base_url, + urls, playlist_id, + playlist_item_ids, token, share, comment_tag @@ -48,6 +49,7 @@ const Ramp = ({ const [manifestUrl, setManifestUrl] = React.useState(''); const [activeItemTitle, setActiveItemTitle] = React.useState(); const [activeItemSummary, setActiveItemSummary] = React.useState(); + const [startCanvasId, setStartCanvasId] = React.useState(); let interval; @@ -55,8 +57,17 @@ const Ramp = ({ const IS_MOBILE = (/Mobi/i).test(USER_AGENT); React.useEffect(() => { + const { base_url, fullpath_url } = urls; let url = `${base_url}/playlists/${playlist_id}/manifest.json`; if (token) url += `?token=${token}`; + + let [fullpath, position] = fullpath_url.split('?position='); + let start_canvas = playlist_item_ids[position - 1] + setStartCanvasId( + start_canvas && start_canvas != undefined + ? `${base_url}/playlists/${playlist_id}/manifest/canvas/${start_canvas}` + : undefined + ); setManifestUrl(url); interval = setInterval(addPlayerEventListeners, 500); @@ -84,7 +95,9 @@ const Ramp = ({ }; return ( - + diff --git a/app/models/iiif_playlist_canvas_presenter.rb b/app/models/iiif_playlist_canvas_presenter.rb index e37cb37b93..e4bc565a49 100644 --- a/app/models/iiif_playlist_canvas_presenter.rb +++ b/app/models/iiif_playlist_canvas_presenter.rb @@ -13,13 +13,14 @@ # --- END LICENSE_HEADER BLOCK --- class IiifPlaylistCanvasPresenter - attr_reader :playlist_item, :stream_info, :cannot_read_item + attr_reader :playlist_item, :stream_info, :cannot_read_item, :position attr_accessor :media_fragment - def initialize(playlist_item:, stream_info:, cannot_read_item: false, media_fragment: nil, master_file: nil) + def initialize(playlist_item:, stream_info:, cannot_read_item: false, position: nil, media_fragment: nil, master_file: nil) @playlist_item = playlist_item @stream_info = stream_info @cannot_read_item = cannot_read_item + @position = position @media_fragment = media_fragment @master_file = master_file end @@ -97,6 +98,14 @@ def description playlist_item.comment end + def homepage + [{ + "@id" => "#{Rails.application.routes.url_helpers.playlist_url(playlist_item.playlist_id).to_s}?position=#{position}", + "type" => "Text", + "label" => "Playlist Item #{position}" + }] + end + private def playlist_source_link diff --git a/app/views/playlists/show.html.erb b/app/views/playlists/show.html.erb index 49ba451f6d..5b72257141 100644 --- a/app/views/playlists/show.html.erb +++ b/app/views/playlists/show.html.erb @@ -30,8 +30,9 @@ Unless required by applicable law or agreed to in writing, software distributed
<%= react_component("PlaylistRamp", { - base_url: request.protocol+request.host_with_port, + urls: { base_url: request.protocol+request.host_with_port, fullpath_url: request.fullpath }, playlist_id: @playlist.id, + playlist_item_ids: @playlist.item_ids, token: @playlist_token, share: { canShare: (will_partial_list_render? :share), content: render('share') }, comment_tag: { content: render('comments_and_tags') } diff --git a/spec/controllers/playlists_controller_spec.rb b/spec/controllers/playlists_controller_spec.rb index fee3be1139..64e47df3fb 100644 --- a/spec/controllers/playlists_controller_spec.rb +++ b/spec/controllers/playlists_controller_spec.rb @@ -558,8 +558,9 @@ end describe "GET #manifest" do - let(:playlist) { FactoryBot.create(:playlist, items: [playlist_item], visibility: Playlist::PUBLIC) } + let(:playlist) { FactoryBot.create(:playlist, items: [playlist_item, playlist_item_2], visibility: Playlist::PUBLIC) } let(:playlist_item) { FactoryBot.create(:playlist_item, clip: clip) } + let(:playlist_item_2) { FactoryBot.create(:playlist_item, clip: clip) } let(:clip) { FactoryBot.create(:avalon_clip, master_file: master_file) } let(:master_file) { FactoryBot.create(:master_file, :with_derivative, media_object: media_object) } let(:media_object) { FactoryBot.create(:published_media_object, visibility: 'public') } @@ -573,10 +574,48 @@ expect(parsed_response['items']).not_to be_empty end - it "contains metadata about the playlist item's parent media obejct" do - get :manifest, format: 'json', params: { id: playlist.id}, session: valid_session - parsed_response = JSON.parse(response.body) - expect(parsed_response['items'][0]['metadata']).to be_present + context "playlist item" do + it "contains metadata about the playlist item's parent media obejct" do + get :manifest, format: 'json', params: { id: playlist.id }, session: valid_session + parsed_response = JSON.parse(response.body) + expect(parsed_response['items'][0]['metadata']).to be_present + end + + it "contains a homepage with the playlist item's positional URL" do + get :manifest, format: 'json', params:{ id: playlist.id }, session: valid_session + parsed_response = JSON.parse(response.body) + expect(parsed_response['items'][0]['homepage']).to be_present + expect(parsed_response['items'][0]['homepage'][0]['id']).to eq "#{Rails.application.routes.url_helpers.playlist_url(playlist.id)}?position=1" + expect(parsed_response['items'][1]['homepage']).to be_present + expect(parsed_response['items'][1]['homepage'][0]['id']).to eq "#{Rails.application.routes.url_helpers.playlist_url(playlist.id)}?position=2" + end + + context "with deleted source" do + before do + master_file.delete + end + + it "returns a blank canvas" do + get :manifest, format: 'json', params: { id: playlist.id }, session: valid_session + parsed_response = JSON.parse(response.body) + expect(parsed_response['items'][0]['items'][0].keys).to_not include 'items' + end + end + end + + context "playlist item auth" do + let(:playlist_item_2) { FactoryBot.create(:playlist_item, clip: clip_2) } + let(:clip_2) { FactoryBot.create(:avalon_clip, master_file: master_file_2) } + let(:master_file_2) { FactoryBot.create(:master_file, :with_derivative, media_object: media_object_2) } + let(:media_object_2) { FactoryBot.create(:published_media_object, visibility: 'restricted') } + + it "returns populated canvas for public item and blank canvas for restricted item" do + get :manifest, format: 'json', params: { id: playlist.id }, session: valid_session + parsed_response = JSON.parse(response.body) + expect(parsed_response['items'].length).to eq 2 + expect(parsed_response['items'][0]['items'][0].keys).to include 'items' + expect(parsed_response['items'][1]['items'][0].keys).to_not include 'items' + end end context "when playlist is empty" do @@ -611,33 +650,5 @@ expect(parsed_response["service"]).not_to be_present end end - - context "playlist item auth" do - let(:playlist) { FactoryBot.create(:playlist, items: [playlist_item, playlist_item_2], visibility: Playlist::PUBLIC) } - let(:playlist_item_2) { FactoryBot.create(:playlist_item, clip: clip_2) } - let(:clip_2) { FactoryBot.create(:avalon_clip, master_file: master_file_2) } - let(:master_file_2) { FactoryBot.create(:master_file, :with_derivative, media_object: media_object_2) } - let(:media_object_2) { FactoryBot.create(:published_media_object, visibility: 'restricted') } - - it "returns populated canvas for public item and blank canvas for restricted item" do - get :manifest, format: 'json', params: { id: playlist.id }, session: valid_session - parsed_response = JSON.parse(response.body) - expect(parsed_response['items'].length).to eq 2 - expect(parsed_response['items'][0]['items'][0].keys).to include 'items' - expect(parsed_response['items'][1]['items'][0].keys).to_not include 'items' - end - end - - context "playlist item with deleted source" do - before do - master_file.delete - end - - it "returns a blank canvas" do - get :manifest, format: 'json', params: { id: playlist.id }, session: valid_session - parsed_response = JSON.parse(response.body) - expect(parsed_response['items'][0]['items'][0].keys).to_not include 'items' - end - end end end diff --git a/spec/models/iiif_playlist_canvas_presenter_spec.rb b/spec/models/iiif_playlist_canvas_presenter_spec.rb index 4a09934629..a4aaeb4698 100644 --- a/spec/models/iiif_playlist_canvas_presenter_spec.rb +++ b/spec/models/iiif_playlist_canvas_presenter_spec.rb @@ -21,7 +21,7 @@ let(:playlist_item) { FactoryBot.build(:playlist_item, clip: playlist_clip) } let(:playlist_clip) { FactoryBot.build(:avalon_clip, master_file: master_file) } let(:stream_info) { master_file.stream_details } - let(:presenter) { described_class.new(playlist_item: playlist_item, stream_info: stream_info) } + let(:presenter) { described_class.new(playlist_item: playlist_item, stream_info: stream_info, position: 1) } describe '#to_s' do it 'returns the playlist_item label' do @@ -292,4 +292,16 @@ expect(subject).to eq playlist_item.comment end end + + describe "#homepage" do + subject { presenter.homepage } + let(:playlist_item) { FactoryBot.create(:playlist_item, clip: playlist_clip) } + let(:playlist_clip) { FactoryBot.create(:avalon_clip, master_file: master_file) } + + it "it references the item's position within the playlist" do + expect(subject.first['@id']).to eq "#{Rails.application.routes.url_helpers.playlist_url(playlist_item.playlist_id)}?position=1" + expect(subject.first['label']).to be_present + expect(subject.first['type']).to be_present + end + end end From c2e91892b8f446301ea7c95cf46a90720591daa7 Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Wed, 21 Feb 2024 13:25:21 -0500 Subject: [PATCH 02/33] Use website_url as label when website_label is not present --- app/assets/stylesheets/nestable.scss | 18 +++++++++--------- app/presenters/collection_presenter.rb | 3 ++- spec/presenters/collection_presenter_spec.rb | 16 +++++++++++++++- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/app/assets/stylesheets/nestable.scss b/app/assets/stylesheets/nestable.scss index 915400514f..af88ba01ba 100644 --- a/app/assets/stylesheets/nestable.scss +++ b/app/assets/stylesheets/nestable.scss @@ -62,9 +62,9 @@ tr.dd-item { font-weight: bold; border: 1px solid #ccc; background: #fafafa; - background: -webkit-linear-gradient(top, #fafafa 0%, #eee 100%); - background: -moz-linear-gradient(top, #fafafa 0%, #eee 100%); - background: linear-gradient(top, #fafafa 0%, #eee 100%); + background: -webkit-linear-gradient(to bottom, #fafafa 0%, #eee 100%); + background: -moz-linear-gradient(to bottom, #fafafa 0%, #eee 100%); + background: linear-gradient(to bottom, #fafafa 0%, #eee 100%); -webkit-border-radius: 3px; border-radius: 3px; box-sizing: border-box; -moz-box-sizing: border-box; @@ -114,9 +114,9 @@ tr.dd-item { .dd3-content { display: block; margin: 5px 0; padding: 0 0 0 30px; background: #fafafa; - background: -webkit-linear-gradient(top, #fafafa 0%, #eee 100%); - background: -moz-linear-gradient(top, #fafafa 0%, #eee 100%); - background: linear-gradient(top, #fafafa 0%, #eee 100%); + background: -webkit-linear-gradient(to bottom, #fafafa 0%, #eee 100%); + background: -moz-linear-gradient(to bottom, #fafafa 0%, #eee 100%); + background: linear-gradient(to bottom, #fafafa 0%, #eee 100%); -webkit-border-radius: 3px; border-radius: 3px; box-sizing: border-box; -moz-box-sizing: border-box; @@ -135,9 +135,9 @@ tr.dd-item { white-space: nowrap; overflow: hidden; border: 1px solid #aaa; background: #ddd; - background: -webkit-linear-gradient(top, #ddd 0%, #bbb 100%); - background: -moz-linear-gradient(top, #ddd 0%, #bbb 100%); - background: linear-gradient(top, #ddd 0%, #bbb 100%); + background: -webkit-linear-gradient(to bottom, #ddd 0%, #bbb 100%); + background: -moz-linear-gradient(to bottom, #ddd 0%, #bbb 100%); + background: linear-gradient(to bottom, #ddd 0%, #bbb 100%); border-top-right-radius: 0; border-bottom-right-radius: 0; } diff --git a/app/presenters/collection_presenter.rb b/app/presenters/collection_presenter.rb index b4322315fe..f4a1032bb8 100644 --- a/app/presenters/collection_presenter.rb +++ b/app/presenters/collection_presenter.rb @@ -52,7 +52,8 @@ def contact_email end def website_link - view_context.link_to document["website_label_ssi"], document["website_url_ssi"] if document["website_url_ssi"].present? + label = document["website_label_ssi"].presence || document["website_url_ssi"] + view_context.link_to label, document["website_url_ssi"] if document["website_url_ssi"].present? end def as_json(_) diff --git a/spec/presenters/collection_presenter_spec.rb b/spec/presenters/collection_presenter_spec.rb index 6d677c6a95..8d2c71c0a4 100644 --- a/spec/presenters/collection_presenter_spec.rb +++ b/spec/presenters/collection_presenter_spec.rb @@ -23,7 +23,7 @@ let(:website_label) { Faker::Lorem.words.join(' ') } let(:website_url) { Faker::Internet.url } let(:contact_email) { Faker::Internet.email } - let(:website_link) { "#{website_url}" } + let(:website_link) { "#{website_label.presence || website_url}" } let(:solr_doc) do SolrDocument.new( id: id, @@ -41,6 +41,7 @@ before do allow(view_context).to receive(:link_to).with(website_label, website_url).and_return(website_link) + allow(view_context).to receive(:link_to).with(website_url, website_url).and_return(website_link) end it 'provides getters for descriptive fields' do @@ -94,6 +95,19 @@ expect(presenter.website_link).to be_nil end end + + context 'with a blank website label' do + let(:website_label) { '' } + + it 'provides a link tag' do + expect(presenter.website_link).to eq website_link + end + + it 'sets label to the website url' do + expect(view_context).to receive(:link_to).with(website_url, website_url) + presenter.website_link + end + end end describe '#as_json' do From 1fefccbbd53157995e2356caabf1bf2f33a4784a Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Thu, 22 Feb 2024 13:48:14 -0500 Subject: [PATCH 03/33] Set event listeners once for playlist modal We were encountering an issue where adding a new playlist from the dropdown menu would insert the new playlist's name into the list multiple times. This was caused by the `.add_new_playlist_option()` function being triggered every time the Add to Playlist menu was opened. This caused the event listener that updates the dropdown list to be added every time the menu opened, causing the duplication we were seeing. Moving the function call out of the parent event listener so it only runs once fixes the issue. --- app/views/media_objects/_add_to_playlist.html.erb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/media_objects/_add_to_playlist.html.erb b/app/views/media_objects/_add_to_playlist.html.erb index 4266775673..47b96fa37f 100644 --- a/app/views/media_objects/_add_to_playlist.html.erb +++ b/app/views/media_objects/_add_to_playlist.html.erb @@ -205,6 +205,9 @@ Unless required by applicable law or agreed to in writing, software distributed addToPlaylistBtn.disabled = false; } if(!listenersAdded) { + // Add 'Add new playlist' option to dropdown + window.add_new_playlist_option(); + addListeners(); } } @@ -228,8 +231,6 @@ Unless required by applicable law or agreed to in writing, software distributed currentTime = currentPlayer.player.currentTime(); duration = currentPlayer.player.duration(); } - // Add 'Add new playlist' option to dropdown - window.add_new_playlist_option(); let canvasIndex = parseInt(currentPlayer.dataset.canvasindex); mediaObjectTitle = playlistForm.dataset.title; From ca0e4db0d194cc957ca79b3ded0b7395c6544c5d Mon Sep 17 00:00:00 2001 From: cjcolvar Date: Tue, 27 Feb 2024 15:01:19 -0500 Subject: [PATCH 04/33] Fixes and improvements to the reindexing script --- script/reindex.rb | 173 +++++++++++++++++++++++++--------------------- 1 file changed, 94 insertions(+), 79 deletions(-) diff --git a/script/reindex.rb b/script/reindex.rb index f4a8e57c54..d5378a46a9 100644 --- a/script/reindex.rb +++ b/script/reindex.rb @@ -154,7 +154,7 @@ else database_url = options[:database_url] database_url ||= ENV['DATABASE_URL'] - DB = Sequel.connect(database_url) + DB = Sequel.connect(database_url, max_connections: 20, pool_timeout: 10) end if options[:prune] @@ -186,16 +186,23 @@ last_updated_at = items.order(:updated_at).last[:updated_at] query += " AND timestamp:[#{(last_updated_at - 1.day).utc.iso8601} TO *]" end - docs = read_solr.conn.get("select", params: { q: query, qt: 'standard', fl: ["id", "timestamp"], rows: 1_000_000_000 })["response"]["docs"] - # Need to transform ids into uris to match what we get from crawling fedora - docs.map { |doc| doc["id"] = ActiveFedora::Base.id_to_uri(doc["id"]) } - # Need to transform timestamps into DateTime objects - docs.map { |doc| doc["timestamp"] = DateTime.parse(doc["timestamp"]) } - # Skip those that are already waiting reindex - docs.reject! { |doc| items.where(uri: doc["id"], state: "waiting reindex").any? } - # Skip those which haven't changed - docs.reject! { |doc| items.where(uri: doc["id"]).where(Sequel.lit('updated_at >= ?', doc["timestamp"])).any? } - items.import([:uri, :updated_at, :state, :state_changed_at], docs.map(&:values).product([["waiting reindex", DateTime.now]]).map(&:flatten), commit_every: 10_000) + docs = read_solr.conn.get("select", params: { q: query, qt: 'standard', fl: ["id", "timestamp", "has_model_ssim"], rows: 1_000_000_000 })["response"]["docs"] + docs.map do |doc| + # Need to transform ids into uris to match what we get from crawling fedora + doc["id"] = ActiveFedora::Base.id_to_uri(doc["id"]) + # Need to transform timestamps into DateTime objects + doc["timestamp"] = DateTime.parse(doc["timestamp"]) + model = doc["has_model_ssim"]&.first + doc["model"] = model if model + doc.delete("has_model_ssim") + end + docs.reject! do |doc| + # Skip those that are already waiting reindex + items.where(uri: doc["id"], state: "waiting reindex").any? || + # Skip those which haven't changed + items.where(uri: doc["id"]).where(Sequel.lit('updated_at >= ?', doc["timestamp"])).any? + end + items.import([:uri, :updated_at, :model, :state, :state_changed_at], docs.map(&:values).product([["waiting reindex", DateTime.now]]).map(&:flatten), commit_every: 10_000) if options[:delta] already_deleted_uris = items.where(state: ["waiting deletion", "deleted"]).order(:uri).distinct(:uri).select(:uri).pluck(:uri) @@ -276,91 +283,95 @@ softCommit = true uris_to_skip = [/\/poster$/, /\/thumbnail$/, /\/waveform$/, /\/captions$/, /\/structuralMetadata$/] - if options[:parallel_indexing] - require 'parallel' - require 'ruby-progressbar' + models_for_all = DB[:reindexing_nodes].map(:model).uniq - ["Hydra::AccessControl", "Hydra::AccessControls::Permission", "Admin::Collection"] + model_prioritization = ["Hydra::AccessControl", "Hydra::AccessControls::Permission", "Admin::Collection", models_for_all] + + model_prioritization.each do |model| + items_for_reindexing_relation = items.where(state: "waiting reindex", model: model).limit(reindex_limit).map(:uri) + + if options[:parallel_indexing] + require 'parallel' + require 'ruby-progressbar' - Parallel.each(items.where(state: "waiting reindex").limit(reindex_limit).map(:uri).each_slice(batch_size), in_threads: 10, progress: "Reindexing") do |uris| + Parallel.each(items_for_reindexing_relation.each_slice(batch_size), in_threads: 10, progress: "Reindexing") do |uris| + batch = [] + batch_uris = [] + + uris.each do |uri| + begin + if uris_to_skip.any? { |pattern| uri =~ pattern } + items.where(uri: uri, state: "waiting reindex").update(state: "skipped", state_changed_at: DateTime.now) + next + end + obj = ActiveFedora::Base.find(ActiveFedora::Base.uri_to_id(uri)) + batch << (obj.is_a?(MediaObject) ? obj.to_solr(include_child_fields: true) : obj.to_solr) + batch_uris << uri + # Handle speedy_af indexing + if obj.is_a?(MasterFile) || obj.is_a?(Admin::Collection) + obj.declared_attached_files.each_pair do |name, file| + batch << file.to_solr({}, external_index: true) if file.present? && file.respond_to?(:update_external_index) + end + end + rescue Exception => e + puts "#{DateTime.now} Error adding #{uri} to batch: #{e.message}" + puts e.backtrace if options[:verbose] + batch_uris -= [uri] + batch.delete_if { |doc| ActiveFedora::Base.uri_to_id(uri) == doc[:id] } + # Need to worry about removing masterfile attached files from the batch as well? + items.where(uri: uri, state: "waiting reindex").update(state: "errored", state_changed_at: DateTime.now) + next + end + end + + begin + solr.conn.add(batch, params: { softCommit: softCommit }) unless options[:dry_run] + items.where(uri: batch_uris, state: "waiting reindex").update(state: "processed", state_changed_at: DateTime.now) + rescue Exception => e + puts "#{DateTime.now} Error persisting batch to solr: #{e.message}" + puts e.backtrace if options[:verbose] + items.where(uri: batch_uris, state: "waiting reindex").update(state: "errored", state_changed_at: DateTime.now) + end + end + else batch = [] batch_uris = [] - - uris.each do |uri| + items_for_reindexing_relation.each do |uri| begin - if uris_to_skip.any? { |pattern| uri =~ pattern } - items.where(uri: uri, state: "waiting reindex").update(state: "skipped", state_changed_at: DateTime.now) - next - end obj = ActiveFedora::Base.find(ActiveFedora::Base.uri_to_id(uri)) - batch << (obj.is_a?(MediaObject) ? obj.to_solr(include_child_fields: true) : obj.to_solr) - batch_uris << uri + batch << (obj.is_a?(MediaObject) ? obj.to_solr(include_child_fields: true) : obj.to_solr) + batch_uris << uri # Handle speedy_af indexing - if obj.is_a?(MasterFile) || obj.is_a?(Admin::Collection) + if obj.is_a?(MasterFile) || obj.is_a?(Admin::Collection) obj.declared_attached_files.each_pair do |name, file| - batch << file.to_solr({}, external_index: true) if file.present? && file.respond_to?(:update_external_index) + batch << file.to_solr({}, external_index: true) if file.present? && file.respond_to?(:update_external_index) end end rescue Exception => e - puts "#{DateTime.now} Error adding #{uri} to batch: #{e.message}" - puts e.backtrace if options[:verbose] - batch_uris -= [uri] - batch.delete_if { |doc| ActiveFedora::Base.uri_to_id(uri) == doc[:id] } - # Need to worry about removing masterfile attached files from the batch as well? - items.where(uri: uri, state: "waiting reindex").update(state: "errored", state_changed_at: DateTime.now) + puts "#{DateTime.now} Error adding #{uri} to batch: #{e.message}" + puts e.backtrace if options[:verbose] + batch_uris -= [uri] + batch.delete_if { |doc| ActiveFedora::Base.uri_to_id(uri) == doc[:id] } + # Need to worry about removing masterfile attached files from the batch as well? + items.where(uri: uri, state: "waiting reindex").update(state: "errored", state_changed_at: DateTime.now) next end - end - begin - solr.conn.add(batch, params: { softCommit: softCommit }) unless options[:dry_run] - items.where(uri: batch_uris, state: "waiting reindex").update(state: "processed", state_changed_at: DateTime.now) - rescue Exception => e - puts "#{DateTime.now} Error persisting batch to solr: #{e.message}" - puts e.backtrace if options[:verbose] - items.where(uri: batch_uris, state: "waiting reindex").update(state: "errored", state_changed_at: DateTime.now) - end - end - else - batch = [] - batch_uris = [] - # TODO: take batch_size of uris and pass to background job and remove rescue so it will surface - # This could also obviate the need for the final batch processing - # Should this actually be a cron-type job to wake up and look for items needing reindexing? - items.where(state: "waiting reindex").limit(reindex_limit).map(:uri).each do |uri| - begin - obj = ActiveFedora::Base.find(ActiveFedora::Base.uri_to_id(uri)) - batch << (obj.is_a?(MediaObject) ? obj.to_solr(include_child_fields: true) : obj.to_solr) - batch_uris << uri - # Handle speedy_af indexing - if obj.is_a?(MasterFile) || obj.is_a?(Admin::Collection) - obj.declared_attached_files.each_pair do |name, file| - batch << file.to_solr({}, external_index: true) if file.present? && file.respond_to?(:update_external_index) - end - end - rescue Exception => e - puts "#{DateTime.now} Error adding #{uri} to batch: #{e.message}" - puts e.backtrace if options[:verbose] - batch_uris -= [uri] - batch.delete_if { |doc| ActiveFedora::Base.uri_to_id(uri) == doc[:id] } - # Need to worry about removing masterfile attached files from the batch as well? - items.where(uri: uri, state: "waiting reindex").update(state: "errored", state_changed_at: DateTime.now) - next + if (batch.count % batch_size).zero? + solr.conn.add(batch, params: { softCommit: softCommit }) unless options[:dry_run] + items.where(uri: batch_uris, state: "waiting reindex").update(state: "processed", state_changed_at: DateTime.now) + batch.clear + batch_uris.clear + puts "#{DateTime.now} #{items.where(state: "processed").count} processed" if options[:verbose] + end end - if (batch.count % batch_size).zero? - solr.conn.add(batch, params: { softCommit: softCommit }) unless options[:dry_run] - items.where(uri: batch_uris, state: "waiting reindex").update(state: "processed", state_changed_at: DateTime.now) - batch.clear - batch_uris.clear - puts "#{DateTime.now} #{items.where(state: "processed").count} processed" if options[:verbose] + if batch.present? + solr.conn.add(batch, params: { softCommit: softCommit }) unless options[:dry_run] + items.where(uri: batch_uris, state: "waiting reindex").update(state: "processed", state_changed_at: DateTime.now) + batch.clear + batch_uris.clear end end - - if batch.present? - solr.conn.add(batch, params: { softCommit: softCommit }) unless options[:dry_run] - items.where(uri: batch_uris, state: "waiting reindex").update(state: "processed", state_changed_at: DateTime.now) - batch.clear - batch_uris.clear - end end if options[:delta] @@ -378,6 +389,10 @@ items.where(state: "waiting deletion").update(state: "errored", state_changed_at: DateTime.now) end end + + # Do a final hard commit and optimize + solr.conn.commit + solr.conn.optimize end puts "#{DateTime.now} Completed" if options[:verbose] From 413d4d13a1d9d36d9db74057c29b6dab8b5c3a2c Mon Sep 17 00:00:00 2001 From: dwithana Date: Fri, 16 Feb 2024 13:37:43 -0500 Subject: [PATCH 05/33] Reduce player icon size --- app/javascript/components/Ramp.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/javascript/components/Ramp.scss b/app/javascript/components/Ramp.scss index 2a072b9139..b57cd37d36 100644 --- a/app/javascript/components/Ramp.scss +++ b/app/javascript/components/Ramp.scss @@ -28,6 +28,10 @@ scale: 1.5; } } + + .video-js .vjs-control-bar { + font-size: 100% !important; + } } .ramp--structured-nav { @@ -363,4 +367,4 @@ height: 109vh !important; } } -} +} \ No newline at end of file From f1d56ceacb1da7d8d0cf9f293b2c25995e2bed68 Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Mon, 26 Feb 2024 15:10:16 -0500 Subject: [PATCH 06/33] Add collection_id to media_object JSON response --- app/models/concerns/media_object_behavior.rb | 1 + spec/controllers/media_objects_controller_spec.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/app/models/concerns/media_object_behavior.rb b/app/models/concerns/media_object_behavior.rb index 26d03f907f..498522fbae 100644 --- a/app/models/concerns/media_object_behavior.rb +++ b/app/models/concerns/media_object_behavior.rb @@ -19,6 +19,7 @@ def as_json(options={}) id: id, title: title, collection: collection.name, + collection_id: collection.id, unit: collection.unit, main_contributors: creator, publication_date: date_created, diff --git a/spec/controllers/media_objects_controller_spec.rb b/spec/controllers/media_objects_controller_spec.rb index 92d03f4069..74ee109890 100644 --- a/spec/controllers/media_objects_controller_spec.rb +++ b/spec/controllers/media_objects_controller_spec.rb @@ -1193,6 +1193,7 @@ expect(json['id']).to eq(media_object.id) expect(json['title']).to eq(media_object.title) expect(json['collection']).to eq(media_object.collection.name) + expect(json['collection_id']).to eq(media_object.collection.id) expect(json['main_contributors']).to eq(media_object.creator) expect(json['publication_date']).to eq(media_object.date_created) expect(json['published_by']).to eq(media_object.avalon_publisher) From 841e782043e3261a53a502a546ba44fb4eca62e7 Mon Sep 17 00:00:00 2001 From: dwithana Date: Tue, 20 Feb 2024 14:01:59 -0500 Subject: [PATCH 07/33] Enable action buttons on item page only when player is fully loaded --- .../media_objects/_add_to_playlist.html.erb | 5 +++-- app/views/media_objects/_thumbnail.html.erb | 20 ++++++++++++++----- app/views/media_objects/_timeline.html.erb | 18 +++++++++++++---- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/app/views/media_objects/_add_to_playlist.html.erb b/app/views/media_objects/_add_to_playlist.html.erb index 47b96fa37f..a08c60a1f6 100644 --- a/app/views/media_objects/_add_to_playlist.html.erb +++ b/app/views/media_objects/_add_to_playlist.html.erb @@ -177,7 +177,7 @@ Unless required by applicable law or agreed to in writing, software distributed // This timeout enables the add to playlist button, when this happens. It checks the button's state and enables it as needed. setTimeout(() => { enableAddToPlaylist(); - }, 500); + }, 100); player.on('seeked', () => { if(getActiveItem() != undefined) { activeTrack = getActiveItem(false); @@ -200,8 +200,9 @@ Unless required by applicable law or agreed to in writing, software distributed } function enableAddToPlaylist() { + let player = document.getElementById('iiif-media-player'); let addToPlaylistBtn = document.getElementById('addToPlaylistBtn'); - if(addToPlaylistBtn && addToPlaylistBtn.disabled) { + if(addToPlaylistBtn && addToPlaylistBtn.disabled && player?.player.readyState() === 4) { addToPlaylistBtn.disabled = false; } if(!listenersAdded) { diff --git a/app/views/media_objects/_thumbnail.html.erb b/app/views/media_objects/_thumbnail.html.erb index 2310c1a2b7..96af889c9e 100644 --- a/app/views/media_objects/_thumbnail.html.erb +++ b/app/views/media_objects/_thumbnail.html.erb @@ -59,19 +59,29 @@ Unless required by applicable law or agreed to in writing, software distributed if (thumbnailBtn) { thumbnailBtn.disabled = false; } - clearInterval(timeCheck); }); /* Browsers on MacOS sometimes miss the 'loadedmetadata' event resulting in a disabled add to playlist button indefinitely. This timeout enables the add to playlist button, when this happens. It checks the button's state and enables it as needed. + Additional check for player's readyState ensures the button is enabled only when player is ready after the timeout. */ setTimeout(() => { let thumbnailBtn = document.getElementById('create-thumbnail-btn'); - if (thumbnailBtn && thumbnailBtn.disabled) { - thumbnailBtn.disabled = false; + if (thumbnailBtn && thumbnailBtn.disabled && player.player?.readyState() === 4) { + thumbnailBtn.disabled = false; } - clearInterval(timeCheck); - }, 500); + }, 100); + + /* + Disable 'Create Thumbnail' button on player dispose, so that it can be enabled again or keep disabled on the next section load + based on the player status. + */ + player.player.on('dispose', () => { + let thumbnailBtn = document.getElementById('create-thumbnail-btn'); + if (thumbnailBtn) { + thumbnailBtn.disabled = true; + } + }); } $('#thumbnailModal').on('show.bs.modal', function(e) { diff --git a/app/views/media_objects/_timeline.html.erb b/app/views/media_objects/_timeline.html.erb index f5f3f22f27..c857e4344b 100644 --- a/app/views/media_objects/_timeline.html.erb +++ b/app/views/media_objects/_timeline.html.erb @@ -54,19 +54,29 @@ $(document).ready(function() { if (timelineBtn) { timelineBtn.disabled = false; } - clearInterval(timeCheck); }); /* Browsers on MacOS sometimes miss the 'loadedmetadata' event resulting in a disabled add to playlist button indefinitely. This timeout enables the add to playlist button, when this happens. It checks the button's state and enables it as needed. + Additional check for player's readyState ensures the button is enabled only when player is ready after the timeout. */ setTimeout(() => { let timelineBtn = document.getElementById('timelineBtn'); - if (timelineBtn && timelineBtn.disabled) { + if (timelineBtn && timelineBtn.disabled && player.player?.readyState() === 4) { timelineBtn.disabled = false; } - clearInterval(timeCheck); - }, 500); + }, 100); + + /* + Disable 'Create Timeline' button on player dispose, so that it can be enabled again or keep disabled on the next section load + based on the player status. + */ + player.player.on('dispose', () => { + let timelineBtn = document.getElementById('timelineBtn'); + if (timelineBtn) { + timelineBtn.disabled = true; + } + }); } $('#timelineModal').on('shown.bs.modal', function (e) { From ceb59de4df66d3e5353a4cbdc3ed063322756279 Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Tue, 5 Mar 2024 12:11:58 -0500 Subject: [PATCH 08/33] Skip validation and log files with improper mime types --- lib/tasks/avalon_migrations.rake | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/tasks/avalon_migrations.rake b/lib/tasks/avalon_migrations.rake index ee0cc4a8a7..96c45e8b0e 100644 --- a/lib/tasks/avalon_migrations.rake +++ b/lib/tasks/avalon_migrations.rake @@ -26,6 +26,8 @@ namespace :avalon do desc "Migrate legacy IndexedFile captions to ActiveStorage as part of supporting upload of multiple captions files" task caption_files: :environment do count = 0 + error = [] + logger = Logger.new(File.join(Rails.root, 'log/caption_type_errors.log')) # Iterate through all caption IndexedFiles IndexedFile.where("id: */captions").each do |caption_file| # Retrieve parent master file @@ -38,7 +40,14 @@ namespace :avalon do # Create and populate new SupplementalFile record using original metadata supplemental_file = SupplementalFile.new(label: filename, tags: ['caption'], language: 'eng') supplemental_file.file.attach(io: ActiveFedora::FileIO.new(caption_file), filename: filename, content_type: content_type, identify: false) - supplemental_file.save + # Skip validation so that incorrect mimetypes do not bomb the entire task + supplemental_file.save(validate: false) + # Log when a file has an incorrect mimetype for later manual remediation + if !['text/vtt', 'text/srt'].include?(content_type) + message = "File with unsupported mime type: #{supplemental_file.id}" + puts(message) + error += [supplemental_file.id] + end # Link new SupplementalFile to the MasterFile master_file.supplemental_files += [supplemental_file] # Delete legacy caption file @@ -50,6 +59,10 @@ namespace :avalon do end count > 0 ? puts("Successfully updated #{count} records") : puts("All files are already up to date. No records updated.") + if error.present? + logger.info("Files with unsupported mime types: #{error}") + puts("#{error.length} files migrated with unsupported mime types. Refer to caption_type_errors.log for full list of SupplementalFile IDs.") + end end end end From 626c4264997b621ffac001259c6c4f9afc499d9c Mon Sep 17 00:00:00 2001 From: cjcolvar Date: Tue, 5 Mar 2024 15:08:38 -0500 Subject: [PATCH 09/33] Optimize collection media object counts --- app/controllers/admin/collections_controller.rb | 13 ++++++++++++- app/presenters/admin/collection_presenter.rb | 5 +++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/controllers/admin/collections_controller.rb b/app/controllers/admin/collections_controller.rb index 5d7cfa02c9..041922e7bf 100644 --- a/app/controllers/admin/collections_controller.rb +++ b/app/controllers/admin/collections_controller.rb @@ -36,7 +36,18 @@ def load_and_authorize_collections builder.user = user end response = repository.search(builder) - @collections = response.documents.collect { |doc| ::Admin::CollectionPresenter.new(doc) }.sort_by { |c| c.name.downcase } + + # Query solr for facet values for collection media object counts and pass into presenter to avoid making 2 solr queries per collection + count_query = "has_model_ssim:MediaObject" + count_response = ActiveFedora::SolrService.get(count_query, { rows: 0, facet: true, 'facet.field': "isMemberOfCollection_ssim", 'facet.limit': -1 }) + counts_array = count_response["facet_counts"]["facet_fields"]["isMemberOfCollection_ssim"] rescue [] + counts = counts_array.blank? ? {} : [counts_array].to_h + unpublished_query = count_query + " AND workflow_published_sim:Unpublished" + unpublished_count_response = ActiveFedora::SolrService.get(unpublished_query, { rows: 0, facet: true, 'facet.field': "isMemberOfCollection_ssim", 'facet.limit': -1 }) + unpublished_counts_array = unpublished_count_response["facet_counts"]["facet_fields"]["isMemberOfCollection_ssim"] rescue [] + unpublished_counts = unpublished_counts_array.blank? ? {} : [unpublished_counts_array].to_h + + @collections = response.documents.collect { |doc| ::Admin::CollectionPresenter.new(doc, media_object_count: counts[doc.id], unpublished_media_object_count: unpublished_counts[doc.id]) }.sort_by { |c| c.name.downcase } end # GET /collections diff --git a/app/presenters/admin/collection_presenter.rb b/app/presenters/admin/collection_presenter.rb index 9bdadcd387..c4b07064bb 100644 --- a/app/presenters/admin/collection_presenter.rb +++ b/app/presenters/admin/collection_presenter.rb @@ -15,8 +15,10 @@ class Admin::CollectionPresenter attr_reader :document - def initialize(solr_doc) + def initialize(solr_doc, media_object_count: nil, unpublished_media_object_count: nil) @document = solr_doc + @media_object_count = media_object_count + @unpublished_media_object_count = unpublished_media_object_count end delegate :id, to: :document @@ -33,7 +35,6 @@ def description document["description_tesim"]&.first end - # TODO: do these counts in one large query for all collections on the index page to avoid having to do them here def media_object_count @media_object_count ||= MediaObject.where("collection_ssim" => name).count end From 0803130fa86bd0bdfc4a78b20709a8445edd0750 Mon Sep 17 00:00:00 2001 From: cjcolvar Date: Wed, 6 Mar 2024 11:02:16 -0500 Subject: [PATCH 10/33] Correctly convert count result array into hash --- app/controllers/admin/collections_controller.rb | 4 ++-- spec/controllers/admin_collections_controller_spec.rb | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/controllers/admin/collections_controller.rb b/app/controllers/admin/collections_controller.rb index 041922e7bf..d7f4c26afb 100644 --- a/app/controllers/admin/collections_controller.rb +++ b/app/controllers/admin/collections_controller.rb @@ -41,11 +41,11 @@ def load_and_authorize_collections count_query = "has_model_ssim:MediaObject" count_response = ActiveFedora::SolrService.get(count_query, { rows: 0, facet: true, 'facet.field': "isMemberOfCollection_ssim", 'facet.limit': -1 }) counts_array = count_response["facet_counts"]["facet_fields"]["isMemberOfCollection_ssim"] rescue [] - counts = counts_array.blank? ? {} : [counts_array].to_h + counts = counts_array.each_slice(2).to_h unpublished_query = count_query + " AND workflow_published_sim:Unpublished" unpublished_count_response = ActiveFedora::SolrService.get(unpublished_query, { rows: 0, facet: true, 'facet.field': "isMemberOfCollection_ssim", 'facet.limit': -1 }) unpublished_counts_array = unpublished_count_response["facet_counts"]["facet_fields"]["isMemberOfCollection_ssim"] rescue [] - unpublished_counts = unpublished_counts_array.blank? ? {} : [unpublished_counts_array].to_h + unpublished_counts = unpublished_counts_array.each_slice(2).to_h @collections = response.documents.collect { |doc| ::Admin::CollectionPresenter.new(doc, media_object_count: counts[doc.id], unpublished_media_object_count: unpublished_counts[doc.id]) }.sort_by { |c| c.name.downcase } end diff --git a/spec/controllers/admin_collections_controller_spec.rb b/spec/controllers/admin_collections_controller_spec.rb index 6ac3583201..f092311be4 100644 --- a/spec/controllers/admin_collections_controller_spec.rb +++ b/spec/controllers/admin_collections_controller_spec.rb @@ -154,7 +154,8 @@ end describe "#index" do - let!(:collection) { FactoryBot.create(:collection) } + let!(:collection) { FactoryBot.create(:collection, items: 1) } + let!(:collection2) { FactoryBot.create(:collection, items: 1) } subject(:json) { JSON.parse(response.body) } let(:administrator) { FactoryBot.create(:administrator) } @@ -165,7 +166,7 @@ end it "should return list of collections" do get 'index', params: { format:'json' } - expect(json.count).to eq(1) + expect(json.count).to eq(2) expect(json.first['id']).to eq(collection.id) expect(json.first['name']).to eq(collection.name) expect(json.first['unit']).to eq(collection.unit) From 29f8ef7c6526c1917e386572240a3701e7692920 Mon Sep 17 00:00:00 2001 From: cjcolvar Date: Wed, 6 Mar 2024 11:18:36 -0500 Subject: [PATCH 11/33] Assume count of 0 if not in facet results to avoid solr queries in presenter --- app/controllers/admin/collections_controller.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/admin/collections_controller.rb b/app/controllers/admin/collections_controller.rb index d7f4c26afb..aa8f49d93f 100644 --- a/app/controllers/admin/collections_controller.rb +++ b/app/controllers/admin/collections_controller.rb @@ -47,7 +47,11 @@ def load_and_authorize_collections unpublished_counts_array = unpublished_count_response["facet_counts"]["facet_fields"]["isMemberOfCollection_ssim"] rescue [] unpublished_counts = unpublished_counts_array.each_slice(2).to_h - @collections = response.documents.collect { |doc| ::Admin::CollectionPresenter.new(doc, media_object_count: counts[doc.id], unpublished_media_object_count: unpublished_counts[doc.id]) }.sort_by { |c| c.name.downcase } + @collections = response.documents.collect do |doc| + ::Admin::CollectionPresenter.new(doc, + media_object_count: (counts[doc.id] || 0), + unpublished_media_object_count: (unpublished_counts[doc.id] || 0)) + end.sort_by { |c| c.name.downcase } end # GET /collections From aa80c278e993b36226de59ed4252282c833dd881 Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Thu, 7 Mar 2024 11:40:44 -0500 Subject: [PATCH 12/33] Set content_type explicitly based on file extension --- app/models/supplemental_file.rb | 3 +++ spec/factories/supplemental_file.rb | 4 ++++ spec/fixtures/captions.srt | 3 +++ spec/models/supplemental_file_spec.rb | 8 +++++++- .../supplemental_files_controller_examples.rb | 19 +++++++++++++++++++ 5 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/captions.srt diff --git a/app/models/supplemental_file.rb b/app/models/supplemental_file.rb index ec22663acf..9ef41af6f5 100644 --- a/app/models/supplemental_file.rb +++ b/app/models/supplemental_file.rb @@ -28,6 +28,9 @@ def validate_file_type def attach_file(new_file) file.attach(new_file) + extension = File.extname(new_file.original_filename) + content_type = Mime::Type.lookup_by_extension(extension.slice(1..-1)).to_s + self.file.content_type = content_type self.label = file.filename.to_s if label.blank? self.language = tags.include?('caption') ? Settings.caption_default.language : 'eng' end diff --git a/spec/factories/supplemental_file.rb b/spec/factories/supplemental_file.rb index 7e76b26213..066476f77a 100644 --- a/spec/factories/supplemental_file.rb +++ b/spec/factories/supplemental_file.rb @@ -29,6 +29,10 @@ file { fixture_file_upload(Rails.root.join('spec', 'fixtures', 'captions.vtt'), 'text/vtt') } end + trait :with_caption_srt_file do + file { fixture_file_upload(Rails.root.join('spec', 'fixtures', 'captions.srt'), 'text/srt')} + end + trait :with_transcript_tag do tags { ['transcript'] } end diff --git a/spec/fixtures/captions.srt b/spec/fixtures/captions.srt new file mode 100644 index 0000000000..71ea3d55b2 --- /dev/null +++ b/spec/fixtures/captions.srt @@ -0,0 +1,3 @@ +1 +00:00:03,498 --> 00:00:05,000 +- Example Captions diff --git a/spec/models/supplemental_file_spec.rb b/spec/models/supplemental_file_spec.rb index 21897768dd..f5a4f6a430 100644 --- a/spec/models/supplemental_file_spec.rb +++ b/spec/models/supplemental_file_spec.rb @@ -25,12 +25,18 @@ expect(subject.valid?).to be_truthy end end - context 'VTT/SRT caption file' do + context 'VTT caption file' do let(:subject) { FactoryBot.create(:supplemental_file, :with_caption_file, :with_caption_tag) } it 'should validate' do expect(subject.valid?).to be_truthy end end + context 'SRT caption file' do + let(:subject) { FactoryBot.create(:supplemental_file, :with_caption_srt_file, :with_caption_tag) } + it 'should validate' do + expect(subject.valid?).to be_truthy + end + end context 'non-VTT/non-SRT caption file' do let(:subject) { FactoryBot.build(:supplemental_file, :with_attached_file, :with_caption_tag) } it 'should not validate' do diff --git a/spec/support/supplemental_files_controller_examples.rb b/spec/support/supplemental_files_controller_examples.rb index 51b5c08a9f..c79f1a44ad 100644 --- a/spec/support/supplemental_files_controller_examples.rb +++ b/spec/support/supplemental_files_controller_examples.rb @@ -134,6 +134,25 @@ expect(object.supplemental_files.first.tags).to eq tags expect(object.supplemental_files.first.file).to be_attached end + + context 'with mime type that does not match extension' do + let(:tags) { ['caption'] } + let(:extension) { 'srt' } + let(:uploaded_file) { fixture_file_upload(Rails.root.join('spec', 'fixtures', 'captions.srt'), 'text/plain') } + it "creates a SupplementalFile with correct content_type" do + expect{ + post :create, params: { class_id => object.id, supplemental_file: valid_create_attributes_with_tags, format: :json}, session: valid_session + }.to change { object.reload.supplemental_files.size }.by(1) + expect(response).to have_http_status(:created) + expect(response.location).to eq "/#{object_class.model_name.plural}/#{object.id}/supplemental_files/#{assigns(:supplemental_file).id}" + + expect(object.supplemental_files.first.id).to eq 1 + expect(object.supplemental_files.first.label).to eq 'label' + expect(object.supplemental_files.first.tags).to eq tags + expect(object.supplemental_files.first.file).to be_attached + expect(object.supplemental_files.first.file.content_type).to eq Mime::Type.lookup_by_extension(extension) + end + end end context "with invalid params" do From f7eef187a5b88e53598944777b4d8e57ecd97810 Mon Sep 17 00:00:00 2001 From: cjcolvar Date: Fri, 1 Mar 2024 14:54:34 -0500 Subject: [PATCH 13/33] Add ability to run reindex on only certain models, ability to munge uri_ss on IndexedFile objects, respect reindex limit, specifying number of parallel threads --- script/reindex.rb | 51 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/script/reindex.rb b/script/reindex.rb index d5378a46a9..75110dbc50 100644 --- a/script/reindex.rb +++ b/script/reindex.rb @@ -66,8 +66,8 @@ # already processed minus one day. This should run quickly (< 1 hour). # # This script was written for migrating from solr 6 to solr 8/9 with the following process in mind: -# 1. Reindex from solr 6 into new solr 8 instance -# 2. Configure avalon to use new solr 8 instance and restart +# 1. Reindex from solr 6 into new solr 9 instance +# 2. Configure avalon to use new solr 9 instance and restart # 3. Run reindex delta to read from solr 6 and catch any items that have changed since step 1 @@ -114,15 +114,27 @@ end parser.on("--reindex-limit REINDEX_LIMIT", "Limit reindexing to a set number of items") do |rl| - options[:reindex_limit] = rl + options[:reindex_limit] = rl.to_i end parser.on("--parallel-indexing", "Reindex using paralellism") do |p| options[:parallel_indexing] = p end - parser.on("--batch-size", "Size of batches for indexing (default: 50)") do |bs| - options[:batch_size] = bs + parser.on("--parallel-threads THREADS", "Number of parallel threads to use for reindexing") do |pt| + options[:parallel_threads] = pt.to_i + end + + parser.on("--batch-size SIZE", "Size of batches for indexing (default: 50)") do |bs| + options[:batch_size] = bs.to_i + end + + parser.on("--only-models MODELS", "Only index certain models in the order specified by comma-separated list") do |om| + options[:only_models] = om.split(',').map(&:strip) + end + + parser.on("--file-fedora-url URL", "Replace fedora url in IndexedFile uri_ss fields (This is not validated so be careful!)") do |ffu| + options[:file_fedora_url] = ffu end parser.on("--delta", "Only find changes since last reindexing") do |d| @@ -273,27 +285,28 @@ # Re-index unless options[:skip_reindexing] reindex_limit = options[:reindex_limit] || nil - if reindex_limit - puts "#{DateTime.now} Attempting reindex of #{reindex_limit} nodes out of #{items.where(state:"waiting reindex").count}." if options[:verbose] - else - puts "#{DateTime.now} Attempting reindex of #{items.where(state:"waiting reindex").count} nodes." if options[:verbose] - end - batch_size = options[:batch_size] || 50 softCommit = true uris_to_skip = [/\/poster$/, /\/thumbnail$/, /\/waveform$/, /\/captions$/, /\/structuralMetadata$/] models_for_all = DB[:reindexing_nodes].map(:model).uniq - ["Hydra::AccessControl", "Hydra::AccessControls::Permission", "Admin::Collection"] - model_prioritization = ["Hydra::AccessControl", "Hydra::AccessControls::Permission", "Admin::Collection", models_for_all] + model_prioritization = options[:only_models] || ["Hydra::AccessControl", "Hydra::AccessControls::Permission", "Admin::Collection", models_for_all] model_prioritization.each do |model| items_for_reindexing_relation = items.where(state: "waiting reindex", model: model).limit(reindex_limit).map(:uri) + if reindex_limit + puts "#{DateTime.now} Attempting reindex of #{reindex_limit} #{model} nodes out of #{items.where(state:"waiting reindex", model: model).count}." if options[:verbose] + reindex_limit = reindex_limit - items_for_reindexing_relation.count + else + puts "#{DateTime.now} Attempting reindex of #{items.where(state:"waiting reindex", model: model).count} #{model} nodes." if options[:verbose] + end + if options[:parallel_indexing] require 'parallel' require 'ruby-progressbar' - Parallel.each(items_for_reindexing_relation.each_slice(batch_size), in_threads: 10, progress: "Reindexing") do |uris| + Parallel.each(items_for_reindexing_relation.each_slice(batch_size), in_threads: options[:parallel_threads] || 10, progress: "Reindexing") do |uris| batch = [] batch_uris = [] @@ -309,13 +322,17 @@ # Handle speedy_af indexing if obj.is_a?(MasterFile) || obj.is_a?(Admin::Collection) obj.declared_attached_files.each_pair do |name, file| - batch << file.to_solr({}, external_index: true) if file.present? && file.respond_to?(:update_external_index) + next unless file.present? + file_doc = file.to_solr({}, external_index: true) if file.respond_to?(:update_external_index) + file_doc[:uri_ss] = file_doc[:uri_ss].sub(ActiveFedora.fedora_config.credentials[:url], options[:file_fedora_url]) if options[:file_fedora_url].present? && file.is_a?(IndexedFile) + batch << file_doc end end rescue Exception => e puts "#{DateTime.now} Error adding #{uri} to batch: #{e.message}" puts e.backtrace if options[:verbose] batch_uris -= [uri] + batch.compact! batch.delete_if { |doc| ActiveFedora::Base.uri_to_id(uri) == doc[:id] } # Need to worry about removing masterfile attached files from the batch as well? items.where(uri: uri, state: "waiting reindex").update(state: "errored", state_changed_at: DateTime.now) @@ -343,13 +360,17 @@ # Handle speedy_af indexing if obj.is_a?(MasterFile) || obj.is_a?(Admin::Collection) obj.declared_attached_files.each_pair do |name, file| - batch << file.to_solr({}, external_index: true) if file.present? && file.respond_to?(:update_external_index) + next unless file.present? + file_doc = file.to_solr({}, external_index: true) if file.respond_to?(:update_external_index) + file_doc[:uri_ss] = file_doc[:uri_ss].sub(ActiveFedora.fedora_config.credentials[:url], options[:file_fedora_url]) if options[:file_fedora_url].present? && file.is_a?(IndexedFile) + batch << file_doc end end rescue Exception => e puts "#{DateTime.now} Error adding #{uri} to batch: #{e.message}" puts e.backtrace if options[:verbose] batch_uris -= [uri] + batch.compact! batch.delete_if { |doc| ActiveFedora::Base.uri_to_id(uri) == doc[:id] } # Need to worry about removing masterfile attached files from the batch as well? items.where(uri: uri, state: "waiting reindex").update(state: "errored", state_changed_at: DateTime.now) From 1359a560e5493d4c357d2a5060691d46d8480154 Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Mon, 11 Mar 2024 13:03:08 -0400 Subject: [PATCH 14/33] Limit explicitly setting mime-type to just SRT files --- app/models/supplemental_file.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/models/supplemental_file.rb b/app/models/supplemental_file.rb index 9ef41af6f5..42b012138c 100644 --- a/app/models/supplemental_file.rb +++ b/app/models/supplemental_file.rb @@ -29,8 +29,7 @@ def validate_file_type def attach_file(new_file) file.attach(new_file) extension = File.extname(new_file.original_filename) - content_type = Mime::Type.lookup_by_extension(extension.slice(1..-1)).to_s - self.file.content_type = content_type + self.file.content_type = Mime::Type.lookup_by_extension(extension.slice(1..-1)).to_s if extension == '.srt' self.label = file.filename.to_s if label.blank? self.language = tags.include?('caption') ? Settings.caption_default.language : 'eng' end From 605e1e66f2561664d5c0772fe02573ee136c2f3f Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Fri, 8 Mar 2024 13:46:59 -0500 Subject: [PATCH 15/33] Add '.convert_from_srt' method to SupplementalFile --- app/models/supplemental_file.rb | 16 ++++++++++++++++ spec/models/supplemental_file_spec.rb | 8 ++++++++ 2 files changed, 24 insertions(+) diff --git a/app/models/supplemental_file.rb b/app/models/supplemental_file.rb index 42b012138c..2873074e36 100644 --- a/app/models/supplemental_file.rb +++ b/app/models/supplemental_file.rb @@ -45,4 +45,20 @@ def caption? def machine_generated? tags.include?('machine_generated') end + + # Adapted from https://github.com/opencoconut/webvtt-ruby/blob/e07d59220260fce33ba5a0c3b355e3ae88b99457/lib/webvtt/parser.rb#L11-L30 + def self.convert_from_srt(srt) + # normalize timestamps in srt + # This Regex looks for malformed time stamp pieces such as '00:1:00,000', '0:01:00,000', etc. + # When it finds a match it prepends a 0 to the capture group so both of the above examples + # would return '00:01:00,000' + srt.gsub!(/(:|^)(\d)(,|:)/, '\10\2\3') + # convert timestamps and save the file + # VTT uses '.' as its decimal separator, SRT uses ',' so we convert the punctuation + srt.gsub!(/([0-9]{2}:[0-9]{2}:[0-9]{2})([,])([0-9]{3})/, '\1.\3') + # normalize new line character + srt.gsub!("\r\n", "\n") + + "WEBVTT\n\n#{srt}".strip + end end diff --git a/spec/models/supplemental_file_spec.rb b/spec/models/supplemental_file_spec.rb index f5a4f6a430..d7085bdbc7 100644 --- a/spec/models/supplemental_file_spec.rb +++ b/spec/models/supplemental_file_spec.rb @@ -90,4 +90,12 @@ expect(subject.reload.language).to eq "ger" end end + + describe '.convert_from_srt' do + let(:file) { Rails.root.join('spec', 'fixtures', 'captions.srt') } + let(:output) { "WEBVTT\n\n1\n00:00:03.498 --> 00:00:05.000\n- Example Captions" } + it 'converts SRT format captions into VTT captions' do + expect(SupplementalFile.convert_from_srt(File.read(file))).to eq output + end + end end From c40f7784965e61f6b4409e10a503ebd24a2396ea Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Fri, 8 Mar 2024 13:47:57 -0500 Subject: [PATCH 16/33] Captions endpoint refactor to convert SRT to VTT --- app/controllers/master_files_controller.rb | 2 +- .../supplemental_files_controller.rb | 28 +++++++--- .../iiif_supplemental_file_behavior.rb | 2 +- config/routes.rb | 2 +- .../supplemental_files_controller_spec.rb | 51 +++++++++++++++++++ 5 files changed, 75 insertions(+), 10 deletions(-) diff --git a/app/controllers/master_files_controller.rb b/app/controllers/master_files_controller.rb index f88a5392a6..a26932ea7b 100644 --- a/app/controllers/master_files_controller.rb +++ b/app/controllers/master_files_controller.rb @@ -286,7 +286,7 @@ def caption_manifest @caption_url = if caption_id == 'master_file_caption' captions_master_file_path else - master_file_supplemental_file_path(master_file_id: @master_file.id, id: caption_id) + captions_master_file_supplemental_file_path(master_file_id: @master_file.id, id: caption_id) end end diff --git a/app/controllers/supplemental_files_controller.rb b/app/controllers/supplemental_files_controller.rb index c9dbef8ccc..298289a855 100644 --- a/app/controllers/supplemental_files_controller.rb +++ b/app/controllers/supplemental_files_controller.rb @@ -58,12 +58,7 @@ def create end def show - # TODO: Use a master file presenter which reads from solr instead of loading the masterfile from fedora - # FIXME: authorize supplemental file directly (needs supplemental file to have reference to masterfile) - raise Avalon::NotFound, "Supplemental file: #{params[:id]} not found" unless SupplementalFile.exists? params[:id].to_s - - @supplemental_file = SupplementalFile.find(params[:id]) - raise Avalon::NotFound, "Supplemental file: #{@supplemental_file.id} not found" unless @object.supplemental_files.any? { |f| f.id == @supplemental_file.id } + find_supplemental_file # Redirect or proxy the content if Settings.supplemental_files.proxy @@ -107,6 +102,15 @@ def destroy end end + def captions + find_supplemental_file + + file_content = @supplemental_file.file.download + content = @supplemental_file.file.content_type == 'text/srt' ? SupplementalFile.convert_from_srt(file_content) : file_content + + send_data content, filename: @supplemental_file.file.filename.to_s, type: 'text/vtt', disposition: 'attachment' + end + private def set_object @@ -118,6 +122,16 @@ def supplemental_file_params params.fetch(:supplemental_file, {}).permit(:label, :language, :file, tags: []) end + def find_supplemental_file + # TODO: Use a master file presenter which reads from solr instead of loading the masterfile from fedora + # FIXME: authorize supplemental file directly (needs supplemental file to have reference to masterfile) + raise Avalon::NotFound, "Supplemental file: #{params[:id]} not found" unless SupplementalFile.exists? params[:id].to_s + + @supplemental_file = SupplementalFile.find(params[:id]) + raise Avalon::NotFound, "Supplemental file: #{@supplemental_file.id} not found" unless @object.supplemental_files.any? { |f| f.id == @supplemental_file.id } + end + + def handle_error(message:, status:) if request.format == :json render json: { errors: message }, status: status @@ -158,7 +172,7 @@ def object_supplemental_file_path end def authorize_object - action = action_name.to_sym == :show ? :show : :edit + action = [:show, :captions].include?(action_name.to_sym) ? :show : :edit authorize! action, @object, message: "You do not have sufficient privileges to #{action_name} this supplemental file" end end diff --git a/app/models/concerns/iiif_supplemental_file_behavior.rb b/app/models/concerns/iiif_supplemental_file_behavior.rb index d8f355d38b..7e43a9b61d 100644 --- a/app/models/concerns/iiif_supplemental_file_behavior.rb +++ b/app/models/concerns/iiif_supplemental_file_behavior.rb @@ -36,7 +36,7 @@ def object_supplemental_file_url(object, supplemental_file) def determine_rendering_type(mime) case mime - when 'application/pdf', 'application/msword', 'application/vnd.oasis.opendocument.text', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'text/html', 'text/plain', 'text/vtt' + when 'application/pdf', 'application/msword', 'application/vnd.oasis.opendocument.text', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'text/html', 'text/plain', 'text/srt', 'text/vtt' 'Text' when /image\/.+/ 'Image' diff --git a/config/routes.rb b/config/routes.rb index fdc4c039fd..923fcf5bc6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -166,7 +166,7 @@ # Supplemental Files resources :supplemental_files, except: [:new, :index, :edit] do member do - get 'captions', :to => redirect('/master_files/%{master_file_id}/supplemental_files/%{id}') + get 'captions' get 'transcripts', :to => redirect('/master_files/%{master_file_id}/supplemental_files/%{id}') end end diff --git a/spec/controllers/supplemental_files_controller_spec.rb b/spec/controllers/supplemental_files_controller_spec.rb index 5da8ad72a2..52ea93b79c 100644 --- a/spec/controllers/supplemental_files_controller_spec.rb +++ b/spec/controllers/supplemental_files_controller_spec.rb @@ -17,4 +17,55 @@ RSpec.describe SupplementalFilesController, type: :controller do it_behaves_like "a nested controller for", MasterFile it_behaves_like "a nested controller for", MediaObject + + describe 'captions endpoint for MasterFile' do + let(:supplemental_file) { FactoryBot.create(:supplemental_file, :with_caption_file, :with_caption_tag) } + # This should return the minimal set of values that should be in the session + # in order to pass any filters (e.g. authentication) defined in + # SupplementalFilesController. Be sure to keep this updated too. + let(:valid_session) { {} } + + + describe 'security' do + let(:master_file) { FactoryBot.create(:master_file, :with_media_object, supplemental_files: [supplemental_file]) } + + context 'with unauthenticated user' do + it 'should return 401' do + expect(get :captions, params: { master_file_id: master_file.id, id: supplemental_file.id }).to have_http_status(401) + end + end + context 'with end-user without permissions' do + before do + login_as :user + end + it 'should return 401' do + expect(get :captions, params: { master_file_id: master_file.id, id: supplemental_file.id }).to have_http_status(401) + end + end + end + + describe "GET #captions" do + let(:public_media_object) { FactoryBot.create(:fully_searchable_media_object) } + let(:master_file) { FactoryBot.create(:master_file, media_object: public_media_object, supplemental_files: [supplemental_file]) } + before { allow(Settings.supplemental_files).to receive(:proxy).and_return(true) } + + it "returns the caption file content" do + get :captions, params: { master_file_id: master_file.id, id: supplemental_file.id }, session: valid_session + expect(response).to have_http_status(200) + expect(response.header["Content-Type"]).to eq 'text/vtt' + expect(response.body).to eq supplemental_file.file.download + end + + context 'with SRT caption' do + let(:supplemental_file) { FactoryBot.create(:supplemental_file, :with_caption_tag, :with_caption_srt_file) } + let(:file) { Rails.root.join('spec', 'fixtures', 'captions.srt')} + it 'returns the caption file content in VTT format' do + get :captions, params: { master_file_id: master_file.id, id: supplemental_file.id }, session: valid_session + expect(response).to have_http_status(200) + expect(response.header["Content-Type"]).to eq 'text/vtt' + expect(response.body).to eq SupplementalFile.convert_from_srt(File.read(file)) + end + end + end + end end From 7c56bfcd592b86b98db8c835e66895c202f46e90 Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Fri, 8 Mar 2024 14:31:19 -0500 Subject: [PATCH 17/33] Fix supplemental file routing spec --- spec/routing/supplemental_files_routing_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/routing/supplemental_files_routing_spec.rb b/spec/routing/supplemental_files_routing_spec.rb index bee254f4b5..0349520e97 100644 --- a/spec/routing/supplemental_files_routing_spec.rb +++ b/spec/routing/supplemental_files_routing_spec.rb @@ -28,11 +28,12 @@ it "routes to #update" do expect(:put => "/master_files/abc1234/supplemental_files/edf567").to route_to("supplemental_files#update", master_file_id: 'abc1234', id: 'edf567') end + it "routes to #captions" do + expect(:get => "/master_files/abc1234/supplemental_files/edf567/captions").to route_to("supplemental_files#captions", master_file_id: 'abc1234', id: 'edf567') + end # Redirects are not testable from the routing spec out of the box. # Forcing the tests to `type: :request` to keep routing tests in one place. it "redirects to supplemental_files#show", type: :request do - get "/master_files/abc1234/supplemental_files/edf567/captions" - expect(response).to redirect_to("/master_files/abc1234/supplemental_files/edf567") get "/master_files/abc1234/supplemental_files/edf567/transcripts" expect(response).to redirect_to("/master_files/abc1234/supplemental_files/edf567") end From 4ab5fb0c12e94c49c9a137c7143b5a04fb7b66a9 Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Mon, 11 Mar 2024 13:46:52 -0400 Subject: [PATCH 18/33] Refactor to avoid modifying input SRT in place Also includes a change to the test for readability --- app/models/supplemental_file.rb | 8 ++++---- spec/models/supplemental_file_spec.rb | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/models/supplemental_file.rb b/app/models/supplemental_file.rb index 2873074e36..84722d1fa5 100644 --- a/app/models/supplemental_file.rb +++ b/app/models/supplemental_file.rb @@ -52,13 +52,13 @@ def self.convert_from_srt(srt) # This Regex looks for malformed time stamp pieces such as '00:1:00,000', '0:01:00,000', etc. # When it finds a match it prepends a 0 to the capture group so both of the above examples # would return '00:01:00,000' - srt.gsub!(/(:|^)(\d)(,|:)/, '\10\2\3') + conversion = srt.gsub(/(:|^)(\d)(,|:)/, '\10\2\3') # convert timestamps and save the file # VTT uses '.' as its decimal separator, SRT uses ',' so we convert the punctuation - srt.gsub!(/([0-9]{2}:[0-9]{2}:[0-9]{2})([,])([0-9]{3})/, '\1.\3') + conversion.gsub!(/([0-9]{2}:[0-9]{2}:[0-9]{2})([,])([0-9]{3})/, '\1.\3') # normalize new line character - srt.gsub!("\r\n", "\n") + conversion.gsub!("\r\n", "\n") - "WEBVTT\n\n#{srt}".strip + "WEBVTT\n\n#{conversion}".strip end end diff --git a/spec/models/supplemental_file_spec.rb b/spec/models/supplemental_file_spec.rb index d7085bdbc7..da66391a37 100644 --- a/spec/models/supplemental_file_spec.rb +++ b/spec/models/supplemental_file_spec.rb @@ -92,10 +92,10 @@ end describe '.convert_from_srt' do - let(:file) { Rails.root.join('spec', 'fixtures', 'captions.srt') } + let(:input) { "1\n00:00:03,498 --> 00:00:05,000\n- Example Captions\n" } let(:output) { "WEBVTT\n\n1\n00:00:03.498 --> 00:00:05.000\n- Example Captions" } it 'converts SRT format captions into VTT captions' do - expect(SupplementalFile.convert_from_srt(File.read(file))).to eq output + expect(SupplementalFile.convert_from_srt(input)).to eq output end end end From edc47537ef1e912a6c7eb40760a0a741486d2f00 Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Mon, 11 Mar 2024 15:41:00 -0400 Subject: [PATCH 19/33] Allow fuzzier searching on playlist titles --- app/models/playlist.rb | 6 +++++- spec/models/playlist_spec.rb | 11 +++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/models/playlist.rb b/app/models/playlist.rb index 6d90ee0a64..56b58c7a25 100644 --- a/app/models/playlist.rb +++ b/app/models/playlist.rb @@ -15,7 +15,11 @@ class Playlist < ActiveRecord::Base belongs_to :user scope :by_user, ->(user) { where(user_id: user.id) } - scope :title_like, ->(title_filter) { where("title LIKE ?", "%#{title_filter}%")} + scope :title_like, ->(title_filter) do + term_array = title_filter.split.map { |term| "%#{sanitize_sql_like(term).downcase}%" } + query = Array.new(term_array.size, "LOWER(title) LIKE ?").join(" AND ") + where(query, *term_array) + end scope :with_tag, ->(tag_filter) { where("tags LIKE ?", "%\n- #{tag_filter}\n%") } validates :user, presence: true diff --git a/spec/models/playlist_spec.rb b/spec/models/playlist_spec.rb index 23f40fe9a8..4a0ea7bde6 100644 --- a/spec/models/playlist_spec.rb +++ b/spec/models/playlist_spec.rb @@ -157,13 +157,20 @@ let(:playlist3) { FactoryBot.create(:playlist, title: 'Favorites') } let(:title_filter) { 'moose' } it 'returns playlists with matching titles' do - # Commented out since case insensitivity is default for mysql but not postgres - # expect(Playlist.title_like(title_filter)).to include(playlist1) + expect(Playlist.title_like(title_filter)).to include(playlist1) expect(Playlist.title_like(title_filter)).to include(playlist2) end it 'does not return playlists without matching titles' do expect(Playlist.title_like(title_filter)).not_to include(playlist3) end + it 'searches on multiple terms' do + expect(Playlist.title_like('Fav Moose')).to include(playlist2) + expect(Playlist.title_like('Fav Moose')).not_to include(playlist1) + expect(Playlist.title_like('Fav Moose')).not_to include(playlist3) + expect(Playlist.title_like('Moose Fav')).to include(playlist2) + expect(Playlist.title_like('Moose Fav')).not_to include(playlist1) + expect(Playlist.title_like('Moose Fav')).not_to include(playlist3) + end end describe 'with_tag' do let(:playlist1) { FactoryBot.create(:playlist, tags: ['Moose']) } From 97674d31e8c06f1945423c69144a1cb0e7764972 Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Tue, 12 Mar 2024 16:47:23 -0400 Subject: [PATCH 20/33] Disable create thumbnail button for audio items --- app/views/media_objects/_thumbnail.html.erb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/media_objects/_thumbnail.html.erb b/app/views/media_objects/_thumbnail.html.erb index 96af889c9e..e071081bf1 100644 --- a/app/views/media_objects/_thumbnail.html.erb +++ b/app/views/media_objects/_thumbnail.html.erb @@ -56,7 +56,8 @@ Unless required by applicable law or agreed to in writing, software distributed if (player && player.player != undefined) { player.player.on('loadedmetadata', () => { let thumbnailBtn = document.getElementById('create-thumbnail-btn'); - if (thumbnailBtn) { + // Leave 'Create Thumbnail' button disabled when item is audio + if (thumbnailBtn && !player.player.isAudio()) { thumbnailBtn.disabled = false; } }); @@ -67,7 +68,8 @@ Unless required by applicable law or agreed to in writing, software distributed */ setTimeout(() => { let thumbnailBtn = document.getElementById('create-thumbnail-btn'); - if (thumbnailBtn && thumbnailBtn.disabled && player.player?.readyState() === 4) { + // Leave 'Create Thumbnail' button disabled when item is audio + if (thumbnailBtn && thumbnailBtn.disabled && player.player?.readyState() === 4 && !player.player.isAudio()) { thumbnailBtn.disabled = false; } }, 100); From 0c9cb61fe1e4a182503df8ce3d8aa1bfa861554d Mon Sep 17 00:00:00 2001 From: Dananji Withana Date: Wed, 13 Mar 2024 13:43:43 -0400 Subject: [PATCH 21/33] Reduce Video.js icon size for mobile devices to 90% (#5705) * Reduce Video.js icon size for mobile devices to 90% * Adjust audio player height to match font-size --- app/javascript/components/Ramp.scss | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/javascript/components/Ramp.scss b/app/javascript/components/Ramp.scss index b57cd37d36..fb47b8101e 100644 --- a/app/javascript/components/Ramp.scss +++ b/app/javascript/components/Ramp.scss @@ -27,10 +27,16 @@ .video-js .vjs-big-play-button { scale: 1.5; } - } - .video-js .vjs-control-bar { - font-size: 100% !important; + .video-js .vjs-control-bar { + font-size: 90% !important; + } + + // reduce player height to match with adjusted font-size + // for smaller screens + .video-js.vjs-audio { + min-height: 2.9em; + } } } @@ -367,4 +373,4 @@ height: 109vh !important; } } -} \ No newline at end of file +} From f89139bcff80d7fb8a6e1a3be7850d7f389b5c4b Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Wed, 13 Mar 2024 16:31:11 -0400 Subject: [PATCH 22/33] Remove captions from HLS manifest --- app/controllers/master_files_controller.rb | 14 +----- .../master_files/caption_manifest.m3u8.erb | 23 ---------- app/views/master_files/hls_manifest.m3u8.erb | 17 +------ .../modules/player/_video_js_element.html.erb | 5 +-- config/application.rb | 1 - config/routes.rb | 1 - .../master_files_controller_spec.rb | 45 ------------------- spec/routing/master_files_routing_spec.rb | 3 -- 8 files changed, 4 insertions(+), 105 deletions(-) delete mode 100644 app/views/master_files/caption_manifest.m3u8.erb diff --git a/app/controllers/master_files_controller.rb b/app/controllers/master_files_controller.rb index a26932ea7b..31f2011ffe 100644 --- a/app/controllers/master_files_controller.rb +++ b/app/controllers/master_files_controller.rb @@ -21,8 +21,8 @@ class MasterFilesController < ApplicationController include NoidValidator before_action :authenticate_user!, :only => [:create] - before_action :set_masterfile_proxy, except: [:create, :oembed, :attach_structure, :attach_captions, :delete_structure, :delete_captions, :destroy, :update, :set_structure] - before_action :set_masterfile, only: [:attach_structure, :attach_captions, :delete_structure, :delete_captions, :destroy, :update, :set_structure] + before_action :set_masterfile_proxy, except: [:create, :oembed, :attach_structure, :delete_structure, :destroy, :update, :set_structure] + before_action :set_masterfile, only: [:attach_structure, :delete_structure, :destroy, :update, :set_structure] before_action :ensure_readable_filedata, :only => [:create] skip_before_action :verify_authenticity_token, only: [:set_structure, :delete_structure] @@ -280,16 +280,6 @@ def hls_manifest end end - def caption_manifest - return head :unauthorized if cannot?(:read, @master_file) - caption_id = params[:c_id] - @caption_url = if caption_id == 'master_file_caption' - captions_master_file_path - else - captions_master_file_supplemental_file_path(master_file_id: @master_file.id, id: caption_id) - end - end - def structure authorize! :read, @master_file, message: "You do not have sufficient privileges" render json: @master_file.structuralMetadata.as_json diff --git a/app/views/master_files/caption_manifest.m3u8.erb b/app/views/master_files/caption_manifest.m3u8.erb deleted file mode 100644 index c2101f5e9b..0000000000 --- a/app/views/master_files/caption_manifest.m3u8.erb +++ /dev/null @@ -1,23 +0,0 @@ -<%# -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 --- -%> -#EXTM3U -#EXT-X-TARGETDURATION:<%= @master_file.duration %> -#EXT-X-VERSION:3 -#EXT-X-MEDIA-SEQUENCE:0 -#EXT-X-PLAYLIST-TYPE:VOD -#EXTINF:<%= @master_file.duration %> -<%= @caption_url %> -#EXT-X-ENDLIST diff --git a/app/views/master_files/hls_manifest.m3u8.erb b/app/views/master_files/hls_manifest.m3u8.erb index 5a6d528854..7b2db72b83 100644 --- a/app/views/master_files/hls_manifest.m3u8.erb +++ b/app/views/master_files/hls_manifest.m3u8.erb @@ -14,22 +14,7 @@ Unless required by applicable law or agreed to in writing, software distributed --- END LICENSE_HEADER BLOCK --- %> #EXTM3U -<% if @master_file.has_captions? %> -<% captions_list = @master_file.supplemental_file_captions %> -<% captions_list.append(@master_file.captions) if @master_file.captions&.content %> -<% captions_list.each_with_index do |caption, index| %> -<% label = caption.is_a?(SupplementalFile) ? caption.label : 'English' %> -<% language = caption.is_a?(SupplementalFile) ? caption.language : 'en' %> -<% id = caption.is_a?(SupplementalFile) ? caption.id : 'master_file_caption' %> -#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",LANGUAGE="<%= language %>",NAME="<%= label %>",DEFAULT=<%= index == 0 ? "YES" : "NO" %>,AUTOSELECT=YES,URI="<%= caption_manifest_master_file_path(c_id: id) %>" -<% end %> -<% @hls_streams.each do |hls| %> -#EXT-X-STREAM-INF:BANDWIDTH=<%= hls[:bitrate] %>,SUBTITLES="subs" -<%= hls[:url] %> -<% end %> -<% else %> <% @hls_streams.each do |hls| %> #EXT-X-STREAM-INF:BANDWIDTH=<%= hls[:bitrate] %> <%= hls[:url] %> -<% end %> -<% end %> +<% end %> \ No newline at end of file diff --git a/app/views/modules/player/_video_js_element.html.erb b/app/views/modules/player/_video_js_element.html.erb index 24c8afe746..38e869a58b 100644 --- a/app/views/modules/player/_video_js_element.html.erb +++ b/app/views/modules/player/_video_js_element.html.erb @@ -64,10 +64,7 @@ Unless required by applicable law or agreed to in writing, software distributed <% section_info[:stream_hls].each do |hls| %> <% end %> - <%# Captions are contained in the HLS manifest and so we do not need to manually provide them to VideoJS here %> - <%# TODO: Reenable if/when we remove captions from HLS %> - <% skip_captions = true %> - <% if section_info[:caption_paths].present? && !skip_captions %> + <% if section_info[:caption_paths].present? %> <% section_info[:caption_paths].each do |c| %> label="<%= c[:label] %>" <% end %> srclang="<%= c[:language] %>" kind="subtitles" type="<%= c[:mime_type] %>" src="<%= c[:path] %>"> <% end %> diff --git a/config/application.rb b/config/application.rb index 032119dc35..18a8f037b7 100644 --- a/config/application.rb +++ b/config/application.rb @@ -48,7 +48,6 @@ class Application < Rails::Application resource '/master_files/*/structure.json', headers: :any, methods: [:get, :post, :delete] resource '/master_files/*/waveform.json', headers: :any, methods: [:get] resource '/master_files/*/*.m3u8', headers: :any, credentials: true, methods: [:get, :head] - resource '/master_files/*/caption_manifest/*', headers: :any, methods: [:get] resource '/master_files/*/captions', headers: :any, methods: [:get] resource '/master_files/*/supplemental_files/*', headers: :any, methods: [:get] resource '/playlists/*/manifest.json', headers: :any, credentials: true, methods: [:get] diff --git a/config/routes.rb b/config/routes.rb index 923fcf5bc6..3f803d67d1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -155,7 +155,6 @@ get :captions get :waveform match ':quality.m3u8', to: 'master_files#hls_manifest', via: [:get], as: :hls_manifest - get 'caption_manifest/:c_id', to: 'master_files#caption_manifest', as: :caption_manifest get 'structure', to: 'master_files#structure', constraints: { format: 'json' } post 'structure', to: 'master_files#set_structure', constraints: { format: 'json' } delete 'structure', to: 'master_files#delete_structure', constraints: { format: 'json' } diff --git a/spec/controllers/master_files_controller_spec.rb b/spec/controllers/master_files_controller_spec.rb index fbe2f17e94..0f2fa35d41 100644 --- a/spec/controllers/master_files_controller_spec.rb +++ b/spec/controllers/master_files_controller_spec.rb @@ -647,51 +647,6 @@ class << file end end - describe '#caption_manifest' do - let(:media_object) { FactoryBot.create(:published_media_object) } - let(:master_file) { FactoryBot.create(:master_file, :with_captions, media_object: media_object) } - let(:public_media_object) { FactoryBot.create(:published_media_object, visibility: 'public') } - let(:public_master_file) { FactoryBot.create(:master_file, :with_captions, media_object: public_media_object) } - - context 'master file has been deleted' do - before do - master_file.destroy - end - - it 'returns unauthorized (401)' do - expect(get('caption_manifest', params: { id: master_file.id, c_id: 1 }, xhr: true)).to have_http_status(:unauthorized) - end - end - - it 'returns unauthorized (401) if cannot read the master file' do - expect(get('caption_manifest', params: { id: master_file.id, c_id: 1 }, xhr: true)).to have_http_status(:unauthorized) - end - - it 'returns the caption manifest' do - login_as :administrator - expect(get('caption_manifest', params: { id: master_file.id, c_id: 1 }, xhr: true)).to have_http_status(:ok) - expect(response.content_type).to eq 'application/x-mpegURL; charset=utf-8' - end - - it 'returns a manifest if public' do - expect(get('caption_manifest', params: { id: public_master_file.id, c_id: 1 }, xhr: true)).to have_http_status(:ok) - expect(response.content_type).to eq 'application/x-mpegURL; charset=utf-8' - expect(get('caption_manifest', params: { id: public_master_file.id, c_id: 'master_file_caption' }, xhr: true)).to have_http_status(:ok) - expect(response.content_type).to eq 'application/x-mpegURL; charset=utf-8' - end - - context 'read from solr' do - it 'should not read from fedora' do - public_master_file - perform_enqueued_jobs(only: MediaObjectIndexingJob) - WebMock.reset_executed_requests! - login_as :administrator - get('caption_manifest', params: { id: public_master_file.id, c_id: 1 }, xhr: true) - expect(a_request(:any, /#{ActiveFedora.fedora.base_uri}/)).not_to have_been_made - end - end - end - describe '#move' do let(:master_file) { FactoryBot.create(:master_file, :with_media_object) } let(:target_media_object) { FactoryBot.create(:media_object) } diff --git a/spec/routing/master_files_routing_spec.rb b/spec/routing/master_files_routing_spec.rb index 8fb7bf4c94..8be9b032c2 100644 --- a/spec/routing/master_files_routing_spec.rb +++ b/spec/routing/master_files_routing_spec.rb @@ -19,8 +19,5 @@ it "routes to #move" do expect(:post => "/master_files/abc1234/move").to route_to("master_files#move", id: 'abc1234') end - it "routes to #caption_manifest" do - expect(:get => "/master_files/abc1234/caption_manifest/def5678").to route_to("master_files#caption_manifest", id: 'abc1234', c_id: 'def5678') - end end end From ba1b2948c48e72a78618267cf93cc1c99831d460 Mon Sep 17 00:00:00 2001 From: dwithana Date: Thu, 14 Mar 2024 16:35:21 -0400 Subject: [PATCH 23/33] Latest code from Ramp --- package.json | 2 +- yarn.lock | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 10efefdb09..78d09be615 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": "^3.0.0", + "@samvera/ramp": "https://github.com/samvera-labs/ramp.git", "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 a5a9623882..5a9beee9ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1337,10 +1337,9 @@ estree-walker "^2.0.2" picomatch "^2.3.1" -"@samvera/ramp@^3.0.0": +"@samvera/ramp@https://github.com/samvera-labs/ramp.git": version "3.0.0" - resolved "https://registry.yarnpkg.com/@samvera/ramp/-/ramp-3.0.0.tgz#41251e0fb30ceb86d5d3905d27d9fa8089c193fe" - integrity sha512-Hapi2XJhNUHdye0XyRryBU8DlA51jvE7uDUy+cRflnjxDKMZuO6AqgqrZG0SRraS1ziXa1o5t2awAqzEzRi/KQ== + resolved "https://github.com/samvera-labs/ramp.git#497fffa8b863486d57b0fc67cba3237beb422b5c" dependencies: "@rollup/plugin-json" "^6.0.1" "@silvermine/videojs-quality-selector" "^1.2.4" @@ -4831,9 +4830,9 @@ react-dom@^17.0.1: scheduler "^0.20.2" react-error-boundary@^4.0.11: - version "4.0.12" - resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.12.tgz#59f8f1dbc53bbbb34fc384c8db7cf4082cb63e2c" - integrity sha512-kJdxdEYlb7CPC1A0SeUY38cHpjuu6UkvzKiAmqmOFL21VRfMhOcWxTCBgLVCO0VEMh9JhFNcVaXlV4/BTpiwOA== + version "4.0.13" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.13.tgz#80386b7b27b1131c5fbb7368b8c0d983354c7947" + integrity sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ== dependencies: "@babel/runtime" "^7.12.5" @@ -5183,9 +5182,9 @@ safe-json-parse@4.0.0: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sanitize-html@^2.10.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.11.0.tgz#9a6434ee8fcaeddc740d8ae7cd5dd71d3981f8f6" - integrity sha512-BG68EDHRaGKqlsNjJ2xUB7gpInPA8gVx/mvjO743hZaeMCZ2DwzW7xvsqZ+KNU4QKwj86HJ3uu2liISf2qBBUA== + version "2.12.1" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.12.1.tgz#280a0f5c37305222921f6f9d605be1f6558914c7" + integrity sha512-Plh+JAn0UVDpBRP/xEjsk+xDCoOvMBwQUf/K+/cBAVuTbtX8bj2VB7S1sL1dssVpykqp0/KPSesHrqXtokVBpA== dependencies: deepmerge "^4.2.2" escape-string-regexp "^4.0.0" From a7824018cdb207b1813babbeffd00870e9ed1747 Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Mon, 18 Mar 2024 09:22:30 -0400 Subject: [PATCH 24/33] Set caption language to system default when migrating --- lib/tasks/avalon_migrations.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/avalon_migrations.rake b/lib/tasks/avalon_migrations.rake index 96c45e8b0e..0ce4d442cc 100644 --- a/lib/tasks/avalon_migrations.rake +++ b/lib/tasks/avalon_migrations.rake @@ -38,7 +38,7 @@ namespace :avalon do filename = caption_file.original_name content_type = caption_file.mime_type # Create and populate new SupplementalFile record using original metadata - supplemental_file = SupplementalFile.new(label: filename, tags: ['caption'], language: 'eng') + supplemental_file = SupplementalFile.new(label: filename, tags: ['caption'], language: Settings.caption_default.language) supplemental_file.file.attach(io: ActiveFedora::FileIO.new(caption_file), filename: filename, content_type: content_type, identify: false) # Skip validation so that incorrect mimetypes do not bomb the entire task supplemental_file.save(validate: false) From 0fe383da971d42b78adb6ad9b0287e2808025a78 Mon Sep 17 00:00:00 2001 From: cjcolvar Date: Mon, 25 Mar 2024 10:34:10 -0400 Subject: [PATCH 25/33] Bump JS dependencies --- yarn.lock | 182 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 133 insertions(+), 49 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5a9beee9ab..dbe7b0c6ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25,6 +25,14 @@ "@babel/highlight" "^7.22.10" chalk "^2.4.2" +"@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.1": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== + dependencies: + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" + "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" @@ -61,6 +69,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.1.tgz#e67e06f68568a4ebf194d1c6014235344f0476d0" + integrity sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A== + dependencies: + "@babel/types" "^7.24.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.14.5": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.15.4.tgz#3d0e43b00c5e49fdb6c57e421601a7a658d5f835" @@ -128,6 +146,11 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + "@babel/helper-environment-visitor@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" @@ -141,6 +164,14 @@ "@babel/template" "^7.22.5" "@babel/types" "^7.22.5" +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + "@babel/helper-hoist-variables@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" @@ -241,11 +272,21 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== +"@babel/helper-string-parser@^7.23.4": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" + integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== + "@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9": version "7.15.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + "@babel/helper-validator-identifier@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" @@ -297,11 +338,26 @@ chalk "^2.4.2" js-tokens "^4.0.0" +"@babel/highlight@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" + integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/parser@^7.22.10", "@babel/parser@^7.22.5": version "7.22.10" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.10.tgz#e37634f9a12a1716136c44624ef54283cabd3f55" integrity sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ== +"@babel/parser@^7.24.0", "@babel/parser@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.1.tgz#1e416d3627393fab1cb5b0f2f1796a100ae9133a" + integrity sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.5.tgz#87245a21cd69a73b0b81bcda98d443d6df08f05e" @@ -1049,6 +1105,15 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/template@^7.22.15": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" + integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + "@babel/template@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" @@ -1059,19 +1124,19 @@ "@babel/types" "^7.22.5" "@babel/traverse@^7.22.10": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.10.tgz#20252acb240e746d27c2e82b4484f199cf8141aa" - integrity sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig== - dependencies: - "@babel/code-frame" "^7.22.10" - "@babel/generator" "^7.22.10" - "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-function-name" "^7.22.5" + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.1.tgz#d65c36ac9dd17282175d1e4a3c49d5b7988f530c" + integrity sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ== + dependencies: + "@babel/code-frame" "^7.24.1" + "@babel/generator" "^7.24.1" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.22.10" - "@babel/types" "^7.22.10" - debug "^4.1.0" + "@babel/parser" "^7.24.1" + "@babel/types" "^7.24.0" + debug "^4.3.1" globals "^11.1.0" "@babel/types@^7.14.9", "@babel/types@^7.15.4", "@babel/types@^7.4.4": @@ -1091,6 +1156,15 @@ "@babel/helper-validator-identifier" "^7.22.5" to-fast-properties "^2.0.0" +"@babel/types@^7.23.0", "@babel/types@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" + integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@discoveryjs/json-ext@^0.5.0": version "0.5.7" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" @@ -1179,6 +1253,15 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + "@jridgewell/resolve-uri@3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" @@ -1194,6 +1277,11 @@ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + "@jridgewell/source-map@^0.3.3": version "0.3.5" resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91" @@ -1220,6 +1308,14 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@jridgewell/trace-mapping@^0.3.9": version "0.3.17" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" @@ -2627,6 +2723,13 @@ debug@^4.1.0, debug@^4.1.1: dependencies: ms "2.1.2" +debug@^4.3.1: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + deepmerge@^4.0, deepmerge@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" @@ -3048,15 +3151,10 @@ find-up@^4.0.0: locate-path "^5.0.0" path-exists "^4.0.0" -follow-redirects@^1.0.0: - version "1.15.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== - -follow-redirects@^1.15.4: - version "1.15.5" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" - integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== +follow-redirects@^1.0.0, follow-redirects@^1.15.4: + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== for-each@^0.3.3: version "0.3.3" @@ -4135,11 +4233,6 @@ nanocolors@^0.2.12: resolved "https://registry.yarnpkg.com/nanocolors/-/nanocolors-0.2.12.tgz#4d05932e70116078673ea4cc6699a1c56cc77777" integrity sha512-SFNdALvzW+rVlzqexid6epYdt8H9Zol7xDoQarioEFcFN0JHo4CYNztAxmtfgGTVRCmFlEOqqhBpoFGKqSAMug== -nanoid@^3.3.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== - nanoid@^3.3.7: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" @@ -4670,23 +4763,14 @@ postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.3.11: - version "8.4.35" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.35.tgz#60997775689ce09011edf083a549cea44aabe2f7" - integrity sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA== +postcss@^8.3.11, postcss@^8.4.21, postcss@^8.4.24: + version "8.4.38" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" + integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== dependencies: nanoid "^3.3.7" picocolors "^1.0.0" - source-map-js "^1.0.2" - -postcss@^8.4.21, postcss@^8.4.24: - version "8.4.28" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.28.tgz#c6cc681ed00109072816e1557f889ef51cf950a5" - integrity sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw== - dependencies: - nanoid "^3.3.6" - picocolors "^1.0.0" - source-map-js "^1.0.2" + source-map-js "^1.2.0" pretty-error@^3.0.4: version "3.0.4" @@ -5267,12 +5351,7 @@ selfsigned@^2.1.1: dependencies: node-forge "^1" -semver@^6.0.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^6.3.1: +semver@^6.0.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -5417,11 +5496,16 @@ sockjs@^0.3.24: uuid "^8.3.2" websocket-driver "^0.7.4" -"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2: +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + source-map-support@~0.5.12: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" @@ -5883,9 +5967,9 @@ webpack-cli@4: webpack-merge "^5.7.3" webpack-dev-middleware@^5.3.1: - version "5.3.3" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f" - integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA== + version "5.3.4" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517" + integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q== dependencies: colorette "^2.0.10" memfs "^3.4.3" From c45b2dee60fd211f2e69391f0cfbcebda7cf27b2 Mon Sep 17 00:00:00 2001 From: cjcolvar Date: Mon, 25 Mar 2024 11:03:51 -0400 Subject: [PATCH 26/33] Bump ruby dependencies --- Gemfile | 4 +- Gemfile.lock | 164 +++++++++++++++++++++++++-------------------------- 2 files changed, 84 insertions(+), 84 deletions(-) diff --git a/Gemfile b/Gemfile index c4a93d60a5..52377a3cdd 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,7 @@ gem 'bootsnap', require: false gem 'listen' gem 'net-smtp', require: false gem 'psych', '< 4' -gem 'rails', '=7.0.4.3' +gem 'rails', '~>7.0.8' gem 'sprockets', '~>3.7.2' #gem 'sprockets-rails', require: 'sprockets/railtie' gem 'sqlite3' @@ -155,7 +155,7 @@ group :production do gem 'google-analytics-rails', '1.1.0' gem 'lograge' gem 'okcomputer' - gem 'puma', '>= 4.3.8' + gem 'puma', '>= 6.4.2' end # Install the bundle --with aws when running on Amazon Elastic Beanstalk diff --git a/Gemfile.lock b/Gemfile.lock index df5c2a3dc7..85d762674a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -76,47 +76,47 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.0.4.3) - actionpack (= 7.0.4.3) - activesupport (= 7.0.4.3) + actioncable (7.0.8.1) + actionpack (= 7.0.8.1) + activesupport (= 7.0.8.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.4.3) - actionpack (= 7.0.4.3) - activejob (= 7.0.4.3) - activerecord (= 7.0.4.3) - activestorage (= 7.0.4.3) - activesupport (= 7.0.4.3) + actionmailbox (7.0.8.1) + actionpack (= 7.0.8.1) + activejob (= 7.0.8.1) + activerecord (= 7.0.8.1) + activestorage (= 7.0.8.1) + activesupport (= 7.0.8.1) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.4.3) - actionpack (= 7.0.4.3) - actionview (= 7.0.4.3) - activejob (= 7.0.4.3) - activesupport (= 7.0.4.3) + actionmailer (7.0.8.1) + actionpack (= 7.0.8.1) + actionview (= 7.0.8.1) + activejob (= 7.0.8.1) + activesupport (= 7.0.8.1) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.4.3) - actionview (= 7.0.4.3) - activesupport (= 7.0.4.3) - rack (~> 2.0, >= 2.2.0) + actionpack (7.0.8.1) + actionview (= 7.0.8.1) + activesupport (= 7.0.8.1) + rack (~> 2.0, >= 2.2.4) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.4.3) - actionpack (= 7.0.4.3) - activerecord (= 7.0.4.3) - activestorage (= 7.0.4.3) - activesupport (= 7.0.4.3) + actiontext (7.0.8.1) + actionpack (= 7.0.8.1) + activerecord (= 7.0.8.1) + activestorage (= 7.0.8.1) + activesupport (= 7.0.8.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.4.3) - activesupport (= 7.0.4.3) + actionview (7.0.8.1) + activesupport (= 7.0.8.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -153,8 +153,8 @@ GEM om (~> 3.1) rdf (~> 3.2) rdf-rdfxml (~> 3.2) - activejob (7.0.4.3) - activesupport (= 7.0.4.3) + activejob (7.0.8.1) + activesupport (= 7.0.8.1) globalid (>= 0.3.6) activejob-traffic_control (0.1.3) activejob (>= 4.2) @@ -163,25 +163,25 @@ GEM activejob-uniqueness (0.2.5) activejob (>= 4.2, < 7.1) redlock (>= 1.2, < 2) - activemodel (7.0.4.3) - activesupport (= 7.0.4.3) - activerecord (7.0.4.3) - activemodel (= 7.0.4.3) - activesupport (= 7.0.4.3) + activemodel (7.0.8.1) + activesupport (= 7.0.8.1) + activerecord (7.0.8.1) + activemodel (= 7.0.8.1) + activesupport (= 7.0.8.1) activerecord-session_store (2.0.0) actionpack (>= 5.2.4.1) activerecord (>= 5.2.4.1) multi_json (~> 1.11, >= 1.11.2) rack (>= 2.0.8, < 3) railties (>= 5.2.4.1) - activestorage (7.0.4.3) - actionpack (= 7.0.4.3) - activejob (= 7.0.4.3) - activerecord (= 7.0.4.3) - activesupport (= 7.0.4.3) + activestorage (7.0.8.1) + actionpack (= 7.0.8.1) + activejob (= 7.0.8.1) + activerecord (= 7.0.8.1) + activesupport (= 7.0.8.1) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.4.3) + activesupport (7.0.8.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -343,7 +343,7 @@ GEM activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) - date (3.3.3) + date (3.3.4) declarative (0.0.20) deep_merge (1.2.2) deprecation (1.1.0) @@ -354,7 +354,7 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) - devise_invitable (2.0.8) + devise_invitable (2.0.9) actionmailer (>= 5.0) devise (>= 4.6) diff-lcs (1.5.0) @@ -446,8 +446,8 @@ GEM fugit (1.8.1) et-orbi (~> 1, >= 1.2.7) raabro (~> 1.4) - globalid (1.1.0) - activesupport (>= 5.0) + globalid (1.2.1) + activesupport (>= 6.1) google-analytics-rails (1.1.0) google-apis-core (0.11.0) addressable (~> 2.5, >= 2.5.1) @@ -499,7 +499,7 @@ GEM hydra-access-controls (= 12.1.0) hydra-core (= 12.1.0) rails (>= 5.2, < 7.1) - i18n (1.14.1) + i18n (1.14.4) concurrent-ruby (~> 1.0) iconv (1.0.8) ims-lti (1.1.13) @@ -563,7 +563,7 @@ GEM activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.21.3) + loofah (2.22.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -575,42 +575,42 @@ GEM rexml scrub_rb (>= 1.0.1, < 2) unf - marcel (1.0.2) + marcel (1.0.4) matrix (0.4.2) memoist (0.16.2) method_source (1.0.0) mime-types (3.4.1) mime-types-data (~> 3.2015) mime-types-data (3.2022.0105) - mini_mime (1.1.2) - mini_portile2 (2.8.4) + mini_mime (1.1.5) + mini_portile2 (2.8.5) minitar (0.9) - minitest (5.21.2) + minitest (5.22.3) msgpack (1.6.0) multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.3.0) mysql2 (0.5.5) - net-imap (0.3.4) + net-imap (0.4.10) date net-protocol net-ldap (0.18.0) net-pop (0.1.2) net-protocol - net-protocol (0.2.1) + net-protocol (0.2.2) timeout net-scp (4.0.0) net-ssh (>= 2.6.5, < 8.0.0) - net-smtp (0.3.3) + net-smtp (0.4.0.1) net-protocol net-ssh (7.0.1) netrc (0.11.0) - nio4r (2.5.8) + nio4r (2.7.1) noid (0.9.0) noid-rails (3.1.0) actionpack (>= 5.0.0, < 7.1) noid (~> 0.9) - nokogiri (1.15.3) + nokogiri (1.16.3) mini_portile2 (~> 2.8.2) racc (~> 1.4) nom-xml (1.2.0) @@ -656,12 +656,12 @@ GEM pry (>= 0.10.4) psych (3.3.4) public_suffix (5.0.1) - puma (6.3.0) + puma (6.4.2) nio4r (~> 2.0) raabro (1.4.0) - racc (1.7.1) - rack (2.2.8) - rack-cors (2.0.1) + racc (1.7.3) + rack (2.2.9) + rack-cors (2.0.2) rack (>= 2.0.0) rack-protection (3.0.5) rack @@ -669,20 +669,20 @@ GEM rack rack-test (2.1.0) rack (>= 1.3) - rails (7.0.4.3) - actioncable (= 7.0.4.3) - actionmailbox (= 7.0.4.3) - actionmailer (= 7.0.4.3) - actionpack (= 7.0.4.3) - actiontext (= 7.0.4.3) - actionview (= 7.0.4.3) - activejob (= 7.0.4.3) - activemodel (= 7.0.4.3) - activerecord (= 7.0.4.3) - activestorage (= 7.0.4.3) - activesupport (= 7.0.4.3) + rails (7.0.8.1) + actioncable (= 7.0.8.1) + actionmailbox (= 7.0.8.1) + actionmailer (= 7.0.8.1) + actionpack (= 7.0.8.1) + actiontext (= 7.0.8.1) + actionview (= 7.0.8.1) + activejob (= 7.0.8.1) + activemodel (= 7.0.8.1) + activerecord (= 7.0.8.1) + activestorage (= 7.0.8.1) + activesupport (= 7.0.8.1) bundler (>= 1.15.0) - railties (= 7.0.4.3) + railties (= 7.0.8.1) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -697,15 +697,15 @@ GEM rails_same_site_cookie (0.1.9) rack (>= 1.5) user_agent_parser (~> 2.6) - railties (7.0.4.3) - actionpack (= 7.0.4.3) - activesupport (= 7.0.4.3) + railties (7.0.8.1) + actionpack (= 7.0.8.1) + activesupport (= 7.0.8.1) method_source rake (>= 12.2) thor (~> 1.0) zeitwerk (~> 2.5) rainbow (3.1.1) - rake (13.0.6) + rake (13.1.0) rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) @@ -864,7 +864,7 @@ GEM semantic_range (>= 2.3.0) shoulda-matchers (5.3.0) activesupport (>= 5.2.0) - sidekiq (6.5.9) + sidekiq (6.5.12) connection_pool (>= 2.2.5, < 3) rack (~> 2.0) redis (>= 4.5.0, < 5) @@ -924,9 +924,9 @@ GEM rdf (~> 3.2) terser (1.2.0) execjs (>= 0.3.0, < 3) - thor (1.2.2) + thor (1.3.1) tilt (2.0.11) - timeout (0.3.2) + timeout (0.4.1) trailblazer-option (0.1.2) twitter-typeahead-rails (0.11.1.pre.corejavascript) actionpack (>= 3.1) @@ -943,7 +943,7 @@ GEM unicode-display_width (2.4.2) unicode-types (1.8.0) user_agent_parser (2.14.0) - view_component (2.82.0) + view_component (2.83.0) activesupport (>= 5.2.0, < 8.0) concurrent-ruby (~> 1.0) method_source (~> 1.0) @@ -964,7 +964,7 @@ GEM crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) webrick (1.8.1) - websocket-driver (0.7.5) + websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) with_locking (1.0.2) @@ -972,7 +972,7 @@ GEM rexml xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.11) + zeitwerk (2.6.13) zk (1.10.0) zookeeper (~> 1.5.0) zookeeper (1.5.5) @@ -1070,9 +1070,9 @@ DEPENDENCIES pry-byebug pry-rails psych (< 4) - puma (>= 4.3.8) + puma (>= 6.4.2) rack-cors - rails (= 7.0.4.3) + rails (~> 7.0.8) rails-controller-testing rails_same_site_cookie rb-readline From d608f1301738869c87e184c19d48ddd1614e9bb4 Mon Sep 17 00:00:00 2001 From: cjcolvar Date: Wed, 27 Mar 2024 17:33:51 -0400 Subject: [PATCH 27/33] Set duration of MasterFile from ActiveEncode instead of relying on the imprecise mediainfo duration --- app/models/master_file.rb | 10 +++++++--- spec/factories/encode.rb | 18 +++++++++++++++++- spec/models/master_file_spec.rb | 29 ++++++++++++++--------------- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/app/models/master_file.rb b/app/models/master_file.rb index 9ffe260089..6ae184a631 100644 --- a/app/models/master_file.rb +++ b/app/models/master_file.rb @@ -294,9 +294,13 @@ def update_progress_on_success!(encode) #TODO pull this from the encode self.date_digitized ||= Time.now.utc.iso8601 - # Set duration after transcode if mediainfo fails to find. - # e.x. WebM files missing technical metadata - self.duration ||= encode.input.duration + # Update the duration detected by ActiveEncode + # because it has higher precision than mediainfo + # Set for the first time for files without duration + # e.g. WebM files missing technical metadata + # ActiveEncode returns duration in milliseconds which + # is stored as an integer string + self.duration = encode.input.duration.to_i.to_s if encode.input.duration.present? outputs = Array(encode.output).collect do |output| { diff --git a/spec/factories/encode.rb b/spec/factories/encode.rb index ba2c5d2590..0a4b5770a7 100644 --- a/spec/factories/encode.rb +++ b/spec/factories/encode.rb @@ -26,12 +26,14 @@ state { :running } percent_complete { 50.5 } current_operations { ['encoding'] } + input { FactoryBot.build(:encode_output) } end trait :succeeded do state { :completed } percent_complete { 100 } current_operations { ['DONE'] } + input { FactoryBot.build(:encode_output) } output { [ FactoryBot.build(:encode_output) ] } end @@ -39,15 +41,29 @@ state { :failed } percent_complete { 50.5 } current_operations { ['FAILED'] } + input { FactoryBot.build(:encode_output) } errors { ['Out of disk space.'] } end end + factory :encode_input, class: ActiveEncode::Input do + id { SecureRandom.uuid } + label { 'quality-high' } + url { 'file://path/to/output.mp4' } + duration { '21575.0' } + audio_bitrate { '163842.0' } + audio_codec { 'AAC' } + video_bitrate { '4000000.0' } + video_codec { 'AVC' } + width { '1024' } + height { '768' } + end + factory :encode_output, class: ActiveEncode::Output do id { SecureRandom.uuid } label { 'quality-high' } url { 'file://path/to/output.mp4' } - duration { '21575' } + duration { '21575.0' } audio_bitrate { '163842.0' } audio_codec { 'AAC' } video_bitrate { '4000000.0' } diff --git a/spec/models/master_file_spec.rb b/spec/models/master_file_spec.rb index 488e950a4d..70ffa27e91 100644 --- a/spec/models/master_file_spec.rb +++ b/spec/models/master_file_spec.rb @@ -539,21 +539,6 @@ end end - describe '#update_progress_on_success!' do - subject(:master_file) { FactoryBot.create(:master_file) } - let(:encode) { double("encode", :output => []) } - before do - allow(master_file).to receive(:update_ingest_batch).and_return(true) - end - - it 'should set the digitized date' do - master_file.update_progress_on_success!(encode) - master_file.reload - expect(master_file.date_digitized).to_not be_empty - end - - end - describe "#structural_metadata_labels" do subject(:master_file) { FactoryBot.create(:master_file, :with_structure) } it 'should return correct list of labels' do @@ -816,11 +801,25 @@ let(:master_file) { FactoryBot.build(:master_file) } let(:encode_succeeded) { FactoryBot.build(:encode, :succeeded) } + before do + allow(master_file).to receive(:update_derivatives) + allow(master_file).to receive(:run_hook) + end + it 'calls update_derivatives' do expect(master_file).to receive(:update_derivatives).with(array_including(hash_including(label: 'quality-high'))) expect(master_file).to receive(:run_hook).with(:after_transcoding) master_file.update_progress_on_success!(encode_succeeded) end + + it 'updates duration' do + expect { master_file.update_progress_on_success!(encode_succeeded) }.to change { master_file.duration }.from("200000").to("21575") + end + + it 'should set the digitized date' do + master_file.update_progress_on_success!(encode_succeeded) + expect(master_file.date_digitized).to_not be_empty + end end describe 'update_derivatives' do From 38423bc7ffc4e14e879eda18efcde402c1bfe2fe Mon Sep 17 00:00:00 2001 From: dwithana Date: Fri, 29 Mar 2024 15:52:21 -0400 Subject: [PATCH 28/33] Ramp build for Avalon 7.7.1 --- yarn.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/yarn.lock b/yarn.lock index dbe7b0c6ed..9477cc7b89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1435,7 +1435,7 @@ "@samvera/ramp@https://github.com/samvera-labs/ramp.git": version "3.0.0" - resolved "https://github.com/samvera-labs/ramp.git#497fffa8b863486d57b0fc67cba3237beb422b5c" + resolved "https://github.com/samvera-labs/ramp.git#11cddd50f9818d6281e25f73582fa54d272b77c2" dependencies: "@rollup/plugin-json" "^6.0.1" "@silvermine/videojs-quality-selector" "^1.2.4" @@ -4052,9 +4052,9 @@ make-dir@^3.0.2, make-dir@^3.1.0: semver "^6.0.0" mammoth@^1.4.19: - version "1.7.0" - resolved "https://registry.yarnpkg.com/mammoth/-/mammoth-1.7.0.tgz#e59b2d89a4907595906429c9d3313729d67afca7" - integrity sha512-ptFhft61dqieLffpdpHD7PUS0cX9YvHQIO3n3ejRhj1bi5Na+RL5wovtNHHXAK6Oj554XfGrVcyTuxgegN6umw== + version "1.7.1" + resolved "https://registry.yarnpkg.com/mammoth/-/mammoth-1.7.1.tgz#8e0b19ba2ce6a0c364e3ea7afa0ecfe67b7f33d3" + integrity sha512-ckxfvNH5sUaJh+SbYbxpvB7urZTGS02jA91rFCNiL928CgE9FXXMyXxcJBY0n+CpmKE/eWh7qaV0+v+Dbwun3Q== dependencies: "@xmldom/xmldom" "^0.8.6" argparse "~1.0.3" @@ -5266,9 +5266,9 @@ safe-json-parse@4.0.0: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sanitize-html@^2.10.0: - version "2.12.1" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.12.1.tgz#280a0f5c37305222921f6f9d605be1f6558914c7" - integrity sha512-Plh+JAn0UVDpBRP/xEjsk+xDCoOvMBwQUf/K+/cBAVuTbtX8bj2VB7S1sL1dssVpykqp0/KPSesHrqXtokVBpA== + version "2.13.0" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.13.0.tgz#71aedcdb777897985a4ea1877bf4f895a1170dae" + integrity sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA== dependencies: deepmerge "^4.2.2" escape-string-regexp "^4.0.0" From 25ef7d9288e9f242693bec0d0a39bbe106447ea8 Mon Sep 17 00:00:00 2001 From: dwithana Date: Fri, 29 Mar 2024 16:16:59 -0400 Subject: [PATCH 29/33] SME build with fixes for 7.7.1 --- package.json | 2 +- yarn.lock | 43 +++++++++++++++++++++++++------------------ 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 78d09be615..a2325d5cd3 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "react": "^17.0.1", "react-bootstrap": "^1.0.0", "react-dom": "^17.0.1", - "react-structural-metadata-editor": "https://github.com/avalonmediasystem/react-structural-metadata-editor#avalon-7.7", + "react-structural-metadata-editor": "https://github.com/avalonmediasystem/react-structural-metadata-editor#avalon-7.7.1", "react_ujs": "^2.4.4", "sass": "^1.65.1", "sass-loader": "^13.3.2", diff --git a/yarn.lock b/yarn.lock index dbe7b0c6ed..099c47ddce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1084,10 +1084,10 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.8.3", "@babel/runtime@^7.9.2": - version "7.23.9" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7" - integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== +"@babel/runtime@^7.1.2", "@babel/runtime@^7.15.4", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.8.3", "@babel/runtime@^7.9.2": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.1.tgz#431f9a794d173b53720e69a6464abc6f0e2a5c57" + integrity sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ== dependencies: regenerator-runtime "^0.14.0" @@ -1098,6 +1098,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.12.5": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7" + integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.13.8", "@babel/runtime@^7.14.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a" @@ -2080,11 +2087,11 @@ available-typed-arrays@^1.0.5: integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== axios@^1.6.0: - version "1.6.7" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" - integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA== + version "1.6.8" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66" + integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== dependencies: - follow-redirects "^1.15.4" + follow-redirects "^1.15.6" form-data "^4.0.0" proxy-from-env "^1.1.0" @@ -3151,7 +3158,7 @@ find-up@^4.0.0: locate-path "^5.0.0" path-exists "^4.0.0" -follow-redirects@^1.0.0, follow-redirects@^1.15.4: +follow-redirects@^1.0.0, follow-redirects@^1.15.6: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== @@ -3374,9 +3381,9 @@ he@^1.2.0: integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== hls.js@^1.1.2: - version "1.5.5" - resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-1.5.5.tgz#8791a1d9e88d0d3f0bb023b573cfd54dea68bff7" - integrity sha512-IMio4uloZNDoBLXUMup2dW0/wOSe+0lHL9qYfRV4oUcPQ/nkTBQibG5h4OPTXOTXSI3iUwcon7TFgNb6uEoKIQ== + version "1.5.7" + resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-1.5.7.tgz#e069e78fe962a422d16aa17a2bfc2f1e2321089d" + integrity sha512-Hnyf7ojTBtXHeOW1/t6wCBJSiK1WpoKF9yg7juxldDx8u3iswrkPt2wbOA/1NiwU4j27DSIVoIEJRAhcdMef/A== "hls.js@https://github.com/avalonmediasystem/hls.js#stricter_ts_probing": version "0.13.1" @@ -3911,9 +3918,9 @@ kind-of@^6.0.2, kind-of@^6.0.3: integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== konva@^9.0.0: - version "9.3.3" - resolved "https://registry.yarnpkg.com/konva/-/konva-9.3.3.tgz#b4e719c287fd8ffa88360e25e2fd3201d6896f7a" - integrity sha512-cg/AHxnfawZ1rKxygCnzx0TZY7hQiQiAKgAHPinEwMn49MVrBkeKLj2d0EaleoFG/0y0XhEKTD0dFZiPPdWlCQ== + version "9.3.6" + resolved "https://registry.yarnpkg.com/konva/-/konva-9.3.6.tgz#62b36292dbe06c56eb161d5ead221c2b5c5a8926" + integrity sha512-dqR8EbcM0hjuilZCBP6xauQ5V3kH3m9kBcsDkqPypQuRgsXbcXUrxqYxhNbdvKZpYNW8Amq94jAD/C0NY3qfBQ== launch-editor@^2.6.0: version "2.6.0" @@ -4961,9 +4968,9 @@ react-redux@^7.2.6: prop-types "^15.7.2" react-is "^17.0.2" -"react-structural-metadata-editor@https://github.com/avalonmediasystem/react-structural-metadata-editor#avalon-7.7": - version "1.2.0" - resolved "https://github.com/avalonmediasystem/react-structural-metadata-editor#5ed306b30e762cb5b955a03142db13f125665c59" +"react-structural-metadata-editor@https://github.com/avalonmediasystem/react-structural-metadata-editor#avalon-7.7.1": + version "2.0.0" + resolved "https://github.com/avalonmediasystem/react-structural-metadata-editor#482222931c842ce864aa35cc7c19c9a09a0fe0c0" dependencies: "@babel/runtime" "^7.4.4" "@fortawesome/fontawesome-svg-core" "^1.2.4" From 3e32eb08d7dd65b25b573f4fb87ed23bd7f9772c Mon Sep 17 00:00:00 2001 From: cjcolvar Date: Mon, 1 Apr 2024 13:25:52 -0400 Subject: [PATCH 30/33] Bump version for release --- config/application.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/application.rb b/config/application.rb index 18a8f037b7..9f78a4087f 100644 --- a/config/application.rb +++ b/config/application.rb @@ -8,7 +8,7 @@ Bundler.require(*Rails.groups) module Avalon - VERSION = '7.7.0' + VERSION = '7.7.1' class Application < Rails::Application require 'avalon/configuration' From f7c69479b55936ee772287345fd07a10b14be304 Mon Sep 17 00:00:00 2001 From: cjcolvar Date: Mon, 1 Apr 2024 16:45:41 -0400 Subject: [PATCH 31/33] Use released version of iiif_manifest --- Gemfile | 2 +- Gemfile.lock | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/Gemfile b/Gemfile index 52377a3cdd..a6274460e6 100644 --- a/Gemfile +++ b/Gemfile @@ -53,7 +53,7 @@ gem 'avalon-about', git: 'https://github.com/avalonmediasystem/avalon-about.git' #gem 'bootstrap-sass', '< 3.4.1' # Pin to less than 3.4.1 due to change in behavior with popovers gem 'bootstrap-toggle-rails' gem 'bootstrap_form' -gem 'iiif_manifest', git: 'https://github.com/samvera/iiif_manifest.git', branch: 'canvas-homepage' +gem 'iiif_manifest', '~> 1.5' gem 'rack-cors', require: 'rack/cors' gem 'rails_same_site_cookie' gem 'recaptcha', require: 'recaptcha/rails' diff --git a/Gemfile.lock b/Gemfile.lock index 85d762674a..2871b823f9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -65,14 +65,6 @@ GIT ims-lti omniauth -GIT - remote: https://github.com/samvera/iiif_manifest.git - revision: e7aea3ab9614f645ccdb760f78954a7ef2cd893c - branch: canvas-homepage - specs: - iiif_manifest (1.4.0) - activesupport (>= 4) - GEM remote: https://rubygems.org/ specs: @@ -502,6 +494,8 @@ GEM i18n (1.14.4) concurrent-ruby (~> 1.0) iconv (1.0.8) + iiif_manifest (1.5.0) + activesupport (>= 4) ims-lti (1.1.13) builder oauth (>= 0.4.5, < 0.6) @@ -1043,7 +1037,7 @@ DEPENDENCIES httpx hydra-head (~> 12.0) iconv (~> 1.0.6) - iiif_manifest! + iiif_manifest (~> 1.5) ims-lti (~> 1.1.13) jbuilder (~> 2.0) jquery-datatables From 2d53815ccd3be3211faca39046e63aef14c0ab39 Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Thu, 4 Apr 2024 12:51:40 -0400 Subject: [PATCH 32/33] Serialize captions to IIIF manifest with text/vtt as the format SRT formatted captions are converted to VTT format when the content is requested by Ramp. However, the iiif manifest still had the format for these files listed as 'text/srt'. This caused Ramp to not process the file correctly and the caption file would not show up in the subs/captions list in the player. Setting the format to 'text/vtt' allows Ramp to correctly process the caption track. --- app/models/iiif_canvas_presenter.rb | 6 +++++- spec/models/iiif_canvas_presenter_spec.rb | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/models/iiif_canvas_presenter.rb b/app/models/iiif_canvas_presenter.rb index fc2b6620d5..7b650248a5 100644 --- a/app/models/iiif_canvas_presenter.rb +++ b/app/models/iiif_canvas_presenter.rb @@ -209,7 +209,11 @@ def manifest_attributes(quality, media_type) def supplemental_attributes(file) if file.is_a?(SupplementalFile) label = file.tags.include?('machine_generated') ? file.label + ' (machine generated)' : file.label - format = file.file.content_type + format = if file.file.content_type == 'text/srt' && file.tags.include?('caption') + 'text/vtt' + else + file.file.content_type + end language = file.language || 'en' filename = file.file.filename.to_s else diff --git a/spec/models/iiif_canvas_presenter_spec.rb b/spec/models/iiif_canvas_presenter_spec.rb index 345870020e..051d320fa0 100644 --- a/spec/models/iiif_canvas_presenter_spec.rb +++ b/spec/models/iiif_canvas_presenter_spec.rb @@ -269,6 +269,16 @@ expect(subject.any? { |content| content.body_id =~ /master_files\/#{master_file.id}\/captions/ }).to eq false end + context 'srt captions' do + let(:srt_caption_file) { FactoryBot.create(:supplemental_file, :with_caption_srt_file, :with_caption_tag) } + let(:supplemental_files) { [supplemental_file, transcript_file, caption_file, srt_caption_file] } + it 'sets format to "text/vtt"' do + captions = subject.select { |s| s.body_id.include?('captions') } + expect(captions.none? { |content| content.format == 'text/srt' }).to eq true + expect(captions.all? { |content| content.format == 'text/vtt' }).to eq true + end + end + context 'legacy master file captions' do let(:master_file) { FactoryBot.create(:master_file, :with_waveform, :with_captions, supplemental_files_json: supplemental_files_json, media_object: media_object, derivatives: [derivative]) } From 7f4d76f78cd8932611faccc1af618a55da581034 Mon Sep 17 00:00:00 2001 From: dwithana Date: Thu, 4 Apr 2024 16:24:17 -0400 Subject: [PATCH 33/33] Bump Ramp from 3.0.0 to 3.1.0 --- package.json | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index a2325d5cd3..f2a9cc741c 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", + "@samvera/ramp": "^3.1.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 27e23c383a..7516286e5b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1440,9 +1440,10 @@ estree-walker "^2.0.2" picomatch "^2.3.1" -"@samvera/ramp@https://github.com/samvera-labs/ramp.git": - version "3.0.0" - resolved "https://github.com/samvera-labs/ramp.git#11cddd50f9818d6281e25f73582fa54d272b77c2" +"@samvera/ramp@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@samvera/ramp/-/ramp-3.1.0.tgz#6254646f34b9a434ff6e6046f1e013130d036e28" + integrity sha512-B5UiU0DDxi7Ub+DfbKC7j80zjMCmkuLnmDJNUqnftjO1L2gocdMOuUfYZ30pAh9SO9HbMCEtAFtLUHPlizPz3A== dependencies: "@rollup/plugin-json" "^6.0.1" "@silvermine/videojs-quality-selector" "^1.2.4"