diff --git a/Gemfile.lock b/Gemfile.lock index 192e716d39..acecd6c9c8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -51,40 +51,40 @@ GEM remote: https://rubygems.org/ specs: RedCloth (4.3.2) - actioncable (6.1.7.3) - actionpack (= 6.1.7.3) - activesupport (= 6.1.7.3) + actioncable (6.1.7.6) + actionpack (= 6.1.7.6) + activesupport (= 6.1.7.6) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.7.3) - actionpack (= 6.1.7.3) - activejob (= 6.1.7.3) - activerecord (= 6.1.7.3) - activestorage (= 6.1.7.3) - activesupport (= 6.1.7.3) + actionmailbox (6.1.7.6) + actionpack (= 6.1.7.6) + activejob (= 6.1.7.6) + activerecord (= 6.1.7.6) + activestorage (= 6.1.7.6) + activesupport (= 6.1.7.6) mail (>= 2.7.1) - actionmailer (6.1.7.3) - actionpack (= 6.1.7.3) - actionview (= 6.1.7.3) - activejob (= 6.1.7.3) - activesupport (= 6.1.7.3) + actionmailer (6.1.7.6) + actionpack (= 6.1.7.6) + actionview (= 6.1.7.6) + activejob (= 6.1.7.6) + activesupport (= 6.1.7.6) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.7.3) - actionview (= 6.1.7.3) - activesupport (= 6.1.7.3) + actionpack (6.1.7.6) + actionview (= 6.1.7.6) + activesupport (= 6.1.7.6) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.7.3) - actionpack (= 6.1.7.3) - activerecord (= 6.1.7.3) - activestorage (= 6.1.7.3) - activesupport (= 6.1.7.3) + actiontext (6.1.7.6) + actionpack (= 6.1.7.6) + activerecord (= 6.1.7.6) + activestorage (= 6.1.7.6) + activesupport (= 6.1.7.6) nokogiri (>= 1.8.5) - actionview (6.1.7.3) - activesupport (= 6.1.7.3) + actionview (6.1.7.6) + activesupport (= 6.1.7.6) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -94,14 +94,14 @@ GEM activemodel (>= 4.1, < 7.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (6.1.7.3) - activesupport (= 6.1.7.3) + activejob (6.1.7.6) + activesupport (= 6.1.7.6) globalid (>= 0.3.6) - activemodel (6.1.7.3) - activesupport (= 6.1.7.3) - activerecord (6.1.7.3) - activemodel (= 6.1.7.3) - activesupport (= 6.1.7.3) + activemodel (6.1.7.6) + activesupport (= 6.1.7.6) + activerecord (6.1.7.6) + activemodel (= 6.1.7.6) + activesupport (= 6.1.7.6) activerecord-import (1.3.0) activerecord (>= 4.2) activerecord-session_store (2.0.0) @@ -110,14 +110,14 @@ GEM multi_json (~> 1.11, >= 1.11.2) rack (>= 2.0.8, < 3) railties (>= 5.2.4.1) - activestorage (6.1.7.3) - actionpack (= 6.1.7.3) - activejob (= 6.1.7.3) - activerecord (= 6.1.7.3) - activesupport (= 6.1.7.3) + activestorage (6.1.7.6) + actionpack (= 6.1.7.6) + activejob (= 6.1.7.6) + activerecord (= 6.1.7.6) + activesupport (= 6.1.7.6) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.7.3) + activesupport (6.1.7.6) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -294,8 +294,8 @@ GEM omniauth (>= 1.3, < 3) pyu-ruby-sasl (>= 0.0.3.3, < 0.1) rubyntlm (~> 0.5) - globalid (1.1.0) - activesupport (>= 5.0) + globalid (1.2.1) + activesupport (>= 6.1) google-analytics-rails (1.1.1) gyoku (0.4.6) builder (>= 2.1.2) @@ -459,8 +459,9 @@ GEM mimemagic (0.3.10) nokogiri (~> 1) rake - mini_mime (1.1.2) - minitest (5.18.0) + mini_mime (1.1.5) + mini_portile2 (2.8.4) + minitest (5.20.0) minitest-reporters (1.5.0) ansi builder @@ -479,7 +480,7 @@ GEM net-http-digest_auth (1.4.1) net-http-persistent (4.0.1) connection_pool (~> 2.2) - net-imap (0.3.4) + net-imap (0.3.7) date net-protocol net-ldap (0.17.1) @@ -492,6 +493,7 @@ GEM netrc (0.11.0) nio4r (2.5.9) nokogiri (1.14.5) + mini_portile2 (~> 2.8.0) racc (~> 1.4) nori (1.1.5) oauth2 (2.0.9) @@ -564,7 +566,7 @@ GEM nio4r (~> 2.0) pyu-ruby-sasl (0.0.3.3) racc (1.7.1) - rack (2.2.7) + rack (2.2.8) rack-attack (6.6.0) rack (>= 1.0, < 3) rack-cors (1.1.1) @@ -581,26 +583,26 @@ GEM rack rack-test (2.1.0) rack (>= 1.3) - rails (6.1.7.3) - actioncable (= 6.1.7.3) - actionmailbox (= 6.1.7.3) - actionmailer (= 6.1.7.3) - actionpack (= 6.1.7.3) - actiontext (= 6.1.7.3) - actionview (= 6.1.7.3) - activejob (= 6.1.7.3) - activemodel (= 6.1.7.3) - activerecord (= 6.1.7.3) - activestorage (= 6.1.7.3) - activesupport (= 6.1.7.3) + rails (6.1.7.6) + actioncable (= 6.1.7.6) + actionmailbox (= 6.1.7.6) + actionmailer (= 6.1.7.6) + actionpack (= 6.1.7.6) + actiontext (= 6.1.7.6) + actionview (= 6.1.7.6) + activejob (= 6.1.7.6) + activemodel (= 6.1.7.6) + activerecord (= 6.1.7.6) + activestorage (= 6.1.7.6) + activesupport (= 6.1.7.6) bundler (>= 1.15.0) - railties (= 6.1.7.3) + railties (= 6.1.7.6) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) activesupport (>= 5.0.1.rc1) - rails-dom-testing (2.1.1) + rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) @@ -622,9 +624,9 @@ GEM json require_all (~> 3.0) ruby-progressbar - railties (6.1.7.3) - actionpack (= 6.1.7.3) - activesupport (= 6.1.7.3) + railties (6.1.7.6) + actionpack (= 6.1.7.6) + activesupport (= 6.1.7.6) method_source rake (>= 12.2) thor (~> 1.0) @@ -847,7 +849,7 @@ GEM sparql-client (3.2.0) net-http-persistent (~> 4.0, >= 4.0.1) rdf (~> 3.2) - sprockets (4.2.0) + sprockets (4.2.1) concurrent-ruby (~> 1.0) rack (>= 2.2.4, < 4) sprockets-rails (3.4.2) @@ -886,7 +888,7 @@ GEM tilt (2.0.10) time (0.2.2) date - timeout (0.3.2) + timeout (0.4.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) ucf (2.0.2) @@ -934,7 +936,7 @@ GEM hashdiff (>= 0.4.0, < 2.0.0) webrick (1.7.0) webrobots (0.1.2) - websocket-driver (0.7.5) + websocket-driver (0.7.6) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) whenever (1.0.0) @@ -947,7 +949,7 @@ GEM rake (>= 0.8.7) yard (0.9.27) webrick (~> 1.7.0) - zeitwerk (2.6.8) + zeitwerk (2.6.11) zip-container (4.0.2) rubyzip (~> 2.0.0) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index c2cecc0619..eeb6c760ef 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -56,7 +56,7 @@ //= require calendar //= require inplace //= require strain -//= require checkbox +//= require batch_asset_selection //= require cytoscape.js-2.5.0/cytoscape //= require cytoscape_isa_graph //= require bives diff --git a/app/assets/javascripts/batch_asset_selection.js b/app/assets/javascripts/batch_asset_selection.js new file mode 100644 index 0000000000..e6bb2e2c27 --- /dev/null +++ b/app/assets/javascripts/batch_asset_selection.js @@ -0,0 +1,121 @@ +$j(document).ready(function () { + $j('.batch-selection-select-children').click(BatchAssetSelection.selectChildren); + $j('.batch-selection-deselect-children').click(BatchAssetSelection.deselectChildren); + $j('.batch-selection-collapse-children').click(BatchAssetSelection.collapseRecursively); + $j('.batch-selection-expand-children').click(BatchAssetSelection.expandRecursively); + $j('.batch-selection-show-permissions').click(function (event) { + event.preventDefault(); + $j('.batch-selection-permission-list', $j(this).closest('.batch-selection-scope')).show(); + }) + $j('.batch-selection-hide-permissions').click(function (event) { + event.preventDefault(); + $j('.batch-selection-permission-list', $j(this).closest('.batch-selection-scope')).hide(); + }) + $j('.batch-selection-hide-blocked').click(BatchAssetSelection.hideBlocked).click(); // Trigger on page load + $j('.batch-selection-show-blocked').click(BatchAssetSelection.showBlocked); + $j('.batch-selection-collapse-toggle').click(function () { + BatchAssetSelection.toggleCollapse(this); + return false; + }); + $j('.batch-selection-check-btn').click(function (event) { + if (event.target.nodeName.includes('BUTTON')) { + $j(this).find(':checkbox').click(); + } + }); + $j('.batch-selection-check-btn :checkbox').click(function () { + BatchAssetSelection.checkRepeatedItems(this.className, this.checked); + }); + $j('.batch-selection-managed-by-toggle').click(function (event) { + event.preventDefault(); + $j('.batch-selection-managed-by-list:first', $j(this).closest('.batch-selection-scope')).toggle(); + }); + $j('.batch-selection-permissions-toggle').click(function (event) { + event.preventDefault(); + $j('.batch-selection-permission-list:first', $j(this).closest('.batch-selection-scope')).toggle(); + }); +}); + +const BatchAssetSelection = { + blockedSelectors: '.not-visible, .not-manageable, .already-published', + selectChildren: function (event) { + event.preventDefault(); + BatchAssetSelection.setChildren($j(this).closest('.batch-selection-scope'), true); + }, + + deselectChildren: function (event) { + event.preventDefault(); + BatchAssetSelection.setChildren($j(this).closest('.batch-selection-scope'), false); + }, + + setChildren: function (scope, value) { + const children = $j(':checkbox', scope); + const classes = new Set(); + for (let child of children) { + classes.add(child.className); + } + + classes.forEach(c => BatchAssetSelection.checkRepeatedItems(c, value)); + }, + + checkRepeatedItems: function (className, check) { + document.getElementById('batch-asset-selection') + .querySelectorAll('.' + className).forEach(e => e.checked = check); + }, + + toggleManagers: function () { + $j('.batch-selection-managed-by-list', $j(this).closest('.batch-selection-asset')).toggle(); + + return false; + }, + + toggleCollapse: function (element, state) { + if (state === undefined) { + state = !element.classList.contains('open'); + } + element.classList.toggle('open', state); + $j(element).closest('.batch-selection-scope').children('.batch-selection-collapse').toggle(state); + }, + + collapseRecursively: function () { + const scope = $j(this).closest('.batch-selection-scope').children('.batch-selection-collapse'); + const toggles = $j('.batch-selection-collapse-toggle', scope); + for (let toggle of toggles) { + BatchAssetSelection.toggleCollapse(toggle, false); + } + + return false; + }, + + expandRecursively: function () { + const scope = $j(this).closest('.batch-selection-scope').children('.batch-selection-collapse'); + const toggles = $j('.batch-selection-collapse-toggle', scope); + for (let toggle of toggles) { + BatchAssetSelection.toggleCollapse(toggle, true); + } + + return false; + }, + + hideBlocked: function () { + const children = $j(this).closest('.batch-selection-scope') + .find(BatchAssetSelection.blockedSelectors) + .closest('.batch-selection-asset'); + for (let child of children) { + const element = $j(child); + // Don't hide if any non-blocked children + if (!$j(':checkbox', element).length) { + element.hide(); + } + } + + return false; + }, + + showBlocked: function () { + $j(this).closest('.batch-selection-scope') + .find(BatchAssetSelection.blockedSelectors) + .closest('.batch-selection-asset').show(); + + return false; + } +} diff --git a/app/assets/javascripts/checkbox.js b/app/assets/javascripts/checkbox.js deleted file mode 100644 index d5751b2fa2..0000000000 --- a/app/assets/javascripts/checkbox.js +++ /dev/null @@ -1,42 +0,0 @@ -$j(document).ready(function () { - $j("a.selectChildren").click(function (event) { - event.preventDefault(); - selectChildren(this,$j(this).data("cb_parent_selector")) - }) - $j("a.deselectChildren").click(function (event) { - event.preventDefault(); - deselectChildren(this,$j(this).data("cb_parent_selector")) - }) - $j('#jstree').on('click', 'a.selectChildren', function (event) { - event.preventDefault(); - selectChildren(this,$j(this).data("cb_parent_selector")) - }) - $j('#jstree').on('click', 'a.deselectChildren', function (event) { - event.preventDefault(); - deselectChildren(this,$j(this).data("cb_parent_selector")) - }) -}) - -function selectChildren(select_all_element,cb_parent_selector){ - let children_checkboxes = $j(':checkbox', $j(select_all_element).parents(cb_parent_selector)) - for(let checkbox of children_checkboxes){ - let checkbox_element = { className: checkbox.className, checked: true } - checkRepeatedItems(checkbox_element) - } -} - -function deselectChildren(deselect_all_element,cb_parent_selector){ - let children_checkboxes = $j(':checkbox', $j(deselect_all_element).parents(cb_parent_selector)) - for(let checkbox of children_checkboxes){ - let checkbox_element = { className: checkbox.className, checked: false } - checkRepeatedItems(checkbox_element) - } -} - -function checkRepeatedItems(checkbox_element) { - let repeated_elements = document.getElementsByClassName(checkbox_element.className) - let check = checkbox_element.checked - for(let element of repeated_elements){ - element.checked = check - } -} \ No newline at end of file diff --git a/app/assets/javascripts/single_page/index.js.erb b/app/assets/javascripts/single_page/index.js.erb index f9beb902db..85994ee285 100644 --- a/app/assets/javascripts/single_page/index.js.erb +++ b/app/assets/javascripts/single_page/index.js.erb @@ -282,6 +282,25 @@ async function batchUpdateSample(sampleTypes) { } } +async function handleUploadSubmit(formData){ + $j.ajax({ + type: 'POST', + url: "<%= upload_samples_single_pages_path %>", + data: formData, + dataType: 'html', + processData: false, + contentType: false, + enctype: 'multipart/form-data', + success: function(response){ + $j('#upload-excel-modal').modal({backdrop: 'static', keyboard: false}, 'show').focus(); + $j('#upload-excel').html(response); + }, + error: function(err){ + location.reload(); // Page needs reloading for the notice message to appear + } + }); +} + function isMobile() { /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) } diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 8e4e9d6ff9..cd54504be6 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -45,5 +45,5 @@ *= require jquery.splitter/jquery.splitter *= require select2.min *= require select2.bootstrap.min -*= require linked_custom_metadata +*= require linked_extended_metadata */ diff --git a/app/assets/stylesheets/linked_custom_metadata.css b/app/assets/stylesheets/linked_custom_metadata.css deleted file mode 100644 index 265f876954..0000000000 --- a/app/assets/stylesheets/linked_custom_metadata.css +++ /dev/null @@ -1,31 +0,0 @@ -.linked_custom_metdata .panel-default .panel-heading { - font-weight: bold; - color: #333; - background-color: #ffffff; - border: 0; - padding-left:0px; -} - -.linked_custom_metdata .panel-body { - background-color: rgb(248,248,248); - padding: 10px 15px; - border-radius: 5px; -} - - -.linked_custom_metdata .panel { - border: 0; -} - -.linked_custom_metdata_even { - background-color: rgb(248,248,248); - padding: 10px 15px 10px 15px; - border-radius: 5px; -} - -.linked_custom_metdata_odd { - background-color: rgb(224,224,224); - - padding: 10px 15px 10px 15px; - border-radius: 5px; -} \ No newline at end of file diff --git a/app/assets/stylesheets/linked_extended_metadata.css b/app/assets/stylesheets/linked_extended_metadata.css new file mode 100644 index 0000000000..138061afb7 --- /dev/null +++ b/app/assets/stylesheets/linked_extended_metadata.css @@ -0,0 +1,57 @@ +.linked_extended_metdata .panel-default .panel-heading { + font-weight: bold; + color: #333; + background-color: #ffffff; + border: 0; + padding-left:0px; +} + +.linked_extended_metdata .panel-body { + background-color: rgb(248,248,248); + padding: 10px 15px; + border-radius: 5px; +} + + +.linked_extended_metdata .panel { + border: 0; +} + +.linked_extended_metdata_even { + background-color: rgb(248,248,248); + padding: 10px 15px 10px 15px; + border-radius: 5px; +} + +.linked_extended_metdata_odd { + background-color: rgb(224,224,224); + + padding: 10px 15px 10px 15px; + border-radius: 5px; +} + +.multi_linked_extended_metdata { + background-color: rgb(246, 246, 246); + padding: 10px 15px 10px 15px; + border-radius: 5px; + margin-bottom: 15px; +} + +.linked_extended_metdata_display .panel-default .panel-heading { + font-weight: bold; + border: none; + background-color: #ffffff; + padding: 0px 0px; +} + + +.linked_extended_metdata_display .panel { + border: none; + box-shadow: none; + margin-bottom: 0px; +} + +.linked_extended_metdata_display .panel-body { + padding: 0px 0px; + border-radius: 5px; +} diff --git a/app/assets/stylesheets/publishing.scss b/app/assets/stylesheets/publishing.scss index 1de7aec917..3a9e3bed42 100644 --- a/app/assets/stylesheets/publishing.scss +++ b/app/assets/stylesheets/publishing.scss @@ -17,14 +17,6 @@ ul.publishing_options li.secondary { } -.published { - color: limegreen; -} - -span.published { - color: green; -} - span.approve { color: limegreen; font-weight: bolder; @@ -64,13 +56,12 @@ ul.item_for_decision { .publishing_options { padding: 0.5em 1em; - &.publishable { - background-color: #fafaff; - border-left-color: $btn-success-bg; + &.not-manageable { + border-left-color: $btn-warning-bg; } - &.not-publishable { - border-left-color: $btn-warning-bg; + &.not-visible { + border-left-color: $btn-danger-bg; } &.already-published { @@ -78,18 +69,6 @@ ul.item_for_decision { } } -.publish-colour { - color: $btn-success-bg; -} - -.publish-checkbox { - background-color: $btn-success-bg; - color: $btn-success-color; - padding: 0px 10px; - border-radius: 5px; - display: inline-block; -} - ul.decided_item { padding-left: 1em; } @@ -108,3 +87,43 @@ ul.decided_item li.type_and_title { padding: 0.5em 15px; margin: 0.3em 0em; } + +.batch-selection-buttons { + margin-bottom: 1em; + display: flex; + gap: 1em; +} + +.batch-selection-asset-row { + display: flex; + gap: 1em; + align-items: center; + .visibility_icon { + margin: 0; + } +} + +.batch-selection-permission-list, +.batch-selection-managed-by-list, +.batch-selection-children { + margin-left: 3em; +} + +.batch-selection-check-btn { + input { + margin: 0; + vertical-align: middle; + } +} + +.batch-selection-collapse-toggle { + cursor: pointer; + + .glyphicon-menu-down { display: none; }; + .glyphicon-menu-right { display: inline; }; + + &.open { + .glyphicon-menu-down { display: inline; }; + .glyphicon-menu-right { display: none; }; + } +} diff --git a/app/assets/stylesheets/sharing.scss b/app/assets/stylesheets/sharing.scss index d396101a0c..6f56e31c5f 100644 --- a/app/assets/stylesheets/sharing.scss +++ b/app/assets/stylesheets/sharing.scss @@ -210,17 +210,3 @@ padding-left: 16px; } } - -.parent-btn-checkbox { - padding: 1px 6px 0px 6px; - border-radius: 5px; - display: inline-block; - height: 25px; -} -.parent-btn-dropdown { - padding: 2px 6px 10px 0px; - border-radius: 5px; - display: inline-block; - height: 25px; -} - diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index bacc1830b1..77d2b76e3d 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -163,6 +163,7 @@ def update_features_enabled Seek::Config.life_monitor_url = params[:life_monitor_url]&.strip&.chomp('/') Seek::Config.life_monitor_client_id = params[:life_monitor_client_id]&.strip Seek::Config.life_monitor_client_secret = params[:life_monitor_client_secret]&.strip + Seek::Config.life_monitor_ui_url = params[:life_monitor_ui_url]&.strip&.chomp('/') Seek::Config.bio_tools_enabled = string_to_boolean(params[:bio_tools_enabled]) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 559e898b55..a6307da81a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -596,34 +596,27 @@ def relationify_collection(collection) end end - def determine_custom_metadata_keys - - root_key = controller_name.singularize.to_sym - return [] unless params[root_key][:custom_metadata_attributes].present? - attribute_params = params[root_key][:custom_metadata_attributes] - recursive_determine_custom_metadata_keys(attribute_params) - + def determine_extended_metadata_keys + keys = [] + type_id = params.dig(controller_name.singularize.to_sym, :extended_metadata_attributes, :extended_metadata_type_id) + if type_id.present? + metadata_type = ExtendedMetadataType.find(type_id) + keys = [:extended_metadata_type_id, :id, data: recursive_determine_extended_metadata_keys(metadata_type)] + end + keys end - # todo currently 2-level nested attributes are tested, we would like to test if it also works with more level nested attributes - def recursive_determine_custom_metadata_keys(attribute_params) + def recursive_determine_extended_metadata_keys(metadata_type) keys = [] - if attribute_params && attribute_params[:custom_metadata_type_id].present? - metadata_type = CustomMetadataType.find(attribute_params[:custom_metadata_type_id]) - if metadata_type - keys = [:custom_metadata_type_id,:id,:custom_metadata_attribute_id] - cma= [] - metadata_type.custom_metadata_attributes.each do |attr| - if attr.sample_attribute_type.controlled_vocab? || attr.sample_attribute_type.seek_sample_multi? || attr.sample_attribute_type.seek_sample? - cma << {attr.title=>[]} - cma << attr.title.to_s - elsif attr.linked_custom_metadata? - cma << { attr.title => recursive_determine_custom_metadata_keys(attribute_params[:data][attr.title.to_sym])} - else - cma << attr.title.to_s - end - end - keys = keys + [{data:cma}] + metadata_type.extended_metadata_attributes.each do |attr| + key = attr.title.to_sym + if attr.sample_attribute_type.controlled_vocab? || attr.sample_attribute_type.seek_sample_multi? || attr.sample_attribute_type.seek_sample? + keys << key + keys << { key => [] } + elsif attr.linked_extended_metadata? || attr.linked_extended_metadata_multi? + keys << { key => recursive_determine_extended_metadata_keys(attr.linked_extended_metadata_type) } + else + keys << key end end keys diff --git a/app/controllers/assays_controller.rb b/app/controllers/assays_controller.rb index 858a12adae..0c88fabba4 100644 --- a/app/controllers/assays_controller.rb +++ b/app/controllers/assays_controller.rb @@ -108,6 +108,7 @@ def create def delete_linked_sample_types return unless is_single_page_assay? + return if @assay.sample_type.nil? @assay.sample_type.destroy end @@ -185,13 +186,10 @@ def assay_params { data_files_attributes: %i[asset_id direction relationship_type_id] }, { placeholders_attributes: %i[asset_id direction relationship_type_id] }, { publication_ids: [] }, - { custom_metadata_attributes: determine_custom_metadata_keys }, - { discussion_links_attributes: %i[id url label _destroy] }).tap do |assay_params| - if assay_params.key?(:document_ids) - assay_params[:document_ids].select! do |id| - Document.find_by_id(id).try(:can_view?) - end - end + { extended_metadata_attributes: determine_extended_metadata_keys }, + { discussion_links_attributes:[:id, :url, :label, :_destroy] } + ).tap do |assay_params| + assay_params[:document_ids].select! { |id| Document.find_by_id(id).try(:can_view?) } if assay_params.key?(:document_ids) assay_params[:sop_ids].select! { |id| Sop.find_by_id(id).try(:can_view?) } if assay_params.key?(:sop_ids) assay_params[:model_ids].select! { |id| Model.find_by_id(id).try(:can_view?) } if assay_params.key?(:model_ids) end diff --git a/app/controllers/custom_metadata_types_controller.rb b/app/controllers/custom_metadata_types_controller.rb deleted file mode 100644 index 5192bed4db..0000000000 --- a/app/controllers/custom_metadata_types_controller.rb +++ /dev/null @@ -1,36 +0,0 @@ -class CustomMetadataTypesController < ApplicationController - respond_to :json - - before_action :find_custom_metadata_type, only: [:show] - - # generated for form, to display fields for selected metadata type - def form_fields - id = params[:id] - respond_to do |format| - if id.blank? - format.html { render html: '' } - else - cm = CustomMetadataType.find(id) - resource = safe_class_lookup(cm.supported_type).new - resource.custom_metadata = CustomMetadata.new(custom_metadata_type: cm) - format.html do - render partial: 'custom_metadata/custom_metadata_fields', - locals: { custom_metadata_type: cm, resource: resource } - end - end - end - end - - def show - respond_to do |format| - format.json {render json: @custom_metadata_type} - end - end - - private - - def find_custom_metadata_type - @custom_metadata_type = CustomMetadataType.find(params[:id]) - end - -end diff --git a/app/controllers/extended_metadata_types_controller.rb b/app/controllers/extended_metadata_types_controller.rb new file mode 100644 index 0000000000..3e886c73ae --- /dev/null +++ b/app/controllers/extended_metadata_types_controller.rb @@ -0,0 +1,36 @@ +class ExtendedMetadataTypesController < ApplicationController + respond_to :json + + before_action :find_extended_metadata_type, only: [:show] + + # generated for form, to display fields for selected metadata type + def form_fields + id = params[:id] + respond_to do |format| + if id.blank? + format.html { render html: '' } + else + cm = ExtendedMetadataType.find(id) + resource = safe_class_lookup(cm.supported_type).new + resource.extended_metadata = ExtendedMetadata.new(extended_metadata_type: cm) + format.html do + render partial: 'extended_metadata/extended_metadata_fields', + locals: { extended_metadata_type: cm, resource: resource } + end + end + end + end + + def show + respond_to do |format| + format.json {render json: @extended_metadata_type} + end + end + + private + + def find_extended_metadata_type + @extended_metadata_type = ExtendedMetadataType.find(params[:id]) + end + +end diff --git a/app/controllers/investigations_controller.rb b/app/controllers/investigations_controller.rb index 39f6e48fa7..a57ffd1795 100644 --- a/app/controllers/investigations_controller.rb +++ b/app/controllers/investigations_controller.rb @@ -141,7 +141,7 @@ def investigation_params params.require(:investigation).permit(:title, :description, { project_ids: [] }, *creator_related_params, :position, { publication_ids: [] }, { discussion_links_attributes:[:id, :url, :label, :_destroy] }, - { custom_metadata_attributes: determine_custom_metadata_keys }) + { extended_metadata_attributes: determine_extended_metadata_keys }) end def check_studies_are_for_this_investigation diff --git a/app/controllers/isa_assays_controller.rb b/app/controllers/isa_assays_controller.rb index 18a0800b64..9db704fc6b 100644 --- a/app/controllers/isa_assays_controller.rb +++ b/app/controllers/isa_assays_controller.rb @@ -86,7 +86,7 @@ def assay_params { samples_attributes: %i[asset_id direction] }, { data_files_attributes: %i[asset_id direction relationship_type_id] }, { publication_ids: [] }, - { custom_metadata_attributes: determine_custom_metadata_keys }, + { extended_metadata_attributes: determine_extended_metadata_keys }, { discussion_links_attributes: %i[id url label _destroy] }] end diff --git a/app/controllers/isa_studies_controller.rb b/app/controllers/isa_studies_controller.rb index fe7b85f6aa..2da3ccdd1b 100644 --- a/app/controllers/isa_studies_controller.rb +++ b/app/controllers/isa_studies_controller.rb @@ -88,7 +88,7 @@ def study_params [:title, :description, :experimentalists, :investigation_id, { sop_ids: [] }, *creator_related_params, :position, { scales: [] }, { publication_ids: [] }, { discussion_links_attributes: %i[id url label _destroy] }, - { custom_metadata_attributes: determine_custom_metadata_keys }] + { extended_metadata_attributes: determine_extended_metadata_keys }] end def sample_type_params(params, field) diff --git a/app/controllers/samples_controller.rb b/app/controllers/samples_controller.rb index 362c1c1390..91d53f5645 100644 --- a/app/controllers/samples_controller.rb +++ b/app/controllers/samples_controller.rb @@ -75,6 +75,11 @@ def show def edit @sample = Sample.find(params[:id]) + if !@sample.originating_data_file.nil? && @sample.edit_count.zero? + flash.now[:error] = 'Warning: This sample was extracted from a datafile. + If you edit the sample, it will no longer correspond to the original source data.
+ Unless you cancel, a label will be added to the sample\'s source field to indicate it is no longer valid.'.html_safe + end respond_with(@sample) end diff --git a/app/controllers/single_pages_controller.rb b/app/controllers/single_pages_controller.rb index 1914f403bc..591eb2600c 100644 --- a/app/controllers/single_pages_controller.rb +++ b/app/controllers/single_pages_controller.rb @@ -1,8 +1,11 @@ require 'isatab_converter' +# Controller for the Single Page view class SinglePagesController < ApplicationController include Seek::AssetsCommon include Seek::Sharing::SharingCommon + include Seek::Publishing::PublishingCommon + include Seek::Data::SpreadsheetExplorerRepresentation before_action :set_up_instance_variable before_action :project_single_page_enabled? @@ -14,9 +17,7 @@ class SinglePagesController < ApplicationController def show @project = Project.find(params[:id]) @folders = project_folders - respond_to do |format| - format.html - end + respond_to(&:html) end def index; end @@ -61,21 +62,20 @@ def export_isa end def download_samples_excel - sample_ids, sample_type_id, study_id, assay_id = Rails.cache.read(params[:uuid]).values_at(:sample_ids, :sample_type_id, - :study_id, :assay_id) + :study_id, :assay_id) @study = Study.find(study_id) @assay = Assay.find(assay_id) unless assay_id.nil? @project = @study.projects.first - @samples = Sample.where(id: sample_ids)&.authorized_for(:view).sort_by(&:id) + @samples = Sample.where(id: sample_ids)&.authorized_for(:view)&.sort_by(&:id) - raise "Nothing to export to Excel." if @samples.nil? || @samples == [] || sample_type_id.nil? + raise 'Nothing to export to Excel.' if @samples.nil? || @samples == [] || sample_type_id.nil? @sample_type = SampleType.find(sample_type_id) sample_attributes = @sample_type.sample_attributes.map do |sa| - obj = if (sa.sample_controlled_vocab_id.nil?) + obj = if sa.sample_controlled_vocab_id.nil? { sa_cv_title: sa.title, sa_cv_id: nil } else { sa_cv_title: sa.title, sa_cv_id: sa.sample_controlled_vocab_id } @@ -83,17 +83,20 @@ def download_samples_excel obj.merge({ required: sa.required }) end - @sa_cv_terms = [{ "name" => "id", "has_cv" => false, "data" => nil, "allows_custom_input" => nil, "required" => nil }, - { "name" => "uuid", "has_cv" => false, "data" => nil, "allows_custom_input" => nil, "required" => nil }] + @sa_cv_terms = [{ 'name' => 'id', 'has_cv' => false, 'data' => nil, 'allows_custom_input' => nil, 'required' => nil }, + { 'name' => 'uuid', 'has_cv' => false, 'data' => nil, 'allows_custom_input' => nil, + 'required' => nil }] sample_attributes.map do |sa| - if sa[:sa_cv_id].nil? - @sa_cv_terms.push({ "name" => sa[:sa_cv_title], "has_cv" => false, "data" => nil, "allows_custom_input" => nil, "required" => sa[:required] }) - else - allows_custom_input = SampleControlledVocab.find(sa[:sa_cv_id])&.custom_input - sa_terms = SampleControlledVocabTerm.where(sample_controlled_vocab_id: sa[:sa_cv_id]).map(&:label) - @sa_cv_terms.push({ "name" => sa[:sa_cv_title], "has_cv" => true, "data" => sa_terms, "allows_custom_input" => allows_custom_input, "required" => sa[:required] }) - end + if sa[:sa_cv_id].nil? + @sa_cv_terms.push({ 'name' => sa[:sa_cv_title], 'has_cv' => false, 'data' => nil, + 'allows_custom_input' => nil, 'required' => sa[:required] }) + else + allows_custom_input = SampleControlledVocab.find(sa[:sa_cv_id])&.custom_input + sa_terms = SampleControlledVocabTerm.where(sample_controlled_vocab_id: sa[:sa_cv_id]).map(&:label) + @sa_cv_terms.push({ 'name' => sa[:sa_cv_title], 'has_cv' => true, 'data' => sa_terms, + 'allows_custom_input' => allows_custom_input, 'required' => sa[:required] }) + end end @template = Template.find(@sample_type.template_id) @@ -102,13 +105,15 @@ def download_samples_excel flash[:error] = e.message respond_to do |format| format.html { redirect_to single_page_path(@project.id) } - format.json { render json: { parameters: { sample_ids: sample_ids, sample_type_id: sample_type_id, study_id: study_id } } } + format.json do + render json: { parameters: { sample_ids:, sample_type_id:, study_id: } } + end end end def export_to_excel cache_uuid = UUID.new.generate - id_label = "#{Seek::Config::instance_name} id" + id_label = "#{Seek::Config.instance_name} id" sample_ids = JSON.parse(params[:sample_data]).map { |sample| sample[id_label] unless sample[id_label] == '#HIDDEN' } sample_type_id = JSON.parse(params[:sample_type_id]) study_id = JSON.parse(params[:study_id]) @@ -122,8 +127,232 @@ def export_to_excel end end + def upload_samples + uploaded_file = params[:file] + project_id = params[:project_id] + @project = Project.find(project_id) + + # Check file type if is xls or xlsx + case uploaded_file.content_type + when 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + spreadsheet_xml = spreadsheet_to_xml(uploaded_file.path, Seek::Config.jvm_memory_allocation) + wb = parse_spreadsheet_xml(spreadsheet_xml) + metadata_sheet = wb.sheet('Metadata') + samples_sheet = wb.sheet('Samples') + else + raise "Please upload a valid spreadsheet file with extension '.xlsx'" + end + + sample_type_id_ui = params[:sample_type_id].to_i + + unless valid_workbook?(wb) + raise 'Invalid workbook! Cannot process this spreadsheet. Consider first exporting the table as a spreadsheet for the proper format.' + end + + # Extract Samples metadata from spreadsheet + study_id = metadata_sheet.cell(2, 2).value.to_i + @study = Study.find(study_id) + sample_type_id = metadata_sheet.cell(5, 2).value.to_i + @sample_type = SampleType.find(sample_type_id) + is_assay = @sample_type.assays.any? + @assay = @sample_type.assays.first + + # Sample Type validation rules + unless sample_type_id_ui == @sample_type&.id + raise "Sample Type #{@sample_type&.id} from spreadsheet doesn't match Sample Type #{sample_type_id_ui} from the table. Please upload in the correct table." + end + unless @study.sample_types.include?(@sample_type) || is_assay + raise "Sample Type '#{@sample_type.id}' doesn't belong to Study #{@study.id}. Sample Upload aborted." + end + unless (@assay&.sample_type == @sample_type) || !is_assay + raise "Sample Type '#{@sample_type.id}' doesn't belong to Assay #{@assay.id}. Sample Upload aborted." + end + + @multiple_input_fields = @sample_type.sample_attributes.map do |sa_attr| + sa_attr.title if sa_attr.sample_attribute_type.base_type == 'SeekSampleMulti' + end + + sample_fields, samples_data = get_spreadsheet_data(samples_sheet) + + # Compare Excel header row to Sample Type Sample Attributes + # Should raise an error if they don't match + sample_type_attributes = %w[id uuid].concat(@sample_type.sample_attributes.map(&:title)) + has_unmapped_sample_attributes = sample_type_attributes.map { |sa| sample_fields.include?(sa) }.include?(false) + if has_unmapped_sample_attributes + raise "The Sample Attributes from the excel sheet don't match those of the Sample Type in the database. Sample upload was aborted!" + end + + # Construct Samples objects from Excel data + excel_samples = generate_excel_samples(samples_data, sample_fields) + + existing_excel_samples = excel_samples.map { |sample| sample unless sample['id'].nil? }.compact + new_excel_samples = excel_samples.map { |sample| sample if sample['id'].nil? }.compact + + # Retrieve all samples of the Sample Type, also the unauthorized ones + @db_samples = sample_type_samples(@sample_type) + # Retrieve the Sample Types samples wich are authorized for editing + @authorized_db_samples = sample_type_samples(@sample_type, :edit) + + # Determine whether samples have been modified or not, + # and checking whether the user is permitted to edit them + @unauthorized_samples, @update_samples = separate_unauthorized_samples(existing_excel_samples, @db_samples, + @authorized_db_samples) + + # Determine if the new samples are no duplicates of existing ones, + # based on the attribute values + @possible_duplicates, @new_samples = separate_possible_duplicates(new_excel_samples, @db_samples) + + upload_data = { study: @study, + assay: @assay, + sampleType: @sample_type, + excel_samples:, + existingExcelSamples: existing_excel_samples, + newExcelSamples: new_excel_samples, + updateSamples: @update_samples, + newSamples: @new_samples, + possibleDuplicates: @possible_duplicates, + dbSamples: @db_samples, + authorized_db_samples: @authorized_db_samples, + unauthorized_samples: @unauthorized_samples } + + respond_to do |format| + format.json { render json: { uploadData: upload_data } } + format.html { render 'single_pages/sample_upload_content', { layout: false } } + end + rescue StandardError => e + flash[:error] = e.message + respond_to do |format| + format.html { redirect_to single_page_path(@project), status: :bad_request } + format.json { render json: { error: e }, status: :bad_request } + end + end + private + def get_spreadsheet_data(samples_sheet) + sample_fields = samples_sheet.row(1).actual_cells.map { |field| field&.value&.sub(' *', '') }.compact + samples_data = (2..samples_sheet.actual_rows.size).map do |i| + sample_cells = samples_sheet.row(i).cells + next if sample_cells.all? { |cell| (cell&.value == '' || cell&.value.nil?) } + + sample_cells.map do |cell| + cell&.value + end.drop(1) + end.compact + + [sample_fields, samples_data] + end + + def generate_excel_samples(samples_data, sample_fields) + samples_data.map do |excel_sample| + obj = {} + (0..sample_fields.size - 1).map do |i| + if @multiple_input_fields.include?(sample_fields[i]) + parsed_excel_input_samples = JSON.parse(excel_sample[i].gsub(/"=>/x, '":')).map do |subsample| + # Uploader should at least have viewing permissions for the inputs he's using + unless Sample.find(subsample['id'])&.authorized_for_view? + raise "Unauthorized Sample was detected in spreadsheet: #{subsample.inspect}" + end + + subsample + end + obj.merge!(sample_fields[i] => parsed_excel_input_samples) + elsif sample_fields[i] == 'id' + if excel_sample[i] == '' + obj.merge!(sample_fields[i] => nil) + else + obj.merge!(sample_fields[i] => excel_sample[i]&.to_i) + end + else + obj.merge!(sample_fields[i] => excel_sample[i]) + end + end + obj + end + end + + def sample_type_samples(sample_type, authorization_method = nil) + if authorization_method + sample_type.samples&.authorized_for(authorization_method)&.map do |sample| + attributes = JSON.parse(sample[:json_metadata]) + { 'id' => sample.id, + 'uuid' => sample.uuid }.merge(attributes) + end + else + sample_type.samples&.map do |sample| + attributes = JSON.parse(sample[:json_metadata]) + { 'id' => sample.id, + 'uuid' => sample.uuid }.merge(attributes) + end + end + end + + def separate_unauthorized_samples(existing_excel_samples, db_samples, authorized_db_samples) + update_samples = [] + unauthorized_samples = [] + existing_excel_samples.map do |ees| + db_sample = db_samples.select { |s| s['id'] == ees['id'] }.first + + # An exception is raised if the ID of an existing Sample cannot be found in the DB + raise "Sample with id '#{ees['id']}' does not exist in the database. Sample upload was aborted!" if db_sample.nil? + + is_authorized_for_update = authorized_db_samples.select { |s| s['id'] == ees['id'] }.any? + + is_changed = false + + db_sample.map do |k, v| + unless ees[k] == v + is_changed = true + break + end + end + + if is_changed + if is_authorized_for_update + update_samples.append(ees) + else + unauthorized_samples.append(ees) + end + end + end + [unauthorized_samples, update_samples] + end + + def separate_possible_duplicates(new_excel_samples, db_samples) + possible_duplicates = [] + new_samples = [] + new_excel_samples.map do |nes| + is_duplicate = true + + db_samples.map do |dbs| + dbs.map do |k, v| + unless %w[id uuid].include?(k) + is_duplicate = (nes[k] == v) + break unless is_duplicate + end + end + + if is_duplicate + possible_duplicates.append(nes.merge({ 'duplicate' => dbs })) + break + end + end + + if db_samples.none? + new_samples.append(nes) + else + new_samples.append(nes) unless is_duplicate + end + end + [possible_duplicates, new_samples] + end + + def valid_workbook?(workbook) + !((workbook.sheet_names.map do |sheet| + %w[Metadata Samples cv_ontology].include? sheet + end.include? false) && (workbook.sheets.size != 3)) + end + def set_up_instance_variable @single_page = true end @@ -134,8 +363,8 @@ def find_authorized_investigation end def check_user_logged_in - unless current_user - render json: { status: :unprocessable_entity, error: 'You must be logged in to access batch sharing permission.' } - end + return if current_user + + render json: { status: :unprocessable_entity, error: 'You must be logged in to access batch sharing permission.' } end end diff --git a/app/controllers/snapshots_controller.rb b/app/controllers/snapshots_controller.rb index 7f1d9fb36a..ebeb52f528 100644 --- a/app/controllers/snapshots_controller.rb +++ b/app/controllers/snapshots_controller.rb @@ -15,8 +15,13 @@ class SnapshotsController < ApplicationController def create @snapshot = @resource.create_snapshot - flash[:notice] = "Snapshot created" - redirect_to polymorphic_path([@resource, @snapshot]) + if @snapshot + flash[:notice] = "Snapshot created" + redirect_to polymorphic_path([@resource, @snapshot]) + else + flash[:error] = @resource.errors.full_messages.join(', ') + redirect_to polymorphic_path(@resource) + end end def show diff --git a/app/controllers/studies_controller.rb b/app/controllers/studies_controller.rb index bec5ef69fc..2849b2f1a8 100644 --- a/app/controllers/studies_controller.rb +++ b/app/controllers/studies_controller.rb @@ -70,11 +70,10 @@ def update format.html { redirect_to(@study) } end else - @study.attributes = study_params + @study.assign_attributes(study_params) update_sharing_policies @study update_annotations(params[:tag_list], @study) update_relationships(@study, params) - update_linked_custom_metadatas @study respond_to do |format| if @study.save @@ -91,6 +90,7 @@ def update def delete_linked_sample_types return unless is_single_page_study? + return if @study.sample_types.empty? # The study sample types must be destroyed in reversed order # otherwise the first sample type won't be removed becaused it is linked from the second @@ -114,7 +114,6 @@ def create update_sharing_policies @study update_annotations(params[:tag_list], @study) update_relationships(@study, params) - update_linked_custom_metadatas @study ### TO DO: what about validation of person responsible? is it already included (for json?) if @study.save @@ -203,7 +202,7 @@ def batch_create # e.g: Study.new(title: 'title', investigation: investigations(:metabolomics_investigation), policy: FactoryBot.create(:private_policy)) # study.policy = Policy.create(name: 'default policy', access_type: 1) # render plain: params[:studies].inspect - metadata_types = CustomMetadataType.where(title: 'MIAPPE metadata', supported_type: 'Study').last + metadata_types = ExtendedMetadataType.where(title: 'MIAPPE metadata', supported_type: 'Study').last studies_length = params[:studies][:title].length studies_uploaded = false data_file_uploaded = false @@ -213,14 +212,14 @@ def batch_create title: params[:studies][:title][index], description: params[:studies][:description][index], investigation_id: params[:study][:investigation_id], - custom_metadata: CustomMetadata.new( - custom_metadata_type: metadata_types, + extended_metadata: ExtendedMetadata.new( + extended_metadata_type: metadata_types, data: metadata ) } @study = Study.new(study_params) StudyBatchUpload.check_study_is_MIAPPE_compliant(@study, metadata) - if @study.valid? && @study.save! && @study.custom_metadata.valid? + if @study.valid? && @study.save! && @study.extended_metadata.valid? studies_uploaded = true if @study.save end data_file_uploaded = create_batch_assay_asset(params, index) @@ -264,7 +263,7 @@ def create_batch_assay_asset(params, index) data_file_names.length.times do |data_file_index| study_metadata_id = params[:studies][:id][index] - study_id = CustomMetadata.where('json_metadata LIKE ?', "%\"id\":\"#{study_metadata_id}\"%").last.item_id + study_id = ExtendedMetadata.where('json_metadata LIKE ?', "%\"id\":\"#{study_metadata_id}\"%").last.item_id assay_class_id = AssayClass.where(title: 'Experimental assay').first.id data_file_description = params[:studies][:data_file_description][index].remove(' ').split(',') assay_params = { @@ -345,7 +344,7 @@ def remove_existing_studies(studies) metadata_id = JSON.parse(study.gsub("=>",":"))["metadata_id"].to_i Study.where(id: study_id).delete_all - CustomMetadata.where(id: metadata_id).delete_all + ExtendedMetadata.where(id: metadata_id).delete_all assays = Assay.where(study_id: study_id) assays.each do |assay| AssayAsset.where(assay_id: assay.id).delete_all @@ -359,7 +358,7 @@ def study_params params.require(:study).permit(:title, :description, :experimentalists, :investigation_id, *creator_related_params, :position, { publication_ids: [] }, { discussion_links_attributes:[:id, :url, :label, :_destroy] }, - { custom_metadata_attributes: determine_custom_metadata_keys }) + { extended_metadata_attributes: determine_extended_metadata_keys }) end end diff --git a/app/helpers/assets_helper.rb b/app/helpers/assets_helper.rb index b3b74a9228..05c4d64ee1 100644 --- a/app/helpers/assets_helper.rb +++ b/app/helpers/assets_helper.rb @@ -94,18 +94,6 @@ def publishing_item_param(item) "publish[#{item.class.name}][#{item.id}]" end - def sharing_item_param(item) - if item.try(:is_isa?) - "share_isa[#{item.class.name}][#{item.id}]" - elsif (item.respond_to? (:investigations)) && (!item.investigations.any?) - "share_not_isa[#{item.class.name}][#{item.id}]" - elsif !item.respond_to? (:investigations) - "share_not_isa[#{item.class.name}][#{item.id}]" - else - "share_isa[#{item.class.name}][#{item.id}]" - end - end - def include_downloadable_item?(items) has_downloadable_item = false items.each do |item| @@ -346,4 +334,10 @@ def controlled_vocab_annotation_items(controlled_vocab_terms) end.join(', ').html_safe end + def batch_selection_collapse_toggle + content_tag(:span, class: 'batch-selection-collapse-toggle open') do + concat content_tag(:span, '', class: 'glyphicon glyphicon-menu-down', 'aria-hidden' => 'true') + concat content_tag(:span, '', class: 'glyphicon glyphicon-menu-right', 'aria-hidden' => 'true') + end + end end diff --git a/app/helpers/custom_metadata_helper.rb b/app/helpers/custom_metadata_helper.rb deleted file mode 100644 index 1801554c4c..0000000000 --- a/app/helpers/custom_metadata_helper.rb +++ /dev/null @@ -1,27 +0,0 @@ -module CustomMetadataHelper - include SamplesHelper - - def custom_metadata_form_field_for_attribute(attribute, resource) - element_class = "custom_metadata_attribute_#{attribute.sample_attribute_type.base_type.downcase}" - element_name = "#{resource.class.name.underscore}[custom_metadata_attributes][data][#{attribute.title}]" - - if attribute.linked_custom_metadata? - content_tag(:span, class: 'linked_custom_metdata') do - folding_panel(attribute.label, false, id:attribute.title) do - attribute_form_element(attribute, resource.custom_metadata, element_name, element_class) - end - end - else - content_tag(:label,attribute.label, class: attribute.required? ? 'required' : '') + - attribute_form_element(attribute, resource.custom_metadata, element_name, element_class) - end - end - - def custom_metadata_attribute_description(description) - html = '

