diff --git a/.gitignore b/.gitignore index 23f1e082..c1b3dc81 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ spec/test_app/log/*.log spec/test_app/tmp/ tmp/** *~undo-tree~ +/vendor diff --git a/Gemfile b/Gemfile index 2f857495..f35b4129 100644 --- a/Gemfile +++ b/Gemfile @@ -32,6 +32,10 @@ group :development, :test do gem 'sqlite3', '~> 1.4' end +group :test do + gem 'rails-controller-testing' +end + group :lint do gem 'bixby' gem 'rubocop-factory_bot', require: false diff --git a/app/assets/javascripts/bulkrax/bulkrax.js b/app/assets/javascripts/bulkrax/bulkrax.js index 006ce820..e7b298ed 100644 --- a/app/assets/javascripts/bulkrax/bulkrax.js +++ b/app/assets/javascripts/bulkrax/bulkrax.js @@ -5,18 +5,20 @@ $(document).on('turbolinks:load ready', function() { $('button#err_toggle').click(function() { $('#error_trace').toggle(); }); + $('button#fm_toggle').click(function() { $('#field_mapping').toggle(); }); + $('#bulkraxItemModal').on('show.bs.modal', function (event) { - var button = $(event.relatedTarget) // Button that triggered the modal - var recipient = button.data('entry-id') // Extract info from data-* attributes + var button = $(event.relatedTarget); // Button that triggered the modal + var recipient = button.data('entry-id'); // Extract info from data-* attributes // If necessary, you could initiate an AJAX request here (and then do the updating in a callback). // Update the modal's content. We'll use jQuery here, but you could use a data binding library or other methods instead. - var modal = $(this) + var modal = $(this); modal.find('a').each(function() { - this.href = this.href.replace(/\d+\?/, recipient + '?') - }) - return true - }) + this.href = this.href.replace(/\d+\?/, recipient + '?'); + }); + return true; + }); }); diff --git a/app/assets/javascripts/bulkrax/importers.js.erb b/app/assets/javascripts/bulkrax/importers.js.erb index e254c60e..de11ecdd 100644 --- a/app/assets/javascripts/bulkrax/importers.js.erb +++ b/app/assets/javascripts/bulkrax/importers.js.erb @@ -2,205 +2,240 @@ // All this logic will automatically be available in application.js. function prepBulkrax(event) { - var refresh_button = $('.refresh-set-source') - var base_url = $('#importer_parser_fields_base_url') - var initial_base_url = base_url.val() - var file_path_value = $('#importer_parser_fields_import_file_path').val() - handleFileToggle(file_path_value) + var refresh_button = $('.refresh-set-source'); + var base_url = $('#importer_parser_fields_base_url'); + var initial_base_url = base_url.val(); + var file_path_value = $('#importer_parser_fields_import_file_path').val(); + handleFileToggle(file_path_value); + + // Initialize the uploader only if hyraxUploader is defined + if (typeof $.fn.hyraxUploader === 'function') { + // Initialize the uploader + $('.fileupload-bulkrax').hyraxUploader({ maxNumberOfFiles: 1 }); + + // Function to toggle 'required' attribute based on uploaded files + function toggleRequiredAttribute() { + const fileInput = $('#addfiles'); + const uploadedFilesTable = $('.fileupload-bulkrax tbody.files'); + + if (uploadedFilesTable.find('tr.template-download').length > 0) { + // Remove 'required' if there are uploaded files + fileInput.removeAttr('required'); + } else { + // Add 'required' if no uploaded files + fileInput.attr('required', 'required'); + } + } + + // Check the required attribute when a file is added or removed + $('#addfiles').on('change', function() { + toggleRequiredAttribute(); + }); + + // Also check when an upload completes or is canceled + $('.fileupload-bulkrax').on('fileuploadcompleted fileuploaddestroyed', function() { + toggleRequiredAttribute(); + }); + + // Ensure 'required' is only added if there are no files on form reset + $('#file-upload-cancel-btn').on('click', function() { + $('#addfiles').attr('required', 'required'); + $('#addfiles').val(''); // Clear file input to ensure 'required' behavior resets + }); + + // Initial check in case files are already uploaded + toggleRequiredAttribute(); + } // handle refreshing/loading of external sets via button click $('body').on('click', '.refresh-set-source', function(e) { - e.preventDefault() + e.preventDefault(); - external_set_select = $("#importer_parser_fields_set") - handleSourceLoad(refresh_button, base_url, external_set_select) - }) + external_set_select = $("#importer_parser_fields_set"); + handleSourceLoad(refresh_button, base_url, external_set_select); + }); // handle refreshing/loading of external sets via blur event for the base_url field $('body').on('blur', '#importer_parser_fields_base_url', function(e) { - e.preventDefault() + e.preventDefault(); // retrieve the latest base_url - base_url = $('#importer_parser_fields_base_url') + base_url = $('#importer_parser_fields_base_url'); // ensure we don't make another query if the value is the same -- this can be forced by clicking the refresh button if (initial_base_url != base_url.val()) { - external_set_select = $("#importer_parser_fields_set") - handleSourceLoad(refresh_button, base_url, external_set_select) - initial_base_url = base_url.val() + external_set_select = $("#importer_parser_fields_set"); + handleSourceLoad(refresh_button, base_url, external_set_select); + initial_base_url = base_url.val(); } - }) + }); // hide and show correct parser fields depending on klass setting $('body').on('change', '#importer_parser_klass', function(e) { - handleParserKlass() - }) - handleParserKlass() + handleParserKlass(); + }); + handleParserKlass(); // observer for cloud files being added var form = document.getElementById('new_importer'); if (form == null) { - var form = document.getElementsByClassName('edit_importer')[0]; + form = document.getElementsByClassName('edit_importer')[0]; } + // only setup the observer on the new and edit importer pages if (form != null) { var config = { childList: true, attributes: true }; var callback = function(mutationsList) { for(var mutation of mutationsList) { - if (mutation.type == 'childList') { browseButton = document.getElementById('browse'); - var exp = /selected_files\[[0-9]*\]\[url\]/ + var exp = /selected_files\[[0-9]*\]\[url\]/; for (var node of mutation.addedNodes) { if (node.attributes != undefined) { - var name = node.attributes.name.value + var name = node.attributes.name.value; if (exp.test(name)) { browseButton.innerHTML = 'Cloud Files Added'; browseButton.style.backgroundColor = 'green'; - browseButton.after(document.createElement("br"), node.value.toString()) + browseButton.after(document.createElement("br"), node.value.toString()); } } } } } }; - var observer = new MutationObserver (callback); - observer.observe (form, config); + var observer = new MutationObserver(callback); + observer.observe(form, config); } } function handleFileToggle(file_path) { if (file_path === undefined || file_path.length === 0) { - $('#file_path').hide() - $('#file_upload').hide() - $('#cloud').hide() - $('#existing_options').hide() - $('#file_path input').attr('required', null) - $('#file_upload input').attr('required', null) + $('#file_path').hide(); + $('#file_upload').hide(); + $('#cloud').hide(); + $('#existing_options').hide(); + $('#file_path input').attr('required', null); + $('#file_upload input').attr('required', null); } else { - $('#file_path').show() - $('#file_upload').hide() - $('#cloud').hide() - $('#existing_options').hide() - $('#file_path input').attr('required', 'required') - $('#file_upload input').attr('required', null) - $('#importer_parser_fields_file_style_specify_a_path_on_the_server').attr('checked', true) + $('#file_path').show(); + $('#file_upload').hide(); + $('#cloud').hide(); + $('#existing_options').hide(); + $('#file_path input').attr('required', 'required'); + $('#file_upload input').attr('required', null); + $('#importer_parser_fields_file_style_specify_a_path_on_the_server').attr('checked', true); } $('#importer_parser_fields_file_style_upload_a_file').click(function(e){ - $('#file_path').hide() - $('#file_upload').show() - $('#cloud').hide() - $('#existing_options').hide() - $('#file_path input').attr('required', null) - $('#file_upload input').attr('required', 'required') - }) + $('#file_path').hide(); + $('#file_upload').show(); + $('#cloud').hide(); + $('#existing_options').hide(); + $('#file_path input').attr('required', null); + $('#file_upload input').attr('required', 'required'); + }); $('#importer_parser_fields_file_style_specify_a_path_on_the_server').click(function(e){ - $('#file_path').show() - $('#file_upload').hide() - $('#cloud').hide() - $('#existing_options').hide() - $('#file_path input').attr('required', 'required') - $('#file_upload input').attr('required', null) - }) + $('#file_path').show(); + $('#file_upload').hide(); + $('#cloud').hide(); + $('#existing_options').hide(); + $('#file_path input').attr('required', 'required'); + $('#file_upload input').attr('required', null); + }); $('#importer_parser_fields_file_style_add_cloud_file').click(function(e){ - $('#file_path').hide() - $('#file_upload').hide() - $('#cloud').show() - $('#existing_options').hide() - $('#file_path input').attr('required', null) - $('#file_upload input').attr('required', null) - }) + $('#file_path').hide(); + $('#file_upload').hide(); + $('#cloud').show(); + $('#existing_options').hide(); + $('#file_path input').attr('required', null); + $('#file_upload input').attr('required', null); + }); $('#importer_parser_fields_file_style_existing_entries').click(function(e){ - $('#file_path').hide() - $('#file_upload').hide() - $('#cloud').hide() - $('#existing_options').show() - $('#file_path input').attr('required', null) - $('#file_upload input').attr('required', null) - }) - + $('#file_path').hide(); + $('#file_upload').hide(); + $('#cloud').hide(); + $('#existing_options').show(); + $('#file_path input').attr('required', null); + $('#file_upload input').attr('required', null); + }); } function handleParserKlass() { <% Bulkrax.parsers.map{ |p| p[:partial]}.uniq.each do |partial| %> if($('.<%= partial %>').length > 0) { - window.<%= partial %> = $('.<%= partial %>').detach() + window.<%= partial %> = $('.<%= partial %>').detach(); } <% end %> - var parser_klass = $("#importer_parser_klass option:selected") + var parser_klass = $("#importer_parser_klass option:selected"); if(parser_klass.length > 0 && parser_klass.data('partial') && parser_klass.data('partial').length > 0) { - $('.parser_fields').append(window[parser_klass.data('partial')]) + $('.parser_fields').append(window[parser_klass.data('partial')]); } - handleBrowseEverything() - var file_path_value = $('#importer_parser_fields_import_file_path').val() - handleFileToggle(file_path_value) + handleBrowseEverything(); + var file_path_value = $('#importer_parser_fields_import_file_path').val(); + handleFileToggle(file_path_value); } function handleBrowseEverything(){ - var button = $("button[data-toggle='browse-everything']") + var button = $("button[data-toggle='browse-everything']"); if(button.length == 0) { return; } button.browseEverything({ route: button.data('route'), target: button.data('target') }).done(function(data) { var evt = { isDefaultPrevented: function() { return false; } }; - $('.ev-browser.show').removeClass('show') + $('.ev-browser.show').removeClass('show'); if($('#fileupload').length > 0) { - var files = $.map(data, function(d) { return { name: d.file_name, size: d.file_size, id: d.url } }); + var files = $.map(data, function(d) { return { name: d.file_name, size: d.file_size, id: d.url }; }); $.blueimp.fileupload.prototype.options.done.call($('#fileupload').fileupload(), evt, { result: { files: files }}); } - return true - // User has submitted files; data contains an array of URLs and their options + return true; }).cancel(function() { - $('.ev-browser.show').removeClass('show') - // User cancelled the browse operation + $('.ev-browser.show').removeClass('show'); }).fail(function(status, error, text) { - $('.ev-browser.show').removeClass('show') - // URL retrieval experienced a technical failure + $('.ev-browser.show').removeClass('show'); }); } function handleSourceLoad(refresh_button, base_url, external_set_select) { if (base_url.val() == "") { // ignore empty base_url value - return + return; } - var initial_button_text = refresh_button.html() + var initial_button_text = refresh_button.html(); - refresh_button.html('Refreshing...') - refresh_button.attr('disabled', true) + refresh_button.html('Refreshing...'); + refresh_button.attr('disabled', true); $.post('/importers/external_sets', { base_url: base_url.val(), }, function(res) { if (!res.error) { - genExternalSetOptions(external_set_select, res.sets) // sets is [[name, spec]...] + genExternalSetOptions(external_set_select, res.sets); } else { - setError(external_set_select, res.error) + setError(external_set_select, res.error); } - refresh_button.html(initial_button_text) - refresh_button.attr('disabled', false) - }) + refresh_button.html(initial_button_text); + refresh_button.attr('disabled', false); + }); } function genExternalSetOptions(selector, sets) { - out = '' + out = ''; out += sets.map(function(set) { - return '' - }) + return ''; + }); - selector.html(out) - selector.attr('disabled', false) + selector.html(out); + selector.attr('disabled', false); } function setError(selector, error) { - selector.html('') - selector.attr('disabled', true) + selector.html(''); + selector.attr('disabled', true); } -$(document).on({'ready': prepBulkrax, 'turbolinks:load': prepBulkrax}) +$(document).on({'ready': prepBulkrax, 'turbolinks:load': prepBulkrax}); diff --git a/app/controllers/bulkrax/importers_controller.rb b/app/controllers/bulkrax/importers_controller.rb index 2fe5c470..d088fdb1 100644 --- a/app/controllers/bulkrax/importers_controller.rb +++ b/app/controllers/bulkrax/importers_controller.rb @@ -78,11 +78,13 @@ def edit # POST /importers # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/AbcSize def create # rubocop:disable Style/IfInsideElse if api_request? return return_json_response unless valid_create_params? end + uploads = Hyrax::UploadedFile.find(params[:uploaded_files]) if params[:uploaded_files].present? file = file_param cloud_files = cloud_params @@ -93,7 +95,7 @@ def create # on a new import otherwise it only gets updated during the update path @importer.parser_fields['update_files'] = true if params[:commit] == 'Create and Import' if @importer.save - files_for_import(file, cloud_files) + files_for_import(file, cloud_files, uploads) if params[:commit] == 'Create and Import' Bulkrax::ImporterJob.send(@importer.parser.perform_method, @importer.id) render_request('Importer was successfully created and import has been queued.') @@ -112,6 +114,7 @@ def create end # rubocop:enable Style/IfInsideElse end + # rubocop:enable Metrics/AbcSize # PATCH/PUT /importers/1 # # @todo refactor so as to not need to disable rubocop @@ -120,7 +123,7 @@ def update if api_request? return return_json_response unless valid_update_params? end - + uploads = Hyrax::UploadedFile.find(params[:uploaded_files]) if params[:uploaded_files].present? file = file_param cloud_files = cloud_params @@ -128,7 +131,7 @@ def update field_mapping_params if params[:importer][:parser_fields].present? if @importer.update(importer_params) - files_for_import(file, cloud_files) unless file.nil? && cloud_files.nil? + files_for_import(file, cloud_files, uploads) # do not perform the import unless params[:commit] == 'Update Importer' set_files_parser_fields @@ -218,8 +221,9 @@ def export_errors private - def files_for_import(file, cloud_files) - return if file.blank? && cloud_files.blank? + def files_for_import(file, cloud_files, uploads) + return if file.blank? && cloud_files.blank? && uploads.blank? + @importer[:parser_fields]['import_file_path'] = @importer.parser.write_import_file(file) if file.present? if cloud_files.present? @importer[:parser_fields]['cloud_file_paths'] = cloud_files @@ -229,6 +233,13 @@ def files_for_import(file, cloud_files) target = @importer.parser.retrieve_cloud_files(cloud_files, @importer) @importer[:parser_fields]['import_file_path'] = target if target.present? end + + if uploads.present? + uploads.each do |upload| + @importer[:parser_fields]['import_file_path'] = @importer.parser.write_import_file(upload.file.file) + end + end + @importer.save end diff --git a/app/views/bulkrax/importers/_bagit_fields.html.erb b/app/views/bulkrax/importers/_bagit_fields.html.erb index 6e7bbd41..f52c2022 100644 --- a/app/views/bulkrax/importers/_bagit_fields.html.erb +++ b/app/views/bulkrax/importers/_bagit_fields.html.erb @@ -41,9 +41,16 @@
File upload and Cloud File upload must be a Zip file containing a single BagIt Bag, or a folder containing multiple BagIt Bags.
The Server Path can point to a BagIt Bag, a folder containing BagIt Bags, or a zip file containing either.
- <%= fi.input :file_style, collection: ['Upload a File', 'Specify a Path on the Server', 'Add Cloud File'], as: :radio_buttons, label: false %> +<%= fi.input :file_style, + collection: ['Upload a File', 'Specify a Path on the Server'] + + (defined?(::Hyrax) && Hyrax.config.browse_everything? ? ['Add Cloud File'] : []), + as: :radio_buttons, label: false %>Choose files to upload. The filenames must be unique, and the filenames must be referenced in a column called 'file' in the accompanying CSV file.
+Choose files to upload. The filenames must be unique, and the filenames must be referenced in a column called 'file' in the accompanying CSV file.
<%= render 'browse_everything', form: form %> <% end %>File upload and Cloud File upload MUST be a either a single XML file (for metadata only import) OR a Zip file containing the XML files and data files, each in a separate folder.
The Server Path can point to a folder containing XML files and data files to import, or direct to the XML file itself.
- <%= fi.input :file_style, collection: ['Upload a File', 'Specify a Path on the Server', 'Add Cloud File'], as: :radio_buttons, label: false %> +<%= fi.input :file_style, + collection: ['Upload a File', 'Specify a Path on the Server'] + + (defined?(::Hyrax) && Hyrax.config.browse_everything? ? ['Add Cloud File'] : []), + as: :radio_buttons, label: false %>