Skip to content

Commit

Permalink
Merge pull request #4331 from sanger/y24-055-2
Browse files Browse the repository at this point in the history
Y24-055 Take 2
  • Loading branch information
BenTopping authored Sep 11, 2024
2 parents 1fdca0a + 55022bd commit ecbdee7
Show file tree
Hide file tree
Showing 15 changed files with 518 additions and 4 deletions.
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ GEM
nio4r (2.7.3)
nokogiri (1.16.7-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.7-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.16.7-x86_64-linux)
racc (~> 1.4)
parallel (1.24.0)
Expand Down Expand Up @@ -565,6 +567,7 @@ GEM
PLATFORMS
arm64-darwin
arm64-darwin-23
x86_64-darwin-21
x86_64-linux

DEPENDENCIES
Expand Down
12 changes: 12 additions & 0 deletions app/models/tag_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
class TagSet < ApplicationRecord
# For dual index tags, tag_group is i7 oligos and tag2_group is i5 oligos
belongs_to :tag_group, class_name: 'TagGroup', optional: false

# In order to support a unified access to dual and single index sets,
# it allows tag2_group to be null.
belongs_to :tag2_group, class_name: 'TagGroup', optional: true

# We can assume adapter_type is the same for both tag groups
Expand All @@ -15,7 +18,16 @@ class TagSet < ApplicationRecord
validates :name, presence: true, uniqueness: true
validate :tag_group_adapter_types_must_match

scope :dual_index, -> { where.not(tag2_group: nil) }
scope :visible,
-> { joins(:tag_group, :tag2_group).where(tag_group: { visible: true }, tag2_group: { visible: true }) }

# The scoping retrieves the visible tag sets and makes sure they are dual index.
scope :visible_dual_index, -> { dual_index.visible }

# Dynamic method to determine the visibility of a tag_set based on the visibility of its tag_groups
# TagSet has a method to check if itself is visible by checking
# the visibility of both tag_group and (if not null) tag2_group.
def visible
tag_group.visible && (tag2_group.nil? || tag2_group.visible)
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

module SequencescapeExcel
module SpecialisedField
##
# DualIndexTagSet
class DualIndexTagSet
include Base
include ValueRequired

validate :dual_index_tag_set

def tag_set_id
@tag_set_id ||= ::TagSet.dual_index.visible.find_by(name: value)&.id
end

private

# Check the Dual Index Tag Set with a visible tag_group and tag2_group exists here
# Check the TagSet/TagWell combination in DualIndexTagWell
def dual_index_tag_set
return if tag_set_id.present?

errors.add(:base, "could not find a visible dual index Tag Set with name '#{value}'.")
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# frozen_string_literal: true

module SequencescapeExcel
module SpecialisedField
##
# DualIndexTagWell
class DualIndexTagWell
include Base
include ValueRequired

# ValueToUpcase converts the `value` to uppercase
# For exqmple the `value` used in the well_index method would be 'A1' instead of 'a1'
# This is important because the description_to_vertical_plate_position method
# returns a different value for 'A1' vs 'a1', where the upcase version is correct
include ValueToUpcase

attr_accessor :sf_dual_index_tag_set

validates :well_index, presence: { message: 'is not valid' }
validates :tag, presence: { message: 'does not have associated i7 tag' }, if: :well_index
validates :tag2, presence: { message: 'does not have associated i5 tag' }, if: :well_index

PLATE_SIZE = 96

def update(_attributes = {})
return unless valid?

raise StandardError, 'Tag aliquot mismatch' unless asset.aliquots.one?

# For dual index tags, tag is a i7 oligo and tag2 is a i5 oligo
asset.aliquots.first.update(tag: tag, tag2: tag2)
end

def link(other_fields)
self.sf_dual_index_tag_set = other_fields[SequencescapeExcel::SpecialisedField::DualIndexTagSet]
end

# From the validation in DualIndexTagSet, we know this tag set is a valid dual index tag set
# with a visible tag group and visible tag2 group
def dual_index_tag_set
@dual_index_tag_set = TagSet.find(sf_dual_index_tag_set.tag_set_id) if sf_dual_index_tag_set&.tag_set_id
end

def tag_group_id
@tag_group_id ||= ::TagGroup.find_by(id: dual_index_tag_set.tag_group_id, visible: true).id
end

def tag2_group_id
@tag2_group_id ||= ::TagGroup.find_by(id: dual_index_tag_set.tag2_group_id, visible: true).id
end

private

# This assumes that the tags within a tag group for dual index tags are listed in 'column' order,
# i.e. the first tag is the one in the first column, the second tag is the one in the second column, etc.
# therefore description_to_vertical_plate_position is used to get the correct map_id
# A1 --> 1
# B1 --> 2
# ...
# H12 --> 96
def well_index
@well_index = Map::Coordinate.description_to_vertical_plate_position(value, PLATE_SIZE)
end

# i7 tag
def tag
Tag.find_by(tag_group_id: tag_group_id, map_id: well_index)
end