' - html += ''+description+'' - html += '

' - html.html_safe - end - -end \ No newline at end of file diff --git a/app/helpers/extended_metadata_helper.rb b/app/helpers/extended_metadata_helper.rb new file mode 100644 index 0000000000..e86a5094f0 --- /dev/null +++ b/app/helpers/extended_metadata_helper.rb @@ -0,0 +1,45 @@ +module ExtendedMetadataHelper + include SamplesHelper + + def extended_metadata_form_field_for_attribute(attribute, resource) + element_class = "extended_metadata_attribute_#{attribute.sample_attribute_type.base_type.downcase}" + element_name = "#{resource.class.name.underscore}[extended_metadata_attributes][data][#{attribute.title}]" + + if attribute.linked_extended_metadata? || attribute.linked_extended_metadata_multi? + content_tag(:span, class: 'linked_extended_metdata') do + folding_panel(attribute.label, false, id:attribute.title) do + attribute_form_element(attribute, resource.extended_metadata.get_attribute_value(attribute.title), element_name, element_class) + end + end + else + content_tag(:label,attribute.label, class: attribute.required? ? 'required' : '') + + attribute_form_element(attribute, resource.extended_metadata.get_attribute_value(attribute.title), element_name, element_class) + end + end + + def extended_metadata_attribute_description(description) + html = '

