Skip to content

Commit

Permalink
Merge pull request #4596 from sanger/y24-475-bug-improve-integration-…
Browse files Browse the repository at this point in the history
…suite-uat-actions-error-message

Y24-475 [PR] [BUG] Improve integration suite UAT actions error message
  • Loading branch information
yoldas authored Jan 16, 2025
2 parents 644d4c1 + e324a76 commit 49baaf3
Show file tree
Hide file tree
Showing 8 changed files with 509 additions and 6 deletions.
64 changes: 58 additions & 6 deletions app/uat_actions/uat_actions/generate_plates.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
# frozen_string_literal: true

# Will construct plates with well_count wells filled with samples
# rubocop:disable Metrics/ClassLength
class UatActions::GeneratePlates < UatActions
self.title = 'Generate plate'
self.description = 'Generate plates in the selected study.'
self.category = :generating_samples

ERROR_WELL_COUNT_EXCEEDS_PLATE_SIZE = "Well count of %s exceeds the plate size of %s for the plate purpose '%s'."
ERROR_PLATE_PURPOSE_DOES_NOT_EXIST = "Plate purpose '%s' does not exist."
ERROR_STUDY_DOES_NOT_EXIST = 'Study %s does not exist.'

form_field :plate_purpose_name,
:select,
label: 'Plate Purpose',
Expand Down Expand Up @@ -45,8 +50,27 @@ class UatActions::GeneratePlates < UatActions
help: 'The order in which wells are laid out. Affects where empty wells are located.',
select_options: %w[Column Row Random]

validate :well_count_smaller_than_plate_size
validates :number_of_samples_in_each_well, numericality: { greater_than: 0, only_integer: true, allow_blank: false }
validates :plate_purpose_name, presence: true
validates :plate_count, numericality: { greater_than: 0, smaller_than: 20, only_integer: true, allow_blank: false }
validates :well_count, numericality: { greater_than: 0, only_integer: true, allow_blank: false }
validates :number_of_samples_in_each_well,
numericality: {
greater_than: 0,
smaller_than: 20,
only_integer: true,
allow_blank: false
}
validates :study_name, presence: true
validates :well_layout,
presence: true,
inclusion: {
in: %w[Column Row Random],
message: 'must be Column, Row, or Random'
}

validate :validate_plate_purpose_exists
validate :validate_well_count_is_smaller_than_plate_size
validate :validate_study_exists

