Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

DPL-823 [Part 1 - download] Import PBMC pool plates into Sequencescape #4017

Merged
merged 18 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/controllers/sdb/sample_manifests_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def show
@study_id = @sample_manifest.study_id
@samples = @sample_manifest.samples.paginate(page: params[:page])
end

def new # rubocop:todo Metrics/AbcSize
params[:only_first_label] ||= false
@sample_manifest = SampleManifest.new(new_manifest_params)
Expand Down
11 changes: 10 additions & 1 deletion app/jobs/sample_manifest/generate_wells_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,17 @@
def perform
ActiveRecord::Base.transaction do
# Ensure the order of the wells are maintained
# Why does the order of the wells matter? Maybe can't use a hash if it does.
# Keep key of hash as map_id and query Maps in the generate_wells_job method?
maps = Map.find(map_ids).index_by(&:id)
well_data = map_ids_to_sample_ids.map { |map_id, sample_id| [maps[map_id], sample_id] }
well_data = {}
map_ids_to_sample_ids.each do |map_id, sample_id|
if (well_data[maps[map_id]])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GenerateWellsJob#perform calls 'maps[map_id]' 3 times

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GenerateWellsJob#perform calls 'well_data[maps[map_id]]' 2 times

well_data[maps[map_id]] << sample_id
else
well_data[maps[map_id]] = [sample_id]
end
end

sample_manifest.core_behaviour.generate_wells_job(well_data, plate)
end
Expand Down
10 changes: 9 additions & 1 deletion app/models/sample_manifest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# for the potential samples. It also generates a {SampleManifestExcel}
# spreadsheet which gets sent to the customer.
#
# The labware that gets generate is determined by the {#asset_type} which
# The labware that gets generated is determined by the {#asset_type} which
# switches out the {#core_behaviour} module {SampleManifest::CoreBehaviour}.
# This is concerned with generating {Labware} and {Receptacle receptacles},
# generating any event specific to the asset type, and setting manifest specific
Expand Down Expand Up @@ -49,6 +49,7 @@ def self.included(base)
has_uploaded_document :generated, differentiator: 'generated'

attr_accessor :override, :only_first_label
attr_writer :rows_per_well
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SampleManifest#rows_per_well is a writable attribute


class_attribute :spreadsheet_offset
class_attribute :spreadsheet_header_row
Expand Down Expand Up @@ -128,6 +129,13 @@ def default_filename
"#{study_id}stdy_manifest_#{id}_#{created_at.to_formatted_s(:dmy)}"
end

# Use a default value of 1 for rows_per_well if not set
def rows_per_well
1
# TODO: replace above line with below line when we want to turn the 'rows_per_well' feature on
KatyTaylor marked this conversation as resolved.
Show resolved Hide resolved
# @rows_per_well || 1
end