' + html += ''+description+'' + html += '

' + html.html_safe + end + + def render_extended_metadata_value(attribute, resource) + + if resource.extended_metadata.data[attribute.title].blank? + return '' # Return an empty string if the extended metadata is blank. + end + + content_tag(:div, class: 'extended_metadata') do + if attribute.linked_extended_metadata? || attribute.linked_extended_metadata_multi? + content_tag(:span, class: 'linked_extended_metdata_display') do + folding_panel(attribute.label, true, id: attribute.title) do + display_attribute(resource.extended_metadata, attribute, link: true) + end + end + else + label_tag("#{attribute.label} : ") + " " + display_attribute(resource.extended_metadata, attribute, link: true) + end + end + end +end diff --git a/app/helpers/policy_helper.rb b/app/helpers/policy_helper.rb index 1a70d38ff7..7c431caa93 100644 --- a/app/helpers/policy_helper.rb +++ b/app/helpers/policy_helper.rb @@ -159,7 +159,7 @@ def project_policy_json(project) hash.to_json.html_safe end - def permission_title(permission, member_prefix: false, icon: false) + def permission_title(permission, member_prefix: false, icon: false, link: false) if permission.is_a?(Permission) type = permission.contributor_type contributor = permission.contributor @@ -168,18 +168,31 @@ def permission_title(permission, member_prefix: false, icon: false) contributor = permission end - if type == 'Person' + option = { target: :_blank } + case type + when 'Person' text = "#{contributor.first_name} #{contributor.last_name}" - elsif type == 'WorkGroup' - text = "#{member_prefix ? 'Members of ' : ''}#{contributor.project.title} @ #{contributor.institution.title}" + text = link_to(h(text), contributor, option).html_safe if link + when 'WorkGroup' + institution = contributor.institution + project = contributor.project + if link + text = "#{member_prefix ? 'Members of ' : ''}#{link_to(h(project.title), project, option)} @ #{link_to(h(institution.title), institution, option)}".html_safe + else + text = "#{member_prefix ? 'Members of ' : ''}#{project.title} @ #{institution.title}" + end else - text = "#{member_prefix ? 'Members of ' : ''}#{contributor.title}" + if link + text = "#{member_prefix ? 'Members of ' : ''}#{link_to(h(contributor.title), contributor, option)}".html_safe + else + text = "#{member_prefix ? 'Members of ' : ''}#{contributor.title}" + end end if icon content_tag(:span, class: 'type-icon-wrapper') do image_tag(asset_path(icon_filename_for_key(type.underscore)), class: 'type-icon') - end.html_safe + " #{text}" + end.html_safe + " #{text}".html_safe else text end diff --git a/app/helpers/sample_types_helper.rb b/app/helpers/sample_types_helper.rb index acc6935501..f2806d453f 100644 --- a/app/helpers/sample_types_helper.rb +++ b/app/helpers/sample_types_helper.rb @@ -91,7 +91,7 @@ def sample_attribute_pid_help_icon private def displayed_sample_attribute_types - SampleAttributeType.all.select{|x|!x.linked_custom_metadata?} + SampleAttributeType.all.reject{ |x|x.linked_extended_metadata? || x.linked_extended_metadata_multi? } end def attribute_type_link(sample_type_attribute) diff --git a/app/helpers/samples_helper.rb b/app/helpers/samples_helper.rb index 873898de09..5102b13263 100644 --- a/app/helpers/samples_helper.rb +++ b/app/helpers/samples_helper.rb @@ -3,7 +3,7 @@ def sample_form_field_for_attribute(attribute, resource) element_class = "sample_attribute_#{attribute.sample_attribute_type.base_type.downcase}" element_name = "sample[data][#{attribute.title}]" - attribute_form_element(attribute, resource, element_name, element_class) + attribute_form_element(attribute, resource.get_attribute_value(attribute.title), element_name, element_class) end def controlled_vocab_form_field(sample_controlled_vocab, element_name, values, limit = 1) @@ -41,33 +41,31 @@ def controlled_vocab_list_form_field(sample_controlled_vocab, element_name, valu controlled_vocab_form_field(sample_controlled_vocab, element_name, values, nil) end - def linked_custom_metadata_form_field(attribute,resource,element_name, element_class,depth) - linked_cms = resource.linked_custom_metadatas.select{|cm|cm.custom_metadata_attribute==attribute} - - id = linked_cms.blank? ? nil : linked_cms.select{|cm| cm.custom_metadata_type.id == attribute.linked_custom_metadata_type.id}.first.id + def linked_extended_metadata_multi_form_field(attribute, value, element_name, element_class) + render partial: 'extended_metadata/fancy_linked_extended_metadata_multi_attribute_fields', + locals: { value: value, attribute: attribute, element_name: element_name, element_class: element_class, collapsed: false } + end + def linked_extended_metadata_form_field(attribute, value, element_name, element_class,depth) html = '' - html += hidden_field_tag "#{element_name}[id]",id - html += hidden_field_tag "#{element_name}[custom_metadata_type_id]", attribute.linked_custom_metadata_type.id - html += hidden_field_tag "#{element_name}[custom_metadata_attribute_id]", attribute.id - attribute.linked_custom_metadata_type.custom_metadata_attributes.each do |attr| - linked_cm = linked_cms.select{|cm| cm.custom_metadata_type_id == attr.custom_metadata_type_id}.first - linked_cm ||= CustomMetadata.new(:custom_metadata_type_id => attr.custom_metadata_type_id) + Rails.logger.info ActiveSupport::LogSubscriber.new.send(:color, attribute.inspect, :blue, bold = true) - attr_element_name = "#{element_name}][data][#{attr.title}]" + attribute.linked_extended_metadata_type.extended_metadata_attributes.each do |attr| + attr_element_name = "#{element_name}[#{attr.title}]" html += '
' html += required_span if attr.required? - if attr.linked_custom_metadata? - html += '
' - html += attribute_form_element(attr, linked_cm, attr_element_name, element_class,depth+1) + v = value ? value[attr.title] : nil + if attr.linked_extended_metadata? + html += '
' + html += attribute_form_element(attr, v, attr_element_name, element_class,depth+1) html += '
' else - html += attribute_form_element(attr, linked_cm, attr_element_name, element_class) + html += attribute_form_element(attr, v, attr_element_name, element_class) end unless attr.description.nil? - html += custom_metadata_attribute_description(attr.description) + html += extended_metadata_attribute_description(attr.description) end html += '
' end @@ -118,8 +116,12 @@ def sample_attribute_display_title(attribute) title.html_safe end - def display_attribute(sample, attribute, options = {}) - value = sample.get_attribute_value(attribute) + def display_attribute(resource, attribute, options = {}) + value = resource.get_attribute_value(attribute) + display_attribute_value(value, attribute, options.merge(resource: resource)) + end + + def display_attribute_value(value, attribute, options = {}) if value.blank? text_or_not_specified(value) else @@ -140,10 +142,12 @@ def display_attribute(sample, attribute, options = {}) seek_cv_attribute_display(value, attribute) when Seek::Samples::BaseType::CV_LIST value.each{|v| seek_cv_attribute_display(v, attribute) }.join(', ') - when Seek::Samples::BaseType::LINKED_CUSTOM_METADATA - linked_custom_metadata_attribute_display(value) + when Seek::Samples::BaseType::LINKED_EXTENDED_METADATA + linked_extended_metadata_attribute_display(value, attribute) + when Seek::Samples::BaseType::LINKED_EXTENDED_METADATA_MULTI + linked_extended_metadata_multi_attribute_display(value, attribute) else - default_attribute_display(attribute, options, sample, value) + default_attribute_display(value, attribute, options) end end end @@ -157,19 +161,36 @@ def seek_cv_attribute_display(value, attribute) content end - def linked_custom_metadata_attribute_display(value) + def linked_extended_metadata_attribute_display(value, attribute) html = '' html += '' html.html_safe end + def linked_extended_metadata_multi_attribute_display(values, attribute) + html = '' + values.each do |value| + html += linked_extended_metadata_attribute_display(value, attribute) + end + html.html_safe + end + def seek_sample_attribute_display(value) if value.kind_of?(Array) value.map {|v| seek_resource_attribute_display(Sample,v)} .join(", ").html_safe @@ -195,14 +216,14 @@ def seek_resource_attribute_display(clz, value) end end - def default_attribute_display(attribute, options, sample, value) - resolution = attribute.resolve (value) - if (resolution != nil) + def default_attribute_display(value, attribute, options) + resolution = attribute.resolve(value) + if resolution link_to(value, resolution, target: :_blank) - else if options[:link] && attribute.is_title - link_to(value, sample) + else + if options[:link] && options[:resource] && attribute.is_title + link_to(value, options[:resource]) else - text_or_not_specified(value, auto_link: options[:link]) end end @@ -282,8 +303,7 @@ def show_sample_extraction_status?(data_file) private - def attribute_form_element(attribute, resource, element_name, element_class, depth=1) - value = resource.get_attribute_value(attribute.title) + def attribute_form_element(attribute, value, element_name, element_class, depth=1) placeholder = "e.g. #{attribute.sample_attribute_type.placeholder}" unless attribute.sample_attribute_type.placeholder.blank? case attribute.sample_attribute_type.base_type @@ -327,8 +347,10 @@ def attribute_form_element(attribute, resource, element_name, element_class, dep sample_form_field attribute, element_name, value when Seek::Samples::BaseType::SEEK_SAMPLE_MULTI sample_multi_form_field attribute, element_name, value - when Seek::Samples::BaseType::LINKED_CUSTOM_METADATA - linked_custom_metadata_form_field attribute, resource, element_name, element_class,depth + when Seek::Samples::BaseType::LINKED_EXTENDED_METADATA + linked_extended_metadata_form_field attribute, value, element_name, element_class,depth + when Seek::Samples::BaseType::LINKED_EXTENDED_METADATA_MULTI + linked_extended_metadata_multi_form_field attribute, value, element_name, element_class else text_field_tag element_name, value, class: "form-control #{element_class}", placeholder: placeholder end diff --git a/app/helpers/sharing_permissions_helper.rb b/app/helpers/sharing_permissions_helper.rb deleted file mode 100644 index 63389141bb..0000000000 --- a/app/helpers/sharing_permissions_helper.rb +++ /dev/null @@ -1,225 +0,0 @@ -module SharingPermissionsHelper - - ITEMS_NOT_IN_ISA_HASH = { - "id": "not_isa-tree", - "data": { - "loadable": false - }, - "li_attr": { - class:"root-node" - }, - "a_attr": { - - }, - "children": [ - ], - "text": "Not in ISA", - "state": { - "opened": true - } - } - - ALL_INVESTIGATIONS_HASH = { - "id": "isa-tree", - "data": { - "loadable": false - }, - "li_attr": { - class:"root-node" - }, - "a_attr": { - - }, - "children": [ - ], - "text": "ISA", - "state": { - "opened": true - } - } - - def build_tree_json(hash, root_item) - - objects = hash[:nodes].map(&:object) - real_edges = hash[:edges].select { |e| objects.include?(e[0]) } - - roots = hash[:nodes].select do |n| - real_edges.none? { |_parent, child| child == n.object } - end - - nodes = roots.map { |root| create_tree_node(hash, root.object, root_item) }.flatten - nodes.to_json - end - - def create_tree_node(hash, object, root_item = nil) - - child_edges = hash[:edges].select do |parent, _child| - parent == object - end - - node = hash[:nodes].detect { |n| n.object == object } - - entry = { - id: unique_node_id(object), - data: { loadable: false }, - li_attr: { 'data-node-id' => node_id(object) }, - children: [] - } - - entry[:text] = object.title - entry[:icon] = asset_path(resource_avatar_path(object) || icon_filename_for_key("#{object.class.name.downcase}_avatar")) - - filtered_child_edges = child_edges.reject { |c| (c[1].instance_of? Publication) || (c[1].instance_of?(Seek::ObjectAggregation))} - child_edges_with_permission = filtered_child_edges.select { |c| c[1].can_manage? } - - unless child_edges_with_permission.blank? - entry[:children] += child_edges_with_permission.map { |c| create_tree_node(hash, c[1], root_item) } - end - - if node.child_count > 0 - if node.child_count > child_edges.count - entry[:children] << { - id: unique_child_count_id(object), - parent: entry[:id], - text: "Show #{node.child_count - child_edges.count} more", - a_attr: { class: 'child-count-leaf' }, - li_attr: { 'data-node-id' => child_count_id(object) }, - data: { child_count: true } - } - end - entry[:state] = { opened: false } - else - entry[:state] = { opened: false } - end - entry - end - - def add_permissions_to_tree_json (parent_node) - - parent_node.each do |node| - node["li_attr"][:class] = "asset-node-row" - node["a_attr"] = {} - node["a_attr"][:class] = "asset-node" - - if !node["children"].nil? && node["children"].size > 0 - add_permissions_to_tree_json (node["children"]) - end - add_asset_permission_nodes(node) - end - parent_node - end - - def add_asset_permission_nodes(parent_node) - asset_type = parent_node["id"].split("-")[0] - asset_id = parent_node["id"].split("-")[1].to_i - - # get asset instance - asset = safe_class_lookup(asset_type.camelize).find(asset_id) - parent_node["text"] = "#{h(asset.title)} #{icon_link_to("", "new_window", asset , options = {target:'blank',class:'asset-icon',:onclick => 'window.open(this.href, "_blank");'})}" - - permissions_array = get_permission(asset) - parent_node["children"] = permissions_array + parent_node["children"] - parent_node - end - - def asset_node_json(resource_type, resource_items) - - parent = { - id: resource_type+"-not_isa", - data: { loadable: false }, - li_attr: {class:"asset-type-node"}, - a_attr: {}, - children: [], - text: resource_type - } - - resource_items.each do |item| - - entry_item = { - id: unique_node_id(item), - data: { loadable: false }, - li_attr: { 'data-node-id' => node_id(item), class:"asset-node-row"}, - a_attr: {class:"asset-node"}, - children: [] , - icon: asset_path(resource_avatar_path(item) || icon_filename_for_key("#{item.class.name.downcase}_avatar")), - text: "#{h(item.title)} #{icon_link_to("", "new_window", item , options = {target:'blank',class:'asset-icon',:onclick => 'window.open(this.href, "_blank");'})}" - } - - permissions_array = get_permission(item) - entry_item[:children] = permissions_array - parent[:children].append(entry_item) - end - parent - end - - - def create_policy_node(item, policy_text,sharing_policy_changed) - - entry = { - id: unique_policy_node_id(item), - data: { loadable: false }, - li_attr: { 'data-node-id' => "Permission-"+node_id(item), class:"hide_permission" }, - a_attr: { class:"permission-node #{sharing_policy_changed}"}, - children: [], - text:policy_text - } - entry - end - - def get_permission (item) - - policy =[] - - # policy - downloadable = item.try(:is_downloadable?) - policy_text = "#{Policy.get_access_type_wording(item.policy.access_type, downloadable)} by Public" - sharing_policy_changed = (@batch_sharing_permission_changed && (@items_for_sharing.include? item) && !@policy_params[:access_type].nil?)? "sharing_permission_changed" : "" - - policy.append(create_policy_node(item,policy_text,sharing_policy_changed)) - - #permission - option = {:onclick => 'window.open(this.href, "_blank");'} - - item.policy.permissions.map do |permission| - case permission.contributor_type - when Permission::PROJECT - m = Project.find(permission.contributor_id) - policy_text ="#{Policy.get_access_type_wording(permission.access_type, downloadable)} by Project #{link_to(h(m.title), m, option)}" - sharing_policy_changed = @batch_sharing_permission_changed && (@items_for_sharing.include? item) ? PolicyHelper::permission_changed_item_class(permission, @policy_params) : "" - policy.append(create_policy_node(item,policy_text,sharing_policy_changed)) - when Permission::WORKGROUP - m = WorkGroup.find(permission.contributor_id) - institution = Institution.find(m.institution_id) - project = Project.find(m.project_id) - policy_text ="#{Policy.get_access_type_wording(permission.access_type, downloadable)} by #{link_to(h(project.title), project,option)} @ #{link_to(h(institution.title), institution,option)}" - sharing_policy_changed = @batch_sharing_permission_changed && (@items_for_sharing.include? item) ? PolicyHelper::permission_changed_item_class(permission, @policy_params) : "" - policy.append(create_policy_node(item,policy_text,sharing_policy_changed)) - when Permission::INSTITUTION - m = Institution.find(permission.contributor_id) - policy_text ="#{Policy.get_access_type_wording(permission.access_type, downloadable)} by Institution #{link_to(h(m.title), m,option)}" - sharing_policy_changed = @batch_sharing_permission_changed && (@items_for_sharing.include? item) ? PolicyHelper::permission_changed_item_class(permission, @policy_params) : "" - policy.append(create_policy_node(item,policy_text,sharing_policy_changed)) - when Permission::PERSON - m = Person.find(permission.contributor_id) - policy_text ="#{Policy.get_access_type_wording(permission.access_type, downloadable)} by People #{link_to(h(m.title), m,option)}" - sharing_policy_changed = @batch_sharing_permission_changed && (@items_for_sharing.include? item) ? PolicyHelper::permission_changed_item_class(permission, @policy_params) : "" - policy.append(create_policy_node(item,policy_text,sharing_policy_changed)) - when Permission::PROGRAMME - m = Programme.find(permission.contributor_id) - policy_text ="#{Policy.get_access_type_wording(permission.access_type, downloadable)} by Programme #{link_to(h(m.title), m,option)}" - sharing_policy_changed = @batch_sharing_permission_changed && (@items_for_sharing.include? item) ? PolicyHelper::permission_changed_item_class(permission, @policy_params) : "" - policy.append(create_policy_node(item,policy_text,sharing_policy_changed)) - end - end - policy - end - - - private - - def unique_policy_node_id(object) - "Permission-#{node_id(object)}-#{rand(2**32).to_s(36)}" - end - - -end diff --git a/app/helpers/studies_helper.rb b/app/helpers/studies_helper.rb index be64a73a89..1e95a3b95c 100644 --- a/app/helpers/studies_helper.rb +++ b/app/helpers/studies_helper.rb @@ -24,6 +24,6 @@ def authorised_studies(projects = nil) end def show_batch_miappe_button? - CustomMetadataType.where(supported_type: 'Study', title: 'MIAPPE metadata v1.1').any? + ExtendedMetadataType.where(supported_type: 'Study', title: 'MIAPPE metadata v1.1').any? end end diff --git a/app/helpers/workflows_helper.rb b/app/helpers/workflows_helper.rb index ca875639ca..a447ff61e1 100644 --- a/app/helpers/workflows_helper.rb +++ b/app/helpers/workflows_helper.rb @@ -49,6 +49,10 @@ def maturity_badge(level) content_tag(:span, t("maturity_level.#{level}"), class: "maturity-level label #{label_class}") end + def life_monitor_status_page_url(resource, base: Seek::Config.life_monitor_ui_url) + URI.join(base, "/workflow;uuid=#{resource.uuid}").to_s + end + def test_status_badge(resource) status = resource.test_status case status @@ -65,9 +69,8 @@ def test_status_badge(resource) label_class = 'label-default' label = t('test_status.not_available') end - url = LifeMonitor::Rest::Client.status_page_url(resource) - link_to(url, class: 'lifemonitor-status btn btn-default', target: '_blank', rel: 'noopener', - 'data-tooltip' => 'Click to view in LifeMonitor') do + link_to(life_monitor_status_page_url(resource), class: 'lifemonitor-status btn btn-default', target: '_blank', + rel: 'noopener', 'data-tooltip' => 'Click to view in LifeMonitor') do image('life_monitor_icon', class: 'icon lifemonitor-logo') + 'Tests ' + content_tag(:span, label, class: "test-status label #{label_class}") diff --git a/app/models/application_record.rb b/app/models/application_record.rb index a89af79011..1cd2df850e 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -15,7 +15,7 @@ class ApplicationRecord < ActiveRecord::Base include Seek::TitleTrimmer include Seek::ActsAsAsset include Seek::ActsAsISA - include HasCustomMetadata + include HasExtendedMetadata include Seek::Doi::ActsAsDoiMintable include Seek::Doi::ActsAsDoiParent include Seek::ResearchObjects::ActsAsSnapshottable diff --git a/app/models/concerns/has_custom_metadata.rb b/app/models/concerns/has_custom_metadata.rb deleted file mode 100644 index c618cd5344..0000000000 --- a/app/models/concerns/has_custom_metadata.rb +++ /dev/null @@ -1,28 +0,0 @@ -module HasCustomMetadata - extend ActiveSupport::Concern - - included do - def custom_metadata_attribute_values_for_search - custom_metadata ? custom_metadata.data.values.reject(&:blank?).uniq : [] - end - end - - class_methods do - def has_extended_custom_metadata - has_one :custom_metadata, as: :item, dependent: :destroy - accepts_nested_attributes_for :custom_metadata - - if Seek::Config.solr_enabled - searchable(auto_index: false) do - text :custom_metadata_attribute_values do - custom_metadata_attribute_values_for_search - end - text :custom_metadata_type do - custom_metadata.custom_metadata_type.title if custom_metadata.present? - end - end - end - end - end -end - diff --git a/app/models/concerns/has_extended_metadata.rb b/app/models/concerns/has_extended_metadata.rb new file mode 100644 index 0000000000..c99293ab19 --- /dev/null +++ b/app/models/concerns/has_extended_metadata.rb @@ -0,0 +1,28 @@ +module HasExtendedMetadata + extend ActiveSupport::Concern + + included do + def extended_metadata_attribute_values_for_search + extended_metadata ? extended_metadata.data.values.reject(&:blank?).uniq : [] + end + end + + class_methods do + def has_extended_metadata + has_one :extended_metadata, as: :item, dependent: :destroy, autosave: true + accepts_nested_attributes_for :extended_metadata + + if Seek::Config.solr_enabled + searchable(auto_index: false) do + text :extended_metadata_attribute_values do + extended_metadata_attribute_values_for_search + end + text :extended_metadata_type do + extended_metadata.extended_metadata_type.title if extended_metadata.present? + end + end + end + end + end +end + diff --git a/app/models/custom_metadata.rb b/app/models/custom_metadata.rb deleted file mode 100644 index 9831795302..0000000000 --- a/app/models/custom_metadata.rb +++ /dev/null @@ -1,78 +0,0 @@ -class CustomMetadata < ApplicationRecord - include Seek::JSONMetadata::Serialization - - belongs_to :item, polymorphic: true - belongs_to :custom_metadata_type, validate: true - belongs_to :custom_metadata_attribute - - has_many :custom_metadata_resource_links, inverse_of: :custom_metadata, dependent: :destroy - has_many :linked_custom_metadatas, through: :custom_metadata_resource_links, source: :resource, source_type: 'CustomMetadata', dependent: :destroy - accepts_nested_attributes_for :linked_custom_metadatas - - validates_with CustomMetadataValidator - validates_associated :linked_custom_metadatas - - delegate :custom_metadata_attributes, to: :custom_metadata_type - - - after_create :update_linked_custom_metadata_id, if: :has_linked_custom_metadatas? - - def update_linked_custom_metadata_id - linked_custom_metadatas.each do |cm| - attr_name = cm.custom_metadata_attribute.title - data.mass_assign(data.to_hash.update({attr_name => cm.id}), pre_process: false) - update_column(:json_metadata, data.to_json) - end - end - - def has_linked_custom_metadatas? - linked_custom_metadatas.any? - end - - - # for polymorphic behaviour with sample - alias_method :metadata_type, :custom_metadata_type - - def custom_metadata_type=(type) - super - @data = Seek::JSONMetadata::Data.new(type) - update_json_metadata - type - end - - def attribute_class - CustomMetadataAttribute - end - - def update_linked_custom_metadata(parameters) - cmt_id = parameters[:custom_metadata_type_id] - - # return no custom metdata is filled - seek_cm_attrs = CustomMetadataType.find(cmt_id).custom_metadata_attributes.select(&:linked_custom_metadata?) - return if seek_cm_attrs.blank? - - seek_cm_attrs.each do |cma| - cma_params = parameters[:data][cma.title.to_sym] - self.set_linked_custom_metadatas(cma, cma_params) unless cma_params.nil? - - cma_linked_cmt = cma.linked_custom_metadata_type.attributes_with_linked_custom_metadata_type - - unless cma_linked_cmt.blank? - cm = self.linked_custom_metadatas.select{|cm| cm.custom_metadata_type.id == cma[:linked_custom_metadata_type_id]}.first - cm.update_linked_custom_metadata(cma_params) - end - - end - end - - def set_linked_custom_metadatas(cma, cm_params) - - if self.new_record? - self.linked_custom_metadatas.build(custom_metadata_type: cma.linked_custom_metadata_type, data: cm_params[:data], custom_metadata_attribute_id: cm_params[:custom_metadata_attribute_id]) - else - linked_cm = self.linked_custom_metadatas.select{|cm| cm.custom_metadata_type_id.to_s == cm_params[:custom_metadata_type_id]}.select{|cm|cm.custom_metadata_attribute==cma}.first - linked_cm.update(cm_params.permit!) - end - end - -end diff --git a/app/models/custom_metadata_resource_link.rb b/app/models/custom_metadata_resource_link.rb deleted file mode 100644 index 2d77cb1984..0000000000 --- a/app/models/custom_metadata_resource_link.rb +++ /dev/null @@ -1,4 +0,0 @@ -class CustomMetadataResourceLink < ApplicationRecord - belongs_to :custom_metadata - belongs_to :resource, polymorphic: true -end diff --git a/app/models/custom_metadata_type.rb b/app/models/custom_metadata_type.rb deleted file mode 100644 index d8cf297946..0000000000 --- a/app/models/custom_metadata_type.rb +++ /dev/null @@ -1,37 +0,0 @@ -class CustomMetadataType < ApplicationRecord - has_many :custom_metadata_attributes, inverse_of: :custom_metadata_type, dependent: :destroy - - validates :title, presence: true - validates :custom_metadata_attributes, presence: true - validates :supported_type, presence: true - validate :supported_type_must_be_valid_type - validate :unique_titles_for_custom_metadata_attributes - - alias_method :metadata_attributes, :custom_metadata_attributes - - def attribute_by_title(title) - custom_metadata_attributes.where(title: title).first - end - - def attribute_by_method_name(method_name) - custom_metadata_attributes.detect { |attr| attr.method_name == method_name } - end - - def attributes_with_linked_custom_metadata_type - custom_metadata_attributes.reject {|attr| attr.linked_custom_metadata_type.nil?} - end - - def supported_type_must_be_valid_type - return if supported_type.blank? # already convered by presence validation - unless Seek::Util.lookup_class(supported_type, raise: false) - errors.add(:supported_type, 'is not a type that can supported custom metadata') - end - end - - def unique_titles_for_custom_metadata_attributes - titles = custom_metadata_attributes.collect(&:title) - if titles != titles.uniq - errors.add(:custom_metadata_attributes, 'must have unique titles') - end - end -end diff --git a/app/models/data_file.rb b/app/models/data_file.rb index e77a9a6565..d46c400947 100644 --- a/app/models/data_file.rb +++ b/app/models/data_file.rb @@ -150,7 +150,7 @@ def extract_samples(sample_type, confirm = false, overwrite = false) sample.project_ids = project_ids sample.contributor = contributor sample.originating_data_file = self - sample.policy = policy + sample.policy = policy.deep_copy sample.save if sample.valid? && confirm extracted << sample diff --git a/app/models/extended_metadata.rb b/app/models/extended_metadata.rb new file mode 100644 index 0000000000..873b2a3a2e --- /dev/null +++ b/app/models/extended_metadata.rb @@ -0,0 +1,25 @@ +class ExtendedMetadata < ApplicationRecord + include Seek::JSONMetadata::Serialization + + belongs_to :item, polymorphic: true + belongs_to :extended_metadata_type, validate: true + belongs_to :extended_metadata_attribute + + validates_with ExtendedMetadataValidator + + delegate :extended_metadata_attributes, to: :extended_metadata_type + + # for polymorphic behaviour with sample + alias_method :metadata_type, :extended_metadata_type + + def extended_metadata_type=(type) + super + @data = Seek::JSONMetadata::Data.new(type) + update_json_metadata + type + end + + def attribute_class + ExtendedMetadataAttribute + end +end diff --git a/app/models/custom_metadata_attribute.rb b/app/models/extended_metadata_attribute.rb similarity index 52% rename from app/models/custom_metadata_attribute.rb rename to app/models/extended_metadata_attribute.rb index 5385115637..0bfdd55db1 100644 --- a/app/models/custom_metadata_attribute.rb +++ b/app/models/extended_metadata_attribute.rb @@ -1,9 +1,9 @@ -class CustomMetadataAttribute < ApplicationRecord +class ExtendedMetadataAttribute < ApplicationRecord include Seek::JSONMetadata::Attribute - belongs_to :custom_metadata_type - belongs_to :linked_custom_metadata_type, class_name: 'CustomMetadataType' - has_many :custom_metadatas + belongs_to :extended_metadata_type + belongs_to :linked_extended_metadata_type, class_name: 'ExtendedMetadataType' + has_many :extended_metadatas # to behave like a sample attribute, but is never a title def is_title diff --git a/app/models/extended_metadata_type.rb b/app/models/extended_metadata_type.rb new file mode 100644 index 0000000000..5b17a9abe4 --- /dev/null +++ b/app/models/extended_metadata_type.rb @@ -0,0 +1,37 @@ +class ExtendedMetadataType < ApplicationRecord + has_many :extended_metadata_attributes, inverse_of: :extended_metadata_type, dependent: :destroy + + validates :title, presence: true + validates :extended_metadata_attributes, presence: true + validates :supported_type, presence: true + validate :supported_type_must_be_valid_type + validate :unique_titles_for_extended_metadata_attributes + + alias_method :metadata_attributes, :extended_metadata_attributes + + def attribute_by_title(title) + extended_metadata_attributes.where(title: title).first + end + + def attribute_by_method_name(method_name) + extended_metadata_attributes.detect { |attr| attr.method_name == method_name } + end + + def attributes_with_linked_extended_metadata_type + extended_metadata_attributes.reject {|attr| attr.linked_extended_metadata_type.nil?} + end + + def supported_type_must_be_valid_type + return if supported_type.blank? # already convered by presence validation + unless Seek::Util.lookup_class(supported_type, raise: false) + errors.add(:supported_type, 'is not a type that can supported extended metadata') + end + end + + def unique_titles_for_extended_metadata_attributes + titles = extended_metadata_attributes.collect(&:title) + if titles != titles.uniq + errors.add(:extended_metadata_attributes, 'must have unique titles') + end + end +end diff --git a/app/models/sample.rb b/app/models/sample.rb index 94ad1db112..38c8bb6dd6 100644 --- a/app/models/sample.rb +++ b/app/models/sample.rb @@ -89,22 +89,10 @@ def referenced_samples referenced_resources.select { |r| r.is_a?(Sample) } end - def state_allows_edit?(*args) - (id.nil? || originating_data_file.nil?) && super - end - def extracted? !!originating_data_file end - def projects - extracted? ? originating_data_file.projects : super - end - - def project_ids - extracted? ? originating_data_file.project_ids : super - end - def creators extracted? ? originating_data_file.creators : super end diff --git a/app/models/sample_attribute.rb b/app/models/sample_attribute.rb index d348a1669c..4c1f6c847a 100644 --- a/app/models/sample_attribute.rb +++ b/app/models/sample_attribute.rb @@ -54,6 +54,10 @@ def short_pid URI.parse(pid).fragment || pid.gsub(/.*\//,'') || pid end + def linked_extended_metadata_type + nil + end + private def store_accessor_name diff --git a/app/models/sample_attribute_type.rb b/app/models/sample_attribute_type.rb index 79cdbdd895..d53e3191f0 100644 --- a/app/models/sample_attribute_type.rb +++ b/app/models/sample_attribute_type.rb @@ -4,7 +4,7 @@ class SampleAttributeType < ApplicationRecord validate :validate_allowed_type, :validate_regular_expression, :validate_resolution has_many :sample_attributes, inverse_of: :sample_attribute_type - has_many :custom_metadata_attributes, inverse_of: :sample_attribute_type + has_many :extended_metadata_attributes, inverse_of: :sample_attribute_type before_save :set_defaults_attributes after_initialize :set_defaults_attributes @@ -84,8 +84,12 @@ def seek_resource? base_type_handler.is_a?(Seek::Samples::AttributeTypeHandlers::SeekResourceAttributeTypeHandler) end - def linked_custom_metadata? - base_type == Seek::Samples::BaseType::LINKED_CUSTOM_METADATA + def linked_extended_metadata? + base_type == Seek::Samples::BaseType::LINKED_EXTENDED_METADATA + end + + def linked_extended_metadata_multi? + base_type == Seek::Samples::BaseType::LINKED_EXTENDED_METADATA_MULTI end def seek_sample? diff --git a/app/models/sample_controlled_vocab.rb b/app/models/sample_controlled_vocab.rb index 00e4cb0596..fb9bfdcec1 100644 --- a/app/models/sample_controlled_vocab.rb +++ b/app/models/sample_controlled_vocab.rb @@ -6,7 +6,7 @@ class SampleControlledVocab < ApplicationRecord after_remove: :update_sample_type_templates, dependent: :destroy has_many :sample_attributes, inverse_of: :sample_controlled_vocab - has_many :custom_metadata_attributes, inverse_of: :sample_controlled_vocab + has_many :extended_metadata_attributes, inverse_of: :sample_controlled_vocab has_many :sample_types, through: :sample_attributes has_many :samples, through: :sample_types diff --git a/app/models/study_batch_upload.rb b/app/models/study_batch_upload.rb index 4c8c1cf122..6a85f47c88 100644 --- a/app/models/study_batch_upload.rb +++ b/app/models/study_batch_upload.rb @@ -29,7 +29,7 @@ def self.extract_study_data_from_file(studies_file) def self.extract_studies_from_file(studies_file) studies = [] parsed_sheet = Seek::Templates::StudiesReader.new(studies_file) - metadata_type = CustomMetadataType.where(title: 'MIAPPE metadata', supported_type: 'Study').last + metadata_type = ExtendedMetadataType.where(title: 'MIAPPE metadata', supported_type: 'Study').last columns = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] study_start_row_index = 4 parsed_sheet.each_record(3, columns) do |index, data| @@ -37,8 +37,8 @@ def self.extract_studies_from_file(studies_file) studies << Study.new( title: data[1].value, description: data[2].value, - custom_metadata: CustomMetadata.new( - custom_metadata_type: metadata_type, + extended_metadata: ExtendedMetadata.new( + extended_metadata_type: metadata_type, data: generate_metadata(data) ) ) @@ -106,8 +106,8 @@ def self.unzip_batch(file_path, user_uuid) def self.get_existing_studies(studies) existing_studies = [] studies.each do |study| - study_metadata_id = study.custom_metadata.data[:id] - find_metadata = CustomMetadata.where('json_metadata LIKE ?', "%\"id\":\"#{study_metadata_id}\"%") + study_metadata_id = study.extended_metadata.data[:id] + find_metadata = ExtendedMetadata.where('json_metadata LIKE ?', "%\"id\":\"#{study_metadata_id}\"%") next if find_metadata.nil? find_metadata.each do |metadata| diff --git a/app/serializers/base_serializer.rb b/app/serializers/base_serializer.rb index 5083ce6965..cf38c19f80 100644 --- a/app/serializers/base_serializer.rb +++ b/app/serializers/base_serializer.rb @@ -121,22 +121,11 @@ def BaseSerializer.permits policy end end - attribute :extended_attributes, if: -> { object.respond_to?(:custom_metadata) && !object.custom_metadata.blank? } do - { extended_metadata_type_id: object.custom_metadata.custom_metadata_type_id.to_s, - attribute_map: get_custom_metadata } + attribute :extended_attributes, if: -> { object.respond_to?(:extended_metadata) && !object.extended_metadata.blank? } do + { extended_metadata_type_id: object.extended_metadata.extended_metadata_type_id.to_s, + attribute_map: object.extended_metadata.data.to_hash } end - def get_custom_metadata - data = object.custom_metadata.data.to_hash - CustomMetadata.find(object.custom_metadata.id).custom_metadata_attributes.each do |attr| - if attr.linked_custom_metadata? - data[attr.title] = display_custom_metadata(data,attr) - end - end - data - end - - def show_policy? return false unless object.respond_to?('can_manage?') @@ -155,17 +144,6 @@ def submitter private - def display_custom_metadata(data,attribute) - linked_data = CustomMetadata.find(data[attribute.title]).data.to_hash - CustomMetadata.find(data[attribute.title]).custom_metadata_attributes.each do |attr| - if attr.linked_custom_metadata? - linked_data[attr.title] = display_custom_metadata(linked_data,attr) - end - end - - linked_data - end - def determine_submitter(object) return object.owner if object.respond_to?('owner') result = object.contributor if object.respond_to?('contributor') && !object.is_a?(Permission) diff --git a/app/serializers/custom_metadata_type_serializer.rb b/app/serializers/extended_metadata_type_serializer.rb similarity index 67% rename from app/serializers/custom_metadata_type_serializer.rb rename to app/serializers/extended_metadata_type_serializer.rb index db672815b3..36b251edba 100644 --- a/app/serializers/custom_metadata_type_serializer.rb +++ b/app/serializers/extended_metadata_type_serializer.rb @@ -1,14 +1,14 @@ -class CustomMetadataTypeSerializer < BaseSerializer +class ExtendedMetadataTypeSerializer < BaseSerializer attributes :title, :supported_type - attribute :custom_metadata_attributes + attribute :extended_metadata_attributes - def custom_metadata_attributes - object.custom_metadata_attributes.collect do |attribute| - get_custom_metadata_attribute(attribute) + def extended_metadata_attributes + object.extended_metadata_attributes.collect do |attribute| + get_extended_metadata_attribute(attribute) end end - def get_custom_metadata_attribute(attribute) + def get_extended_metadata_attribute(attribute) { "id": attribute.id.to_s, "title": attribute.title, diff --git a/app/validators/custom_metadata_validator.rb b/app/validators/custom_metadata_validator.rb deleted file mode 100644 index c73b9cb1d1..0000000000 --- a/app/validators/custom_metadata_validator.rb +++ /dev/null @@ -1,16 +0,0 @@ -class CustomMetadataValidator < ActiveModel::Validator - - def validate(record) - record.custom_metadata_attributes.each do |attribute| - val = record.get_attribute_value(attribute) - if attribute.test_blank?(val) - record.errors.add(attribute.title, 'is required') if attribute.required? - else - unless attribute.validate_value?(val) - record.errors.add(attribute.title, "is not a valid #{attribute.sample_attribute_type.title}") - end - end - end - end - -end \ No newline at end of file diff --git a/app/validators/extended_metadata_validator.rb b/app/validators/extended_metadata_validator.rb new file mode 100644 index 0000000000..ba110e9d61 --- /dev/null +++ b/app/validators/extended_metadata_validator.rb @@ -0,0 +1,33 @@ +class ExtendedMetadataValidator < ActiveModel::Validator + def validate(record) + record.extended_metadata_attributes.each do |attribute| + val = record.get_attribute_value(attribute) + validate_attribute(record, attribute, val) + end + end + + private + + def validate_attribute(record, attribute, value, prefix = '') + if attribute.test_blank?(value) + record.errors.add("#{prefix}#{attribute.title}", 'is required') if attribute.required? + else + unless attribute.validate_value?(value) + record.errors.add("#{prefix}#{attribute.title}", "is not a valid #{attribute.sample_attribute_type.title}") + end + end + + if attribute.linked_extended_metadata? + attribute.linked_extended_metadata_type.extended_metadata_attributes.each do |attr| + validate_attribute(record, attr, value ? value[attr.accessor_name.to_s] : nil, "#{attribute.title}.") + end + elsif attribute.linked_extended_metadata_multi? + linked_attributes = attribute.linked_extended_metadata_type.extended_metadata_attributes + value.each_with_index do |val, index| + linked_attributes.each do |attr| + validate_attribute(record, attr, val ? val[attr.accessor_name.to_s] : nil, "#{attribute.title}.#{index + 1}.") + end + end + end + end +end \ No newline at end of file diff --git a/app/views/admin/features_enabled.html.erb b/app/views/admin/features_enabled.html.erb index 63039940a4..f46346c9d5 100644 --- a/app/views/admin/features_enabled.html.erb +++ b/app/views/admin/features_enabled.html.erb @@ -193,11 +193,13 @@
<%= admin_text_setting(:life_monitor_url, Seek::Config.life_monitor_url, - 'LifeMonitor URL', "The URL of a LifeMonitor instance.") %> + 'LifeMonitor API URL', "The API URL of a LifeMonitor instance.") %> <%= admin_text_setting(:life_monitor_client_id, Seek::Config.life_monitor_client_id, 'LifeMonitor OAuth client ID', 'The ID for this application to authenticate users through the LifeMonitor OAuth provider.') %> <%= admin_text_setting(:life_monitor_client_secret, Seek::Config.life_monitor_client_secret, 'LifeMonitor OAuth client secret', 'The secret token for this application to authenticate users through the LifeMonitor OAuth provider.') %> + <%= admin_text_setting(:life_monitor_ui_url, Seek::Config.life_monitor_ui_url, + 'LifeMonitor UI URL', "The UI (app) URL of a LifeMonitor instance.") %>
diff --git a/app/views/assays/_form.html.erb b/app/views/assays/_form.html.erb index 27e638566c..827f93a834 100644 --- a/app/views/assays/_form.html.erb +++ b/app/views/assays/_form.html.erb @@ -15,9 +15,9 @@ <%= f.text_area :description, :rows => 5, :class=>"form-control rich-text-edit" -%> -<%= render partial: 'custom_metadata/custom_metadata_type_selection', locals:{f:f, resource:@assay} %> +<%= render partial: 'extended_metadata/extended_metadata_type_selection', locals:{f:f, resource:@assay} %> -<%= render partial: 'custom_metadata/custom_metadata_attribute_input', locals:{f:f,resource:@assay} %> +<%= render partial: 'extended_metadata/extended_metadata_attribute_input', locals:{f:f,resource:@assay} %>
diff --git a/app/views/assays/show.html.erb b/app/views/assays/show.html.erb index b0489c36bc..986cb2576c 100644 --- a/app/views/assays/show.html.erb +++ b/app/views/assays/show.html.erb @@ -71,7 +71,7 @@
<% end %> - <%= render partial: 'custom_metadata/custom_metadata_attribute_values', locals: { resource: @assay } %> + <%= render partial: 'extended_metadata/extended_metadata_attribute_values', locals: { resource: @assay } %> <% if ((@assay.is_modelling?) && !@assay.models.empty? && !@assay.data_files.empty?) %><%#MODELLING ASSAY %>
diff --git a/app/views/assets/_batch_asset_selection.html.erb b/app/views/assets/_batch_asset_selection.html.erb new file mode 100644 index 0000000000..3f34a53470 --- /dev/null +++ b/app/views/assets/_batch_asset_selection.html.erb @@ -0,0 +1,108 @@ +<% + publishing ||= false + show_hide_blocked ||= false + show_permissions ||= false + show_managers ||= false +-%> + +
+ <%= render partial: 'assets/batch_selection/buttons', locals: { + text: "your items", + select_deselect_all: true, + collapse_expand: true, + show_hide_blocked: publishing, + show_hide_permissions: show_permissions + } %> + + + +
+
+ <% @assets.sort_by { |k, v| v.first.class.name }.each do |type, items| %> +
+

