From 86279fc1d5008927bfef146f5d1ae274318068cd Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 15 Jan 2024 10:50:09 +0100 Subject: [PATCH 01/41] Add migration for assay streams --- db/migrate/20240112141513_add_assay_stream_id_to_assay.rb | 6 ++++++ db/schema.rb | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20240112141513_add_assay_stream_id_to_assay.rb 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 From 50e5728cc1b50eefe199a0df345dc6480b801ae7 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 15 Jan 2024 10:50:27 +0100 Subject: [PATCH 02/41] Add new Assay Class --- app/models/assay_class.rb | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/app/models/assay_class.rb b/app/models/assay_class.rb index a7a72dc0fa..956488fbac 100644 --- a/app/models/assay_class.rb +++ b/app/models/assay_class.rb @@ -1,31 +1,38 @@ 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 types "experimental" or "modelling" + # if there is not a match nil is returned + def self.for_type(type) + keys = { "experimental": 'EXP', "modelling": 'MODEL', 'assay_stream': 'ASS' } + AssayClass.find_by(key: keys[type]) end def self.experimental - self.for_type('experimental') + for_type('experimental') end def self.modelling - self.for_type('modelling') + for_type('modelling') + end + + def self.assay_stream + for_type('assay_stream') end def is_modelling? - key == "MODEL" + key == 'MODEL' end def is_experimental? key == 'EXP' end + def is_assay_stream? + key == 'ASS' + end + # 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] + { 'EXP': 'Experimental Assay', 'MODEL': 'Modelling Analysis', 'ASS': 'Assay Stream' }[key] end end From ada37ab192c1155f651ebd5bb73bc0f26cc19b06 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 15 Jan 2024 10:50:59 +0100 Subject: [PATCH 03/41] Add self-referencing association to assays --- app/models/assay.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/models/assay.rb b/app/models/assay.rb index e2fe2430fa..cb84abdbf4 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' + 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,6 +68,10 @@ class Assay < ApplicationRecord enforce_authorization_on_association :study, :view + def is_assay_stream? + child_assays.any? + end + def previous_linked_assay_sample_type sample_type.sample_attributes.detect { |sa| sa.isa_tag.nil? && sa.title.include?('Input') }&.linked_sample_type end From 4109b53b1e5385cc9745ff59b4994a006ca14d7a Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 15 Jan 2024 13:26:33 +0100 Subject: [PATCH 04/41] Modify treeview to show extra assay stream level --- lib/treeview_builder.rb | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/treeview_builder.rb b/lib/treeview_builder.rb index d64e6f4217..3996b2da2b 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.assays.select { |assay| assay.is_assay_stream? }.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 }) From d37ef6f4a642f2a695f60cbc99d72fd775e9ed07 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 16 Jan 2024 14:38:59 +0100 Subject: [PATCH 05/41] Change button text --- app/views/assays/_buttons.html.erb | 10 +++++++--- app/views/studies/_buttons.html.erb | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/views/assays/_buttons.html.erb b/app/views/assays/_buttons.html.erb index ac5b58e309..3d7659a054 100644 --- a/app/views/assays/_buttons.html.erb +++ b/app/views/assays/_buttons.html.erb @@ -21,10 +21,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])) %> + <% 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])) %> + <% end %> <% end %> <% else %> <%= add_new_item_to_dropdown(item) %> diff --git a/app/views/studies/_buttons.html.erb b/app/views/studies/_buttons.html.erb index b27edc214f..50e728dca8 100644 --- a/app/views/studies/_buttons.html.erb +++ b/app/views/studies/_buttons.html.erb @@ -21,7 +21,7 @@ <% if item.can_edit? -%> <% if Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? %> <% if item&.sample_types.present? %> - <%= button_link_to("Design #{t('assay')}", 'new', new_isa_assay_path(study_id: item.id, single_page: params[:single_page], is_source: true)) %> + <%= button_link_to("Design #{t('assay')} Stream", 'new', new_isa_assay_path(study_id: item.id, single_page: params[:single_page], is_source: true)) %> <% end -%> <% else -%> <%= add_new_item_to_dropdown(item) %> From ba27eb36490de5f56bc2ef20401c353d45e63cbf Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 16 Jan 2024 14:40:15 +0100 Subject: [PATCH 06/41] Add is_assay_stream function to assay model --- app/models/assay.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/assay.rb b/app/models/assay.rb index cb84abdbf4..ece4e750a1 100644 --- a/app/models/assay.rb +++ b/app/models/assay.rb @@ -69,7 +69,7 @@ class Assay < ApplicationRecord enforce_authorization_on_association :study, :view def is_assay_stream? - child_assays.any? + assay_class.is_assay_stream? end def previous_linked_assay_sample_type @@ -102,7 +102,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 From f09cd0d6de6a9d17eab326e8df9f919ddbf90005 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 11:28:55 +0100 Subject: [PATCH 07/41] Make child assays depend on the essay stream for deletion --- app/models/assay.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/assay.rb b/app/models/assay.rb index ece4e750a1..575afce5d5 100644 --- a/app/models/assay.rb +++ b/app/models/assay.rb @@ -25,7 +25,7 @@ class Assay < ApplicationRecord belongs_to :sample_type - has_many :child_assays, class_name: 'Assay', foreign_key: 'assay_stream_id' + 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 From 6d3e23287f6ed21771cb74c157d89c9a521ee384 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 11:38:02 +0100 Subject: [PATCH 08/41] Update the controller to handle assay streams --- app/controllers/isa_assays_controller.rb | 35 ++++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/app/controllers/isa_assays_controller.rb b/app/controllers/isa_assays_controller.rb index df0643ba43..8bdb451a7d 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.find_by(key: 'ASS').id } }) + else + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.find_by(key: 'EXP').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.find_by(key: 'ASS').id } }) + else + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.find_by(key: 'EXP').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? From 79379c954f403428dd21e0e11be94b8f3924bf82 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 14:39:29 +0100 Subject: [PATCH 09/41] Modify helper function to accept alternative names --- app/helpers/images_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/images_helper.rb b/app/helpers/images_helper.rb index a126c4a054..bc23201ea8 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) From 9cc63e3f71aa512f491115b8dc5060a8a3d68b42 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 14:40:01 +0100 Subject: [PATCH 10/41] Modify buttons for assay streams --- app/views/assays/_buttons.html.erb | 26 ++++++++++++++++++++------ app/views/studies/_buttons.html.erb | 9 +++++++-- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/app/views/assays/_buttons.html.erb b/app/views/assays/_buttons.html.erb index 3d7659a054..e247754156 100644 --- a/app/views/assays/_buttons.html.erb +++ b/app/views/assays/_buttons.html.erb @@ -1,4 +1,14 @@ -<% assay_word = item.is_modelling? ? t('assays.modelling_analysis') : t('assays.assay') %> +<% assay_word ||= + if item.is_modelling? + t('assays.modelling_analysis') + elsif item.is_assay_stream? + 'Assay Stream' + elsif Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? + t('isa_assay') + else + t('assays.assay') + end +%> <%= render :partial => "subscriptions/subscribe", :locals => {:object => item} %> <% if Seek::Config.project_single_page_enabled %> @@ -25,9 +35,9 @@ <% valid_assay = item&.is_isa_json_compliant? %> <% if valid_study && valid_assay %> <% 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])) %> + <%= 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])) %> + <%= 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 %> @@ -42,7 +52,11 @@ <%= item_actions_dropdown do %> <% if item.can_edit? %> <% if Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? %> -
  • <%= image_tag_for_key('edit', edit_isa_assay_path(item, source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page]), "Edit #{t('isa_assay')}", nil, "Edit #{t('isa_assay')}") -%>
  • + <% if item&.is_assay_stream? %> +
  • <%= image_tag_for_key('edit', edit_isa_assay_path(item, source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page], is_assay_stream: true), "Edit #{assay_word}", nil, "Edit #{assay_word}") -%>
  • + <% else %> +
  • <%= image_tag_for_key('edit', edit_isa_assay_path(item, source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page], assay_stream_id: item.assay_stream_id), "Edit #{assay_word}", nil, "Edit #{assay_word}") -%>
  • + <% end %> <% else %>
  • <%= image_tag_for_key('edit', edit_assay_path(item), "Edit #{assay_word}", nil, "Edit #{assay_word}") -%>
  • <% end %> @@ -50,8 +64,8 @@ <% if item.can_manage? -%>
  • <%= image_tag_for_key('manage', manage_assay_path(item), "Manage #{assay_word}", nil, "Manage #{assay_word}") -%>
  • - <%= render :partial => 'snapshots/new_snapshot_link', :locals => {:item => item} %> + <%= render partial: 'snapshots/new_snapshot_link', locals: {item: item} %> <% end -%> - <%= delete_icon(item, current_user) %> + <%= delete_icon(item, current_user, 'Are you sure?', assay_word) %> <% end %> diff --git a/app/views/studies/_buttons.html.erb b/app/views/studies/_buttons.html.erb index 50e728dca8..4b63c58c86 100644 --- a/app/views/studies/_buttons.html.erb +++ b/app/views/studies/_buttons.html.erb @@ -21,7 +21,7 @@ <% if item.can_edit? -%> <% if Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? %> <% if item&.sample_types.present? %> - <%= button_link_to("Design #{t('assay')} Stream", 'new', new_isa_assay_path(study_id: item.id, single_page: params[:single_page], is_source: true)) %> + <%= button_link_to("Design #{t('assay')} Stream", 'new', new_isa_assay_path(study_id: item.id, single_page: params[:single_page], is_assay_stream: true)) %> <% end -%> <% else -%> <%= add_new_item_to_dropdown(item) %> @@ -38,7 +38,12 @@ <% end %> <% if item.can_manage? -%> -
  • <%= image_tag_for_key('manage', manage_study_path(item), "Manage #{t('study')}", nil, "Manage #{t('study')}") -%>
  • + <% if Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? %> +
  • <%= image_tag_for_key('manage', manage_study_path(item), "Manage #{t('study')}", nil, "Manage #{t('isa_study')}") -%>
  • + <% else %> +
  • <%= image_tag_for_key('manage', manage_study_path(item), "Manage #{t('study')}", nil, "Manage #{t('study')}") -%>
  • + <% end %> + <%= render :partial => 'snapshots/new_snapshot_link', :locals => { :item => item } %> <% end -%> From c6fdcb7f3b72bf58b66abbedf15bdb8c6362f439 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 14:43:02 +0100 Subject: [PATCH 11/41] Change model for assay streams --- app/forms/isa_assay.rb | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) 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 From b196d09101de201da47b4e0d932fefaff550a95f Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 15:03:34 +0100 Subject: [PATCH 12/41] Modify form for handling assay streams --- app/views/isa_assays/_form.html.erb | 62 ++++++++++++++++------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/app/views/isa_assays/_form.html.erb b/app/views/isa_assays/_form.html.erb index 35d5b38d2c..13a77cced1 100644 --- a/app/views/isa_assays/_form.html.erb +++ b/app/views/isa_assays/_form.html.erb @@ -1,35 +1,40 @@ <% assay = params[:isa_assay][:assay] if params.dig(:isa_assay, :assay) %> <% study = Study.find(params[:study_id] || assay[:study_id]) %> <% -input_sample_type_id = params[:isa_assay][:input_sample_type_id] if params.dig(:isa_assay, :input_sample_type_id) -source_assay = Assay.find(params[:source_assay_id]) if params[:source_assay_id] - -if @isa_assay.assay.new_record? - if params[:source_assay_id] - assay_position = source_assay.position + 1 - else - assay_position = 0 - end -else - assay_position = @isa_assay.assay.position -end - -# assay_position = params[:source_assay_id] ? source_assay.position + 1 : 0 -input_sample_type_id ||= - if params[:is_source] - study.sample_types.second.id + source_assay = Assay.find(params[:source_assay_id]) if params[:source_assay_id] + assay_stream_id = params[:assay_stream_id] if params[:assay_stream_id] + + if @isa_assay.assay.new_record? + if params[:is_assay_stream] + assay_position = 0 + assay_class_id = AssayClass.find_by(key: 'ASS')&.id + is_assay_stream = true + else + assay_position = params[:source_assay_id].nil? ? 1 : source_assay.position + 1 + assay_class_id = AssayClass.find_by(key: 'EXP')&.id + is_assay_stream = false + end else - source_assay.sample_type.id if params[:source_assay_id] + assay_position = @isa_assay.assay.position + assay_class_id = @isa_assay.assay.assay_class_id + is_assay_stream = @isa_assay.assay.is_assay_stream? end -show_extended_metadata = - if params[:is_source] - true - elsif source_assay&.position&.zero? && !@isa_assay.assay.new_record? - true # Custom metadata should be shown in edit as well if assay position is 0. - else - false - end + input_sample_type_id ||= + if is_assay_stream || source_assay&.assay_class&.is_assay_stream? + study.sample_types.second.id + else + source_assay.sample_type.id if source_assay + end + + show_extended_metadata = + if is_assay_stream + true + elsif source_assay&.position&.zero? && !@isa_assay.assay.new_record? + true # Custom metadata should be shown in edit as well if assay position is 0. + else + false + end %> <%= error_messages_for :isa_assay %> @@ -60,7 +65,8 @@ show_extended_metadata = <%= assay_fields.number_field :position, value: assay_position || study.assays.length -%> - <%= assay_fields.hidden_field :assay_class_id -%> + <%= assay_fields.hidden_field :assay_stream_id, value: assay_stream_id -%> + <%= assay_fields.hidden_field :assay_class_id, value: assay_class_id -%> <% if User.current_user -%> <%= render partial: 'assets/manage_specific_attributes', locals:{f:assay_fields} if show_form_manage_specific_attributes? %> @@ -75,7 +81,7 @@ show_extended_metadata = <%= f.hidden_field :input_sample_type_id, value: input_sample_type_id -%> -<% if @isa_assay.sample_type %> +<% unless is_assay_stream %> <%= folding_panel("Define #{t(:sample_type)} for #{t(:assay)}") do %> <%= render partial: 'isa_studies/sample_types_form', locals: {f: f, sample_type: @isa_assay.sample_type, id_suffix: "_sample_type", isa_element: "assay", action: action} %> <% end %> From dcb5794812e3fd4e2d08e53951a483bcf7e6b78b Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 15:23:52 +0100 Subject: [PATCH 13/41] Seed Assay Stream class --- db/seeds/017_minimal_starter_isa_templates.seeds.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/db/seeds/017_minimal_starter_isa_templates.seeds.rb b/db/seeds/017_minimal_starter_isa_templates.seeds.rb index e78e477916..f44069c7e7 100644 --- a/db/seeds/017_minimal_starter_isa_templates.seeds.rb +++ b/db/seeds/017_minimal_starter_isa_templates.seeds.rb @@ -200,3 +200,8 @@ end puts 'Seeded minimal templates for organizing ISA JSON compliant experiments.' + +disable_authorization_checks do + AssayClass.find_or_create_by(title: 'Assay Stream', key: 'ASS', + description: 'Special type of class that is user in Single Page, specifying this is a container for a stream of assays') +end From 2c583d0235c8fd3c9eebd0d4651881b7cd465e10 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 16:34:33 +0100 Subject: [PATCH 14/41] Adapt isa exporter to handle assay streams --- app/models/assay.rb | 7 ---- app/models/study.rb | 4 ++ lib/isa_exporter.rb | 89 +++++++++++++++++++++------------------------ 3 files changed, 45 insertions(+), 55 deletions(-) diff --git a/app/models/assay.rb b/app/models/assay.rb index 575afce5d5..f7210d3235 100644 --- a/app/models/assay.rb +++ b/app/models/assay.rb @@ -80,13 +80,6 @@ def has_linked_child_assay? sample_type&.linked_sample_attributes&.any? 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 - end - def default_contributor User.current_user.try :person 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/lib/isa_exporter.rb b/lib/isa_exporter.rb index 108144752a..f015bc5d8d 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,37 @@ 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 + all_sample_types = child_assays.map(&:sample_type).compact + # first_assay = assays.detect { |s| s.position.zero? } + # raise 'No assay could be found!' unless first_assay - stream_name = "assays_#{assays.pluck(:id).join('_')}" - assay_comments = convert_assay_comments(assays) + stream_name = "assays_#{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? 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 +264,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? From 158cdd40d2b4a9d6c8ed923026a44e2f9412cb84 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 17:22:34 +0100 Subject: [PATCH 15/41] Add rake upgrade task to add Assay Stream AssayClass --- db/seeds/017_minimal_starter_isa_templates.seeds.rb | 2 ++ lib/tasks/seek_upgrades.rake | 1 + 2 files changed, 3 insertions(+) diff --git a/db/seeds/017_minimal_starter_isa_templates.seeds.rb b/db/seeds/017_minimal_starter_isa_templates.seeds.rb index f44069c7e7..2b1929fbfb 100644 --- a/db/seeds/017_minimal_starter_isa_templates.seeds.rb +++ b/db/seeds/017_minimal_starter_isa_templates.seeds.rb @@ -205,3 +205,5 @@ AssayClass.find_or_create_by(title: 'Assay Stream', key: 'ASS', description: 'Special type of class that is user in Single Page, specifying this is a container for a stream of assays') end + +puts 'Seeded Extra Assay Class' diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index fb779e184a..6497f980b5 100644 --- a/lib/tasks/seek_upgrades.rake +++ b/lib/tasks/seek_upgrades.rake @@ -15,6 +15,7 @@ namespace :seek do rename_registered_sample_multiple_attribute_type remove_ontology_attribute_type db:seed:007_sample_attribute_types + db:seed:017_minimal_starter_isa_templates recognise_isa_json_compliant_items ] From 0da942e4ae223f4350fe9cd38ab46727974080b0 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 17:22:57 +0100 Subject: [PATCH 16/41] Add unit test --- test/factories/assays.rb | 15 +++++++++++++++ test/fixtures/assay_classes.yml | 10 +++++++--- test/unit/assay_test.rb | 9 +++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/test/factories/assays.rb b/test/factories/assays.rb index c0ab8efcf5..eab8a81d18 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 { 'Assay Stream' } + key { 'ASS' } + description { "An assay stream class description" } + end + # SuggestedTechnologyType factory(:suggested_technology_type) do sequence(:label) { | n | "A TechnologyType#{n}" } @@ -104,6 +110,15 @@ 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) + 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..fc730abd78 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: 'Assay Stream' + key: 'ASS' diff --git a/test/unit/assay_test.rb b/test/unit/assay_test.rb index a8a9199528..72907b408a 100644 --- a/test/unit/assay_test.rb +++ b/test/unit/assay_test.rb @@ -760,4 +760,13 @@ def new_valid_assay isa_json_compliant_assay = FactoryBot.create(:isa_json_compliant_assay) 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 end From 9407039c4fa457ea6182152a0d6cc8b2506406b2 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 17:44:53 +0100 Subject: [PATCH 17/41] Test the button text on the assays controller --- app/models/assay_class.rb | 2 +- test/functional/assays_controller_test.rb | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/models/assay_class.rb b/app/models/assay_class.rb index 956488fbac..bc2a45728f 100644 --- a/app/models/assay_class.rb +++ b/app/models/assay_class.rb @@ -1,5 +1,5 @@ class AssayClass < ApplicationRecord - # this returns an instance of AssayClass according to one of the types "experimental" or "modelling" + # this returns an instance of AssayClass according to one of the types "experimental", "modelling" or "assay_stream" # if there is not a match nil is returned def self.for_type(type) keys = { "experimental": 'EXP', "modelling": 'MODEL', 'assay_stream': 'ASS' } diff --git a/test/functional/assays_controller_test.rb b/test/functional/assays_controller_test.rb index 77253f7191..ede6bcfa51 100644 --- a/test/functional/assays_controller_test.rb +++ b/test/functional/assays_controller_test.rb @@ -2012,9 +2012,21 @@ 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) + assay_stream = FactoryBot.create(:assay_stream, contributor: current_user.person) + assay1 = FactoryBot.create(:isa_json_compliant_assay, contributor: current_user.person, study: assay_stream.study, assay_stream:) + assay2 = FactoryBot.create(:isa_json_compliant_assay, contributor: current_user.person, study: assay_stream.study, assay_stream:) - get :show, params: { id: assay } + 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 From 299dfefd0c1f350edce7b1e5f52447ffff6c0597 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 18:08:12 +0100 Subject: [PATCH 18/41] Fix studiescontroller test --- test/functional/studies_controller_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From bc6cb12830756670b0d955d13fb2c2d33c80e494 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 17 Jan 2024 21:41:01 +0100 Subject: [PATCH 19/41] Fix for_type function in AssayClass --- app/controllers/isa_assays_controller.rb | 8 ++++---- app/models/assay_class.rb | 6 +++--- app/views/isa_assays/_form.html.erb | 4 ++-- config/default_data/assay_classes.yml | 7 ++++++- config/locales/en.yml | 1 + db/seeds/017_minimal_starter_isa_templates.seeds.rb | 7 ------- lib/tasks/seek_upgrades.rake | 2 +- test/fixtures/assay_classes.yml | 4 ++-- 8 files changed, 19 insertions(+), 20 deletions(-) diff --git a/app/controllers/isa_assays_controller.rb b/app/controllers/isa_assays_controller.rb index 8bdb451a7d..0a44ce8d18 100644 --- a/app/controllers/isa_assays_controller.rb +++ b/app/controllers/isa_assays_controller.rb @@ -7,9 +7,9 @@ class IsaAssaysController < ApplicationController def new if params[:is_assay_stream] - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.find_by(key: 'ASS').id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type('assay_stream').id } }) else - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.find_by(key: 'EXP').id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type('experimental').id } }) end end @@ -141,9 +141,9 @@ def set_up_instance_variable def find_requested_item if params[:is_assay_stream] - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.find_by(key: 'ASS').id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type('assay_stream').id } }) else - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.find_by(key: 'EXP').id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type('experimental').id } }) end @isa_assay.populate(params[:id]) diff --git a/app/models/assay_class.rb b/app/models/assay_class.rb index bc2a45728f..37a148de50 100644 --- a/app/models/assay_class.rb +++ b/app/models/assay_class.rb @@ -2,8 +2,8 @@ class AssayClass < ApplicationRecord # this returns an instance of AssayClass according to one of the types "experimental", "modelling" or "assay_stream" # if there is not a match nil is returned def self.for_type(type) - keys = { "experimental": 'EXP', "modelling": 'MODEL', 'assay_stream': 'ASS' } - AssayClass.find_by(key: keys[type]) + keys = { "experimental": 'EXP', "modelling": 'MODEL', "assay_stream": 'ASS' } + AssayClass.find_by(key: keys[type.to_sym]) end def self.experimental @@ -15,7 +15,7 @@ def self.modelling end def self.assay_stream - for_type('assay_stream') + for_type('assaystream') end def is_modelling? diff --git a/app/views/isa_assays/_form.html.erb b/app/views/isa_assays/_form.html.erb index 13a77cced1..13460bd080 100644 --- a/app/views/isa_assays/_form.html.erb +++ b/app/views/isa_assays/_form.html.erb @@ -7,11 +7,11 @@ if @isa_assay.assay.new_record? if params[:is_assay_stream] assay_position = 0 - assay_class_id = AssayClass.find_by(key: 'ASS')&.id + assay_class_id = AssayClass.for_type('assay_stream').id is_assay_stream = true else assay_position = params[:source_assay_id].nil? ? 1 : source_assay.position + 1 - assay_class_id = AssayClass.find_by(key: 'EXP')&.id + assay_class_id = AssayClass.for_type('experimental').id is_assay_stream = false end else diff --git a/config/default_data/assay_classes.yml b/config/default_data/assay_classes.yml index 94a08a2a99..56c2c6790e 100644 --- a/config/default_data/assay_classes.yml +++ b/config/default_data/assay_classes.yml @@ -6,4 +6,9 @@ experimental_assay_class: modelling_assay_class: id: 2 title: <%= I18n.t('assays.modelling_analysis') %> - key: MODEL \ No newline at end of file + key: MODEL + +assay_stream_class: + id: 3 + title: <%= I18n.t('assays.assay_stream') %> + key: ASS diff --git a/config/locales/en.yml b/config/locales/en.yml index ced43e1a21..8880ff79e8 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/db/seeds/017_minimal_starter_isa_templates.seeds.rb b/db/seeds/017_minimal_starter_isa_templates.seeds.rb index 2b1929fbfb..e78e477916 100644 --- a/db/seeds/017_minimal_starter_isa_templates.seeds.rb +++ b/db/seeds/017_minimal_starter_isa_templates.seeds.rb @@ -200,10 +200,3 @@ end puts 'Seeded minimal templates for organizing ISA JSON compliant experiments.' - -disable_authorization_checks do - AssayClass.find_or_create_by(title: 'Assay Stream', key: 'ASS', - description: 'Special type of class that is user in Single Page, specifying this is a container for a stream of assays') -end - -puts 'Seeded Extra Assay Class' diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index 6497f980b5..38990a2e7d 100644 --- a/lib/tasks/seek_upgrades.rake +++ b/lib/tasks/seek_upgrades.rake @@ -15,7 +15,7 @@ namespace :seek do rename_registered_sample_multiple_attribute_type remove_ontology_attribute_type db:seed:007_sample_attribute_types - db:seed:017_minimal_starter_isa_templates + db:seed:001_create_controlled_vocabs recognise_isa_json_compliant_items ] diff --git a/test/fixtures/assay_classes.yml b/test/fixtures/assay_classes.yml index fc730abd78..4a90f2e308 100644 --- a/test/fixtures/assay_classes.yml +++ b/test/fixtures/assay_classes.yml @@ -7,5 +7,5 @@ modelling_assay_class: key: MODEL assay_stream_class: - title: 'Assay Stream' - key: 'ASS' + title: <%= I18n.t('assays.assay_stream') %> + key: ASS From 0dc8b6c12b8390799ca6bdfa853afc50742e9659 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 18 Jan 2024 15:40:23 +0100 Subject: [PATCH 20/41] Fix functional tests about the isa json exporter --- lib/isa_exporter.rb | 11 +++++-- test/factories/assays.rb | 4 +-- .../investigations_controller_test.rb | 29 +++++++++++++------ 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/lib/isa_exporter.rb b/lib/isa_exporter.rb index f015bc5d8d..5cc20eeda1 100644 --- a/lib/isa_exporter.rb +++ b/lib/isa_exporter.rb @@ -184,9 +184,14 @@ def convert_assays(assay_stream) return unless assay_stream.can_view?(@current_user) return unless child_assays.all? { |a| a.can_view?(@current_user) } + 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 + all_sample_types = child_assays.map(&:sample_type).compact - # first_assay = assays.detect { |s| s.position.zero? } - # raise 'No assay could be found!' unless first_assay stream_name = "assays_#{child_assays.pluck(:id).join('_')}" assay_comments = convert_assay_comments(assay_stream) @@ -745,7 +750,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/test/factories/assays.rb b/test/factories/assays.rb index eab8a81d18..ffa20fd9ee 100644 --- a/test/factories/assays.rb +++ b/test/factories/assays.rb @@ -14,7 +14,7 @@ end factory(:assay_stream_class, class: AssayClass) do - title { 'Assay Stream' } + title { I18n.t('assays.assay_stream') } key { 'ASS' } description { "An assay stream class description" } end @@ -115,7 +115,7 @@ 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) + assay.study ||= FactoryBot.create(:isa_json_compliant_study, contributor: assay.contributor) end end 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'] } From 9ec5cfabd57fdbdb902889b045428efccdef413a Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 19 Jan 2024 12:23:51 +0100 Subject: [PATCH 21/41] Fix failing tests --- app/models/assay_class.rb | 2 +- app/views/assays/_buttons.html.erb | 2 +- test/factories/assays.rb | 2 +- test/functional/assays_controller_test.rb | 9 ++++++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/models/assay_class.rb b/app/models/assay_class.rb index 37a148de50..bd1dee611c 100644 --- a/app/models/assay_class.rb +++ b/app/models/assay_class.rb @@ -33,6 +33,6 @@ def is_assay_stream? # 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', 'ASS': 'Assay Stream' }[key] + { 'EXP': 'Experimental Assay', 'MODEL': 'Modelling Analysis', 'ASS': 'Assay Stream' }[key.to_sym] end end diff --git a/app/views/assays/_buttons.html.erb b/app/views/assays/_buttons.html.erb index e247754156..6c5b795cc5 100644 --- a/app/views/assays/_buttons.html.erb +++ b/app/views/assays/_buttons.html.erb @@ -2,7 +2,7 @@ if item.is_modelling? t('assays.modelling_analysis') elsif item.is_assay_stream? - 'Assay Stream' + t('assays.assay_stream') elsif Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? t('isa_assay') else diff --git a/test/factories/assays.rb b/test/factories/assays.rb index ffa20fd9ee..dfc298b213 100644 --- a/test/factories/assays.rb +++ b/test/factories/assays.rb @@ -105,7 +105,7 @@ 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 diff --git a/test/functional/assays_controller_test.rb b/test/functional/assays_controller_test.rb index ede6bcfa51..7e93bf1626 100644 --- a/test/functional/assays_controller_test.rb +++ b/test/functional/assays_controller_test.rb @@ -2012,9 +2012,12 @@ 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_stream = FactoryBot.create(:assay_stream, contributor: current_user.person) - assay1 = FactoryBot.create(:isa_json_compliant_assay, contributor: current_user.person, study: assay_stream.study, assay_stream:) - assay2 = FactoryBot.create(:isa_json_compliant_assay, contributor: current_user.person, study: assay_stream.study, assay_stream:) + 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:) + + assert_equal assay_stream.study, assay1.study get :show, params: { id: assay_stream } assert_response :success From 5445bc14c41794594c2f1afe0d2d5641720d84d4 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 19 Jan 2024 13:12:54 +0100 Subject: [PATCH 22/41] Fix unit tests for assays --- test/unit/assay_test.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/unit/assay_test.rb b/test/unit/assay_test.rb index 72907b408a..b6e3c41c3b 100644 --- a/test/unit/assay_test.rb +++ b/test/unit/assay_test.rb @@ -754,10 +754,14 @@ 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 From b31ab9f92317311f2907133633bae98a330fb7d6 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Fri, 19 Jan 2024 13:34:48 +0100 Subject: [PATCH 23/41] Fix functional tests for isa_assays --- app/models/assay.rb | 2 +- test/functional/isa_assays_controller_test.rb | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/models/assay.rb b/app/models/assay.rb index f7210d3235..e07718d35d 100644 --- a/app/models/assay.rb +++ b/app/models/assay.rb @@ -69,7 +69,7 @@ class Assay < ApplicationRecord enforce_authorization_on_association :study, :view def is_assay_stream? - assay_class.is_assay_stream? + assay_class&.is_assay_stream? end def previous_linked_assay_sample_type diff --git a/test/functional/isa_assays_controller_test.rb b/test/functional/isa_assays_controller_test.rb index f6b436f227..582be6bb94 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.for_type('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.for_type('experimental').id, policy_attributes: } isa_assay_attributes = { assay: assay_attributes.merge(emt_attributes), input_sample_type_id: sample_collection_sample_type.id, From 7d3afa81298771c39533e47620c624a33b31e852 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 22 Jan 2024 09:41:36 +0100 Subject: [PATCH 24/41] update model and controller to work with assay streams --- app/controllers/assays_controller.rb | 2 +- app/models/assay.rb | 28 ++++++++-- test/functional/assays_controller_test.rb | 4 +- test/unit/assay_test.rb | 65 +++++++++++++++++++++++ 4 files changed, 93 insertions(+), 6 deletions(-) diff --git a/app/controllers/assays_controller.rb b/app/controllers/assays_controller.rb index 0c88fabba4..e2a552fd4f 100644 --- a/app/controllers/assays_controller.rb +++ b/app/controllers/assays_controller.rb @@ -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/models/assay.rb b/app/models/assay.rb index e07718d35d..ab9e5b95e6 100644 --- a/app/models/assay.rb +++ b/app/models/assay.rb @@ -72,12 +72,34 @@ def is_assay_stream? assay_class&.is_assay_stream? end - def previous_linked_assay_sample_type - sample_type.sample_attributes.detect { |sa| sa.isa_tag.nil? && sa.title.include?('Input') }&.linked_sample_type + 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 + + 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 diff --git a/test/functional/assays_controller_test.rb b/test/functional/assays_controller_test.rb index 7e93bf1626..4ee3790a91 100644 --- a/test/functional/assays_controller_test.rb +++ b/test/functional/assays_controller_test.rb @@ -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 diff --git a/test/unit/assay_test.rb b/test/unit/assay_test.rb index b6e3c41c3b..f72a1e7d50 100644 --- a/test/unit/assay_test.rb +++ b/test/unit/assay_test.rb @@ -773,4 +773,69 @@ def new_valid_assay 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 From e9051532761625a2a8a24d0aa9fff568c0d9a118 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 22 Jan 2024 10:01:22 +0100 Subject: [PATCH 25/41] Add rake upgrade task --- lib/tasks/seek_upgrades.rake | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index 38990a2e7d..0797d29613 100644 --- a/lib/tasks/seek_upgrades.rake +++ b/lib/tasks/seek_upgrades.rake @@ -17,6 +17,7 @@ namespace :seek do 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 @@ -177,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) + .select(&:is_isa_json_compliant?) + .select { |a| a.assay_stream_id.nil? && (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.for_type('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 ## From b768b13470be3f4fcd123c261a9ef87947d7ae77 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 22 Jan 2024 15:13:29 +0100 Subject: [PATCH 26/41] Make title dependent on whether it's an assay stream or not --- app/views/isa_assays/edit.erb | 7 +++++-- app/views/isa_assays/new.html.erb | 6 +++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/views/isa_assays/edit.erb b/app/views/isa_assays/edit.erb index 6afac60429..476b164dcd 100644 --- a/app/views/isa_assays/edit.erb +++ b/app/views/isa_assays/edit.erb @@ -1,5 +1,8 @@ -

    Edit <%= t('isa_assay') %>

    - +<% if @isa_assay.assay.is_assay_stream? %> +

    Edit <%= t('assays.assay_stream') %>

    +<% else %> +

    Edit <%= t('isa_assay') %>

    +<% end %> <%= render partial: "templates/template_modal" -%>
    <%= 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..4af1f56a50 100644 --- a/app/views/isa_assays/new.html.erb +++ b/app/views/isa_assays/new.html.erb @@ -1,5 +1,9 @@
    -

    New <%=t("isa_assay")%>

    + <% if params[:is_assay_stream] %> +

    New <%=t("assays.assay_stream")%>

    + <% else %> +

    New <%=t("isa_assay")%>

    + <% end %>
    <%= render partial: "templates/template_modal" -%> From cf258e2dc69283c1bf3647c2d19ff403e88cddef Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Mon, 22 Jan 2024 15:14:44 +0100 Subject: [PATCH 27/41] Hide SOPs, Documents, Publications and discussion links if assay is assay stream --- app/views/isa_assays/_form.html.erb | 16 +++--- config/routes.rb | 2 +- test/functional/isa_assays_controller_test.rb | 56 +++++++++++++++++++ 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/app/views/isa_assays/_form.html.erb b/app/views/isa_assays/_form.html.erb index 13460bd080..eabfdf1abf 100644 --- a/app/views/isa_assays/_form.html.erb +++ b/app/views/isa_assays/_form.html.erb @@ -68,15 +68,17 @@ <%= assay_fields.hidden_field :assay_stream_id, value: assay_stream_id -%> <%= assay_fields.hidden_field :assay_class_id, value: assay_class_id -%> - <% if User.current_user -%> - <%= render partial: 'assets/manage_specific_attributes', locals:{f:assay_fields} if show_form_manage_specific_attributes? %> - <%= assay_fields.fancy_multiselect(:sops, other_projects_checkbox: true, name: "isa_assay[assay][sop_ids]")%> - <%= assay_fields.fancy_multiselect :publications, { other_projects_checkbox: true, name: "isa_assay[assay][publication_ids]" } %> - <%= assay_fields.fancy_multiselect :documents, { other_projects_checkbox: true, name: "isa_assay[assay][document_ids]" } %> - <% end -%> + <% unless is_assay_stream %> + <% if User.current_user -%> + <%= render partial: 'assets/manage_specific_attributes', locals:{f:assay_fields} if show_form_manage_specific_attributes? %> + <%= assay_fields.fancy_multiselect(:sops, other_projects_checkbox: true, name: "isa_assay[assay][sop_ids]")%> + <%= assay_fields.fancy_multiselect :publications, { other_projects_checkbox: true, name: "isa_assay[assay][publication_ids]" } %> + <%= assay_fields.fancy_multiselect :documents, { other_projects_checkbox: true, name: "isa_assay[assay][document_ids]" } %> + <% end -%> - <%= render :partial=> "assets/discussion_links_form", :locals=>{:resource => @isa_assay.assay} -%> + <%= render partial: "assets/discussion_links_form", locals: { resource: @isa_assay.assay } -%> + <% end -%> <% end -%> <%= f.hidden_field :input_sample_type_id, value: input_sample_type_id -%> 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/test/functional/isa_assays_controller_test.rb b/test/functional/isa_assays_controller_test.rb index 582be6bb94..1cf8c6b35f 100644 --- a/test/functional/isa_assays_controller_test.rb +++ b/test/functional/isa_assays_controller_test.rb @@ -225,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 From 9493fef09c4cfe01770bd6df554bbc05d8f00eaf Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 23 Jan 2024 14:15:39 +0100 Subject: [PATCH 28/41] move cv_modal back from the sample_types form to the new page --- app/views/isa_assays/new.html.erb | 3 +++ app/views/isa_studies/_sample_types_form.html.erb | 3 --- app/views/isa_studies/new.html.erb | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/views/isa_assays/new.html.erb b/app/views/isa_assays/new.html.erb index 4af1f56a50..07da9cf1e0 100644 --- a/app/views/isa_assays/new.html.erb +++ b/app/views/isa_assays/new.html.erb @@ -1,4 +1,7 @@
    + + <%= sample_controlled_vocab_model_dialog('cv-modal') %> + <% if params[:is_assay_stream] %>

    New <%=t("assays.assay_stream")%>

    <% else %> diff --git a/app/views/isa_studies/_sample_types_form.html.erb b/app/views/isa_studies/_sample_types_form.html.erb index b1d7bf1ebd..8889467eb3 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 %> diff --git a/app/views/isa_studies/new.html.erb b/app/views/isa_studies/new.html.erb index 38f08dc616..021e3e17f4 100644 --- a/app/views/isa_studies/new.html.erb +++ b/app/views/isa_studies/new.html.erb @@ -1,6 +1,10 @@ <% if Investigation.authorized_for('view').none? %> <%= button_link_to("New #{t('investigation')}", 'arrow_right', new_investigation_path) -%> <% else %> + + <%= sample_controlled_vocab_model_dialog('cv-modal') %> + +

    New <%=t('isa_study')%>

    <%= render :partial => "templates/template_modal" -%> From 9edc7f6801c173fc2bfa53f3f2f2cf99fbace6a0 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 23 Jan 2024 14:43:05 +0100 Subject: [PATCH 29/41] Hide assay type and technology type in case of isa json compliant assay --- app/views/assays/show.html.erb | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/app/views/assays/show.html.erb b/app/views/assays/show.html.erb index 6f2c697b1b..6c31f71d60 100644 --- a/app/views/assays/show.html.erb +++ b/app/views/assays/show.html.erb @@ -47,16 +47,18 @@ <%= "Assay position" %>: <%= @assay.position %>

    -

    - <%= assay_type_text -%>: - <%= link_to_assay_type(@assay) -%> -

    - <% unless @assay.is_modelling? -%> -

    - Technology type: - <%= link_to_technology_type(@assay) -%> + <% unless @assay.is_isa_json_compliant? %> +

    + <%= assay_type_text -%>: + <%= link_to_assay_type(@assay) -%>

    - <% end -%> + <% unless @assay.is_modelling? -%> +

    + Technology type: + <%= link_to_technology_type(@assay) -%> +

    + <% end -%> + <% end %> <% if Seek::Config.organisms_enabled %> <%= list_assay_organisms("Organisms", @assay.assay_organisms, { :id => "organism" }) %> From 67c1778aaf330ffb6e70bcbcc0c2d9257d1677fe Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 23 Jan 2024 14:59:41 +0100 Subject: [PATCH 30/41] Add assay stream and related child assays to show --- app/views/assays/show.html.erb | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/app/views/assays/show.html.erb b/app/views/assays/show.html.erb index 6c31f71d60..192f49da35 100644 --- a/app/views/assays/show.html.erb +++ b/app/views/assays/show.html.erb @@ -70,12 +70,30 @@ <% if Seek::Config.isa_json_compliance_enabled %>

    - <%= "Is ISA-JSON compliant" %>: + Is ISA-JSON compliant: <%= @assay.is_isa_json_compliant? %>

    <%= render partial: 'isa_studies/applied_templates', locals: { resource: @assay } -%> <% end %> + <% if @assay.is_assay_stream? %> +

    + Child assays: +

      + <% @assay.child_assays.map do |ca| %> +
    • + <%= link_to ca.title, ca %> +
    • + <% end %> +
    +

    + <% else %> +

    + <%= t('assays.assay_stream') %>: + <%= link_to @assay.assay_stream.title, @assay.assay_stream %> +

    + <% end %> +
    From 37dbaaef37418036261c68f7fec1a71604d35cb1 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 23 Jan 2024 15:31:18 +0100 Subject: [PATCH 31/41] Set assay filename in ISA JSON to the assay stream name --- lib/isa_exporter.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/isa_exporter.rb b/lib/isa_exporter.rb index 5cc20eeda1..5d5a3ff288 100644 --- a/lib/isa_exporter.rb +++ b/lib/isa_exporter.rb @@ -193,12 +193,13 @@ def convert_assays(assay_stream) all_sample_types = child_assays.map(&:sample_type).compact - stream_name = "assays_#{child_assays.pluck(:id).join('_')}" + # 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/#{child_assays.pluck(:id).join('_')}" From 31a5ade77c4efdeef923fe12e6c5b60bb6410d2b Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Tue, 23 Jan 2024 16:16:51 +0100 Subject: [PATCH 32/41] Fix failing integration test --- app/views/assays/show.html.erb | 36 +++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/app/views/assays/show.html.erb b/app/views/assays/show.html.erb index 192f49da35..9f85fc1b39 100644 --- a/app/views/assays/show.html.erb +++ b/app/views/assays/show.html.erb @@ -76,22 +76,26 @@ <%= render partial: 'isa_studies/applied_templates', locals: { resource: @assay } -%> <% end %> - <% if @assay.is_assay_stream? %> -

    - Child assays: -

      - <% @assay.child_assays.map do |ca| %> -
    • - <%= link_to ca.title, ca %> -
    • - <% end %> -
    -

    - <% else %> -

    - <%= t('assays.assay_stream') %>: - <%= link_to @assay.assay_stream.title, @assay.assay_stream %> -

    + <% if @assay.is_isa_json_compliant? %> + <% if @assay.is_assay_stream? %> +

    + Child assays: +

      + <% @assay.child_assays.map do |ca| %> + <% unless @assay.child_assays.blank? %> +
    • + <%= link_to ca.title, ca %> +
    • + <% end %> + <% end %> +
    +

    + <% else %> +

    + <%= t('assays.assay_stream') %>: + <%= link_to @assay.assay_stream.title, @assay.assay_stream %> +

    + <% end %> <% end %> From 8855f09b9b812fe65a7df118eb2374c96098099c Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 24 Jan 2024 16:55:42 +0100 Subject: [PATCH 33/41] Abstact AssayClass keys to constants --- app/controllers/assays_controller.rb | 2 +- app/controllers/isa_assays_controller.rb | 8 ++++---- app/models/assay_class.rb | 20 +++++++++---------- app/views/isa_assays/_form.html.erb | 4 ++-- config/default_data/assay_classes.yml | 2 +- lib/seek/isa/assay_class.rb | 17 ++++++++++++++++ lib/seek/ontologies/synchronize.rb | 2 +- lib/seek/openbis/seek_util.rb | 6 +++--- lib/seek/projects/population.rb | 10 +++++----- test/factories/assays.rb | 6 +++--- test/fixtures/assay_classes.yml | 2 +- test/functional/isa_assays_controller_test.rb | 4 ++-- test/unit/assay_class_test.rb | 8 ++++---- .../ontology_synchronization_test.rb | 4 ++-- 14 files changed, 56 insertions(+), 39 deletions(-) create mode 100644 lib/seek/isa/assay_class.rb diff --git a/app/controllers/assays_controller.rb b/app/controllers/assays_controller.rb index e2a552fd4f..900ddef19e 100644 --- a/app/controllers/assays_controller.rb +++ b/app/controllers/assays_controller.rb @@ -82,7 +82,7 @@ def edit end def create - params[:assay_class_id] ||= AssayClass.for_type('experimental').id + params[:assay_class_id] ||= AssayClass.for_type(Seek::ISA::AssayClass::EXP).id @assay = Assay.new(assay_params) update_assay_organisms @assay, params diff --git a/app/controllers/isa_assays_controller.rb b/app/controllers/isa_assays_controller.rb index 0a44ce8d18..7771d17b3e 100644 --- a/app/controllers/isa_assays_controller.rb +++ b/app/controllers/isa_assays_controller.rb @@ -7,9 +7,9 @@ class IsaAssaysController < ApplicationController def new if params[:is_assay_stream] - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type('assay_stream').id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::STREAM).id } }) else - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type('experimental').id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::EXP).id } }) end end @@ -141,9 +141,9 @@ def set_up_instance_variable def find_requested_item if params[:is_assay_stream] - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type('assay_stream').id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::STREAM).id } }) else - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type('experimental').id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::EXP).id } }) end @isa_assay.populate(params[:id]) diff --git a/app/models/assay_class.rb b/app/models/assay_class.rb index bd1dee611c..bb53cdec8e 100644 --- a/app/models/assay_class.rb +++ b/app/models/assay_class.rb @@ -1,38 +1,38 @@ class AssayClass < ApplicationRecord - # this returns an instance of AssayClass according to one of the types "experimental", "modelling" or "assay_stream" + # 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) - keys = { "experimental": 'EXP', "modelling": 'MODEL', "assay_stream": 'ASS' } - AssayClass.find_by(key: keys[type.to_sym]) + AssayClass.find_by(key: type) end def self.experimental - for_type('experimental') + for_type(Seek::ISA::AssayClass::EXP) end def self.modelling - for_type('modelling') + for_type(Seek::ISA::AssayClass::MODEL) + end def self.assay_stream - for_type('assaystream') + for_type(Seek::ISA::AssayClass::STREAM) end def is_modelling? - key == 'MODEL' + key == Seek::ISA::AssayClass::MODEL end def is_experimental? - key == 'EXP' + key == Seek::ISA::AssayClass::EXP end def is_assay_stream? - key == 'ASS' + key == Seek::ISA::AssayClass::STREAM end # 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', 'ASS': 'Assay Stream' }[key.to_sym] + { 'EXP': 'Experimental Assay', 'MODEL': 'Modelling Analysis', 'STREAM': 'Assay Stream' }[key.to_sym] end end diff --git a/app/views/isa_assays/_form.html.erb b/app/views/isa_assays/_form.html.erb index eabfdf1abf..06b9a057ad 100644 --- a/app/views/isa_assays/_form.html.erb +++ b/app/views/isa_assays/_form.html.erb @@ -7,11 +7,11 @@ if @isa_assay.assay.new_record? if params[:is_assay_stream] assay_position = 0 - assay_class_id = AssayClass.for_type('assay_stream').id + assay_class_id = AssayClass.for_type(Seek::ISA::AssayClass::STREAM).id is_assay_stream = true else assay_position = params[:source_assay_id].nil? ? 1 : source_assay.position + 1 - assay_class_id = AssayClass.for_type('experimental').id + assay_class_id = AssayClass.for_type(Seek::ISA::AssayClass::EXP).id is_assay_stream = false end else diff --git a/config/default_data/assay_classes.yml b/config/default_data/assay_classes.yml index 56c2c6790e..8b01bc6ad3 100644 --- a/config/default_data/assay_classes.yml +++ b/config/default_data/assay_classes.yml @@ -11,4 +11,4 @@ modelling_assay_class: assay_stream_class: id: 3 title: <%= I18n.t('assays.assay_stream') %> - key: ASS + key: STREAM diff --git a/lib/seek/isa/assay_class.rb b/lib/seek/isa/assay_class.rb new file mode 100644 index 0000000000..9be0b02dce --- /dev/null +++ b/lib/seek/isa/assay_class.rb @@ -0,0 +1,17 @@ +module Seek + module ISA + module AssayClass + # Creates constants based on the AssayClass key attributes + # Example: AssayClass key 'EXP' can be represented by Seek::ISA:AssayClass::EXP + ALL_TYPES = %w[EXP MODEL STREAM] + + ALL_TYPES.each do |type| + AssayClass.const_set(type.underscore.upcase, type) + end + + def self.valid?(value) + ALL_TYPES.include?(value) + end + end + end +end diff --git a/lib/seek/ontologies/synchronize.rb b/lib/seek/ontologies/synchronize.rb index e15f0119e8..fd3b83d4c4 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.for_type(Seek::ISA::AssayClass::MODEL) : AssayClass.for_type(Seek::ISA::AssayClass::EXP) 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..f6303e2e18 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.for_type(Seek::ISA::AssayClass::EXP).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.for_type(Seek::ISA::AssayClass::EXP).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..f0fa3d1e20 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.for_type(Seek::ISA::AssayClass::EXP) 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/test/factories/assays.rb b/test/factories/assays.rb index dfc298b213..ad88ee763f 100644 --- a/test/factories/assays.rb +++ b/test/factories/assays.rb @@ -4,18 +4,18 @@ factory(:modelling_assay_class, class: AssayClass) do title { I18n.t('assays.modelling_analysis') } - key { 'MODEL' } + key { Seek::ISA::AssayClass::MODEL } end factory(:experimental_assay_class, class: AssayClass) do title { I18n.t('assays.experimental_assay') } - key { 'EXP' } + key { Seek::ISA::AssayClass::EXP } description { "An experimental assay class description" } end factory(:assay_stream_class, class: AssayClass) do title { I18n.t('assays.assay_stream') } - key { 'ASS' } + key { Seek::ISA::AssayClass::STREAM } description { "An assay stream class description" } end diff --git a/test/fixtures/assay_classes.yml b/test/fixtures/assay_classes.yml index 4a90f2e308..cf87a8f7d1 100644 --- a/test/fixtures/assay_classes.yml +++ b/test/fixtures/assay_classes.yml @@ -8,4 +8,4 @@ modelling_assay_class: assay_stream_class: title: <%= I18n.t('assays.assay_stream') %> - key: ASS + key: STREAM diff --git a/test/functional/isa_assays_controller_test.rb b/test/functional/isa_assays_controller_test.rb index 1cf8c6b35f..665aa54dcc 100644 --- a/test/functional/isa_assays_controller_test.rb +++ b/test/functional/isa_assays_controller_test.rb @@ -45,7 +45,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', - assay_class_id: AssayClass.for_type('experimental').id, + assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::EXP).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, @@ -189,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: AssayClass.for_type('experimental').id, policy_attributes: } + position: 0, assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::EXP).id, policy_attributes: } isa_assay_attributes = { assay: assay_attributes.merge(emt_attributes), input_sample_type_id: sample_collection_sample_type.id, diff --git a/test/unit/assay_class_test.rb b/test/unit/assay_class_test.rb index 3a3d002f6d..c78b3ec6c1 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.for_type(Seek::ISA::AssayClass::EXP).key + assert_equal 'MODEL', AssayClass.for_type(Seek::ISA::AssayClass::MODEL).key end test 'is_modelling?' do - assert AssayClass.for_type('modelling').is_modelling? - refute AssayClass.for_type('experimental').is_modelling? + assert AssayClass.for_type(Seek::ISA::AssayClass::MODEL).is_modelling? + refute AssayClass.for_type(Seek::ISA::AssayClass::EXP).is_modelling? end end diff --git a/test/unit/ontologies/ontology_synchronization_test.rb b/test/unit/ontologies/ontology_synchronization_test.rb index 50aeb1c446..c8e5836f14 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.for_type(Seek::ISA::AssayClass::MODEL) FactoryBot.create(:modelling_assay_class) end - unless AssayClass.for_type('experimental') + unless AssayClass.for_type(Seek::ISA::AssayClass::EXP) FactoryBot.create(:experimental_assay_class) end end From 518ebc2656070576a0a1261890ac899c5bdca519 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 24 Jan 2024 16:56:26 +0100 Subject: [PATCH 34/41] Let the database do the job instead ruby in memory --- lib/tasks/seek_upgrades.rake | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index 0797d29613..b936eda04b 100644 --- a/lib/tasks/seek_upgrades.rake +++ b/lib/tasks/seek_upgrades.rake @@ -186,15 +186,15 @@ namespace :seek do # 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) - .select(&:is_isa_json_compliant?) - .select { |a| a.assay_stream_id.nil? && (a.previous_linked_sample_type == a.study.sample_types.second) } + 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.for_type('assay_stream').id, + assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::STREAM).id, contributor: fas.contributor, position: 0) From a6089802363ea89d2ff03078e38f7431c3c8bca5 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Wed, 24 Jan 2024 17:23:08 +0100 Subject: [PATCH 35/41] Fix failing tests --- test/functional/assays_controller_test.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/functional/assays_controller_test.rb b/test/functional/assays_controller_test.rb index 4ee3790a91..c4e6bc2f7e 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: Seek::ISA::AssayClass::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: Seek::ISA::AssayClass::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: Seek::ISA::AssayClass::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: Seek::ISA::AssayClass::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: Seek::ISA::AssayClass::MODEL } assert_response :success assert_select 'label', text: /Biological problem addressed/i assert_select 'select#assay_assay_type_uri' do From 4c8c9cd0a7b088195b40d65ff1a4d2efb890ee1d Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 25 Jan 2024 08:57:04 +0100 Subject: [PATCH 36/41] Use constants instead of keys as string --- app/models/assay_class.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/assay_class.rb b/app/models/assay_class.rb index bb53cdec8e..fb98ad0bb3 100644 --- a/app/models/assay_class.rb +++ b/app/models/assay_class.rb @@ -33,6 +33,6 @@ def is_assay_stream? # 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', 'STREAM': 'Assay Stream' }[key.to_sym] + { "#{Seek::ISA::AssayClass::EXP}": 'Experimental Assay', "#{Seek::ISA::AssayClass::MODEL}": 'Modelling Analysis', "#{Seek::ISA::AssayClass::STREAM}": 'Assay Stream' }[key.to_sym] end end From f3a0251eadd2351c1d4a4016454af73805c55993 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 25 Jan 2024 09:04:15 +0100 Subject: [PATCH 37/41] Use the assay class's long_key method to determin the button text --- app/views/assays/_buttons.html.erb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/views/assays/_buttons.html.erb b/app/views/assays/_buttons.html.erb index 6c5b795cc5..72c59241fe 100644 --- a/app/views/assays/_buttons.html.erb +++ b/app/views/assays/_buttons.html.erb @@ -1,12 +1,10 @@ <% assay_word ||= - if item.is_modelling? - t('assays.modelling_analysis') - elsif item.is_assay_stream? + 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.assay') + t("assays.#{item.assay_class.long_key.delete(' ').underscore}") end %> <%= render :partial => "subscriptions/subscribe", :locals => {:object => item} %> From a8316e2226c4e2a02f69765ae52e72378bc2d1dd Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 1 Feb 2024 09:20:53 +0100 Subject: [PATCH 38/41] Remove the AssayClass constants --- app/controllers/assays_controller.rb | 4 ++-- app/controllers/isa_assays_controller.rb | 8 ++++---- app/models/assay_class.rb | 14 +++++++------- app/views/isa_assays/_form.html.erb | 4 ++-- lib/seek/isa/assay_class.rb | 17 ----------------- lib/seek/ontologies/synchronize.rb | 2 +- lib/seek/openbis/seek_util.rb | 4 ++-- lib/seek/projects/population.rb | 2 +- lib/tasks/seek_upgrades.rake | 2 +- test/factories/assays.rb | 6 +++--- test/functional/assays_controller_test.rb | 10 +++++----- test/functional/isa_assays_controller_test.rb | 4 ++-- test/unit/assay_class_test.rb | 8 ++++---- .../ontologies/ontology_synchronization_test.rb | 4 ++-- 14 files changed, 36 insertions(+), 53 deletions(-) delete mode 100644 lib/seek/isa/assay_class.rb diff --git a/app/controllers/assays_controller.rb b/app/controllers/assays_controller.rb index 900ddef19e..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(Seek::ISA::AssayClass::EXP).id + params[:assay_class_id] ||= AssayClass.experimental.id @assay = Assay.new(assay_params) update_assay_organisms @assay, params diff --git a/app/controllers/isa_assays_controller.rb b/app/controllers/isa_assays_controller.rb index 7771d17b3e..cc0c8128ed 100644 --- a/app/controllers/isa_assays_controller.rb +++ b/app/controllers/isa_assays_controller.rb @@ -7,9 +7,9 @@ class IsaAssaysController < ApplicationController def new if params[:is_assay_stream] - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::STREAM).id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.assay_stream.id } }) else - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::EXP).id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.experimental.id } }) end end @@ -141,9 +141,9 @@ def set_up_instance_variable def find_requested_item if params[:is_assay_stream] - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::STREAM).id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.assay_stream.id } }) else - @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::EXP).id } }) + @isa_assay = IsaAssay.new({ assay: { assay_class_id: AssayClass.experimental.id } }) end @isa_assay.populate(params[:id]) diff --git a/app/models/assay_class.rb b/app/models/assay_class.rb index fb98ad0bb3..42298a17dd 100644 --- a/app/models/assay_class.rb +++ b/app/models/assay_class.rb @@ -6,33 +6,33 @@ def self.for_type(type) end def self.experimental - for_type(Seek::ISA::AssayClass::EXP) + for_type('EXP') end def self.modelling - for_type(Seek::ISA::AssayClass::MODEL) + for_type('MODEL') end def self.assay_stream - for_type(Seek::ISA::AssayClass::STREAM) + for_type('STREAM') end def is_modelling? - key == Seek::ISA::AssayClass::MODEL + key == 'MODEL' end def is_experimental? - key == Seek::ISA::AssayClass::EXP + key == 'EXP' end def is_assay_stream? - key == Seek::ISA::AssayClass::STREAM + key == 'STREAM' end # 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 - { "#{Seek::ISA::AssayClass::EXP}": 'Experimental Assay', "#{Seek::ISA::AssayClass::MODEL}": 'Modelling Analysis', "#{Seek::ISA::AssayClass::STREAM}": 'Assay Stream' }[key.to_sym] + { 'EXP': 'Experimental Assay', 'MODEL': 'Modelling Analysis', 'STREAM': 'Assay Stream' }[key.to_sym] end end diff --git a/app/views/isa_assays/_form.html.erb b/app/views/isa_assays/_form.html.erb index 06b9a057ad..cc707ccf20 100644 --- a/app/views/isa_assays/_form.html.erb +++ b/app/views/isa_assays/_form.html.erb @@ -7,11 +7,11 @@ if @isa_assay.assay.new_record? if params[:is_assay_stream] assay_position = 0 - assay_class_id = AssayClass.for_type(Seek::ISA::AssayClass::STREAM).id + assay_class_id = AssayClass.assay_stream.id is_assay_stream = true else assay_position = params[:source_assay_id].nil? ? 1 : source_assay.position + 1 - assay_class_id = AssayClass.for_type(Seek::ISA::AssayClass::EXP).id + assay_class_id = AssayClass.experimental.id is_assay_stream = false end else diff --git a/lib/seek/isa/assay_class.rb b/lib/seek/isa/assay_class.rb deleted file mode 100644 index 9be0b02dce..0000000000 --- a/lib/seek/isa/assay_class.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Seek - module ISA - module AssayClass - # Creates constants based on the AssayClass key attributes - # Example: AssayClass key 'EXP' can be represented by Seek::ISA:AssayClass::EXP - ALL_TYPES = %w[EXP MODEL STREAM] - - ALL_TYPES.each do |type| - AssayClass.const_set(type.underscore.upcase, type) - end - - def self.valid?(value) - ALL_TYPES.include?(value) - end - end - end -end diff --git a/lib/seek/ontologies/synchronize.rb b/lib/seek/ontologies/synchronize.rb index fd3b83d4c4..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(Seek::ISA::AssayClass::MODEL) : AssayClass.for_type(Seek::ISA::AssayClass::EXP) + 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 f6303e2e18..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(Seek::ISA::AssayClass::EXP).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(Seek::ISA::AssayClass::EXP).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 diff --git a/lib/seek/projects/population.rb b/lib/seek/projects/population.rb index f0fa3d1e20..faeb03241b 100644 --- a/lib/seek/projects/population.rb +++ b/lib/seek/projects/population.rb @@ -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(Seek::ISA::AssayClass::EXP) + assay.assay_class = AssayClass.experimental set_assignees(assay, r, assignee_indices) diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake index b936eda04b..e9b1ae3c15 100644 --- a/lib/tasks/seek_upgrades.rake +++ b/lib/tasks/seek_upgrades.rake @@ -194,7 +194,7 @@ namespace :seek do stream_name = "Assay Stream - #{UUID.generate}" assay_stream = Assay.create(title: stream_name, study_id: fas.study_id, - assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::STREAM).id, + assay_class_id: AssayClass.assay_stream.id, contributor: fas.contributor, position: 0) diff --git a/test/factories/assays.rb b/test/factories/assays.rb index ad88ee763f..24f95f1ae0 100644 --- a/test/factories/assays.rb +++ b/test/factories/assays.rb @@ -4,18 +4,18 @@ factory(:modelling_assay_class, class: AssayClass) do title { I18n.t('assays.modelling_analysis') } - key { Seek::ISA::AssayClass::MODEL } + key { 'MODEL' } end factory(:experimental_assay_class, class: AssayClass) do title { I18n.t('assays.experimental_assay') } - key { Seek::ISA::AssayClass::EXP } + key { 'EXP' } description { "An experimental assay class description" } end factory(:assay_stream_class, class: AssayClass) do title { I18n.t('assays.assay_stream') } - key { Seek::ISA::AssayClass::STREAM } + key { 'STREAM' } description { "An assay stream class description" } end diff --git a/test/functional/assays_controller_test.rb b/test/functional/assays_controller_test.rb index c4e6bc2f7e..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: Seek::ISA::AssayClass::EXP } + 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: Seek::ISA::AssayClass::MODEL } + 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: Seek::ISA::AssayClass::EXP } + 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: Seek::ISA::AssayClass::EXP } + 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: Seek::ISA::AssayClass::MODEL } + get :new, params: { class: 'MODEL' } assert_response :success assert_select 'label', text: /Biological problem addressed/i assert_select 'select#assay_assay_type_uri' do diff --git a/test/functional/isa_assays_controller_test.rb b/test/functional/isa_assays_controller_test.rb index 665aa54dcc..1212dd21d1 100644 --- a/test/functional/isa_assays_controller_test.rb +++ b/test/functional/isa_assays_controller_test.rb @@ -45,7 +45,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', - assay_class_id: AssayClass.for_type(Seek::ISA::AssayClass::EXP).id, + 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, @@ -189,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: AssayClass.for_type(Seek::ISA::AssayClass::EXP).id, 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, diff --git a/test/unit/assay_class_test.rb b/test/unit/assay_class_test.rb index c78b3ec6c1..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(Seek::ISA::AssayClass::EXP).key - assert_equal 'MODEL', AssayClass.for_type(Seek::ISA::AssayClass::MODEL).key + assert_equal 'EXP', AssayClass.experimental.key + assert_equal 'MODEL', AssayClass.modelling.key end test 'is_modelling?' do - assert AssayClass.for_type(Seek::ISA::AssayClass::MODEL).is_modelling? - refute AssayClass.for_type(Seek::ISA::AssayClass::EXP).is_modelling? + assert AssayClass.modelling.is_modelling? + refute AssayClass.experimental.is_modelling? end end diff --git a/test/unit/ontologies/ontology_synchronization_test.rb b/test/unit/ontologies/ontology_synchronization_test.rb index c8e5836f14..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(Seek::ISA::AssayClass::MODEL) + unless AssayClass.modelling FactoryBot.create(:modelling_assay_class) end - unless AssayClass.for_type(Seek::ISA::AssayClass::EXP) + unless AssayClass.experimental FactoryBot.create(:experimental_assay_class) end end From cd06baa6b8cceedbc928f8dac2892586a225ac87 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 1 Feb 2024 09:36:07 +0100 Subject: [PATCH 39/41] Move long key mapping to a constant outside --- app/models/assay_class.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/assay_class.rb b/app/models/assay_class.rb index 42298a17dd..5e8940f9ee 100644 --- a/app/models/assay_class.rb +++ b/app/models/assay_class.rb @@ -30,9 +30,11 @@ 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', 'STREAM': 'Assay Stream' }[key.to_sym] + LONG_KEYS[key.to_sym] end end From 14089f6ed19079e01c3b0af4f79fcea2d5948aa1 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 1 Feb 2024 10:09:34 +0100 Subject: [PATCH 40/41] Use the definition instead of manual text setting --- app/views/studies/_buttons.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/studies/_buttons.html.erb b/app/views/studies/_buttons.html.erb index 4b63c58c86..a308561224 100644 --- a/app/views/studies/_buttons.html.erb +++ b/app/views/studies/_buttons.html.erb @@ -21,7 +21,7 @@ <% if item.can_edit? -%> <% if Seek::Config.isa_json_compliance_enabled && item.is_isa_json_compliant? %> <% if item&.sample_types.present? %> - <%= button_link_to("Design #{t('assay')} Stream", 'new', new_isa_assay_path(study_id: item.id, single_page: params[:single_page], is_assay_stream: true)) %> + <%= button_link_to("Design #{t('assays.assay_stream')}", 'new', new_isa_assay_path(study_id: item.id, single_page: params[:single_page], is_assay_stream: true)) %> <% end -%> <% else -%> <%= add_new_item_to_dropdown(item) %> From 6fdd31fee6dcd2095187536a0d6204b035943065 Mon Sep 17 00:00:00 2001 From: Kevin De Pelseneer Date: Thu, 1 Feb 2024 10:12:05 +0100 Subject: [PATCH 41/41] Use assay_streams method instead of select statement --- lib/treeview_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/treeview_builder.rb b/lib/treeview_builder.rb index 3996b2da2b..6f89ddf2d3 100644 --- a/lib/treeview_builder.rb +++ b/lib/treeview_builder.rb @@ -16,7 +16,7 @@ def build_tree_data @project.investigations.map do |investigation| if investigation.is_isa_json_compliant? investigation.studies.map do |study| - assay_stream_items = study.assays.select { |assay| assay.is_assay_stream? }.map do |assay_stream| + 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