diff --git a/.gitignore b/.gitignore
index d3fd6cc3ab..5c5f043efe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -66,6 +66,7 @@ public/javascripts/i18n.js
public/javascripts/translations.js
public/stylesheets/*_cached.css
public/system/
+public/sitemaps/*
public/assets/
public/assets_dev/
diff --git a/Gemfile b/Gemfile
index fd40d4e720..619ca2babd 100644
--- a/Gemfile
+++ b/Gemfile
@@ -109,6 +109,7 @@ gem 'ransack'
gem 'terser', '~> 1.1', '>= 1.1.1'
+
# Rails 4 upgrade
gem 'activerecord-session_store'
gem 'rails-observers'
@@ -177,6 +178,8 @@ group :development do
gem 'web-console', '>= 4.1.0'
gem 'rack-mini-profiler', '~> 2.0'
+ gem "flamegraph", "~> 0.9.5"
+ gem "stackprof", "~> 0.2.25"
gem 'listen', '~> 3.3'
end
@@ -202,3 +205,5 @@ group :test, :development do
gem 'teaspoon'
gem 'teaspoon-mocha'
end
+
+gem "sitemap_generator", "~> 6.3"
diff --git a/Gemfile.lock b/Gemfile.lock
index b2797432af..192e716d39 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -286,6 +286,7 @@ GEM
loofah (>= 2.3.1)
sax-machine (>= 1.0)
ffi (1.15.5)
+ flamegraph (0.9.5)
fssm (0.2.10)
gem-licenses (0.2.2)
gitlab_omniauth-ldap (2.2.0)
@@ -828,6 +829,8 @@ GEM
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.3)
+ sitemap_generator (6.3.0)
+ builder (~> 3.0)
slop (3.6.0)
snaky_hash (2.0.0)
hashie
@@ -852,6 +855,7 @@ GEM
activesupport (>= 5.2)
sprockets (>= 3.0.0)
sqlite3 (1.4.2)
+ stackprof (0.2.25)
stringio (3.0.1)
sunspot (2.6.0)
pr_geohash (~> 1.0)
@@ -990,6 +994,7 @@ DEPENDENCIES
exception_notification
factory_bot (~> 6.2.1)
feedjira
+ flamegraph (~> 0.9.5)
fleximage!
fssm
gem-licenses
@@ -1073,8 +1078,10 @@ DEPENDENCIES
seedbank
simple-spreadsheet-extractor (~> 0.18.0)
simplecov
+ sitemap_generator (~> 6.3)
sprockets-rails
sqlite3 (~> 1.4)
+ stackprof (~> 0.2.25)
stringio (= 3.0.1)
sunspot_matchers
sunspot_rails
diff --git a/app/controllers/assays_controller.rb b/app/controllers/assays_controller.rb
index 48c13e0dd1..796642c82d 100644
--- a/app/controllers/assays_controller.rb
+++ b/app/controllers/assays_controller.rb
@@ -6,6 +6,7 @@ class AssaysController < ApplicationController
before_action :assays_enabled?
before_action :find_assets, :only=>[:index]
before_action :find_and_authorize_requested_item, :only=>[:edit, :update, :destroy, :manage, :manage_update, :show, :new_object_based_on_existing_one]
+ 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
@@ -18,7 +19,7 @@ class AssaysController < ApplicationController
api_actions :index, :show, :create, :update, :destroy
def new_object_based_on_existing_one
- @existing_assay = Assay.find(params[:id])
+ @existing_assay = Assay.find(params[:id])
@assay = @existing_assay.clone_with_associations
if @existing_assay.can_view?
@@ -62,9 +63,7 @@ def new_object_based_on_existing_one
flash[:error]="You do not have the necessary permissions to copy this #{t('assays.assay')}"
redirect_to @existing_assay
end
-
-
- end
+ end
def new
@assay=setup_new_asset
@@ -112,6 +111,13 @@ def create
end
end
+
+ def delete_linked_sample_types
+ return unless is_single_page_assay?
+
+ @assay.sample_type.destroy
+ end
+
def update
update_assay_organisms @assay, params
update_assay_human_diseases @assay, params
@@ -177,4 +183,10 @@ 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
diff --git a/app/controllers/studies_controller.rb b/app/controllers/studies_controller.rb
index 2aed0d36a1..bec5ef69fc 100644
--- a/app/controllers/studies_controller.rb
+++ b/app/controllers/studies_controller.rb
@@ -6,6 +6,7 @@ class StudiesController < ApplicationController
before_action :studies_enabled?
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 :delete_linked_sample_types, only: [:destroy]
# project_membership_required_appended is an alias to project_membership_required, but is necesary to include the actions
# defined in the application controller
@@ -88,6 +89,16 @@ def update
end
end
+ def delete_linked_sample_types
+ return unless is_single_page_study?
+
+ # The study sample types must be destroyed in reversed order
+ # otherwise the first sample type won't be removed becaused it is linked from the second
+ study_st_ids = @study.sample_types.map(&:id).sort { |a, b| b <=> a }
+ SampleType.destroy(study_st_ids)
+ end
+
+
def show
@study = Study.find(params[:id])
@@ -201,7 +212,7 @@ def batch_create
study_params = {
title: params[:studies][:title][index],
description: params[:studies][:description][index],
- investigation_id: params[:study][:investigation_id],
+ investigation_id: params[:study][:investigation_id],
custom_metadata: CustomMetadata.new(
custom_metadata_type: metadata_types,
data: metadata
@@ -351,3 +362,9 @@ def study_params
{ custom_metadata_attributes: determine_custom_metadata_keys })
end
end
+
+def is_single_page_study?
+ return false unless params.key?(:return_to)
+
+ params[:return_to].start_with? '/single_pages/'
+end
diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb
index 53d070285a..dcb5dace04 100644
--- a/app/controllers/templates_controller.rb
+++ b/app/controllers/templates_controller.rb
@@ -87,7 +87,7 @@ def task_status
def populate_template
uploaded_file = params[:template_json_file]
- dir = Rails.root.join('config', 'default_data', 'source_types')
+ dir = Seek::Config.append_filestore_path('source_types')
if Dir.exist?(dir)
`rm #{dir}/*`
@@ -146,11 +146,11 @@ def set_status
end
def lockfile
- Rails.root.join('tmp', 'populate_templates.lock')
+ Rails.root.join(Seek::Config.temporary_filestore_path, 'populate_templates.lock')
end
def resultfile
- Rails.root.join('tmp', 'populate_templates.result')
+ Rails.root.join(Seek::Config.temporary_filestore_path, 'populate_templates.result')
end
def running!
diff --git a/app/models/assay.rb b/app/models/assay.rb
index d8bb9b9a68..13b2b9279e 100644
--- a/app/models/assay.rb
+++ b/app/models/assay.rb
@@ -77,7 +77,7 @@ def short_description
end
def state_allows_delete?(*args)
- assets.empty? && publications.empty? && super
+ assets.empty? && publications.empty? && associated_samples_through_sample_type.empty? && super
end
# returns true if this is a modelling class of assay
@@ -90,6 +90,10 @@ def is_experimental?
!assay_class.nil? && assay_class.key == 'EXP'
end
+ def associated_samples_through_sample_type
+ (sample_type.nil? || sample_type.samples.nil?) ? [] : sample_type.samples
+ end
+
# Create or update relationship of this assay to another, with a specific relationship type and version
def associate(asset, options = {})
if asset.is_a?(Organism)
diff --git a/app/models/study.rb b/app/models/study.rb
index 944373fc9b..bc1691852e 100644
--- a/app/models/study.rb
+++ b/app/models/study.rb
@@ -2,7 +2,7 @@ class Study < ApplicationRecord
enum status: [:planned, :running, :completed, :cancelled, :failed]
belongs_to :assignee, class_name: 'Person'
-
+
searchable(:auto_index => false) do
text :experimentalists
end if Seek::Config.solr_enabled
@@ -22,7 +22,7 @@ class Study < ApplicationRecord
has_many :sop_versions, through: :assays
has_one :external_asset, as: :seek_entity, dependent: :destroy
-
+
has_and_belongs_to_many :sops
has_and_belongs_to_many :sample_types
@@ -41,7 +41,16 @@ def assets
end
def state_allows_delete? *args
- assays.empty? && super
+ assays.empty? && associated_samples_through_sample_type.empty? && super
+ end
+
+ def associated_samples_through_sample_type
+ return [] if sample_types.nil?
+ st_samples = []
+ sample_types.map do |st|
+ st.samples.map { |sts| st_samples.push sts }
+ end
+ st_samples
end
def clone_with_associations
diff --git a/app/views/general/_index.html.erb b/app/views/general/_index.html.erb
index 574ef2ffc1..37f9ff69e2 100644
--- a/app/views/general/_index.html.erb
+++ b/app/views/general/_index.html.erb
@@ -5,10 +5,14 @@
show_new_button = true unless local_assigns.has_key?(:show_new_button)
title ||= nil
subtitle ||= nil
+ isa_templates_enabled = Seek::Config.sample_type_template_enabled && Seek::Config.project_single_page_advanced_enabled
%>
<% if show_new_button && controller_model.can_create? %>
+ <% if isa_templates_enabled %>
+ <%= link_to "Query by #{t('template').pluralize}", query_form_samples_path, class: "btn btn-default btn" %>
+ <% end %>
<%= button_link_to(new_item_label, "new", new_item_path) %>
<% end %>
diff --git a/app/views/programmes/activation_review.html.erb b/app/views/programmes/activation_review.html.erb
index b7562f03b1..d373da2cd8 100644
--- a/app/views/programmes/activation_review.html.erb
+++ b/app/views/programmes/activation_review.html.erb
@@ -1,4 +1,4 @@
-<%= render :partial => "general/item_title",:locals => {:item=>@programme, :title_prefix=>"Acitivation required for: "} %>
+<%= render :partial => "general/item_title",:locals => {:item=>@programme, :title_prefix=>"Activation required for: "} %>
A <%= t('programme') %> has been created by a user, and requires activation. The <%= t('programme') %> created is <%= link_to @programme.title, @programme %>.
diff --git a/config/schedule.rb b/config/schedule.rb
index 5216b0f962..743d0d60ed 100644
--- a/config/schedule.rb
+++ b/config/schedule.rb
@@ -71,6 +71,11 @@ def offset(off_hours)
runner "Seek::BioSchema::DataDump.generate_dumps"
end
+# Generate a new sitemap...
+every 1.day, at: '12:45 am' do
+ rake "-s sitemap:refresh"
+end
+
# not safe to automatically add in a non containerised environment
if Seek::Docker.using_docker?
every 10.minutes do
diff --git a/config/sitemap.rb b/config/sitemap.rb
new file mode 100644
index 0000000000..90cfab2813
--- /dev/null
+++ b/config/sitemap.rb
@@ -0,0 +1,19 @@
+# https://github.com/kjvarga/sitemap_generator#sitemapgenerator
+SitemapGenerator::Sitemap.sitemaps_path = "sitemaps"
+SitemapGenerator::Sitemap.create_index = "auto"
+SitemapGenerator::Sitemap.compress = false
+SitemapGenerator::Sitemap.default_host = URI.parse(Seek::Config.site_base_url)
+
+SitemapGenerator::Sitemap.create do
+ Seek::Util.searchable_types.each do |type|
+ add polymorphic_path(type), lastmod: type.maximum(:updated_at), changefreq: 'daily', priority: 0.7
+ end
+end
+
+Seek::Util.searchable_types.each do |type|
+ SitemapGenerator::Sitemap.create(filename: type.table_name, include_root: false) do
+ type.authorized_for('view', nil).find_all do |obj|
+ add polymorphic_path(obj), lastmod: obj.updated_at, changefreq: 'daily', priority: 0.7
+ end
+ end
+end
diff --git a/lib/seek/isa_templates/template_extractor.rb b/lib/seek/isa_templates/template_extractor.rb
index b0487a7e24..236b8d5606 100644
--- a/lib/seek/isa_templates/template_extractor.rb
+++ b/lib/seek/isa_templates/template_extractor.rb
@@ -12,7 +12,7 @@ def self.extract_templates
disable_authorization_checks do
client = Ebi::OlsClient.new
project = Project.find_or_create_by(title: 'Default Project')
- directory = Rails.root.join('config', 'default_data', 'source_types')
+ directory = Seek::Config.append_filestore_path('source_types')
directory_files = Dir.exist?(directory) ? Dir.glob("#{directory}/*.json") : []
raise '
- Make sure to upload files that have the ".json" extension.
' if directory_files == []
@@ -172,11 +172,11 @@ def self.seed_isa_tags
end
def self.lockfile
- Rails.root.join('tmp', 'populate_templates.lock')
+ Rails.root.join(Seek::Config.temporary_filestore_path, 'populate_templates.lock')
end
def self.resultfile
- Rails.root.join('tmp', 'populate_templates.result')
+ Rails.root.join(Seek::Config.temporary_filestore_path, 'populate_templates.result')
end
end
end
diff --git a/lib/tasks/seek_upgrades.rake b/lib/tasks/seek_upgrades.rake
index ca6b6e607a..51ef422a1c 100644
--- a/lib/tasks/seek_upgrades.rake
+++ b/lib/tasks/seek_upgrades.rake
@@ -8,28 +8,6 @@ namespace :seek do
# these are the tasks required for this version upgrade
task upgrade_version_tasks: %i[
environment
- db:seed:007_sample_attribute_types
- update_missing_openbis_istest
- update_missing_publication_versions
- update_edam_controlled_vocab_keys
- db:seed:011_topics_controlled_vocab
- db:seed:012_operations_controlled_vocab
- db:seed:013_formats_controlled_vocab
- db:seed:014_data_controlled_vocab
- db:seed:015_isa_tags
- db:seed:003_model_formats
- db:seed:004_model_recommended_environments
- remove_orphaned_versions
- refresh_workflow_internals
- remove_scale_annotations
- remove_spreadsheet_annotations
- remove_node_annotations
- convert_roles
- update_edam_annotation_attributes
- remove_orphaned_project_subscriptions
- remove_node_activity_logs
- remove_node_asset_creators
- set_default_sample_type_creators
]
# these are the tasks that are executes for each upgrade as standard, and rarely change
@@ -66,201 +44,6 @@ namespace :seek do
end
end
- task(update_missing_openbis_istest: :environment) do
- puts '... creating missing is_test for OpenbisEndpoint...'
- create = 0
- disable_authorization_checks do
- OpenbisEndpoint.find_each do |openbis_endpoint|
- # check if the publication has a version
- # then create one if missing
- if openbis_endpoint.is_test.nil?
- openbis_endpoint.is_test = false # default -> prod, https
- openbis_endpoint.save
- unless openbis_endpoint.is_test.nil?
- create += 1
- end
- end
- # publication.save
- end
- end
- puts " ... finished creating missing is_test for #{create.to_s} OpenbisEndpoint(s)"
- end
-
- task(update_missing_publication_versions: :environment) do
- puts '... creating missing publications versions ...'
- create = 0
- disable_authorization_checks do
- Publication.find_each do |publication|
- # check if the publication has a version
- # then create one if missing
- if publication.latest_version.nil?
- publication.save_as_new_version 'Version for legacy entries'
- unless publication.latest_version.nil?
- create += 1
- end
- end
- # publication.save
- end
- end
- puts " ... finished creating missing publications versions for #{create.to_s} publications"
- end
-
- task(remove_orphaned_versions: [:environment]) do
- puts 'Removing orphaned versions ...'
- count = 0
- types = [DataFile::Version, Document::Version, Sop::Version, Model::Version, Presentation::Version,
- Sop::Version, Workflow::Version]
- disable_authorization_checks do
- types.each do |type|
- found = type.where.missing(:parent)
- count += found.length
- found.each(&:destroy)
- end
- end
- puts "... finished removing #{count} orphaned versions"
- end
-
- task(remove_scale_annotations: [:environment]) do
- a = Annotation.joins(:annotation_attribute).where(annotation_attribute: { name: ['additional_scale_info', 'scale'] })
- count = a.count
- a.destroy_all
- AnnotationAttribute.where(name:['scale','additional_scale_info']).destroy_all
- puts "Removed #{count} scale related annotations" if count > 0
- end
-
- task(remove_spreadsheet_annotations: [:environment]) do
- annotations = Annotation.where(annotatable_type: 'CellRange')
- count = annotations.count
- values = TextValue.joins(:annotations).where(annotations: { annotatable_type: 'CellRange' })
- values.select{|v| v.annotations.count == 1}.each(&:destroy)
- annotations.destroy_all
- AnnotationAttribute.where(name:'annotation').destroy_all
- puts "Removed #{count} spreadsheet related annotations" if count > 0
- end
-
- task(remove_node_annotations: [:environment]) do
- annotations = Annotation.where(annotatable_type: 'Node')
- count = annotations.count
- values = TextValue.joins(:annotations).where(annotations: { annotatable_type: 'Node' })
- values.select{|v| v.annotations.count == 1}.each(&:destroy)
- annotations.destroy_all
- puts "Removed #{count} Node related annotations" if count > 0
- end
-
- task(convert_roles: [:environment]) do
- puts 'Converting roles...'
- disable_authorization_checks do
- Person.find_each do |person|
- RoleType.for_system.each do |rt|
- mask = rt.id
- if (person.roles_mask & mask) != 0
- Role.where(role_type_id: rt.id, person_id: person.id, scope: nil).first_or_create!
- end
- end
- end
-
- class AdminDefinedRoleProject < ActiveRecord::Base; end
-
- AdminDefinedRoleProject.find_each do |role|
- RoleType.for_projects.each do |rt|
- mask = rt.id
- if (role.role_mask & mask) != 0
- Role.where(role_type_id: rt.id, person_id: role.person_id,
- scope_type: 'Project', scope_id: role.project_id).first_or_create!
- end
- end
- end
-
- class AdminDefinedRoleProgramme < ActiveRecord::Base; end
-
- AdminDefinedRoleProgramme.find_each do |role|
- RoleType.for_programmes.each do |rt|
- mask = rt.id
- if (role.role_mask & mask) != 0
- Role.where(role_type_id: rt.id, person_id: role.person_id,
- scope_type: 'Programme', scope_id: role.programme_id).first_or_create!
- end
- end
- end
- end
- end
-
- task(update_edam_annotation_attributes: [:environment]) do
- defs = {
- "edam_formats": "data_format_annotations",
- "edam_topics": "topic_annotations",
- "edam_operations": "operation_annotations",
- "edam_data": "data_type_annotations"
- }
- defs.each do |old_name,new_name|
- query = AnnotationAttribute.where(name: old_name)
- if query.any?
- puts "Updating EDAM based #{old_name} Annotation Attributes"
- query.update_all(name: new_name)
- end
- end
- end
-
- task(update_edam_controlled_vocab_keys: [:environment]) do
- defs = {
- topics: 'edam_topics',
- operations: 'edam_operations',
- data_formats: 'edam_formats',
- data_types: 'edam_data'
- }
-
- defs.each do |property, old_key|
- new_key = SampleControlledVocab::SystemVocabs.database_key_for_property(property)
- query = SampleControlledVocab.where(key: old_key)
- if query.any?
- puts "Updating key for #{old_key} controlled vocabulary"
- query.update_all(key: new_key)
- end
- end
- end
-
- task(remove_orphaned_project_subscriptions: [:environment]) do
- disable_authorization_checks do
- ProjectSubscription.where.missing(:project).destroy_all
- end
- end
-
- task(remove_node_activity_logs: [:environment]) do
- logs = ActivityLog.where(activity_loggable_type: 'Node')
- puts "Removing #{logs.count} Node related activity logs" if logs.count > 0
- logs.delete_all
- end
-
- task(remove_node_asset_creators: [:environment]) do
- creators = AssetsCreator.where(asset_type: 'Node')
- puts "Removing #{creators.count} Node related asset creators" if creators.count > 0
- creators.delete_all
- end
-
- task(refresh_workflow_internals: [:environment]) do |task|
- ran = only_once(task) do
- Rake::Task['seek:rebuild_workflow_internals'].invoke
- end
-
- puts "Skipping workflow internals rebuild, already done" unless ran
- end
-
- task(set_default_sample_type_creators: [:environment]) do
- ran = only_once('set_default_sample_type_creators') do
- puts "Setting default Sample Type creators"
- count = 0
- SampleType.all.each do |sample_type|
- if sample_type.assets_creators.empty?
- sample_type.assets_creators.build(creator: sample_type.contributor).save!
- count += 1
- end
- end
- puts "#{count} Sample Types updated"
- end
-
- puts "Skipping setting default Sample Type creators, as already set" unless ran
- end
-
private
##
diff --git a/test/functional/assays_controller_test.rb b/test/functional/assays_controller_test.rb
index 41b1d850d1..86b8c10ef0 100644
--- a/test/functional/assays_controller_test.rb
+++ b/test/functional/assays_controller_test.rb
@@ -1921,4 +1921,20 @@ def check_fixtures_for_authorization_of_sops_and_datafiles_links
assert_select 'span.updated_last_by a', false, 'Last editor should not be shown if editor user has been deleted'
end
+ test 'should delete empty assay with linked sample type' do
+ person = FactoryBot.create(:person)
+ assay_sample_type = FactoryBot.create :linked_sample_type, contributor: person
+ assay = FactoryBot.create(:assay,
+ policy:FactoryBot.create(:private_policy, permissions:[FactoryBot.create(:permission,contributor: person, access_type:Policy::EDITING)]),
+ sample_type: assay_sample_type,
+ contributor: person)
+
+ login_as(person)
+
+ assert_difference('SampleType.count', -1) do
+ assert_difference('Assay.count', -1) do
+ delete :destroy, params: { id: assay.id, return_to: '/single_pages/' }
+ end
+ end
+ end
end
diff --git a/test/functional/studies_controller_test.rb b/test/functional/studies_controller_test.rb
index 70db123ffd..4810d31a41 100644
--- a/test/functional/studies_controller_test.rb
+++ b/test/functional/studies_controller_test.rb
@@ -11,7 +11,7 @@ class StudiesControllerTest < ActionController::TestCase
def setup
login_as FactoryBot.create(:admin).user
end
-
+
test 'should get index' do
FactoryBot.create :study, policy: FactoryBot.create(:public_policy)
get :index
@@ -1229,7 +1229,7 @@ def test_should_show_investigation_tab
policy: FactoryBot.create(:public_policy),
contributor: person)
get :show, params: { id: study.id }
-
+
assert_response :success
assert_select 'a[href=?]',
order_assays_study_path(study), count: 0
@@ -1394,4 +1394,22 @@ def test_should_show_investigation_tab
assert_equal 'my_tag', assigns(:study).tags_as_text_array.first
end
+ test 'should delete empty study with linked sample type' do
+ person = FactoryBot.create(:person)
+ study_source_sample_type = FactoryBot.create :linked_sample_type, contributor: person
+ study_sample_sample_type = FactoryBot.create :linked_sample_type, contributor: person
+ study = FactoryBot.create(:study,
+ policy:FactoryBot.create(:private_policy, permissions:[FactoryBot.create(:permission,contributor: person, access_type:Policy::EDITING)]),
+ sample_types: [study_source_sample_type, study_sample_sample_type],
+ contributor: person)
+
+ login_as(person)
+
+ assert_difference('SampleType.count', -2) do
+ assert_difference('Study.count', -1) do
+ delete :destroy, params: { id: study.id, return_to: '/single_pages/' }
+ end
+ end
+ end
+
end
diff --git a/test/unit/assay_test.rb b/test/unit/assay_test.rb
index 106f225503..1a948b9a21 100644
--- a/test/unit/assay_test.rb
+++ b/test/unit/assay_test.rb
@@ -260,6 +260,25 @@ class AssayTest < ActiveSupport::TestCase
assert !one_assay_with_publication.can_delete?(User.current_user.person)
end
+ # Users shouldn't be able to delete assays populated with samples through their linked sample types
+ test 'can only delete assays with empty sample types' do
+ assay_sample_type = FactoryBot.create(:simple_sample_type, title: "Assay Sample Type with samples")
+ empty_sample_type = FactoryBot.create(:simple_sample_type, title: "Empty assay Sample Type")
+ assay_samples = (0..4).map do |i|
+ FactoryBot.create(:sample, title: "DNA Extract nr. #{i}", sample_type: assay_sample_type)
+ end
+
+ assay = FactoryBot.create(:assay, title: "First Assay", sample_type: assay_sample_type)
+ empty_assay = FactoryBot.create(:assay, title: "Empty assay", sample_type:empty_sample_type)
+
+ assert_equal(assay.sample_type.samples.size, 5)
+ assert(empty_assay.sample_type.samples.none?)
+
+ assert_equal(assay.state_allows_delete?, false)
+ assert_equal(empty_assay.state_allows_delete?, true)
+ end
+
+
test 'assets' do
assay = assays(:metabolomics_assay)
assert_equal 3, assay.assets.size, 'should be 2 sops and 1 data file'
diff --git a/test/unit/study_test.rb b/test/unit/study_test.rb
index d22ded3792..593a12b45e 100644
--- a/test/unit/study_test.rb
+++ b/test/unit/study_test.rb
@@ -45,6 +45,24 @@ class StudyTest < ActiveSupport::TestCase
assert !study.can_delete?(study.contributor)
end
+ # Users shouldn't be able to delete studies populated with samples through their linked sample types
+ test 'can only delete studies with empty sample types' do
+ study_source_sample_type = FactoryBot.create(:simple_sample_type, title: "Source Sample Type")
+ empty_sample_type = FactoryBot.create(:simple_sample_type, title: "Empty study Sample Type")
+ sources = (0..4).map do |i|
+ FactoryBot.create(:sample, title: "Source nr. #{i}", sample_type: study_source_sample_type)
+ end
+
+ study = FactoryBot.create(:study, title: "First study", sample_types: [study_source_sample_type])
+ empty_study = FactoryBot.create(:study, title: "Empty study", sample_types:[empty_sample_type])
+
+ assert_equal(study.sample_types.first.samples.size, 5)
+ assert(empty_study.sample_types.first.samples.none?)
+
+ assert_equal(study.state_allows_delete?, false)
+ assert_equal(empty_study.state_allows_delete?, true)
+ end
+
test 'publications through assays' do
assay1 = FactoryBot.create(:assay)
study = assay1.study