diff --git a/app/assets/javascripts/controlled_vocabs.js.erb b/app/assets/javascripts/controlled_vocabs.js.erb
index 4d1d0ddd1b..db2a303dd3 100644
--- a/app/assets/javascripts/controlled_vocabs.js.erb
+++ b/app/assets/javascripts/controlled_vocabs.js.erb
@@ -30,14 +30,20 @@ var SampleTypeControlledVocab = {
initialise_rich_text_editors(".rich-text-edit");
},
+ copyBlankModalForm: function() {
+ // first destroy any select2 elements, which won't survive being cloned. They will be initialised again when reset
+ $j('#cv-modal select[data-role="seek-objectsinput"]').select2('destroy');
+ SampleTypeControlledVocab.blankControlledVocabModelForm=$j('#cv-modal').clone();
+ },
+
//resets the modal
resetModalControlledVocabForm: function () {
$j('#cv-modal').remove();
$j('#modal-dialogues').append(SampleTypeControlledVocab.blankControlledVocabModelForm.clone());
CVTerms.init();
+ ObjectsInput.init();
SampleTypeControlledVocab.bindNewControlledVocabShowEvent();
SampleTypeControlledVocab.initialise_deferred_rich_editor_modal();
-
},
//selected CV item changed
diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb
index 0e42d115dd..261b2d2c91 100644
--- a/app/controllers/admin_controller.rb
+++ b/app/controllers/admin_controller.rb
@@ -13,7 +13,7 @@ def index
respond_to do |format|
format.html
end
- end
+ end
def update_admins
# Don't let admin remove themselves or they won't be able to manage roles
@@ -134,6 +134,11 @@ def update_features_enabled
Seek::Config.piwik_analytics_url = params[:piwik_analytics_url]
Seek::Config.piwik_analytics_tracking_notice = params[:piwik_analytics_tracking_notice]
+ Seek::Config.custom_analytics_snippet_enabled = string_to_boolean params[:custom_analytics_snippet_enabled]
+ Seek::Config.custom_analytics_snippet = params[:custom_analytics_snippet]
+ Seek::Config.custom_analytics_tracking_notice = params[:custom_analytics_tracking_notice]
+ Seek::Config.custom_analytics_name = params[:custom_analytics_name]
+
Seek::Config.doi_minting_enabled = string_to_boolean params[:doi_minting_enabled]
Seek::Config.datacite_username = params[:datacite_username]
Seek::Config.datacite_password = params[:datacite_password]
diff --git a/app/controllers/assays_controller.rb b/app/controllers/assays_controller.rb
index 0c88fabba4..007e3f7da9 100644
--- a/app/controllers/assays_controller.rb
+++ b/app/controllers/assays_controller.rb
@@ -70,7 +70,7 @@ def new
@permitted_params = assay_params if params[:assay]
# jump straight to experimental if modelling analysis is disabled
- @assay_class ||= 'experimental' unless Seek::Config.modelling_analysis_enabled
+ @assay_class ||= 'EXP' unless Seek::Config.modelling_analysis_enabled
@assay.assay_class = AssayClass.for_type(@assay_class) unless @assay_class.nil?
@@ -82,7 +82,7 @@ def edit
end
def create
- params[:assay_class_id] ||= AssayClass.for_type('experimental').id
+ params[:assay_class_id] ||= AssayClass.experimental.id
@assay = Assay.new(assay_params)
update_assay_organisms @assay, params
@@ -117,7 +117,7 @@ def fix_assay_linkage
return unless is_single_page_assay?
return unless @assay.has_linked_child_assay?
- previous_assay_linked_st_id = @assay.previous_linked_assay_sample_type&.id
+ previous_assay_linked_st_id = @assay.previous_linked_sample_type&.id
next_assay = Assay.all.detect do |a|
a.sample_type&.sample_attributes&.first&.linked_sample_type_id == @assay.sample_type_id
diff --git a/app/controllers/isa_assays_controller.rb b/app/controllers/isa_assays_controller.rb
index df0643ba43..cc0c8128ed 100644
--- a/app/controllers/isa_assays_controller.rb
+++ b/app/controllers/isa_assays_controller.rb
@@ -6,14 +6,18 @@ class IsaAssaysController < ApplicationController
before_action :find_requested_item, only: %i[edit update]
def new
- @isa_assay = IsaAssay.new
+ if params[:is_assay_stream]
+ @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.assay_stream.id } })
+ else
+ @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.experimental.id } })
+ end
end
def create
@isa_assay = IsaAssay.new(isa_assay_params)
update_sharing_policies @isa_assay.assay
@isa_assay.assay.contributor = current_person
- @isa_assay.sample_type.contributor = User.current_user.person
+ @isa_assay.sample_type.contributor = User.current_user.person if isa_assay_params[:sample_type]
if @isa_assay.save
redirect_to single_page_path(id: @isa_assay.assay.projects.first, item_type: 'assay',
item_id: @isa_assay.assay, notice: 'The ISA assay was created successfully!')
@@ -27,7 +31,11 @@ def create
def edit
# let edit the assay if the sample_type is not authorized
- @isa_assay.sample_type = nil unless requested_item_authorized?(@isa_assay.sample_type)
+ if @isa_assay.assay.is_assay_stream?
+ @isa_assay.sample_type = nil
+ else
+ @isa_assay.sample_type = nil unless requested_item_authorized?(@isa_assay.sample_type)
+ end
respond_to do |format|
format.html
@@ -38,9 +46,11 @@ def update
@isa_assay.assay.attributes = isa_assay_params[:assay]
# update the sample_type
- if requested_item_authorized?(@isa_assay.sample_type)
- @isa_assay.sample_type.update(isa_assay_params[:sample_type])
- @isa_assay.sample_type.resolve_inconsistencies
+ unless @isa_assay.assay.is_assay_stream?
+ if requested_item_authorized?(@isa_assay.sample_type)
+ @isa_assay.sample_type.update(isa_assay_params[:sample_type])
+ @isa_assay.sample_type.resolve_inconsistencies
+ end
end
if @isa_assay.save
@@ -87,10 +97,12 @@ def assay_params
{ data_files_attributes: %i[asset_id direction relationship_type_id] },
{ publication_ids: [] },
{ extended_metadata_attributes: determine_extended_metadata_keys(:assay) },
- { discussion_links_attributes: %i[id url label _destroy] }]
+ { discussion_links_attributes: %i[id url label _destroy] }, :assay_stream_id]
end
def sample_type_params(params)
+ return [] unless params[:sample_type]
+
attributes = params[:sample_type][:sample_attributes]
if attributes
params[:sample_type][:sample_attributes_attributes] = []
@@ -128,9 +140,16 @@ def set_up_instance_variable
end
def find_requested_item
- @isa_assay = IsaAssay.new
+ if params[:is_assay_stream]
+ @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.assay_stream.id } })
+ else
+ @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.experimental.id } })
+ end
@isa_assay.populate(params[:id])
+ # Should not deal with sample type if assay has assay_class assay stream
+ return if @isa_assay.assay.is_assay_stream?
+
if @isa_assay.sample_type.nil? || !requested_item_authorized?(@isa_assay.assay)
flash[:error] = "You are not authorized to edit this #{t('isa_assay')}"
flash[:error] = 'Resource not found.' if @isa_assay.sample_type.nil?
diff --git a/app/controllers/workflow_classes_controller.rb b/app/controllers/workflow_classes_controller.rb
index 9097fd341e..2539a5617f 100644
--- a/app/controllers/workflow_classes_controller.rb
+++ b/app/controllers/workflow_classes_controller.rb
@@ -50,6 +50,10 @@ def edit
def index
@workflow_classes = WorkflowClass.order(extractor: :desc).all
+ respond_to do |format|
+ format.html
+ format.jsonld { render json: @workflow_classes.map(&:ro_crate_metadata), adapter: :attributes }
+ end
end
private
diff --git a/app/forms/isa_assay.rb b/app/forms/isa_assay.rb
index d5817e0051..a586c75851 100644
--- a/app/forms/isa_assay.rb
+++ b/app/forms/isa_assay.rb
@@ -3,29 +3,31 @@ class IsaAssay
attr_accessor :assay, :sample_type, :input_sample_type_id
- validates_presence_of :assay, :sample_type, :input_sample_type_id
+ validates_presence_of :assay
validate :validate_objects
def initialize(params = {})
@assay = Assay.new(params[:assay] || {})
- @sample_type = SampleType.new((params[:sample_type] || {}).merge({ project_ids: @assay.project_ids }))
- @sample_type.sample_attributes.build(is_title: true, required: true) unless params[:sample_type]
- @assay.sample_type = @sample_type
- @assay.assay_class = AssayClass.for_type('experimental')
+ unless @assay.is_assay_stream?
+ @sample_type = SampleType.new((params[:sample_type] || {}).merge({ project_ids: @assay.project_ids }))
+ @sample_type.sample_attributes.build(is_title: true, required: true) unless params[:sample_type]
+ @assay.sample_type = @sample_type
+ end
+
@input_sample_type_id = params[:input_sample_type_id]
end
def save
if valid?
- if @assay.new_record?
+ if @assay.new_record? && !@assay.is_assay_stream?
# connect the sample type multi link attribute to the last sample type of the assay's study
input_attribute = @sample_type.sample_attributes.detect(&:seek_sample_multi?)
input_attribute.linked_sample_type_id = @input_sample_type_id
title = SampleType.find(@input_sample_type_id).sample_attributes.detect(&:is_title).title
input_attribute.title = "Input (#{title})"
end
+ @sample_type.save unless @assay.is_assay_stream?
@assay.save
- @sample_type.save
else
false
end
@@ -54,6 +56,12 @@ def populate(id)
def validate_objects
@assay.errors.each { |e| errors.add(:base, "[Assay]: #{e.full_message}") } unless @assay.valid?
+ return if @assay.is_assay_stream?
+
+ errors.add(:base, '[Assay]: The assay is missing a sample type.') if @sample_type.nil?
+
+ return unless @sample_type
+
@sample_type.errors.full_messages.each { |e| errors.add(:base, "[Sample type]: #{e}") } unless @sample_type.valid?
unless @sample_type.sample_attributes.any?(&:seek_sample_multi?)
@@ -66,22 +74,22 @@ def validate_objects
unless @sample_type.sample_attributes.select { |a| a.title.include?('Input') && a.isa_tag.nil? }.one?
errors.add(:base,
- "[Sample type]: Should have exactly one attribute with the title 'Input' and no ISA tag".html_safe)
+ "[Sample type]: Should have exactly one attribute with the title 'Input' and no ISA tag".html_safe)
end
if @sample_type.sample_attributes.select { |a| !a.title.include?('Input') && a.isa_tag.nil? }.any?
errors.add(:base,
- "[Sample type]: All attributes should have an ISA Tag except for the 'Input' attribute (hidden)".html_safe)
+ "[Sample type]: All attributes should have an ISA Tag except for the 'Input' attribute (hidden)".html_safe)
end
assay_sample_or_datafile_attributes = @sample_type.sample_attributes.select do |a|
a.isa_tag&.isa_other_material? || a.isa_tag&.isa_data_file?
end
+
unless assay_sample_or_datafile_attributes.one?
errors.add(:base,
- "[Sample type]: Should have exactly one attribute with the 'data_file' or 'other_material' ISA tag selected".html_safe)
+ "[Sample type]: Should have exactly one attribute with the 'data_file' or 'other_material' ISA tag selected".html_safe)
end
-
errors.add(:base, '[Input Assay]: Input Assay is not provided') if @input_sample_type_id.blank?
end
end
diff --git a/app/helpers/images_helper.rb b/app/helpers/images_helper.rb
index 94a389f07a..3145e98e86 100644
--- a/app/helpers/images_helper.rb
+++ b/app/helpers/images_helper.rb
@@ -70,8 +70,8 @@ def append_size_parameter(url, size)
url
end
- def delete_icon(model_item, user, confirm_msg='Are you sure?')
- item_name = text_for_resource model_item
+ def delete_icon(model_item, user, confirm_msg='Are you sure?', alternative_item_name=nil)
+ item_name = alternative_item_name.nil? ? (text_for_resource model_item) : alternative_item_name
if model_item.can_delete?(user)
fullURL = url_for(model_item)
diff --git a/app/models/assay.rb b/app/models/assay.rb
index e2fe2430fa..ab9e5b95e6 100644
--- a/app/models/assay.rb
+++ b/app/models/assay.rb
@@ -25,6 +25,9 @@ class Assay < ApplicationRecord
belongs_to :sample_type
+ has_many :child_assays, class_name: 'Assay', foreign_key: 'assay_stream_id', dependent: :destroy
+ belongs_to :assay_stream, class_name: 'Assay', optional: true
+
belongs_to :assay_class
has_many :assay_organisms, dependent: :destroy, inverse_of: :assay
has_many :organisms, through: :assay_organisms, inverse_of: :assays
@@ -65,19 +68,38 @@ class Assay < ApplicationRecord
enforce_authorization_on_association :study, :view
- def previous_linked_assay_sample_type
- sample_type.sample_attributes.detect { |sa| sa.isa_tag.nil? && sa.title.include?('Input') }&.linked_sample_type
+ def is_assay_stream?
+ assay_class&.is_assay_stream?
+ end
+
+ def previous_linked_sample_type
+ return unless is_isa_json_compliant?
+
+ if is_assay_stream?
+ study.sample_types.second
+ else
+ sample_type.sample_attributes.detect { |sa| sa.isa_tag.nil? && sa.title.include?('Input') }&.linked_sample_type
+ end
end
def has_linked_child_assay?
- sample_type&.linked_sample_attributes&.any?
+ return false unless is_isa_json_compliant?
+
+ if is_assay_stream?
+ child_assays.any?
+ else
+ sample_type&.linked_sample_attributes&.any?
+ end
end
- # Fetches the assay which is linked through linked_sample_attributes (Single Page specific method)
- def linked_assay
- sample_type.linked_sample_attributes
- .select { |lsa| lsa.isa_tag.nil? && lsa.title.include?('Input') }
- .first&.sample_type&.assays&.first
+ def next_linked_child_assay
+ return unless has_linked_child_assay?
+
+ if is_assay_stream?
+ previous_linked_sample_type&.linked_sample_attributes&.detect { |sa| sa.isa_tag.nil? && sa.title.include?('Input') }&.sample_type&.assays&.first
+ else
+ sample_type.linked_sample_attributes.detect { |sa| sa.isa_tag.nil? && sa.title.include?('Input') }&.sample_type&.assays&.first
+ end
end
def default_contributor
@@ -95,7 +117,7 @@ def state_allows_delete?(*args)
end
def is_isa_json_compliant?
- investigation.is_isa_json_compliant? && !sample_type.nil?
+ investigation.is_isa_json_compliant? && (!sample_type.nil? || is_assay_stream?)
end
# returns true if this is a modelling class of assay
diff --git a/app/models/assay_class.rb b/app/models/assay_class.rb
index a7a72dc0fa..5e8940f9ee 100644
--- a/app/models/assay_class.rb
+++ b/app/models/assay_class.rb
@@ -1,31 +1,40 @@
class AssayClass < ApplicationRecord
-
- #this returns an instance of AssayClass according to one of the types "experimental" or "modelling"
- #if there is not a match nil is returned
- def self.for_type type
- keys={"experimental"=>"EXP","modelling"=>"MODEL"}
- return AssayClass.find_by(key: keys[type])
+ # this returns an instance of AssayClass according to one of the constants defined in seek/isa/assay_class.rb
+ # if there is not a match nil is returned
+ def self.for_type(type)
+ AssayClass.find_by(key: type)
end
def self.experimental
- self.for_type('experimental')
+ for_type('EXP')
end
def self.modelling
- self.for_type('modelling')
+ for_type('MODEL')
+
+ end
+
+ def self.assay_stream
+ for_type('STREAM')
end
def is_modelling?
- key == "MODEL"
+ key == 'MODEL'
end
def is_experimental?
key == 'EXP'
end
+ def is_assay_stream?
+ key == 'STREAM'
+ end
+
+ LONG_KEYS = { 'EXP': 'Experimental Assay', 'MODEL': 'Modelling Analysis', 'STREAM': 'Assay Stream' }.freeze
+
# for cases where a longer more descriptive key is useful, but can't rely on the title
# which may have been changed over time
def long_key
- {'EXP'=>'Experimental Assay','MODEL'=>'Modelling Analysis'}[key]
+ LONG_KEYS[key.to_sym]
end
end
diff --git a/app/models/study.rb b/app/models/study.rb
index f7b6371c73..467a550083 100644
--- a/app/models/study.rb
+++ b/app/models/study.rb
@@ -36,6 +36,10 @@ class Study < ApplicationRecord
has_many "related_#{type.pluralize}".to_sym, -> { distinct }, through: :assays, source: type.pluralize.to_sym
end
+ def assay_streams
+ assays.select(&:is_assay_stream?)
+ end
+
def assets
related_data_files + related_sops + related_models + related_publications + related_documents
end
diff --git a/app/views/admin/features_enabled.html.erb b/app/views/admin/features_enabled.html.erb
index 5f5057cbe7..db7fb3abee 100644
--- a/app/views/admin/features_enabled.html.erb
+++ b/app/views/admin/features_enabled.html.erb
@@ -126,7 +126,7 @@
<%= admin_checkbox_setting(:require_cookie_consent, 1, Seek::Config.require_cookie_consent,
- "Add a cookie consent banner and a cookie setting page", "Show a banner asking for consent for the use of cookies. SEEK uses an essential cookie for session handling, and optional cookies for tracking if set below.
+ "Add a cookie consent banner and a cookie setting page", "Show a banner asking for consent for the use of cookies. SEEK uses an essential cookie for session handling, and optional cookies for tracking if set below.
Some cookies might be also set by embedded content. This banner will offer 3 choices: Only necessary, Embedded content, All.
Setting up this option will also add a cookie setting page accessible from the footer.") %>
@@ -152,6 +152,19 @@
'Piwik tracking notice', "Warn users about the use of analytics software.") %>
+ <%= admin_checkbox_setting(:custom_analytics_snippet_enabled, 1, Seek::Config.custom_analytics_snippet_enabled,
+ "Include a custom analytics snippet", "Adds custom snippet to the page keep track of site traffic.",
+ onchange: toggle_appear_javascript('custom_analytics_block')) %>
+
+
+ <%= admin_text_setting(:custom_analytics_name, Seek::Config.custom_analytics_name,
+ 'Custom analytics name', "The name of the analytics software used for tracking traffic.") %>
+ <%= admin_textarea_setting(:custom_analytics_snippet, Seek::Config.custom_analytics_snippet,
+ "Code Snippet", "A custom code snippet, taking care of site traffic.") %>
+ <%= admin_checkbox_setting(:custom_analytics_tracking_notice, 1, Seek::Config.custom_analytics_tracking_notice,
+ 'Custom tracking notice', "Warn users about the use of analytics software.") %>
+
+
<%= render :partial => "admin/doi_settings" %>
<%= render :partial => "admin/zenodo_settings" %>
diff --git a/app/views/assays/_buttons.html.erb b/app/views/assays/_buttons.html.erb
index ac5b58e309..72c59241fe 100644
--- a/app/views/assays/_buttons.html.erb
+++ b/app/views/assays/_buttons.html.erb
@@ -1,4 +1,12 @@
-<% assay_word = item.is_modelling? ? t('assays.modelling_analysis') : t('assays.assay') %>
+<% assay_word ||=
+ if item.is_assay_stream?
+ t('assays.assay_stream')
+ elsif Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant?
+ t('isa_assay')
+ else
+ t("assays.#{item.assay_class.long_key.delete(' ').underscore}")
+ end
+%>
<%= render :partial => "subscriptions/subscribe", :locals => {:object => item} %>
<% if Seek::Config.project_single_page_enabled %>
@@ -21,10 +29,14 @@
<% if item.can_edit? %>
<% if Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? %>
- <% valid_study = item&.study&.sample_types.present? %>
- <% valid_assay = item&.sample_type.present? %>
+ <% valid_study = item&.study&.is_isa_json_compliant? %>
+ <% valid_assay = item&.is_isa_json_compliant? %>
<% if valid_study && valid_assay %>
- <%= button_link_to("Design the next #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page])) %>
+ <% if item&.is_assay_stream? %>
+ <%= button_link_to("Design #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page], assay_stream_id: item.id)) %>
+ <% else %>
+ <%= button_link_to("Design the next #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page], assay_stream_id: item.assay_stream_id)) %>
+ <% end %>
<% end %>
<% else %>
<%= add_new_item_to_dropdown(item) %>
@@ -38,7 +50,11 @@
<%= item_actions_dropdown do %>
<% if item.can_edit? %>
<% if Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? %>
-
<%= form_for @isa_assay, url: { controller: "isa_assays", action: "update", id: params[:id] }, method: :put do |f| %>
diff --git a/app/views/isa_assays/new.html.erb b/app/views/isa_assays/new.html.erb
index d2a46bab7b..07da9cf1e0 100644
--- a/app/views/isa_assays/new.html.erb
+++ b/app/views/isa_assays/new.html.erb
@@ -1,5 +1,12 @@
-
New <%=t("isa_assay")%>
+
+ <%= sample_controlled_vocab_model_dialog('cv-modal') %>
+
+ <% if params[:is_assay_stream] %>
+ New <%=t("assays.assay_stream")%>
+ <% else %>
+ New <%=t("isa_assay")%>
+ <% end %>
<%= render partial: "templates/template_modal" -%>
diff --git a/app/views/isa_studies/_sample_types_form.html.erb b/app/views/isa_studies/_sample_types_form.html.erb
index b1d7bf1ebd..b2c7605af6 100644
--- a/app/views/isa_studies/_sample_types_form.html.erb
+++ b/app/views/isa_studies/_sample_types_form.html.erb
@@ -5,9 +5,6 @@
<% main_field_name = id_suffix[1..-1] %>
<% isa_element ||= "study" %>
-
- <%= sample_controlled_vocab_model_dialog('cv-modal') %>
-
<%= f.fields_for main_field_name, sample_type do |field| %>
<% unless action == :edit %>
@@ -80,7 +77,7 @@
'
+ Seek::Config.default :custom_analytics_tracking_notice, true
Seek::Config.default :bioportal_api_key,''
Seek::Config.default :project_news_enabled,false
Seek::Config.default :project_news_feed_urls,''
@@ -134,7 +138,7 @@ def load_seek_config_defaults!
Seek::Config.default :contact_link, ''
Seek::Config.default :funding_link, ''
-
+
#Terms and conditions page
Settings.defaults[:terms_enabled]= false
Seek::Config.default :terms_page, File.read(Rails.root.join('config/default_data/terms_and_conditions_example'))
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 6630e1a976..79dbb61c11 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -16,6 +16,7 @@ en:
assay: "Assay"
experimental_assay: "Experimental assay"
modelling_analysis: "Modelling analysis"
+ assay_stream: "Assay Stream"
isa_study: "ISA Study"
study_design: "Study Design"
diff --git a/config/routes.rb b/config/routes.rb
index e632e6f4f0..49bdcba035 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -744,7 +744,7 @@
post :template_attributes
end
collection do
-post :filter_isa_tags_by_level
+ post :filter_isa_tags_by_level
get :task_status
get :default_templates
post :populate_template
diff --git a/db/migrate/20240112141513_add_assay_stream_id_to_assay.rb b/db/migrate/20240112141513_add_assay_stream_id_to_assay.rb
new file mode 100644
index 0000000000..6220df0185
--- /dev/null
+++ b/db/migrate/20240112141513_add_assay_stream_id_to_assay.rb
@@ -0,0 +1,6 @@
+class AddAssayStreamIdToAssay < ActiveRecord::Migration[6.1]
+ def change
+ add_column :assays, :assay_stream_id, :integer
+ add_index :assays, :assay_stream_id
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 22e12632eb..2f51e08778 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2023_12_18_133053) do
+ActiveRecord::Schema.define(version: 2024_01_12_141513) do
create_table "activity_logs", id: :integer, force: :cascade do |t|
t.string "action"
@@ -189,6 +189,8 @@
t.string "deleted_contributor"
t.integer "sample_type_id"
t.integer "position"
+ t.integer "assay_stream_id"
+ t.index ["assay_stream_id"], name: "index_assays_on_assay_stream_id"
t.index ["sample_type_id"], name: "index_assays_on_sample_type_id"
end
diff --git a/lib/isa_exporter.rb b/lib/isa_exporter.rb
index 108144752a..5d5a3ff288 100644
--- a/lib/isa_exporter.rb
+++ b/lib/isa_exporter.rb
@@ -117,7 +117,7 @@ def convert_study(study)
# raise "The Study with the title '#{study.title}' does not have any SOP" if study.sops.blank?
protocols << convert_protocol(study.sops, study.id, with_tag_protocol_study, with_tag_parameter_value_study)
- study.assays.each do |a|
+ study.assay_streams.map(&:child_assays).flatten.each do |a|
# There should be only one attribute with isa_tag == protocol
protocol_attribute = a.sample_type.sample_attributes.detect { |sa| sa.isa_tag&.isa_protocol? }
with_tag_parameter_value = a.sample_type.sample_attributes.select { |sa| sa.isa_tag&.isa_parameter_value? }
@@ -134,20 +134,7 @@ def convert_study(study)
raise "All assays in study `#{study.title}` should be ISA-JSON compliant."
end
- assay_streams = study.assays.map { |assay| [assay] if assay&.position&.zero? }
- .compact
- .map do |assay_stream|
- last_assay = assay_stream.first
- until last_assay.linked_assay.nil?
- linked_assay = last_assay.linked_assay
- assay_stream.push(linked_assay)
- last_assay = linked_assay
- end
- assay_stream
- end
-
- isa_study[:assays] = assay_streams.map { |assay_stream| convert_assays(assay_stream) }.compact
-
+ isa_study[:assays] = study.assay_streams.map { |assay_stream| convert_assays(assay_stream) }.compact
isa_study[:factors] = []
isa_study[:unitCategories] = []
@@ -163,28 +150,25 @@ def convert_annotation(term_uri)
isa_annotation
end
- def convert_assay_comments(assays)
+ def convert_assay_comments(assay_stream)
assay_comments = []
- assay_streams = assays.select { |a| a.position.zero? }
- assay_stream_id = assays.pluck(:id).join('_')
-
- linked_assays = assays.map { |assay| { 'id': assay.id, 'title': assay.title } }.to_json
-
- assay_streams.map do |assay|
- study_id = assay.study_id
- next if assay.extended_metadata.nil?
-
- json = JSON.parse(assay.extended_metadata&.json_metadata)
- cm_attributes = assay.extended_metadata.extended_metadata_attributes
- cm_id = assay.extended_metadata&.id
- json.map do |key, val|
- cma_id = cm_attributes.detect { |cma| cma.title == key }&.id
- assay_comments.push({
- '@id': "#assay_comment/#{[study_id, assay_stream_id, cm_id, cma_id].join('_')}",
- 'name': key,
- 'value': val
- })
- end
+ assay_stream_id = assay_stream&.id
+
+ linked_assays = assay_stream.child_assays.map { |assay| { 'id': assay.id, 'title': assay.title } }.to_json
+
+ study_id = assay_stream.study_id
+ return [] if assay_stream.extended_metadata.nil?
+
+ json = JSON.parse(assay_stream.extended_metadata&.json_metadata)
+ cm_attributes = assay_stream.extended_metadata.extended_metadata_attributes
+ cm_id = assay_stream.extended_metadata&.id
+ json.map do |key, val|
+ cma_id = cm_attributes.detect { |cma| cma.title == key }&.id
+ assay_comments.push({
+ '@id': "#assay_comment/#{[study_id, assay_stream_id, cm_id, cma_id].join('_')}",
+ 'name': key,
+ 'value': val
+ })
end
assay_comments.push({
@@ -195,35 +179,43 @@ def convert_assay_comments(assays)
assay_comments.compact
end
- def convert_assays(assays)
- return unless assays.all? { |a| a.can_view?(@current_user) }
+ def convert_assays(assay_stream)
+ child_assays = assay_stream.child_assays
+ return unless assay_stream.can_view?(@current_user)
+ return unless child_assays.all? { |a| a.can_view?(@current_user) }
- all_sample_types = assays.map(&:sample_type)
- first_assay = assays.detect { |s| s.position.zero? }
- raise 'No assay could be found!' unless first_assay
+ child_assays.map do |ca|
+ unless ca.sample_type.present?
+ raise "No Sample type was found in Assay '#{ca.id} - #{ca.title}'," \
+ " part of Assay Stream '#{assay_stream.id - assay_stream.title}'"
+ end
+ end
- stream_name = "assays_#{assays.pluck(:id).join('_')}"
- assay_comments = convert_assay_comments(assays)
+ all_sample_types = child_assays.map(&:sample_type).compact
+
+ # stream_name = "assays_#{child_assays.pluck(:id).join('_')}"
+ stream_name = "#{ assay_stream.title }_#{assay_stream.id}_#{child_assays.pluck(:id).join('_')}"
+ assay_comments = convert_assay_comments(assay_stream)
# Retrieve assay_stream if
- stream_name_comment = assay_comments.detect { |ac| ac[:name] == 'assay_stream' }
- stream_name = stream_name_comment[:value] unless stream_name_comment.nil?
+ # stream_name_comment = assay_comments.detect { |ac| ac[:name] == 'assay_stream' }
+ # stream_name = stream_name_comment[:value] unless stream_name_comment.nil?
isa_assay = {}
- isa_assay['@id'] = "#assay/#{assays.pluck(:id).join('_')}"
+ isa_assay['@id'] = "#assay/#{child_assays.pluck(:id).join('_')}"
isa_assay[:filename] = "a_#{stream_name.downcase.tr(" ", "_")}.txt"
isa_assay[:measurementType] = { annotationValue: '', termSource: '', termAccession: '' }
isa_assay[:technologyType] = { annotationValue: '', termSource: '', termAccession: '' }
isa_assay[:comments] = assay_comments
isa_assay[:technologyPlatform] = ''
- isa_assay[:characteristicCategories] = convert_characteristic_categories(nil, assays)
+ isa_assay[:characteristicCategories] = convert_characteristic_categories(nil, child_assays)
isa_assay[:materials] = {
# Here, the first assay's samples will be enough
- samples: assay_samples(first_assay), # the samples from study level that are referenced in this assay's samples,
+ samples: assay_samples(child_assays.first), # the samples from study level that are referenced in this assay's samples,
otherMaterials: convert_other_materials(all_sample_types)
}
isa_assay[:processSequence] =
- assays.map { |a| convert_process_sequence(a.sample_type, a.sops.map(&:id).join("_"), a.id) }.flatten
+ child_assays.map { |a| convert_process_sequence(a.sample_type, a.sops.map(&:id).join("_"), a.id) }.flatten
isa_assay[:dataFiles] = convert_data_files(all_sample_types)
isa_assay[:unitCategories] = []
isa_assay
@@ -278,7 +270,14 @@ def convert_publication(publication)
def convert_ontologies
source_ontologies = []
- sample_types = @investigation.studies.map(&:sample_types) + @investigation.assays.map(&:sample_type)
+ sample_types = @investigation.studies.map(&:sample_types) + @investigation.assays
+ .select(&:is_assay_stream?)
+ .map(&:child_assays)
+ .compact
+ .flatten
+ .map(&:sample_type)
+ .compact
+
sample_types.flatten.each do |sa|
sa.sample_attributes.each do |atr|
source_ontologies << atr.sample_controlled_vocab.source_ontology if atr.ontology_based?
@@ -752,7 +751,7 @@ def random_string(len)
end
def get_derived_from_type(sample_type)
- raise 'There is no sample!' if sample_type.samples.length == 0
+ raise "There are no samples in '#{sample_type.title}'!" if sample_type.samples.blank?
prev_sample_type = sample_type.samples[0]&.linked_samples[0]&.sample_type
return nil if prev_sample_type.blank?
diff --git a/lib/seek/config.rb b/lib/seek/config.rb
index b216834f0b..ffcc4484ca 100644
--- a/lib/seek/config.rb
+++ b/lib/seek/config.rb
@@ -7,7 +7,7 @@ module Fallbacks
def instance_admins_name_fallback
instance_name
end
-
+
def instance_admins_link_fallback
instance_link
end
@@ -529,6 +529,10 @@ def self.read_project_setting_attributes
register_encrypted_setting(method) if opts && opts[:encrypt]
end
+ def self.analytics_enabled
+ google_analytics_enabled || piwik_analytics_enabled || custom_analytics_snippet_enabled
+ end
+
def self.schema_org_supported?
true
end
diff --git a/lib/seek/config_setting_attributes.yml b/lib/seek/config_setting_attributes.yml
index e23642e9df..6cff135b3f 100644
--- a/lib/seek/config_setting_attributes.yml
+++ b/lib/seek/config_setting_attributes.yml
@@ -60,6 +60,10 @@ copasi_enabled:
external_search_enabled:
piwik_analytics_enabled:
piwik_analytics_tracking_notice:
+custom_analytics_snippet_enabled:
+custom_analytics_snippet:
+custom_analytics_tracking_notice:
+custom_analytics_name:
seek_video_link:
scales:
delete_asset_version_enabled:
diff --git a/lib/seek/ontologies/synchronize.rb b/lib/seek/ontologies/synchronize.rb
index e15f0119e8..cfce103be3 100644
--- a/lib/seek/ontologies/synchronize.rb
+++ b/lib/seek/ontologies/synchronize.rb
@@ -99,7 +99,7 @@ def assay_changes_class?(assays, ontology_uri)
def determine_assay_class_from_uri(uri)
ontology_class = Seek::Ontologies::AssayTypeReader.instance.class_for_uri(uri)
- ontology_class.nil? ? AssayClass.for_type('modelling') : AssayClass.for_type('experimental')
+ ontology_class.nil? ? AssayClass.modelling : AssayClass.experimental
end
def get_suggested_types_found_in_ontology(type)
diff --git a/lib/seek/openbis/seek_util.rb b/lib/seek/openbis/seek_util.rb
index 6476676d54..a99b15949e 100644
--- a/lib/seek/openbis/seek_util.rb
+++ b/lib/seek/openbis/seek_util.rb
@@ -27,7 +27,7 @@ def createObisAssay(assay_params, creator, obis_asset)
zample = obis_asset.content
openbis_endpoint = obis_asset.seek_service
- assay_params[:assay_class_id] ||= AssayClass.for_type('experimental').id
+ assay_params[:assay_class_id] ||= AssayClass.experimental.id
assay_params[:title] ||= extract_title(zample) ## "OpenBIS #{zample.perm_id}"
assay = Assay.new(assay_params)
@@ -88,7 +88,7 @@ def fake_file_assay(study)
assay = study.assays.where(title: FAKE_FILE_ASSAY_NAME).first
return assay if assay
- assay_params = {assay_class_id: AssayClass.for_type('experimental').id,
+ assay_params = {assay_class_id: AssayClass.experimental.id,
title: FAKE_FILE_ASSAY_NAME,
description: 'Automatically generated assay to host openbis files that are linked to
the original OpenBIS experiment. Its content and linked data files will be updated by the system
@@ -446,7 +446,7 @@ def study_types(openbis_endpoint)
Seek::Openbis::EntityType.ExperimentType(openbis_endpoint).find_by_codes(study_codes)
end
-
+
end
end
end
diff --git a/lib/seek/projects/population.rb b/lib/seek/projects/population.rb
index 2ab008c1e4..faeb03241b 100644
--- a/lib/seek/projects/population.rb
+++ b/lib/seek/projects/population.rb
@@ -21,7 +21,7 @@ def populate_from_spreadsheet_impl
r = sheet.rows[1]
if r.cell(1).value.blank?
- flash[:error]= "Unable to find header cells in #{datafile.title}"
+ flash[:error]= "Unable to find header cells in #{datafile.title}"
return
end
@@ -78,7 +78,7 @@ def populate_from_spreadsheet_impl
assay_position = 1
investigation.save!
end
-
+
if r.cell(study_index)&.value.present?
if investigation.blank?
flash[:error]= "Study specified without Investigation in #{datafile.title} at row #{r.index}"
@@ -111,7 +111,7 @@ def populate_from_spreadsheet_impl
set_description(assay, r, description_index)
assay.position = assay_position
assay_position += 1
- assay.assay_class = AssayClass.for_type('experimental')
+ assay.assay_class = AssayClass.experimental
set_assignees(assay, r, assignee_indices)
@@ -134,7 +134,7 @@ def set_description(object, r, description_index)
end
object.description = description
end
-
+
def set_assignees(assay, r, assignee_indices)
assignees = []
assignee_indices.each do |x|
@@ -154,7 +154,7 @@ def set_assignees(assay, r, assignee_indices)
end
assay.creators = known_creators
assay.other_creators = other_creators.join(';')
- end
+ end
def set_protocol(assay, r, protocol_index)
protocol_string = r.cell(protocol_index)&.value&.to_s.strip
diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake
index fb779e184a..e9b1ae3c15 100644
--- a/lib/tasks/seek_upgrades.rake
+++ b/lib/tasks/seek_upgrades.rake
@@ -15,7 +15,9 @@ namespace :seek do
rename_registered_sample_multiple_attribute_type
remove_ontology_attribute_type
db:seed:007_sample_attribute_types
+ db:seed:001_create_controlled_vocabs
recognise_isa_json_compliant_items
+ implement_assay_streams_for_isa_assays
]
# these are the tasks that are executes for each upgrade as standard, and rarely change
@@ -176,6 +178,48 @@ namespace :seek do
puts "...Updated #{investigations_updated.to_s} investigations"
end
+ task(implement_assay_streams_for_isa_assays: [:environment]) do
+ puts '... Organising isa json compliant assays in assay streams'
+ assay_streams_created = 0
+ disable_authorization_checks do
+ # find assays linked to a study through their sample_types
+ # Should be isa json compliant
+ # Shouldn't already have an assay stream (don't update assays that have been updated already)
+ # Previous ST should be second ST of study
+ first_assays_in_stream = Assay.joins(:sample_type, study: :investigation)
+ .where(assay_stream_id: nil, investigation: { is_isa_json_compliant: true })
+ .select { |a| a.previous_linked_sample_type == a.study.sample_types.second }
+
+ first_assays_in_stream.map do |fas|
+ stream_name = "Assay Stream - #{UUID.generate}"
+ assay_stream = Assay.create(title: stream_name,
+ study_id: fas.study_id,
+ assay_class_id: AssayClass.assay_stream.id,
+ contributor: fas.contributor,
+ position: 0)
+
+ # Transfer extended metadata from first assay to newly created assay stream
+ unless fas.extended_metadata.nil?
+ em = ExtendedMetadata.find_by(item_id: fas.id)
+ em.update_column(:item_id, assay_stream.id)
+ end
+
+ assay_position = 1
+ current_assay = fas
+ while current_assay
+ current_assay.update_column(:position, assay_position)
+ current_assay.update_column(:assay_stream_id, assay_stream.id)
+
+ assay_position += 1
+ current_assay = current_assay.next_linked_child_assay
+ end
+ assay_streams_created += 1
+ end
+ end
+
+ puts "...Created #{assay_streams_created} new assay streams"
+ end
+
private
##
diff --git a/lib/treeview_builder.rb b/lib/treeview_builder.rb
index d64e6f4217..6f89ddf2d3 100644
--- a/lib/treeview_builder.rb
+++ b/lib/treeview_builder.rb
@@ -14,9 +14,22 @@ def build_tree_data
investigation_items = []
@project.investigations.map do |investigation|
- investigation.studies.map do |study|
- assay_items = study.assays.map { |assay| build_assay_item(assay) }
- study_items << build_study_item(study, assay_items)
+ if investigation.is_isa_json_compliant?
+ investigation.studies.map do |study|
+ assay_stream_items = study.assay_streams.map do |assay_stream|
+ assay_items = assay_stream.child_assays.map do |child_assay|
+ build_assay_item(child_assay)
+ end
+ build_assay_stream_item(assay_stream, assay_items)
+ end
+
+ study_items << build_study_item(study, assay_stream_items)
+ end
+ else
+ investigation.studies.map do |study|
+ assay_items = study.assays.map { |assay| build_assay_item(assay) }
+ study_items << build_study_item(study, assay_items)
+ end
end
investigation_items << build_investigation_item(investigation, study_items)
study_items = []
@@ -125,6 +138,11 @@ def build_study_item(study, assay_items)
children: isa_study_elements(study) + assay_items, resource: study })
end
+ def build_assay_stream_item(assay_stream, child_assays)
+ create_node({ text: assay_stream.title, _type: 'assay', label: 'Assay Stream', _id: assay_stream.id, a_attr: BOLD,
+ children: isa_assay_elements(assay_stream) + child_assays, resource: assay_stream })
+ end
+
def build_assay_item(assay)
create_node({ text: assay.title, _type: 'assay', label: 'Assay', _id: assay.id, a_attr: BOLD,
children: isa_assay_elements(assay), resource: assay })
diff --git a/test/factories/assays.rb b/test/factories/assays.rb
index c0ab8efcf5..24f95f1ae0 100644
--- a/test/factories/assays.rb
+++ b/test/factories/assays.rb
@@ -13,6 +13,12 @@
description { "An experimental assay class description" }
end
+ factory(:assay_stream_class, class: AssayClass) do
+ title { I18n.t('assays.assay_stream') }
+ key { 'STREAM' }
+ description { "An assay stream class description" }
+ end
+
# SuggestedTechnologyType
factory(:suggested_technology_type) do
sequence(:label) { | n | "A TechnologyType#{n}" }
@@ -99,11 +105,20 @@
title { 'ISA JSON compliant assay' }
description { 'An assay linked to an ISA JSON compliant study and a sample type' }
after(:build) do |assay|
- assay.study = FactoryBot.create(:isa_json_compliant_study)
+ assay.study ||= FactoryBot.create(:isa_json_compliant_study)
assay.sample_type = FactoryBot.create(:isa_assay_material_sample_type, linked_sample_type: assay.study.sample_types.last)
end
end
+ factory(:assay_stream, parent: :assay_base) do
+ title { 'Assay Stream' }
+ description { 'A holder assay holding multiple child assays' }
+ association :assay_class, factory: :assay_stream_class
+ after(:build) do |assay|
+ assay.study ||= FactoryBot.create(:isa_json_compliant_study, contributor: assay.contributor)
+ end
+ end
+
# AssayAsset
factory :assay_asset do
association :assay
diff --git a/test/fixtures/assay_classes.yml b/test/fixtures/assay_classes.yml
index 6811c4bdb0..cf87a8f7d1 100644
--- a/test/fixtures/assay_classes.yml
+++ b/test/fixtures/assay_classes.yml
@@ -1,7 +1,11 @@
experimental_assay_class:
title: <%= I18n.t('assays.experimental_assay') %>
key: EXP
-
-modelling_assay_class:
+
+modelling_assay_class:
title: <%= I18n.t('assays.modelling_analysis') %>
- key: MODEL
\ No newline at end of file
+ key: MODEL
+
+assay_stream_class:
+ title: <%= I18n.t('assays.assay_stream') %>
+ key: STREAM
diff --git a/test/functional/assays_controller_test.rb b/test/functional/assays_controller_test.rb
index 77253f7191..1996d36918 100644
--- a/test/functional/assays_controller_test.rb
+++ b/test/functional/assays_controller_test.rb
@@ -587,14 +587,14 @@ def test_title
test 'get new with class doesnt present options for class' do
login_as(:model_owner)
- get :new, params: { class: 'experimental' }
+ get :new, params: { class: 'EXP' }
assert_response :success
assert_select 'a[href=?]', new_assay_path(class: :experimental), count: 0
assert_select 'a', text: /An #{I18n.t('assays.experimental_assay')}/i, count: 0
assert_select 'a[href=?]', new_assay_path(class: :modelling), count: 0
assert_select 'a', text: /A #{I18n.t('assays.modelling_analysis')}/i, count: 0
- get :new, params: { class: 'modelling' }
+ get :new, params: { class: 'MODEL' }
assert_response :success
assert_select 'a[href=?]', new_assay_path(class: :experimental), count: 0
assert_select 'a', text: /An #{I18n.t('assays.experimental_assay')}/i, count: 0
@@ -1065,7 +1065,7 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links
end
test 'new should include tags element' do
- get :new, params: { class: :experimental }
+ get :new, params: { class: 'EXP' }
assert_response :success
assert_select 'div.panel-heading', text: /Tags/, count: 1
assert_select 'select#tag_list', count: 1
@@ -1121,7 +1121,7 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links
end
test 'should show experimental assay types for new experimental assay' do
- get :new, params: { class: :experimental }
+ get :new, params: { class: 'EXP' }
assert_response :success
assert_select 'label', text: /assay type/i
assert_select 'select#assay_assay_type_uri' do
@@ -1131,7 +1131,7 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links
end
test 'should show modelling assay types for new modelling assay' do
- get :new, params: { class: :modelling }
+ get :new, params: { class: 'MODEL' }
assert_response :success
assert_select 'label', text: /Biological problem addressed/i
assert_select 'select#assay_assay_type_uri' do
@@ -1947,7 +1947,7 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links
# person = User.current_user.person
person = FactoryBot.create(:person)
project = person.projects.first
- investigation = FactoryBot.create(:investigation, projects: [project])
+ investigation = FactoryBot.create(:investigation, projects: [project], is_isa_json_compliant: true)
source_st = FactoryBot.create(:isa_source_sample_type, contributor: person, projects: [project])
sample_collection_st = FactoryBot.create(:isa_sample_collection_sample_type, contributor: person, projects: [project],
@@ -1984,7 +1984,7 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links
assay3.reload
- assert_equal(assay3.previous_linked_assay_sample_type&.id, assay1.sample_type&.id)
+ assert_equal(assay3.previous_linked_sample_type&.id, assay1.sample_type&.id)
end
test 'do not get index if feature disabled' do
@@ -2012,9 +2012,24 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links
with_config_value(:isa_json_compliance_enabled, true) do
current_user = FactoryBot.create(:user)
login_as(current_user)
- assay = FactoryBot.create(:isa_json_compliant_assay, contributor: current_user.person)
+ study = FactoryBot.create(:isa_json_compliant_study)
+ assay_stream = FactoryBot.create(:assay_stream, study: , contributor: current_user.person)
+ assay1 = FactoryBot.create(:isa_json_compliant_assay, contributor: current_user.person, study: , assay_stream:)
+ assay2 = FactoryBot.create(:isa_json_compliant_assay, contributor: current_user.person, study: , assay_stream:)
- get :show, params: { id: assay }
+ assert_equal assay_stream.study, assay1.study
+
+ get :show, params: { id: assay_stream }
+ assert_response :success
+
+ assert_select 'a', text: /Design #{I18n.t('assay')}/i, count: 1
+
+ get :show, params: { id: assay1 }
+ assert_response :success
+
+ assert_select 'a', text: /Design the next #{I18n.t('assay')}/i, count: 1
+
+ get :show, params: { id: assay2 }
assert_response :success
assert_select 'a', text: /Design the next #{I18n.t('assay')}/i, count: 1
diff --git a/test/functional/investigations_controller_test.rb b/test/functional/investigations_controller_test.rb
index a564eb29a4..b38571a183 100644
--- a/test/functional/investigations_controller_test.rb
+++ b/test/functional/investigations_controller_test.rb
@@ -911,16 +911,22 @@ def test_title
contributor: other_user.person)
# Create a 'private' assay in an assay stream
- assay_1_stream_1_sample_type = FactoryBot.create(:isa_assay_material_sample_type, linked_sample_type: sample_collection_sample_type, template_id: FactoryBot.create(:isa_assay_material_template).id)
- assay_1_stream_1 = FactoryBot.create(:assay, position: 0, sample_type: assay_1_stream_1_sample_type, study: accessible_study, contributor: current_user.person)
- assay_2_stream_1_sample_type = FactoryBot.create(:isa_assay_data_file_sample_type, linked_sample_type: assay_1_stream_1_sample_type, template_id: FactoryBot.create(:isa_assay_data_file_template).id)
- assay_2_stream_1 = FactoryBot.create(:assay, position:1, sample_type: assay_2_stream_1_sample_type, study: accessible_study, contributor: other_user.person)
+ stream_1 = FactoryBot.create(:assay_stream, title: 'Assay Stream 1', study: accessible_study, contributor: other_user.person)
+ assert_equal(stream_1.study, accessible_study)
+ assert(stream_1.is_assay_stream?)
+ assay_1_stream_1_sample_type = FactoryBot.create(:isa_assay_material_sample_type, contributor: other_user.person, linked_sample_type: sample_collection_sample_type, template_id: FactoryBot.create(:isa_assay_material_template).id)
+ assay_1_stream_1 = FactoryBot.create(:assay, position: 1, sample_type: assay_1_stream_1_sample_type, study: accessible_study, contributor: other_user.person, assay_stream_id: stream_1.id)
+ assay_2_stream_1_sample_type = FactoryBot.create(:isa_assay_data_file_sample_type, contributor: other_user.person, linked_sample_type: assay_1_stream_1_sample_type, template_id: FactoryBot.create(:isa_assay_data_file_template).id)
+ assay_2_stream_1 = FactoryBot.create(:assay, position: 2, sample_type: assay_2_stream_1_sample_type, study: accessible_study, contributor: other_user.person, assay_stream_id: stream_1.id)
# Create an assay stream with all assays visible
- assay_1_stream_2_sample_type = FactoryBot.create(:isa_assay_material_sample_type, linked_sample_type: sample_collection_sample_type, template_id: FactoryBot.create(:isa_assay_material_template).id)
- assay_1_stream_2 = FactoryBot.create(:assay, position: 0, sample_type: assay_1_stream_2_sample_type, study: accessible_study, contributor: current_user.person)
- assay_2_stream_2_sample_type = FactoryBot.create(:isa_assay_data_file_sample_type, linked_sample_type: assay_1_stream_2_sample_type, template_id: FactoryBot.create(:isa_assay_data_file_template).id)
- assay_2_stream_2 = FactoryBot.create(:assay, position:1, sample_type: assay_2_stream_2_sample_type, study: accessible_study, contributor: current_user.person)
+ stream_2 = FactoryBot.create(:assay_stream, title: 'Assay Stream 2', study: accessible_study, contributor: current_user.person)
+ assert_equal(stream_2.study, accessible_study)
+ assert(stream_2.is_assay_stream?)
+ assay_1_stream_2_sample_type = FactoryBot.create(:isa_assay_material_sample_type, contributor: current_user.person, linked_sample_type: sample_collection_sample_type, template_id: FactoryBot.create(:isa_assay_material_template).id)
+ assay_1_stream_2 = FactoryBot.create(:assay, position: 1, sample_type: assay_1_stream_2_sample_type, study: accessible_study, contributor: current_user.person, assay_stream_id: stream_2.id)
+ assay_2_stream_2_sample_type = FactoryBot.create(:isa_assay_data_file_sample_type, contributor: current_user.person, linked_sample_type: assay_1_stream_2_sample_type, template_id: FactoryBot.create(:isa_assay_data_file_template).id)
+ assay_2_stream_2 = FactoryBot.create(:assay, position: 2, sample_type: assay_2_stream_2_sample_type, study: accessible_study, contributor: current_user.person, assay_stream_id: stream_2.id)
# create samples in second assay stream with viewing permission
@@ -1096,8 +1102,13 @@ def test_title
assert json_investigation['studies'].map { |s| s['title'] }.include? accessible_study.title
study_json = json_investigation['studies'].first
+ # Total assays
+ assert_equal accessible_study.assays.count, 6
+ # Assay_streams
+ assert_equal accessible_study.assay_streams.count, 2
+ # Child assays
+ assert_equal accessible_study.assay_streams.map(&:child_assays).compact.flatten.count, 4
# Only one assay should end up in 1 assay stream in the ISA JSON
- assert_equal accessible_study.assays.count, 4
assert_equal study_json['assays'].count, 1
sample_ids = study_json['materials']['samples'].map { |sample| sample['@id'] }
diff --git a/test/functional/isa_assays_controller_test.rb b/test/functional/isa_assays_controller_test.rb
index f6b436f227..1212dd21d1 100644
--- a/test/functional/isa_assays_controller_test.rb
+++ b/test/functional/isa_assays_controller_test.rb
@@ -45,7 +45,8 @@ def setup
sop_ids: [FactoryBot.create(:sop, policy: FactoryBot.create(:public_policy)).id],
creator_ids: [this_person.id, other_creator.id],
other_creators: 'other collaborators',
- position: 0, assay_class_id: 1, policy_attributes: },
+ assay_class_id: AssayClass.experimental.id,
+ position: 0, policy_attributes: },
input_sample_type_id: sample_collection_sample_type.id,
sample_type: { title: 'assay sample_type', project_ids: [projects.first.id], template_id: 1,
sample_attributes_attributes: {
@@ -188,7 +189,7 @@ def setup
sop_ids: [FactoryBot.create(:sop, policy: FactoryBot.create(:public_policy)).id],
creator_ids: [this_person.id, other_creator.id],
other_creators: 'other collaborators',
- position: 0, assay_class_id: 1, policy_attributes: }
+ position: 0, assay_class_id: AssayClass.experimental.id, policy_attributes: }
isa_assay_attributes = { assay: assay_attributes.merge(emt_attributes),
input_sample_type_id: sample_collection_sample_type.id,
@@ -224,4 +225,60 @@ def setup
end
end
end
+
+ test 'hide sops, publications, documents, and discussion channels if assay stream' do
+ person = FactoryBot.create(:person)
+ study = FactoryBot.create(:isa_json_compliant_study, contributor: person)
+ assay_stream = FactoryBot.create(:assay_stream, study: , contributor: person)
+
+ get :new, params: {study_id: study.id, is_assay_stream: true}
+ assert_response :success
+
+ assert_select 'div#add_sops_form', text: /SOPs/i, count: 0
+ assert_select 'div#add_publications_form', text: /Publications/i, count: 0
+ assert_select 'div#add_documents_form', text: /Documents/i, count: 0
+ assert_select 'div.panel-heading', text: /Discussion Channels/i, count: 0
+ assert_select 'div.panel-heading', text: /Define Sample type for Assay/i, count: 0
+
+ get :edit, params: { id: assay_stream.id, study_id: study.id, source_assay_id: assay_stream.id, is_assay_stream: true }
+ assert_response :success
+
+ assert_select 'div#add_sops_form', text: /SOPs/i, count: 0
+ assert_select 'div#add_publications_form', text: /Publications/i, count: 0
+ assert_select 'div#add_documents_form', text: /Documents/i, count: 0
+ assert_select 'div.panel-heading', text: /Discussion Channels/i, count: 0
+ assert_select 'div.panel-heading', text: /Define Sample type for Assay/i, count: 0
+end
+
+ test 'show sops, publications, documents, and discussion channels if experimental assay' do
+ person = FactoryBot.create(:person)
+ project = person.projects.first
+ investigation = FactoryBot.create(:investigation, is_isa_json_compliant: true, contributor: person, projects: [project])
+ study = FactoryBot.create(:isa_json_compliant_study, contributor: person, investigation: )
+ assay_stream = FactoryBot.create(:assay_stream, study: , contributor: person, position: 0)
+
+ login_as(person)
+
+ get :new, params: {study_id: study.id, assay_stream_id: assay_stream.id, source_assay_id: assay_stream.id}
+ assert_response :success
+
+ assert_select 'div#add_sops_form', text: /SOPs/i, count: 1
+ assert_select 'div#add_publications_form', text: /Publications/i, count: 1
+ assert_select 'div#add_documents_form', text: /Documents/i, count:1
+ assert_select 'div.panel-heading', text: /Discussion Channels/i, count: 1
+ assert_select 'div.panel-heading', text: /Define Sample type for Assay/i, count: 1
+
+ first_assay_st = FactoryBot.create(:isa_assay_material_sample_type, contributor: person, projects: [project], linked_sample_type: study.sample_types.second)
+ first_assay = FactoryBot.create(:assay, contributor: person, study: , assay_stream: , position: 1, sample_type: first_assay_st)
+ assert_equal assay_stream, first_assay.assay_stream
+
+ get :edit, params: { id: first_assay.id, assay_stream_id: assay_stream.id, source_assay_id: first_assay.id, study_id: study.id }
+ assert_response :success
+
+ assert_select 'div#add_sops_form', text: /SOPs/i, count: 1
+ assert_select 'div#add_publications_form', text: /Publications/i, count: 1
+ assert_select 'div#add_documents_form', text: /Documents/i, count: 1
+ assert_select 'div.panel-heading', text: /Discussion Channels/i, count: 1
+ assert_select 'div.panel-heading', text: /Define Sample type for Assay/i, count: 1
+ end
end
diff --git a/test/functional/studies_controller_test.rb b/test/functional/studies_controller_test.rb
index 69a5ef4419..a29d1c8891 100644
--- a/test/functional/studies_controller_test.rb
+++ b/test/functional/studies_controller_test.rb
@@ -2022,7 +2022,7 @@ def test_should_show_investigation_tab
get :show, params: { id: study }
assert_response :success
- assert_select 'a', text: /Design #{I18n.t('assay')}/i, count: 1
+ assert_select 'a', text: /Design #{I18n.t('assay')} Stream/i, count: 1
assert_select 'a', text: /Add new #{I18n.t('assay')}/i, count: 0
end
end
diff --git a/test/functional/workflow_classes_controller_test.rb b/test/functional/workflow_classes_controller_test.rb
index 9a115efc52..79519f0841 100644
--- a/test/functional/workflow_classes_controller_test.rb
+++ b/test/functional/workflow_classes_controller_test.rb
@@ -32,6 +32,40 @@ class WorkflowClassesControllerTest < ActionController::TestCase
workflow_class_avatar_path(user_added_3, user_added_3.avatar, size: '32x32'), count: 1
end
+ test 'get index as json-ld' do
+ person = FactoryBot.create(:person)
+ disable_authorization_checks do
+ FactoryBot.create(:cwl_workflow_class)
+ FactoryBot.create(:galaxy_workflow_class)
+ FactoryBot.create(:nextflow_workflow_class)
+ WorkflowClass.create!(title: 'My Class', key: 'mine', contributor: person)
+ end
+
+ login_as(person)
+
+ get :index, format: :jsonld
+
+ assert_response :success
+ classes = JSON.parse(response.body)
+ assert_equal 4, classes.length
+
+ cwl = classes.detect { |c| c['@id'] == '#cwl' }
+ assert cwl
+ assert_equal 'Common Workflow Language', cwl['name']
+ assert_equal 'ComputerLanguage', cwl['@type']
+ assert_equal 'CWL', cwl['alternateName']
+ assert_equal({ '@id' => 'https://w3id.org/cwl/v1.0/' }, cwl['identifier'])
+ assert_equal({ '@id' =>'https://www.commonwl.org/' }, cwl['url'])
+
+ assert classes.detect { |c| c['@id'] == '#galaxy' }
+ assert classes.detect { |c| c['@id'] == '#nextflow' }
+
+ user_added = classes.detect { |c| c['@id'] == '#mine' }
+ assert_equal 'My Class', user_added['name']
+ refute user_added.key?('identifier')
+ refute user_added.key?('url')
+ end
+
test 'admin can edit any workflow class' do
person = FactoryBot.create(:person)
core_type, c1, c2 = nil
diff --git a/test/integration/cookie_consent_integration_test.rb b/test/integration/cookie_consent_integration_test.rb
index 1dd8608fcb..17bbc00ee0 100644
--- a/test/integration/cookie_consent_integration_test.rb
+++ b/test/integration/cookie_consent_integration_test.rb
@@ -45,6 +45,20 @@ class CookieConsentIntegrationTest < ActionDispatch::IntegrationTest
end
end
+ test 'cookie consent banner shown with tracking option if custom analytics enabled' do
+ with_config_value(:require_cookie_consent, true) do
+ with_config_value(:custom_analytics_snippet_enabled, true) do
+ get root_path
+
+ assert_select '#cookie-banner' do
+ assert_select 'a[href=?]', cookies_consent_path(allow: 'necessary')
+ assert_select 'a[href=?]', cookies_consent_path(allow: 'necessary,embedding')
+ assert_select 'a[href=?]', cookies_consent_path(allow: all_options)
+ end
+ end
+ end
+ end
+
test 'cookie consent banner not shown if not required' do
with_config_value(:require_cookie_consent, false) do
get root_path
@@ -138,6 +152,19 @@ class CookieConsentIntegrationTest < ActionDispatch::IntegrationTest
end
end
+ test 'custom analytics code not present if only necessary cookies allowed' do
+ with_config_value(:require_cookie_consent, true) do
+ with_config_value(:custom_analytics_snippet_enabled, true) do
+ post cookies_consent_path, params: { allow: 'necessary' }
+
+ get root_path
+
+ assert_equal ['necessary'], CookieConsent.new(cookies).options
+ assert_select '#custom-tracking-script', count: 0
+ end
+ end
+ end
+
test 'matomo analytics code not present if necessary and embedded cookies allowed' do
with_config_value(:require_cookie_consent, true) do
with_config_value(:piwik_analytics_enabled, true) do
@@ -151,6 +178,19 @@ class CookieConsentIntegrationTest < ActionDispatch::IntegrationTest
end
end
+ test 'custom analytics code not present if necessary and embedded cookies allowed' do
+ with_config_value(:require_cookie_consent, true) do
+ with_config_value(:custom_analytics_snippet_enabled, true) do
+ post cookies_consent_path, params: { allow: 'necessary,embedding' }
+
+ get root_path
+
+ assert_equal ['necessary', 'embedding'], CookieConsent.new(cookies).options
+ assert_select '#custom-tracking-script', count: 0
+ end
+ end
+ end
+
test 'matomo analytics code present if only all cookies allowed' do
with_config_value(:require_cookie_consent, true) do
with_config_value(:piwik_analytics_enabled, true) do
@@ -164,6 +204,19 @@ class CookieConsentIntegrationTest < ActionDispatch::IntegrationTest
end
end
+ test 'custom analytics code present if only all cookies allowed' do
+ with_config_value(:require_cookie_consent, true) do
+ with_config_value(:custom_analytics_snippet_enabled, true) do
+ post cookies_consent_path, params: { allow: all_options }
+
+ get root_path
+
+ assert CookieConsent.new(cookies).allow_tracking?
+ assert_select '#custom-tracking-script', count: 1
+ end
+ end
+ end
+
test 'matomo analytics code present if cookie consent not required' do
with_config_value(:require_cookie_consent, false) do
with_config_value(:piwik_analytics_enabled, true) do
@@ -179,6 +232,21 @@ class CookieConsentIntegrationTest < ActionDispatch::IntegrationTest
end
end
+ test 'custom analytics code present if cookie consent not required' do
+ with_config_value(:require_cookie_consent, false) do
+ with_config_value(:custom_analytics_snippet_enabled, true) do
+ post cookies_consent_path, params: { allow: 'necessary' }
+
+ get root_path
+
+ cookie_consent = CookieConsent.new(cookies)
+ assert_equal ['necessary'], cookie_consent.options
+ assert cookie_consent.allow_tracking?
+ assert_select '#custom-tracking-script', count: 1
+ end
+ end
+ end
+
test 'can access and use cookie consent page as anonymous user' do
with_config_value(:require_cookie_consent, true) do
with_config_value(:google_analytics_enabled, true) do
diff --git a/test/unit/assay_class_test.rb b/test/unit/assay_class_test.rb
index 3a3d002f6d..3ef0593df8 100644
--- a/test/unit/assay_class_test.rb
+++ b/test/unit/assay_class_test.rb
@@ -4,12 +4,12 @@ class AssayClassTest < ActiveSupport::TestCase
# Replace this with your real tests.
fixtures :assay_classes
test 'for_type' do
- assert_equal 'EXP', AssayClass.for_type('experimental').key
- assert_equal 'MODEL', AssayClass.for_type('modelling').key
+ assert_equal 'EXP', AssayClass.experimental.key
+ assert_equal 'MODEL', AssayClass.modelling.key
end
test 'is_modelling?' do
- assert AssayClass.for_type('modelling').is_modelling?
- refute AssayClass.for_type('experimental').is_modelling?
+ assert AssayClass.modelling.is_modelling?
+ refute AssayClass.experimental.is_modelling?
end
end
diff --git a/test/unit/assay_test.rb b/test/unit/assay_test.rb
index a8a9199528..f72a1e7d50 100644
--- a/test/unit/assay_test.rb
+++ b/test/unit/assay_test.rb
@@ -754,10 +754,88 @@ def new_valid_assay
test 'isa json compliance' do
isa_json_compliant_study = FactoryBot.create(:isa_json_compliant_study)
assert isa_json_compliant_study.is_isa_json_compliant?
+
default_assay = FactoryBot.create(:assay, study: isa_json_compliant_study)
refute default_assay.is_isa_json_compliant?
- isa_json_compliant_assay = FactoryBot.create(:isa_json_compliant_assay)
+ assay_stream = FactoryBot.create(:assay_stream, study: isa_json_compliant_study)
+ assert assay_stream.is_isa_json_compliant?
+
+ isa_json_compliant_assay = FactoryBot.create(:isa_json_compliant_assay, study: isa_json_compliant_study)
assert isa_json_compliant_assay.is_isa_json_compliant?
end
+
+ test 'is assay stream' do
+ isa_json_compliant_study = FactoryBot.create(:isa_json_compliant_study)
+ assay_stream = FactoryBot.create(:assay_stream, study: isa_json_compliant_study)
+ assert assay_stream.is_assay_stream?
+
+ default_assay = FactoryBot.create(:assay)
+ refute default_assay.is_assay_stream?
+ end
+
+ test 'previous linked sample type' do
+ isa_study = FactoryBot.create(:isa_json_compliant_study)
+ def_study = FactoryBot.create(:study)
+
+ assay_stream = FactoryBot.create(:assay_stream, study: isa_study)
+ assert_equal assay_stream.previous_linked_sample_type, isa_study.sample_types.second
+
+ def_assay = FactoryBot.create(:assay, study:def_study)
+ assert_nil def_assay.previous_linked_sample_type
+
+ first_isa_assay = FactoryBot.create(:isa_json_compliant_assay,
+ assay_stream: ,
+ study: isa_study)
+ assert_equal first_isa_assay.previous_linked_sample_type, isa_study.sample_types.second
+
+ data_file_sample_type = FactoryBot.create(:isa_assay_data_file_sample_type,
+ linked_sample_type: first_isa_assay.sample_type)
+ second_isa_assay = FactoryBot.create(:assay,
+ study: isa_study,
+ assay_stream: ,
+ sample_type: data_file_sample_type)
+
+ assert_equal second_isa_assay.previous_linked_sample_type, first_isa_assay.sample_type
+ end
+
+ test 'has_linked_child_assay?' do
+ isa_study = FactoryBot.create(:isa_json_compliant_study)
+ def_study = FactoryBot.create(:study)
+ def_assay = FactoryBot.create(:assay, study:def_study)
+
+ assay_stream = FactoryBot.create(:assay_stream, study: isa_study)
+ first_isa_assay = FactoryBot.create(:isa_json_compliant_assay, study: isa_study)
+ data_file_sample_type = FactoryBot.create(:isa_assay_data_file_sample_type,
+ linked_sample_type: first_isa_assay.sample_type)
+ second_isa_assay = FactoryBot.create(:assay,
+ study: isa_study,
+ assay_stream: ,
+ sample_type: data_file_sample_type)
+
+ assert assay_stream.has_linked_child_assay?
+ refute def_assay.has_linked_child_assay?
+ assert first_isa_assay.has_linked_child_assay?
+ refute second_isa_assay.has_linked_child_assay?
+ end
+
+ test 'next_linked_child_assay' do
+ isa_study = FactoryBot.create(:isa_json_compliant_study)
+ def_study = FactoryBot.create(:study)
+ def_assay = FactoryBot.create(:assay, study:def_study)
+
+ assay_stream = FactoryBot.create(:assay_stream, study: isa_study)
+ first_isa_assay = FactoryBot.create(:isa_json_compliant_assay, study: isa_study)
+ data_file_sample_type = FactoryBot.create(:isa_assay_data_file_sample_type,
+ linked_sample_type: first_isa_assay.sample_type)
+ second_isa_assay = FactoryBot.create(:assay,
+ study: isa_study,
+ assay_stream: ,
+ sample_type: data_file_sample_type)
+
+ assert_equal assay_stream.next_linked_child_assay, first_isa_assay
+ assert_nil def_assay.next_linked_child_assay
+ assert_equal first_isa_assay.next_linked_child_assay, second_isa_assay
+ assert_nil second_isa_assay.next_linked_child_assay
+ end
end
diff --git a/test/unit/config_test.rb b/test/unit/config_test.rb
index fcf99c62a4..669c6f84ba 100644
--- a/test/unit/config_test.rb
+++ b/test/unit/config_test.rb
@@ -177,6 +177,16 @@ class ConfigTest < ActiveSupport::TestCase
assert_equal 'localhost/piwik/', Seek::Config.piwik_analytics_url
end
+ test 'custom_analytics_enabled' do
+ assert !Seek::Config.custom_analytics_snippet_enabled
+ end
+ test 'custom analytics name' do
+ assert_equal 'Custom name', Seek::Config.custom_analytics_name
+ end
+ test 'custom analytics snippet' do
+ assert_equal '', Seek::Config.custom_analytics_snippet
+ end
+
# homepage settings
test 'project_news_enabled' do
assert !Seek::Config.project_news_enabled
diff --git a/test/unit/ontologies/ontology_synchronization_test.rb b/test/unit/ontologies/ontology_synchronization_test.rb
index 50aeb1c446..06ba1d31df 100644
--- a/test/unit/ontologies/ontology_synchronization_test.rb
+++ b/test/unit/ontologies/ontology_synchronization_test.rb
@@ -3,10 +3,10 @@
class OntologySynchronizationTest < ActiveSupport::TestCase
def setup
- unless AssayClass.for_type('modelling')
+ unless AssayClass.modelling
FactoryBot.create(:modelling_assay_class)
end
- unless AssayClass.for_type('experimental')
+ unless AssayClass.experimental
FactoryBot.create(:experimental_assay_class)
end
end