scope :pending_manifests,
-> {
order(id: :desc).includes(:uploaded_document).references(:uploaded_document).where(documents: { id: nil })
Expand Down
10 changes: 9 additions & 1 deletion app/models/sample_manifest/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,21 @@ def execute_print_job
end

def attributes
params.except(:template, :barcode_printer, :only_first_label).merge(user: user, asset_type: asset_type)
params
.except(:template, :barcode_printer, :only_first_label)
.merge(user: user, asset_type: asset_type, rows_per_well: rows_per_well)
end

def asset_type
configuration.manifest_types.find_by(params[:template]).asset_type
end

# Retrieves the value of the rows_per_well attribute from the manifest_types.yml config ('template' dropdown in the UI).
# Returns nil if the rows_per_well attribute is not set in the manifest_types.yml config.
def rows_per_well
configuration.manifest_types.find_by(params[:template]).rows_per_well
end

def only_first_label
ActiveRecord::Type::Boolean.new.cast(params[:only_first_label])
end
Expand Down
21 changes: 13 additions & 8 deletions app/models/sample_manifest/plate_behaviour.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def generate_wells(well_data, plates)
@details_array =
plates.flat_map do |plate|
well_data
.slice!(0, plate.size)
.slice!(0, plate.size * @manifest.rows_per_well)
.map { |map, sample_id| { barcode: plate.human_barcode, position: map.description, sample_id: sample_id } }
end
end
Expand Down Expand Up @@ -88,12 +88,15 @@ def labware
alias printables labware

# Called by {SampleManifest::GenerateWellsJob} and builds the wells
# wells_for_plate: Hash, Map to list of SangerSampleIds
def generate_wells_job(wells_for_plate, plate)
wells_for_plate.map do |map, sanger_sample_id|
wells_for_plate.map do |map, sanger_sample_ids|
plate
.wells
.create!(map: map) do |well|
SampleManifestAsset.create(sanger_sample_id: sanger_sample_id, asset: well, sample_manifest: @manifest)
sanger_sample_ids.each do |sanger_sample_id|
SampleManifestAsset.create(sanger_sample_id: sanger_sample_id, asset: well, sample_manifest: @manifest)
end
end
end
RequestFactory.create_assets_requests(plate.wells, study)
Expand All @@ -107,7 +110,7 @@ def generate_wells_job(wells_for_plate, plate)
# truncate the data.
def generate_wells_for_plates(well_data, plates)
cloned_well_data = well_data.dup
plates.each { |plate| yield(cloned_well_data.slice!(0, plate.size), plate) }
plates.each { |plate| yield(cloned_well_data.slice!(0, plate.size * @manifest.rows_per_well), plate) }
end

def labware_from_barcodes
Expand All @@ -126,13 +129,15 @@ def generate_plates(purpose) # rubocop:todo Metrics/AbcSize
plates = Array.new(count) { purpose.create!(:without_wells) }.sort_by(&:human_barcode)

plates.each do |plate|
sanger_sample_ids = generate_sanger_ids(plate.size)
sanger_sample_ids = generate_sanger_ids(plate.size * @manifest.rows_per_well)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SampleManifest::PlateBehaviour::Base#generate_plates calls '@manifest.rows_per_well' 2 times


plate.maps.in_column_major_order.each do |well_map|
sanger_sample_id = sanger_sample_ids.shift
generated_sanger_sample_id = SangerSampleId.generate_sanger_sample_id!(study_abbreviation, sanger_sample_id)
@manifest.rows_per_well.times do
sanger_sample_id = sanger_sample_ids.shift
generated_sanger_sample_id = SangerSampleId.generate_sanger_sample_id!(study_abbreviation, sanger_sample_id)

well_data << [well_map, generated_sanger_sample_id]
well_data << [well_map, generated_sanger_sample_id]
end
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def each(&block)
manifest_types.each(&block)
end

# Hash version of the manifest_types.yml config file
def manifest_types
@manifest_types ||= {}
end
Expand Down Expand Up @@ -49,7 +50,7 @@ def <=>(other)
class ManifestType
include SequencescapeExcel::Helpers::Attributes

setup_attributes :name, :columns, :heading, :asset_type
setup_attributes :name, :columns, :heading, :asset_type, :rows_per_well

def initialize(attributes = {})
super
Expand All @@ -62,7 +63,8 @@ def to_a
def ==(other)
return false unless other.is_a?(self.class)

name == other.name && columns == other.columns && heading == other.heading && asset_type == other.asset_type
name == other.name && columns == other.columns && heading == other.heading && asset_type == other.asset_type &&
rows_per_well == other.rows_per_well
end
end

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
# scRNA Core pipeline purposes
# Most are defined in the Limber config, but the below also need to be defined in Sequencescape.
# They are in a separate file so it can be 'feature flagged off' until needed.
---
# The 'LRC PBMC Pools' purpose is controlled by Limber. However, it has been
# added here to create submission and request type records for scRNA Core cDNA
# Prep stage.
---
LRC PBMC Pools:
stock_plate: false
cherrypickable_target: false
# The 'LRC PBMC Pools Input' purpose is included here so that it's available for
# sample manifests.
LRC PBMC Pools Input:
stock_plate: true
cherrypickable_target: false
10 changes: 10 additions & 0 deletions config/sample_manifest_excel/manifest_types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,16 @@ plate_bioscan:
- :sample_type
- :donor_id
- :bioscan_control_type
plate_scrna_core_pools:
heading: "scRNA Core Pools Plate"
asset_type: "plate"
rows_per_well: 20
columns:
- :sanger_plate_id
- :well
- :sanger_sample_id
- :collected_by_for_scrna_core
- :donor_id_mandatory
tube_default:
heading: "Default Tube"
asset_type: "1dtube"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddRowsPerWellToSampleManifests < ActiveRecord::Migration[6.0]
def change
add_column :sample_manifests, :rows_per_well, :integer
end
end
3 changes: 2 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2023_12_04_163029) do
ActiveRecord::Schema.define(version: 2024_02_14_144515) do

