From eb94a7a6f0f965732fee41544fc134234c8a784c Mon Sep 17 00:00:00 2001
From: Finn Bacall
Date: Fri, 6 Oct 2023 12:41:42 +0100
Subject: [PATCH 001/272] Endpoint to get workflow classes as JSON-LD
In the format that should appear in a Workflow RO-Crate
---
.../workflow_classes_controller.rb | 4 +++
.../workflow_classes_controller_test.rb | 34 +++++++++++++++++++
2 files changed, 38 insertions(+)
diff --git a/app/controllers/workflow_classes_controller.rb b/app/controllers/workflow_classes_controller.rb
index 9097fd341e..2539a5617f 100644
--- a/app/controllers/workflow_classes_controller.rb
+++ b/app/controllers/workflow_classes_controller.rb
@@ -50,6 +50,10 @@ def edit
def index
@workflow_classes = WorkflowClass.order(extractor: :desc).all
+ respond_to do |format|
+ format.html
+ format.jsonld { render json: @workflow_classes.map(&:ro_crate_metadata), adapter: :attributes }
+ end
end
private
diff --git a/test/functional/workflow_classes_controller_test.rb b/test/functional/workflow_classes_controller_test.rb
index 9a115efc52..79519f0841 100644
--- a/test/functional/workflow_classes_controller_test.rb
+++ b/test/functional/workflow_classes_controller_test.rb
@@ -32,6 +32,40 @@ class WorkflowClassesControllerTest < ActionController::TestCase
workflow_class_avatar_path(user_added_3, user_added_3.avatar, size: '32x32'), count: 1
end
+ test 'get index as json-ld' do
+ person = FactoryBot.create(:person)
+ disable_authorization_checks do
+ FactoryBot.create(:cwl_workflow_class)
+ FactoryBot.create(:galaxy_workflow_class)
+ FactoryBot.create(:nextflow_workflow_class)
+ WorkflowClass.create!(title: 'My Class', key: 'mine', contributor: person)
+ end
+
+ login_as(person)
+
+ get :index, format: :jsonld
+
+ assert_response :success
+ classes = JSON.parse(response.body)
+ assert_equal 4, classes.length
+
+ cwl = classes.detect { |c| c['@id'] == '#cwl' }
+ assert cwl
+ assert_equal 'Common Workflow Language', cwl['name']
+ assert_equal 'ComputerLanguage', cwl['@type']
+ assert_equal 'CWL', cwl['alternateName']
+ assert_equal({ '@id' => 'https://w3id.org/cwl/v1.0/' }, cwl['identifier'])
+ assert_equal({ '@id' =>'https://www.commonwl.org/' }, cwl['url'])
+
+ assert classes.detect { |c| c['@id'] == '#galaxy' }
+ assert classes.detect { |c| c['@id'] == '#nextflow' }
+
+ user_added = classes.detect { |c| c['@id'] == '#mine' }
+ assert_equal 'My Class', user_added['name']
+ refute user_added.key?('identifier')
+ refute user_added.key?('url')
+ end
+
test 'admin can edit any workflow class' do
person = FactoryBot.create(:person)
core_type, c1, c2 = nil
From 86279fc1d5008927bfef146f5d1ae274318068cd Mon Sep 17 00:00:00 2001
From: Kevin De Pelseneer
Date: Mon, 15 Jan 2024 10:50:09 +0100
Subject: [PATCH 002/272] 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 003/272] 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 004/272] 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 005/272] 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 006/272] 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 007/272] 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 008/272] 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 009/272] 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 010/272] 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 011/272] 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 012/272] 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 013/272] 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 014/272] 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 015/272] 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 016/272] 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 017/272] 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 018/272] 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 019/272] 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 020/272] 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 021/272] 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 1cc9b45403597198fc1c7df9b44f3b0d68c76ab5 Mon Sep 17 00:00:00 2001
From: Kevin De Pelseneer
Date: Fri, 19 Jan 2024 10:19:34 +0100
Subject: [PATCH 022/272] Move instanceName to the window-level and declare it
in both assay_design and study_design
---
app/assets/javascripts/single_page/dynamic_table.js.erb | 2 +-
app/views/isa_assays/_assay_design.html.erb | 1 +
app/views/isa_studies/_study_design.html.erb | 1 +
app/views/single_pages/show.html.erb | 2 +-
4 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/app/assets/javascripts/single_page/dynamic_table.js.erb b/app/assets/javascripts/single_page/dynamic_table.js.erb
index c344d5e762..e3032c5cfe 100644
--- a/app/assets/javascripts/single_page/dynamic_table.js.erb
+++ b/app/assets/javascripts/single_page/dynamic_table.js.erb
@@ -83,7 +83,7 @@ const handleSelect = (e) => {
if (cellData == "#HIDDEN") $j(td).addClass("disabled");
};
// Changes the id header to an instance id
- if (c.title == "id") c.title = instanceName + " id";
+ if (c.title == "id") c.title = window.instanceName + " id";
});
// Retrieve the column index of the multi-input cells (select2 items)
// if column has a multi-input cell, it adds the index to the t array (=accumulator)
diff --git a/app/views/isa_assays/_assay_design.html.erb b/app/views/isa_assays/_assay_design.html.erb
index 98415832d7..9240363691 100644
--- a/app/views/isa_assays/_assay_design.html.erb
+++ b/app/views/isa_assays/_assay_design.html.erb
@@ -42,6 +42,7 @@
<% end %>