From b54f2c52e294360c50df5110add64ec8e773cd77 Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Wed, 14 Jan 2026 09:12:55 +0000 Subject: [PATCH 01/57] add orange color --- config/sample_manifest_excel/columns.yml | 5 +++-- config/sample_manifest_excel/conditional_formattings.yml | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/config/sample_manifest_excel/columns.yml b/config/sample_manifest_excel/columns.yml index 58be3e02e2..e71a2d0f50 100644 --- a/config/sample_manifest_excel/columns.yml +++ b/config/sample_manifest_excel/columns.yml @@ -39,7 +39,7 @@ i7: promptTitle: "i7" prompt: "Input i7." conditional_formattings: - empty_cell: + empty_mandatory_cell_2: i5: heading: i5 TAG SEQUENCE unlocked: true @@ -50,10 +50,11 @@ i5: formula1: "255" allowBlank: false showInputMessage: true + errorStyle: :warning promptTitle: "i5" prompt: "Input i5." conditional_formattings: - empty_cell: + empty_mandatory_cell_2: tag_group: heading: TAG GROUP unlocked: true diff --git a/config/sample_manifest_excel/conditional_formattings.yml b/config/sample_manifest_excel/conditional_formattings.yml index 1554673165..5dc77ad9d6 100644 --- a/config/sample_manifest_excel/conditional_formattings.yml +++ b/config/sample_manifest_excel/conditional_formattings.yml @@ -16,6 +16,15 @@ empty_mandatory_cell: formula: "FALSE" operator: :equal priority: 1 +empty_mandatory_cell_2: + style: + bg_color: "FF991C" + type: :dxf + options: + type: :cellIs + formula: "FALSE" + operator: :equal + priority: 1 len: style: bg_color: "FF0000" From 86cbf2eeea80b54691977c1f94a91cede5e7548a Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Wed, 21 Jan 2026 10:53:32 +0000 Subject: [PATCH 02/57] add validation of i7, i5 column --- .../sample_manifest_excel/upload/row.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb b/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb index ccce068b63..f608f8ca4b 100644 --- a/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb +++ b/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb @@ -22,6 +22,22 @@ class Row # rubocop:todo Metrics/ClassLength validate :country_of_origin_has_correct_case, if: -> { data.present? && columns.present? && columns.names.include?('country_of_origin') } + validate :i7_present + # Ensure i7 column is not blank if it exists in the manifest + def i7_present + return unless columns.names.include?('i7') && value('i7').blank? + + errors.add(:base, "#{row_title} i7 can't be blank") + end + + validate :i5_present + # Ensure i5 column is not blank if it exists in the manifest + def i5_present + return unless columns.names.include?('i5') && value('i5').blank? + + errors.add(:base, "#{row_title} i5 can't be blank, putting “n/a” in i5 if only needs one set of tags") + end + delegate :present?, to: :sample, prefix: true delegate :aliquots, :asset, to: :manifest_asset From cc016ff531eb9df6743bccb56084bc33321febf4 Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Wed, 21 Jan 2026 15:04:04 +0000 Subject: [PATCH 03/57] add unit test --- .../sample_manifest_excel/upload/row.rb | 4 +- spec/sample_manifest_excel/upload/row_spec.rb | 49 ++++++++++++++++++- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb b/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb index f608f8ca4b..c466503b6d 100644 --- a/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb +++ b/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb @@ -25,7 +25,7 @@ class Row # rubocop:todo Metrics/ClassLength validate :i7_present # Ensure i7 column is not blank if it exists in the manifest def i7_present - return unless columns.names.include?('i7') && value('i7').blank? + return unless columns.present? && data.present? && columns.names.include?('i7') && value('i7').blank? errors.add(:base, "#{row_title} i7 can't be blank") end @@ -33,7 +33,7 @@ def i7_present validate :i5_present # Ensure i5 column is not blank if it exists in the manifest def i5_present - return unless columns.names.include?('i5') && value('i5').blank? + return unless columns.present? && data.present? && columns.names.include?('i5') && value('i5').blank? errors.add(:base, "#{row_title} i5 can't be blank, putting “n/a” in i5 if only needs one set of tags") end diff --git a/spec/sample_manifest_excel/upload/row_spec.rb b/spec/sample_manifest_excel/upload/row_spec.rb index 96a1b7299c..24b464653e 100644 --- a/spec/sample_manifest_excel/upload/row_spec.rb +++ b/spec/sample_manifest_excel/upload/row_spec.rb @@ -24,7 +24,7 @@ tube.human_barcode, sample_manifest.sample_manifest_assets.first.sanger_sample_id, 'AA', - '', + 'CC', 'My reference genome', 'My New Library Type', 200, @@ -101,6 +101,47 @@ 'Unknown' ] end + let(:data_without_i7_i5) do + [ + tube.human_barcode, + sample_manifest.sample_manifest_assets.first.sanger_sample_id, + '', + '', + 'My reference genome', + 'My New Library Type', + 200, + 1500, + 'SCG--1222_A01', + '', + 1, + 1, + 'Unknown', + '', + '', + '', + 'Cell Line', + 'Nov-16', + 'Nov-16', + '', + 'No', + '', + 'OTHER', + '', + '', + '', + '', + '', + 'SCG--1222_A01', + 9606, + 'Homo sapiens', + '', + '', + '', + '', + 11, + 'Unknown' + ] + end it 'is not valid without row number' do expect(described_class.new(number: 'one', data: data, columns: columns)).not_to be_valid @@ -115,6 +156,10 @@ expect(described_class.new(number: 1, data: data)).not_to be_valid end + it 'is not valid without i7/i5 if i7/i5 column exists' do + expect(described_class.new(number: 1, data: data_without_i7_i5, columns: columns)).not_to be_valid + end + it '#value returns value for specified key' do expect(described_class.new(number: 1, data: data, columns: columns).value(:sanger_sample_id)).to eq( sample_manifest.labware.first.sample_manifest_assets.first.sanger_sample_id @@ -184,7 +229,7 @@ aliquot = row.aliquots.first expect(Sample.count - sample_count).to eq(1) expect(aliquot.tag.oligo).to eq('AA') - expect(aliquot.tag2).to be_nil + expect(aliquot.tag2.oligo).to eq('CC') expect(aliquot.insert_size_from).to eq(200) expect(aliquot.insert_size_to).to eq(1500) end From 6092baa98063fd52221ba2af5e0d60960fa60282 Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Wed, 21 Jan 2026 20:26:14 +0000 Subject: [PATCH 04/57] add i7/i5 to the test fiile --- spec/factories/sample_manifest_excel/test_download_tubes.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/factories/sample_manifest_excel/test_download_tubes.rb b/spec/factories/sample_manifest_excel/test_download_tubes.rb index 3241bd7f3b..da95f58694 100644 --- a/spec/factories/sample_manifest_excel/test_download_tubes.rb +++ b/spec/factories/sample_manifest_excel/test_download_tubes.rb @@ -31,6 +31,8 @@ sample_taxon_id: 9606, sample_common_name: 'Homo sapiens', donor_id: 'id', + 'i7' => 'ATTACTCG', + 'i5' => 'CC', phenotype: 'Unknown' }.with_indifferent_access end From 772bc944e7126daf2b8cab2323e10f6c5bff455c Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Thu, 22 Jan 2026 11:27:45 +0000 Subject: [PATCH 05/57] update the test --- spec/factories/sample_manifest_excel/test_download_tubes.rb | 2 -- spec/sample_manifest_excel/upload/rows_spec.rb | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/factories/sample_manifest_excel/test_download_tubes.rb b/spec/factories/sample_manifest_excel/test_download_tubes.rb index da95f58694..3241bd7f3b 100644 --- a/spec/factories/sample_manifest_excel/test_download_tubes.rb +++ b/spec/factories/sample_manifest_excel/test_download_tubes.rb @@ -31,8 +31,6 @@ sample_taxon_id: 9606, sample_common_name: 'Homo sapiens', donor_id: 'id', - 'i7' => 'ATTACTCG', - 'i5' => 'CC', phenotype: 'Unknown' }.with_indifferent_access end diff --git a/spec/sample_manifest_excel/upload/rows_spec.rb b/spec/sample_manifest_excel/upload/rows_spec.rb index 19ecff54b4..d5e1ff63c6 100644 --- a/spec/sample_manifest_excel/upload/rows_spec.rb +++ b/spec/sample_manifest_excel/upload/rows_spec.rb @@ -39,7 +39,7 @@ end it 'is valid if some rows are empty' do - download = build(:test_download_tubes_partial, columns:) + download = build(:test_download_tubes_partial, columns:, manifest_type: 'tube_library_with_tag_sequences') download.save(test_file_name) expect(described_class.new(SampleManifestExcel::Upload::Data.new(test_file), columns)).to be_valid end From 3a4574eaa63f465c7984b0c1dd58514ff6fc0d8f Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Thu, 22 Jan 2026 11:33:13 +0000 Subject: [PATCH 06/57] fix linting --- spec/sample_manifest_excel/upload/rows_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/sample_manifest_excel/upload/rows_spec.rb b/spec/sample_manifest_excel/upload/rows_spec.rb index d5e1ff63c6..8efd3e9186 100644 --- a/spec/sample_manifest_excel/upload/rows_spec.rb +++ b/spec/sample_manifest_excel/upload/rows_spec.rb @@ -39,7 +39,7 @@ end it 'is valid if some rows are empty' do - download = build(:test_download_tubes_partial, columns:, manifest_type: 'tube_library_with_tag_sequences') + download = build(:test_download_tubes_partial, columns: columns, manifest_type: 'tube_library_with_tag_sequences') download.save(test_file_name) expect(described_class.new(SampleManifestExcel::Upload::Data.new(test_file), columns)).to be_valid end From d6489dc6d07dc943af11c4e14801a093ad64da1a Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Thu, 22 Jan 2026 12:03:33 +0000 Subject: [PATCH 07/57] fix the test --- .../uploader_for_manifests_with_tag_sequences_spec.rb | 5 +++-- spec/sample_manifest_excel/upload/upload_spec.rb | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/features/sample_manifests/uploader_for_manifests_with_tag_sequences_spec.rb b/spec/features/sample_manifests/uploader_for_manifests_with_tag_sequences_spec.rb index a62990cec1..4000cb56b4 100644 --- a/spec/features/sample_manifests/uploader_for_manifests_with_tag_sequences_spec.rb +++ b/spec/features/sample_manifests/uploader_for_manifests_with_tag_sequences_spec.rb @@ -26,6 +26,7 @@ let!(:user) { create(:admin) } let(:columns) { SampleManifestExcel.configuration.columns.tube_library_with_tag_sequences.dup } let(:test_file) { 'test_file.xlsx' } + let(:manifest_type) { 'tube_library_with_tag_sequences' } before do download.save(test_file) @@ -33,7 +34,7 @@ context 'valid' do context 'standard' do - let(:download) { build(:test_download_tubes, columns:) } + let(:download) { build(:test_download_tubes, columns:, manifest_type:) } it 'upload' do login_user(user) @@ -81,7 +82,7 @@ end context 'cgap foreign barcodes' do - let(:download) { build(:test_download_tubes_cgap, columns:) } + let(:download) { build(:test_download_tubes_cgap, columns:, manifest_type:) } it 'upload' do login_user(user) diff --git a/spec/sample_manifest_excel/upload/upload_spec.rb b/spec/sample_manifest_excel/upload/upload_spec.rb index e45611d999..fb7456dae6 100644 --- a/spec/sample_manifest_excel/upload/upload_spec.rb +++ b/spec/sample_manifest_excel/upload/upload_spec.rb @@ -28,7 +28,8 @@ download = build( :test_download_tubes, - columns: SampleManifestExcel.configuration.columns.tube_library_with_tag_sequences.dup + columns: SampleManifestExcel.configuration.columns.tube_library_with_tag_sequences.dup, + manifest_type: 'tube_library_with_tag_sequences' ) download.save(test_file_name) upload = SampleManifestExcel::Upload::Base.new(file: test_file, column_list: columns, start_row: 9) From 177f438d9df155319eb66791d696b98ec51b2399 Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Wed, 4 Feb 2026 16:28:20 +0000 Subject: [PATCH 08/57] add more columns to be orange --- .../sample_manifest_excel/upload/row.rb | 32 +++++++++++++++++++ config/sample_manifest_excel/columns.yml | 25 ++++++++++----- spec/data/sample_manifest_excel/columns.yml | 8 ++--- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb b/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb index c466503b6d..34aa49c03b 100644 --- a/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb +++ b/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb @@ -38,6 +38,38 @@ def i5_present errors.add(:base, "#{row_title} i5 can't be blank, putting “n/a” in i5 if only needs one set of tags") end + validate :chromium_tag_group + # Ensure chromium_tag_group column is not blank if it exists in the manifest + def chromium_tag_group + return unless columns.present? && data.present? && columns.names.include?('chromium_tag_group') && value('chromium_tag_group').blank? + + errors.add(:base, "#{row_title} chromium_tag_group can't be blank") + end + + validate :chromium_tag_well + # Ensure chromium_tag_well column is not blank if it exists in the manifest + def chromium_tag_well + return unless columns.present? && data.present? && columns.names.include?('chromium_tag_well') && value('chromium_tag_well').blank? + + errors.add(:base, "#{row_title} chromium_tag_well can't be blank") + end + + validate :dual_index_tag_set + # Ensure dual_index_tag_set column is not blank if it exists in the manifest + def dual_index_tag_set + return unless columns.present? && data.present? && columns.names.include?('dual_index_tag_set') && value('dual_index_tag_set').blank? + + errors.add(:base, "#{row_title} dual_index_tag_set can't be blank") + end + + validate :dual_index_tag_well + # Ensure dual_index_tag_well column is not blank if it exists in the manifest + def dual_index_tag_well + return unless columns.present? && data.present? && columns.names.include?('dual_index_tag_well') && value('dual_index_tag_well').blank? + + errors.add(:base, "#{row_title} dual_index_tag_well can't be blank") + end + delegate :present?, to: :sample, prefix: true delegate :aliquots, :asset, to: :manifest_asset diff --git a/config/sample_manifest_excel/columns.yml b/config/sample_manifest_excel/columns.yml index e71a2d0f50..9096fd1dca 100644 --- a/config/sample_manifest_excel/columns.yml +++ b/config/sample_manifest_excel/columns.yml @@ -32,12 +32,17 @@ i7: validation: options: type: :textLength - operator: :lessThanOrEqual - formula1: "255" + operator: :between + formula1: "1" + formula2: "255" allowBlank: false showInputMessage: true + showErrorMessage: true promptTitle: "i7" prompt: "Input i7." + errorStyle: :warning + errorTitle: "i7" + error: "i7 cannot be blank" conditional_formattings: empty_mandatory_cell_2: i5: @@ -46,11 +51,15 @@ i5: validation: options: type: :textLength - operator: :lessThanOrEqual - formula1: "255" + operator: :between + formula1: "1" + formula2: "255" allowBlank: false showInputMessage: true + showErrorMessage: true errorStyle: :warning + errorTitle: "i5" + error: "i5 cannot be blank" promptTitle: "i5" prompt: "Input i5." conditional_formattings: @@ -138,7 +147,7 @@ chromium_tag_group: prompt: "Input the name of a valid tag set. All samples in a library need to be tagged with the same tag set." range_name: :chromium_tag_groups conditional_formattings: - empty_cell: + empty_mandatory_cell_2: chromium_tag_well: heading: CHROMIUM TAG WELL unlocked: true @@ -156,7 +165,7 @@ chromium_tag_well: errorTitle: "Tag well" error: "Tag Index must be a well." conditional_formattings: - empty_cell: + empty_mandatory_cell_2: is_number: len: formula: @@ -236,7 +245,7 @@ dual_index_tag_set: prompt: "Input the name of a valid dual index tag plate." range_name: :dual_index_tag_sets conditional_formattings: - empty_cell: + empty_mandatory_cell_2: dual_index_tag_well: heading: DUAL INDEX TAG WELL unlocked: true @@ -254,7 +263,7 @@ dual_index_tag_well: errorTitle: "Dual index tag well" error: "Dual Index Tag must be a well." conditional_formattings: - empty_cell: + empty_mandatory_cell_2: is_number: len: formula: diff --git a/spec/data/sample_manifest_excel/columns.yml b/spec/data/sample_manifest_excel/columns.yml index 48a29008c3..f6727872f4 100644 --- a/spec/data/sample_manifest_excel/columns.yml +++ b/spec/data/sample_manifest_excel/columns.yml @@ -127,7 +127,7 @@ chromium_tag_group: prompt: "Input the name of a valid tag set. All samples in a library need to be tagged with the same tag set." range_name: :chromium_tag_groups conditional_formattings: - empty_cell: + empty_mandatory_cell_2: chromium_tag_well: heading: CHROMIUM TAG WELL unlocked: true @@ -145,7 +145,7 @@ chromium_tag_well: errorTitle: "Tag well" error: "Tag Index must be a well." conditional_formattings: - empty_cell: + empty_mandatory_cell_2: is_number: len: formula: @@ -184,7 +184,7 @@ dual_index_tag_set: prompt: "Input the name of a valid dual index tag plate." range_name: :dual_index_tag_sets conditional_formattings: - empty_cell: + empty_mandatory_cell_2: dual_index_tag_well: heading: DUAL INDEX TAG WELL unlocked: true @@ -202,7 +202,7 @@ dual_index_tag_well: errorTitle: "Dual index tag well" error: "Dual Index Tag must be a well." conditional_formattings: - empty_cell: + empty_mandatory_cell_2: is_number: len: formula: From 9493c6f94b2ef5c1c74b155957e1880c1fcbd6af Mon Sep 17 00:00:00 2001 From: Ben Topping Date: Fri, 6 Feb 2026 11:28:43 +0000 Subject: [PATCH 09/57] wip: adds scrna core cdna pooling plan download link to submission show page --- app/controllers/submissions_controller.rb | 6 ++++++ app/views/submissions/show.html.erb | 5 ++++- config/routes.rb | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 3f04313817..82e79674ca 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -115,6 +115,12 @@ def study @submissions = @study.submissions end + def download_scrna_core_cdna_pooling_plan + csv_string = '' + send_data csv_string, type: 'text/plain', filename: "#{params[:id]}_scrna_core_cdna_pooling_plan.csv", + disposition: 'attachment' + end + ################################################### AJAX ROUTES # TODO[sd9]: These AJAX routes could be re-factored diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb index ebd72980d9..80dc9263dd 100644 --- a/app/views/submissions/show.html.erb +++ b/app/views/submissions/show.html.erb @@ -4,6 +4,10 @@ add :menu, "Print labels for #{order.asset_group.name}" => print_study_asset_group_path(order.study,order.asset_group) unless order.asset_group.nil? end %> <%- add :menu, "Submissions Inbox" => submissions_path if can? :read, Submission -%> +<%- add :menu, "Download scRNA Core cDNA pooling plan" => download_scrna_core_cdna_pooling_plan_submission_path(@presenter.submission.id) if + # TODO - Move this to a helper method + @presenter.submission.orders.first.template_name == 'Limber-Htp - scRNA Core cDNA Prep GEM-X 5p' +-%> <%= page_title("Submission", "#{@presenter.submission.id} - #{@presenter.template_name}") %> @@ -70,5 +74,4 @@ end %> <% end %> <% end %> - <%= vite_javascript_tag 'submissions' %> diff --git a/config/routes.rb b/config/routes.rb index c9c46204c2..8cecf2973c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -322,6 +322,7 @@ member do post :change_priority post :cancel + get :download_scrna_core_cdna_pooling_plan end end From 58f2ee84135f5ff66110a7c368577b3cc20a3ab4 Mon Sep 17 00:00:00 2001 From: Ben Topping Date: Fri, 6 Feb 2026 14:54:29 +0000 Subject: [PATCH 10/57] wip: initial attempt at fleshing out pooling plan logic --- app/controllers/submissions_controller.rb | 28 +++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 82e79674ca..1e10d6e3dc 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -115,8 +115,32 @@ def study @submissions = @study.submissions end - def download_scrna_core_cdna_pooling_plan - csv_string = '' + def download_scrna_core_cdna_pooling_plan # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/CyclomaticComplexity + submission = Submission.find(params[:id]) + + # Group requests by study/project/donor and cells_per_chip_well + grouped_labware = submission.requests.group_by do |request| + aliquot = request.asset.aliquots.first + study = aliquot.study.name + project = aliquot&.project&.name + donor_id = aliquot.sample.sample_metadata.donor_id + "#{study} / #{project} / #{donor_id}" + end + + # Then group by cells_per_chip_well within those groups + grouped_labware = grouped_labware.flat_map do |key, group| + group.group_by { |request| request.request_metadata.cells_per_chip_well } + .map { |cells, subgroup| [key, cells, subgroup] } + end + + csv_string = CSV.generate(row_sep: "\r\n") do |csv| + csv << ['Study / Project / Donor ID', 'Pools (num samples)', 'Cells per chip well'] + grouped_labware.each do |study_project_donor, cells_per_chip_well, subgroup| + number_of_samples_in_pool, = subgroup.size.divmod(subgroup.first.request_metadata.number_of_pools) + csv << [study_project_donor, number_of_samples_in_pool, cells_per_chip_well] + end + end + send_data csv_string, type: 'text/plain', filename: "#{params[:id]}_scrna_core_cdna_pooling_plan.csv", disposition: 'attachment' end From 91d3780ba5d6f1b69cef008a56eb79b41e336978 Mon Sep 17 00:00:00 2001 From: Ben Topping Date: Mon, 9 Feb 2026 13:13:45 +0000 Subject: [PATCH 11/57] tests: adds pooling download tests --- app/controllers/submissions_controller.rb | 13 ++--- app/models/ability/base_user.rb | 2 +- config/routes.rb | 2 +- .../submissions_controller_spec.rb | 21 +++++++- .../submissions/submission_show_spec.rb | 50 +++++++++++++++++++ 5 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 spec/features/submissions/submission_show_spec.rb diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 1e10d6e3dc..95274c5d92 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -115,10 +115,10 @@ def study @submissions = @study.submissions end - def download_scrna_core_cdna_pooling_plan # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/CyclomaticComplexity + def download_scrna_core_cdna_pooling_plan # rubocop:disable Metrics/AbcSize,Metrics/MethodLength submission = Submission.find(params[:id]) - # Group requests by study/project/donor and cells_per_chip_well + # Group requests by study/project/donor grouped_labware = submission.requests.group_by do |request| aliquot = request.asset.aliquots.first study = aliquot.study.name @@ -127,16 +127,11 @@ def download_scrna_core_cdna_pooling_plan # rubocop:disable Metrics/AbcSize,Metr "#{study} / #{project} / #{donor_id}" end - # Then group by cells_per_chip_well within those groups - grouped_labware = grouped_labware.flat_map do |key, group| - group.group_by { |request| request.request_metadata.cells_per_chip_well } - .map { |cells, subgroup| [key, cells, subgroup] } - end - csv_string = CSV.generate(row_sep: "\r\n") do |csv| csv << ['Study / Project / Donor ID', 'Pools (num samples)', 'Cells per chip well'] - grouped_labware.each do |study_project_donor, cells_per_chip_well, subgroup| + grouped_labware.each do |study_project_donor, subgroup| number_of_samples_in_pool, = subgroup.size.divmod(subgroup.first.request_metadata.number_of_pools) + cells_per_chip_well = subgroup.first.request_metadata.cells_per_chip_well csv << [study_project_donor, number_of_samples_in_pool, cells_per_chip_well] end end diff --git a/app/models/ability/base_user.rb b/app/models/ability/base_user.rb index 5cec001764..eb3a2347fe 100644 --- a/app/models/ability/base_user.rb +++ b/app/models/ability/base_user.rb @@ -32,7 +32,7 @@ def grant_privileges # rubocop:todo Metrics/AbcSize, Metrics/MethodLength can %i[read create], Study can :print_asset_group_labels, Study, owners: { id: user.id } can :print_asset_group_labels, Study, managers: { id: user.id } - can %i[read create update edit], Submission + can %i[read create update edit download_scrna_core_cdna_pooling_plan], Submission can :read, [TagGroup, TagLayoutTemplate, TagSet] can %i[read update print_swipecard], User, { id: user.id } can %i[projects study_reports], User diff --git a/config/routes.rb b/config/routes.rb index 8cecf2973c..34cc42a627 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -320,9 +320,9 @@ get :study end member do + get :download_scrna_core_cdna_pooling_plan post :change_priority post :cancel - get :download_scrna_core_cdna_pooling_plan end end diff --git a/spec/controllers/submissions_controller_spec.rb b/spec/controllers/submissions_controller_spec.rb index 4d15730d21..1d11ad1ae3 100644 --- a/spec/controllers/submissions_controller_spec.rb +++ b/spec/controllers/submissions_controller_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe SubmissionsController do +RSpec.describe SubmissionsController, type: :controller do render_views let(:request_type) { create(:well_request_type) } @@ -356,6 +356,25 @@ assert_select 'div.alert-danger', 0 end end + + describe '#download_scrna_core_cdna_pooling_plan' do + before do + @template = create(:submission_template, name: 'Limber-Htp - scRNA Core cDNA Prep GEM-X 5p') + @study = create(:study, user: @user) + @project = create(:project) + submission_order = create(:order_with_submission, template_name: @template.name, study: @study, + project: @project, + user: @user, + asset_group: create(:asset_group, study: @study)) + @submission = submission_order.submission + end + + it 'downloads a pooling plan' do + get :download_scrna_core_cdna_pooling_plan, params: { id: @submission.id } + + expect(response.headers['Content-Disposition']).to include("#{@submission.id}_scrna_core_cdna_pooling_plan.csv") + end + end end def plate_submission(text) diff --git a/spec/features/submissions/submission_show_spec.rb b/spec/features/submissions/submission_show_spec.rb new file mode 100644 index 0000000000..73fce64c9b --- /dev/null +++ b/spec/features/submissions/submission_show_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Submission show' do + let(:user) { create(:admin, login: 'user') } + let(:template) { create(:submission_template) } + let(:study) { create(:study, name: 'abc123_study') } + let(:project) { create(:project, name: 'Test project') } + let(:submission) do + order = create(:order_with_submission, template_name: template.name, study: study, project: project, + asset_group: create(:asset_group, study:)) + order.submission + end + + before do + login_user user + visit submission_path(id: submission.id) + end + + describe 'has the correct content' do + it 'shows the submission information' do + expect(page).to have_content("Submission #{submission.id} - #{template.name}") + expect(page).to have_content("Project #{project.name}") + expect(page).to have_content("Study #{study.name}") + end + + it 'shows the correct sidebar links' do + expect(page).to have_link('Print labels for') + expect(page).to have_link('Submissions Inbox') + # This should only be visible for submissions with the correct scRNA template + expect(page).to have_no_link('Download scRNA Core cDNA pooling plan') + end + end + + describe 'Limber-Htp - scRNA Core cDNA Prep GEM-X 5p submissions' do + let(:template) { create(:submission_template, name: 'Limber-Htp - scRNA Core cDNA Prep GEM-X 5p') } + + it 'shows the correct sidebar links' do + expect(page).to have_link('Download scRNA Core cDNA pooling plan') + end + + it 'downloads the correct pooling plan' do + click_link 'Download scRNA Core cDNA pooling plan' + expect(page.response_headers['Content-Disposition']).to include( + "#{submission.id}_scrna_core_cdna_pooling_plan.csv" + ) + end + end +end From 1dd45b26371cb495660d113dacd343efec5f7cb8 Mon Sep 17 00:00:00 2001 From: Ben Topping Date: Mon, 9 Feb 2026 13:26:45 +0000 Subject: [PATCH 12/57] feat(submission): adds submission helper method to identify scrna core pooling submissions --- app/controllers/submissions_controller.rb | 15 +++++++++++++- app/models/submission.rb | 5 +++++ app/views/submissions/show.html.erb | 3 +-- .../submissions_controller_spec.rb | 18 +++++++++++++++++ spec/models/submission_spec.rb | 20 +++++++++++++++++++ 5 files changed, 58 insertions(+), 3 deletions(-) diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 95274c5d92..274e268333 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -116,7 +116,20 @@ def study end def download_scrna_core_cdna_pooling_plan # rubocop:disable Metrics/AbcSize,Metrics/MethodLength - submission = Submission.find(params[:id]) + begin + submission = Submission.find(params[:id]) + rescue ActiveRecord::RecordNotFound + flash[:error] = "Submission not found with id #{params[:id]}" + redirect_to submissions_path + return + end + + unless submission.scrna_core_cdna_prep_gem_x_5p_submission? + flash[:error] = + 'This submission does not have the correct template for downloading a scRNA Core cDNA pooling plan' + redirect_to submission + return + end # Group requests by study/project/donor grouped_labware = submission.requests.group_by do |request| diff --git a/app/models/submission.rb b/app/models/submission.rb index 7d3b6e4e2c..5688c9f945 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -22,6 +22,7 @@ class Submission < ApplicationRecord # rubocop:todo Metrics/ClassLength include Submission::Priorities PER_ORDER_REQUEST_OPTIONS = %w[pre_capture_plex_level gigabases_expected].freeze + SCRNA_CORE_CDNA_PREP_GEM_X_5P = 'Limber-Htp - scRNA Core cDNA Prep GEM-X 5p' self.per_page = 500 @@ -125,6 +126,10 @@ def multiplexed? orders.any?(&:multiplexed?) end + def scrna_core_cdna_prep_gem_x_5p_submission? + orders.first.template_name == SCRNA_CORE_CDNA_PREP_GEM_X_5P + end + # Attempts to find the multiplexed asset (usually a multiplexed library tube) associated # with the submission. Useful when trying to pool requests into a pre-existing tube at the # end of the process. diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb index 80dc9263dd..d61e652014 100644 --- a/app/views/submissions/show.html.erb +++ b/app/views/submissions/show.html.erb @@ -5,8 +5,7 @@ end %> <%- add :menu, "Submissions Inbox" => submissions_path if can? :read, Submission -%> <%- add :menu, "Download scRNA Core cDNA pooling plan" => download_scrna_core_cdna_pooling_plan_submission_path(@presenter.submission.id) if - # TODO - Move this to a helper method - @presenter.submission.orders.first.template_name == 'Limber-Htp - scRNA Core cDNA Prep GEM-X 5p' + @presenter.submission.scrna_core_cdna_prep_gem_x_5p_submission? -%> diff --git a/spec/controllers/submissions_controller_spec.rb b/spec/controllers/submissions_controller_spec.rb index 1d11ad1ae3..77d89cedf0 100644 --- a/spec/controllers/submissions_controller_spec.rb +++ b/spec/controllers/submissions_controller_spec.rb @@ -374,6 +374,24 @@ expect(response.headers['Content-Disposition']).to include("#{@submission.id}_scrna_core_cdna_pooling_plan.csv") end + + it 'redirects with an error if the submission is not found' do + get :download_scrna_core_cdna_pooling_plan, params: { id: 'nonexistent' } + + expect(flash[:error]).to eq('Submission not found with id nonexistent') + expect(response).to redirect_to(submissions_path) + end + + it 'redirects with an error if the submission does not have the correct template' do + @submission.orders.first.update(template_name: 'Some other template') + + get :download_scrna_core_cdna_pooling_plan, params: { id: @submission.id } + + expect(flash[:error]).to eq( + 'This submission does not have the correct template for downloading a scRNA Core cDNA pooling plan' + ) + expect(response).to redirect_to(submission_path(@submission)) + end end end diff --git a/spec/models/submission_spec.rb b/spec/models/submission_spec.rb index 844f660113..53dfad4d86 100644 --- a/spec/models/submission_spec.rb +++ b/spec/models/submission_spec.rb @@ -132,4 +132,24 @@ expect(submission.used_tags).to eq([[tag_a.oligo, tag2_a.oligo], [tag_b.oligo, tag2_b.oligo]]) end end + + describe '#scrna_core_cdna_prep_gem_x_5p_submission?' do + let(:submission) { build(:submission, orders: [order]) } + + context 'when the submission has an order with the Limber-Htp - scRNA Core cDNA Prep GEM-X 5p template' do + let(:order) { build(:order, template_name: 'Limber-Htp - scRNA Core cDNA Prep GEM-X 5p') } + + it 'returns true' do + expect(submission.scrna_core_cdna_prep_gem_x_5p_submission?).to be true + end + end + + context 'when the submission does not have an order with the Limber-Htp - scRNA Core cDNA Prep GEM-X 5p template' do + let(:order) { build(:order, template_name: 'Some other template') } + + it 'returns false' do + expect(submission.scrna_core_cdna_prep_gem_x_5p_submission?).to be false + end + end + end end From 0f8b90a49d830062861f6217991a761e66b3234a Mon Sep 17 00:00:00 2001 From: Ben Topping Date: Mon, 9 Feb 2026 15:03:18 +0000 Subject: [PATCH 13/57] feat(submission): correct scrna core pooling plan pool distribution logic --- app/controllers/submissions_controller.rb | 26 +++++++++++++++-------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 274e268333..99f1450a15 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -5,7 +5,7 @@ # such as the fields that get displayed when a submission template is selected, and # the creation of each independent order. # Most the actual heavy lifting occurs in {Submission::SubmissionCreator} -class SubmissionsController < ApplicationController +class SubmissionsController < ApplicationController # rubocop:disable Metrics/ClassLength # WARNING! This filter bypasses security mechanisms in rails 4 and mimics rails 2 behviour. # It should be removed wherever possible and the correct Strong Parameter options applied in its place. before_action :evil_parameter_hack! @@ -115,7 +115,7 @@ def study @submissions = @study.submissions end - def download_scrna_core_cdna_pooling_plan # rubocop:disable Metrics/AbcSize,Metrics/MethodLength + def download_scrna_core_cdna_pooling_plan # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity begin submission = Submission.find(params[:id]) rescue ActiveRecord::RecordNotFound @@ -131,21 +131,29 @@ def download_scrna_core_cdna_pooling_plan # rubocop:disable Metrics/AbcSize,Metr return end - # Group requests by study/project/donor + # Group requests by study/project grouped_labware = submission.requests.group_by do |request| aliquot = request.asset.aliquots.first study = aliquot.study.name project = aliquot&.project&.name - donor_id = aliquot.sample.sample_metadata.donor_id - "#{study} / #{project} / #{donor_id}" + "#{study} / #{project}" end csv_string = CSV.generate(row_sep: "\r\n") do |csv| - csv << ['Study / Project / Donor ID', 'Pools (num samples)', 'Cells per chip well'] - grouped_labware.each do |study_project_donor, subgroup| - number_of_samples_in_pool, = subgroup.size.divmod(subgroup.first.request_metadata.number_of_pools) + csv << ['Study / Project', 'Pools (num samples)', 'Cells per chip well'] + # It would be nice to refactor the scRNA Validator logic here to pull out the pooling plan logic + grouped_labware.each do |study_project, subgroup| + number_of_pools = subgroup.first.request_metadata.number_of_pools + number_of_samples_in_pool, remainder = subgroup.size.divmod(number_of_pools) + samples_in_pool_string = '' + (1..number_of_pools).each do |_| + samples_in_pool_string += "#{number_of_samples_in_pool}, " + end + samples_in_pool_string += remainder.positive? ? remainder.to_s : '' + cells_per_chip_well = subgroup.first.request_metadata.cells_per_chip_well - csv << [study_project_donor, number_of_samples_in_pool, cells_per_chip_well] + + csv << [study_project, number_of_samples_in_pool, cells_per_chip_well] end end From e81cf1e7b280a2ef945a89b589c200bee3d4dfaa Mon Sep 17 00:00:00 2001 From: Ben Topping Date: Mon, 9 Feb 2026 15:17:51 +0000 Subject: [PATCH 14/57] refactor: clean up pool allocation logic --- app/controllers/submissions_controller.rb | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 99f1450a15..02068768ef 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -115,7 +115,7 @@ def study @submissions = @study.submissions end - def download_scrna_core_cdna_pooling_plan # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity + def download_scrna_core_cdna_pooling_plan # rubocop:disable Metrics/AbcSize,Metrics/MethodLength begin submission = Submission.find(params[:id]) rescue ActiveRecord::RecordNotFound @@ -143,13 +143,18 @@ def download_scrna_core_cdna_pooling_plan # rubocop:disable Metrics/AbcSize,Metr csv << ['Study / Project', 'Pools (num samples)', 'Cells per chip well'] # It would be nice to refactor the scRNA Validator logic here to pull out the pooling plan logic grouped_labware.each do |study_project, subgroup| + # Get number_of_pools requested from the submission number_of_pools = subgroup.first.request_metadata.number_of_pools - number_of_samples_in_pool, remainder = subgroup.size.divmod(number_of_pools) - samples_in_pool_string = '' - (1..number_of_pools).each do |_| - samples_in_pool_string += "#{number_of_samples_in_pool}, " - end - samples_in_pool_string += remainder.positive? ? remainder.to_s : '' + # Ideal pool size is just the number of samples divided by the number of pools, but we need to account for + # any remainder if the division isn't exact + ideal_pool_size, remainder = subgroup.size.divmod(number_of_pools) + # Build the pools + pool_sizes = Array.new(number_of_pools, ideal_pool_size) + # Add the remainders + remainder.times { |i| pool_sizes[i] += 1 } + + # Join the pool sizes into a string for the CSV output + number_of_samples_in_pool = pool_sizes.join(', ') cells_per_chip_well = subgroup.first.request_metadata.cells_per_chip_well From bc60325c0d2028bc35557928654c88cc367e2330 Mon Sep 17 00:00:00 2001 From: Ben Topping Date: Tue, 10 Feb 2026 11:32:02 +0000 Subject: [PATCH 15/57] feat(submission): moves scrna core pooling plan csv file generation to separate module --- app/controllers/submissions_controller.rb | 34 +--------- ...a_core_cdna_prep_pooling_plan_generator.rb | 54 +++++++++++++++ ...e_cdna_prep_pooling_plan_generator_spec.rb | 66 +++++++++++++++++++ 3 files changed, 123 insertions(+), 31 deletions(-) create mode 100644 app/models/submission/scrna_core_cdna_prep_pooling_plan_generator.rb create mode 100644 spec/models/submission/scrna_core_cdna_prep_pooling_plan_generator_spec.rb diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 02068768ef..ea1fe6290b 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -5,7 +5,7 @@ # such as the fields that get displayed when a submission template is selected, and # the creation of each independent order. # Most the actual heavy lifting occurs in {Submission::SubmissionCreator} -class SubmissionsController < ApplicationController # rubocop:disable Metrics/ClassLength +class SubmissionsController < ApplicationController # WARNING! This filter bypasses security mechanisms in rails 4 and mimics rails 2 behviour. # It should be removed wherever possible and the correct Strong Parameter options applied in its place. before_action :evil_parameter_hack! @@ -131,36 +131,8 @@ def download_scrna_core_cdna_pooling_plan # rubocop:disable Metrics/AbcSize,Metr return end - # Group requests by study/project - grouped_labware = submission.requests.group_by do |request| - aliquot = request.asset.aliquots.first - study = aliquot.study.name - project = aliquot&.project&.name - "#{study} / #{project}" - end - - csv_string = CSV.generate(row_sep: "\r\n") do |csv| - csv << ['Study / Project', 'Pools (num samples)', 'Cells per chip well'] - # It would be nice to refactor the scRNA Validator logic here to pull out the pooling plan logic - grouped_labware.each do |study_project, subgroup| - # Get number_of_pools requested from the submission - number_of_pools = subgroup.first.request_metadata.number_of_pools - # Ideal pool size is just the number of samples divided by the number of pools, but we need to account for - # any remainder if the division isn't exact - ideal_pool_size, remainder = subgroup.size.divmod(number_of_pools) - # Build the pools - pool_sizes = Array.new(number_of_pools, ideal_pool_size) - # Add the remainders - remainder.times { |i| pool_sizes[i] += 1 } - - # Join the pool sizes into a string for the CSV output - number_of_samples_in_pool = pool_sizes.join(', ') - - cells_per_chip_well = subgroup.first.request_metadata.cells_per_chip_well - - csv << [study_project, number_of_samples_in_pool, cells_per_chip_well] - end - end + # Generate the pooling plan CSV string using the ScrnaCoreCdnaPrepPoolingPlanGenerator module + csv_string = Submission::ScrnaCoreCdnaPrepPoolingPlanGenerator.generate_pooling_plan(submission) send_data csv_string, type: 'text/plain', filename: "#{params[:id]}_scrna_core_cdna_pooling_plan.csv", disposition: 'attachment' diff --git a/app/models/submission/scrna_core_cdna_prep_pooling_plan_generator.rb b/app/models/submission/scrna_core_cdna_prep_pooling_plan_generator.rb new file mode 100644 index 0000000000..8f5365fda8 --- /dev/null +++ b/app/models/submission/scrna_core_cdna_prep_pooling_plan_generator.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true +# +# This module provides a basic pooling plan generator for scRNA core cDNA prep submissions. +# It generates a CSV string which outlines the pooling strategy for their samples +# based on the number of pools and cells per chip well specified in their submission. +# The pooling plan is generated based on the labware associated with the submission, grouped by study and project. +# The logic for determining the pool layout is aimed to be a mirror of the pooling logic +# used in Limber, specifically in the DonorPoolingCalculator's allocate_wells_to_pools method. +# +# This module also assumes the submission has already been validated with the scrna_core_cdna_prep validators +# to ensure donor clashes and pooling parameters are already checked. +module Submission::ScrnaCoreCdnaPrepPoolingPlanGenerator + # This logic attemps to mirror Limber's pooling logic + # See Limber LabwareCreators::DonorPoolingCalculator (allocate_wells_to_pools) + def self.generate_pooling_plan(submission) + CSV.generate(row_sep: "\r\n") do |csv| + csv << ['Study / Project', 'Pools (num samples)', 'Cells per chip well'] + # It would be nice to refactor the scRNA Validator logic here to pull out the pooling plan logic + grouped_labware(submission).each do |study_project, subgroup| + # Get number_of_pools and cells_per_chip_well requested from the submission + number_of_pools = subgroup.first.request_metadata.number_of_pools + cells_per_chip_well = subgroup.first.request_metadata.cells_per_chip_well + # Build the pools + pools_layout = calculate_pools_layout(subgroup.size, number_of_pools) + + # Join the pool sizes into a string for the CSV output + number_of_samples_in_pool = pools_layout.join(', ') + + csv << [study_project, number_of_samples_in_pool, cells_per_chip_well] + end + end + end + + # This method calculates the layout of pools based on the total number of samples and the number of pools requested. + # It divides the samples as evenly as possible across the pools, and evenly distributes any remainder samples + def self.calculate_pools_layout(number_of_samples, number_of_pools) + # Ideal pool size is just the number of samples divided by the number of pools, but we need to account for + # any remainder if the division isn't exact + ideal_pool_size, remainder = number_of_samples.divmod(number_of_pools) + pools_layout = Array.new(number_of_pools, ideal_pool_size) + remainder.times { |i| pools_layout[i] += 1 } + pools_layout + end + + # Groups the labware associated with a submission by study and project. + def self.grouped_labware(submission) + submission.requests.group_by do |request| + aliquot = request.asset.aliquots.first + study = aliquot.study.name + project = aliquot&.project&.name + "#{study} / #{project}" + end + end +end diff --git a/spec/models/submission/scrna_core_cdna_prep_pooling_plan_generator_spec.rb b/spec/models/submission/scrna_core_cdna_prep_pooling_plan_generator_spec.rb new file mode 100644 index 0000000000..8ffb9258e0 --- /dev/null +++ b/spec/models/submission/scrna_core_cdna_prep_pooling_plan_generator_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'rails_helper' + +# NB. Search for 'scRNA Core Pooling Developer Documentation' page in Confluence (public) +# for a more verbose explanation of the logic tested here. +RSpec.describe Submission::ScrnaCoreCdnaPrepPoolingPlanGenerator do + describe '.calculate_pools_layout' do + it 'evenly divides samples into pools when there is no remainder' do + expect(described_class.calculate_pools_layout(12, 3)).to eq([4, 4, 4]) + end + + it 'distributes remainder samples across pools' do + expect(described_class.calculate_pools_layout(14, 3)).to eq([5, 5, 4]) + end + end + + describe '.grouped_labware' do + let(:study) { create(:study) } + let(:project) { create(:project) } + let(:template) { create(:submission_template, name: 'Limber-Htp - scRNA Core cDNA Prep GEM-X 5p') } + let(:library_request_type) { create(:library_request_type) } + + it 'groups labware by study and project' do # rubocop:disable RSpec/ExampleLength,RSpec/MultipleExpectations + # Build the submission with the correct template and study/project associations + submission_order = create(:order_with_submission, template_name: template.name, study: study, + project: project, + asset_group: create(:asset_group, study:)) + submission = submission_order.submission + + # Add sample tubes and requests to the submission + sample_tubes = create_list(:sample_tube, 2, study:, project:) + # Add sample tubes with the same study but different project to ensure grouping is correct + project2 = create(:project) + sample_tubes << create_list(:sample_tube, 2, study: study, project: project2) + # Add sample tubes with the same project but different study to ensure grouping is correct + study2 = create(:study) + sample_tubes << create_list(:sample_tube, 3, study: study2, project: project) + + # Create library requests for each sample tube in the submission + sample_tubes.flatten.each do |tube| + create(:library_request, asset: tube.receptacle, submission: submission, + request_type: library_request_type) + end + + grouped = described_class.grouped_labware(submission) + + expected_groups = { + "#{study.name} / #{project.name}" => 2, + "#{study.name} / #{project2.name}" => 2, + "#{study2.name} / #{project.name}" => 3 + } + + expect(grouped.keys).to match_array(expected_groups.keys) + expected_groups.each do |group, count| + expect(grouped[group].size).to eq(count) + end + end + end + + describe '.generate_pooling_plan', skip: 'todo' do + it 'generates a CSV string with the correct headers and pooling plan' do + # Add test to check csv contents + end + end +end From b6df3bd0c0dfe1607a55d6c79a55f3c0fe3a7f18 Mon Sep 17 00:00:00 2001 From: Ben Topping Date: Wed, 11 Feb 2026 08:59:12 +0000 Subject: [PATCH 16/57] tests: adds generate_pooling_plan tests for ScrnaCoreCdnaPrepPoolingPlanGenerator --- ...a_core_cdna_prep_pooling_plan_generator.rb | 5 +- ...e_cdna_prep_pooling_plan_generator_spec.rb | 95 +++++++++++++++---- 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/app/models/submission/scrna_core_cdna_prep_pooling_plan_generator.rb b/app/models/submission/scrna_core_cdna_prep_pooling_plan_generator.rb index 8f5365fda8..6ea847a586 100644 --- a/app/models/submission/scrna_core_cdna_prep_pooling_plan_generator.rb +++ b/app/models/submission/scrna_core_cdna_prep_pooling_plan_generator.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true # # This module provides a basic pooling plan generator for scRNA core cDNA prep submissions. -# It generates a CSV string which outlines the pooling strategy for their samples -# based on the number of pools and cells per chip well specified in their submission. -# The pooling plan is generated based on the labware associated with the submission, grouped by study and project. +# It generates a CSV string which outlines the pooling strategy for the submitted samples +# based on the number of pools and cells per chip well specified in the submission and grouped by study and project. # The logic for determining the pool layout is aimed to be a mirror of the pooling logic # used in Limber, specifically in the DonorPoolingCalculator's allocate_wells_to_pools method. # diff --git a/spec/models/submission/scrna_core_cdna_prep_pooling_plan_generator_spec.rb b/spec/models/submission/scrna_core_cdna_prep_pooling_plan_generator_spec.rb index 8ffb9258e0..fee253819b 100644 --- a/spec/models/submission/scrna_core_cdna_prep_pooling_plan_generator_spec.rb +++ b/spec/models/submission/scrna_core_cdna_prep_pooling_plan_generator_spec.rb @@ -5,6 +5,78 @@ # NB. Search for 'scRNA Core Pooling Developer Documentation' page in Confluence (public) # for a more verbose explanation of the logic tested here. RSpec.describe Submission::ScrnaCoreCdnaPrepPoolingPlanGenerator do + let(:study) { create(:study) } + let(:project) { create(:project) } + let(:template) { create(:submission_template, name: 'Limber-Htp - scRNA Core cDNA Prep GEM-X 5p') } + let(:submission) do + submission_order = create(:order_with_submission, template_name: template.name, study: study, + project: project, + asset_group: create(:asset_group, study:)) + submission_order.submission + end + + describe '.generate_pooling_plan' do + it 'generates a CSV string with the correct headers' do + # A basic submission is fine for testing the headers + submission = create(:submission) + csv_string = described_class.generate_pooling_plan(submission) + csv = CSV.parse(csv_string, headers: true) + + expect(csv.headers).to eq(['Study / Project', 'Pools (num samples)', 'Cells per chip well']) + end + + it 'generates a CSV string with the correct pooling plan based on the submission requests' do # rubocop:disable RSpec/MultipleExpectations, RSpec/ExampleLength + # Add sample tubes and requests to the submission + sample_tubes = create_list(:sample_tube, 5, study:, project:) + sample_tubes.each do |tube| + create(:customer_request, sti_type: 'PbmcPoolingCustomerRequest', asset: tube.receptacle, + submission: submission, + request_metadata_attributes: { number_of_pools: 3, cells_per_chip_well: 100 }) + end + + csv_string = described_class.generate_pooling_plan(submission) + csv = CSV.parse(csv_string, headers: true) + + expect(csv.length).to eq(1) # We have one study/project group + expect(csv[0]['Study / Project']).to eq("#{study.name} / #{project.name}") + expect(csv[0]['Pools (num samples)']).to eq('2, 2, 1') # With 5 samples and 3 pools, we expect a layout of 2, 2, 1 + expect(csv[0]['Cells per chip well']).to eq('100') # We set this in the request metadata for each request + end + + it 'handles multiple study/project groups correctly' do # rubocop:disable RSpec/ExampleLength, RSpec/MultipleExpectations + # Add sample tubes and requests for two different study/project groups + project2 = create(:project) + study2 = create(:study) + sample_tubes_group1 = create_list(:sample_tube, 4, study:, project:) + sample_tubes_group2 = create_list(:sample_tube, 8, study: study2, project: project2) + + sample_tubes_group1.each do |tube| + create(:customer_request, sti_type: 'PbmcPoolingCustomerRequest', asset: tube.receptacle, + submission: submission, + request_metadata_attributes: { number_of_pools: 2, cells_per_chip_well: 50 }) + end + + sample_tubes_group2.each do |tube| + create(:customer_request, sti_type: 'PbmcPoolingCustomerRequest', asset: tube.receptacle, + submission: submission, + request_metadata_attributes: { number_of_pools: 3, cells_per_chip_well: 150 }) + end + + csv_string = described_class.generate_pooling_plan(submission) + csv = CSV.parse(csv_string, headers: true) + + expect(csv.length).to eq(2) # We have two study/project groups + + group1 = csv.find { |row| row['Study / Project'] == "#{study.name} / #{project.name}" } + expect(group1['Pools (num samples)']).to eq('2, 2') # With 4 samples and 2 pools, we expect a layout of 2, 2 + expect(group1['Cells per chip well']).to eq('50') + + group2 = csv.find { |row| row['Study / Project'] == "#{study2.name} / #{project2.name}" } + expect(group2['Pools (num samples)']).to eq('3, 3, 2') # With 8 samples and 3 pools, we expect a layout of 3, 3, 2 + expect(group2['Cells per chip well']).to eq('150') + end + end + describe '.calculate_pools_layout' do it 'evenly divides samples into pools when there is no remainder' do expect(described_class.calculate_pools_layout(12, 3)).to eq([4, 4, 4]) @@ -16,18 +88,7 @@ end describe '.grouped_labware' do - let(:study) { create(:study) } - let(:project) { create(:project) } - let(:template) { create(:submission_template, name: 'Limber-Htp - scRNA Core cDNA Prep GEM-X 5p') } - let(:library_request_type) { create(:library_request_type) } - it 'groups labware by study and project' do # rubocop:disable RSpec/ExampleLength,RSpec/MultipleExpectations - # Build the submission with the correct template and study/project associations - submission_order = create(:order_with_submission, template_name: template.name, study: study, - project: project, - asset_group: create(:asset_group, study:)) - submission = submission_order.submission - # Add sample tubes and requests to the submission sample_tubes = create_list(:sample_tube, 2, study:, project:) # Add sample tubes with the same study but different project to ensure grouping is correct @@ -37,10 +98,10 @@ study2 = create(:study) sample_tubes << create_list(:sample_tube, 3, study: study2, project: project) - # Create library requests for each sample tube in the submission + # Create customer requests for each sample tube in the submission sample_tubes.flatten.each do |tube| - create(:library_request, asset: tube.receptacle, submission: submission, - request_type: library_request_type) + create(:customer_request, sti_type: 'PbmcPoolingCustomerRequest', asset: tube.receptacle, + submission: submission) end grouped = described_class.grouped_labware(submission) @@ -57,10 +118,4 @@ end end end - - describe '.generate_pooling_plan', skip: 'todo' do - it 'generates a CSV string with the correct headers and pooling plan' do - # Add test to check csv contents - end - end end From f0e3bad318e3737b3c170dd57a85e04bbab0ca43 Mon Sep 17 00:00:00 2001 From: Ben Topping Date: Wed, 11 Feb 2026 10:09:26 +0000 Subject: [PATCH 17/57] fix: changes ScrnaCoreCdnaPrepPoolingPlanGenerator to group submission data by request study/project instead of asset aliquot --- ...a_core_cdna_prep_pooling_plan_generator.rb | 11 +++--- ...e_cdna_prep_pooling_plan_generator_spec.rb | 37 +++++++++++-------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/app/models/submission/scrna_core_cdna_prep_pooling_plan_generator.rb b/app/models/submission/scrna_core_cdna_prep_pooling_plan_generator.rb index 6ea847a586..0e15f3fc83 100644 --- a/app/models/submission/scrna_core_cdna_prep_pooling_plan_generator.rb +++ b/app/models/submission/scrna_core_cdna_prep_pooling_plan_generator.rb @@ -15,7 +15,7 @@ def self.generate_pooling_plan(submission) CSV.generate(row_sep: "\r\n") do |csv| csv << ['Study / Project', 'Pools (num samples)', 'Cells per chip well'] # It would be nice to refactor the scRNA Validator logic here to pull out the pooling plan logic - grouped_labware(submission).each do |study_project, subgroup| + grouped_requests(submission).each do |study_project, subgroup| # Get number_of_pools and cells_per_chip_well requested from the submission number_of_pools = subgroup.first.request_metadata.number_of_pools cells_per_chip_well = subgroup.first.request_metadata.cells_per_chip_well @@ -41,12 +41,11 @@ def self.calculate_pools_layout(number_of_samples, number_of_pools) pools_layout end - # Groups the labware associated with a submission by study and project. - def self.grouped_labware(submission) + # Groups the requests associated with a submission by study and project. + def self.grouped_requests(submission) submission.requests.group_by do |request| - aliquot = request.asset.aliquots.first - study = aliquot.study.name - project = aliquot&.project&.name + study = request.initial_study.name + project = request.initial_project.name "#{study} / #{project}" end end diff --git a/spec/models/submission/scrna_core_cdna_prep_pooling_plan_generator_spec.rb b/spec/models/submission/scrna_core_cdna_prep_pooling_plan_generator_spec.rb index fee253819b..607ba84f19 100644 --- a/spec/models/submission/scrna_core_cdna_prep_pooling_plan_generator_spec.rb +++ b/spec/models/submission/scrna_core_cdna_prep_pooling_plan_generator_spec.rb @@ -27,10 +27,10 @@ it 'generates a CSV string with the correct pooling plan based on the submission requests' do # rubocop:disable RSpec/MultipleExpectations, RSpec/ExampleLength # Add sample tubes and requests to the submission - sample_tubes = create_list(:sample_tube, 5, study:, project:) - sample_tubes.each do |tube| + create_list(:sample_tube, 5, study:, project:).each do |tube| create(:customer_request, sti_type: 'PbmcPoolingCustomerRequest', asset: tube.receptacle, submission: submission, + initial_study: study, initial_project: project, request_metadata_attributes: { number_of_pools: 3, cells_per_chip_well: 100 }) end @@ -47,18 +47,18 @@ # Add sample tubes and requests for two different study/project groups project2 = create(:project) study2 = create(:study) - sample_tubes_group1 = create_list(:sample_tube, 4, study:, project:) - sample_tubes_group2 = create_list(:sample_tube, 8, study: study2, project: project2) - sample_tubes_group1.each do |tube| + create_list(:sample_tube, 4, study:, project:).each do |tube| create(:customer_request, sti_type: 'PbmcPoolingCustomerRequest', asset: tube.receptacle, submission: submission, + initial_study: study, initial_project: project, request_metadata_attributes: { number_of_pools: 2, cells_per_chip_well: 50 }) end - sample_tubes_group2.each do |tube| + create_list(:sample_tube, 8, study: study2, project: project2).each do |tube| create(:customer_request, sti_type: 'PbmcPoolingCustomerRequest', asset: tube.receptacle, submission: submission, + initial_study: study2, initial_project: project2, request_metadata_attributes: { number_of_pools: 3, cells_per_chip_well: 150 }) end @@ -87,24 +87,29 @@ end end - describe '.grouped_labware' do - it 'groups labware by study and project' do # rubocop:disable RSpec/ExampleLength,RSpec/MultipleExpectations + describe '.grouped_requests' do + it 'groups requests by study and project' do # rubocop:disable RSpec/ExampleLength,RSpec/MultipleExpectations # Add sample tubes and requests to the submission - sample_tubes = create_list(:sample_tube, 2, study:, project:) + create_list(:sample_tube, 2, study:, project:).each do |tube| + create(:customer_request, sti_type: 'PbmcPoolingCustomerRequest', asset: tube.receptacle, + submission: submission, initial_study: study, initial_project: project) + end + # Add sample tubes with the same study but different project to ensure grouping is correct project2 = create(:project) - sample_tubes << create_list(:sample_tube, 2, study: study, project: project2) + create_list(:sample_tube, 2, study: study, project: project2).each do |tube| + create(:customer_request, sti_type: 'PbmcPoolingCustomerRequest', asset: tube.receptacle, + submission: submission, initial_study: study, initial_project: project2) + end + # Add sample tubes with the same project but different study to ensure grouping is correct study2 = create(:study) - sample_tubes << create_list(:sample_tube, 3, study: study2, project: project) - - # Create customer requests for each sample tube in the submission - sample_tubes.flatten.each do |tube| + create_list(:sample_tube, 3, study: study2, project: project).each do |tube| create(:customer_request, sti_type: 'PbmcPoolingCustomerRequest', asset: tube.receptacle, - submission: submission) + submission: submission, initial_study: study2, initial_project: project) end - grouped = described_class.grouped_labware(submission) + grouped = described_class.grouped_requests(submission) expected_groups = { "#{study.name} / #{project.name}" => 2, From fc5e0a61d93a8a073e1dbbeafc8955aaa7795164 Mon Sep 17 00:00:00 2001 From: Ben Topping Date: Wed, 11 Feb 2026 15:58:38 +0000 Subject: [PATCH 18/57] fix: adds asset uniqueness logic to ScrnaCoreCdnaPrepPoolingPlanGenerator grouped_requests --- ...a_core_cdna_prep_pooling_plan_generator.rb | 4 ++- ...e_cdna_prep_pooling_plan_generator_spec.rb | 31 +++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/app/models/submission/scrna_core_cdna_prep_pooling_plan_generator.rb b/app/models/submission/scrna_core_cdna_prep_pooling_plan_generator.rb index 0e15f3fc83..98491c1ae5 100644 --- a/app/models/submission/scrna_core_cdna_prep_pooling_plan_generator.rb +++ b/app/models/submission/scrna_core_cdna_prep_pooling_plan_generator.rb @@ -43,7 +43,9 @@ def self.calculate_pools_layout(number_of_samples, number_of_pools) # Groups the requests associated with a submission by study and project. def self.grouped_requests(submission) - submission.requests.group_by do |request| + # Unique by asset to avoid counting the same sample tube multiple times if it appears in multiple requests + # e.g. if a sample tube is requested in two different lanes, we only want to count it once for pooling plan purposes + submission.requests.uniq(&:asset).group_by do |request| study = request.initial_study.name project = request.initial_project.name "#{study} / #{project}" diff --git a/spec/models/submission/scrna_core_cdna_prep_pooling_plan_generator_spec.rb b/spec/models/submission/scrna_core_cdna_prep_pooling_plan_generator_spec.rb index 607ba84f19..fc3a25b0c2 100644 --- a/spec/models/submission/scrna_core_cdna_prep_pooling_plan_generator_spec.rb +++ b/spec/models/submission/scrna_core_cdna_prep_pooling_plan_generator_spec.rb @@ -92,21 +92,24 @@ # Add sample tubes and requests to the submission create_list(:sample_tube, 2, study:, project:).each do |tube| create(:customer_request, sti_type: 'PbmcPoolingCustomerRequest', asset: tube.receptacle, - submission: submission, initial_study: study, initial_project: project) + submission: submission, initial_study: study, initial_project: project, + request_metadata_attributes: { number_of_pools: 1, cells_per_chip_well: 150 }) end # Add sample tubes with the same study but different project to ensure grouping is correct project2 = create(:project) create_list(:sample_tube, 2, study: study, project: project2).each do |tube| create(:customer_request, sti_type: 'PbmcPoolingCustomerRequest', asset: tube.receptacle, - submission: submission, initial_study: study, initial_project: project2) + submission: submission, initial_study: study, initial_project: project2, + request_metadata_attributes: { number_of_pools: 1, cells_per_chip_well: 150 }) end # Add sample tubes with the same project but different study to ensure grouping is correct study2 = create(:study) create_list(:sample_tube, 3, study: study2, project: project).each do |tube| create(:customer_request, sti_type: 'PbmcPoolingCustomerRequest', asset: tube.receptacle, - submission: submission, initial_study: study2, initial_project: project) + submission: submission, initial_study: study2, initial_project: project, + request_metadata_attributes: { number_of_pools: 1, cells_per_chip_well: 150 }) end grouped = described_class.grouped_requests(submission) @@ -122,5 +125,27 @@ expect(grouped[group].size).to eq(count) end end + + it 'counts each sample tube only once even if it appears in multiple requests' do # rubocop:disable RSpec/ExampleLength,RSpec/MultipleExpectations + # Create a sample tube + tube = create(:sample_tube, study:, project:) + # Create multiple requests for the same tube + create_list(:customer_request, 3, sti_type: 'PbmcPoolingCustomerRequest', asset: tube.receptacle, + submission: submission, initial_study: study, initial_project: project, + request_metadata_attributes: { number_of_pools: 1, cells_per_chip_well: 150 }) + + # Create some other requests for different tubes to ensure the grouping logic is still correct + create_list(:sample_tube, 2, study:, project:).each do |tube| + create(:customer_request, sti_type: 'PbmcPoolingCustomerRequest', asset: tube.receptacle, + submission: submission, initial_study: study, initial_project: project, + request_metadata_attributes: { number_of_pools: 1, cells_per_chip_well: 150 }) + end + + grouped = described_class.grouped_requests(submission) + + expect(grouped.keys).to eq(["#{study.name} / #{project.name}"]) + # 3 as we only have 3 requests with uniq assets. + expect(grouped["#{study.name} / #{project.name}"].size).to eq(3) + end end end From 587e9ebb933195aac76efda8df918efb8fda8701 Mon Sep 17 00:00:00 2001 From: Ben Topping Date: Thu, 12 Feb 2026 13:55:26 +0000 Subject: [PATCH 19/57] fix: fixes multiplexed_library_tube throwing errors if no creation_requests exist --- app/models/multiplexed_library_tube.rb | 6 ++++-- spec/models/broadcast_event/pool_released_spec.rb | 9 +++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/models/multiplexed_library_tube.rb b/app/models/multiplexed_library_tube.rb index 9d5453273b..8c8d3ca9d6 100644 --- a/app/models/multiplexed_library_tube.rb +++ b/app/models/multiplexed_library_tube.rb @@ -18,7 +18,7 @@ def asset_type_for_request_types end def team - creation_requests.first&.product_line + creation_requests&.first&.product_line end def role @@ -41,6 +41,8 @@ def creation_requests direct = requests_as_target.where_is_a(Request::LibraryCreation) return direct unless direct.empty? - parents.includes(:requests_as_target).first.requests_as_target + # Parents should exist but in the case they don't (e.g. asset_links are yet to be created) + # we want to avoid an error and just return an empty array. + parents.includes(:requests_as_target).first&.requests_as_target end end diff --git a/spec/models/broadcast_event/pool_released_spec.rb b/spec/models/broadcast_event/pool_released_spec.rb index 579c69db25..9b8e3a71cb 100644 --- a/spec/models/broadcast_event/pool_released_spec.rb +++ b/spec/models/broadcast_event/pool_released_spec.rb @@ -112,5 +112,14 @@ expect(metadata['team']).not_to be_nil expect(metadata['team']).not_to eq('UNKNOWN') end + + # This is an edge case, but we want to make sure that if there are no creation requests or ancestors + # we don't error out and instead return 'UNKNOWN' for the team. + it 'has an unknown team if tube has no creation requests' do + tube.ancestors = [] + tube.save! + + expect(metadata['team']).to eq('UNKNOWN') + end end end From 9ac5b1504ba8c1cbce729395bd937fcd001c1e31 Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Thu, 12 Feb 2026 21:39:24 +0000 Subject: [PATCH 20/57] save working version --- ...st_upload_with_tag_sequences_controller.rb | 8 ++++ .../sample_manifest_excel/upload/row.rb | 47 +++++-------------- .../new.html.erb | 10 ++++ config/sample_manifest_excel/columns.yml | 12 ++--- .../conditional_formattings.yml | 2 +- 5 files changed, 38 insertions(+), 41 deletions(-) diff --git a/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb b/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb index dfa17a794c..5ac1fad099 100644 --- a/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb +++ b/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb @@ -10,6 +10,12 @@ def create return error('No file attached') if params[:upload].blank? if upload_manifest + @rows_with_warnings = + @uploader.upload.rows.select { |row| row.respond_to?(:warnings) && row.warnings.any? } + + flash[:warnings] = + @rows_with_warnings.flat_map { |row| row.warnings.full_messages } + success('Sample manifest successfully uploaded.') else error('Your sample manifest couldn\'t be uploaded.') @@ -25,12 +31,14 @@ def create_uploader def upload_manifest @uploader = create_uploader @uploader.run! + # @rows_with_warnings = @uploader.upload.rows.select { |row| row.warnings.any? } end def success(message) flash[:notice] = message redirect_target = (@uploader.study.present? ? sample_manifests_study_path(@uploader.study) : sample_manifests_path) + # render :new redirect_to redirect_target end diff --git a/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb b/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb index 34aa49c03b..8ff9a4d900 100644 --- a/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb +++ b/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb @@ -15,6 +15,17 @@ class Row # rubocop:todo Metrics/ClassLength attr_accessor :number, :data, :columns, :cache attr_reader :sanger_sample_id + # attr_reader :warnings + + # def initialize(attributes = {}) + # super + # @warnings = ActiveModel::Errors.new(self) + # end + + # @return [ActiveModel::Errors] the warnings collection + def warnings + @warnings ||= ActiveModel::Errors.new(self) + end validates :number, presence: true, numericality: true validate :sanger_sample_id_exists?, if: :sanger_sample_id @@ -27,7 +38,7 @@ class Row # rubocop:todo Metrics/ClassLength def i7_present return unless columns.present? && data.present? && columns.names.include?('i7') && value('i7').blank? - errors.add(:base, "#{row_title} i7 can't be blank") + warnings.add(:base, "#{row_title} i7 is blank! ") end validate :i5_present @@ -35,39 +46,7 @@ def i7_present def i5_present return unless columns.present? && data.present? && columns.names.include?('i5') && value('i5').blank? - errors.add(:base, "#{row_title} i5 can't be blank, putting “n/a” in i5 if only needs one set of tags") - end - - validate :chromium_tag_group - # Ensure chromium_tag_group column is not blank if it exists in the manifest - def chromium_tag_group - return unless columns.present? && data.present? && columns.names.include?('chromium_tag_group') && value('chromium_tag_group').blank? - - errors.add(:base, "#{row_title} chromium_tag_group can't be blank") - end - - validate :chromium_tag_well - # Ensure chromium_tag_well column is not blank if it exists in the manifest - def chromium_tag_well - return unless columns.present? && data.present? && columns.names.include?('chromium_tag_well') && value('chromium_tag_well').blank? - - errors.add(:base, "#{row_title} chromium_tag_well can't be blank") - end - - validate :dual_index_tag_set - # Ensure dual_index_tag_set column is not blank if it exists in the manifest - def dual_index_tag_set - return unless columns.present? && data.present? && columns.names.include?('dual_index_tag_set') && value('dual_index_tag_set').blank? - - errors.add(:base, "#{row_title} dual_index_tag_set can't be blank") - end - - validate :dual_index_tag_well - # Ensure dual_index_tag_well column is not blank if it exists in the manifest - def dual_index_tag_well - return unless columns.present? && data.present? && columns.names.include?('dual_index_tag_well') && value('dual_index_tag_well').blank? - - errors.add(:base, "#{row_title} dual_index_tag_well can't be blank") + warnings.add(:base, "#{row_title} i5 is blank! ") end delegate :present?, to: :sample, prefix: true diff --git a/app/views/sample_manifest_upload_with_tag_sequences/new.html.erb b/app/views/sample_manifest_upload_with_tag_sequences/new.html.erb index 92d1e8d7de..f56463ecd9 100644 --- a/app/views/sample_manifest_upload_with_tag_sequences/new.html.erb +++ b/app/views/sample_manifest_upload_with_tag_sequences/new.html.erb @@ -8,6 +8,16 @@ <% @uploader.errors.full_messages.each do |message| %>