def self.default
new(
Expand All @@ -72,11 +96,38 @@ def perform

private

def well_count_smaller_than_plate_size
return true if well_count.to_i <= plate_purpose.size
# Validates that the plate purpose exists for the selected plate purpose name.
#
# @return [void]
def validate_plate_purpose_exists
return if plate_purpose_name.blank?
return if PlatePurpose.exists?(name: plate_purpose_name)

message = format(ERROR_PLATE_PURPOSE_DOES_NOT_EXIST, plate_purpose_name)
errors.add(:plate_purpose_name, message)
end

# Validates that the well count is smaller than the plate size for the
# selected plate purpose.
#
# @return [void]
def validate_well_count_is_smaller_than_plate_size
return unless PlatePurpose.exists?(name: plate_purpose_name)
return if well_count.to_i <= plate_purpose.size

message = format(ERROR_WELL_COUNT_EXCEEDS_PLATE_SIZE, well_count, plate_purpose.size, plate_purpose.name)
errors.add(:well_count, message)
end

# Validates that the study exists for the selected study name.
#
# @return [void]
def validate_study_exists
return if study_name.blank?
return if Study.exists?(name: study_name)

errors.add(:well_count, "is larger than the size of a #{plate_purpose.name} (plate_purpose.size)")
false
message = format(ERROR_STUDY_DOES_NOT_EXIST, study_name)
errors.add(:study_name, message)
end

# Ensures number of samples per occupied well is at least 1
Expand Down Expand Up @@ -171,3 +222,4 @@ def plate_purpose
Purpose.find_by!(name: plate_purpose_name)
end
end
# rubocop:enable Metrics/ClassLength
16 changes: 16 additions & 0 deletions app/uat_actions/uat_actions/generate_sample_manifest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class UatActions::GenerateSampleManifest < UatActions
self.description = 'Generate sample manifest with the provided information.'
self.category = :generating_samples

ERROR_TUBE_PURPOSE_DOES_NOT_EXIST = "Tube purpose '%s' does not exist."

form_field :study_name,
:select,
label: 'Study Name',
Expand Down Expand Up @@ -41,6 +43,9 @@ class UatActions::GenerateSampleManifest < UatActions

form_field :with_samples, :check_box, help: 'Create new samples for recipients?', label: 'With Samples?'

validates :tube_purpose_name, presence: true
validate :validate_tube_purpose_exists

def self.default
new(
study_name: UatActions::StaticRecords.study.name,
Expand All @@ -59,6 +64,17 @@ def perform
true
end

# Validates that the tube purpose exists for the selected tube purpose name.
#
# @return [void]
def validate_tube_purpose_exists
return if tube_purpose_name.blank? # Already validated by presence
return if Tube::Purpose.exists?(name: tube_purpose_name)

message = format(ERROR_TUBE_PURPOSE_DOES_NOT_EXIST, tube_purpose_name)
errors.add(:tube_purpose_name, message)
end

def create_sample_manifest
SampleManifest.create!(study:, supplier:, asset_type:, count:, purpose:)
end
Expand Down
77 changes: 77 additions & 0 deletions app/uat_actions/uat_actions/test_submission.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ class UatActions::TestSubmission < UatActions # rubocop:todo Metrics/ClassLength
'This may produce odd results for some pipelines.'
self.category = :setup_and_test

ERROR_SUBMISSION_TEMPLATE_DOES_NOT_EXIST = "Submission template '%s' does not exist."
ERROR_PLATE_DOES_NOT_EXIST = 'Plate with barcode %s does not exist.'
ERROR_PLATE_PURPOSE_DOES_NOT_EXIST = "Plate purpose '%s' does not exist."
ERROR_LIBRARY_TYPE_DOES_NOT_EXIST = "Library type '%s' does not exist."
ERROR_PRIMER_PANEL_DOES_NOT_EXIST = "Primer panel '%s' does not exist."

# Form fields
form_field :submission_template_name,
:select,
Expand Down Expand Up @@ -95,6 +101,12 @@ class UatActions::TestSubmission < UatActions # rubocop:todo Metrics/ClassLength
validates :number_of_samples_in_each_well, numericality: { greater_than: 0, only_integer: true, allow_blank: true }
validates :number_of_wells_to_submit, numericality: { greater_than: 0, only_integer: true, allow_blank: true }

validate :validate_submission_template_exists
validate :validate_plate_exists
validate :validate_plate_purpose_exists
validate :validate_library_type_exists
validate :validate_primer_panel_exists

#
# Returns a default copy of the UatAction which will be used to fill in the form
#
Expand Down Expand Up @@ -145,6 +157,71 @@ def perform # rubocop:todo Metrics/AbcSize

private

# Validates that the submission template exists for the specified submission
# template name.
#
# @return [void]
def validate_submission_template_exists
return if submission_template_name.blank? # already validated by presence
return if SubmissionTemplate.exists?(name: submission_template_name)

message = format(ERROR_SUBMISSION_TEMPLATE_DOES_NOT_EXIST, submission_template_name)
errors.add(:submission_template_name, message)
end

# Validates that the plate exists for the specified plate barcode. It is
# is skipped if no barcode is provided, because a new plate is generated.
#
# @return [void]
def validate_plate_exists
return if plate_barcode.blank? # an appropriate plate will be generated
return if Plate.find_by_barcode(plate_barcode.strip).present?

message = format(ERROR_PLATE_DOES_NOT_EXIST, plate_barcode)
errors.add(:plate_barcode, message)
end

# Validates that the plate purpose exists for the specified plate purpose
# name. It will be skipped if a barcode specified. It will be also skipped
# when no purpose is provided because an appropriate purpose will be used when
# generating a new plate.
#
# @return [void]
def validate_plate_purpose_exists
return if plate_barcode.present? # takes precedence over plate purpose
return if plate_purpose_name.blank? # an appropriate purpose will be used
return if PlatePurpose.exists?(name: plate_purpose_name)

message = format(ERROR_PLATE_PURPOSE_DOES_NOT_EXIST, plate_purpose_name)
errors.add(:plate_purpose_name, message)
end

# Validates that the library type exists for the specified library type name.
# It is skipped if no library type name is provided because the first library
# type found will be used.
#
# return [void]
def validate_library_type_exists
return if library_type_name.blank? # first library type found will be used
return if LibraryType.exists?(name: library_type_name)

message = format(ERROR_LIBRARY_TYPE_DOES_NOT_EXIST, library_type_name)
errors.add(:library_type_name, message)
end

# Validates that the primer panel exists for the specified primer panel name.
# It is skipped if no primer panel name is provided because it is not
# applicable for the current submission template.
#
# return [void]
def validate_primer_panel_exists
return if primer_panel_name.blank? # not applicable for the template
return if PrimerPanel.exists?(name: primer_panel_name)

message = format(ERROR_PRIMER_PANEL_DOES_NOT_EXIST, primer_panel_name)
errors.add(:primer_panel_name, message)
end

def submission_template
@submission_template = SubmissionTemplate.find_by(name: submission_template_name)
end
Expand Down
51 changes: 51 additions & 0 deletions app/uat_actions/uat_actions/tube_submission.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
# This UAT Action will generates a basic submission for tubes. Initially, it
# has been designed for generating scRNA Core Donor Pooling and cDNA Prep
# submissions on LRC Bank Seq/Spare tubes.
# rubocop:disable Metrics/ClassLength
class UatActions::TubeSubmission < UatActions
self.title = 'Tube submission'
self.description = 'Generates a basic submission for tubes.'
self.category = :setup_and_test

ERROR_SUBMISSION_TEMPLATE_DOES_NOT_EXIST = "Submission template '%s' does not exist."
ERROR_TUBES_DO_NOT_EXIST = 'Tubes with barcodes do not exist: %s'
ERROR_LIBRARY_TYPE_DOES_NOT_EXIST = "Library type '%s' does not exist."

form_field :submission_template_name,
:select,
label: 'Submission Template',
Expand Down Expand Up @@ -56,6 +61,11 @@ class UatActions::TubeSubmission < UatActions
}

validates :submission_template, presence: { message: 'could not be found' }
validates :tube_barcodes, presence: true

validate :validate_submission_template_exists
validate :validate_tubes_exist
validate :validate_library_type_exists

# Returns a default copy of the UatAction which will be used to fill in the form
#
Expand Down Expand Up @@ -95,6 +105,46 @@ def perform
true
end

# Validates that the submission template exists for the specified submission
# template name.
#
# @return [void]
def validate_submission_template_exists
return if submission_template_name.blank? # already validated by presence
return if SubmissionTemplate.exists?(name: submission_template_name)

message = format(ERROR_SUBMISSION_TEMPLATE_DOES_NOT_EXIST, submission_template_name)
errors.add(:submission_template_name, message)
end

# Validates that the tubes exist for the specified barcodes.
#
# @return [void]
def validate_tubes_exist
return if tube_barcodes.blank? # already validated by presence
barcodes =
tube_barcodes
.gsub(/(\\[trfvn])+/, ' ')
.split
.select do |barcode|
Tube.find_by_barcode(barcode).blank? # not found
end

message = format(ERROR_TUBES_DO_NOT_EXIST, barcodes.join(', '))
errors.add(:tube_barcodes, message)
end

# Validates that the library type exists for the specified library type name.
#
# return [void]
def validate_library_type_exists
return if library_type_name.blank? # optional
return if LibraryType.exists?(name: library_type_name)

message = format(ERROR_LIBRARY_TYPE_DOES_NOT_EXIST, library_type_name)
errors.add(:library_type_name, message)
end

# Fills the report with the information from the submission
#
# @return [Void]
Expand Down Expand Up @@ -191,3 +241,4 @@ def user
UatActions::StaticRecords.user
end
end
# rubocop:enable Metrics/ClassLength
79 changes: 79 additions & 0 deletions spec/uat_actions/generate_plates_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,83 @@
it 'returns a default' do
expect(described_class.default).to be_a described_class
end

describe '#valid?' do
let(:uat_action) { described_class.new(parameters) }

describe '#validate_plate_purpose_exists' do
let(:parameters) { { plate_purpose_name: } }
let(:error_message) { format(described_class::ERROR_PLATE_PURPOSE_DOES_NOT_EXIST, plate_purpose_name) }

context 'when the plate purpose does not exist' do
let(:plate_purpose_name) { 'Invalid Plate Purpose' }

it 'adds the error message' do
expect(uat_action.valid?).to be false
expect(uat_action.errors[:plate_purpose_name]).to include(error_message)
end
end

context 'when the plate purpose exists' do
let(:plate) { create(:plate) }
let(:plate_purpose_name) { plate.purpose.name }

it 'does not add the error message' do
uat_action.valid? # run validations
expect(uat_action.errors[:plate_purpose_name]).not_to include(error_message)
end
end
end

describe '#validate_well_count_is_smaller_than_plate_size' do
let(:parameters) { { plate_purpose_name:, well_count: } }
let(:plate) { create(:plate, size: 96) }
let(:plate_purpose_name) { plate.purpose.name }
let(:error_message) do
format(described_class::ERROR_WELL_COUNT_EXCEEDS_PLATE_SIZE, well_count, plate.size, plate_purpose_name)
end

context 'when the well count exceeds the plate size' do
let(:well_count) { plate.size + 1 }

it 'adds the error message' do
expect(uat_action.valid?).to be false
expect(uat_action.errors[:well_count]).to include(error_message)
end
end

context 'when the well count is equal to the plate size' do
let(:well_count) { plate.size }

it 'does not add the error message' do
uat_action.valid? # run validations
expect(uat_action.errors[:well_count]).not_to include(error_message)
end
end
end

describe '#validate_study_exists' do
let(:parameters) { { study_name: } }
let(:error_message) { format(described_class::ERROR_STUDY_DOES_NOT_EXIST, study_name) }

context 'when the study exists' do
let(:study) { create(:study) }
let(:study_name) { study.name }

it 'does not add the error message' do
uat_action.valid? # run validations
expect(uat_action.errors[:study_name]).not_to include(error_message)
end
end

context 'when the study does not exist' do
let(:study_name) { 'Invalid Study' }

it 'adds the error message' do
expect(uat_action.valid?).to be false
expect(uat_action.errors[:study_name]).to include(error_message)
end
end
end
end
end
Loading

0 comments on commit 49baaf3

Please sign in to comment.