Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 1656 create an assay between two existing assays #1735

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f994a36
Replace `is_single_page_assay?` by `is_isa_json_compliant?`
kdp-cloud Jan 23, 2024
a1faa5d
Add fix linkage to isa assays after a new one is created.
kdp-cloud Jan 24, 2024
f8c2200
Order assays by position in assay_stream
kdp-cloud Jan 24, 2024
3e0237b
Create function to find the input attribute
kdp-cloud Jan 25, 2024
8b5db61
Add functions to find the next and previous linked sample type
kdp-cloud Jan 25, 2024
6b06477
Simplify dynamic table helper
kdp-cloud Jan 26, 2024
ce06b7c
Fix sample type linkage when inserting new assays
kdp-cloud Jan 26, 2024
69a4ef6
Rearrange assay positions when inserting or deleting assays
kdp-cloud Jan 26, 2024
61c374f
Move code to private section
kdp-cloud Jan 26, 2024
bdd97c4
simplify assay linkage when deleting
kdp-cloud Jan 26, 2024
af66b53
Split functionality over assay and isa assay
kdp-cloud Jan 26, 2024
e1c7903
Delete ST first, fix linkage later
kdp-cloud Jan 29, 2024
f4b6783
Simplify deletion of linked sampletypes in studies
kdp-cloud Jan 29, 2024
532c9f3
Fix existing assays controller tests
kdp-cloud Jan 29, 2024
88c2053
Fix existing studies_controller tests
kdp-cloud Jan 29, 2024
50dfe2a
Correct sample type linkage
kdp-cloud Jan 30, 2024
4a6c0eb
Fix existing tests
kdp-cloud Jan 30, 2024
8366b9e
Write test for inserting new assay between assay stream and first assay
kdp-cloud Jan 30, 2024
78c3a7e
Write test for inserting an assay between two experimental assays
kdp-cloud Jan 30, 2024
fc4c991
Actually better as a before_action since the sample_type is created b…
kdp-cloud Jan 30, 2024
d2d1a90
Change the button text when inserting an assay between existing assays
kdp-cloud Jan 30, 2024
3f75302
Modify existing tests for inserting assays
kdp-cloud Jan 30, 2024
183c8ed
Disable button when inserting an assay before an assay with samples i…
kdp-cloud Jan 30, 2024
a09434b
Add test for disabled button
kdp-cloud Jan 30, 2024
518b8ce
Test prevent inserting new assay if next assay's sample type has samples
kdp-cloud Jan 30, 2024
d8aec7d
do not rearrange position if not isa json compliant or if assay stream
kdp-cloud Jan 30, 2024
735896e
Fix tests
kdp-cloud Jan 30, 2024
1ef73ff
test assay position after deletion
kdp-cloud Feb 1, 2024
e255e68
Order assay streams by position
kdp-cloud Feb 1, 2024
d9c8af5
Test positions after creating intermediate assays
kdp-cloud Feb 1, 2024
102aeb9
Fix / simplify unit tests
kdp-cloud Feb 2, 2024
8a6c20c
Fix the existing functional tests
kdp-cloud Feb 2, 2024
33a4e0c
Test isa asssay positions
kdp-cloud Feb 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 34 additions & 32 deletions app/controllers/assays_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ class AssaysController < ApplicationController
before_action :find_assets, only: [:index]
before_action :find_and_authorize_requested_item,
only: %i[edit update destroy manage manage_update show new_object_based_on_existing_one]
before_action :fix_assay_linkage, only: [:destroy]
before_action :delete_linked_sample_types, only: [:destroy]

# project_membership_required_appended is an alias to project_membership_required, but is necessary to include the actions
# defined in the application controller
before_action :project_membership_required_appended, only: [:new_object_based_on_existing_one]

# Only for ISA JSON compliant assays
# => Fix sample type linkage
before_action :fix_assay_linkage_when_deleting_assays, only: :destroy
# => Delete sample type of deleted assay
before_action :delete_linked_sample_types, only: :destroy
# => Rearrange positions
after_action :rearrange_assay_positions_at_destroy, only: :destroy

include Seek::Publishing::PublishingCommon

include Seek::IsaGraphExtensions
Expand Down Expand Up @@ -106,30 +112,6 @@ def create
end
end

def delete_linked_sample_types
return unless is_single_page_assay?
return if @assay.sample_type.nil?

