From 1ac271ca49c6cad6b0e79474cdff694dbb30c6f6 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Mon, 9 Feb 2026 11:02:03 +0000 Subject: [PATCH 01/17] fix: add missing accessioning-enabled guard clauses --- app/helpers/samples_helper.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/helpers/samples_helper.rb b/app/helpers/samples_helper.rb index 03f3753c72..6413215b32 100644 --- a/app/helpers/samples_helper.rb +++ b/app/helpers/samples_helper.rb @@ -4,7 +4,9 @@ module SamplesHelper # Indicate to the user that saving the sample will also accession it # This will not happen if the study has not been accessioned def save_text(sample) - return 'Save and Accession' if sample.should_be_accessioned? + if [accessioning_enabled?, sample.should_be_accessioned?, permitted_to_accession?(sample)].all? + return 'Save and Accession' + end 'Save Sample' end From 597434b2e9e1d42c95b0a54cf9d1475339f0b514 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Mon, 9 Feb 2026 11:18:10 +0000 Subject: [PATCH 02/17] refactor: improve reability of if statement --- app/views/studies/information/_accession_statuses.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/studies/information/_accession_statuses.html.erb b/app/views/studies/information/_accession_statuses.html.erb index 33852b716e..cccbf5536e 100644 --- a/app/views/studies/information/_accession_statuses.html.erb +++ b/app/views/studies/information/_accession_statuses.html.erb @@ -36,7 +36,7 @@ <%= accession_status&.status&.capitalize %> <%= accession_status&.updated_at&.in_time_zone&.strftime('%Y-%m-%d %H:%M:%S %Z') %> - <% if permitted_to_accession?(sample) & sample.should_be_accessioned? & accessioning_enabled? %> + <% if [accessioning_enabled?, sample.should_be_accessioned?, permitted_to_accession?(sample)].all? %> <%# Add link to perform sample accessioning %> <% if sample.current_accession_status %> <%# If status exists, offer retry %> From 641aeeafddab1c317e20ba916c9d5e295edd7adc Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Mon, 9 Feb 2026 11:18:29 +0000 Subject: [PATCH 03/17] style: add reminder that some checks don't belong in a model --- app/models/sample.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/sample.rb b/app/models/sample.rb index b4cfbf7937..de8a013572 100644 --- a/app/models/sample.rb +++ b/app/models/sample.rb @@ -553,7 +553,8 @@ def should_be_accessioned? false end - # NOTE: this does not check whether the current user is permitted to accession the sample + # NOTE: this does not check whether the current user is permitted to accession the sample, + # nor if accessioning is enabled, as these belong in a controller or library, rather than the model. def accession_and_handle_validation_errors event_user = current_user # the event_user for this sample must be set from the calling controller Accession.accession_sample(self, event_user, perform_now: true) From 71757ff8bb4b31a4803c851cd065a39288b19668 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Mon, 9 Feb 2026 11:25:03 +0000 Subject: [PATCH 04/17] fix: add more missing accessioning-enabled guard clauses --- app/views/sdb/sample_manifests/_samples.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/sdb/sample_manifests/_samples.html.erb b/app/views/sdb/sample_manifests/_samples.html.erb index 535e9d9b11..5dc50d480c 100644 --- a/app/views/sdb/sample_manifests/_samples.html.erb +++ b/app/views/sdb/sample_manifests/_samples.html.erb @@ -38,7 +38,7 @@ <%= accession_status&.status&.capitalize %> <%= accession_status&.updated_at&.in_time_zone&.strftime('%Y-%m-%d %H:%M:%S %Z') %> - <% if can?(:accession, sample) && configatron.accession_samples %> + <% if [accessioning_enabled?, sample.should_be_accessioned?, permitted_to_accession?(sample)].all? %> <%# Add link to perform sample accessioning %> <% if sample.current_accession_status %> <%# If status exists, offer retry %> From 55b09b99b529576baf5766cff95f32484ddfb755 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Mon, 9 Feb 2026 11:43:17 +0000 Subject: [PATCH 05/17] refactor: move accession_all_samples out of study model --- app/controllers/studies_controller.rb | 32 ++++++++++++++++++++++++--- app/models/study.rb | 25 --------------------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/app/controllers/studies_controller.rb b/app/controllers/studies_controller.rb index 7598cc2899..7d8fd8ec72 100644 --- a/app/controllers/studies_controller.rb +++ b/app/controllers/studies_controller.rb @@ -253,6 +253,16 @@ def accession end end + # Accession all samples in the study. + # + # If the study does not have an accession number, adds an error to the study and returns. + # Otherwise, iterates through each sample in the study and attempts to accession it, + # unless the sample already has an accession number. + # If an Accession::Error occurs for a sample, adds the error message to the study's errors. + # + # NOTE: this does not check if the current user has permission to accession samples in this study + # + # @return [void] def accession_all_samples # rubocop:disable Metrics/AbcSize,Metrics/MethodLength @study = Study.find(params[:id]) return accessioning_not_enabled_redirect unless accessioning_enabled? @@ -260,11 +270,27 @@ def accession_all_samples # rubocop:disable Metrics/AbcSize,Metrics/MethodLength # TODO: Y26-026 - Enforce accessioning permissions # return accession_permission_denied_redirect unless permitted_to_accession?(@study) - @study.accession_all_samples(current_user) + unless accession_number? + flash[:error] = 'Please accession the study before accessioning samples' + return redirect_to(study_path(@study)) + end + unless samples_accessionable? + flash[:error] = 'Study cannot accession samples, see Study Accessioning tab for details' + return redirect_to(study_path(@study)) + end + + samples.find_each do |sample| + next if sample.accession_number? + + begin + Accession.accession_sample(sample, current_user) + rescue Accession::Error => e + @study.errors.add(:base, e.message) + end + end if @study.errors.any? - error_messages = compile_accession_errors(@study.errors) - flash[:error] = error_messages + flash[:error] = compile_accession_errors(@study.errors) else flash[:notice] = 'All of the samples in this study have been sent for accessioning. ' \ 'Please check back in 5 minutes to confirm that accessioning was successful.' diff --git a/app/models/study.rb b/app/models/study.rb index 84bf3ac864..ca504dac2b 100644 --- a/app/models/study.rb +++ b/app/models/study.rb @@ -583,31 +583,6 @@ def samples_accessionable? ].all? end - # Accession all samples in the study. - # - # If the study does not have an accession number, adds an error to the study and returns. - # Otherwise, iterates through each sample in the study and attempts to accession it, - # unless the sample already has an accession number. - # If an Accession::Error occurs for a sample, adds the error message to the study's errors. - # - # NOTE: this does not check if the current user has permission to accession samples in this study - # - # @return [void] - def accession_all_samples(event_user) - return errors.add(:base, 'Please accession the study before accessioning samples') unless accession_number? - - unless samples_accessionable? - return errors.add(:base, - 'Study cannot accession samples, see Study Accessioning tab for details') - end - - samples.find_each do |sample| - Accession.accession_sample(sample, event_user) unless sample.accession_number? - rescue Accession::Error => e - errors.add(:base, e.message) - end - end - def abbreviation abbreviation = study_metadata.study_name_abbreviation abbreviation.presence || "#{id}STDY" From 5d776424189f8790c46d226d756ff945f9a66d66 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Mon, 9 Feb 2026 11:51:43 +0000 Subject: [PATCH 06/17] fix: repair missing model reference --- app/controllers/studies_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/studies_controller.rb b/app/controllers/studies_controller.rb index 7d8fd8ec72..8fbb039d2d 100644 --- a/app/controllers/studies_controller.rb +++ b/app/controllers/studies_controller.rb @@ -270,16 +270,16 @@ def accession_all_samples # rubocop:disable Metrics/AbcSize,Metrics/MethodLength # TODO: Y26-026 - Enforce accessioning permissions # return accession_permission_denied_redirect unless permitted_to_accession?(@study) - unless accession_number? + unless @study.accession_number? flash[:error] = 'Please accession the study before accessioning samples' return redirect_to(study_path(@study)) end - unless samples_accessionable? + unless @study.samples_accessionable? flash[:error] = 'Study cannot accession samples, see Study Accessioning tab for details' return redirect_to(study_path(@study)) end - samples.find_each do |sample| + @study.samples.find_each do |sample| next if sample.accession_number? begin From 546042e38fb24a652c98aefedfb83ec39f77930a Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Mon, 9 Feb 2026 12:45:39 +0000 Subject: [PATCH 07/17] test: update tests to match code changes --- spec/controllers/studies_controller_spec.rb | 42 +++++++ spec/lib/accession/study_spec.rb | 129 -------------------- 2 files changed, 42 insertions(+), 129 deletions(-) delete mode 100644 spec/lib/accession/study_spec.rb diff --git a/spec/controllers/studies_controller_spec.rb b/spec/controllers/studies_controller_spec.rb index ba3f93b587..3ef9f06d88 100644 --- a/spec/controllers/studies_controller_spec.rb +++ b/spec/controllers/studies_controller_spec.rb @@ -192,6 +192,48 @@ end context 'when the accessioning succeeds' do + it 'accessions all samples in the study' do + study.samples.each do |sample| + expect(sample.reload.sample_metadata.sample_ebi_accession_number).to eq('EGA00001000240') + end + end + + it 'redirects to the accession-statuses tab of the study page' do + expect(subject).to redirect_to(study_path(study, anchor: 'accession-statuses')) + end + + it 'does not set a flash error message' do + expect(flash[:error]).to be_nil + end + + it 'does not set a flash warning message' do + expect(flash[:warning]).to be_nil + end + + it 'sets a flash notice message' do + expect(flash[:notice]).to eq( + 'All of the samples in this study have been sent for accessioning. ' \ + 'Please check back in 5 minutes to confirm that accessioning was successful.' + ) + end + + it 'does not set a flash info message' do + expect(flash[:info]).to be_nil + end + end + + context 'when a sample already has an accession number' do + # add a 6th already accessioned sample to the study + let(:samples) { create_list(:sample_for_accessioning, number_of_samples) + create_list(:accessioned_sample, 1) } + let(:study) { create(:open_study, accession_number: 'ENA123', samples: samples) } + + it 'does not attempt to accession accessioned samples' do + # confirm that only 5 calls were made to the accession client, not 6 + expect(Accession::Submission.client) + .to have_received(:submit_and_fetch_accession_number) + .exactly(number_of_samples).times + end + it 'redirects to the accession-statuses tab of the study page' do expect(subject).to redirect_to(study_path(study, anchor: 'accession-statuses')) end diff --git a/spec/lib/accession/study_spec.rb b/spec/lib/accession/study_spec.rb deleted file mode 100644 index 9c10e3e4cf..0000000000 --- a/spec/lib/accession/study_spec.rb +++ /dev/null @@ -1,129 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -MISSING_METADATA = { - managed_study: %w[sample-taxon-id sample-common-name gender phenotype donor-id].sort.to_sentence, - open_study: %w[sample-taxon-id sample-common-name].sort.to_sentence -}.freeze -STUDY_TYPES = %i[open_study managed_study].freeze - -RSpec.describe Study, :accession, :accessioning_enabled, :un_delay_jobs, type: :model do - include AccessionV1ClientHelper - - let(:current_user) { create(:user) } - let(:accession_number) { 'SAMPLE123456' } - let(:accessionable_samples) { create_list(:sample_for_accessioning, 5) } - let(:non_accessionable_samples) { create_list(:sample, 3) } - - before do - create(:user, api_key: configatron.accession_local_key) - allow(Accession::Submission).to receive(:client).and_return( - stub_accession_client(:submit_and_fetch_accession_number, return_value: accession_number) - ) - end - - after do - SampleManifestExcel.reset! - end - - STUDY_TYPES.each do |study_type| - context "in a #{study_type}" do - let(:missing_metadata_for_study) { MISSING_METADATA[study_type] } - - context 'when all samples in a study are accessionable' do - let(:study) { create(study_type, accession_number: 'ENA123', samples: accessionable_samples) } - - before do - study.accession_all_samples(current_user) - study.reload - end - - it 'accessions only the samples with accession numbers' do - expect(study.samples.count { |sample| sample.sample_metadata.sample_ebi_accession_number.present? }).to eq( - accessionable_samples.count - ) - end - end - - context 'with studies missing accession numbers' do - let(:study) { create(study_type, samples: create_list(:sample_for_accessioning, 5)) } - - before do - # Verify expectation before running the method - expect(Accession).not_to receive(:accession_sample).with(study.samples.first, anything) - study.accession_all_samples(current_user) - study.reload - end - - it 'does not accession any samples' do - expect(study.samples).to be_all { |sample| sample.sample_metadata.sample_ebi_accession_number.nil? } - end - end - - context 'when some samples in a study are not accessionable' do - let(:study) do - create(study_type, accession_number: 'ENA123', samples: accessionable_samples + non_accessionable_samples) - end - - before do - study.accession_all_samples(current_user) - study.reload - end - - it 'adds errors to the sample model' do - expect(study.errors.full_messages).to eq( - [ - "Sample 'Sample6' cannot be accessioned: " \ - "Sample does not have the required metadata: #{missing_metadata_for_study}.", - "Sample 'Sample7' cannot be accessioned: " \ - "Sample does not have the required metadata: #{missing_metadata_for_study}.", - "Sample 'Sample8' cannot be accessioned: " \ - "Sample does not have the required metadata: #{missing_metadata_for_study}." - ] - ) - end - - it 'accessions only the samples with accession numbers' do - expect(study.samples.count { |sample| sample.sample_metadata.sample_ebi_accession_number.present? }).to eq( - accessionable_samples.count - ) - end - - it 'does not accession samples without accession numbers' do - expect(study.samples.count { |sample| sample.sample_metadata.sample_ebi_accession_number.nil? }).to eq( - non_accessionable_samples.count - ) - end - end - - context 'when none of the samples in a study are accessionable' do - let(:study) { create(study_type, accession_number: 'ENA123', samples: non_accessionable_samples) } - - before do - study.accession_all_samples(current_user) - study.reload - end - - it 'adds errors to the sample model' do - expect(study.errors.full_messages).to eq( - [ - "Sample 'Sample1' cannot be accessioned: " \ - "Sample does not have the required metadata: #{missing_metadata_for_study}.", - "Sample 'Sample2' cannot be accessioned: " \ - "Sample does not have the required metadata: #{missing_metadata_for_study}.", - "Sample 'Sample3' cannot be accessioned: " \ - "Sample does not have the required metadata: #{missing_metadata_for_study}." - ] - ) - end - - it 'does not accession samples without accession numbers' do - expect(study.samples.count { |sample| sample.sample_metadata.sample_ebi_accession_number.nil? }).to eq( - non_accessionable_samples.count - ) - end - end - end - end -end From 5af76cf44a867183e5cfd8ddc6246321c749ac71 Mon Sep 17 00:00:00 2001 From: sabrine33 Date: Mon, 9 Feb 2026 14:20:05 +0000 Subject: [PATCH 08/17] support '/' in plate purpose names --- app/models/purpose.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/purpose.rb b/app/models/purpose.rb index 56fd0c76b5..023236f14f 100644 --- a/app/models/purpose.rb +++ b/app/models/purpose.rb @@ -44,7 +44,7 @@ class Purpose < ApplicationRecord before_validation :set_default_barcode_prefix - validates :name, format: { with: /\A\w[\s\w.-]+\w\z/i }, presence: true, uniqueness: { case_sensitive: false } + validates :name, format: { with: /\A\w[\s\w.\-\/]+\w\z/i }, presence: true, uniqueness: { case_sensitive: false } # NOTE: We should validate against valid asset subclasses, but running into some issues with # subclass loading while seeding. From d1fcaf4d9df6d5a4ea7b2e413a5193a409ab2680 Mon Sep 17 00:00:00 2001 From: sabrine33 Date: Mon, 9 Feb 2026 14:24:37 +0000 Subject: [PATCH 09/17] run rubocop --- app/models/purpose.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/purpose.rb b/app/models/purpose.rb index 023236f14f..7359357854 100644 --- a/app/models/purpose.rb +++ b/app/models/purpose.rb @@ -44,7 +44,7 @@ class Purpose < ApplicationRecord before_validation :set_default_barcode_prefix - validates :name, format: { with: /\A\w[\s\w.\-\/]+\w\z/i }, presence: true, uniqueness: { case_sensitive: false } + validates :name, format: { with: %r{\A\w[\s\w.\-/]+\w\z}i }, presence: true, uniqueness: { case_sensitive: false } # NOTE: We should validate against valid asset subclasses, but running into some issues with # subclass loading while seeding. From b9dab24a6ddba2d8735d31f420207fa000cb63c8 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 22:40:19 +0000 Subject: [PATCH 10/17] Update faraday to version 2.14.1 --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index eace765b67..27cf5c9182 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -236,7 +236,7 @@ GEM factory_bot_rails (6.5.1) factory_bot (~> 6.5) railties (>= 6.1.0) - faraday (2.14.0) + faraday (2.14.1) faraday-net_http (>= 2.0, < 3.5) json logger @@ -338,8 +338,8 @@ GEM mutex_m (0.3.0) mysql2 (0.5.7) bigdecimal - net-http (0.7.0) - uri + net-http (0.9.1) + uri (>= 0.11.1) net-imap (0.5.9) date net-protocol From 9fe53312c53ab2d9e27051c322423a110db44346 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 10 Feb 2026 09:31:52 +0000 Subject: [PATCH 11/17] tests: add additional helper specs --- spec/helpers/samples_helper_spec.rb | 47 +++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 spec/helpers/samples_helper_spec.rb diff --git a/spec/helpers/samples_helper_spec.rb b/spec/helpers/samples_helper_spec.rb new file mode 100644 index 0000000000..0cbb901a28 --- /dev/null +++ b/spec/helpers/samples_helper_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe SamplesHelper, type: :helper do + subject { helper.save_text(sample) } + + let(:sample) { create(:sample) } + + before do + allow(helper).to receive(:accessioning_enabled?).and_return(accessioning_enabled) + allow(helper).to receive(:permitted_to_accession?).with(sample).and_return(permitted) + allow(sample).to receive(:should_be_accessioned?).and_return(should_be_accessioned) + end + + context 'when accessioning is enabled, sample should be accessioned, and user is permitted' do + let(:accessioning_enabled) { true } + let(:should_be_accessioned) { true } + let(:permitted) { true } + + it { is_expected.to eq('Save and Accession') } + end + + context 'when accessioning is disabled' do + let(:accessioning_enabled) { false } + let(:should_be_accessioned) { true } + let(:permitted) { true } + + it { is_expected.to eq('Save Sample') } + end + + context 'when sample should not be accessioned' do + let(:accessioning_enabled) { true } + let(:should_be_accessioned) { false } + let(:permitted) { true } + + it { is_expected.to eq('Save Sample') } + end + + context 'when user is not permitted to accession' do + let(:accessioning_enabled) { true } + let(:should_be_accessioned) { true } + let(:permitted) { false } + + it { is_expected.to eq('Save Sample') } + end +end From 3fa9e631ce584ba82bc39b71ffc325acb0519fa2 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 10 Feb 2026 09:45:36 +0000 Subject: [PATCH 12/17] tests: add additional controller specs --- spec/controllers/studies_controller_spec.rb | 57 +++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/spec/controllers/studies_controller_spec.rb b/spec/controllers/studies_controller_spec.rb index 3ef9f06d88..6ac4bd4257 100644 --- a/spec/controllers/studies_controller_spec.rb +++ b/spec/controllers/studies_controller_spec.rb @@ -350,6 +350,63 @@ end end end + + context 'when the study does not have an accession number' do + let(:study) { create(:managed_study, samples:) } + + it 'does not attempt to accession samples' do + expect(Accession::Submission.client).not_to have_received(:submit_and_fetch_accession_number) + end + + it 'redirects to the study page' do + expect(subject).to redirect_to(study_path(study)) + end + + it 'does not set a flash warning message' do + expect(flash[:warning]).to be_nil + end + + it 'does not set a flash notice message' do + expect(flash[:notice]).to be_nil + end + + it 'sets a flash error message' do + expect(flash[:error]).to eq('Please accession the study before accessioning samples') + end + + it 'does not set a flash info message' do + expect(flash[:info]).to be_nil + end + end + + context 'when a study is not longer accessionable' do + let(:study_metadata) { create(:study_metadata_for_accessioning, study_ebi_accession_number: 'EGA123') } + let(:study) { create(:study, study_metadata:, samples:) } + + it 'does not attempt to accession samples' do + expect(Accession::Submission.client).not_to have_received(:submit_and_fetch_accession_number) + end + + it 'redirects to the study page' do + expect(subject).to redirect_to(study_path(study)) + end + + it 'does not set a flash warning message' do + expect(flash[:warning]).to be_nil + end + + it 'does not set a flash notice message' do + expect(flash[:notice]).to be_nil + end + + it 'sets a flash error message' do + expect(flash[:error]).to eq('Study cannot accession samples, see Study Accessioning tab for details') + end + + it 'does not set a flash info message' do + expect(flash[:info]).to be_nil + end + end end end end From 03e9f4f74b7e279e43e988cfd3a372e3e4015adf Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 10 Feb 2026 16:30:02 +0000 Subject: [PATCH 13/17] test: remove extraneous lets --- spec/helpers/samples_helper_spec.rb | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/spec/helpers/samples_helper_spec.rb b/spec/helpers/samples_helper_spec.rb index 0cbb901a28..881e25b8df 100644 --- a/spec/helpers/samples_helper_spec.rb +++ b/spec/helpers/samples_helper_spec.rb @@ -7,6 +7,10 @@ let(:sample) { create(:sample) } + let(:accessioning_enabled) { true } + let(:should_be_accessioned) { true } + let(:permitted) { true } + before do allow(helper).to receive(:accessioning_enabled?).and_return(accessioning_enabled) allow(helper).to receive(:permitted_to_accession?).with(sample).and_return(permitted) @@ -14,32 +18,22 @@ end context 'when accessioning is enabled, sample should be accessioned, and user is permitted' do - let(:accessioning_enabled) { true } - let(:should_be_accessioned) { true } - let(:permitted) { true } - it { is_expected.to eq('Save and Accession') } end context 'when accessioning is disabled' do let(:accessioning_enabled) { false } - let(:should_be_accessioned) { true } - let(:permitted) { true } it { is_expected.to eq('Save Sample') } end context 'when sample should not be accessioned' do - let(:accessioning_enabled) { true } let(:should_be_accessioned) { false } - let(:permitted) { true } it { is_expected.to eq('Save Sample') } end context 'when user is not permitted to accession' do - let(:accessioning_enabled) { true } - let(:should_be_accessioned) { true } let(:permitted) { false } it { is_expected.to eq('Save Sample') } From 6c0ad46eb7f9675f848465a2e2bdfef8774cb678 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Wed, 11 Feb 2026 12:16:22 +0000 Subject: [PATCH 14/17] fix: add missing safe navigation operators to fix humanize on nil error --- .../studies/information/_study_accession_checklist.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/studies/information/_study_accession_checklist.html.erb b/app/views/studies/information/_study_accession_checklist.html.erb index c5ece5c113..3425e42975 100644 --- a/app/views/studies/information/_study_accession_checklist.html.erb +++ b/app/views/studies/information/_study_accession_checklist.html.erb @@ -25,7 +25,7 @@ <%= checklist_item( condition: !@study.study_metadata.strategy_not_applicable?, - good: "Study release strategy is #{Study::DATA_RELEASE_STRATEGY_OPEN.humanize} or #{Study::DATA_RELEASE_STRATEGY_MANAGED.humanize} #{tag.strong { "(#{@study.study_metadata.data_release_strategy.humanize})" }}".html_safe, + good: "Study release strategy is #{Study::DATA_RELEASE_STRATEGY_OPEN.humanize} or #{Study::DATA_RELEASE_STRATEGY_MANAGED.humanize} #{tag.strong { "(#{@study.study_metadata.data_release_strategy&.humanize})" }}".html_safe, bad: "Study release strategy is #{Study::DATA_RELEASE_STRATEGY_NOT_APPLICABLE.humanize}".html_safe, action: link_to('Change release strategy', edit_study_path(@study)), action_permission: can?(:edit, @study), @@ -34,7 +34,7 @@ <%= checklist_item( condition: !@study.study_metadata.never_release?, - good: "Study release timing is #{@study.study_metadata.data_release_timing.humanize}".html_safe, + good: "Study release timing is #{@study.study_metadata.data_release_timing&.humanize}".html_safe, bad: "Study release timing is #{Study::DATA_RELEASE_TIMING_NEVER.humanize}".html_safe, action: link_to('Change release settings', edit_study_path(@study)), action_permission: can?(:edit, @study), From bac092d3e4ea27869279db82d1d81fa0f55e810a Mon Sep 17 00:00:00 2001 From: sabrine33 Date: Mon, 9 Feb 2026 14:20:05 +0000 Subject: [PATCH 15/17] support '/' in plate purpose names --- app/models/purpose.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/purpose.rb b/app/models/purpose.rb index 56fd0c76b5..023236f14f 100644 --- a/app/models/purpose.rb +++ b/app/models/purpose.rb @@ -44,7 +44,7 @@ class Purpose < ApplicationRecord before_validation :set_default_barcode_prefix - validates :name, format: { with: /\A\w[\s\w.-]+\w\z/i }, presence: true, uniqueness: { case_sensitive: false } + validates :name, format: { with: /\A\w[\s\w.\-\/]+\w\z/i }, presence: true, uniqueness: { case_sensitive: false } # NOTE: We should validate against valid asset subclasses, but running into some issues with # subclass loading while seeding. From ca08e0a9b5843e224ac15969261238d5cd1bba3f Mon Sep 17 00:00:00 2001 From: sabrine33 Date: Mon, 9 Feb 2026 14:24:37 +0000 Subject: [PATCH 16/17] run rubocop --- app/models/purpose.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/purpose.rb b/app/models/purpose.rb index 023236f14f..7359357854 100644 --- a/app/models/purpose.rb +++ b/app/models/purpose.rb @@ -44,7 +44,7 @@ class Purpose < ApplicationRecord before_validation :set_default_barcode_prefix - validates :name, format: { with: /\A\w[\s\w.\-\/]+\w\z/i }, presence: true, uniqueness: { case_sensitive: false } + validates :name, format: { with: %r{\A\w[\s\w.\-/]+\w\z}i }, presence: true, uniqueness: { case_sensitive: false } # NOTE: We should validate against valid asset subclasses, but running into some issues with # subclass loading while seeding. From 410d19f8620797a172952985381f43eb662fa9c5 Mon Sep 17 00:00:00 2001 From: sabrine33 Date: Fri, 13 Feb 2026 10:21:52 +0000 Subject: [PATCH 17/17] add an intermediate state 'processed' for labware between started and passed. --- app/models/transfer_request.rb | 7 ++++++- .../018_limber_lcm_triomics_request_types.yml | 12 ++++++++++++ .../014_lcm_triomics_submission_templates.yml | 7 +++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/models/transfer_request.rb b/app/models/transfer_request.rb index 624330b9c2..37bf570f7a 100644 --- a/app/models/transfer_request.rb +++ b/app/models/transfer_request.rb @@ -71,6 +71,7 @@ class TransferRequest < ApplicationRecord # rubocop:todo Metrics/ClassLength # as being more concise as it has fewer states. state :pending, initial: true state :started + state :processed state :processed_1 state :processed_2 state :processed_3 @@ -85,6 +86,10 @@ class TransferRequest < ApplicationRecord # rubocop:todo Metrics/ClassLength transitions to: :started, from: [:pending], after: :on_started end + event :progress do + transitions to: :processed, from: [:started], after: :on_started + end + event :process_1 do transitions to: :processed_1, from: [:pending], after: :on_started end @@ -104,7 +109,7 @@ class TransferRequest < ApplicationRecord # rubocop:todo Metrics/ClassLength event :pass do # Jumping straight to passed moves through an implied started state. transitions to: :passed, from: :pending, after: :on_started - transitions to: :passed, from: %i[started failed processed_2 processed_3 processed_4] + transitions to: :passed, from: %i[started failed processed processed_2 processed_3 processed_4] end event :fail do diff --git a/config/default_records/request_types/018_limber_lcm_triomics_request_types.yml b/config/default_records/request_types/018_limber_lcm_triomics_request_types.yml index cdc3f13ce4..e2ff4ee696 100644 --- a/config/default_records/request_types/018_limber_lcm_triomics_request_types.yml +++ b/config/default_records/request_types/018_limber_lcm_triomics_request_types.yml @@ -24,3 +24,15 @@ limber_lcm_triomics_wgs: - LCMT DNA Adp Lig library_types: - LCMB +limber_lcm_triomics_rnaseq: + name: LCM Triomics RNA-seq + asset_type: Well + order: 1 + request_class_name: IlluminaHtp::Requests::StdLibraryRequest + for_multiplexing: false + billable: true + product_line_name: Short Read + acceptable_purposes: + - LCMT Lysate + library_types: + - Combined LCM RNA diff --git a/config/default_records/submission_templates/014_lcm_triomics_submission_templates.yml b/config/default_records/submission_templates/014_lcm_triomics_submission_templates.yml index 7a55ace723..c4d1933df9 100644 --- a/config/default_records/submission_templates/014_lcm_triomics_submission_templates.yml +++ b/config/default_records/submission_templates/014_lcm_triomics_submission_templates.yml @@ -14,3 +14,10 @@ Limber-Htp - LCM Triomics WGS: request_type_keys: ["limber_lcm_triomics_wgs"] product_line_name: LCM Triomics product_catalogue_name: LCM Triomics + +Limber-Htp - LCM Triomics RNA-seq: + submission_class_name: "LinearSubmission" + related_records: + request_type_keys: ["limber_lcm_triomics_rnaseq"] + product_line_name: LCM Triomics + product_catalogue_name: LCM Triomics