<%= batch_selection_collapse_toggle -%> <%= text_for_resource items.first %>(s)

+
+ <%= render partial: 'assets/batch_selection/buttons', locals: { + text: (text_for_resource items.first).downcase.pluralize, + show_hide_blocked: publishing, + select_deselect_all: true + } %> + <% items.each do |item| %> + <%= render partial: 'assets/batch_selection/asset_row', + object: item, + locals: { html_classes: 'publishing_options', + publishing: publishing, + show_permissions: show_permissions, + show_managers: show_managers + } -%> + <% end %> +
+
+ <% end %> +
+ +
+ <% unless @assets_not_in_isa.empty? %> +
+

<%= batch_selection_collapse_toggle -%> Items not in ISA

+
+ <%= render partial: 'assets/batch_selection/buttons', locals: { + text: "items not in ISA", + show_hide_blocked: publishing, + select_deselect_all: true + } %> + <% @assets_not_in_isa.each do |item| %> + <%= render partial: 'assets/batch_selection/asset_row', + object: item, + locals: { html_classes: "publishing_options", + publishing: publishing, + show_permissions: show_permissions, + show_managers: show_managers + } -%> + <% end %> +
+
+ <% end %> + + <% unless @investigations.empty? %> +
+

<%= batch_selection_collapse_toggle-%> Items in ISA