@assay.sample_type.destroy
end

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_sample_type&.id

next_assay = Assay.all.detect do |a|
a.sample_type&.sample_attributes&.first&.linked_sample_type_id == @assay.sample_type_id
end

next_assay_st_attr = next_assay.sample_type&.sample_attributes&.first

return unless next_assay && previous_assay_linked_st_id && next_assay_st_attr

next_assay_st_attr.update(linked_sample_type_id: previous_assay_linked_st_id)
end

def update
update_assay_organisms @assay, params
update_assay_human_diseases @assay, params
Expand Down Expand Up @@ -178,6 +160,32 @@ def show

private

def delete_linked_sample_types
return unless @assay.is_isa_json_compliant?
return if @assay.sample_type.nil?

@assay.sample_type.destroy
end

def fix_assay_linkage_when_deleting_assays
return unless @assay.is_isa_json_compliant?
return unless @assay.has_linked_child_assay?

previous_assay_linked_st_id = @assay.previous_linked_sample_type&.id

next_assay = @assay.next_linked_child_assay

next_assay_st_attr = next_assay.sample_type&.sample_attributes&.detect(&:input_attribute?)

return unless next_assay && previous_assay_linked_st_id && next_assay_st_attr

next_assay_st_attr.update(linked_sample_type_id: previous_assay_linked_st_id)
end

def rearrange_assay_positions_at_destroy
rearrange_assay_positions(@assay.assay_stream)
end