<%= message %>

<% end %> + + <% if flash[:warnings].present? %> + <%= alert(:warning) do %> + <% flash[:warnings].each do |message| %> +

<%= message %>

+ <% end %> + <% end %> + <% end %> + + <% end %> <% end %> diff --git a/config/sample_manifest_excel/columns.yml b/config/sample_manifest_excel/columns.yml index 9096fd1dca..3245f2d206 100644 --- a/config/sample_manifest_excel/columns.yml +++ b/config/sample_manifest_excel/columns.yml @@ -44,7 +44,7 @@ i7: errorTitle: "i7" error: "i7 cannot be blank" conditional_formattings: - empty_mandatory_cell_2: + empty_warning_cell: i5: heading: i5 TAG SEQUENCE unlocked: true @@ -63,7 +63,7 @@ i5: promptTitle: "i5" prompt: "Input i5." conditional_formattings: - empty_mandatory_cell_2: + empty_warning_cell: tag_group: heading: TAG GROUP unlocked: true @@ -147,7 +147,7 @@ chromium_tag_group: prompt: "Input the name of a valid tag set. All samples in a library need to be tagged with the same tag set." range_name: :chromium_tag_groups conditional_formattings: - empty_mandatory_cell_2: + empty_cell: chromium_tag_well: heading: CHROMIUM TAG WELL unlocked: true @@ -165,7 +165,7 @@ chromium_tag_well: errorTitle: "Tag well" error: "Tag Index must be a well." conditional_formattings: - empty_mandatory_cell_2: + empty_cell: is_number: len: formula: @@ -245,7 +245,7 @@ dual_index_tag_set: prompt: "Input the name of a valid dual index tag plate." range_name: :dual_index_tag_sets conditional_formattings: - empty_mandatory_cell_2: + empty_cell: dual_index_tag_well: heading: DUAL INDEX TAG WELL unlocked: true @@ -263,7 +263,7 @@ dual_index_tag_well: errorTitle: "Dual index tag well" error: "Dual Index Tag must be a well." conditional_formattings: - empty_mandatory_cell_2: + empty_cell: is_number: len: formula: diff --git a/config/sample_manifest_excel/conditional_formattings.yml b/config/sample_manifest_excel/conditional_formattings.yml index 5dc77ad9d6..a633439a3c 100644 --- a/config/sample_manifest_excel/conditional_formattings.yml +++ b/config/sample_manifest_excel/conditional_formattings.yml @@ -16,7 +16,7 @@ empty_mandatory_cell: formula: "FALSE" operator: :equal priority: 1 -empty_mandatory_cell_2: +empty_warning_cell: style: bg_color: "FF991C" type: :dxf From 42b8ad1688fa2aa39aa1578c9d5be0318bf1b9d4 Mon Sep 17 00:00:00 2001 From: Ben Topping Date: Fri, 13 Feb 2026 14:13:43 +0000 Subject: [PATCH 21/57] refactor: removes cnda term from scrna core pooling plan to avoid confusion --- app/controllers/submissions_controller.rb | 6 +++--- app/models/ability/base_user.rb | 2 +- app/views/submissions/show.html.erb | 2 +- config/routes.rb | 2 +- spec/controllers/submissions_controller_spec.rb | 12 ++++++------ spec/features/submissions/submission_show_spec.rb | 8 ++++---- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index ea1fe6290b..818ec1d08b 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -115,7 +115,7 @@ def study @submissions = @study.submissions end - def download_scrna_core_cdna_pooling_plan # rubocop:disable Metrics/AbcSize,Metrics/MethodLength + def download_scrna_core_pooling_plan # rubocop:disable Metrics/AbcSize,Metrics/MethodLength begin submission = Submission.find(params[:id]) rescue ActiveRecord::RecordNotFound @@ -126,7 +126,7 @@ def download_scrna_core_cdna_pooling_plan # rubocop:disable Metrics/AbcSize,Metr unless submission.scrna_core_cdna_prep_gem_x_5p_submission? flash[:error] = - 'This submission does not have the correct template for downloading a scRNA Core cDNA pooling plan' + 'This submission does not have the correct template for downloading a scRNA Core pooling plan' redirect_to submission return end @@ -134,7 +134,7 @@ def download_scrna_core_cdna_pooling_plan # rubocop:disable Metrics/AbcSize,Metr # Generate the pooling plan CSV string using the ScrnaCoreCdnaPrepPoolingPlanGenerator module csv_string = Submission::ScrnaCoreCdnaPrepPoolingPlanGenerator.generate_pooling_plan(submission) - send_data csv_string, type: 'text/plain', filename: "#{params[:id]}_scrna_core_cdna_pooling_plan.csv", + send_data csv_string, type: 'text/plain', filename: "#{params[:id]}_scrna_core_pooling_plan.csv", disposition: 'attachment' end diff --git a/app/models/ability/base_user.rb b/app/models/ability/base_user.rb index eb3a2347fe..ffb0e74cd9 100644 --- a/app/models/ability/base_user.rb +++ b/app/models/ability/base_user.rb @@ -32,7 +32,7 @@ def grant_privileges # rubocop:todo Metrics/AbcSize, Metrics/MethodLength can %i[read create], Study can :print_asset_group_labels, Study, owners: { id: user.id } can :print_asset_group_labels, Study, managers: { id: user.id } - can %i[read create update edit download_scrna_core_cdna_pooling_plan], Submission + can %i[read create update edit download_scrna_core_pooling_plan], Submission can :read, [TagGroup, TagLayoutTemplate, TagSet] can %i[read update print_swipecard], User, { id: user.id } can %i[projects study_reports], User diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb index d61e652014..4abdef998b 100644 --- a/app/views/submissions/show.html.erb +++ b/app/views/submissions/show.html.erb @@ -4,7 +4,7 @@ add :menu, "Print labels for #{order.asset_group.name}" => print_study_asset_group_path(order.study,order.asset_group) unless order.asset_group.nil? end %> <%- add :menu, "Submissions Inbox" => submissions_path if can? :read, Submission -%> -<%- add :menu, "Download scRNA Core cDNA pooling plan" => download_scrna_core_cdna_pooling_plan_submission_path(@presenter.submission.id) if +<%- add :menu, "Download scRNA Core pooling plan" => download_scrna_core_pooling_plan_submission_path(@presenter.submission.id) if @presenter.submission.scrna_core_cdna_prep_gem_x_5p_submission? -%> diff --git a/config/routes.rb b/config/routes.rb index 34cc42a627..27acc5a666 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -320,7 +320,7 @@ get :study end member do - get :download_scrna_core_cdna_pooling_plan + get :download_scrna_core_pooling_plan post :change_priority post :cancel end diff --git a/spec/controllers/submissions_controller_spec.rb b/spec/controllers/submissions_controller_spec.rb index 77d89cedf0..304c9a9faf 100644 --- a/spec/controllers/submissions_controller_spec.rb +++ b/spec/controllers/submissions_controller_spec.rb @@ -357,7 +357,7 @@ end end - describe '#download_scrna_core_cdna_pooling_plan' do + describe '#download_scrna_core_pooling_plan' do before do @template = create(:submission_template, name: 'Limber-Htp - scRNA Core cDNA Prep GEM-X 5p') @study = create(:study, user: @user) @@ -370,13 +370,13 @@ end it 'downloads a pooling plan' do - get :download_scrna_core_cdna_pooling_plan, params: { id: @submission.id } + get :download_scrna_core_pooling_plan, params: { id: @submission.id } - expect(response.headers['Content-Disposition']).to include("#{@submission.id}_scrna_core_cdna_pooling_plan.csv") + expect(response.headers['Content-Disposition']).to include("#{@submission.id}_scrna_core_pooling_plan.csv") end it 'redirects with an error if the submission is not found' do - get :download_scrna_core_cdna_pooling_plan, params: { id: 'nonexistent' } + get :download_scrna_core_pooling_plan, params: { id: 'nonexistent' } expect(flash[:error]).to eq('Submission not found with id nonexistent') expect(response).to redirect_to(submissions_path) @@ -385,10 +385,10 @@ it 'redirects with an error if the submission does not have the correct template' do @submission.orders.first.update(template_name: 'Some other template') - get :download_scrna_core_cdna_pooling_plan, params: { id: @submission.id } + get :download_scrna_core_pooling_plan, params: { id: @submission.id } expect(flash[:error]).to eq( - 'This submission does not have the correct template for downloading a scRNA Core cDNA pooling plan' + 'This submission does not have the correct template for downloading a scRNA Core pooling plan' ) expect(response).to redirect_to(submission_path(@submission)) end diff --git a/spec/features/submissions/submission_show_spec.rb b/spec/features/submissions/submission_show_spec.rb index 73fce64c9b..ef8a05de06 100644 --- a/spec/features/submissions/submission_show_spec.rb +++ b/spec/features/submissions/submission_show_spec.rb @@ -29,7 +29,7 @@ expect(page).to have_link('Print labels for') expect(page).to have_link('Submissions Inbox') # This should only be visible for submissions with the correct scRNA template - expect(page).to have_no_link('Download scRNA Core cDNA pooling plan') + expect(page).to have_no_link('Download scRNA Core pooling plan') end end @@ -37,13 +37,13 @@ let(:template) { create(:submission_template, name: 'Limber-Htp - scRNA Core cDNA Prep GEM-X 5p') } it 'shows the correct sidebar links' do - expect(page).to have_link('Download scRNA Core cDNA pooling plan') + expect(page).to have_link('Download scRNA Core pooling plan') end it 'downloads the correct pooling plan' do - click_link 'Download scRNA Core cDNA pooling plan' + click_link 'Download scRNA Core pooling plan' expect(page.response_headers['Content-Disposition']).to include( - "#{submission.id}_scrna_core_cdna_pooling_plan.csv" + "#{submission.id}_scrna_core_pooling_plan.csv" ) end end From 5383dabfee4e451c66ee48cabf4c7501b80a1a9d Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 00:00:16 +0000 Subject: [PATCH 22/57] Update Node.js to version 24.13.1 --- .nvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nvmrc b/.nvmrc index 3fe3b1570a..32f8c50de0 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24.13.0 +24.13.1 From 87217c058276a63b3f803792b230cd64549e9d73 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Wed, 18 Feb 2026 11:14:35 +0000 Subject: [PATCH 23/57] feat: add built-in rails endpoint --- config/routes.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index c9c46204c2..d4db24d74e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,9 +4,12 @@ user_is_admin = ->(req) { User.find_by(id: req.session[:user])&.administrator? } root to: 'homes#show' - resource :health, only: [:show] resource :home, only: [:show] + # Health check endpoints + get 'health' => 'health#show', constraints: ->(req) { req.format == :json } # json with stats + get 'health' => 'rails/health#show', as: :rails_health_check # default Rails health check + resource :phi_x, only: [:show] do scope module: :phi_x do resources :stocks From 87ee016420997293c4f21cf54864e5bc32238696 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Wed, 18 Feb 2026 11:21:11 +0000 Subject: [PATCH 24/57] refactor: order and group routes --- config/routes.rb | 52 ++++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index d4db24d74e..9537b58216 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,7 +2,7 @@ Rails.application.routes.draw do # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html - user_is_admin = ->(req) { User.find_by(id: req.session[:user])&.administrator? } + # Home root to: 'homes#show' resource :home, only: [:show] @@ -10,18 +10,31 @@ get 'health' => 'health#show', constraints: ->(req) { req.format == :json } # json with stats get 'health' => 'rails/health#show', as: :rails_health_check # default Rails health check - resource :phi_x, only: [:show] do - scope module: :phi_x do - resources :stocks - resources :spiked_buffers - end - end - # Error handling endpoints get '/404', to: 'errors#not_found' get '/500', to: 'errors#internal_server_error' get '/503', to: 'errors#service_unavailable' + # Session authentication endpoints + match '/login' => 'sessions#login', :as => :login, :via => %i[get post] + match '/logout' => 'sessions#logout', :as => :logout, :via => %i[get post] + # this is for test only test/functional/authentication_controller_test.rb + # to be removed? + get 'authentication/open' + get 'authentication/restricted' + + # Feature flags + user_is_admin = ->(req) { User.find_by(id: req.session[:user])&.administrator? } + mount Flipper::UI.app => '/flipper', :constraints => user_is_admin + + # Search + resources :searches + resources :lab_searches + + get 'advanced_search' => 'advanced_search#index' + post 'advanced_search/search' => 'advanced_search#search' + + # API v1 mount Api::RootService.new => '/api/1' unless ENV['DISABLE_V1_API'] # @todo Update v2 resources exceptions to reflect resources (e.g., `, except: %i[update]` for `lot`), @@ -144,9 +157,6 @@ end end - match '/login' => 'sessions#login', :as => :login, :via => %i[get post] - match '/logout' => 'sessions#logout', :as => :logout, :via => %i[get post] - resources :plate_summaries, only: %i[index show] do collection { get :search } end @@ -347,8 +357,6 @@ end end - resources :searches - namespace :admin do resources :abilities, only: :index resources :custom_texts @@ -452,12 +460,8 @@ end end - resources :lab_searches resources :events - get 'advanced_search' => 'advanced_search#index' - post 'advanced_search/search' => 'advanced_search#search' - resources :workflows, only: [] do member do # Yes, this is every bit as horrible as it looks. @@ -471,6 +475,13 @@ collection { get :generate_manifest } end + resource :phi_x, only: [:show] do + scope module: :phi_x do + resources :stocks + resources :spiked_buffers + end + end + resources :asset_audits resources :qc_reports, except: %i[delete update] do @@ -630,11 +641,6 @@ resources :location_reports, only: %i[index show create] - # this is for test only test/functional/authentication_controller_test.rb - # to be removed? - get 'authentication/open' - get 'authentication/restricted' - resources :messengers, only: :show # We removed workflows, which broke study links. Some customers may have their own studies bookmarked @@ -649,8 +655,6 @@ end end - mount Flipper::UI.app => '/flipper', :constraints => user_is_admin - # Custom standalone route for bioscan control locations, allowing only # the POST request, migrated from the Lighthouse pickings endpoint. post 'bioscan_control_locations', to: 'bioscan_control_locations#create' From 01ca40bb30102495fc2c7f8f6ca729bfadd60900 Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Wed, 18 Feb 2026 15:07:33 +0000 Subject: [PATCH 25/57] save working version --- ...st_upload_with_tag_sequences_controller.rb | 15 +++++++----- .../stylesheets/all/sequencescape.scss | 21 +++++++++++++++++ .../new.html.erb | 23 +++++++++++++------ 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb b/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb index 5ac1fad099..9b86e672a3 100644 --- a/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb +++ b/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb @@ -12,11 +12,16 @@ def create if upload_manifest @rows_with_warnings = @uploader.upload.rows.select { |row| row.respond_to?(:warnings) && row.warnings.any? } + if @rows_with_warnings.any? + flash[:warnings] = 'Sample manifest uploaded with warnings!' + flash[:warning_messages] = + @rows_with_warnings.flat_map { |row| row.warnings.full_messages } + else + flash[:notice] = 'Sample manifest successfully uploaded.' + end + redirect_target = (@uploader.study.present? ? sample_manifests_study_path(@uploader.study) : sample_manifests_path) + redirect_to redirect_target - flash[:warnings] = - @rows_with_warnings.flat_map { |row| row.warnings.full_messages } - - success('Sample manifest successfully uploaded.') else error('Your sample manifest couldn\'t be uploaded.') end @@ -31,14 +36,12 @@ def create_uploader def upload_manifest @uploader = create_uploader @uploader.run! - # @rows_with_warnings = @uploader.upload.rows.select { |row| row.warnings.any? } end def success(message) flash[:notice] = message redirect_target = (@uploader.study.present? ? sample_manifests_study_path(@uploader.study) : sample_manifests_path) - # render :new redirect_to redirect_target end diff --git a/app/frontend/stylesheets/all/sequencescape.scss b/app/frontend/stylesheets/all/sequencescape.scss index 828ca5bfe4..9b7ccf0902 100644 --- a/app/frontend/stylesheets/all/sequencescape.scss +++ b/app/frontend/stylesheets/all/sequencescape.scss @@ -171,6 +171,27 @@ h3.card-header-custom { .alert-cancelled { @extend .alert-warning; } + +.alert-warnings { + background-color: orange; /* your preferred orange */ + color: black; + border-color: darkorange; +} + +.alert-warnings .alert-summary { + font-size: 1rem; + margin-bottom: 0.75rem; +} + +.alert-warnings .alert-details { + padding-left: 1.25rem; +} + +.alert-warnings .alert-details li { + margin-bottom: 0.4rem; + font-size: 0.95rem; +} + .text-notice, .text-passed { @extend .text-success; diff --git a/app/views/sample_manifest_upload_with_tag_sequences/new.html.erb b/app/views/sample_manifest_upload_with_tag_sequences/new.html.erb index f56463ecd9..5d2dbffcfb 100644 --- a/app/views/sample_manifest_upload_with_tag_sequences/new.html.erb +++ b/app/views/sample_manifest_upload_with_tag_sequences/new.html.erb @@ -8,16 +8,25 @@ <% @uploader.errors.full_messages.each do |message| %>

