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..c316881cf0 --- /dev/null +++ b/app/uat_actions/uat_actions/generate_plate_volumes.rb @@ -0,0 +1,101 @@ +# 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..043c108636 --- /dev/null +++ b/spec/uat_actions/generate_plate_volumes_spec.rb @@ -0,0 +1,56 @@ +# 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!(: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 } } + + 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 '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 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 + + 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