def assay_params
params.require(:assay).permit(:title, :description, :study_id, :assay_class_id, :assay_type_uri, :technology_type_uri,
:license, *creator_related_params, :position, { document_ids: [] },
Expand All @@ -194,10 +202,4 @@ def assay_params
assay_params[:model_ids].select! { |id| Model.find_by_id(id).try(:can_view?) } if assay_params.key?(:model_ids)
end
end

def is_single_page_assay?
return false unless params.key?(:return_to)

params[:return_to].start_with? '/single_pages/'
end
end
43 changes: 39 additions & 4 deletions app/controllers/isa_assays_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ class IsaAssaysController < ApplicationController

before_action :set_up_instance_variable
before_action :find_requested_item, only: %i[edit update]
before_action :initialize_isa_assay, only: :create
before_action :fix_assay_linkage_for_new_assays, only: :create
after_action :rearrange_assay_positions_create_isa_assay, only: :create

def new
if params[:is_assay_stream]
Expand All @@ -14,10 +17,6 @@ def new
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 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!')
Expand Down Expand Up @@ -66,6 +65,42 @@ def update

private

def fix_assay_linkage_for_new_assays
return unless @isa_assay.assay.is_isa_json_compliant?
return if @isa_assay.assay.is_assay_stream? # Should not fix anything when creating an assay stream

previous_sample_type = SampleType.find(params[:isa_assay][:input_sample_type_id])
previous_assay = previous_sample_type.assays.first

# In case an assay is inserted right at the beginning of an assay stream,
# the next assay is the current first one in the assay stream.
next_assay = previous_assay.nil? ? @isa_assay.assay.assay_stream.next_linked_child_assay : previous_assay.next_linked_child_assay

# In case no next assay (an assay was appended to the end of the stream), assay linkage does not have to be fixed.
return unless next_assay

next_assay_input_attribute_id = next_assay.sample_type.sample_attributes.detect(&:input_attribute?).id
return unless next_assay_input_attribute_id

# Add link of next assay sample type to currently created assay sample type
updated_lsai = @isa_assay.assay.sample_type.linked_sample_attribute_ids.push(next_assay_input_attribute_id)
@isa_assay.assay.sample_type.update(linked_sample_attribute_ids: updated_lsai)
end

def rearrange_assay_positions_create_isa_assay
return if @isa_assay.assay.is_assay_stream?
return unless @isa_assay.assay.is_isa_json_compliant?

rearrange_assay_positions(@isa_assay.assay.assay_stream)
end

def initialize_isa_assay
@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 if isa_assay_params[:sample_type]
end

def isa_assay_params
# TODO: get the params from a shared module
isa_assay_params = params.require(:isa_assay).permit(
Expand Down
8 changes: 1 addition & 7 deletions app/controllers/studies_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def update
end

def delete_linked_sample_types
return unless is_single_page_study?
return unless @study.is_isa_json_compliant?
return if @study.sample_types.empty?

# The study sample types must be destroyed in reversed order
Expand Down Expand Up @@ -361,9 +361,3 @@ def study_params
{ extended_metadata_attributes: determine_extended_metadata_keys })
end
end

def is_single_page_study?
return false unless params.key?(:return_to)

params[:return_to].start_with? '/single_pages/'
end
6 changes: 6 additions & 0 deletions app/forms/isa_assay.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ def populate(id)
def validate_objects
@assay.errors.each { |e| errors.add(:base, "[Assay]: #{e.full_message}") } unless @assay.valid?

if @assay.new_record? && @assay.next_linked_child_assay&.sample_type&.samples&.any?
next_assay_id = @assay.next_linked_child_assay.id
next_assay_title = @assay.next_linked_child_assay.title
errors.add(:base, "[Assay]: Not allowed to create an assay before assay '#{next_assay_id} - #{next_assay_title}'. It has samples linked to it.")
end

return if @assay.is_assay_stream?

errors.add(:base, '[Assay]: The assay is missing a sample type.') if @sample_type.nil?
Expand Down
6 changes: 3 additions & 3 deletions app/helpers/dynamic_table_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def dt_aggregated(study, assay = nil)
# Links all sample_types in a sequence of sample_types
def link_sequence(sample_type)
sequence = [sample_type]
while link = sample_type.sample_attributes.detect(&:seek_sample_multi?)&.linked_sample_type
while link = sample_type.previous_linked_sample_type
sequence << link
sample_type = link
end
Expand Down Expand Up @@ -96,8 +96,8 @@ def dt_cols(sample_type)
is_seek_multi_sample = a.sample_attribute_type.base_type == Seek::Samples::BaseType::SEEK_SAMPLE_MULTI
is_cv_list = a.sample_attribute_type.base_type == Seek::Samples::BaseType::CV_LIST
cv_allows_free_text = a.allow_cv_free_text
attribute.merge!({ multi_link: true, linked_sample_type: a.linked_sample_type.id }) if is_seek_multi_sample
attribute.merge!({ multi_link: false, linked_sample_type: a.linked_sample_type.id }) if is_seek_sample
attribute.merge!({ multi_link: true, linked_sample_type: a.linked_sample_type_id }) if is_seek_multi_sample
attribute.merge!({ multi_link: false, linked_sample_type: a.linked_sample_type_id }) if is_seek_sample
attribute.merge!({is_cv_list: , cv_allows_free_text:}) if is_cv_list
attribute
end
Expand Down
20 changes: 16 additions & 4 deletions app/models/assay.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ def is_assay_stream?
def previous_linked_sample_type
return unless is_isa_json_compliant?

if is_assay_stream?
if is_assay_stream? || first_assay_in_stream?
study.sample_types.second
else
sample_type.sample_attributes.detect { |sa| sa.isa_tag.nil? && sa.title.include?('Input') }&.linked_sample_type
sample_type.previous_linked_sample_type
end
end

Expand All @@ -96,12 +96,24 @@ 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
first_assay_in_stream
else
sample_type.next_linked_sample_types.map(&:assays).flatten.detect { |a| a.assay_stream_id == assay_stream_id }
end
end

def first_assay_in_stream
if is_assay_stream?
child_assays.detect { |a| a.sample_type.previous_linked_sample_type == a.study.sample_types.second }
else
sample_type.linked_sample_attributes.detect { |sa| sa.isa_tag.nil? && sa.title.include?('Input') }&.sample_type&.assays&.first
assay_stream.child_assays.detect { |a| a.sample_type.previous_linked_sample_type == a.study.sample_types.second }
end
end

def first_assay_in_stream?
self == first_assay_in_stream
end

def default_contributor
User.current_user.try :person
end
Expand Down
4 changes: 4 additions & 0 deletions app/models/sample_attribute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ class SampleAttribute < ApplicationRecord
# whether this attribute is tied to a controlled vocab which is based on an ontology
delegate :ontology_based?, to: :sample_controlled_vocab, allow_nil: true

def input_attribute?
isa_tag.nil? && title.downcase.include?('input') && seek_sample_multi?
end

def title=(title)
super
store_accessor_name
Expand Down
24 changes: 19 additions & 5 deletions app/models/sample_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ class SampleType < ApplicationRecord

has_annotation_type :sample_type_tag, method_name: :tags

def previous_linked_sample_type
sample_attributes.detect(&:input_attribute?)&.linked_sample_type
end

def next_linked_sample_types
linked_sample_attributes.select(&:input_attribute?).map(&:sample_type).compact
end

def is_isa_json_compliant?
studies.any? || assays.any?
end
Expand Down Expand Up @@ -120,11 +128,17 @@ def can_edit?(user = User.current_user)
end

def can_delete?(user = User.current_user)
can_edit?(user) && samples.empty? &&
linked_sample_attributes.detect do |attr|
attr.sample_type &&
attr.sample_type != self
end.nil?
# Users should be able to delete an ISA JSON compliant sample type that has linked sample attributes,
# as long as it's ISA JSON compliant.
if is_isa_json_compliant?
can_edit?(user) && samples.empty?
else
can_edit?(user) && samples.empty? &&
linked_sample_attributes.detect do |attr|
attr.sample_type &&
attr.sample_type != self
end.nil?
end
end

def can_view?(user = User.current_user, referring_sample = nil, view_in_single_page = false)
Expand Down
27 changes: 24 additions & 3 deletions app/views/assays/_buttons.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@
else
t("assays.#{item.assay_class.long_key.delete(' ').underscore}")
end

isa_assay_verb ||=
if item&.is_assay_stream?
if item.next_linked_child_assay
"Insert a new"
else
"Design"
end
else
if item.next_linked_child_assay
"Insert a new"
else
"Design the next"
end
end

hide_new_assays_button = item.next_linked_child_assay&.sample_type&.samples&.any?
%>
<%= render :partial => "subscriptions/subscribe", :locals => {:object => item} %>

Expand All @@ -32,10 +49,14 @@
<% valid_study = item&.study&.is_isa_json_compliant? %>
<% 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], assay_stream_id: item.id)) %>
<% if hide_new_assays_button %>
<%= button_link_to("#{isa_assay_verb} #{t('assay')}", 'new', nil, disabled_reason: 'The next linked assay has samples. Cannot insert new assay here.') %>
<% else %>
<%= button_link_to("Design the next #{t('assay')}", 'new', new_isa_assay_path(source_assay_id: item.id, study_id: item.study.id, single_page: params[:single_page], assay_stream_id: item.assay_stream_id)) %>
<% if item&.is_assay_stream? %>
<%= button_link_to("#{isa_assay_verb} #{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("#{isa_assay_verb} #{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 %>
<% end %>
<% else %>
Expand Down
24 changes: 18 additions & 6 deletions app/views/isa_assays/_form.html.erb
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
<% assay = params[:isa_assay][:assay] if params.dig(:isa_assay, :assay) %>
<% study = Study.find(params[:study_id] || assay[:study_id]) %>
<% def first_assay_in_stream?
params[:source_assay_id].nil? || (params[:source_assay_id] == params[:assay_stream_id])
end
%>
<%
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_position = study.assay_streams.any? ? study.assay_streams.map(&:position).max + 1 : 0
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_position = first_assay_in_stream? ? 0 : source_assay.position + 1
assay_class_id = AssayClass.experimental.id
is_assay_stream = false
end
Expand Down Expand Up @@ -48,7 +52,7 @@

<div class="form-group">
<%= assay_fields.label :description -%><br/>
<%= assay_fields.text_area :description, :rows => 5, :class=>"form-control rich-text-edit" -%>
<%= assay_fields.text_area :description, rows: 5, class: "form-control rich-text-edit" -%>
</div>

<% if show_extended_metadata %>
Expand All @@ -61,9 +65,17 @@
<%= assay_study_selection('isa_assay[assay][study_id]',@isa_assay.assay.study) %>
</div>

<div class="hidden">
<%= assay_fields.number_field :position, value: assay_position || study.assays.length -%>
</div>
<% if is_assay_stream %>
<div class="form-group">
<%= assay_fields.label "Assay position" -%><br/>
<%= assay_fields.number_field :position, rows: 5, class: "form-control", value: assay_position %>
</div>
<% else %>
<div class="hidden">
<%= assay_fields.hidden_field :position, value: assay_position -%>
</div>
<% end %>


<%= assay_fields.hidden_field :assay_stream_id, value: assay_stream_id -%>
<%= assay_fields.hidden_field :assay_class_id, value: assay_class_id -%>
Expand Down
Loading
Loading