+
+ <%= render partial: 'assets/batch_selection/buttons', locals: { + text: "items in ISA", + select_deselect_all: true, + collapse_expand: true, + show_hide_blocked: show_hide_blocked + } %> +
+ <% @investigations.each do |inv| %> + <% collection = inv.assays.map(&:study).map(&:investigation).flatten.uniq %> + <% collection = inv.studies.map(&:investigation).flatten.uniq if collection.empty? %> + <% collection = [inv] if collection.empty?%> + <%= render partial: 'assets/batch_selection/asset_row', + locals: { publishing: publishing, + show_permissions: show_permissions, + show_managers: show_managers, + show_children: true + }, + collection: collection %> + <% end %> +
+
+
+ <% end %> +
+
+
+ diff --git a/app/views/assets/batch_selection/_asset_row.html.erb b/app/views/assets/batch_selection/_asset_row.html.erb new file mode 100644 index 0000000000..0390f0982b --- /dev/null +++ b/app/views/assets/batch_selection/_asset_row.html.erb @@ -0,0 +1,104 @@ +<% + item = asset_row + preselected ||= nil + checked ||= (item == preselected) + publishing ||= false + can_manage = item.can_manage? + can_view = item.can_view? + published = false + html_classes ||= "publishing_options" + tree_class = 'not-manageable' + tree_class = 'manageable' if can_manage + tree_class = 'not-visible' unless can_view + if publishing + if item.is_published? + published = true + tree_class = 'already-published' + end + end + item_id = "#{item.class.name}_#{item.id}" + show_managers ||= false + show_permissions ||= false + show_managers = show_managers && (can_view || item.can_see_hidden_item?(current_user.person)) + show_children ||= false + children = [] + if show_children + case item + when Investigation + children = item.studies + when Study + children = item.assays + when Assay + children = item.assets + end + end +-%> +
+
+ <%= batch_selection_collapse_toggle if children.any? %> + + <%= render partial: 'assets/batch_selection/checkbox', + locals: { checkbox_id: publishing_item_param(item), + checkbox_class: item_id, + checked: can_view && checked, + not_visible: !can_view, + published: can_view && published, + cant_manage: !can_manage, + has_children: children.any? } -%> + + + <%= text_for_resource item -%>: + <% if can_view %> + <%= link_to item.title, item, target: "_blank" -%> + <% else %> + This item is hidden to you + <% end %> + + + <% if show_permissions %> + <% if can_view %> + + <%= list_item_visibility(item)-%> + + <% else %> + <%= list_item_visibility(item)-%> + <% end %> + <% end %> + + <% if show_managers %> + + + + <% end %> +
+ + <% if show_managers %> + + <% end %> + <% if show_permissions && can_view %> + <%= render partial: 'assets/batch_selection/permission_list', locals: { item: item } -%> + <% end %> + + <% if show_children && children.any? %> +
+ <% children.each do |child| %> + <%= render partial: 'assets/batch_selection/asset_row', + object: child, + locals: { preselected: preselected, + publishing: publishing, + html_classes: "publishing_options", + show_permissions: show_permissions, + show_managers: show_managers, + show_children: true + } -%> + <% end %> +
+ <% end %> +
diff --git a/app/views/assets/batch_selection/_buttons.html.erb b/app/views/assets/batch_selection/_buttons.html.erb new file mode 100644 index 0000000000..52b856cc25 --- /dev/null +++ b/app/views/assets/batch_selection/_buttons.html.erb @@ -0,0 +1,50 @@ +<% + text ||= "your items" + select_deselect_all ||= false + collapse_expand ||= false + show_hide_blocked ||= false + show_hide_permissions ||= false +%> + +
+ <% if select_deselect_all %> + + <% end %> + <% if collapse_expand %> + + <% end %> + <% if show_hide_blocked %> + + <% end %> + <% if show_hide_permissions %> + + <% end %> +
diff --git a/app/views/assets/batch_selection/_checkbox.html.erb b/app/views/assets/batch_selection/_checkbox.html.erb new file mode 100644 index 0000000000..b3da44f54a --- /dev/null +++ b/app/views/assets/batch_selection/_checkbox.html.erb @@ -0,0 +1,44 @@ +<% + checkbox_class ||= "no_name" # <- Needed! + published ||= false + cant_manage ||= false + not_visible ||= false + checked ||= false + has_children ||= false +-%> +
+ <% if published %> + + <% elsif not_visible %> + + <% elsif cant_manage %> + + <% else %> + + <% end %> + + <% if has_children %> + + + <% end %> +
diff --git a/app/views/assets/batch_selection/_permission_list.html.erb b/app/views/assets/batch_selection/_permission_list.html.erb new file mode 100644 index 0000000000..643f7aed42 --- /dev/null +++ b/app/views/assets/batch_selection/_permission_list.html.erb @@ -0,0 +1,18 @@ +<% + item ||= permission_list + downloadable = item.try(:is_downloadable?) +-%> + diff --git a/app/views/assets/publishing/_isa_publishing_preview.html.erb b/app/views/assets/publishing/_isa_publishing_preview.html.erb deleted file mode 100644 index 53779ae75a..0000000000 --- a/app/views/assets/publishing/_isa_publishing_preview.html.erb +++ /dev/null @@ -1,33 +0,0 @@ -<% - item = isa_publishing_preview - - case item - when Investigation - children = item.studies - when Study - children = item.assays - when Assay - children = item.assets - else - children = [] - end --%> -
"> - <%= render :partial => "assets/publishing/options_for_publishing", - :object => item, - :locals => { :html_classes => "publishing_options", - :toggle => children.any?, - :cb_parent_selector => "div\##{item.class.name}_#{item.id}.split_button_parent", - :checked => (item == preselected) } -%> - - <% if children.any? %> -
- <% children.each do |child| %> - <%= render :partial => "assets/publishing/isa_publishing_preview", - :object => child, - :locals => { :preselected => preselected, - :html_classes => "publishing_options"} -%> - <% end %> -
- <% end %> -
\ No newline at end of file diff --git a/app/views/assets/publishing/_options_for_publishing.html.erb b/app/views/assets/publishing/_options_for_publishing.html.erb deleted file mode 100644 index 403fe471ea..0000000000 --- a/app/views/assets/publishing/_options_for_publishing.html.erb +++ /dev/null @@ -1,51 +0,0 @@ -<% - item = options_for_publishing - checked ||= false - - tree_class = 'not-publishable' - tree_class = 'publishable' if item.can_publish? - tree_class = 'already-published' if item.is_published? - - toggle ||=false - cb_parent_selector ||="div\##{item.class.name}_#{item.id}.split_button_parent" - --%> -
- <% if item.can_view? %> -
- <%= text_for_resource item -%>: <%= link_to item.title, item, :target => "_blank" -%> - <%=list_item_visibility(item)-%> -
-
Manageable by <%= item.managers.empty? ? "None".html_safe : item.managers.collect { |m| link_to(h(m.title), m) }.join(", ").html_safe -%>
- <% else %> -
<%= text_for_resource item -%>: This item is hidden to you
- <% if current_user.try(:person) && item.can_see_hidden_item?(current_user.person) %> -
Manageable by <%= item.managers.empty? ? "
None
".html_safe : item.managers.collect { |m| link_to(h(m.title), m) }.join(", ").html_safe -%>
- <% end %> - <% end %> - -
- <% if item.is_published? %> - <%= render :partial => 'general/split_button_checkbox', - locals: { checkbox_id: publishing_item_param(item), - checkbox_class: "#{item.class.name}_#{item.id}", - published: true, - toggle: toggle, - cb_parent_selector: cb_parent_selector} -%> - <% elsif item.can_publish? %> - <%= render :partial => 'general/split_button_checkbox', - locals: { checkbox_id: publishing_item_param(item), - checkbox_class: "#{item.class.name}_#{item.id}", - checkbox_text: "Publish?", - checked: checked, - toggle: toggle, - cb_parent_selector: cb_parent_selector} -%> - <% else %> - - - Can't publish - - <% end %> -
-
- diff --git a/app/views/assets/publishing/batch_publishing_preview.html.erb b/app/views/assets/publishing/batch_publishing_preview.html.erb index 625fca6bae..c51dc30cf1 100644 --- a/app/views/assets/publishing/batch_publishing_preview.html.erb +++ b/app/views/assets/publishing/batch_publishing_preview.html.erb @@ -11,45 +11,27 @@