create_table "aliquot_indices", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC", force: :cascade do |t|
t.integer "aliquot_id", null: false
Expand Down Expand Up @@ -1327,6 +1327,7 @@
t.string "password"
t.integer "purpose_id"
t.integer "tube_rack_purpose_id"
t.integer "rows_per_well"
t.index ["purpose_id"], name: "fk_rails_5627ab4aaa"
t.index ["study_id"], name: "index_sample_manifests_on_study_id"
t.index ["supplier_id"], name: "index_sample_manifests_on_supplier_id"
Expand Down
1 change: 1 addition & 0 deletions spec/data/sample_manifest_excel/extract/manifest_types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ test_2:
test_3:
heading: "Yet Another test"
asset_type: "plate"
rows_per_well: 2
columns:
- :sanger_plate_id
- :well
Expand Down
9 changes: 9 additions & 0 deletions spec/data/sample_manifest_excel/manifest_types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -701,3 +701,12 @@ tube_extraction:
- :sample_ebi_accession_number
- :donor_id
- :phenotype
pools_plate:
heading: "Pools Plate"
asset_type: "plate"
rows_per_well: 2
columns:
- :sanger_plate_id
- :well
- :sanger_sample_id
- :supplier_name
4 changes: 2 additions & 2 deletions spec/features/sample_manifests/create_manifest_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def load_manifest_spec
select(study.name, from: 'Study')
select(supplier.name, from: 'Supplier')
within('#sample_manifest_template') do
expect(page).to have_css('option', count: 7)
expect(page).to have_css('option', count: 8)
expect(page).not_to have_css('option', text: 'Default Tube')
end
select('Default Plate', from: 'Template')
Expand Down Expand Up @@ -66,7 +66,7 @@ def load_manifest_spec

it 'indicate the purpose field is used for plates only' do
visit(new_sample_manifest_path)
within('#sample_manifest_template') { expect(page).to have_css('option', count: 22) }
within('#sample_manifest_template') { expect(page).to have_css('option', count: 23) }
select(created_purpose.name, from: 'Purpose')
expect(page).to have_text('Used for plate manifests only')
end
Expand Down
25 changes: 23 additions & 2 deletions spec/models/sample_manifest/generator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@
let!(:supplier) { create(:supplier) }
let!(:barcode_printer) { create(:barcode_printer) }
let(:configuration) { SampleManifestExcel.configuration }
let(:template) { 'plate_full' }

let(:attributes) do
{ template: 'plate_full', study_id: study.id, supplier_id: supplier.id, count: '4' }.with_indifferent_access
{ template: template, study_id: study.id, supplier_id: supplier.id, count: '4' }.with_indifferent_access
end

after(:all) { SampleManifestExcel.reset! }
Expand All @@ -39,7 +40,7 @@
expect(described_class.new(attributes, nil, configuration)).not_to be_valid
end

it 'is not be unless all of the attributes are present' do
it 'is not valid unless all of the attributes are present' do
SampleManifest::Generator::REQUIRED_ATTRIBUTES.each do |attribute|
expect(described_class.new(attributes.except(attribute), user, configuration)).not_to be_valid
end
Expand Down Expand Up @@ -123,4 +124,24 @@
it 'does not have a print job if printer name has not been provided' do
expect(described_class.new(attributes, user, configuration)).not_to be_print_job_required
end

context 'with rows_per_well set' do
let(:template) { 'pools_plate' }

# TODO: un-skip when rows_per_well feature is enabled
KatyTaylor marked this conversation as resolved.
Show resolved Hide resolved
skip 'generates a details array with more than one entry per well' do

Check warning on line 132 in spec/models/sample_manifest/generator_spec.rb

View workflow job for this annotation

GitHub Actions / rspec_tests (3, 1)

SampleManifest::Generator with rows_per_well set generates a details array with more than one entry per well Skipped: No reason given

Check warning on line 132 in spec/models/sample_manifest/generator_spec.rb

View workflow job for this annotation

GitHub Actions / rspec_tests (3, 0)

SampleManifest::Generator with rows_per_well set generates a details array with more than one entry per well Skipped: No reason given
generator = described_class.new(attributes, user, configuration)
generator.execute
expect(generator.sample_manifest.details_array.size).to eq(4*96*2)
end
end

context 'with rows_per_well not set' do
# TODO: un-skip when rows_per_well feature is enabled
KatyTaylor marked this conversation as resolved.
Show resolved Hide resolved
skip 'generates a details array with one entry per well' do

Check warning on line 141 in spec/models/sample_manifest/generator_spec.rb

View workflow job for this annotation

GitHub Actions / rspec_tests (3, 1)

SampleManifest::Generator with rows_per_well not set generates a details array with one entry per well Skipped: No reason given

Check warning on line 141 in spec/models/sample_manifest/generator_spec.rb

View workflow job for this annotation

GitHub Actions / rspec_tests (3, 0)

SampleManifest::Generator with rows_per_well not set generates a details array with one entry per well Skipped: No reason given
generator = described_class.new(attributes, user, configuration)
generator.execute
expect(generator.sample_manifest.details_array.size).to eq(4*96)
end
end
end
1 change: 1 addition & 0 deletions spec/sample_manifest_excel/manifest_type_list_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
expect(manifest_type.heading).to eq(v['heading'])
expect(manifest_type.columns).to eq(v['columns'])
expect(manifest_type.asset_type).to eq(v['asset_type'])
expect(manifest_type.rows_per_well).to eq(v['rows_per_well']) # if this attribute isn't present, just compares nil == nil
end
end

Expand Down
Loading