# i5 tag
def tag2
Tag.find_by(tag_group_id: tag2_group_id, map_id: well_index)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module SequencescapeExcel
module SpecialisedField
##
# ValueToUpcase
module ValueToUpcase
def value=(value)
@value = value.upcase if value.present?
end
end
end
end
41 changes: 39 additions & 2 deletions config/sample_manifest_excel/columns.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ chromium_tag_well:
showErrorMessage: true
errorStyle: :stop
errorTitle: "Tag well"
error: "Tag Index must a well."
error: "Tag Index must be a well."
conditional_formattings:
empty_cell:
is_number:
Expand Down Expand Up @@ -202,6 +202,43 @@ library_type_long_read:
conditional_formattings:
empty_cell:
is_error:
dual_index_tag_set:
heading: TAG PLATE NAME
unlocked: true
validation:
options:
type: :list
formula1: "$A$1:$A$2"
allowBlank: false
showInputMessage: true
promptTitle: "Dual Index Tag Plate Name"
prompt: "Input the name of a valid dual index tag plate."
range_name: :dual_index_tag_sets
conditional_formattings:
empty_cell:
dual_index_tag_well:
heading: DUAL INDEX TAG WELL
unlocked: true
validation:
options:
type: :textLength
operator: :lessThanOrEqual
formula1: "3"
allowBlank: false
showInputMessage: true
promptTitle: "Dual Index Tag well"
prompt: "The name of the well, eg. A1 which supplied the dual index tag"
showErrorMessage: true
errorStyle: :stop
errorTitle: "Dual index tag well"
error: "Dual Index Tag must be a well."
conditional_formattings:
empty_cell:
is_number:
len:
formula:
operator: ">"
operand: 3
reference_genome:
heading: REFERENCE GENOME
unlocked: true
Expand Down Expand Up @@ -461,7 +498,7 @@ dna_source:
empty_cell:
is_error:
date_of_sample_collection:
heading: DATE OF SAMPLE COLLECTION (YYY-MM-DD)
heading: DATE OF SAMPLE COLLECTION (YYYY-MM-DD)
unlocked: true
validation:
options:
Expand Down
59 changes: 58 additions & 1 deletion config/sample_manifest_excel/manifest_types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ plate_rnachip:
- :sample_ebi_accession_number
- :donor_id
plate_library:
heading: "Library Plate"
heading: "Library Plate - custom tags"
asset_type: "library_plate"
columns:
- :sanger_plate_id
Expand Down Expand Up @@ -184,6 +184,63 @@ plate_library:
- :sample_ebi_accession_number
- :donor_id
- :primer_panel
plate_dual_index_tag_library:
heading: "Library Plate - dual index tag plate"
asset_type: "library_plate"
columns:
- :sanger_plate_id
- :well
- :sanger_sample_id
- :supplier_name
- :dual_index_tag_set
- :dual_index_tag_well
- :reference_genome
- :library_type
- :insert_size_from
- :insert_size_to
- :cohort
- :volume
- :concentration
- :gender
- :country_of_origin
- :geographical_region
- :ethnicity
- :dna_source
- :date_of_sample_collection
- :date_of_sample_extraction
- :sample_extraction_method
- :sample_purified
- :purification_method
- :concentration_determined_by
- :sample_storage_conditions
- :mother
- :father
- :sibling
- :gc_content
- :sample_public_name
- :sample_taxon_id
- :sample_common_name
- :sample_description
- :sample_strain_att
- :sample_type
- :genotype
- :phenotype
- :age
- :developmental_stage
- :cell_type
- :disease_state
- :compound
- :dose
- :immunoprecipitate
- :growth_condition
- :rnai
- :organism_part
- :time_point
- :treatment
- :subject
- :disease
- :sample_ebi_accession_number
- :donor_id
plate_chromium_library:
heading: "Chromium Library Plate"
asset_type: "library_plate"
Expand Down
4 changes: 4 additions & 0 deletions config/sample_manifest_excel/ranges.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ chromium_tag_groups:
identifier: :name
scope: :chromium
scope_on: TagGroup
dual_index_tag_sets:
identifier: :name
scope: :visible_dual_index
scope_on: TagSet
gc_content:
options:
- "Neutral"
Expand Down
37 changes: 37 additions & 0 deletions spec/data/sample_manifest_excel/columns.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,43 @@ library_type:
conditional_formattings:
empty_cell:
is_error:
dual_index_tag_set:
heading: TAG PLATE NAME
unlocked: true
validation:
options:
type: :list
formula1: "$A$1:$A$2"
allowBlank: false
showInputMessage: true
promptTitle: "Dual Index Tag Plate Name"
prompt: "Input the name of a valid dual index tag plate."
range_name: :dual_index_tag_sets
conditional_formattings:
empty_cell:
dual_index_tag_well:
heading: DUAL INDEX TAG WELL
unlocked: true
validation:
options:
type: :textLength
operator: :lessThanOrEqual
formula1: "3"
allowBlank: false
showInputMessage: true
promptTitle: "Dual Index Tag well"
prompt: "The name of the well, eg. A1, which is supplied in the dual index tag plate"
showErrorMessage: true
errorStyle: :stop
errorTitle: "Dual index tag well"
error: "Dual Index Tag must be a well."
conditional_formattings:
empty_cell:
is_number:
len:
formula:
operator: ">"
operand: 3
reference_genome:
heading: REFERENCE GENOME
unlocked: true
Expand Down
Loading

0 comments on commit ecbdee7

Please sign in to comment.