- You can select an item to be published by checking the Publish - checkbox beside that item. + You can select an item to be published by checking the checkbox beside that item.

-
- | - - -<%= form_tag({:action => :check_related_items},:method=>:post) do -%> +<%= form_tag({action: :check_related_items},method: :post) do -%> <% if @assets.empty? %> All your assets are published or you have no assets in <%= Seek::Config.instance_name %>

<%= link_to "Back to profile", person_path(params[:id].to_i) -%> <% else %> - <% @assets.sort_by { |k, v| v.first.class.name }.each do |type, items| %> -
-

<%= text_for_resource items.first %>(s)

- | - - <% items.each do |item| %> - <%= render :partial => "assets/publishing/options_for_publishing", - :object => item, - :locals => { :html_classes => "publishing_options" } -%> -
- <% end %> -
- <% end %> + <%= render partial: "assets/batch_asset_selection", + locals: { publishing: true, + show_hide_blocked: true, + show_permissions: true, + show_managers: false + } + -%>
- - <%= submit_tag "Next",data: { disable_with: 'Next' }, :class => 'btn btn-primary' -%> + <%= submit_tag "Next",data: { disable_with: 'Next' }, class: 'btn btn-primary' -%> Or <%= cancel_button person_path(params[:id].to_i)-%> <% end -%> diff --git a/app/views/assets/publishing/publish_final_confirmation.html.erb b/app/views/assets/publishing/publish_final_confirmation.html.erb index fc24050d67..d8d815fc50 100644 --- a/app/views/assets/publishing/publish_final_confirmation.html.erb +++ b/app/views/assets/publishing/publish_final_confirmation.html.erb @@ -1,21 +1,74 @@ <%= show_title "Confirm publishing" -%> -

- You are about the publish the following items. -

-<%= form_tag :action => :publish do %> -