<%= message %>

<% end %> + <% end %> +<% end %> - <% if flash[:warnings].present? %> - <%= alert(:warning) do %> - <% flash[:warnings].each do |message| %> -

<%= message %>

- <% end %> - <% end %> - <% end %> +

======Warnings present? <%= flash[:warnings].present? %>

+

Warning messages present? <%= flash[:warning_messages].present? %>

+<% Rails.logger.info "flash[:warnings]: #{flash[:warnings].inspect}" %> +<% Rails.logger.info "flash[:warning_messages]: #{flash[:warning_messages].inspect}" %> +<% if flash[:warnings].present? %> + <%= alert(:warning) do %> +

<%= flash[:warnings] %>

+ <% if flash[:warning_messages].present? %> +
    + <% flash[:warning_messages].each do |message| %> +
  • <%= message %>
  • + <% end %> +
+ <% end %> <% end %> <% end %> From 3ab74d7ee2e496b7240d92522e854f96a2a8a983 Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Wed, 18 Feb 2026 15:35:53 +0000 Subject: [PATCH 26/57] save changes --- ...st_upload_with_tag_sequences_controller.rb | 6 +++--- .../stylesheets/all/sequencescape.scss | 20 ++++++------------- .../sample_manifest_excel/upload/row.rb | 1 + 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb b/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb index 9b86e672a3..faabac196d 100644 --- a/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb +++ b/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb @@ -11,17 +11,17 @@ def create if upload_manifest @rows_with_warnings = - @uploader.upload.rows.select { |row| row.respond_to?(:warnings) && row.warnings.any? } + @uploader.upload.rows.select { |row| row.respond_to?(:warnings) && row.warnings.any? } if @rows_with_warnings.any? flash[:warnings] = 'Sample manifest uploaded with warnings!' - flash[:warning_messages] = + flash[:warning_messages] = @rows_with_warnings.flat_map { |row| row.warnings.full_messages } else flash[:notice] = 'Sample manifest successfully uploaded.' end redirect_target = (@uploader.study.present? ? sample_manifests_study_path(@uploader.study) : sample_manifests_path) redirect_to redirect_target - + else error('Your sample manifest couldn\'t be uploaded.') end diff --git a/app/frontend/stylesheets/all/sequencescape.scss b/app/frontend/stylesheets/all/sequencescape.scss index 9b7ccf0902..4157b7c6c0 100644 --- a/app/frontend/stylesheets/all/sequencescape.scss +++ b/app/frontend/stylesheets/all/sequencescape.scss @@ -172,24 +172,16 @@ h3.card-header-custom { @extend .alert-warning; } -.alert-warnings { - background-color: orange; /* your preferred orange */ +.alert-warnings, +.alert-warning_messages { + background-color: orange; color: black; border-color: darkorange; } -.alert-warnings .alert-summary { - font-size: 1rem; - margin-bottom: 0.75rem; -} - -.alert-warnings .alert-details { - padding-left: 1.25rem; -} - -.alert-warnings .alert-details li { - margin-bottom: 0.4rem; - font-size: 0.95rem; +.alert-warnings { + font-size: 20px; /* or whatever size you want */ + font-weight: 600; /* optional, makes it stand out */ } .text-notice, diff --git a/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb b/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb index 8ff9a4d900..b166e145bc 100644 --- a/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb +++ b/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb @@ -15,6 +15,7 @@ class Row # rubocop:todo Metrics/ClassLength attr_accessor :number, :data, :columns, :cache attr_reader :sanger_sample_id + # attr_reader :warnings # def initialize(attributes = {}) From ceecf8bfac7e3de5ca73e54946301427201a948e Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Wed, 18 Feb 2026 16:12:16 +0000 Subject: [PATCH 27/57] fix linting --- app/frontend/stylesheets/all/sequencescape.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/frontend/stylesheets/all/sequencescape.scss b/app/frontend/stylesheets/all/sequencescape.scss index 4157b7c6c0..842cbd1564 100644 --- a/app/frontend/stylesheets/all/sequencescape.scss +++ b/app/frontend/stylesheets/all/sequencescape.scss @@ -180,8 +180,8 @@ h3.card-header-custom { } .alert-warnings { - font-size: 20px; /* or whatever size you want */ - font-weight: 600; /* optional, makes it stand out */ + font-size: 20px; /* or whatever size you want */ + font-weight: 600; /* optional, makes it stand out */ } .text-notice, From 6625abaf5ed02ebdb9e2fc8f5d107ca80f0b11e9 Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Wed, 18 Feb 2026 16:41:46 +0000 Subject: [PATCH 28/57] remove redundant codes --- .../new.html.erb | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/app/views/sample_manifest_upload_with_tag_sequences/new.html.erb b/app/views/sample_manifest_upload_with_tag_sequences/new.html.erb index 5d2dbffcfb..4eaec4ed60 100644 --- a/app/views/sample_manifest_upload_with_tag_sequences/new.html.erb +++ b/app/views/sample_manifest_upload_with_tag_sequences/new.html.erb @@ -1,4 +1,3 @@ - <%= render partial: "side_links" %> <%= page_title "Sample Manifests", "New and upload" %> @@ -11,25 +10,6 @@ <% end %> <% end %> -

======Warnings present? <%= flash[:warnings].present? %>

-

Warning messages present? <%= flash[:warning_messages].present? %>

-<% Rails.logger.info "flash[:warnings]: #{flash[:warnings].inspect}" %> -<% Rails.logger.info "flash[:warning_messages]: #{flash[:warning_messages].inspect}" %> - -<% if flash[:warnings].present? %> - <%= alert(:warning) do %> -

<%= flash[:warnings] %>

- - <% if flash[:warning_messages].present? %> -
    - <% flash[:warning_messages].each do |message| %> -
  • <%= message %>
  • - <% end %> -
- <% end %> - <% end %> -<% end %> - <%= render partial: "upload" %> <%= pagination @sample_manifests %> From e46dd53f26dae4d9ea6ec94b0ba65975cd2217fe Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Wed, 18 Feb 2026 18:13:19 +0000 Subject: [PATCH 29/57] remove duplicate warnings --- ...ample_manifest_upload_with_tag_sequences_controller.rb | 2 +- .../sample_manifest_excel/upload/row.rb | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb b/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb index faabac196d..5be3e155c8 100644 --- a/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb +++ b/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb @@ -15,7 +15,7 @@ def create if @rows_with_warnings.any? flash[:warnings] = 'Sample manifest uploaded with warnings!' flash[:warning_messages] = - @rows_with_warnings.flat_map { |row| row.warnings.full_messages } + @rows_with_warnings.flat_map { |row| row.warnings.full_messages }.uniq else flash[:notice] = 'Sample manifest successfully uploaded.' end diff --git a/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb b/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb index b166e145bc..f85c9c7643 100644 --- a/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb +++ b/app/sample_manifest_excel/sample_manifest_excel/upload/row.rb @@ -16,14 +16,6 @@ class Row # rubocop:todo Metrics/ClassLength attr_accessor :number, :data, :columns, :cache attr_reader :sanger_sample_id - # attr_reader :warnings - - # def initialize(attributes = {}) - # super - # @warnings = ActiveModel::Errors.new(self) - # end - - # @return [ActiveModel::Errors] the warnings collection def warnings @warnings ||= ActiveModel::Errors.new(self) end From 330d88e71d5c36842cb1d78af36bdd51f2d3e2ff Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Wed, 18 Feb 2026 21:41:24 +0000 Subject: [PATCH 30/57] refactor to fix linting --- ...st_upload_with_tag_sequences_controller.rb | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb b/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb index 5be3e155c8..154a463d26 100644 --- a/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb +++ b/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb @@ -10,18 +10,7 @@ def create return error('No file attached') if params[:upload].blank? if upload_manifest - @rows_with_warnings = - @uploader.upload.rows.select { |row| row.respond_to?(:warnings) && row.warnings.any? } - if @rows_with_warnings.any? - flash[:warnings] = 'Sample manifest uploaded with warnings!' - flash[:warning_messages] = - @rows_with_warnings.flat_map { |row| row.warnings.full_messages }.uniq - else - flash[:notice] = 'Sample manifest successfully uploaded.' - end - redirect_target = (@uploader.study.present? ? sample_manifests_study_path(@uploader.study) : sample_manifests_path) - redirect_to redirect_target - + set_upload_flash_message else error('Your sample manifest couldn\'t be uploaded.') end @@ -38,6 +27,26 @@ def upload_manifest @uploader.run! end + def set_upload_flash_message + warning_rows = rows_with_warnings + return success('Sample manifest successfully uploaded.') if warning_rows.empty? + + apply_warning_flash(warning_rows) + end + + def rows_with_warnings + @uploader.upload.rows.select do |row| + row.respond_to?(:warnings) && row.warnings.any? + end + end + + def apply_warning_flash(rows) + flash[:warnings] = 'Sample manifest uploaded with warnings!' + flash[:warning_messages] = rows.flat_map { |row| row.warnings.full_messages }.uniq + redirect_target = (@uploader.study.present? ? sample_manifests_study_path(@uploader.study) : sample_manifests_path) + redirect_to redirect_target + end + def success(message) flash[:notice] = message redirect_target = (@uploader.study.present? ? sample_manifests_study_path(@uploader.study) : sample_manifests_path) From 7da2aaa1e482bf0793284b0710da0174b0f9613e Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Thu, 19 Feb 2026 15:16:25 +0000 Subject: [PATCH 31/57] update the i7/i5 conditional formatting --- spec/data/sample_manifest_excel/columns.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/spec/data/sample_manifest_excel/columns.yml b/spec/data/sample_manifest_excel/columns.yml index f6727872f4..83329b9978 100644 --- a/spec/data/sample_manifest_excel/columns.yml +++ b/spec/data/sample_manifest_excel/columns.yml @@ -37,7 +37,7 @@ i7: promptTitle: "i7" prompt: "Input i7." conditional_formattings: - empty_cell: + empty_warning_cell: i5: heading: i5 TAG SEQUENCE unlocked: true @@ -50,6 +50,8 @@ i5: showInputMessage: true promptTitle: "i5" prompt: "i7." + conditional_formattings: + empty_warning_cell: tag_group: heading: TAG GROUP unlocked: true @@ -127,7 +129,7 @@ chromium_tag_group: prompt: "Input the name of a valid tag set. All samples in a library need to be tagged with the same tag set." range_name: :chromium_tag_groups conditional_formattings: - empty_mandatory_cell_2: + empty_cell: chromium_tag_well: heading: CHROMIUM TAG WELL unlocked: true @@ -145,7 +147,7 @@ chromium_tag_well: errorTitle: "Tag well" error: "Tag Index must be a well." conditional_formattings: - empty_mandatory_cell_2: + empty_cell: is_number: len: formula: @@ -184,7 +186,7 @@ dual_index_tag_set: prompt: "Input the name of a valid dual index tag plate." range_name: :dual_index_tag_sets conditional_formattings: - empty_mandatory_cell_2: + empty_cell: dual_index_tag_well: heading: DUAL INDEX TAG WELL unlocked: true @@ -202,7 +204,7 @@ dual_index_tag_well: errorTitle: "Dual index tag well" error: "Dual Index Tag must be a well." conditional_formattings: - empty_mandatory_cell_2: + empty_cell: is_number: len: formula: From 540115d142e4765abb384902859ce3e28b804ecc Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Thu, 19 Feb 2026 16:09:27 +0000 Subject: [PATCH 32/57] add new conditional formatings to test data files --- .../sample_manifest_excel/conditional_formattings.yml | 9 +++++++++ .../extract/conditional_formattings.yml | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/spec/data/sample_manifest_excel/conditional_formattings.yml b/spec/data/sample_manifest_excel/conditional_formattings.yml index 1554673165..a633439a3c 100644 --- a/spec/data/sample_manifest_excel/conditional_formattings.yml +++ b/spec/data/sample_manifest_excel/conditional_formattings.yml @@ -16,6 +16,15 @@ empty_mandatory_cell: formula: "FALSE" operator: :equal priority: 1 +empty_warning_cell: + style: + bg_color: "FF991C" + type: :dxf + options: + type: :cellIs + formula: "FALSE" + operator: :equal + priority: 1 len: style: bg_color: "FF0000" diff --git a/spec/data/sample_manifest_excel/extract/conditional_formattings.yml b/spec/data/sample_manifest_excel/extract/conditional_formattings.yml index 939fb024b0..aa4d6a6930 100644 --- a/spec/data/sample_manifest_excel/extract/conditional_formattings.yml +++ b/spec/data/sample_manifest_excel/extract/conditional_formattings.yml @@ -7,6 +7,15 @@ empty_cell: formula: "FALSE" operator: :equal priority: 1 +empty_warning_cell: + style: + bg_color: "FF991C" + type: :dxf + options: + type: :cellIs + formula: "FALSE" + operator: :equal + priority: 1 len: style: bg_color: "FF0000" From 409c514e770d38dd92e2f3bbcb2aace4aeab7c8a Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Thu, 19 Feb 2026 19:25:22 +0000 Subject: [PATCH 33/57] remove not valid test --- spec/sample_manifest_excel/upload/row_spec.rb | 45 ------------------- 1 file changed, 45 deletions(-) diff --git a/spec/sample_manifest_excel/upload/row_spec.rb b/spec/sample_manifest_excel/upload/row_spec.rb index 24b464653e..5b7f489605 100644 --- a/spec/sample_manifest_excel/upload/row_spec.rb +++ b/spec/sample_manifest_excel/upload/row_spec.rb @@ -101,47 +101,6 @@ 'Unknown' ] end - let(:data_without_i7_i5) do - [ - tube.human_barcode, - sample_manifest.sample_manifest_assets.first.sanger_sample_id, - '', - '', - 'My reference genome', - 'My New Library Type', - 200, - 1500, - 'SCG--1222_A01', - '', - 1, - 1, - 'Unknown', - '', - '', - '', - 'Cell Line', - 'Nov-16', - 'Nov-16', - '', - 'No', - '', - 'OTHER', - '', - '', - '', - '', - '', - 'SCG--1222_A01', - 9606, - 'Homo sapiens', - '', - '', - '', - '', - 11, - 'Unknown' - ] - end it 'is not valid without row number' do expect(described_class.new(number: 'one', data: data, columns: columns)).not_to be_valid @@ -156,10 +115,6 @@ expect(described_class.new(number: 1, data: data)).not_to be_valid end - it 'is not valid without i7/i5 if i7/i5 column exists' do - expect(described_class.new(number: 1, data: data_without_i7_i5, columns: columns)).not_to be_valid - end - it '#value returns value for specified key' do expect(described_class.new(number: 1, data: data, columns: columns).value(:sanger_sample_id)).to eq( sample_manifest.labware.first.sample_manifest_assets.first.sanger_sample_id From 0dcc9b8c13ee5623a37f79ac2740ca64ad41df7b Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Thu, 19 Feb 2026 21:03:12 +0000 Subject: [PATCH 34/57] fix tests --- ...load_with_tag_sequences_controller_spec.rb | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/spec/controllers/sample_manifest_upload_with_tag_sequences_controller_spec.rb b/spec/controllers/sample_manifest_upload_with_tag_sequences_controller_spec.rb index f117068b4e..f3c51fb7a7 100644 --- a/spec/controllers/sample_manifest_upload_with_tag_sequences_controller_spec.rb +++ b/spec/controllers/sample_manifest_upload_with_tag_sequences_controller_spec.rb @@ -6,7 +6,7 @@ RSpec.describe SampleManifestUploadWithTagSequencesController, type: :controller do let(:user) { create(:user) } let(:upload_file) { 'pretend-this-is-an-actual-file' } - let(:uploader) { instance_double(SampleManifest::Uploader, run!: true, study: nil) } + let(:uploader) { instance_double(SampleManifest::Uploader, study: nil) } before do allow(controller).to receive(:current_user).and_return(user) @@ -26,8 +26,15 @@ end end - context 'when the file is uploaded successfully' do - before { post :create, params: { upload: upload_file } } + context 'when the file is uploaded successfully without warning' do + before do + allow(controller).to receive(:upload_manifest) do + controller.instance_variable_set(:@uploader, uploader) + true + end + allow(controller).to receive(:rows_with_warnings).and_return([]) + post :create, params: { upload: upload_file } + end it 'sets a success flash message' do expect(flash[:notice]).to eq('Sample manifest successfully uploaded.') @@ -38,6 +45,33 @@ end end + context 'when upload succeeds with warnings' do + let(:warning_row) do + double(warnings: double(full_messages: ['Row 10 warning'])) + end + + before do + allow(controller).to receive(:upload_manifest) do + controller.instance_variable_set(:@uploader, uploader) + true + end + allow(controller).to receive(:rows_with_warnings).and_return([warning_row]) + post :create, params: { upload: upload_file } + end + + it 'sets warning flash message' do + expect(flash[:warnings]).to eq('Sample manifest uploaded with warnings!') + end + + it 'sets warning messages' do + expect(flash[:warning_messages]).to eq(['Row 10 warning']) + end + + it 'redirects correctly' do + expect(response).to redirect_to(sample_manifests_path) + end + end + context 'when the upload fails due to invalid data' do before do allow(uploader).to receive(:run!).and_raise(AccessionService::AccessionValidationFailed, 'Invalid data') From c60a6700b5f24c5a7ab085975854d6e498bedfa9 Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Thu, 19 Feb 2026 21:38:06 +0000 Subject: [PATCH 35/57] add LCM Triomics RNASeq template --- .../014_lcm_triomics_submission_templates.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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..3a3006a896 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 @@ -1,7 +1,7 @@ # Submission templates for LCM Triomics WGS and EMSeq --- # LCM Triomics EMSeq submission template -Limber-Htp - LCM Triomics: +Limber-Htp - LCM Triomics EMSeq: submission_class_name: "LinearSubmission" related_records: request_type_keys: ["limber_lcm_triomics_emseq"] @@ -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 +# LCM Triomics RNASeq submission template +Limber-Htp - LCM Triomics RNASeq: + submission_class_name: "LinearSubmission" + related_records: + request_type_keys: ["limber_lcm_triomics_rnaseq"] + product_line_name: LCM Triomics + product_catalogue_name: LCM Triomics \ No newline at end of file From e4f9c3728d9951bed2c4337227957f87ca7cf539 Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Thu, 19 Feb 2026 21:40:28 +0000 Subject: [PATCH 36/57] fix linting --- .../014_lcm_triomics_submission_templates.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3a3006a896..4e99ca8cb3 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 @@ -20,4 +20,4 @@ Limber-Htp - LCM Triomics RNASeq: related_records: request_type_keys: ["limber_lcm_triomics_rnaseq"] product_line_name: LCM Triomics - product_catalogue_name: LCM Triomics \ No newline at end of file + product_catalogue_name: LCM Triomics From 8778e37b5dea801e00c18d20df4953a4c6138e40 Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Fri, 20 Feb 2026 13:03:55 +0000 Subject: [PATCH 37/57] add request type etc --- .../018_limber_lcm_triomics_request_types.yml | 12 ++++++++++++ .../014_lcm_triomics_submission_templates.yml | 2 ++ 2 files changed, 14 insertions(+) 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..f1300f6b1f 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 @@ -12,6 +12,18 @@ limber_lcm_triomics_emseq: - LCMT Lysate library_types: - emSEQ +limber_lcm_triomics_rnaseq: + name: LCM Triomics RNASeq + 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 limber_lcm_triomics_wgs: name: LCM Triomics WGS asset_type: Well 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 4e99ca8cb3..ee6a0fba63 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 @@ -7,6 +7,7 @@ Limber-Htp - LCM Triomics EMSeq: request_type_keys: ["limber_lcm_triomics_emseq"] product_line_name: LCM Triomics product_catalogue_name: LCM Triomics + project_name: "UAT Study" # LCM Triomics WGS submission template Limber-Htp - LCM Triomics WGS: submission_class_name: "LinearSubmission" @@ -21,3 +22,4 @@ Limber-Htp - LCM Triomics RNASeq: request_type_keys: ["limber_lcm_triomics_rnaseq"] product_line_name: LCM Triomics product_catalogue_name: LCM Triomics + project_name: "UAT Study" From 7144dca74f3d0343ccc697185882f475200e8d2c Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Fri, 20 Feb 2026 16:16:45 +0000 Subject: [PATCH 38/57] add project name to submission template --- .../014_lcm_triomics_submission_templates.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ee6a0fba63..510d955d5a 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 @@ -7,7 +7,7 @@ Limber-Htp - LCM Triomics EMSeq: request_type_keys: ["limber_lcm_triomics_emseq"] product_line_name: LCM Triomics product_catalogue_name: LCM Triomics - project_name: "UAT Study" + project_name: "UAT Project" # LCM Triomics WGS submission template Limber-Htp - LCM Triomics WGS: submission_class_name: "LinearSubmission" @@ -22,4 +22,4 @@ Limber-Htp - LCM Triomics RNASeq: request_type_keys: ["limber_lcm_triomics_rnaseq"] product_line_name: LCM Triomics product_catalogue_name: LCM Triomics - project_name: "UAT Study" + project_name: "UAT Project" From 01e59f0ff084054bbc9f7d734a77e41342b9a884 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 19:00:19 +0000 Subject: [PATCH 39/57] Update mocha to version 3.0.2 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index cf0a6863c4..9759f2369f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -327,7 +327,7 @@ GEM minitest-profiler (0.0.2) activesupport (>= 4.1.0) minitest (>= 5.3.3) - mocha (3.0.1) + mocha (3.0.2) ruby2_keywords (>= 0.0.5) msgpack (1.8.0) multi_json (1.17.0) From 546e842479b6af4374965342aeb37bc56bfa56df Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Mon, 23 Feb 2026 14:26:10 +0000 Subject: [PATCH 40/57] fix: allow flashes to render described lists --- .../stylesheets/all/sequencescape.scss | 5 +- app/helpers/application_helper.rb | 34 ++++++++++--- spec/helpers/application_helper_spec.rb | 51 +++++++++++++++++++ 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/app/frontend/stylesheets/all/sequencescape.scss b/app/frontend/stylesheets/all/sequencescape.scss index 842cbd1564..3725e6aa48 100644 --- a/app/frontend/stylesheets/all/sequencescape.scss +++ b/app/frontend/stylesheets/all/sequencescape.scss @@ -149,7 +149,10 @@ h3.card-header-custom { } } -.alert-error ul { +.alert-error ul, +.alert-warning ul, +.alert-notice ul, +.alert-success ul { margin: 0; } diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 9daa0f8348..67059c53e5 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -76,17 +76,37 @@ def render_flashes nil end - # A helper method for render_flashes - If multiple messages, render them as a list, else render as a single div - # @param messages [Array, String] The flash message or messages to be rendered - def render_message(messages) - messages = Array(messages) - if messages.size > 1 - tag.ul { messages.each { |m| concat tag.li(m) } } + # A helper method for render_flashes. + # If messages is a Hash, renders them as a described list, with the keys as the description and + # the values as the items. + # If messages is an Array with multiple messages, renders them as a list. + # If a single message, renders as a single div. + # + # @param messages [Hash, Array, String] The flash message or messages to be rendered + # @return [ActiveSupport::SafeBuffer] HTML-safe string for rendering the messages + def render_message(messages) # rubocop:disable Metrics/MethodLength + case messages + when Hash + safe_join( + messages.map do |description, items| + tag.div(description) + render_in_list(Array(items)) + end + ) + when Array + if messages.size > 1 + render_in_list(messages) + else # messages has only one element, render it as a single div + tag.div(messages.first) + end else - tag.div(messages.first) + tag.div(messages) end end + def render_in_list(messages) + tag.ul { messages.each { |message| concat tag.li(message) } } + end + def api_data { api_version: RELEASE.api_version } end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index a44d79bafb..eda801fb55 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -109,4 +109,55 @@ end end end + + describe '#render_message' do + let(:html) { helper.render_message(messages) } + + context 'when messages is a Hash' do + let(:messages) { { 'Description 1' => ['Item 1', 'Item 2'], 'Description 2' => 'Single Item' } } + + it 'renders each key as a div and each value as a list' do + expect(html).to include('
Description 1
') + .and include('
  • Item 1
  • ') + .and include('
  • Item 2
  • ') + .and include('
    Description 2
    ') + .and include('
  • Single Item
  • ') + end + end + + context 'when messages is an Array with multiple items' do + let(:messages) { ['Error 1', 'Error 2'] } + + it 'renders the messages as a list' do + expect(html).to include('
      ') + .and include('
    • Error 1
    • ') + .and include('
    • Error 2
    • ') + end + end + + context 'when messages is an Array with one item' do + let(:messages) { ['Only one error'] } + + it 'renders the single message as a div' do + expect(html).to include('
      Only one error
      ') + end + end + + context 'when messages is a String' do + let(:messages) { 'Just a string' } + + it 'renders the string as a div' do + expect(html).to include('
      Just a string
      ') + end + end + end + + describe '#render_in_list' do + it 'renders an array of messages as a list' do + html = helper.render_in_list(%w[foo bar]) + expect(html).to include('
        ') + .and include('
      • foo
      • ') + .and include('
      • bar
      • ') + end + end end From 52a9761b242c8f029625fa934674e3e93ac8a273 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Mon, 23 Feb 2026 14:33:50 +0000 Subject: [PATCH 41/57] refactor: use new hash-style flash --- ...sample_manifest_upload_with_tag_sequences_controller.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb b/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb index 154a463d26..e319f65aa4 100644 --- a/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb +++ b/app/controllers/sample_manifest_upload_with_tag_sequences_controller.rb @@ -41,8 +41,11 @@ def rows_with_warnings end def apply_warning_flash(rows) - flash[:warnings] = 'Sample manifest uploaded with warnings!' - flash[:warning_messages] = rows.flat_map { |row| row.warnings.full_messages }.uniq + flash[:warning] = { + 'Sample manifest uploaded with warnings!': + rows.flat_map { |row| row.warnings.full_messages }.uniq + } + redirect_target = (@uploader.study.present? ? sample_manifests_study_path(@uploader.study) : sample_manifests_path) redirect_to redirect_target end From 9dca0713cc9ea370dcf9d25e96719178b44fff79 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Mon, 23 Feb 2026 14:34:59 +0000 Subject: [PATCH 42/57] refactor: remove unused css classes --- app/frontend/stylesheets/all/sequencescape.scss | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/app/frontend/stylesheets/all/sequencescape.scss b/app/frontend/stylesheets/all/sequencescape.scss index 3725e6aa48..f385b3be1c 100644 --- a/app/frontend/stylesheets/all/sequencescape.scss +++ b/app/frontend/stylesheets/all/sequencescape.scss @@ -175,18 +175,6 @@ h3.card-header-custom { @extend .alert-warning; } -.alert-warnings, -.alert-warning_messages { - background-color: orange; - color: black; - border-color: darkorange; -} - -.alert-warnings { - font-size: 20px; /* or whatever size you want */ - font-weight: 600; /* optional, makes it stand out */ -} - .text-notice, .text-passed { @extend .text-success; From 72ac228261ab38fdba886d5da0d567135f2966a3 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Mon, 23 Feb 2026 14:51:39 +0000 Subject: [PATCH 43/57] test: update specs --- ...le_manifest_upload_with_tag_sequences_controller_spec.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/spec/controllers/sample_manifest_upload_with_tag_sequences_controller_spec.rb b/spec/controllers/sample_manifest_upload_with_tag_sequences_controller_spec.rb index f3c51fb7a7..c4815f3947 100644 --- a/spec/controllers/sample_manifest_upload_with_tag_sequences_controller_spec.rb +++ b/spec/controllers/sample_manifest_upload_with_tag_sequences_controller_spec.rb @@ -60,11 +60,7 @@ end it 'sets warning flash message' do - expect(flash[:warnings]).to eq('Sample manifest uploaded with warnings!') - end - - it 'sets warning messages' do - expect(flash[:warning_messages]).to eq(['Row 10 warning']) + expect(flash[:warning]).to eq({ 'Sample manifest uploaded with warnings!': ['Row 10 warning'] }) end it 'redirects correctly' do From abfd39bb1c6b98cb0c7c0a6305b7f43dd236ce35 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:35:14 +0000 Subject: [PATCH 44/57] Update vite-plugin-ruby to version 5.1.3 --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2ea540ed43..c62ae64dd0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2249,9 +2249,9 @@ util-deprecate@^1.0.2, util-deprecate@~1.0.1: integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== "vite-plugin-ruby@>=5.1.0 <5.1.2 || ^5.1.3 || ^5.2": - version "5.1.1" - resolved "https://registry.yarnpkg.com/vite-plugin-ruby/-/vite-plugin-ruby-5.1.1.tgz#ecd72591ddb90a23613051005bd70a6410945129" - integrity sha512-I1dXJq2ywdvTD2Cz5LYNcYLujqQ3eUxPoCjruRdfm2QBtHBY15NEeb6x5HuPM3T5S+y8S3p9fwRsieQQCjk0gg== + version "5.1.3" + resolved "https://registry.yarnpkg.com/vite-plugin-ruby/-/vite-plugin-ruby-5.1.3.tgz#96c0dcd9fadb7a941062cc93a411f1e4d5eb8907" + integrity sha512-vpTOKbR6AmnupmXvQccRcr/jIY+oobNuCbNJHUzkT8oQ2BBQSlp22Xec3zszBBc7GjwDGn2+E+IQoNRXdEY7Ig== dependencies: debug "^4.3.4" fast-glob "^3.3.2" From 8b593883fa4b770c339439e32c221e3d043e3594 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 04:20:21 +0000 Subject: [PATCH 45/57] Update ruby-prof to version 2.0.2 --- Gemfile.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index cf0a6863c4..b77d38d35a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -542,8 +542,9 @@ GEM rubocop-rspec (~> 3.5) ruby-graphviz (1.2.5) rexml - ruby-prof (1.7.2) + ruby-prof (2.0.2) base64 + ostruct ruby-progressbar (1.13.0) ruby-units (4.1.0) ruby-vips (2.2.3) From 399d60b02e40c3b764ed9296593b8327103fb4c7 Mon Sep 17 00:00:00 2001 From: Ben Topping Date: Tue, 24 Feb 2026 11:23:23 +0000 Subject: [PATCH 46/57] fix: makes creation_requests return consistent type --- app/models/multiplexed_library_tube.rb | 2 +- spec/models/broadcast_event/pool_released_spec.rb | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/models/multiplexed_library_tube.rb b/app/models/multiplexed_library_tube.rb index 8c8d3ca9d6..82ea0fafce 100644 --- a/app/models/multiplexed_library_tube.rb +++ b/app/models/multiplexed_library_tube.rb @@ -43,6 +43,6 @@ def creation_requests # Parents should exist but in the case they don't (e.g. asset_links are yet to be created) # we want to avoid an error and just return an empty array. - parents.includes(:requests_as_target).first&.requests_as_target + parents.includes(:requests_as_target).first&.requests_as_target || [] end end diff --git a/spec/models/broadcast_event/pool_released_spec.rb b/spec/models/broadcast_event/pool_released_spec.rb index 9b8e3a71cb..65cf464fe5 100644 --- a/spec/models/broadcast_event/pool_released_spec.rb +++ b/spec/models/broadcast_event/pool_released_spec.rb @@ -113,13 +113,20 @@ expect(metadata['team']).not_to eq('UNKNOWN') end - # This is an edge case, but we want to make sure that if there are no creation requests or ancestors + # These are edge cases, but we want to make sure that if there are no creation requests or ancestors # we don't error out and instead return 'UNKNOWN' for the team. - it 'has an unknown team if tube has no creation requests' do + it 'has an unknown team if tube has no parents' do tube.ancestors = [] tube.save! expect(metadata['team']).to eq('UNKNOWN') end + + it 'has an unknown team if tube has a parent with no requests_as_target' do + # This is the requests_as_target for the source plate + library_request.destroy + + expect(metadata['team']).to eq('UNKNOWN') + end end end From 487159c3f0551995dd1282b86ebb5aff820fbb80 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 19:00:22 +0000 Subject: [PATCH 47/57] Update rspec-rails to version 8.0.3 --- Gemfile.lock | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index c33cd11771..93af492a05 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -133,7 +133,7 @@ GEM backports (3.25.2) base64 (0.3.0) benchmark (0.5.0) - bigdecimal (3.3.1) + bigdecimal (4.0.1) bootsnap (1.22.0) msgpack (~> 1.2) builder (3.3.0) @@ -167,7 +167,7 @@ GEM marcel (~> 1.0) nokogiri (~> 1.10, >= 1.10.4) rubyzip (>= 2.4, < 4) - cgi (0.5.0) + cgi (0.5.1) childprocess (5.1.0) logger (~> 1.5) choice (0.2.0) @@ -272,14 +272,15 @@ GEM http-accept (1.7.0) http-cookie (1.1.0) domain_name (~> 0.5) - i18n (1.14.7) + i18n (1.14.8) concurrent-ruby (~> 1.0) image_processing (1.14.0) mini_magick (>= 4.9.5, < 6) ruby-vips (>= 2.0.17, < 3) - io-console (0.8.1) - irb (1.15.3) + io-console (0.8.2) + irb (1.17.0) pp (>= 0.6.0) + prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) json (2.18.1) @@ -302,7 +303,7 @@ GEM rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) logger (1.7.0) - loofah (2.24.1) + loofah (2.25.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.9.0) @@ -430,8 +431,8 @@ GEM activesupport (>= 4.2) choice (~> 0.2.0) ruby-graphviz (~> 1.2) - rails-html-sanitizer (1.6.2) - loofah (~> 2.21) + rails-html-sanitizer (1.7.0) + loofah (~> 2.25) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) rails-perftest (0.0.7) railties (7.2.3) @@ -454,7 +455,7 @@ GEM rbtree (0.4.6) rdoc (6.3.4.1) regexp_parser (2.11.3) - reline (0.6.2) + reline (0.6.3) io-console (~> 0.5) rest-client (2.1.0) http-accept (>= 1.7.0, < 2.0) @@ -474,9 +475,9 @@ GEM rspec-mocks (~> 3.13.0) rspec-collection_matchers (1.2.1) rspec-expectations (>= 2.99.0.beta1) - rspec-core (3.13.3) + rspec-core (3.13.6) rspec-support (~> 3.13.0) - rspec-expectations (3.13.3) + rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-github (3.0.0) @@ -487,10 +488,10 @@ GEM rspec-json_expectations (2.2.0) rspec-longrun (3.1.0) rspec-core (>= 3.5.0, < 4) - rspec-mocks (3.13.2) + rspec-mocks (3.13.7) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (8.0.2) + rspec-rails (8.0.3) actionpack (>= 7.2) activesupport (>= 7.2) railties (>= 7.2) @@ -498,7 +499,7 @@ GEM rspec-expectations (~> 3.13) rspec-mocks (~> 3.13) rspec-support (~> 3.13) - rspec-support (3.13.2) + rspec-support (3.13.7) rubocop (1.84.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) @@ -605,7 +606,7 @@ GEM logger temple (0.10.4) test-prof (1.5.2) - thor (1.4.0) + thor (1.5.0) tilt (2.6.1) timecop (0.9.10) timeout (0.4.4) @@ -634,7 +635,7 @@ GEM addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webrick (1.9.1) + webrick (1.9.2) websocket (1.2.11) websocket-driver (0.8.0) base64 @@ -656,7 +657,7 @@ GEM ostruct rainbow yard - zeitwerk (2.7.3) + zeitwerk (2.7.5) PLATFORMS arm64-darwin From c7eaab48126a7b77896c0d547b27f8f28123d7f4 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 03:15:23 +0000 Subject: [PATCH 48/57] Update selenium-webdriver to version 4.41.0 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 93af492a05..e5f70abc1f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -562,7 +562,7 @@ GEM crass (~> 1.0.2) nokogiri (>= 1.16.8) securerandom (0.4.1) - selenium-webdriver (4.40.0) + selenium-webdriver (4.41.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) From 34defdd38f2dc26366925a9a5507a63aec578bb5 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 19:00:21 +0000 Subject: [PATCH 49/57] Update bootsnap to version 1.23.0 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index e5f70abc1f..b0583be27d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -134,7 +134,7 @@ GEM base64 (0.3.0) benchmark (0.5.0) bigdecimal (4.0.1) - bootsnap (1.22.0) + bootsnap (1.23.0) msgpack (~> 1.2) builder (3.3.0) bullet (8.1.0) From b2048afa275a45447830c14db4254647074b6432 Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Tue, 3 Mar 2026 10:20:48 +0000 Subject: [PATCH 50/57] rename the updated config files --- ...st_types.yml => 018_limber_lcm_triomics_request_types.wip.yml} | 0 ...emplates.yml => 014_lcm_triomics_submission_templates.wip.yml} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename config/default_records/request_types/{018_limber_lcm_triomics_request_types.yml => 018_limber_lcm_triomics_request_types.wip.yml} (100%) rename config/default_records/submission_templates/{014_lcm_triomics_submission_templates.yml => 014_lcm_triomics_submission_templates.wip.yml} (100%) 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.wip.yml similarity index 100% rename from config/default_records/request_types/018_limber_lcm_triomics_request_types.yml rename to config/default_records/request_types/018_limber_lcm_triomics_request_types.wip.yml 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.wip.yml similarity index 100% rename from config/default_records/submission_templates/014_lcm_triomics_submission_templates.yml rename to config/default_records/submission_templates/014_lcm_triomics_submission_templates.wip.yml From 78f3a633b7c2813e6a636bfb19181553994c84b3 Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Tue, 3 Mar 2026 14:25:14 +0000 Subject: [PATCH 51/57] move the new code into separte files --- ...> 018_limber_lcm_triomics_request_types.yml} | 0 ...lcm_triomics_new_added_request_types.wip.yml | 14 ++++++++++++++ .../014_lcm_triomics_submission_templates.yml | 17 +++++++++++++++++ ...mics_new_added_submission_templates.wip.yml} | 11 ++--------- 4 files changed, 33 insertions(+), 9 deletions(-) rename config/default_records/request_types/{018_limber_lcm_triomics_request_types.wip.yml => 018_limber_lcm_triomics_request_types.yml} (100%) create mode 100644 config/default_records/request_types/025_limber_lcm_triomics_new_added_request_types.wip.yml create mode 100644 config/default_records/submission_templates/014_lcm_triomics_submission_templates.yml rename config/default_records/submission_templates/{014_lcm_triomics_submission_templates.wip.yml => 020_lcm_triomics_new_added_submission_templates.wip.yml} (65%) diff --git a/config/default_records/request_types/018_limber_lcm_triomics_request_types.wip.yml b/config/default_records/request_types/018_limber_lcm_triomics_request_types.yml similarity index 100% rename from config/default_records/request_types/018_limber_lcm_triomics_request_types.wip.yml rename to config/default_records/request_types/018_limber_lcm_triomics_request_types.yml diff --git a/config/default_records/request_types/025_limber_lcm_triomics_new_added_request_types.wip.yml b/config/default_records/request_types/025_limber_lcm_triomics_new_added_request_types.wip.yml new file mode 100644 index 0000000000..cecf8357a5 --- /dev/null +++ b/config/default_records/request_types/025_limber_lcm_triomics_new_added_request_types.wip.yml @@ -0,0 +1,14 @@ +# Request types for LCM Triomics RNASeq +--- +limber_lcm_triomics_rnaseq: + name: LCM Triomics RNASeq + 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 new file mode 100644 index 0000000000..70255ccf46 --- /dev/null +++ b/config/default_records/submission_templates/014_lcm_triomics_submission_templates.yml @@ -0,0 +1,17 @@ +# Submission templates for LCM Triomics WGS and EMSeq +--- +# LCM Triomics EMSeq submission template +Limber-Htp - LCM Triomics: + submission_class_name: "LinearSubmission" + related_records: + request_type_keys: ["limber_lcm_triomics_emseq"] + product_line_name: LCM Triomics + product_catalogue_name: LCM Triomics + +# LCM Triomics WGS submission template +Limber-Htp - LCM Triomics WGS: + submission_class_name: "LinearSubmission" + related_records: + request_type_keys: ["limber_lcm_triomics_wgs"] + product_line_name: LCM Triomics + product_catalogue_name: LCM Triomics diff --git a/config/default_records/submission_templates/014_lcm_triomics_submission_templates.wip.yml b/config/default_records/submission_templates/020_lcm_triomics_new_added_submission_templates.wip.yml similarity index 65% rename from config/default_records/submission_templates/014_lcm_triomics_submission_templates.wip.yml rename to config/default_records/submission_templates/020_lcm_triomics_new_added_submission_templates.wip.yml index 510d955d5a..824e90cde6 100644 --- a/config/default_records/submission_templates/014_lcm_triomics_submission_templates.wip.yml +++ b/config/default_records/submission_templates/020_lcm_triomics_new_added_submission_templates.wip.yml @@ -1,5 +1,4 @@ -# Submission templates for LCM Triomics WGS and EMSeq ---- +# Submission templates for LCM Triomics EMSeq and RNASeq --- # LCM Triomics EMSeq submission template Limber-Htp - LCM Triomics EMSeq: submission_class_name: "LinearSubmission" @@ -8,13 +7,7 @@ Limber-Htp - LCM Triomics EMSeq: product_line_name: LCM Triomics product_catalogue_name: LCM Triomics project_name: "UAT Project" -# LCM Triomics WGS submission template -Limber-Htp - LCM Triomics WGS: - submission_class_name: "LinearSubmission" - related_records: - request_type_keys: ["limber_lcm_triomics_wgs"] - product_line_name: LCM Triomics - product_catalogue_name: LCM Triomics + # LCM Triomics RNASeq submission template Limber-Htp - LCM Triomics RNASeq: submission_class_name: "LinearSubmission" From 556b078c0496b5b17c3f858f240afc6a8366483f Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Tue, 3 Mar 2026 14:38:55 +0000 Subject: [PATCH 52/57] update the file --- .../018_limber_lcm_triomics_request_types.yml | 13 +------------ .../014_lcm_triomics_submission_templates.yml | 1 - 2 files changed, 1 insertion(+), 13 deletions(-) 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 f1300f6b1f..51fba8e078 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 @@ -12,18 +12,7 @@ limber_lcm_triomics_emseq: - LCMT Lysate library_types: - emSEQ -limber_lcm_triomics_rnaseq: - name: LCM Triomics RNASeq - 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 + limber_lcm_triomics_wgs: name: LCM Triomics WGS asset_type: Well 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 70255ccf46..7a55ace723 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 @@ -7,7 +7,6 @@ Limber-Htp - LCM Triomics: request_type_keys: ["limber_lcm_triomics_emseq"] product_line_name: LCM Triomics product_catalogue_name: LCM Triomics - # LCM Triomics WGS submission template Limber-Htp - LCM Triomics WGS: submission_class_name: "LinearSubmission" From 2345a131e65a26ef91452b11b6c238617e9ab43a Mon Sep 17 00:00:00 2001 From: Wendy Yang Date: Tue, 3 Mar 2026 14:40:42 +0000 Subject: [PATCH 53/57] reverse existing config --- .../request_types/018_limber_lcm_triomics_request_types.yml | 1 - 1 file changed, 1 deletion(-) 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 51fba8e078..cdc3f13ce4 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 @@ -12,7 +12,6 @@ limber_lcm_triomics_emseq: - LCMT Lysate library_types: - emSEQ - limber_lcm_triomics_wgs: name: LCM Triomics WGS asset_type: Well From 59e1e3e43e53b60384169b58afdb8ae0ffc5bc9b Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 3 Mar 2026 16:47:58 +0000 Subject: [PATCH 54/57] build: update datatables --- yarn.lock | 81 +++++++++++++++++++++++-------------------------------- 1 file changed, 33 insertions(+), 48 deletions(-) diff --git a/yarn.lock b/yarn.lock index c62ae64dd0..e10b4ef20f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -862,20 +862,12 @@ data-urls@^5.0.0: whatwg-mimetype "^4.0.0" whatwg-url "^14.0.0" -datatables.net-bs4@>=1.11.0, datatables.net-bs4@^2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/datatables.net-bs4/-/datatables.net-bs4-2.3.3.tgz#f4fabcd5ff78bad36bbda0ea62d6433cd9272a70" - integrity sha512-LFXCaB1nzsW0cXpRRctzfBnJtw4NbBpkcHehfdkeV8MuXnD0F9D1OkLIHLe/EnXbuWX7J1qk3QikigGHnox1LA== +datatables.net-bs4@>=1.11.0, datatables.net-bs4@^2, datatables.net-bs4@^2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/datatables.net-bs4/-/datatables.net-bs4-2.3.7.tgz#c361ff8f439bf63e1ec4862085ec667fb96bfd7f" + integrity sha512-ZyofK/3Unj0lQdAIsI9gT5oGCwTnx8y2UF6muYkbdO/Rw9h+x2kQwFkuCX5J0j6N2B+YCwzHk2OPu9yU8nk1Kw== dependencies: - datatables.net "2.3.3" - jquery ">=1.7" - -datatables.net-bs4@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/datatables.net-bs4/-/datatables.net-bs4-2.3.6.tgz#1b7148d1a15d0694f75b61650442d27ff353256a" - integrity sha512-9R7y8aTNLzyJKr4o+AoBbCZMdMT19VQCI4q/plPK5uXDHkUwmcAaI8/4sCrwlgXqkQK0h87bXyCFv1IE6xIa1w== - dependencies: - datatables.net "2.3.6" + datatables.net "2.3.7" jquery ">=1.7" datatables.net-buttons-bs4@^3.2.6: @@ -913,35 +905,35 @@ datatables.net-fixedcolumns@5.0.5: jquery ">=1.7" datatables.net-fixedheader-bs4@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/datatables.net-fixedheader-bs4/-/datatables.net-fixedheader-bs4-4.0.5.tgz#14d2a0ee029d3939d687114e22c28b194d205b4a" - integrity sha512-FSIPXPyp5asrGW/fniphFjiERWx0OcpKkzCoOjWt5itqk+V2cFxxpsriKnpSbsVrqKVFZir9md0CCD/Rs5UjeA== + version "4.0.6" + resolved "https://registry.yarnpkg.com/datatables.net-fixedheader-bs4/-/datatables.net-fixedheader-bs4-4.0.6.tgz#848310af658eb132ce1049623933e8d443b7c134" + integrity sha512-I2uPehQGDURxDH2JL/N+xVmB5sZq8H3pP2rZM2Y4JMHjtItLONgc3H1CF0LPWh20+9Ui28y0b3dgAroxvLt1Xg== dependencies: datatables.net-bs4 "^2" - datatables.net-fixedheader "4.0.5" + datatables.net-fixedheader "4.0.6" jquery ">=1.7" -datatables.net-fixedheader@4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/datatables.net-fixedheader/-/datatables.net-fixedheader-4.0.5.tgz#075fff97f47efac9f3ba72d34f8f0ea67470f165" - integrity sha512-cobQhOhjzqIYXTvMRrHUulULS8Re+hd2mmgFiOGKcZwHV0mofIwBlgiU3Ol4LHikHUCvsGnTEXoI+C7Ozma5sA== +datatables.net-fixedheader@4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/datatables.net-fixedheader/-/datatables.net-fixedheader-4.0.6.tgz#0c361a8a90542d75402f897db401085433efcebe" + integrity sha512-icYg/qKDpqGDrAVRWfsjt0xQdngk48R7LWkS9t8kaZFp9c4xrLFcmmPtRLgPp5/S4JHZbbsxmVkF16kscjNZjg== dependencies: datatables.net "^2" jquery ">=1.7" datatables.net-responsive-bs4@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/datatables.net-responsive-bs4/-/datatables.net-responsive-bs4-3.0.7.tgz#20b1cf1df5ad753032dc74a20ae67841492db9fd" - integrity sha512-N5TDe0A31J1pEIpcd1GlU70deHUUeqbuLbFr6LUJ8Q3HXiD9bjyrYiuev8G4viFtRCNHMcrUCJnSRHu5ZtuK1A== + version "3.0.8" + resolved "https://registry.yarnpkg.com/datatables.net-responsive-bs4/-/datatables.net-responsive-bs4-3.0.8.tgz#a1bbdb4029b8618981a0e0d6bf85f4f243a9fdb3" + integrity sha512-OvaJdidQhSGbpTb+BMyUSzeRvPrQUHn8h87Z0Sbt9fnPFF7QsqowWo7a9/gxqwP+ZnXhdHLdOM7+DTxir2YRFA== dependencies: datatables.net-bs4 "^2" - datatables.net-responsive "3.0.7" + datatables.net-responsive "3.0.8" jquery ">=1.7" -datatables.net-responsive@3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/datatables.net-responsive/-/datatables.net-responsive-3.0.7.tgz#7b57574bcfba105dc0827b77ec75b72b63e461fb" - integrity sha512-MngWU41M1LDDMjKFJ3rAHc4Zb3QhOysDTh+TfKE1ycrh5dpnKa1vobw2MKMMbvbx4q05OXZY9jtLSPIkaJRsuw== +datatables.net-responsive@3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/datatables.net-responsive/-/datatables.net-responsive-3.0.8.tgz#c41d706c98442122e61a8fb9b02a8b2995cd487d" + integrity sha512-htslaX9g/9HFrJeyFQKEe/XJWpawPxpvy+M6vc/NkKQIrKhbxSoPc3phPqmlnZth6b9hgawqWDT0e0lwf5p+KA== dependencies: datatables.net "^2" jquery ">=1.7" @@ -964,33 +956,26 @@ datatables.net-rowgroup@1.6.0: jquery ">=1.7" datatables.net-rowreorder-bs4@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/datatables.net-rowreorder-bs4/-/datatables.net-rowreorder-bs4-1.5.0.tgz#5006eb235d9dfd78974d113df25a6b921328f013" - integrity sha512-4C9tzLRaisO+IOdJ1n6NRQ8ak0zC0IbX50Ed5hg+5PT7VJLI9klH+GBwE6hpF28UTWk7E6somzOwnRHLexqXdA== + version "1.5.1" + resolved "https://registry.yarnpkg.com/datatables.net-rowreorder-bs4/-/datatables.net-rowreorder-bs4-1.5.1.tgz#85fd5a4c2741f678da27a6798869b1d8ff1f600d" + integrity sha512-26Xx4JbrKhHQDh+GgvVK/XXeFIW6RLQnU5mLtMpy2S+K/9Yffyc71aZ8lyEmm5q7rNnw3LE+hY/FZN5sPTldTQ== dependencies: datatables.net-bs4 ">=1.11.0" - datatables.net-rowreorder "1.5.0" + datatables.net-rowreorder "1.5.1" jquery ">=1.7" -datatables.net-rowreorder@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/datatables.net-rowreorder/-/datatables.net-rowreorder-1.5.0.tgz#f8b5242bd97df6b9522573378e98e8c7d64002dd" - integrity sha512-Kkq57tdJHrUCYkS8vywhL5tqKtl2q3K3p8A6wkIdwMX2b8YVjtywhQbXvvVLZnlMQsdX194FXVK1AgAwcm4hFg== +datatables.net-rowreorder@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/datatables.net-rowreorder/-/datatables.net-rowreorder-1.5.1.tgz#257aa1dc2c29ed4421a95434a7044d3d4dd2a7fe" + integrity sha512-hVGXjl9lcLXHMdtwzCs2y47ZqYbSz9XhV+KSVkGsXu8M0MHma1rJXY7uOeCy5u/PMD4oONzgT2B8QCUBBbqvQA== dependencies: datatables.net ">=1.11.0" jquery ">=1.7" -datatables.net@2.3.3, datatables.net@>=1.11.0, datatables.net@^2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-2.3.3.tgz#fe4f96bdbc4cf47c8d11162a7af525ca6a3683d2" - integrity sha512-SWL3za6nheY6gdoiLgCc++tYmxbwrmv2bjrEiII9rXBWXXSbOZct6pjR3FueMVRM5jmt7pQcXiGovfuFDnutQg== - dependencies: - jquery ">=1.7" - -datatables.net@2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-2.3.6.tgz#a11be57a2b50d7231cae2980a8ff1df3c18b7b17" - integrity sha512-xQ/dCxrjfxM0XY70wSIzakkTZ6ghERwlLmAPyCnu8Sk5cyt9YvOVyOsFNOa/BZ/lM63Q3i2YSSvp/o7GXZGsbg== +datatables.net@2.3.7, datatables.net@>=1.11.0, datatables.net@^2: + version "2.3.7" + resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-2.3.7.tgz#3cd34f6f5d1f40a46b5a20a4ba32604bdbcd6738" + integrity sha512-AvsjG/Nkp6OxeyBKYZauemuzQCPogE1kOtKwG4sYjvdqGCSLiGaJagQwXv4YxG+ts5vaJr6qKGG9ec3g6vTo3w== dependencies: jquery ">=1.7" From a9c4ef3d11a2c49a3bdbea27534c7d807d598717 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 00:00:27 +0000 Subject: [PATCH 55/57] Update Node.js to version 24.14.0 --- .nvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nvmrc b/.nvmrc index 32f8c50de0..d845d9d88d 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24.13.1 +24.14.0 From fd0ca2b68fe2122ef11fcf20549c75dbbcb17794 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 00:45:21 +0000 Subject: [PATCH 56/57] Update flipper to version 1.4.0 --- Gemfile.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b0583be27d..c8ad9a573e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -247,14 +247,14 @@ GEM ffi (1.17.3-arm64-darwin) ffi (1.17.3-x86_64-darwin) ffi (1.17.3-x86_64-linux-gnu) - flipper (1.3.6) + flipper (1.4.0) concurrent-ruby (< 2) - flipper-active_record (1.3.6) + flipper-active_record (1.4.0) activerecord (>= 4.2, < 9) - flipper (~> 1.3.6) - flipper-ui (1.3.6) + flipper (~> 1.4.0) + flipper-ui (1.4.0) erubi (>= 1.0.0, < 2.0.0) - flipper (~> 1.3.6) + flipper (~> 1.4.0) rack (>= 1.4, < 4) rack-protection (>= 1.5.3, < 5.0.0) rack-session (>= 1.0.2, < 3.0.0) From f5ed910f80ff2590795be3b4225ce0271fb3365f Mon Sep 17 00:00:00 2001 From: Ben Topping Date: Mon, 9 Mar 2026 10:41:24 +0000 Subject: [PATCH 57/57] misc: bumps release version --- .release-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.release-version b/.release-version index d39ebf95de..88927ab769 100644 --- a/.release-version +++ b/.release-version @@ -1 +1 @@ -14.88.0 +14.89.0