From 3184c3134595272d1acee0f0d962aa0e7b82a04f Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 28 Nov 2024 08:24:43 +0000 Subject: [PATCH 1/9] fix: generate plate volumes --- .../uat_actions/generate_plate_volumes.rb | 103 ++++++++++++++++++ .../generate_plate_volumes_spec.rb | 37 +++++++ 2 files changed, 140 insertions(+) create mode 100644 app/uat_actions/uat_actions/generate_plate_volumes.rb create mode 100644 spec/uat_actions/generate_plate_volumes_spec.rb diff --git a/app/uat_actions/uat_actions/generate_plate_volumes.rb b/app/uat_actions/uat_actions/generate_plate_volumes.rb new file mode 100644 index 0000000000..4a577abe11 --- /dev/null +++ b/app/uat_actions/uat_actions/generate_plate_volumes.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +# Will generate volumes for a given plate +class UatActions::GeneratePlateVolumes < UatActions + self.title = 'Generate volumes for a plate' + self.description = 'Generate a set of randomised volumes for a plate.' + self.category = :quality_control + + form_field :plate_barcode, + :text_field, + label: 'Plate barcode', + help: + 'Enter the barcode of the plate for which you want to add volumes. ' \ + 'NB. only well containing aliquots will have volumes set.' + form_field :minimum_volume, + :number_field, + label: 'Minimum volume (µl)', + help: 'The minimum volume the wells should have.', + options: { + minimum: 0 + } + form_field :maximum_volume, + :number_field, + label: 'Maximum volume (µl)', + help: 'The maximum volume the wells should have.', + options: { + minimum: 0 + } + + + # + # Returns a default copy of the UatAction which will be used to fill in the form, with values + # for the units, and min and max volumes. + # + # @return [UatActions::GeneratePlateVolumes] A default object for rendering a form + def self.default + new( minimum_volume: 0, maximum_volume: 100) + end + + + validates :plate_barcode, presence: { message: 'could not be found' } + validates :minimum_volume, numericality: { only_integer: false } + validates :maximum_volume, numericality: { greater_than: 0, only_integer: false } + validate :maximum_greater_than_minimum + + def perform + qc_assay_results = construct_qc_assay + report['number_well_volumes_written'] = qc_assay_results[:num_wells_written] + qc_assay_results[:qc_assay_success] + end + + private + + def maximum_greater_than_minimum + return true if max_vol > min_vol + + errors.add(:maximum_volume, 'needs to be greater than minimum volume') + false + end + + def labware + @labware ||= Plate.find_by_barcode(plate_barcode.strip) + end + + def key + @key ||= 'volume' + end + + def min_vol + @min_vol ||= minimum_volume.to_f + end + + def max_vol + @max_vol ||= maximum_volume.to_f + end + + def create_random_volume + value = (rand * (max_vol - min_vol)) + min_vol + format('%.3f', value) + end + + def construct_qc_assay + qc_assay = QcAssay.new + num_wells_written = 0 + + labware.wells.each do |well| + next if well.aliquots.empty? + + QcResult.create!( + asset: well, + key: key, + value: create_random_volume, + units: 'µl', + assay_type: 'UAT_Testing', + assay_version: 'Binning', + qc_assay: qc_assay + ) + num_wells_written += 1 + end + qc_assay_success = qc_assay.save + { qc_assay_success:, num_wells_written: } + end +end diff --git a/spec/uat_actions/generate_plate_volumes_spec.rb b/spec/uat_actions/generate_plate_volumes_spec.rb new file mode 100644 index 0000000000..fcb826677d --- /dev/null +++ b/spec/uat_actions/generate_plate_volumes_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe UatActions::GeneratePlateVolumes do + context 'with valid options' do + let(:plate) { create(:plate_with_untagged_wells, sample_count: 3) } + let(:uat_action) { described_class.new(parameters) } + let(:report) do + # A report is a hash of key value pairs which get returned to the user. + # It should include information such as barcodes and identifiers + { 'number_well_volumes_written' => 3 } + end + + context 'when ul volumes' do + let(:parameters) do + { + plate_barcode: plate.barcodes.first.barcode, + minimum_volume: 0, + maximum_volume: 30 + } + end + + it 'can be performed' do + expect(uat_action.perform).to be true + expect(uat_action.report).to eq report + expect(plate.wells.map(&:qc_results).size).to eq 3 + expect(plate.wells.first.qc_results.first.assay_type).to eq 'UAT_Testing' + end + end + + end + + it 'returns a default' do + expect(described_class.default).to be_a described_class + end +end From 12deaa2c01c3aefe227aed524e04c461b5a8923b Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 28 Nov 2024 08:33:33 +0000 Subject: [PATCH 2/9] style: lindt --- app/uat_actions/uat_actions/generate_plate_volumes.rb | 4 +--- spec/uat_actions/generate_plate_volumes_spec.rb | 9 +-------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/app/uat_actions/uat_actions/generate_plate_volumes.rb b/app/uat_actions/uat_actions/generate_plate_volumes.rb index 4a577abe11..c316881cf0 100644 --- a/app/uat_actions/uat_actions/generate_plate_volumes.rb +++ b/app/uat_actions/uat_actions/generate_plate_volumes.rb @@ -27,17 +27,15 @@ class UatActions::GeneratePlateVolumes < UatActions minimum: 0 } - # # Returns a default copy of the UatAction which will be used to fill in the form, with values # for the units, and min and max volumes. # # @return [UatActions::GeneratePlateVolumes] A default object for rendering a form def self.default - new( minimum_volume: 0, maximum_volume: 100) + new(minimum_volume: 0, maximum_volume: 100) end - validates :plate_barcode, presence: { message: 'could not be found' } validates :minimum_volume, numericality: { only_integer: false } validates :maximum_volume, numericality: { greater_than: 0, only_integer: false } diff --git a/spec/uat_actions/generate_plate_volumes_spec.rb b/spec/uat_actions/generate_plate_volumes_spec.rb index fcb826677d..115596e63f 100644 --- a/spec/uat_actions/generate_plate_volumes_spec.rb +++ b/spec/uat_actions/generate_plate_volumes_spec.rb @@ -13,13 +13,7 @@ end context 'when ul volumes' do - let(:parameters) do - { - plate_barcode: plate.barcodes.first.barcode, - minimum_volume: 0, - maximum_volume: 30 - } - end + let(:parameters) { { plate_barcode: plate.barcodes.first.barcode, minimum_volume: 0, maximum_volume: 30 } } it 'can be performed' do expect(uat_action.perform).to be true @@ -28,7 +22,6 @@ expect(plate.wells.first.qc_results.first.assay_type).to eq 'UAT_Testing' end end - end it 'returns a default' do From df603b40d9c980545801a9d75dae7b892487a9db Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 28 Nov 2024 08:51:56 +0000 Subject: [PATCH 3/9] tests: split out individual expectations --- spec/uat_actions/generate_plate_volumes_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/uat_actions/generate_plate_volumes_spec.rb b/spec/uat_actions/generate_plate_volumes_spec.rb index 115596e63f..1ef656c3e9 100644 --- a/spec/uat_actions/generate_plate_volumes_spec.rb +++ b/spec/uat_actions/generate_plate_volumes_spec.rb @@ -17,8 +17,17 @@ it 'can be performed' do expect(uat_action.perform).to be true + end + + it 'generates the correct report' do expect(uat_action.report).to eq report + end + + it 'creates the correct number of QC results' do expect(plate.wells.map(&:qc_results).size).to eq 3 + end + + it 'sets the correct assay type for the first QC result' do expect(plate.wells.first.qc_results.first.assay_type).to eq 'UAT_Testing' end end From b3084f7fd2d62e14760f78ddde638907a2e8508f Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Thu, 28 Nov 2024 09:12:28 +0000 Subject: [PATCH 4/9] tests: refactor and fix expectations --- .../generate_plate_volumes_spec.rb | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/spec/uat_actions/generate_plate_volumes_spec.rb b/spec/uat_actions/generate_plate_volumes_spec.rb index 1ef656c3e9..043c108636 100644 --- a/spec/uat_actions/generate_plate_volumes_spec.rb +++ b/spec/uat_actions/generate_plate_volumes_spec.rb @@ -6,34 +6,51 @@ context 'with valid options' do let(:plate) { create(:plate_with_untagged_wells, sample_count: 3) } let(:uat_action) { described_class.new(parameters) } + let!(:performed_action) { uat_action.perform } let(:report) do # A report is a hash of key value pairs which get returned to the user. # It should include information such as barcodes and identifiers { 'number_well_volumes_written' => 3 } end - context 'when ul volumes' do - let(:parameters) { { plate_barcode: plate.barcodes.first.barcode, minimum_volume: 0, maximum_volume: 30 } } + let(:parameters) { { plate_barcode: plate.barcodes.first.barcode, minimum_volume: 0, maximum_volume: 30 } } - it 'can be performed' do - expect(uat_action.perform).to be true - end + it 'can be performed' do + expect(performed_action).to be true + end + + it 'generates the correct report' do + expect(uat_action.report).to eq report + end - it 'generates the correct report' do - expect(uat_action.report).to eq report - end + it 'creates the correct number of QC results' do + expect(plate.wells.map(&:qc_results).size).to eq 3 + end - it 'creates the correct number of QC results' do - expect(plate.wells.map(&:qc_results).size).to eq 3 - end + it 'sets the correct assay type for the first QC result' do + expect(plate.wells.first.qc_results.first.assay_type).to eq 'UAT_Testing' + end - it 'sets the correct assay type for the first QC result' do - expect(plate.wells.first.qc_results.first.assay_type).to eq 'UAT_Testing' - end + it 'sets the volumes to be within the specified range' do + expect(plate.wells.map { |well| well.qc_results.first.value.to_f }).to all(be_between(0, 30)) end end - it 'returns a default' do - expect(described_class.default).to be_a described_class + context 'with default options' do + it 'returns an instance of described_class' do + expect(described_class.default).to be_a described_class + end + + it 'has a nil plate_barcode' do + expect(described_class.default.plate_barcode).to be_nil + end + + it 'has a minimum_volume of 0' do + expect(described_class.default.minimum_volume).to eq 0 + end + + it 'has a maximum_volume of 100' do + expect(described_class.default.maximum_volume).to eq 100 + end end end From ca14fa166dd39107558769aa005c05c1e53f708c Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Mon, 2 Dec 2024 17:00:23 +0000 Subject: [PATCH 5/9] tests: add additional tests for validation --- .../generate_plate_volumes_spec.rb | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/spec/uat_actions/generate_plate_volumes_spec.rb b/spec/uat_actions/generate_plate_volumes_spec.rb index 043c108636..e90521938e 100644 --- a/spec/uat_actions/generate_plate_volumes_spec.rb +++ b/spec/uat_actions/generate_plate_volumes_spec.rb @@ -3,17 +3,19 @@ require 'rails_helper' describe UatActions::GeneratePlateVolumes do + let(:plate) { create(:plate_with_untagged_wells, sample_count: 3) } + let(:plate_barcode) { plate.barcodes.first.barcode } + let(:uat_action) { described_class.new(parameters) } + let!(:performed_action) { uat_action.perform } + context 'with valid options' do - let(:plate) { create(:plate_with_untagged_wells, sample_count: 3) } - let(:uat_action) { described_class.new(parameters) } - let!(:performed_action) { uat_action.perform } let(:report) do # A report is a hash of key value pairs which get returned to the user. # It should include information such as barcodes and identifiers { 'number_well_volumes_written' => 3 } end - let(:parameters) { { plate_barcode: plate.barcodes.first.barcode, minimum_volume: 0, maximum_volume: 30 } } + let(:parameters) { { plate_barcode: plate_barcode, minimum_volume: 0, maximum_volume: 30 } } it 'can be performed' do expect(performed_action).to be true @@ -37,6 +39,8 @@ end context 'with default options' do + let(:parameters) { { plate_barcode: } } + it 'returns an instance of described_class' do expect(described_class.default).to be_a described_class end @@ -53,4 +57,29 @@ expect(described_class.default.maximum_volume).to eq 100 end end + + context 'with invalid options' do + let(:parameters) { {plate_barcode: plate_barcode, minimum_volume: 110, maximum_volume: 10 } } + let!(:saved_action) { uat_action.save } + + it 'has a minimum_volume of 110' do + expect(uat_action.minimum_volume).to eq 110 + end + + it 'has a maximum_volume of 10' do + expect(uat_action.maximum_volume).to eq 10 + end + + it 'is invalid' do + expect(uat_action.valid?).to be false + end + + it 'can not be saved' do + expect(saved_action).to be false + end + + it 'adds an error' do + expect(uat_action.errors.full_messages).to include('Maximum volume needs to be greater than minimum volume') + end + end end From 48a0ba159b6a6dd206b67db42521b59630f6238f Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 3 Dec 2024 09:25:47 +0000 Subject: [PATCH 6/9] style: lint --- spec/uat_actions/generate_plate_volumes_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/uat_actions/generate_plate_volumes_spec.rb b/spec/uat_actions/generate_plate_volumes_spec.rb index e90521938e..e655213412 100644 --- a/spec/uat_actions/generate_plate_volumes_spec.rb +++ b/spec/uat_actions/generate_plate_volumes_spec.rb @@ -59,7 +59,7 @@ end context 'with invalid options' do - let(:parameters) { {plate_barcode: plate_barcode, minimum_volume: 110, maximum_volume: 10 } } + let(:parameters) { { plate_barcode: plate_barcode, minimum_volume: 110, maximum_volume: 10 } } let!(:saved_action) { uat_action.save } it 'has a minimum_volume of 110' do From 9c82b866e11f196bde1f9fe64764cf0b15a22d78 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 3 Dec 2024 15:53:28 +0000 Subject: [PATCH 7/9] fix: allow maximum to equal minimum --- app/uat_actions/uat_actions/generate_plate_volumes.rb | 8 ++++---- spec/uat_actions/generate_plate_volumes_spec.rb | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/uat_actions/uat_actions/generate_plate_volumes.rb b/app/uat_actions/uat_actions/generate_plate_volumes.rb index c316881cf0..0afd4cacb9 100644 --- a/app/uat_actions/uat_actions/generate_plate_volumes.rb +++ b/app/uat_actions/uat_actions/generate_plate_volumes.rb @@ -39,7 +39,7 @@ def self.default validates :plate_barcode, presence: { message: 'could not be found' } validates :minimum_volume, numericality: { only_integer: false } validates :maximum_volume, numericality: { greater_than: 0, only_integer: false } - validate :maximum_greater_than_minimum + validate :maximum_greater_than_or_equal_to_minimum def perform qc_assay_results = construct_qc_assay @@ -49,10 +49,10 @@ def perform private - def maximum_greater_than_minimum - return true if max_vol > min_vol + def maximum_greater_than_or_equal_to_minimum + return true if max_vol >= min_vol - errors.add(:maximum_volume, 'needs to be greater than minimum volume') + errors.add(:maximum_volume, 'needs to be greater than or equal to minimum volume') false end diff --git a/spec/uat_actions/generate_plate_volumes_spec.rb b/spec/uat_actions/generate_plate_volumes_spec.rb index e655213412..794fefafcb 100644 --- a/spec/uat_actions/generate_plate_volumes_spec.rb +++ b/spec/uat_actions/generate_plate_volumes_spec.rb @@ -79,7 +79,7 @@ end it 'adds an error' do - expect(uat_action.errors.full_messages).to include('Maximum volume needs to be greater than minimum volume') + expect(uat_action.errors.full_messages).to include('Maximum volume needs to be greater than or equal to minimum volume') end end end From 91b78f328d9ce2a432aedf3e24924034634bb392 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 3 Dec 2024 17:01:33 +0000 Subject: [PATCH 8/9] fix: allow maximum to equal minimum (concentrations) --- .../uat_actions/generate_plate_concentrations.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/uat_actions/uat_actions/generate_plate_concentrations.rb b/app/uat_actions/uat_actions/generate_plate_concentrations.rb index ac97aacb5a..aaf04ab299 100644 --- a/app/uat_actions/uat_actions/generate_plate_concentrations.rb +++ b/app/uat_actions/uat_actions/generate_plate_concentrations.rb @@ -48,7 +48,7 @@ def self.default validates :concentration_units, presence: { message: 'needs a choice' } validates :minimum_concentration, numericality: { only_integer: false } validates :maximum_concentration, numericality: { greater_than: 0, only_integer: false } - validate :maximum_greater_than_minimum + validate :maximum_greater_than_or_equal_to_minimum def perform qc_assay_results = construct_qc_assay @@ -58,10 +58,10 @@ def perform private - def maximum_greater_than_minimum - return true if max_conc > min_conc + def maximum_greater_than_or_equal_to_minimum + return true if max_conc >= min_conc - errors.add(:maximum_concentration, 'needs to be greater than minimum concentration') + errors.add(:maximum_concentration, 'needs to be greater than or equal to minimum concentration') false end From a2acfb96a1c46c446381e11c468fef2d5ee9e012 Mon Sep 17 00:00:00 2001 From: Stephen Hulme Date: Tue, 3 Dec 2024 17:02:34 +0000 Subject: [PATCH 9/9] style: lint --- spec/uat_actions/generate_plate_volumes_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/uat_actions/generate_plate_volumes_spec.rb b/spec/uat_actions/generate_plate_volumes_spec.rb index 794fefafcb..ed51fb389a 100644 --- a/spec/uat_actions/generate_plate_volumes_spec.rb +++ b/spec/uat_actions/generate_plate_volumes_spec.rb @@ -79,7 +79,9 @@ end it 'adds an error' do - expect(uat_action.errors.full_messages).to include('Maximum volume needs to be greater than or equal to minimum volume') + expect(uat_action.errors.full_messages).to include( + 'Maximum volume needs to be greater than or equal to minimum volume' + ) end end end