diff --git a/.github/workflows/compile_test.yml b/.github/workflows/compile_test.yml new file mode 100644 index 000000000..40ca1ec59 --- /dev/null +++ b/.github/workflows/compile_test.yml @@ -0,0 +1,39 @@ +name: Test compilation + +on: + - push + - pull_request + +env: + BUNDLE_WITHOUT: 'test lint' + +jobs: + Build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: nelonoel/branch-name@v1.0.1 + + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + + # Actually run our build + - name: Create Build + run: ./compile-build + + # Create a release tag based on the branch name and .release-version file + - name: Set release tag + # https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable + # On the develop branch this might create RELEASE_VERSION=2.4.6-987654321-develop + # On the master branch this would then only create RELEASE_VERSION=2.4.6 + run: echo "RELEASE_VERSION=$(printf -- '%s%s\n' $(cat .release-version) $([ ${BRANCH_NAME} = "develop" ] && printf -- '-%s-develop' ${GITHUB_RUN_ID} || echo ""))" >> $GITHUB_ENV + + # Do not actually save build or create release itself diff --git a/.github/workflows/js_test.yml b/.github/workflows/js_test.yml deleted file mode 100644 index 7b85a9cab..000000000 --- a/.github/workflows/js_test.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Javascript testing - -on: - - push - - pull_request - -env: - TZ: Europe/London - -jobs: - test: - name: Test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Setup node - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - - name: Node Information - run: node --version - - name: Install - run: yarn install - - name: Run yarn test - run: yarn test diff --git a/.github/workflows/test_js.yml b/.github/workflows/test_js.yml new file mode 100644 index 000000000..c92f4780e --- /dev/null +++ b/.github/workflows/test_js.yml @@ -0,0 +1,33 @@ +name: Javascript testing + +on: + - push + - pull_request + +jobs: + js_test: + runs-on: ubuntu-latest + env: + TZ: Europe/London + + steps: + - uses: actions/checkout@v4 + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + - name: Node Information + run: node --version + - name: Install + run: yarn install + - name: Run yarn test + run: yarn test + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4.0.1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + flags: javascript,${{ github.event_name }} + fail_ci_if_error: true + disable_search: true + files: ${{ github.workspace }}/app/javascript/coverage/lcov.info + # Note: see codecov.yml for more additional settings diff --git a/.github/workflows/ruby_test.yml b/.github/workflows/test_ruby.yml similarity index 67% rename from .github/workflows/ruby_test.yml rename to .github/workflows/test_ruby.yml index 226ed52f4..cbc21de01 100644 --- a/.github/workflows/ruby_test.yml +++ b/.github/workflows/test_ruby.yml @@ -27,9 +27,17 @@ jobs: # example if you need more control over bundler. - name: Additional setup run: bin/setup - - name: Test & publish code coverage + - name: Run rspec + run: bundle exec rspec + - name: Publish code coverage to Code Climate uses: paambaati/codeclimate-action@v5.0.0 env: CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID || '1735fdb62543d410c5ed4469e402641a7986f1ebf62ff7398f3ab8ccc98069ef' }} + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v4.0.1 with: - coverageCommand: bundle exec rspec + token: ${{ secrets.CODECOV_TOKEN }} + flags: ruby,${{ github.event_name }} + fail_ci_if_error: true + disable_search: true + files: ${{ github.workspace }}/lcov.info diff --git a/.release-version b/.release-version index ff3ff28f6..850ac8f28 100644 --- a/.release-version +++ b/.release-version @@ -1 +1 @@ -3.45.0 +3.46.0 diff --git a/Gemfile b/Gemfile index a357e156f..d29f53928 100644 --- a/Gemfile +++ b/Gemfile @@ -46,7 +46,7 @@ group :test do gem 'launchy' # Used by capybara for eg. save_and_open_screenshot gem 'rails-controller-testing' gem 'rspec-json_expectations' - gem 'rspec-rails', '6.1.0' + gem 'rspec-rails', '6.1.1' gem 'selenium-webdriver', '~> 4.1', require: false gem 'simplecov', require: false gem 'simplecov-lcov', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 90a991f86..357b5320e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -110,7 +110,7 @@ GEM base64 (0.2.0) bigdecimal (3.1.6) bindex (0.8.1) - bootsnap (1.17.1) + bootsnap (1.18.3) msgpack (~> 1.2) bootstrap (4.6.2) autoprefixer-rails (>= 9.1.0) @@ -118,11 +118,11 @@ GEM sassc-rails (>= 2.0.0) builder (3.2.4) byebug (11.1.3) - capybara (3.39.2) + capybara (3.40.0) addressable matrix mini_mime (>= 0.1.3) - nokogiri (~> 1.8) + nokogiri (~> 1.11) rack (>= 1.6.0) rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) @@ -133,11 +133,12 @@ GEM coderay (1.1.3) concurrent-ruby (1.2.3) connection_pool (2.4.1) - crack (0.4.5) + crack (1.0.0) + bigdecimal rexml crass (1.0.6) date (3.3.4) - diff-lcs (1.5.0) + diff-lcs (1.5.1) docile (1.4.0) drb (2.2.0) ruby2_keywords @@ -146,7 +147,7 @@ GEM actionmailer (>= 5.2, < 8) activesupport (>= 5.2, < 8) execjs (2.9.1) - factory_bot (6.4.5) + factory_bot (6.4.6) activesupport (>= 5.0.0) faraday (1.10.3) faraday-em_http (~> 1.0) @@ -199,7 +200,7 @@ GEM activesupport (>= 3.0) nokogiri (>= 1.6) io-console (0.7.2) - irb (1.11.1) + irb (1.11.2) rdoc reline (>= 0.4.2) jquery-rails (4.6.0) @@ -229,13 +230,13 @@ GEM method_source (1.0.0) mini_mime (1.1.5) mini_portile2 (2.8.5) - minitest (5.21.2) + minitest (5.22.2) msgpack (1.7.2) multi_json (1.15.0) - multipart-post (2.3.0) + multipart-post (2.4.0) mutex_m (0.2.0) nenv (0.3.0) - net-imap (0.4.9.1) + net-imap (0.4.10) date net-protocol net-pop (0.1.2) @@ -245,7 +246,7 @@ GEM net-smtp (0.4.0.1) net-protocol nio4r (2.7.0) - nokogiri (1.16.0) + nokogiri (1.16.2) mini_portile2 (~> 2.8.2) racc (~> 1.4) notiffany (0.1.3) @@ -254,7 +255,7 @@ GEM oj (3.16.3) bigdecimal (>= 3.0) parallel (1.24.0) - parser (3.3.0.4) + parser (3.3.0.5) ast (~> 2.4.1) racc popper_js (1.16.1) @@ -270,7 +271,7 @@ GEM puma (6.4.2) nio4r (~> 2.0) racc (1.7.3) - rack (3.0.8) + rack (3.0.9) rack-mini-profiler (3.3.0) rack (>= 1.2.0) rack-proxy (0.7.7) @@ -326,20 +327,20 @@ GEM reline (0.4.2) io-console (~> 0.5) rexml (3.2.6) - rspec (3.12.0) - rspec-core (~> 3.12.0) - rspec-expectations (~> 3.12.0) - rspec-mocks (~> 3.12.0) - rspec-core (3.12.2) - rspec-support (~> 3.12.0) - rspec-expectations (3.12.3) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.0) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.12.0) + rspec-support (~> 3.13.0) rspec-json_expectations (2.2.0) - rspec-mocks (3.12.6) + rspec-mocks (3.13.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.12.0) - rspec-rails (6.1.0) + rspec-support (~> 3.13.0) + rspec-rails (6.1.1) actionpack (>= 6.1) activesupport (>= 6.1) railties (>= 6.1) @@ -347,8 +348,8 @@ GEM rspec-expectations (~> 3.12) rspec-mocks (~> 3.12) rspec-support (~> 3.12) - rspec-support (3.12.1) - rubocop (1.60.1) + rspec-support (3.13.0) + rubocop (1.60.2) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -370,7 +371,7 @@ GEM rubocop (>= 1.33.0, < 2.0) rubocop-ast (>= 1.30.0, < 2.0) ruby-progressbar (1.13.0) - ruby-units (4.0.1) + ruby-units (4.0.2) ruby2_keywords (0.0.5) rubyzip (2.3.2) sass-rails (6.0.0) @@ -384,7 +385,8 @@ GEM sprockets-rails tilt select2-rails (4.0.13) - selenium-webdriver (4.16.0) + selenium-webdriver (4.17.0) + base64 (~> 0.2) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) @@ -425,7 +427,7 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webmock (3.19.1) + webmock (3.20.0) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -442,7 +444,7 @@ GEM xpath (3.2.0) nokogiri (~> 1.8) yard (0.9.34) - zeitwerk (2.6.12) + zeitwerk (2.6.13) PLATFORMS ruby @@ -471,7 +473,7 @@ DEPENDENCIES rails-controller-testing rake rspec-json_expectations - rspec-rails (= 6.1.0) + rspec-rails (= 6.1.1) rubocop rubocop-performance rubocop-rails diff --git a/LICENSE b/LICENSE index fe5deec55..f8d7d7434 100644 --- a/LICENSE +++ b/LICENSE @@ -1,22 +1,21 @@ -Copyright (c) 2011-2021 Genome Research Ltd. +MIT License -Author: -Eduardo Martin Rojo -James Glover -Matthew Denner -Sean Dunn -Stephen Inglis -Yana Proskurina +Copyright (c) 2024 Wellcome Sanger Institute - PSD -This program is free software: you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation; either version 3 of the License, or (at your option) any later -version. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -This program is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -details. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -You should have received a copy of the GNU General Public License along with -this program. If not, see . +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/app/assets/stylesheets/limber/pipeline-graph.scss b/app/assets/stylesheets/limber/pipeline-graph.scss index da8496efa..5319b476c 100644 --- a/app/assets/stylesheets/limber/pipeline-graph.scss +++ b/app/assets/stylesheets/limber/pipeline-graph.scss @@ -89,7 +89,7 @@ ul { @extend .list-group; @extend .list-group-flush; - max-height: 500px; + max-height: 50vh; overflow-y: auto; li { diff --git a/app/assets/stylesheets/limber/screen.scss b/app/assets/stylesheets/limber/screen.scss index f01531641..a278be3ec 100644 --- a/app/assets/stylesheets/limber/screen.scss +++ b/app/assets/stylesheets/limber/screen.scss @@ -535,7 +535,13 @@ footer.version-info { /* Set the fixed height of the footer here */ height: 50px; line-height: 50px; /* Vertically center the text there */ + overflow: hidden; background-color: $gray-100; + + .container { + @extend .text-center; + @extend .text-nowrap; + } } .asset-warnings { diff --git a/app/models/labels/base.rb b/app/models/labels/base.rb index ae8e46d9e..5226c0b54 100644 --- a/app/models/labels/base.rb +++ b/app/models/labels/base.rb @@ -49,10 +49,6 @@ def label_template end def label_templates_by_service - # NB. Make sure label_templates.yml contains settings for label definitions - # explicity, in order to avoid incorrect results. The lines below do not - # work as intended because the config is filled with default values for - # missing settings by PurposeConfig class. pmb_template = config[:pmb_template] || default_label_template sprint_template = config[:sprint_template] || default_sprint_label_template { 'PMB' => pmb_template, 'SPrint' => sprint_template } diff --git a/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/duplex_seq/row.rb b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/duplex_seq/row.rb new file mode 100644 index 000000000..f19391b28 --- /dev/null +++ b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/duplex_seq/row.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +# Part of the Labware creator classes +module LabwareCreators + require_dependency 'labware_creators/pcr_cycles_binned_plate/csv_file_for_duplex_seq' + + module PcrCyclesBinnedPlate::CsvFile + # + # This version of the row is for the Duplex Seq pipeline. + # + class DuplexSeq::Row < RowBase + include ActiveModel::Validations + + SUB_POOL_NOT_BLANK = 'has a value when Submit for Sequencing is N, it should be empty, in %s' + SUBMIT_FOR_SEQ_INVALID = 'is empty or has an unrecognised value (should be Y or N), in %s' + COVERAGE_MISSING = 'is missing but should be present when Submit for Sequencing is Y, in %s' + COVERAGE_NEGATIVE = 'is negative but should be a positive value, in %s' + + attr_reader :submit_for_sequencing, :sub_pool, :coverage + + validate :submit_for_sequencing_has_expected_value + validate :sub_pool_within_expected_range + validates :coverage, + presence: { + message: ->(object, _data) { COVERAGE_MISSING % object } + }, + numericality: { + greater_than: 0, + message: ->(object, _data) { COVERAGE_NEGATIVE % object } + }, + unless: -> { empty? || !submit_for_sequencing? } + + delegate :submit_for_sequencing_column, :sub_pool_column, :coverage_column, to: :header + + def initialize_pipeline_specific_columns + @submit_for_sequencing_as_string = @row_data[submit_for_sequencing_column]&.strip&.upcase + @sub_pool = @row_data[sub_pool_column]&.strip&.to_i + @coverage = @row_data[coverage_column]&.strip&.to_i + end + + def submit_for_sequencing? + @submit_for_sequencing ||= (@submit_for_sequencing_as_string == 'Y') + end + + def submit_for_sequencing_has_expected_value + return if empty? + + return if %w[Y N].include? @submit_for_sequencing_as_string + + errors.add('submit_for_sequencing', format(SUBMIT_FOR_SEQ_INVALID, to_s)) + end + + def sub_pool_within_expected_range + return if empty? + + # check the value is within range when we do expect a value to be present + if submit_for_sequencing? + in_range('sub_pool', sub_pool, @row_config.sub_pool_min, @row_config.sub_pool_max) + else + # expect sub-pool field to be blank, possible mistake by user if not + return if sub_pool.blank? + + # sub-pool is NOT blank and should be + errors.add('sub_pool', format(SUB_POOL_NOT_BLANK, to_s)) + end + end + end + end +end diff --git a/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/duplex_seq/well_details_header.rb b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/duplex_seq/well_details_header.rb new file mode 100644 index 000000000..67e70d6df --- /dev/null +++ b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/duplex_seq/well_details_header.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# Part of the Labware creator classes +module LabwareCreators + require_dependency 'labware_creators/pcr_cycles_binned_plate/csv_file_for_duplex_seq' + + module PcrCyclesBinnedPlate::CsvFile + # + # Class WellDetailsHeader provides a simple wrapper for handling and validating + # the plate barcode header row from the customer csv file + # + class DuplexSeq::WellDetailsHeader < WellDetailsHeaderBase + # Return the index of the respective column. + attr_reader :submit_for_sequencing_column, :sub_pool_column, :coverage_column + + SUBMIT_FOR_SEQUENCING_COLUMN = 'Submit for sequencing (Y/N)?' + SUB_POOL_COLUMN = 'Sub-Pool' + COVERAGE_COLUMN = 'Coverage' + NOT_FOUND = 'could not be found in: ' + + validates :submit_for_sequencing_column, presence: { message: ->(object, _data) { "#{NOT_FOUND}'#{object}'" } } + validates :sub_pool_column, presence: { message: ->(object, _data) { "#{NOT_FOUND}'#{object}'" } } + validates :coverage_column, presence: { message: ->(object, _data) { "#{NOT_FOUND}'#{object}'" } } + + private + + def initialize_pipeline_specific_columns + @submit_for_sequencing_column = index_of_header(SUBMIT_FOR_SEQUENCING_COLUMN) + @sub_pool_column = index_of_header(SUB_POOL_COLUMN) + @coverage_column = index_of_header(COVERAGE_COLUMN) + end + end + end +end diff --git a/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/plate_barcode_header.rb b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/plate_barcode_header.rb index b3e9cfc09..ccc689c80 100644 --- a/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/plate_barcode_header.rb +++ b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/plate_barcode_header.rb @@ -2,7 +2,7 @@ # Part of the Labware creator classes module LabwareCreators - require_dependency 'labware_creators/custom_pooled_tubes/csv_file' + require_dependency 'labware_creators/pcr_cycles_binned_plate/csv_file_base' # # Class PlateBarcodeHeader provides a simple wrapper for handling and validating @@ -22,9 +22,10 @@ class PcrCyclesBinnedPlate::CsvFile::PlateBarcodeHeader BARCODE_NOT_MATCHING = 'The plate barcode in the file (%s) does not match the barcode of ' \ 'the plate being uploaded to (%s), please check you have the correct file.' + NOT_FOUND = 'could not be found in: ' - validates :barcode_lbl_index, presence: { message: ->(object, _data) { "could not be found in: '#{object}'" } } - validates :plate_barcode, presence: { message: ->(object, _data) { "could not be found in: '#{object}'" } } + validates :barcode_lbl_index, presence: { message: ->(object, _data) { "#{NOT_FOUND}'#{object}'" } } + validates :plate_barcode, presence: { message: ->(object, _data) { "#{NOT_FOUND}'#{object}'" } } validate :plate_barcode_matches_parent? # diff --git a/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/row.rb b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/row.rb deleted file mode 100644 index 6b7a12b7a..000000000 --- a/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/row.rb +++ /dev/null @@ -1,173 +0,0 @@ -# frozen_string_literal: true - -# Part of the Labware creator classes -module LabwareCreators - require_dependency 'labware_creators/pcr_cycles_binned_plate/csv_file' - - # - # Class CsvRow provides a simple wrapper for handling and validating - # individual CSV rows - # - class PcrCyclesBinnedPlate::CsvFile::Row # rubocop:todo Metrics/ClassLength - include ActiveModel::Validations - - IN_RANGE = 'is empty or contains a value that is out of range (%s to %s), in %s' - SUB_POOL_NOT_BLANK = 'has a value when Submit for Sequencing is N, it should be empty, in %s' - SUBMIT_FOR_SEQ_INVALID = 'is empty or has an unrecognised value (should be Y or N), in %s' - COVERAGE_MISSING = 'is missing but should be present when Submit for Sequencing is Y, in %s' - COVERAGE_NEGATIVE = 'is negative but should be a positive value, in %s' - WELL_NOT_RECOGNISED = 'contains an invalid well name: %s' - - attr_reader :header, - :well, - :concentration, - :sanger_sample_id, - :supplier_sample_name, - :input_amount_available, - :input_amount_desired, - :sample_volume, - :diluent_volume, - :pcr_cycles, - :submit_for_sequencing, - :sub_pool, - :coverage, - :index - - validates :well, - inclusion: { - in: WellHelpers.column_order, - message: ->(object, _data) { WELL_NOT_RECOGNISED % object } - }, - unless: :empty? - validate :input_amount_desired_within_expected_range? - validate :sample_volume_within_expected_range? - validate :diluent_volume_within_expected_range? - validate :pcr_cycles_within_expected_range? - validate :submit_for_sequencing_has_expected_value? - validate :sub_pool_within_expected_range? - validates :coverage, - presence: { - message: ->(object, _data) { COVERAGE_MISSING % object } - }, - numericality: { - greater_than: 0, - message: ->(object, _data) { COVERAGE_NEGATIVE % object } - }, - unless: -> { empty? || !submit_for_sequencing? } - delegate :well_column, - :concentration_column, - :sanger_sample_id_column, - :supplier_sample_name_column, - :input_amount_available_column, - :input_amount_desired_column, - :sample_volume_column, - :diluent_volume_column, - :pcr_cycles_column, - :submit_for_sequencing_column, - :sub_pool_column, - :coverage_column, - to: :header - - # rubocop:todo Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity - def initialize(row_config, header, index, row_data) - @row_config = row_config - @header = header - @index = index - @row_data = row_data - - # initialize supplied fields - @well = (@row_data[well_column] || '').strip.upcase - @concentration = @row_data[concentration_column]&.strip&.to_f - @sanger_sample_id = @row_data[sanger_sample_id_column]&.strip - @supplier_sample_name = (@row_data[supplier_sample_name_column])&.strip - @input_amount_available = @row_data[input_amount_available_column]&.strip&.to_f - - # initialize customer fields - @input_amount_desired = @row_data[input_amount_desired_column]&.strip&.to_f - @sample_volume = @row_data[sample_volume_column]&.strip&.to_f - @diluent_volume = @row_data[diluent_volume_column]&.strip&.to_f - @pcr_cycles = @row_data[pcr_cycles_column]&.strip&.to_i - @submit_for_sequencing_as_string = @row_data[submit_for_sequencing_column]&.strip&.upcase - @sub_pool = @row_data[sub_pool_column]&.strip&.to_i - @coverage = @row_data[coverage_column]&.strip&.to_i - end - - # rubocop:enable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity - - def submit_for_sequencing? - @submit_for_sequencing ||= (@submit_for_sequencing_as_string == 'Y') - end - - def to_s - @well.present? ? "row #{index + 2} [#{@well}]" : "row #{index + 2}" - end - - def input_amount_desired_within_expected_range? - in_range?( - 'input_amount_desired', - input_amount_desired, - @row_config.input_amount_desired_min, - @row_config.input_amount_desired_max - ) - end - - def sample_volume_within_expected_range? - in_range?('sample_volume', sample_volume, @row_config.sample_volume_min, @row_config.sample_volume_max) - end - - def diluent_volume_within_expected_range? - in_range?('diluent_volume', diluent_volume, @row_config.diluent_volume_min, @row_config.diluent_volume_max) - end - - def pcr_cycles_within_expected_range? - in_range?('pcr_cycles', pcr_cycles, @row_config.pcr_cycles_min, @row_config.pcr_cycles_max) - end - - def submit_for_sequencing_has_expected_value? - return true if empty? - - return true if %w[Y N].include? @submit_for_sequencing_as_string - - errors.add('submit_for_sequencing', format(SUBMIT_FOR_SEQ_INVALID, to_s)) - end - - def sub_pool_within_expected_range? - return true if empty? - - # check the value is within range when we do expect a value to be present - if submit_for_sequencing? - return in_range?('sub_pool', sub_pool, @row_config.sub_pool_min, @row_config.sub_pool_max) - end - - # expect sub-pool field to be blank, possible mistake by user if not - return true if sub_pool.blank? - - # sub-pool is NOT blank and should be - errors.add('sub_pool', format(SUB_POOL_NOT_BLANK, to_s)) - false - end - - # Checks whether a row value it within the specified range using min/max values - # from the row config - # - # field_name [string] The name of the field being validated - # field_value [float or int] The value being tested - # min/max [float or int] The minimum and maximum in the range - # - # @return [bool] - def in_range?(field_name, field_value, min, max) - return true if empty? - - result = (min..max).cover? field_value - unless result - msg = format(IN_RANGE, min, max, to_s) - errors.add(field_name, msg) - end - result - end - - def empty? - @row_data.empty? || @row_data.compact.empty? || sanger_sample_id.blank? - end - end -end diff --git a/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/row_base.rb b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/row_base.rb new file mode 100644 index 000000000..ff3872ebe --- /dev/null +++ b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/row_base.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +# Part of the Labware creator classes +module LabwareCreators + # + # Provides a simple wrapper for handling and validating individual CSV rows. + # Abstract class, extend for uses in specific pipelines. + # + class PcrCyclesBinnedPlate::CsvFile::RowBase + include ActiveModel::Validations + + IN_RANGE = 'is empty or contains a value that is out of range (%s to %s), in %s' + WELL_NOT_RECOGNISED = 'contains an invalid well name: %s' + + attr_reader :header, + :well, + :concentration, + :sanger_sample_id, + :supplier_sample_name, + :input_amount_available, + :input_amount_desired, + :sample_volume, + :diluent_volume, + :pcr_cycles, + :index + + validates :well, + inclusion: { + in: WellHelpers.column_order, + message: ->(object, _data) { WELL_NOT_RECOGNISED % object } + }, + unless: :empty? + validate :input_amount_desired_within_expected_range + validate :sample_volume_within_expected_range + validate :diluent_volume_within_expected_range + validate :pcr_cycles_within_expected_range + + delegate :well_column, + :concentration_column, + :sanger_sample_id_column, + :supplier_sample_name_column, + :input_amount_available_column, + :input_amount_desired_column, + :sample_volume_column, + :diluent_volume_column, + :pcr_cycles_column, + to: :header + + # rubocop:todo Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity + def initialize(row_config, header, index, row_data) + @row_config = row_config + @header = header + @index = index + @row_data = row_data + + # initialize supplied fields + @well = (@row_data[well_column] || '').strip.upcase + @concentration = @row_data[concentration_column]&.strip&.to_f + @sanger_sample_id = @row_data[sanger_sample_id_column]&.strip + @supplier_sample_name = (@row_data[supplier_sample_name_column])&.strip + @input_amount_available = @row_data[input_amount_available_column]&.strip&.to_f + + # initialize customer fields + @input_amount_desired = @row_data[input_amount_desired_column]&.strip&.to_f + @sample_volume = @row_data[sample_volume_column]&.strip&.to_f + @diluent_volume = @row_data[diluent_volume_column]&.strip&.to_f + @pcr_cycles = @row_data[pcr_cycles_column]&.strip&.to_i + + initialize_pipeline_specific_columns + end + + # rubocop:enable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity + + def initialize_pipeline_specific_columns + raise '#initialize_pipeline_specific_columns must be implemented on subclasses' + end + + def to_s + @well.present? ? "row #{index + 2} [#{@well}]" : "row #{index + 2}" + end + + def input_amount_desired_within_expected_range + in_range( + 'input_amount_desired', + input_amount_desired, + @row_config.input_amount_desired_min, + @row_config.input_amount_desired_max + ) + end + + def sample_volume_within_expected_range + in_range('sample_volume', sample_volume, @row_config.sample_volume_min, @row_config.sample_volume_max) + end + + def diluent_volume_within_expected_range + in_range('diluent_volume', diluent_volume, @row_config.diluent_volume_min, @row_config.diluent_volume_max) + end + + def pcr_cycles_within_expected_range + in_range('pcr_cycles', pcr_cycles, @row_config.pcr_cycles_min, @row_config.pcr_cycles_max) + end + + # Checks whether a row value it within the specified range using min/max values + # from the row config + # + # field_name [string] The name of the field being validated + # field_value [float or int] The value being tested + # min/max [float or int] The minimum and maximum in the range + def in_range(field_name, field_value, min, max) + return if empty? + + return if (min..max).cover? field_value + + msg = format(IN_RANGE, min, max, to_s) + errors.add(field_name, msg) + end + + def empty? + @row_data.empty? || @row_data.compact.empty? || sanger_sample_id.blank? + end + end +end diff --git a/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/t_nano_seq/row.rb b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/t_nano_seq/row.rb new file mode 100644 index 000000000..8f382a996 --- /dev/null +++ b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/t_nano_seq/row.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# Part of the Labware creator classes +module LabwareCreators + require_dependency 'labware_creators/pcr_cycles_binned_plate/csv_file_for_t_nano_seq' + + module PcrCyclesBinnedPlate::CsvFile + # + # This version of the row is for the Targeted NanoSeq pipeline. + # + class TNanoSeq::Row < RowBase + include ActiveModel::Validations + + HYB_PANEL_MISSING = 'is empty, in %s' + + attr_reader :hyb_panel + + validate :hyb_panel_is_present + + delegate :hyb_panel_column, to: :header + + def initialize_pipeline_specific_columns + @hyb_panel = @row_data[hyb_panel_column]&.strip + end + + # Checks whether the Hyb Panel column is filled in + def hyb_panel_is_present + return if empty? + + # TODO: can we validate the hyb panel value? Does not appear to be tracked in LIMS. + return if hyb_panel.present? + + msg = format(HYB_PANEL_MISSING, to_s) + errors.add('hyb_panel', msg) + end + end + end +end diff --git a/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/t_nano_seq/well_details_header.rb b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/t_nano_seq/well_details_header.rb new file mode 100644 index 000000000..d6e5655c8 --- /dev/null +++ b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/t_nano_seq/well_details_header.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# Part of the Labware creator classes +module LabwareCreators + require_dependency 'labware_creators/pcr_cycles_binned_plate/csv_file_for_t_nano_seq' + + module PcrCyclesBinnedPlate::CsvFile + # + # Class WellDetailsHeader provides a simple wrapper for handling and validating + # the plate barcode header row from the customer csv file + # This version is for the Targeted NanoSeq pipeline. + # + class TNanoSeq::WellDetailsHeader < WellDetailsHeaderBase + include ActiveModel::Validations + + # Return the index of the respective column. + attr_reader :hyb_panel_column + + HYB_PANEL_COLUMN = 'Hyb Panel' + NOT_FOUND = 'could not be found in: ' + + validates :hyb_panel_column, presence: { message: ->(object, _data) { "#{NOT_FOUND}'#{object}'" } } + + private + + def initialize_pipeline_specific_columns + @hyb_panel_column = index_of_header(HYB_PANEL_COLUMN) + end + end + end +end diff --git a/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/well_details_header.rb b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/well_details_header_base.rb similarity index 59% rename from app/models/labware_creators/pcr_cycles_binned_plate/csv_file/well_details_header.rb rename to app/models/labware_creators/pcr_cycles_binned_plate/csv_file/well_details_header_base.rb index 46ae8922d..9b36489f6 100644 --- a/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/well_details_header.rb +++ b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file/well_details_header_base.rb @@ -2,16 +2,15 @@ # Part of the Labware creator classes module LabwareCreators - require_dependency 'labware_creators/custom_pooled_tubes/csv_file' - # # Class WellDetailsHeader provides a simple wrapper for handling and validating # the plate barcode header row from the customer csv file # - class PcrCyclesBinnedPlate::CsvFile::WellDetailsHeader + class PcrCyclesBinnedPlate::CsvFile::WellDetailsHeaderBase include ActiveModel::Validations # Return the index of the respective column. + # These are common columns shared by all versions of the csv file. attr_reader :well_column, :concentration_column, :sanger_sample_id_column, @@ -20,10 +19,7 @@ class PcrCyclesBinnedPlate::CsvFile::WellDetailsHeader :input_amount_desired_column, :sample_volume_column, :diluent_volume_column, - :pcr_cycles_column, - :submit_for_sequencing_column, - :sub_pool_column, - :coverage_column + :pcr_cycles_column WELL_COLUMN = 'Well' CONCENTRATION_COLUMN = 'Concentration (nM)' @@ -34,44 +30,24 @@ class PcrCyclesBinnedPlate::CsvFile::WellDetailsHeader SAMPLE_VOLUME_COLUMN = 'Sample volume' DILUENT_VOLUME_COLUMN = 'Diluent volume' PCR_CYCLES_COLUMN = 'PCR cycles' - SUBMIT_FOR_SEQUENCING_COLUMN = 'Submit for sequencing (Y/N)?' - SUB_POOL_COLUMN = 'Sub-Pool' - COVERAGE_COLUMN = 'Coverage' + NOT_FOUND = 'could not be found in: ' - validates :well_column, presence: { message: ->(object, _data) { "could not be found in: '#{object}'" } } - validates :concentration_column, presence: { message: ->(object, _data) { "could not be found in: '#{object}'" } } - validates :sanger_sample_id_column, - presence: { - message: ->(object, _data) { "could not be found in: '#{object}'" } - } - validates :supplier_sample_name_column, - presence: { - message: ->(object, _data) { "could not be found in: '#{object}'" } - } - validates :input_amount_available_column, - presence: { - message: ->(object, _data) { "could not be found in: '#{object}'" } - } - validates :input_amount_desired_column, - presence: { - message: ->(object, _data) { "could not be found in: '#{object}'" } - } - validates :sample_volume_column, presence: { message: ->(object, _data) { "could not be found in: '#{object}'" } } - validates :diluent_volume_column, presence: { message: ->(object, _data) { "could not be found in: '#{object}'" } } - validates :pcr_cycles_column, presence: { message: ->(object, _data) { "could not be found in: '#{object}'" } } - validates :submit_for_sequencing_column, - presence: { - message: ->(object, _data) { "could not be found in: '#{object}'" } - } - validates :sub_pool_column, presence: { message: ->(object, _data) { "could not be found in: '#{object}'" } } - validates :coverage_column, presence: { message: ->(object, _data) { "could not be found in: '#{object}'" } } + validates :well_column, presence: { message: ->(object, _data) { "#{NOT_FOUND}'#{object}'" } } + validates :concentration_column, presence: { message: ->(object, _data) { "#{NOT_FOUND}'#{object}'" } } + validates :sanger_sample_id_column, presence: { message: ->(object, _data) { "#{NOT_FOUND}'#{object}'" } } + validates :supplier_sample_name_column, presence: { message: ->(object, _data) { "#{NOT_FOUND}'#{object}'" } } + validates :input_amount_available_column, presence: { message: ->(object, _data) { "#{NOT_FOUND}'#{object}'" } } + validates :input_amount_desired_column, presence: { message: ->(object, _data) { "#{NOT_FOUND}'#{object}'" } } + validates :sample_volume_column, presence: { message: ->(object, _data) { "#{NOT_FOUND}'#{object}'" } } + validates :diluent_volume_column, presence: { message: ->(object, _data) { "#{NOT_FOUND}'#{object}'" } } + validates :pcr_cycles_column, presence: { message: ->(object, _data) { "#{NOT_FOUND}'#{object}'" } } # # Generates a well details header from the well details header row array # # @param [Array] row The array of fields extracted from the CSV file # - def initialize(row) # rubocop:todo Metrics/AbcSize + def initialize(row) @row = row || [] @well_column = index_of_header(WELL_COLUMN) @@ -83,9 +59,8 @@ def initialize(row) # rubocop:todo Metrics/AbcSize @sample_volume_column = index_of_header(SAMPLE_VOLUME_COLUMN) @diluent_volume_column = index_of_header(DILUENT_VOLUME_COLUMN) @pcr_cycles_column = index_of_header(PCR_CYCLES_COLUMN) - @submit_for_sequencing_column = index_of_header(SUBMIT_FOR_SEQUENCING_COLUMN) - @sub_pool_column = index_of_header(SUB_POOL_COLUMN) - @coverage_column = index_of_header(COVERAGE_COLUMN) + + initialize_pipeline_specific_columns end # @@ -99,6 +74,10 @@ def to_s private + def initialize_pipeline_specific_columns + raise '#initialize_pipeline_specific_columns must be implemented on subclasses' + end + # # Returns the index of the given column name. Returns nil if the column can't be found. # Uses strip and case insensitive matching diff --git a/app/models/labware_creators/pcr_cycles_binned_plate/csv_file.rb b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file_base.rb similarity index 76% rename from app/models/labware_creators/pcr_cycles_binned_plate/csv_file.rb rename to app/models/labware_creators/pcr_cycles_binned_plate/csv_file_base.rb index 180082f58..11faeeed3 100644 --- a/app/models/labware_creators/pcr_cycles_binned_plate/csv_file.rb +++ b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file_base.rb @@ -5,15 +5,16 @@ # Part of the Labware creator classes module LabwareCreators - require_dependency 'labware_creators/pcr_cycles_binned_plate' + require_dependency 'labware_creators/pcr_cycles_binned_plate_base' # # Takes the user uploaded csv file, validates the content and extracts the well information. # This file will be downloaded from Limber based on the quantification results, then sent out # to and filled in by the customer. It describes how to dilute and bin the samples together # in the child dilution plate. + # This is the abstract version of this labware creator, extend from this class # - class PcrCyclesBinnedPlate::CsvFile + class PcrCyclesBinnedPlate::CsvFileBase include ActiveModel::Validations extend NestedValidation @@ -33,11 +34,11 @@ class PcrCyclesBinnedPlate::CsvFile :sample_volume_column, :diluent_volume_column, :pcr_cycles_column, - :submit_for_sequencing_column, - :sub_pool_column, - :coverage_column, to: :well_details_header_row + # implement on subclasses + FIELDS_FOR_WELL_DETAILS = [].freeze + # # Passing in the file to be parsed, the configuration that holds validation range thresholds, and # the parent plate barcode for validation that we are processing the correct file. @@ -51,7 +52,7 @@ def initialize(file, config, parent_barcode) end def initialize_variables(file, config, parent_barcode) - @config = Utility::PcrCyclesCsvFileUploadConfig.new(config) + @config = get_config_details_from_purpose(config) @parent_barcode = parent_barcode @data = CSV.parse(file.read) remove_bom @@ -83,16 +84,22 @@ def correctly_parsed? end def plate_barcode_header_row - @plate_barcode_header_row ||= PlateBarcodeHeader.new(@parent_barcode, @data[0]) if @data[0] + # data[0] here is the first row in the uploaded file, and should contain the plate barcode + @plate_barcode_header_row ||= + PcrCyclesBinnedPlate::CsvFile::PlateBarcodeHeader.new(@parent_barcode, @data[0]) if @data[0] end # Returns the contents of the header row for the well detail columns def well_details_header_row - @well_details_header_row ||= WellDetailsHeader.new(@data[2]) if @data[2] + raise '#well_details_header_row must be implemented on subclasses' end private + def get_config_details_from_purpose(_config) + raise '#get_config_details_from_purpose must be implemented on subclasses' + end + # remove byte order marker if present def remove_bom return unless @data.present? && @data[0][0].present? @@ -108,10 +115,12 @@ def remove_bom end def transfers - @transfers ||= - @data[3..].each_with_index.map do |row_data, index| - Row.new(@config, well_details_header_row, index + 2, row_data) - end + # sample row data starts on third row of file, 1st row is plate barcode header row, second blank + @transfers ||= @data[3..].each_with_index.map { |row_data, index| create_row(index, row_data) } + end + + def create_row(_index, _row_data) + raise '#create_row must be implemented on subclasses' end # Gates looking for wells if the file is invalid @@ -123,7 +132,8 @@ def correctly_formatted? def generate_well_details_hash return {} unless valid? - fields = %w[diluent_volume pcr_cycles submit_for_sequencing sub_pool coverage sample_volume] + fields = self.class::FIELDS_FOR_WELL_DETAILS + transfers.each_with_object({}) do |row, well_details_hash| next if row.empty? diff --git a/app/models/labware_creators/pcr_cycles_binned_plate/csv_file_for_duplex_seq.rb b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file_for_duplex_seq.rb new file mode 100644 index 000000000..24121d6e2 --- /dev/null +++ b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file_for_duplex_seq.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require './lib/nested_validation' +require 'csv' + +# Part of the Labware creator classes +module LabwareCreators + require_dependency 'labware_creators/pcr_cycles_binned_plate_for_duplex_seq' + + module PcrCyclesBinnedPlate + # + # This version of the csv file is for Duplex Seq. + # + class CsvFileForDuplexSeq < CsvFileBase + delegate :submit_for_sequencing_column, :sub_pool_column, :coverage_column, to: :well_details_header_row + + FIELDS_FOR_WELL_DETAILS = %w[diluent_volume pcr_cycles submit_for_sequencing sub_pool coverage sample_volume] + .freeze + + # Returns the contents of the header row for the well detail columns + def well_details_header_row + @well_details_header_row ||= PcrCyclesBinnedPlate::CsvFile::DuplexSeq::WellDetailsHeader.new(@data[2]) if @data[ + 2 + ] + end + + private + + def get_config_details_from_purpose(config) + Utility::PcrCyclesForDuplexSeqCsvFileUploadConfig.new(config) + end + + def create_row(index, row_data) + PcrCyclesBinnedPlate::CsvFile::DuplexSeq::Row.new(@config, well_details_header_row, index + 2, row_data) + end + + # Gates looking for wells if the file is invalid + def correctly_formatted? + correctly_parsed? && plate_barcode_header_row.valid? && well_details_header_row.valid? + end + end + end +end diff --git a/app/models/labware_creators/pcr_cycles_binned_plate/csv_file_for_t_nano_seq.rb b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file_for_t_nano_seq.rb new file mode 100644 index 000000000..264cb30a0 --- /dev/null +++ b/app/models/labware_creators/pcr_cycles_binned_plate/csv_file_for_t_nano_seq.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require './lib/nested_validation' +require 'csv' + +# Part of the Labware creator classes +module LabwareCreators + require_dependency 'labware_creators/pcr_cycles_binned_plate_for_t_nano_seq' + + module PcrCyclesBinnedPlate + # + # This version of the csv file is for Targeted NanoSeq. + # + class CsvFileForTNanoSeq < CsvFileBase + delegate :hyb_panel_column, to: :well_details_header_row + + FIELDS_FOR_WELL_DETAILS = %w[ + concentration + input_amount_available + input_amount_desired + sample_volume + diluent_volume + pcr_cycles + hyb_panel + ].freeze + + # Returns the contents of the header row for the well detail columns + def well_details_header_row + @well_details_header_row ||= PcrCyclesBinnedPlate::CsvFile::TNanoSeq::WellDetailsHeader.new(@data[2]) if @data[ + 2 + ] + end + + private + + def get_config_details_from_purpose(config) + Utility::PcrCyclesForTNanoSeqCsvFileUploadConfig.new(config) + end + + def create_row(index, row_data) + PcrCyclesBinnedPlate::CsvFile::TNanoSeq::Row.new(@config, well_details_header_row, index + 2, row_data) + end + end + end +end diff --git a/app/models/labware_creators/pcr_cycles_binned_plate.rb b/app/models/labware_creators/pcr_cycles_binned_plate_base.rb similarity index 76% rename from app/models/labware_creators/pcr_cycles_binned_plate.rb rename to app/models/labware_creators/pcr_cycles_binned_plate_base.rb index d1ef9e1e4..1f471a518 100644 --- a/app/models/labware_creators/pcr_cycles_binned_plate.rb +++ b/app/models/labware_creators/pcr_cycles_binned_plate_base.rb @@ -3,12 +3,11 @@ module LabwareCreators # Handles the generation of a plate with wells binned according to the number of # PCR cycles that has been determined by the customer. - # Uploads a file supplied by the customer that has a row per each well and - # includes Sample Volume, Diluent Volume, PCR Cycles, Sub-Pool and Coverage columns. + # Uploads a file supplied by the customer that has a row per each well. # Uses the PCR Cycles column to determine the binning arrangement of the wells, # and the Sample Volume and Diluent Volume columns in the well transfers. - # Sub-Pool and Coverage need to be stored for a later step downstream in the pipeline, - # at the point where custom pooling is performed. + # Values from some columns need to be stored for a later file export step downstream + # in the pipeline. # Wells in the bins are applied to the destination by column order. # If there is enough space on the destination plate each new bin will start in a new # column. Otherwise bins will run consecutively without gaps. @@ -27,7 +26,7 @@ module LabwareCreators # |E1| pcr_cycles = 12 (bin 2) | | | | # +--+--+--~ +--+--+--~ # |G1| pcr_cycles = 12 (bin 2) | | | | - class PcrCyclesBinnedPlate < StampedPlate + class PcrCyclesBinnedPlateBase < StampedPlate include LabwareCreators::CustomPage MISSING_WELL_DETAIL = 'is missing a row for well %s, all wells with content must have a row in the uploaded file.' @@ -77,26 +76,7 @@ def save end def after_transfer! - # called as part of the 'super' call in the 'save' method - # retrieve child plate through v2 api, using uuid got through v1 api - child_v2 = Sequencescape::Api::V2.plate_with_custom_includes(CHILD_PLATE_INCLUDES, uuid: child.uuid) - - # update fields on each well with various metadata - fields_to_update = %w[diluent_volume pcr_cycles submit_for_sequencing sub_pool coverage] - - child_wells_by_location = child_v2.wells.index_by(&:location) - - well_details.each do |parent_location, details| - child_position = transfer_hash[parent_location]['dest_locn'] - child_well = child_wells_by_location[child_position] - - update_well_with_metadata(child_well, details, fields_to_update) - end - end - - def update_well_with_metadata(well, metadata, fields_to_update) - options = fields_to_update.index_with { |field| metadata[field] } - well.update(options) + raise '#after_transfer! must be implemented on subclasses' end def wells_have_required_information? @@ -123,12 +103,17 @@ def filtered_wells # Upload the csv file onto the plate via api v1 # def upload_file - parent_v1.qc_files.create_from_file!(file, 'duplex_seq_customer_file.csv') + parent_v1.qc_files.create_from_file!(file, customer_filename) + end + + # filename for the customer file upload + def customer_filename + raise '#csv_file must be implemented on subclasses' end # Create class that will parse and validate the uploaded file def csv_file - @csv_file ||= CsvFile.new(file, csv_file_upload_config, parent.human_barcode) + raise '#csv_file must be implemented on subclasses' end # Override this method in sub-class if required. diff --git a/app/models/labware_creators/pcr_cycles_binned_plate_for_duplex_seq.rb b/app/models/labware_creators/pcr_cycles_binned_plate_for_duplex_seq.rb new file mode 100644 index 000000000..382dd20ee --- /dev/null +++ b/app/models/labware_creators/pcr_cycles_binned_plate_for_duplex_seq.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module LabwareCreators + # Handles the generation of a plate with wells binned according to the number of + # PCR cycles that has been determined by the customer. + # Uploads a file supplied by the customer that has a row per each well and + # includes Sample Volume, Diluent Volume, PCR Cycles, Sub-Pool and Coverage columns. + # Uses the PCR Cycles column to determine the binning arrangement of the wells, + # and the Sample Volume and Diluent Volume columns in the well transfers. + # Sub-Pool and Coverage need to be stored for a later step downstream in the pipeline, + # at the point where custom pooling is performed. + # Wells in the bins are applied to the destination by column order. + # If there is enough space on the destination plate each new bin will start in a new + # column. Otherwise bins will run consecutively without gaps. + # + # + # Source Plate Dest Plate + # +--+--+--~ +--+--+--~ + # |A1| pcr_cycles = 12 (bin 2) |B1|A1|C1| + # +--+--+--~ +--+--+--~ + # |B1| pcr_cycles = 15 (bin 1) |D1|E1| | + # +--+--+--~ + +--+--+--~ + # |C1| pcr_cycles = 10 (bin 3) | |G1| | + # +--+--+--~ +--+--+--~ + # |D1| pcr_cycles = 15 (bin 1) | | | | + # +--+--+--~ +--+--+--~ + # |E1| pcr_cycles = 12 (bin 2) | | | | + # +--+--+--~ +--+--+--~ + # |G1| pcr_cycles = 12 (bin 2) | | | | + class PcrCyclesBinnedPlateForDuplexSeq < PcrCyclesBinnedPlateBase + self.page = 'pcr_cycles_binned_plate' + + CUSTOMER_FILENAME = 'duplex_seq_customer_file.csv' + + def after_transfer! + # called as part of the 'super' call in the 'save' method + # retrieve child plate through v2 api, using uuid got through v1 api + child_v2 = Sequencescape::Api::V2.plate_with_custom_includes(CHILD_PLATE_INCLUDES, uuid: child.uuid) + + # update fields on each well with various metadata + fields_to_update = %w[diluent_volume pcr_cycles submit_for_sequencing sub_pool coverage] + + child_wells_by_location = child_v2.wells.index_by(&:location) + + well_details.each do |parent_location, details| + child_position = transfer_hash[parent_location]['dest_locn'] + child_well = child_wells_by_location[child_position] + + update_well_with_metadata(child_well, details, fields_to_update) + end + end + + def update_well_with_metadata(well, metadata, fields_to_update) + options = fields_to_update.index_with { |field| metadata[field] } + well.update(options) + end + + private + + # filename for the customer file upload + def customer_filename + CUSTOMER_FILENAME + end + + # Create class that will parse and validate the uploaded file + def csv_file + @csv_file ||= PcrCyclesBinnedPlate::CsvFileForDuplexSeq.new(file, csv_file_upload_config, parent.human_barcode) + end + end +end diff --git a/app/models/labware_creators/pcr_cycles_binned_plate_for_t_nano_seq.rb b/app/models/labware_creators/pcr_cycles_binned_plate_for_t_nano_seq.rb new file mode 100644 index 000000000..ef8afc8b0 --- /dev/null +++ b/app/models/labware_creators/pcr_cycles_binned_plate_for_t_nano_seq.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module LabwareCreators + # This version of the class is specific to the Targeted NanoSeq pipeline. + class PcrCyclesBinnedPlateForTNanoSeq < PcrCyclesBinnedPlateBase + self.page = 'pcr_cycles_binned_plate_for_t_nano_seq' + + CUSTOMER_FILENAME = 'targeted_nano_seq_customer_file.csv' + + # rubocop:disable Metrics/AbcSize + def after_transfer! + # called as part of the 'super' call in the 'save' method + # retrieve child plate through v2 api, using uuid got through v1 api + child_v2_plate = Sequencescape::Api::V2.plate_with_custom_includes(CHILD_PLATE_INCLUDES, uuid: child.uuid) + + # update fields on each well with various metadata + child_wells_by_location = child_v2_plate.wells.index_by(&:location) + + well_details.each do |parent_location, details| + child_well_location = transfer_hash[parent_location]['dest_locn'] + child_well = child_wells_by_location[child_well_location] + + # NB. this seems to return an array of requests via the api but a single request in tests + request = Array(child_well.aliquots.first.request).first + + if request.blank? + raise StandardError, "Unable to identify request for child plate well at location #{child_well_location}" + end + + # create hash containing the key value pairs we want to store as metadata on the request + request_metadata = { + 'original_plate_barcode' => parent.human_barcode, + 'original_well_id' => parent_location, + 'concentration_nm' => details['concentration'], + 'input_amount_available' => details['input_amount_available'], + 'input_amount_desired' => details['input_amount_desired'], + 'sample_volume' => details['sample_volume'], + 'diluent_volume' => details['diluent_volume'], + 'pcr_cycles' => details['pcr_cycles'], + 'hyb_panel' => details['hyb_panel'] + } + create_request_metadata(request, request_metadata, child_well_location) + end + end + + # rubocop:enable Metrics/AbcSize + + # Cycles through a hash of key value pairs and creates a new metadatum for each on the request object. + # NB. makes assumption that metadata with same name does not already exist i.e. we create not update + def create_request_metadata(request, request_metadata, child_well_location) + request_metadata.each do |metadata_key, metadata_value| + pm_v2 = Sequencescape::Api::V2::PolyMetadatum.new(key: metadata_key, value: metadata_value) + + # NB. this is the only way to set the relationship between the polymetadatum and the request, after + # the polymetadatum object has been created + pm_v2.relationships.metadatable = request + + next if pm_v2.save + + raise StandardError, + "New metadata for request (key: #{metadata_key}, value: #{metadata_value}) " \ + "did not save for request at child well location #{child_well_location}" + end + end + + private + + # filename for the customer file upload + def customer_filename + CUSTOMER_FILENAME + end + + # Create class that will parse and validate the uploaded file + def csv_file + @csv_file ||= PcrCyclesBinnedPlate::CsvFileForTNanoSeq.new(file, csv_file_upload_config, parent.human_barcode) + end + end +end diff --git a/app/models/presenters/pcr_cycles_binned_plate_presenter.rb b/app/models/presenters/pcr_cycles_binned_plate_presenter_base.rb similarity index 56% rename from app/models/presenters/pcr_cycles_binned_plate_presenter.rb rename to app/models/presenters/pcr_cycles_binned_plate_presenter_base.rb index 08dd1bc74..56b581917 100644 --- a/app/models/presenters/pcr_cycles_binned_plate_presenter.rb +++ b/app/models/presenters/pcr_cycles_binned_plate_presenter_base.rb @@ -5,19 +5,22 @@ module Presenters # The PcrCyclesBinnedPlatePresenter is used for plates that have had # pcr cycle binning applied. It shows a view of the plate with colours # and keys indicating the various bins. + # This is the base class for the PcrCyclesBinnedPlatePresenter and should + # not be used directly. + # NB. Once DuplexSeq is converted to use the new request poly_metadata, this + # subclassing can be removed and the PcrCyclesBinnedPlateUsingRequestMetadataPresenter + # version will be the only version needed. # - class PcrCyclesBinnedPlatePresenter < PlatePresenter + class PcrCyclesBinnedPlatePresenterBase < PlatePresenter include Presenters::Statemachine::Standard - CURRENT_PLATE_INCLUDES = 'wells.aliquots,wells.qc_results' - self.summary_partial = 'labware/plates/binned_summary' self.aliquot_partial = 'binned_aliquot' validates_with Validators::ActiveRequestValidator def current_plate - @current_plate ||= Sequencescape::Api::V2.plate_with_custom_includes(CURRENT_PLATE_INCLUDES, uuid: labware.uuid) + @current_plate ||= Sequencescape::Api::V2.plate_with_custom_includes(current_plate_includes, uuid: labware.uuid) end def dilutions_calculator @@ -32,19 +35,14 @@ def bin_details @bin_details ||= dilutions_calculator.compute_presenter_bin_details end + def current_plate_includes + raise 'Method current_plate_includes must be implemented in a subclass of PcrCyclesBinnedPlatePresenterBase' + end + private def well_details - # For each well with aliquots on the plate select the pcr cycles metadata - # { 'A1' => { 'pcr_cycles' => 16 }, 'B1' => etc. } - @well_details ||= - current_plate - .wells - .each_with_object({}) do |well, details| - next if well.aliquots.empty? - - details[well.location] = { 'pcr_cycles' => well.attributes['pcr_cycles'] } - end + raise 'Method well_details must be implemented in a subclass of PcrCyclesBinnedPlatePresenterBase' end end end diff --git a/app/models/presenters/pcr_cycles_binned_plate_using_request_metadata_presenter.rb b/app/models/presenters/pcr_cycles_binned_plate_using_request_metadata_presenter.rb new file mode 100644 index 000000000..bd56352ed --- /dev/null +++ b/app/models/presenters/pcr_cycles_binned_plate_using_request_metadata_presenter.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Presenters + # + # This version of the PcrCyclesBinnedPlatePresenter fetches metadata from + # the Request poly_metadata. + # + class PcrCyclesBinnedPlateUsingRequestMetadataPresenter < PcrCyclesBinnedPlatePresenterBase + # include Presenters::Statemachine::Standard + + CURRENT_PLATE_INCLUDES = 'wells.aliquots,wells.qc_results,wells.aliquots.request.poly_metadata' + + def current_plate_includes + CURRENT_PLATE_INCLUDES + end + + private + + # This version of well details fetches the pcr cycles from the request poly_metadata + # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity + def well_details + # For each well with aliquots on the plate select the pcr cycles metadata + # { 'A1' => { 'pcr_cycles' => 16 }, 'B1' => etc. } + @well_details ||= + current_plate + .wells + .each_with_object({}) do |well, details| + next if well.aliquots.empty? + + # Should be a value by this point in order to have calculated the binning + # NB. poly_metadata are stored as strings so need to convert to integer + pcr_cycles = + well.aliquots.first.request.poly_metadata.find { |md| md.key == 'pcr_cycles' }&.value.to_i || nil + raise "No pcr_cycles metadata found for well #{well.location}" if pcr_cycles.nil? + + details[well.location] = { 'pcr_cycles' => pcr_cycles } + end + end + # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity + end +end diff --git a/app/models/presenters/pcr_cycles_binned_plate_using_well_metadata_presenter.rb b/app/models/presenters/pcr_cycles_binned_plate_using_well_metadata_presenter.rb new file mode 100644 index 000000000..50a702ef0 --- /dev/null +++ b/app/models/presenters/pcr_cycles_binned_plate_using_well_metadata_presenter.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Presenters + # + # This version of the PcrCyclesBinnedPlatePresenter fetches metadata from + # the wells. + # + class PcrCyclesBinnedPlateUsingWellMetadataPresenter < PcrCyclesBinnedPlatePresenterBase + CURRENT_PLATE_INCLUDES = 'wells.aliquots,wells.qc_results' + + def current_plate_includes + CURRENT_PLATE_INCLUDES + end + + private + + # This version of well details fetches the pcr cycles from the well metadata + def well_details + # For each well with aliquots on the plate select the pcr cycles metadata + # { 'A1' => { 'pcr_cycles' => 16 }, 'B1' => etc. } + @well_details ||= + current_plate + .wells + .each_with_object({}) do |well, details| + next if well.aliquots.empty? + + # Should be a value by this point in order to have calculated the binning + pcr_cycles = well.attributes['pcr_cycles'] || nil + raise "No pcr_cycles value found on well #{well.location}" if pcr_cycles.nil? + + details[well.location] = { 'pcr_cycles' => pcr_cycles } + end + end + end +end diff --git a/app/models/utility/pcr_cycles_csv_file_upload_config.rb b/app/models/utility/pcr_cycles_csv_file_upload_config_base.rb similarity index 77% rename from app/models/utility/pcr_cycles_csv_file_upload_config.rb rename to app/models/utility/pcr_cycles_csv_file_upload_config_base.rb index 864d87ce1..cbd55caa5 100644 --- a/app/models/utility/pcr_cycles_csv_file_upload_config.rb +++ b/app/models/utility/pcr_cycles_csv_file_upload_config_base.rb @@ -2,7 +2,7 @@ module Utility # Handles the extraction of dilution configuration functions for pcr cycle binning. - class PcrCyclesCsvFileUploadConfig + class PcrCyclesCsvFileUploadConfigBase include ActiveModel::Model attr_reader :csv_file_config @@ -15,14 +15,18 @@ class PcrCyclesCsvFileUploadConfig diluent_volume_min: 'to_f', diluent_volume_max: 'to_f', pcr_cycles_min: 'to_i', - pcr_cycles_max: 'to_i', - sub_pool_min: 'to_i', - sub_pool_max: 'to_i' + pcr_cycles_max: 'to_i' }.freeze def initialize(csv_file_config) @csv_file_config = csv_file_config CONFIG_VARIABLES.each { |k, v| create_method(k) { @csv_file_config[k].send(v) } } + + initialize_pipeline_specific_methods + end + + def initialize_pipeline_specific_methods + raise '#initialize_pipeline_specific_methods must be implemented on subclasses' end def create_method(name, &block) diff --git a/app/models/utility/pcr_cycles_for_duplex_seq_csv_file_upload_config.rb b/app/models/utility/pcr_cycles_for_duplex_seq_csv_file_upload_config.rb new file mode 100644 index 000000000..8f9241c4c --- /dev/null +++ b/app/models/utility/pcr_cycles_for_duplex_seq_csv_file_upload_config.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Utility + # This version is for the Duplex Seq pipeline. + class PcrCyclesForDuplexSeqCsvFileUploadConfig < PcrCyclesCsvFileUploadConfigBase + PIPELINE_SPECIFIC_CONFIG_VARIABLES = { sub_pool_min: 'to_i', sub_pool_max: 'to_i' }.freeze + + def initialize_pipeline_specific_methods + PIPELINE_SPECIFIC_CONFIG_VARIABLES.each { |k, v| create_method(k) { @csv_file_config[k].send(v) } } + end + + def submit_for_sequencing_valid_values + @csv_file_config.fetch(:submit_for_sequencing_valid_values, []).map + end + end +end diff --git a/app/models/utility/pcr_cycles_for_t_nano_seq_csv_file_upload_config.rb b/app/models/utility/pcr_cycles_for_t_nano_seq_csv_file_upload_config.rb new file mode 100644 index 000000000..e6baafbf4 --- /dev/null +++ b/app/models/utility/pcr_cycles_for_t_nano_seq_csv_file_upload_config.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Utility + # This version is for the Targeted NanoSeq pipeline. + class PcrCyclesForTNanoSeqCsvFileUploadConfig < PcrCyclesCsvFileUploadConfigBase + PIPELINE_SPECIFIC_CONFIG_VARIABLES = {}.freeze + + def initialize_pipeline_specific_methods + PIPELINE_SPECIFIC_CONFIG_VARIABLES.each { |k, v| create_method(k) { @csv_file_config[k].send(v) } } + end + end +end diff --git a/app/views/exports/targeted_nanoseq_al_lib_concentrations_for_customer.csv.erb b/app/views/exports/targeted_nanoseq_al_lib_concentrations_for_customer.csv.erb index 93fb1da25..98339ceb8 100644 --- a/app/views/exports/targeted_nanoseq_al_lib_concentrations_for_customer.csv.erb +++ b/app/views/exports/targeted_nanoseq_al_lib_concentrations_for_customer.csv.erb @@ -1,8 +1,8 @@ <%= CSV.generate_line ['Plate Barcode', @plate.labware_barcode.human], row_sep: "" %> -<%= CSV.generate_line ['Well', 'Concentration (nM)', 'Sanger Sample Id', 'Supplier Sample Name', 'Input amount available (fmol)', 'Input amount desired', 'Sample volume', 'Diluent volume', 'PCR cycles', 'Submit for sequencing (Y/N)?', 'Sub-Pool', 'Coverage'], row_sep: "" %> +<%= CSV.generate_line ['Well', 'Concentration (nM)', 'Sanger Sample Id', 'Supplier Sample Name', 'Input amount available (fmol)', 'Input amount desired', 'Sample volume', 'Diluent volume', 'Hyb Panel'], row_sep: "" %> <% @plate.wells_in_columns.each do |well| %> <% unless well.empty? %> -<%= CSV.generate_line [well.location, well.latest_molarity&.value, well.sanger_sample_id, well.supplier_name, well.input_amount_available, nil, nil, nil, nil, nil, nil, nil], row_sep: "" %> +<%= CSV.generate_line [well.location, well.latest_molarity&.value, well.sanger_sample_id, well.supplier_name, well.input_amount_available, nil, nil, nil, nil], row_sep: "" %> <% end %> <% end %> diff --git a/app/views/exports/targeted_nanoseq_pcr_xp_concentrations_for_custom_pooling.csv.erb b/app/views/exports/targeted_nanoseq_pcr_xp_concentrations_for_custom_pooling.csv.erb deleted file mode 100644 index f320a786a..000000000 --- a/app/views/exports/targeted_nanoseq_pcr_xp_concentrations_for_custom_pooling.csv.erb +++ /dev/null @@ -1,11 +0,0 @@ -<%= CSV.generate_line ['Well', 'Concentration (ng/ul)', 'Submit for sequencing (Y/N)?', 'Sub-Pool', 'Coverage'], row_sep: "" %> -<% @plate.wells_in_columns.each_with_index do |well, well_index| %> - <% unless well.empty? || @ancestor_plate.blank? %> - <% ancestor_well = @ancestor_plate.wells_in_columns[well_index] %> - <% if ancestor_well.attributes['submit_for_sequencing'] %> -<%= CSV.generate_line [well.location, well.latest_concentration&.value, 'Y', ancestor_well.attributes['sub_pool']&.to_i, ancestor_well.attributes['coverage']&.to_i], row_sep: "" %> - <% else %> -<%= CSV.generate_line [well.location, well.latest_concentration&.value, 'N', nil, nil], row_sep: "" %> - <% end %> - <% end %> -<% end %> \ No newline at end of file diff --git a/app/views/exports/targeted_nanoseq_pcr_xp_merged_file.csv.erb b/app/views/exports/targeted_nanoseq_pcr_xp_merged_file.csv.erb new file mode 100644 index 000000000..1cb149295 --- /dev/null +++ b/app/views/exports/targeted_nanoseq_pcr_xp_merged_file.csv.erb @@ -0,0 +1,22 @@ +<%= CSV.generate_line ['Original Plate Barcode', 'Original Well ID', 'Concentration (nM)', 'Sanger Sample ID', 'Supplier Sample Name', 'Input amount available (fmol)', 'Input amount desired (fmol)', 'New Plate Barcode', 'New Well ID', 'Concentration (ng/ul)', 'Hyb Panel'], row_sep: "" %> +<% row_array = [] %> +<% @plate.wells_in_columns.each_with_index do |well, well_index| %> + <% unless well.empty? %> + <% request = Array(well.aliquots.first.request).first %> + <% sample = well.aliquots.first.sample %> + <% sample_id = sample.sanger_sample_id %> + <% supplier_sample_name = sample.sample_metadata.supplier_name %> + <% md_original_plate_barcode = request.poly_metadata.select { |md| md.key == 'original_plate_barcode' }.first&.value || nil %> + <% md_original_well_id = request.poly_metadata.select { |md| md.key == 'original_well_id' }.first&.value || nil %> + <% md_concentration_nm = request.poly_metadata.select { |md| md.key == 'concentration_nm' }.first&.value || nil %> + <% md_input_amount_available = request.poly_metadata.select { |md| md.key == 'input_amount_available' }.first&.value || nil %> + <% md_input_amount_desired = request.poly_metadata.select { |md| md.key == 'input_amount_desired' }.first&.value || nil %> + <% md_hyb_panel = request.poly_metadata.select { |md| md.key == 'hyb_panel' }.first&.value || nil %> + <% row_array.push([md_original_plate_barcode, md_original_well_id, md_concentration_nm, sample_id, supplier_sample_name, md_input_amount_available, md_input_amount_desired, @plate.human_barcode, well.location, well.latest_concentration&.value, md_hyb_panel]) %> + <% end %> +<% end %> +<% column_order = (1..12).to_a.product(('A'..'H').to_a).map(&:reverse).map(&:join) %> +<% sorted_row_array = row_array.sort_by! { |row| [row[0], column_order.index(row[1])] } %> +<% sorted_row_array.each do |row| %> +<%= CSV.generate_line row, row_sep: "" %> +<% end %> diff --git a/app/views/plate_creation/pcr_cycles_binned_plate_for_t_nano_seq.html.erb b/app/views/plate_creation/pcr_cycles_binned_plate_for_t_nano_seq.html.erb new file mode 100644 index 000000000..02d5bff33 --- /dev/null +++ b/app/views/plate_creation/pcr_cycles_binned_plate_for_t_nano_seq.html.erb @@ -0,0 +1,62 @@ +<%= page(:'pcr-cycles-binned-plate-for-t-nano-seq') do -%> + <%= content do %> + <%= card title: 'Help' do %> +

Upload the customer completed csv file describing your desired pcr cycles binning strategy. An example is shown below:

+

Please make sure there is a header row for the parent plate barcode, this barcode must match the plate from which the dilution plate is being created. Then leave a spacer row before the row column headings. +

Please also make sure you specify source and diluent volumes, number of pcr cycles, and the hyb panel to use for each well that has a sample.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Plate barcodeDN12345678A
WellConcentration (nM)Sanger Sample IdSupplier Sample NameInput amount available (fmol)Input amount desiredSample volumeDiluent volumePCR cyclesHyb Panel
A115.2101Smp 145.350.05.020.012My Hyb Panel Name
B10.12102Smp 27.150.025.00.016My Hyb Panel Name
C18.7103Smp 330.450.010.015.012My Hyb Panel Name
D121.3104Smp 425.050.04.218.812My Hyb Panel Name
E111.8105Smp 516.950.08.716.314My Hyb Panel Name
F19.1106Smp 636.250.09.617.114My Hyb Panel Name
G10.03107Smp 719.750.025.00.012My Hyb Panel Name
H11.8108Smp 816.650.023.51.514My Hyb Panel Name
A27.6109Smp 942.250.012.612.416My Hyb Panel Name
B214.2110Smp 1029.550.09.515.412My Hyb Panel Name
+

In this example we will generate 3 bins:

+
    +
  • 16 cycles: Source wells B1, A2 into A1 and B1
  • +
  • 14 cycles: Source wells E1, F1, H1 into A2 to C2
  • +
  • 12 cycles: Source wells A1, C1, D1, G1, B2 into A3 to E3
  • +
+

NB. All wells with aliquots in the parent must have values.

+ <% end %> + <% end %> + <%= sidebar do %> + <%= card title: 'File upload' do %> + <%= form_for(@labware_creator, as: :plate, url: limber_plate_children_path(@labware_creator.parent)) do |f| %> + <%= f.hidden_field :purpose_uuid %> +
+ <%= f.file_field :file, accept: '.csv', required: true %> +
+ <%= f.submit class: 'btn btn-success' %> + <% end %> + <% end %> + <% end %> +<%- end -%> diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..059588a88 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,2 @@ +ignore: + - '**/*.vue' # ignore all .vue files - TODO: remove this after Vue SFC is supported (see https://github.com/sanger/limber/pull/1585) diff --git a/config/exports/exports.yml b/config/exports/exports.yml index 6c41c6c9c..0859f7938 100644 --- a/config/exports/exports.yml +++ b/config/exports/exports.yml @@ -15,9 +15,9 @@ duplex_seq_pcr_xp_concentrations_for_custom_pooling: targeted_nanoseq_al_lib_concentrations_for_customer: csv: targeted_nanoseq_al_lib_concentrations_for_customer plate_includes: wells.qc_results,wells.aliquots.sample.sample_metadata -targeted_nanoseq_pcr_xp_concentrations_for_custom_pooling: - csv: targeted_nanoseq_pcr_xp_concentrations_for_custom_pooling - plate_includes: wells.qc_results +targeted_nanoseq_pcr_xp_merged_file: + csv: targeted_nanoseq_pcr_xp_merged_file + plate_includes: wells.qc_results,wells.aliquots.sample.sample_metadata,wells.aliquots.request.poly_metadata ancestor_purpose: LTN AL Lib Dil hamilton_aggregate_cherrypick: csv: hamilton_aggregate_cherrypick diff --git a/config/label_templates.yml b/config/label_templates.yml index 821a24f6e..4da208462 100644 --- a/config/label_templates.yml +++ b/config/label_templates.yml @@ -1,81 +1,89 @@ --- -default_pmb_templates: - :plate_double: plate_6mm_double_code39 - :plate_a: sqsc_96plate_label_template_code39 - :tube_rack: sqsc_96plate_label_template_code39 - :tube: tube_label_template_1d +defaults_by_printer_type: + pmb_templates: + :plate_double: plate_6mm_double_code39 + :plate_a: sqsc_96plate_label_template_code39 + :tube_rack: sqsc_96plate_label_template_code39 + :tube: tube_label_template_1d -default_sprint_templates: - :plate_double: plate_384.yml.erb - :plate_a: plate_96.yml.erb - :tube_rack: plate_96.yml.erb - :tube: tube_label_template_1d.yml.erb + sprint_templates: + :plate_double: plate_384.yml.erb + :plate_a: plate_96.yml.erb + :tube_rack: plate_96.yml.erb + :tube: tube_label_template_1d.yml.erb -default_printer_type_names: - :plate_double: 384 Well Plate Double - :plate_a: 96 Well Plate - :tube_rack: 96 Well Plate - :tube: 1D Tube + printer_type_names: + :plate_double: 384 Well Plate Double + :plate_a: 96 Well Plate + :tube_rack: 96 Well Plate + :tube: 1D Tube -# NB. plate_384.yml.erb is for printing double-sticker 384-well plate labels. -plate_6mm_double: - :label_class: Labels::PlateDoubleLabel - :printer_type: 384 Well Plate Double - :pmb_template: plate_6mm_double_code39 - :sprint_template: plate_384.yml.erb +templates: + plate_a: + :label_class: Labels::PlateLabel + :printer_type: 96 Well Plate + :pmb_template: sqsc_96plate_label_template_code39 + :sprint_template: plate_96.yml.erb -# The following label definition is not used by the current Heron (LTHR-384) pipeline. -plate_6mm_double_qc: - :label_class: Labels::PlateDoubleLabelQc - :printer_type: 384 Well Plate Double - :pmb_template: plate_6mm_double_code39 - :sprint_template: plate_384.yml.erb + # NB. plate_384.yml.erb is for printing double-sticker 384-well plate labels. + plate_6mm_double: + :label_class: Labels::PlateDoubleLabel + :printer_type: 384 Well Plate Double + :pmb_template: plate_6mm_double_code39 + :sprint_template: plate_384.yml.erb -plate_xp: - :label_class: Labels::PlateLabelXp - :printer_type: 96 Well Plate - :pmb_template: sqsc_96plate_label_template_code39 - :sprint_template: plate_96.yml.erb + # The following label definition is not used by the current Heron (LTHR-384) pipeline. + plate_6mm_double_qc: + :label_class: Labels::PlateDoubleLabelQc + :printer_type: 384 Well Plate Double + :pmb_template: plate_6mm_double_code39 + :sprint_template: plate_384.yml.erb -plate_split: - :label_class: Labels::PlateSplit - :printer_type: 96 Well Plate - :pmb_template: sqsc_96plate_label_template_code39 - :sprint_template: plate_96.yml.erb + plate_xp: + :label_class: Labels::PlateLabelXp + :printer_type: 96 Well Plate + :pmb_template: sqsc_96plate_label_template_code39 + :sprint_template: plate_96.yml.erb -plate_lds_al_lib: - :label_class: Labels::PlateLabelLdsAlLib - :printer_type: 96 Well Plate - :pmb_template: sqsc_96plate_label_template_code39 - :sprint_template: plate_96.yml.erb + plate_split: + :label_class: Labels::PlateSplit + :printer_type: 96 Well Plate + :pmb_template: sqsc_96plate_label_template_code39 + :sprint_template: plate_96.yml.erb -plate_ltn_al_lib: - :label_class: Labels::PlateLabelLtnAlLib - :printer_type: 96 Well Plate - :pmb_template: sqsc_96plate_label_template_code39 - :sprint_template: plate_96.yml.erb + plate_lds_al_lib: + :label_class: Labels::PlateLabelLdsAlLib + :printer_type: 96 Well Plate + :pmb_template: sqsc_96plate_label_template_code39 + :sprint_template: plate_96.yml.erb -plate_cellaca_qc: - :label_class: Labels::PlateLabelCellacaQc - :printer_type: 96 Well Plate - :pmb_template: sqsc_96plate_label_template_code39 - :sprint_template: plate_96.yml.erb + plate_ltn_al_lib: + :label_class: Labels::PlateLabelLtnAlLib + :printer_type: 96 Well Plate + :pmb_template: sqsc_96plate_label_template_code39 + :sprint_template: plate_96.yml.erb -# Only Squix printers are used through SPrint for tube_traction_compatible. -# pmb_template setting is for completeness; it is not used. -tube_traction_compatible: - :label_class: Labels::TubeLabelTractionCompatible - :printer_type: 1D Tube - :pmb_template: tube_label_template_1d - :sprint_template: tube_label_traction_compatible.yml.erb + plate_cellaca_qc: + :label_class: Labels::PlateLabelCellacaQc + :printer_type: 96 Well Plate + :pmb_template: sqsc_96plate_label_template_code39 + :sprint_template: plate_96.yml.erb -# Only Squix printers are used through SPrint for plate_384_single. -# pmb_template setting is for completeness; it is not used. Although -# it is for printing single-sticker labels, printer_type is set to -# "384 Well Plate Double" because barcode_printers table of sequencescape -# database use "384 Well Plate Double" for all 384-well plate printers. -plate_384_single: - :label_class: Labels::Plate384SingleLabel - :printer_type: 384 Well Plate Double - :pmb_template: sqsc_384plate_label_template_code39 - :sprint_template: plate_384_single.yml.erb + # Only Squix printers are used through SPrint for tube_traction_compatible. + # pmb_template setting is for completeness; it is not used. + tube_traction_compatible: + :label_class: Labels::TubeLabelTractionCompatible + :printer_type: 1D Tube + :pmb_template: tube_label_template_1d + :sprint_template: tube_label_traction_compatible.yml.erb + + # Only Squix printers are used through SPrint for plate_384_single. + # pmb_template setting is for completeness; it is not used. Although + # it is for printing single-sticker labels, printer_type is set to + # "384 Well Plate Double" because barcode_printers table of sequencescape + # database use "384 Well Plate Double" for all 384-well plate printers. + plate_384_single: + :label_class: Labels::Plate384SingleLabel + :printer_type: 384 Well Plate Double + :pmb_template: sqsc_384plate_label_template_code39 + :sprint_template: plate_384_single.yml.erb diff --git a/config/pipelines/high_throughput_scrna_core_cdna_prep.wip.yml b/config/pipelines/high_throughput_scrna_core_cdna_prep.wip.yml index 51625f050..92eed75e1 100644 --- a/config/pipelines/high_throughput_scrna_core_cdna_prep.wip.yml +++ b/config/pipelines/high_throughput_scrna_core_cdna_prep.wip.yml @@ -1,9 +1,8 @@ # Thawing PBMCs and pooling samples from different donors together scRNA Core Donor Pooling: pipeline_group: scRNA Core cDNA Prep - # TODO: Uncomment when submission template and request types are set up - # filters: - # request_type_key: limber_scrna_core_donor_pooling + filters: + request_type_key: limber_scrna_core_donor_pooling relationships: LRC Bank Seq: LRC PBMC Cryostor LRC Bank Spare: LRC PBMC Cryostor @@ -13,10 +12,9 @@ scRNA Core Donor Pooling: # GEM generation and cDNA Prep scRNA Core cDNA Prep: pipeline_group: scRNA Core cDNA Prep - # TODO: Uncomment when submission template and request types are set up - # filters: - # request_type_key: limber_scrna_core_cDNA_prep - # library_type: Chromium single cell 5 prime HT v2 + filters: + request_type_key: limber_scrna_core_cDNA_prep + library_type: Chromium single cell 5 prime HT v2 relationships: LRC PBMC Pools: LRC HT 5p Chip LRC PBMC Pools Input: LRC HT 5p Chip diff --git a/config/purposes/duplex_seq.yml b/config/purposes/duplex_seq.yml index e5e88ed87..c6e0013a3 100644 --- a/config/purposes/duplex_seq.yml +++ b/config/purposes/duplex_seq.yml @@ -25,8 +25,8 @@ LDS AL Lib: id: 'duplex_seq_al_lib_concentrations_for_customer' LDS AL Lib Dil: :asset_type: plate - :presenter_class: Presenters::PcrCyclesBinnedPlatePresenter - :creator_class: LabwareCreators::PcrCyclesBinnedPlate + :presenter_class: Presenters::PcrCyclesBinnedPlateUsingWellMetadataPresenter + :creator_class: LabwareCreators::PcrCyclesBinnedPlateForDuplexSeq :csv_file_upload: :input_amount_desired_min: 0.0 :input_amount_desired_max: 10000.0 diff --git a/config/purposes/scrna_core_cdna_prep.wip.yml b/config/purposes/scrna_core_cdna_prep.wip.yml index 81865ad94..a5ec716c4 100644 --- a/config/purposes/scrna_core_cdna_prep.wip.yml +++ b/config/purposes/scrna_core_cdna_prep.wip.yml @@ -14,6 +14,33 @@ LRC PBMC Cryostor: # Plate containing defrosted PBMCs in PBS buffer. LRC PBMC Defrost PBS: :asset_type: plate + :label_template: plate_cellaca_qc # creates QC1 up to QC4 barcodes + :file_links: + - name: 'Download Cellaca Input QC1' + id: cellaca_input_file + params: + page: 0 + - name: 'Download Cellaca Input QC2' + id: cellaca_input_file + params: + page: 1 + - name: 'Download Cellaca Input QC3' + id: cellaca_input_file + params: + page: 2 + - name: 'Download Cellaca Input QC4' + id: cellaca_input_file + params: + page: 3 + :qc_thresholds: + viability: + units: '%' + default_threshold: 50 + live_cell_count: + name: Cell count + units: 'cells/ml' + default_threshold: 400000 + decimal_places: 0 :stock_plate: false :input_plate: false # Plate containing pooled PBMCs from different donors. diff --git a/config/purposes/scrna_core_cell_extraction.yml b/config/purposes/scrna_core_cell_extraction.yml index 297089e71..ca9a5d4be 100644 --- a/config/purposes/scrna_core_cell_extraction.yml +++ b/config/purposes/scrna_core_cell_extraction.yml @@ -23,7 +23,7 @@ LRC Blood Aliquot: :type: Tube::Purpose :presenter_class: Presenters::SimpleTubePresenter :creator_class: LabwareCreators::TubeFromTube - :default_printer_type: :tube + :label_template: plate_a # uses a plate printer because it's a 'falcon tube' ### # scRNA Core Cell Extraction Blood Pipeline ### diff --git a/config/purposes/targeted_nanoseq.yml b/config/purposes/targeted_nanoseq.yml index 6a6d66464..c34edb026 100644 --- a/config/purposes/targeted_nanoseq.yml +++ b/config/purposes/targeted_nanoseq.yml @@ -29,8 +29,8 @@ LTN AL Lib: id: 'targeted_nanoseq_al_lib_concentrations_for_customer' LTN AL Lib Dil: :asset_type: plate - :presenter_class: Presenters::PcrCyclesBinnedPlatePresenter - :creator_class: LabwareCreators::PcrCyclesBinnedPlate + :presenter_class: Presenters::PcrCyclesBinnedPlateUsingRequestMetadataPresenter + :creator_class: LabwareCreators::PcrCyclesBinnedPlateForTNanoSeq :csv_file_upload: :input_amount_desired_min: 0.0 :input_amount_desired_max: 10000.0 @@ -40,11 +40,6 @@ LTN AL Lib Dil: :diluent_volume_max: 50.0 :pcr_cycles_min: 1 :pcr_cycles_max: 20 - :submit_for_sequencing_valid_values: - - 'Y' - - 'N' - :sub_pool_min: 1 - :sub_pool_max: 96 :file_links: - name: 'Download Hamilton AL Lib to Dilution CSV' id: 'hamilton_ltn_al_lib_to_ltn_al_lib_dil' @@ -65,8 +60,8 @@ LTN Lib PCR XP: :default_printer_type: :plate_b :label_template: plate_xp :file_links: - - name: 'Download Concentration (ng/ul) CSV for Custom Pooling' - id: 'targeted_nanoseq_pcr_xp_concentrations_for_custom_pooling' + - name: 'Download Merged File CSV' + id: 'targeted_nanoseq_pcr_xp_merged_file' LTN Custom Pool: :asset_type: tube :target: StockMultiplexedLibraryTube diff --git a/docs/creators.md b/docs/creators.md index 6bb89faef..aa5af408d 100644 --- a/docs/creators.md +++ b/docs/creators.md @@ -26,373 +26,372 @@ Labware creators are responsible for creating new labware from a parent labware. {include:LabwareCreators::StampedPlate} - Used directly in 108 purposes: - CLCM DNA End Prep, CLCM DNA Lib PCR XP, CLCM RNA End Prep, CLCM RNA Lib PCR XP, CLCM RT PreAmp, GBS Stock, GBS-96 Stock, GnT MDA Norm, GnT Pico End Prep, GnT Pico-XP, GnT scDNA, GnT Stock, LB Cap Lib, LB Cap Lib PCR, LB Cap Lib PCR-XP, LB cDNA, LB cDNA XP, LB Cherrypick, LB End Prep, LB Lib PCR-XP, LB Post Shear, LB Shear, LBB Cherrypick, LBB Enriched BCR, LBB Enriched BCR HT, LBB Enriched TCR, LBB Enriched TCR HT, LBB Lib-XP, LBB Ligation, LBC 3pV3 GEX Frag 2XP, LBC 3pV3 GEX PCR 2XP, LBC 5p GEX Frag 2XP, LBC 5p GEX PCR 2XP, LBC BCR Enrich1 2XSPRI, LBC BCR Enrich2 2XSPRI, LBC BCR Post PCR, LBC Stock, LBC TCR Enrich1 2XSPRI, LBC TCR Enrich2 2XSPRI, LBC TCR Post PCR, LBR Cherrypick, LBR Frag, LBR Frag cDNA, LBR Globin, LBR Globin DNase, LBR mRNA Cap, LBR Ribo DNase, LBR RiboGlobin DNase, LCA 10X cDNA, LCA PBMC, LCA PBMC Bank, LCMB Cherrypick, LCMB End Prep, LCMB Lib PCR-XP, LDS AL Lib, LDS Cherrypick, LDS Lib PCR XP, LDS Stock, LDS Stock XP, LHR End Prep, LHR PCR 1, LHR PCR 2, LHR RT, LHR-384 AL Lib, LHR-384 End Prep, LHR-384 PCR 1, LHR-384 PCR 2, LHR-384 RT, LHR-384 XP, LSW-96 Stock, LTHR PCR 1, LTHR PCR 2, LTHR RT-S, LTHR-384 PCR 1, LTHR-384 PCR 2, LTN AL Lib, LTN Cherrypick, LTN Lib PCR XP, LTN Post Shear, LTN Shear, LTN Stock, LTN Stock XP, PF Cherrypicked, PF End Prep, PF Lib XP, PF Lib XP2, PF Post Shear, PF Post Shear XP, PF Shear, PF-384 End Prep, PF-384 Lib XP2, pWGS-384 AL Lib, pWGS-384 End Prep, RVI Cap Lib, RVI Cap Lib PCR, RVI Cap Lib PCR XP, RVI cDNA XP, RVI Cherrypick, RVI Lib PCR XP, RVI Lig Bind, RVI RT, scRNA cDNA-XP, scRNA End Prep, scRNA Stock, scRNA-384 cDNA-XP, scRNA-384 End Prep, scRNA-384 Stock, and Tag Plate - 384 + Used directly in 116 purposes: + CLCM DNA End Prep, CLCM DNA Lib PCR XP, CLCM RNA End Prep, CLCM RNA Lib PCR XP, CLCM RT PreAmp, GBS Stock, GBS-96 Stock, GnT MDA Norm, GnT Pico End Prep, GnT Pico-XP, GnT scDNA, GnT Stock, LB Cap Lib, LB Cap Lib PCR, LB Cap Lib PCR-XP, LB cDNA, LB cDNA XP, LB Cherrypick, LB End Prep, LB Lib PCR-XP, LB Post Shear, LB Shear, LBB Cherrypick, LBB Enriched BCR, LBB Enriched BCR HT, LBB Enriched TCR, LBB Enriched TCR HT, LBB Lib-XP, LBB Ligation, LBC 3pV3 GEX Frag 2XP, LBC 3pV3 GEX PCR 2XP, LBC 5p GEX Frag 2XP, LBC 5p GEX PCR 2XP, LBC BCR Enrich1 2XSPRI, LBC BCR Enrich2 2XSPRI, LBC BCR Post PCR, LBC Stock, LBC TCR Enrich1 2XSPRI, LBC TCR Enrich2 2XSPRI, LBC TCR Post PCR, LBR Cherrypick, LBR Frag, LBR Frag cDNA, LBR Globin, LBR Globin DNase, LBR mRNA Cap, LBR Ribo DNase, LBR RiboGlobin DNase, LCA 10X cDNA, LCA PBMC, LCA PBMC Bank, LCMB Cherrypick, LCMB End Prep, LCMB Lib PCR-XP, LDS AL Lib, LDS Cherrypick, LDS Lib PCR XP, LDS Stock, LDS Stock XP, LHR End Prep, LHR PCR 1, LHR PCR 2, LHR RT, LHR-384 AL Lib, LHR-384 End Prep, LHR-384 PCR 1, LHR-384 PCR 2, LHR-384 RT, LHR-384 XP, LRC HT 5p cDNA PCR, LRC HT 5p cDNA PCR XP, LRC HT 5p Chip, LRC HT 5p GEMs, LRC PBMC Cryostor, LRC PBMC Defrost PBS, LRC PBMC Pools, LRC PBMC Pools Input, LSW-96 Stock, LTHR PCR 1, LTHR PCR 2, LTHR RT-S, LTHR-384 PCR 1, LTHR-384 PCR 2, LTN AL Lib, LTN Cherrypick, LTN Lib PCR XP, LTN Post Shear, LTN Shear, LTN Stock, LTN Stock XP, PF Cherrypicked, PF End Prep, PF Lib XP, PF Lib XP2, PF Post Shear, PF Post Shear XP, PF Shear, PF-384 End Prep, PF-384 Lib XP2, pWGS-384 AL Lib, pWGS-384 End Prep, RVI Cap Lib, RVI Cap Lib PCR, RVI Cap Lib PCR XP, RVI cDNA XP, RVI Cherrypick, RVI Lib PCR XP, RVI Lig Bind, RVI RT, scRNA cDNA-XP, scRNA End Prep, scRNA Stock, scRNA-384 cDNA-XP, scRNA-384 End Prep, scRNA-384 Stock, and Tag Plate - 384 {LabwareCreators::StampedPlate View class documentation} -## LabwareCreators::BaitedPlate +## LabwareCreators::CardinalPoolsPlate -{include:LabwareCreators::BaitedPlate} +{include:LabwareCreators::CardinalPoolsPlate} - Used directly in 2 purposes: - LB Hyb and RVI Hyb + Used directly in 1 purposes: + LCA PBMC Pools -{LabwareCreators::BaitedPlate View class documentation} +{LabwareCreators::CardinalPoolsPlate View class documentation} -## LabwareCreators::PartialStampedPlate +## LabwareCreators::PooledTubesBase -{include:LabwareCreators::PartialStampedPlate} +{include:LabwareCreators::PooledTubesBase} **This labware creator is unused** -{LabwareCreators::PartialStampedPlate View class documentation} +{LabwareCreators::PooledTubesBase View class documentation} -## LabwareCreators::ConcentrationBinnedPlate +## LabwareCreators::CustomTaggedPlate -{include:LabwareCreators::ConcentrationBinnedPlate} +{include:LabwareCreators::CustomTaggedPlate} - Used directly in 1 purposes: - LBC 3pV3 GEX Dil + Used directly in 8 purposes: + LBB Chromium Tagged, LBB Lib PCR-XP, LBB Ligation Tagged, LBC 3pV3 GEX LigXP, LBC 5p GEX LigXP, LBC BCR Post Lig 1XSPRI, LBC TCR Post Lig 1XSPRI, and LCA Connect PCRXP -{LabwareCreators::ConcentrationBinnedPlate View class documentation} +{LabwareCreators::CustomTaggedPlate View class documentation} -## LabwareCreators::FixedNormalisedPlate +## LabwareCreators::FinalTube -{include:LabwareCreators::FixedNormalisedPlate} +{include:LabwareCreators::FinalTube} - Used directly in 2 purposes: - LBC BCR Dil 1 and LBC TCR Dil 1 + Used directly in 1 purposes: + LB Lib Pool Norm -{LabwareCreators::FixedNormalisedPlate View class documentation} +{LabwareCreators::FinalTube View class documentation} -## LabwareCreators::NormalisedBinnedPlate +## LabwareCreators::FinalTubeFromPlate -{include:LabwareCreators::NormalisedBinnedPlate} +{include:LabwareCreators::FinalTubeFromPlate} Used directly in 1 purposes: - LBC 5p GEX Dil + Cap Lib Pool Norm -{LabwareCreators::NormalisedBinnedPlate View class documentation} +{LabwareCreators::FinalTubeFromPlate View class documentation} -## LabwareCreators::ConcentrationNormalisedPlate +## LabwareCreators::MultiPlatePool -{include:LabwareCreators::ConcentrationNormalisedPlate} +{include:LabwareCreators::MultiPlatePool} Used directly in 2 purposes: - LBC BCR Dil 2 and LBC TCR Dil 2 + LB Lib PrePool and RVI Lib PrePool -{LabwareCreators::ConcentrationNormalisedPlate View class documentation} +{LabwareCreators::MultiPlatePool View class documentation} -## LabwareCreators::MergedPlate +## LabwareCreators::MultiStamp -{include:LabwareCreators::MergedPlate} +{include:LabwareCreators::MultiStamp} - Used directly in 4 purposes: - LHR XP, LHR-384 cDNA, LTHR Lib PCR pool, and LTHR-384 Lib PCR pool + **This labware creator is unused** -{LabwareCreators::MergedPlate View class documentation} +{LabwareCreators::MultiStamp View class documentation} -## LabwareCreators::PartialStampedPlateWithoutDilution +## LabwareCreators::MultiStampTubes -{include:LabwareCreators::PartialStampedPlateWithoutDilution} +{include:LabwareCreators::MultiStampTubes} - Used directly in 4 purposes: - LBB Enriched BCR, LBB Enriched BCR HT, LBB Enriched TCR, and LBB Enriched TCR HT + Used directly in 3 purposes: + LCA Blood Array, LCA Blood Bank, and LRC Blood Bank -{LabwareCreators::PartialStampedPlateWithoutDilution View class documentation} +{LabwareCreators::MultiStampTubes View class documentation} -## LabwareCreators::PcrCyclesBinnedPlate +## LabwareCreators::PlateSplitToTubeRacks -{include:LabwareCreators::PcrCyclesBinnedPlate} +{include:LabwareCreators::PlateSplitToTubeRacks} Used directly in 2 purposes: - LDS AL Lib Dil and LTN AL Lib Dil + LRC Bank Seq and LRC Bank Spare -{LabwareCreators::PcrCyclesBinnedPlate View class documentation} +{LabwareCreators::PlateSplitToTubeRacks View class documentation} -## LabwareCreators::PlateWithPrimerPanel +## LabwareCreators::PlateWithTemplate -{include:LabwareCreators::PlateWithPrimerPanel} +{include:LabwareCreators::PlateWithTemplate} - **This labware creator is unused** + Used directly in 2 purposes: + LB Cap Lib Pool and RVI Cap Lib Pool -{LabwareCreators::PlateWithPrimerPanel View class documentation} +{LabwareCreators::PlateWithTemplate View class documentation} -## LabwareCreators::QuadrantSplitPlate +## LabwareCreators::PooledTubesFromWholePlates -{include:LabwareCreators::QuadrantSplitPlate} +{include:LabwareCreators::PooledTubesFromWholePlates} - Used directly in 1 purposes: - PF Lib Q-XP2 + Used directly in 2 purposes: + GBS PCR2 Pool Stock and LBSN-384 PCR 2 Pool -{LabwareCreators::QuadrantSplitPlate View class documentation} +{LabwareCreators::PooledTubesFromWholePlates View class documentation} -## LabwareCreators::StampedPlateAddingRandomisedControls +## LabwareCreators::PooledTubesFromWholeTubes -{include:LabwareCreators::StampedPlateAddingRandomisedControls} +{include:LabwareCreators::PooledTubesFromWholeTubes} - Used directly in 1 purposes: - LBSN-96 Lysate + Used directly in 2 purposes: + GBS MiSeq Pool and LBSN-9216 Lib PCR Pool -{LabwareCreators::StampedPlateAddingRandomisedControls View class documentation} +{LabwareCreators::PooledTubesFromWholeTubes View class documentation} -## LabwareCreators::CardinalPoolsPlate +## LabwareCreators::PooledWellsBySampleInGroups -{include:LabwareCreators::CardinalPoolsPlate} +{include:LabwareCreators::PooledWellsBySampleInGroups} Used directly in 1 purposes: - LCA PBMC Pools + LRC PBMC Bank -{LabwareCreators::CardinalPoolsPlate View class documentation} +{LabwareCreators::PooledWellsBySampleInGroups View class documentation} -## LabwareCreators::PooledTubesBase +## LabwareCreators::TaggedPlate -{include:LabwareCreators::PooledTubesBase} +{include:LabwareCreators::TaggedPlate} - **This labware creator is unused** + Used directly in 21 purposes: + CLCM DNA Lib PCR, CLCM RNA Lib PCR, GBS PCR2, GnT Pico Lib PCR, LB Lib PCR, LBSN-384 PCR 2, LCMB Lib PCR, LDS Lib PCR, LHR Lib PCR, LHR-384 Lib PCR, LTHR Lib PCR 1, LTHR Lib PCR 2, LTHR-384 Lib PCR 1, LTHR-384 Lib PCR 2, LTN Lib PCR, PF Lib, PF-384 Lib, pWGS-384 Lib PCR, RVI Lib PCR, scRNA Lib PCR, and scRNA-384 Lib PCR -{LabwareCreators::PooledTubesBase View class documentation} +{LabwareCreators::TaggedPlate View class documentation} -## LabwareCreators::CustomPooledTubes +## LabwareCreators::TubeFromTube -{include:LabwareCreators::CustomPooledTubes} +{include:LabwareCreators::TubeFromTube} - Used directly in 7 purposes: - CLCM DNA Pool, CLCM RNA Pool, LB Custom Pool, LCA Custom Pool, LCMB Custom Pool, LDS Custom Pool, and LTN Custom Pool + Used directly in 17 purposes: + CLCM DNA Pool Norm, CLCM RNA Pool Norm, GBS PCR Pool, GBS PCR Pool Selected, GnT Pico Lib Pool XP, LB Custom Pool Norm, LBC 5p Pool Norm, LBC BCR Pool Norm, LBC TCR Pool Norm, LBSN-9216 Lib PCR Pool XP, LCA Custom Pool Norm, LCMB Custom Pool Norm, LDS Custom Pool Norm, LHR Lib Pool XP, LRC Blood Aliquot, LTN Custom Pool Norm, and scRNA Lib Pool XP -{LabwareCreators::CustomPooledTubes View class documentation} +{LabwareCreators::TubeFromTube View class documentation} -## LabwareCreators::PooledTubesBySample +## LabwareCreators::Uncreatable -{include:LabwareCreators::PooledTubesBySample} +{include:LabwareCreators::Uncreatable} - Used directly in 1 purposes: - LCA Bank Stock + Used directly in 8 purposes: + CLCM Stock, LCA Blood Vac, LDW-96 Stock, LILYS-96 Stock, LRC Blood Vac, LTHR Cherrypick, LTHR RT, and LTHR-384 RT -{LabwareCreators::PooledTubesBySample View class documentation} +{LabwareCreators::Uncreatable View class documentation} -## LabwareCreators::PooledTubesBySubmission +## LabwareCreators::BaitedPlate -{include:LabwareCreators::PooledTubesBySubmission} +{include:LabwareCreators::BaitedPlate} - Used directly in 12 purposes: - GnT Pico Lib Pool, LB Lib Pool, LBB Lib Pool Stock, LBC 3pV3 GLibPS, LBC 5p GLibPS, LBC BCR LibPS, LBC TCR LibPS, LHR Lib Pool, LHR-384 Pool XP, pWGS-384 Lib Pool XP, scRNA Lib Pool, and scRNA-384 Lib Pool XP + Used directly in 2 purposes: + LB Hyb and RVI Hyb -{LabwareCreators::PooledTubesBySubmission View class documentation} +{LabwareCreators::BaitedPlate View class documentation} -## LabwareCreators::PooledTubesBySubmissionWithPhiX +## LabwareCreators::PartialStampedPlate -{include:LabwareCreators::PooledTubesBySubmissionWithPhiX} +{include:LabwareCreators::PartialStampedPlate} - Used directly in 2 purposes: - LTHR Pool XP and LTHR-384 Pool XP + **This labware creator is unused** -{LabwareCreators::PooledTubesBySubmissionWithPhiX View class documentation} +{LabwareCreators::PartialStampedPlate View class documentation} -## LabwareCreators::CustomTaggedPlate +## LabwareCreators::ConcentrationNormalisedPlate -{include:LabwareCreators::CustomTaggedPlate} +{include:LabwareCreators::ConcentrationNormalisedPlate} - Used directly in 8 purposes: - LBB Chromium Tagged, LBB Lib PCR-XP, LBB Ligation Tagged, LBC 3pV3 GEX LigXP, LBC 5p GEX LigXP, LBC BCR Post Lig 1XSPRI, LBC TCR Post Lig 1XSPRI, and LCA Connect PCRXP + Used directly in 2 purposes: + LBC BCR Dil 2 and LBC TCR Dil 2 -{LabwareCreators::CustomTaggedPlate View class documentation} +{LabwareCreators::ConcentrationNormalisedPlate View class documentation} -## LabwareCreators::FinalTube +## LabwareCreators::MergedPlate -{include:LabwareCreators::FinalTube} +{include:LabwareCreators::MergedPlate} - Used directly in 1 purposes: - LB Lib Pool Norm + Used directly in 4 purposes: + LHR XP, LHR-384 cDNA, LTHR Lib PCR pool, and LTHR-384 Lib PCR pool -{LabwareCreators::FinalTube View class documentation} +{LabwareCreators::MergedPlate View class documentation} -## LabwareCreators::FinalTubeFromPlate +## LabwareCreators::PartialStampedPlateWithoutDilution -{include:LabwareCreators::FinalTubeFromPlate} +{include:LabwareCreators::PartialStampedPlateWithoutDilution} - Used directly in 1 purposes: - Cap Lib Pool Norm + **This labware creator is unused** -{LabwareCreators::FinalTubeFromPlate View class documentation} +{LabwareCreators::PartialStampedPlateWithoutDilution View class documentation} -## LabwareCreators::MultiPlatePool +## LabwareCreators::PcrCyclesBinnedPlate -{include:LabwareCreators::MultiPlatePool} +{include:LabwareCreators::PcrCyclesBinnedPlate} Used directly in 2 purposes: - LB Lib PrePool and RVI Lib PrePool + LDS AL Lib Dil and LTN AL Lib Dil -{LabwareCreators::MultiPlatePool View class documentation} +{LabwareCreators::PcrCyclesBinnedPlate View class documentation} -## LabwareCreators::MultiStamp +## LabwareCreators::PlateWithPrimerPanel -{include:LabwareCreators::MultiStamp} +{include:LabwareCreators::PlateWithPrimerPanel} **This labware creator is unused** -{LabwareCreators::MultiStamp View class documentation} +{LabwareCreators::PlateWithPrimerPanel View class documentation} -## LabwareCreators::MultiStampLibrarySplitter +## LabwareCreators::QuadrantSplitPlate -{include:LabwareCreators::MultiStampLibrarySplitter} +{include:LabwareCreators::QuadrantSplitPlate} - Used directly in 2 purposes: - CLCM Lysate DNA and CLCM Lysate RNA + Used directly in 1 purposes: + PF Lib Q-XP2 -{LabwareCreators::MultiStampLibrarySplitter View class documentation} +{LabwareCreators::QuadrantSplitPlate View class documentation} -## LabwareCreators::QuadrantStampBase +## LabwareCreators::StampedPlateAddingRandomisedControls -{include:LabwareCreators::QuadrantStampBase} +{include:LabwareCreators::StampedPlateAddingRandomisedControls} - **This labware creator is unused** + Used directly in 1 purposes: + LBSN-96 Lysate -{LabwareCreators::QuadrantStampBase View class documentation} +{LabwareCreators::StampedPlateAddingRandomisedControls View class documentation} -## LabwareCreators::QuadrantStamp +## LabwareCreators::ConcentrationBinnedPlate -{include:LabwareCreators::QuadrantStamp} +{include:LabwareCreators::ConcentrationBinnedPlate} - Used directly in 4 purposes: - LBSN-384 PCR 1, LTHR-384 RT-Q, PF-384 Post Shear XP, and pWGS-384 Post Shear XP + Used directly in 1 purposes: + LBC 3pV3 GEX Dil -{LabwareCreators::QuadrantStamp View class documentation} +{LabwareCreators::ConcentrationBinnedPlate View class documentation} -## LabwareCreators::QuadrantStampPrimerPanel +## LabwareCreators::FixedNormalisedPlate -{include:LabwareCreators::QuadrantStampPrimerPanel} +{include:LabwareCreators::FixedNormalisedPlate} - Used directly in 1 purposes: - GBS PCR1 + Used directly in 2 purposes: + LBC BCR Dil 1 and LBC TCR Dil 1 -{LabwareCreators::QuadrantStampPrimerPanel View class documentation} +{LabwareCreators::FixedNormalisedPlate View class documentation} -## LabwareCreators::TenStamp +## LabwareCreators::NormalisedBinnedPlate -{include:LabwareCreators::TenStamp} +{include:LabwareCreators::NormalisedBinnedPlate} - Used directly in 2 purposes: - LBC Aggregate and LBC Cherrypick + Used directly in 1 purposes: + LBC 5p GEX Dil -{LabwareCreators::TenStamp View class documentation} +{LabwareCreators::NormalisedBinnedPlate View class documentation} -## LabwareCreators::MultiStampTubes +## LabwareCreators::CustomPooledTubes -{include:LabwareCreators::MultiStampTubes} +{include:LabwareCreators::CustomPooledTubes} - Used directly in 3 purposes: - LCA Blood Array, LCA Blood Bank, and LRC Blood Bank + Used directly in 7 purposes: + CLCM DNA Pool, CLCM RNA Pool, LB Custom Pool, LCA Custom Pool, LCMB Custom Pool, LDS Custom Pool, and LTN Custom Pool -{LabwareCreators::MultiStampTubes View class documentation} +{LabwareCreators::CustomPooledTubes View class documentation} -## LabwareCreators::PlateSplitToTubeRacks +## LabwareCreators::PooledTubesBySample -{include:LabwareCreators::PlateSplitToTubeRacks} +{include:LabwareCreators::PooledTubesBySample} - Used directly in 2 purposes: - LRC Bank Seq and LRC Bank Spare + Used directly in 1 purposes: + LCA Bank Stock -{LabwareCreators::PlateSplitToTubeRacks View class documentation} +{LabwareCreators::PooledTubesBySample View class documentation} -## LabwareCreators::PlateWithTemplate +## LabwareCreators::PooledTubesBySubmission -{include:LabwareCreators::PlateWithTemplate} +{include:LabwareCreators::PooledTubesBySubmission} - Used directly in 2 purposes: - LB Cap Lib Pool and RVI Cap Lib Pool + Used directly in 12 purposes: + GnT Pico Lib Pool, LB Lib Pool, LBB Lib Pool Stock, LBC 3pV3 GLibPS, LBC 5p GLibPS, LBC BCR LibPS, LBC TCR LibPS, LHR Lib Pool, LHR-384 Pool XP, pWGS-384 Lib Pool XP, scRNA Lib Pool, and scRNA-384 Lib Pool XP -{LabwareCreators::PlateWithTemplate View class documentation} +{LabwareCreators::PooledTubesBySubmission View class documentation} -## LabwareCreators::PooledTubesFromWholePlates +## LabwareCreators::PooledTubesBySubmissionWithPhiX -{include:LabwareCreators::PooledTubesFromWholePlates} +{include:LabwareCreators::PooledTubesBySubmissionWithPhiX} Used directly in 2 purposes: - GBS PCR2 Pool Stock and LBSN-384 PCR 2 Pool + LTHR Pool XP and LTHR-384 Pool XP -{LabwareCreators::PooledTubesFromWholePlates View class documentation} +{LabwareCreators::PooledTubesBySubmissionWithPhiX View class documentation} -## LabwareCreators::PooledTubesFromWholeTubes +## LabwareCreators::MultiStampLibrarySplitter -{include:LabwareCreators::PooledTubesFromWholeTubes} +{include:LabwareCreators::MultiStampLibrarySplitter} Used directly in 2 purposes: - GBS MiSeq Pool and LBSN-9216 Lib PCR Pool + CLCM Lysate DNA and CLCM Lysate RNA -{LabwareCreators::PooledTubesFromWholeTubes View class documentation} +{LabwareCreators::MultiStampLibrarySplitter View class documentation} -## LabwareCreators::PooledWellsBySampleInGroups +## LabwareCreators::QuadrantStampBase -{include:LabwareCreators::PooledWellsBySampleInGroups} +{include:LabwareCreators::QuadrantStampBase} - Used directly in 1 purposes: - LRC PBMC Bank + **This labware creator is unused** -{LabwareCreators::PooledWellsBySampleInGroups View class documentation} +{LabwareCreators::QuadrantStampBase View class documentation} -## LabwareCreators::TaggedPlate +## LabwareCreators::TenStamp -{include:LabwareCreators::TaggedPlate} +{include:LabwareCreators::TenStamp} - Used directly in 21 purposes: - CLCM DNA Lib PCR, CLCM RNA Lib PCR, GBS PCR2, GnT Pico Lib PCR, LB Lib PCR, LBSN-384 PCR 2, LCMB Lib PCR, LDS Lib PCR, LHR Lib PCR, LHR-384 Lib PCR, LTHR Lib PCR 1, LTHR Lib PCR 2, LTHR-384 Lib PCR 1, LTHR-384 Lib PCR 2, LTN Lib PCR, PF Lib, PF-384 Lib, pWGS-384 Lib PCR, RVI Lib PCR, scRNA Lib PCR, and scRNA-384 Lib PCR + Used directly in 2 purposes: + LBC Aggregate and LBC Cherrypick -{LabwareCreators::TaggedPlate View class documentation} +{LabwareCreators::TenStamp View class documentation} -## LabwareCreators::TubeFromTube +## LabwareCreators::QuadrantStamp -{include:LabwareCreators::TubeFromTube} +{include:LabwareCreators::QuadrantStamp} - Used directly in 17 purposes: - CLCM DNA Pool Norm, CLCM RNA Pool Norm, GBS PCR Pool, GBS PCR Pool Selected, GnT Pico Lib Pool XP, LB Custom Pool Norm, LBC 5p Pool Norm, LBC BCR Pool Norm, LBC TCR Pool Norm, LBSN-9216 Lib PCR Pool XP, LCA Custom Pool Norm, LCMB Custom Pool Norm, LDS Custom Pool Norm, LHR Lib Pool XP, LRC Blood Aliquot, LTN Custom Pool Norm, and scRNA Lib Pool XP + Used directly in 4 purposes: + LBSN-384 PCR 1, LTHR-384 RT-Q, PF-384 Post Shear XP, and pWGS-384 Post Shear XP -{LabwareCreators::TubeFromTube View class documentation} +{LabwareCreators::QuadrantStamp View class documentation} -## LabwareCreators::Uncreatable +## LabwareCreators::QuadrantStampPrimerPanel -{include:LabwareCreators::Uncreatable} +{include:LabwareCreators::QuadrantStampPrimerPanel} - Used directly in 8 purposes: - CLCM Stock, LCA Blood Vac, LDW-96 Stock, LILYS-96 Stock, LRC Blood Vac, LTHR Cherrypick, LTHR RT, and LTHR-384 RT + Used directly in 1 purposes: + GBS PCR1 -{LabwareCreators::Uncreatable View class documentation} +{LabwareCreators::QuadrantStampPrimerPanel View class documentation} diff --git a/docs/presenters.md b/docs/presenters.md index 168be2697..887127374 100644 --- a/docs/presenters.md +++ b/docs/presenters.md @@ -34,26 +34,6 @@ LBC 3pV3 GEX Dil {Presenters::ConcentrationBinnedPlatePresenter View class documentation} -### Presenters::StockPlatePresenter - -{include:Presenters::StockPlatePresenter} - -Used directly in 16 purposes: -GnT Stock, LB Cherrypick, LBB Cherrypick, LBC Stock, LBR Cherrypick, LCMB Cherrypick, LDS Cherrypick, LDS Stock, LHR RT, LHR-384 RT, LTHR RT, LTHR-384 RT, LTN Cherrypick, LTN Stock, PF Cherrypicked, and scRNA Stock - -{Presenters::StockPlatePresenter View class documentation} - - -### Presenters::FailableStockPlatePresenter - -{include:Presenters::FailableStockPlatePresenter} - -Used directly in 2 purposes: -LBSN-96 Lysate and LILYS-96 Stock - -{Presenters::FailableStockPlatePresenter View class documentation} - - ### Presenters::MinimalPlatePresenter {include:Presenters::MinimalPlatePresenter} @@ -64,26 +44,6 @@ scRNA-384 cDNA-XP and scRNA-384 End Prep {Presenters::MinimalPlatePresenter View class documentation} -### Presenters::MinimalPcrPlatePresenter - -{include:Presenters::MinimalPcrPlatePresenter} - -Used directly in 11 purposes: -GBS PCR1, GBS PCR2, LBSN-384 PCR 1, LBSN-384 PCR 2, LHR-384 Lib PCR, LTHR Lib PCR 1, LTHR Lib PCR 2, LTHR-384 Lib PCR 1, LTHR-384 Lib PCR 2, pWGS-384 Lib PCR, and scRNA-384 Lib PCR - -{Presenters::MinimalPcrPlatePresenter View class documentation} - - -### Presenters::MinimalStockPlatePresenter - -{include:Presenters::MinimalStockPlatePresenter} - -Used directly in 3 purposes: -GBS Stock, GBS-96 Stock, and scRNA-384 Stock - -{Presenters::MinimalStockPlatePresenter View class documentation} - - ### Presenters::NormalisedBinnedPlatePresenter {include:Presenters::NormalisedBinnedPlatePresenter} @@ -94,36 +54,25 @@ LBC 5p GEX Dil {Presenters::NormalisedBinnedPlatePresenter View class documentation} -### Presenters::PcrCyclesBinnedPlatePresenter +### Presenters::PcrCyclesBinnedPlatePresenterBase -{include:Presenters::PcrCyclesBinnedPlatePresenter} +{include:Presenters::PcrCyclesBinnedPlatePresenterBase} -Used directly in 2 purposes: -LDS AL Lib Dil and LTN AL Lib Dil +**This presenter is unused** -{Presenters::PcrCyclesBinnedPlatePresenter View class documentation} +{Presenters::PcrCyclesBinnedPlatePresenterBase View class documentation} ### Presenters::StandardPresenter {include:Presenters::StandardPresenter} -Used directly in 99 purposes: -CLCM DNA End Prep, CLCM DNA Lib PCR XP, CLCM Lysate DNA, CLCM Lysate RNA, CLCM RNA End Prep, CLCM RNA Lib PCR XP, CLCM RT PreAmp, CLCM Stock, GnT Pico End Prep, GnT Pico-XP, GnT scDNA, LB Cap Lib, LB Cap Lib PCR, LB Cap Lib PCR-XP, LB Cap Lib Pool, LB End Prep, LB Hyb, LB Lib PCR-XP, LB Lib PrePool, LB Post Shear, LB Shear, LBB Enriched BCR, LBB Enriched BCR HT, LBB Enriched TCR, LBB Enriched TCR HT, LBB Lib-XP, LBB Ligation, LBC 3pV3 GEX Frag 2XP, LBC 5p GEX Frag 2XP, LBC Aggregate, LBC BCR Dil 1, LBC BCR Dil 2, LBC BCR Enrich1 2XSPRI, LBC BCR Enrich2 2XSPRI, LBC BCR Post PCR, LBC Cherrypick, LBC TCR Dil 1, LBC TCR Dil 2, LBC TCR Enrich1 2XSPRI, LBC TCR Enrich2 2XSPRI, LBC TCR Post PCR, LBR Frag cDNA, LBR Globin, LCA 10X cDNA, LCA Blood Array, LCA Blood Bank, LCA PBMC, LCA PBMC Bank, LCA PBMC Pools, LCMB End Prep, LCMB Lib PCR-XP, LDS AL Lib, LDS Lib PCR XP, LDS Stock XP, LHR End Prep, LHR PCR 1, LHR PCR 2, LHR XP, LHR-384 AL Lib, LHR-384 cDNA, LHR-384 PCR 1, LHR-384 PCR 2, LHR-384 XP, LRC Blood Bank, LRC PBMC Bank, LSW-96 Stock, LTHR Lib PCR pool, LTHR PCR 1, LTHR PCR 2, LTHR RT-S, LTHR-384 Lib PCR pool, LTHR-384 PCR 1, LTHR-384 PCR 2, LTHR-384 RT-Q, LTN AL Lib, LTN Lib PCR XP, LTN Post Shear, LTN Shear, LTN Stock XP, PF End Prep, PF Lib XP, PF Lib XP2, PF Post Shear, PF Post Shear XP, PF Shear, PF-384 End Prep, PF-384 Lib XP2, PF-384 Post Shear XP, pWGS-384 AL Lib, pWGS-384 Post Shear XP, RVI Cap Lib, RVI Cap Lib PCR, RVI Cap Lib PCR XP, RVI Cap Lib Pool, RVI Hyb, RVI Lib PCR XP, RVI Lib PrePool, scRNA cDNA-XP, and scRNA End Prep +Used directly in 107 purposes: +CLCM DNA End Prep, CLCM DNA Lib PCR XP, CLCM Lysate DNA, CLCM Lysate RNA, CLCM RNA End Prep, CLCM RNA Lib PCR XP, CLCM RT PreAmp, CLCM Stock, GnT Pico End Prep, GnT Pico-XP, GnT scDNA, LB Cap Lib, LB Cap Lib PCR, LB Cap Lib PCR-XP, LB Cap Lib Pool, LB End Prep, LB Hyb, LB Lib PCR-XP, LB Lib PrePool, LB Post Shear, LB Shear, LBB Enriched BCR, LBB Enriched BCR HT, LBB Enriched TCR, LBB Enriched TCR HT, LBB Lib-XP, LBB Ligation, LBC 3pV3 GEX Frag 2XP, LBC 5p GEX Frag 2XP, LBC Aggregate, LBC BCR Dil 1, LBC BCR Dil 2, LBC BCR Enrich1 2XSPRI, LBC BCR Enrich2 2XSPRI, LBC BCR Post PCR, LBC Cherrypick, LBC TCR Dil 1, LBC TCR Dil 2, LBC TCR Enrich1 2XSPRI, LBC TCR Enrich2 2XSPRI, LBC TCR Post PCR, LBR Frag cDNA, LBR Globin, LCA 10X cDNA, LCA Blood Array, LCA Blood Bank, LCA PBMC, LCA PBMC Bank, LCA PBMC Pools, LCMB End Prep, LCMB Lib PCR-XP, LDS AL Lib, LDS Lib PCR XP, LDS Stock XP, LHR End Prep, LHR PCR 1, LHR PCR 2, LHR XP, LHR-384 AL Lib, LHR-384 cDNA, LHR-384 PCR 1, LHR-384 PCR 2, LHR-384 XP, LRC Blood Bank, LRC HT 5p cDNA PCR, LRC HT 5p cDNA PCR XP, LRC HT 5p Chip, LRC HT 5p GEMs, LRC PBMC Bank, LRC PBMC Cryostor, LRC PBMC Defrost PBS, LRC PBMC Pools, LRC PBMC Pools Input, LSW-96 Stock, LTHR Lib PCR pool, LTHR PCR 1, LTHR PCR 2, LTHR RT-S, LTHR-384 Lib PCR pool, LTHR-384 PCR 1, LTHR-384 PCR 2, LTHR-384 RT-Q, LTN AL Lib, LTN Lib PCR XP, LTN Post Shear, LTN Shear, LTN Stock XP, PF End Prep, PF Lib XP, PF Lib XP2, PF Post Shear, PF Post Shear XP, PF Shear, PF-384 End Prep, PF-384 Lib XP2, PF-384 Post Shear XP, pWGS-384 AL Lib, pWGS-384 Post Shear XP, RVI Cap Lib, RVI Cap Lib PCR, RVI Cap Lib PCR XP, RVI Cap Lib Pool, RVI Hyb, RVI Lib PCR XP, RVI Lib PrePool, scRNA cDNA-XP, and scRNA End Prep {Presenters::StandardPresenter View class documentation} -### Presenters::PcrPresenter - -{include:Presenters::PcrPresenter} - -Used directly in 20 purposes: -CLCM DNA Lib PCR, CLCM RNA Lib PCR, GnT Pico Lib PCR, LB Lib PCR, LBB Chromium Tagged, LBB Lib PCR-XP, LBB Ligation Tagged, LBC 3pV3 GEX LigXP, LBC 3pV3 GEX PCR 2XP, LBC 5p GEX LigXP, LBC 5p GEX PCR 2XP, LBC BCR Post Lig 1XSPRI, LBC TCR Post Lig 1XSPRI, LCA Connect PCRXP, LCMB Lib PCR, LDS Lib PCR, LHR Lib PCR, LTN Lib PCR, RVI Lib PCR, and scRNA Lib PCR - -{Presenters::PcrPresenter View class documentation} - - ### Presenters::PermissivePresenter {include:Presenters::PermissivePresenter} @@ -164,6 +113,16 @@ PF Lib Q-XP2 {Presenters::SplitPresenter View class documentation} +### Presenters::StockPlatePresenter + +{include:Presenters::StockPlatePresenter} + +Used directly in 18 purposes: +GnT Stock, LB Cherrypick, LBB Cherrypick, LBC Stock, LBR Cherrypick, LBSN-96 Lysate, LCMB Cherrypick, LDS Cherrypick, LDS Stock, LHR RT, LHR-384 RT, LILYS-96 Stock, LTHR RT, LTHR-384 RT, LTN Cherrypick, LTN Stock, PF Cherrypicked, and scRNA Stock + +{Presenters::StockPlatePresenter View class documentation} + + ### Presenters::SubmissionPlatePresenter {include:Presenters::SubmissionPlatePresenter} @@ -183,24 +142,54 @@ LDW-96 Stock and LTHR Cherrypick {Presenters::UnknownPlatePresenter View class documentation} -### Presenters::TagPlate384Presenter +### Presenters::UntaggedPlatePassingPresenter -{include:Presenters::TagPlate384Presenter} +{include:Presenters::UntaggedPlatePassingPresenter} Used directly in 1 purposes: -Tag Plate - 384 +GnT MDA Norm -{Presenters::TagPlate384Presenter View class documentation} +{Presenters::UntaggedPlatePassingPresenter View class documentation} -### Presenters::UntaggedPlatePassingPresenter +### Presenters::MinimalPcrPlatePresenter -{include:Presenters::UntaggedPlatePassingPresenter} +{include:Presenters::MinimalPcrPlatePresenter} + +Used directly in 11 purposes: +GBS PCR1, GBS PCR2, LBSN-384 PCR 1, LBSN-384 PCR 2, LHR-384 Lib PCR, LTHR Lib PCR 1, LTHR Lib PCR 2, LTHR-384 Lib PCR 1, LTHR-384 Lib PCR 2, pWGS-384 Lib PCR, and scRNA-384 Lib PCR + +{Presenters::MinimalPcrPlatePresenter View class documentation} + + +### Presenters::MinimalStockPlatePresenter + +{include:Presenters::MinimalStockPlatePresenter} + +Used directly in 3 purposes: +GBS Stock, GBS-96 Stock, and scRNA-384 Stock + +{Presenters::MinimalStockPlatePresenter View class documentation} + + +### Presenters::PcrPresenter + +{include:Presenters::PcrPresenter} + +Used directly in 20 purposes: +CLCM DNA Lib PCR, CLCM RNA Lib PCR, GnT Pico Lib PCR, LB Lib PCR, LBB Chromium Tagged, LBB Lib PCR-XP, LBB Ligation Tagged, LBC 3pV3 GEX LigXP, LBC 3pV3 GEX PCR 2XP, LBC 5p GEX LigXP, LBC 5p GEX PCR 2XP, LBC BCR Post Lig 1XSPRI, LBC TCR Post Lig 1XSPRI, LCA Connect PCRXP, LCMB Lib PCR, LDS Lib PCR, LHR Lib PCR, LTN Lib PCR, RVI Lib PCR, and scRNA Lib PCR + +{Presenters::PcrPresenter View class documentation} + + +### Presenters::TagPlate384Presenter + +{include:Presenters::TagPlate384Presenter} Used directly in 1 purposes: -GnT MDA Norm +Tag Plate - 384 -{Presenters::UntaggedPlatePassingPresenter View class documentation} +{Presenters::TagPlate384Presenter View class documentation} ## Tube presenters @@ -220,21 +209,11 @@ GnT MDA Norm {include:Presenters::SimpleTubePresenter} Used directly in 36 purposes: -LBC 5p GLibPS, LBC 5p Pool Norm, Cap Lib Pool Norm, LB Custom Pool, CLCM DNA Pool, CLCM RNA Pool, LCMB Custom Pool, pWGS-384 Lib Pool XP, LRC Blood Aliquot, LRC Bank Seq, LRC Bank Spare, LTHR-384 Pool XP, LBSN-384 PCR 2 Pool, LBSN-9216 Lib PCR Pool, LTN Custom Pool, LBC BCR LibPS, LBC BCR Pool Norm, GnT Pico Lib Pool, GnT Pico Lib Pool XP, LB Lib Pool, LDS Custom Pool, LHR-384 Pool XP, GBS PCR2 Pool Stock, GBS PCR Pool, GBS PCR Pool Selected, LCA Custom Pool, LBC TCR LibPS, LBC TCR Pool Norm, LTHR Pool XP, LBB Lib Pool Stock, LBC 3pV3 GLibPS, scRNA Lib Pool, scRNA-384 Lib Pool XP, scRNA Lib Pool XP, LHR Lib Pool, LHR Lib Pool XP, +LBC 5p GLibPS, LBC 5p Pool Norm, LRC Blood Aliquot, LRC Bank Seq, LRC Bank Spare, Cap Lib Pool Norm, LB Custom Pool, CLCM DNA Pool, CLCM RNA Pool, LCMB Custom Pool, pWGS-384 Lib Pool XP, LTHR-384 Pool XP, LBSN-384 PCR 2 Pool, LBSN-9216 Lib PCR Pool, LTN Custom Pool, LBC BCR LibPS, LBC BCR Pool Norm, GnT Pico Lib Pool, GnT Pico Lib Pool XP, LB Lib Pool, LDS Custom Pool, LHR-384 Pool XP, GBS PCR2 Pool Stock, GBS PCR Pool, GBS PCR Pool Selected, LCA Custom Pool, LBC TCR LibPS, LBC TCR Pool Norm, LTHR Pool XP, LBB Lib Pool Stock, LBC 3pV3 GLibPS, scRNA Lib Pool, scRNA-384 Lib Pool XP, scRNA Lib Pool XP, LHR Lib Pool, LHR Lib Pool XP, {Presenters::SimpleTubePresenter View class documentation} -### Presenters::CardinalBankStockTubePresenter - -{include:Presenters::CardinalBankStockTubePresenter} - -Used directly in 1 purposes: -LCA Bank Stock, - -{Presenters::CardinalBankStockTubePresenter View class documentation} - - ### Presenters::FinalTubePresenter {include:Presenters::FinalTubePresenter} @@ -263,3 +242,13 @@ LRC Blood Vac, LCA Blood Vac, {Presenters::VacTubePresenter View class documentation} + +### Presenters::CardinalBankStockTubePresenter + +{include:Presenters::CardinalBankStockTubePresenter} + +Used directly in 1 purposes: +LCA Bank Stock, + +{Presenters::CardinalBankStockTubePresenter View class documentation} + diff --git a/docs/purposes_yaml_files.md b/docs/purposes_yaml_files.md index a379f88b8..f8225d76b 100644 --- a/docs/purposes_yaml_files.md +++ b/docs/purposes_yaml_files.md @@ -359,8 +359,8 @@ which describes the specific fields (barcode, date, user, etc...) which will be displayed on a Plate/Tube label, and a print my barcode template, which describes how those fields are physically laid out on the label. -If unspecified, falls back on the default label template for the given printer -specified in default_pmb_templates in {file:config/label_templates.yml}. +If unspecified, falls back on the default label template for the given printer type +specified in the defaults_by_printer_type section in {file:config/label_templates.yml}. ```yaml :label_template: plate_xp diff --git a/docs/state_changers.md b/docs/state_changers.md index 1324a883b..c03ddbeaf 100644 --- a/docs/state_changers.md +++ b/docs/state_changers.md @@ -18,8 +18,8 @@ manual transfer. {include:StateChangers::DefaultStateChanger} - Used directly in 177 purposes: - CLCM DNA End Prep, CLCM DNA Lib PCR, CLCM DNA Lib PCR XP, CLCM Lysate DNA, CLCM Lysate RNA, CLCM RNA End Prep, CLCM RNA Lib PCR, CLCM RNA Lib PCR XP, CLCM RT PreAmp, CLCM Stock, GBS PCR1, GBS PCR2, GBS Stock, GBS-96 Stock, GnT MDA Norm, GnT Pico End Prep, GnT Pico Lib PCR, GnT Pico-XP, GnT scDNA, GnT Stock, Heron Lysed Tube Rack, LB Cap Lib, LB Cap Lib PCR, LB Cap Lib PCR-XP, LB Cap Lib Pool, LB cDNA, LB cDNA XP, LB Cherrypick, LB End Prep, LB Hyb, LB Lib PCR, LB Lib PCR-XP, LB Lib PrePool, LB Post Shear, LB Shear, LBB Cherrypick, LBB Chromium Tagged, LBB Enriched BCR, LBB Enriched BCR HT, LBB Enriched TCR, LBB Enriched TCR HT, LBB Lib PCR-XP, LBB Lib-XP, LBB Ligation, LBB Ligation Tagged, LBC 3pV3 GEX Dil, LBC 3pV3 GEX Frag 2XP, LBC 3pV3 GEX LigXP, LBC 3pV3 GEX PCR 2XP, LBC 5p GEX Dil, LBC 5p GEX Frag 2XP, LBC 5p GEX LigXP, LBC 5p GEX PCR 2XP, LBC Aggregate, LBC BCR Dil 1, LBC BCR Dil 2, LBC BCR Enrich1 2XSPRI, LBC BCR Enrich2 2XSPRI, LBC BCR Post Lig 1XSPRI, LBC BCR Post PCR, LBC Stock, LBC TCR Dil 1, LBC TCR Dil 2, LBC TCR Enrich1 2XSPRI, LBC TCR Enrich2 2XSPRI, LBC TCR Post Lig 1XSPRI, LBC TCR Post PCR, LBR Cherrypick, LBR Frag, LBR Frag cDNA, LBR Globin, LBR Globin DNase, LBR mRNA Cap, LBR Ribo DNase, LBR RiboGlobin DNase, LBSN-384 PCR 1, LBSN-384 PCR 2, LCA 10X cDNA, LCA Blood Array, LCA Blood Bank, LCA Connect PCRXP, LCA PBMC, LCA PBMC Bank, LCA PBMC Pools, LCMB Cherrypick, LCMB End Prep, LCMB Lib PCR, LCMB Lib PCR-XP, LDS AL Lib, LDS AL Lib Dil, LDS Cherrypick, LDS Lib PCR, LDS Lib PCR XP, LDS Stock, LDS Stock XP, LDW-96 Stock, LHR End Prep, LHR Lib PCR, LHR PCR 1, LHR PCR 2, LHR RT, LHR XP, LHR-384 AL Lib, LHR-384 cDNA, LHR-384 End Prep, LHR-384 Lib PCR, LHR-384 PCR 1, LHR-384 PCR 2, LHR-384 RT, LHR-384 XP, LILYS-96 Stock, LRC Blood Bank, LRC PBMC Bank, LTHR Cherrypick, LTHR Lib PCR 1, LTHR Lib PCR 2, LTHR Lib PCR pool, LTHR PCR 1, LTHR PCR 2, LTHR RT, LTHR RT-S, LTHR-384 Lib PCR 1, LTHR-384 Lib PCR 2, LTHR-384 Lib PCR pool, LTHR-384 PCR 1, LTHR-384 PCR 2, LTHR-384 RT, LTHR-384 RT-Q, LTN AL Lib, LTN AL Lib Dil, LTN Cherrypick, LTN Lib PCR, LTN Lib PCR XP, LTN Post Shear, LTN Shear, LTN Stock, LTN Stock XP, PF Cherrypicked, PF End Prep, PF Lib, PF Lib Q-XP2, PF Lib XP, PF Lib XP2, PF Post Shear, PF Post Shear XP, PF Shear, PF-384 End Prep, PF-384 Lib, PF-384 Lib XP2, PF-384 Post Shear XP, pWGS-384 AL Lib, pWGS-384 End Prep, pWGS-384 Lib PCR, pWGS-384 Post Shear XP, RVI Cap Lib, RVI Cap Lib PCR, RVI Cap Lib PCR XP, RVI Cap Lib Pool, RVI cDNA XP, RVI Cherrypick, RVI Hyb, RVI Lib PCR, RVI Lib PCR XP, RVI Lib PrePool, RVI Lig Bind, RVI RT, scRNA cDNA-XP, scRNA End Prep, scRNA Lib PCR, scRNA Stock, scRNA-384 cDNA-XP, scRNA-384 End Prep, scRNA-384 Lib PCR, scRNA-384 Stock, Tag Plate - 384, TR Stock 48, and TR Stock 96 + Used directly in 185 purposes: + CLCM DNA End Prep, CLCM DNA Lib PCR, CLCM DNA Lib PCR XP, CLCM Lysate DNA, CLCM Lysate RNA, CLCM RNA End Prep, CLCM RNA Lib PCR, CLCM RNA Lib PCR XP, CLCM RT PreAmp, CLCM Stock, GBS PCR1, GBS PCR2, GBS Stock, GBS-96 Stock, GnT MDA Norm, GnT Pico End Prep, GnT Pico Lib PCR, GnT Pico-XP, GnT scDNA, GnT Stock, Heron Lysed Tube Rack, LB Cap Lib, LB Cap Lib PCR, LB Cap Lib PCR-XP, LB Cap Lib Pool, LB cDNA, LB cDNA XP, LB Cherrypick, LB End Prep, LB Hyb, LB Lib PCR, LB Lib PCR-XP, LB Lib PrePool, LB Post Shear, LB Shear, LBB Cherrypick, LBB Chromium Tagged, LBB Enriched BCR, LBB Enriched BCR HT, LBB Enriched TCR, LBB Enriched TCR HT, LBB Lib PCR-XP, LBB Lib-XP, LBB Ligation, LBB Ligation Tagged, LBC 3pV3 GEX Dil, LBC 3pV3 GEX Frag 2XP, LBC 3pV3 GEX LigXP, LBC 3pV3 GEX PCR 2XP, LBC 5p GEX Dil, LBC 5p GEX Frag 2XP, LBC 5p GEX LigXP, LBC 5p GEX PCR 2XP, LBC Aggregate, LBC BCR Dil 1, LBC BCR Dil 2, LBC BCR Enrich1 2XSPRI, LBC BCR Enrich2 2XSPRI, LBC BCR Post Lig 1XSPRI, LBC BCR Post PCR, LBC Stock, LBC TCR Dil 1, LBC TCR Dil 2, LBC TCR Enrich1 2XSPRI, LBC TCR Enrich2 2XSPRI, LBC TCR Post Lig 1XSPRI, LBC TCR Post PCR, LBR Cherrypick, LBR Frag, LBR Frag cDNA, LBR Globin, LBR Globin DNase, LBR mRNA Cap, LBR Ribo DNase, LBR RiboGlobin DNase, LBSN-384 PCR 1, LBSN-384 PCR 2, LCA 10X cDNA, LCA Blood Array, LCA Blood Bank, LCA Connect PCRXP, LCA PBMC, LCA PBMC Bank, LCA PBMC Pools, LCMB Cherrypick, LCMB End Prep, LCMB Lib PCR, LCMB Lib PCR-XP, LDS AL Lib, LDS AL Lib Dil, LDS Cherrypick, LDS Lib PCR, LDS Lib PCR XP, LDS Stock, LDS Stock XP, LDW-96 Stock, LHR End Prep, LHR Lib PCR, LHR PCR 1, LHR PCR 2, LHR RT, LHR XP, LHR-384 AL Lib, LHR-384 cDNA, LHR-384 End Prep, LHR-384 Lib PCR, LHR-384 PCR 1, LHR-384 PCR 2, LHR-384 RT, LHR-384 XP, LILYS-96 Stock, LRC Blood Bank, LRC HT 5p cDNA PCR, LRC HT 5p cDNA PCR XP, LRC HT 5p Chip, LRC HT 5p GEMs, LRC PBMC Bank, LRC PBMC Cryostor, LRC PBMC Defrost PBS, LRC PBMC Pools, LRC PBMC Pools Input, LTHR Cherrypick, LTHR Lib PCR 1, LTHR Lib PCR 2, LTHR Lib PCR pool, LTHR PCR 1, LTHR PCR 2, LTHR RT, LTHR RT-S, LTHR-384 Lib PCR 1, LTHR-384 Lib PCR 2, LTHR-384 Lib PCR pool, LTHR-384 PCR 1, LTHR-384 PCR 2, LTHR-384 RT, LTHR-384 RT-Q, LTN AL Lib, LTN AL Lib Dil, LTN Cherrypick, LTN Lib PCR, LTN Lib PCR XP, LTN Post Shear, LTN Shear, LTN Stock, LTN Stock XP, PF Cherrypicked, PF End Prep, PF Lib, PF Lib Q-XP2, PF Lib XP, PF Lib XP2, PF Post Shear, PF Post Shear XP, PF Shear, PF-384 End Prep, PF-384 Lib, PF-384 Lib XP2, PF-384 Post Shear XP, pWGS-384 AL Lib, pWGS-384 End Prep, pWGS-384 Lib PCR, pWGS-384 Post Shear XP, RVI Cap Lib, RVI Cap Lib PCR, RVI Cap Lib PCR XP, RVI Cap Lib Pool, RVI cDNA XP, RVI Cherrypick, RVI Hyb, RVI Lib PCR, RVI Lib PCR XP, RVI Lib PrePool, RVI Lig Bind, RVI RT, scRNA cDNA-XP, scRNA End Prep, scRNA Lib PCR, scRNA Stock, scRNA-384 cDNA-XP, scRNA-384 End Prep, scRNA-384 Lib PCR, scRNA-384 Stock, Tag Plate - 384, TR Stock 48, and TR Stock 96 {StateChangers::DefaultStateChanger View class documentation} diff --git a/lib/purpose_config.rb b/lib/purpose_config.rb index eb1b6a18d..b121a9f37 100644 --- a/lib/purpose_config.rb +++ b/lib/purpose_config.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# This is used as part of a take task, and will be run within a console. +# This is used as part of a rake task, and will be run within a console. # rubocop:disable Rails/Output # rubocop:disable Metrics/ParameterLists @@ -14,27 +14,33 @@ class PurposeConfig self.default_state_changer = 'StateChangers::DefaultStateChanger' - def self.load(name, options, store, api, submission_templates, label_templates) + def self.load(name, options, store, api, submission_templates, label_template_config) case options.fetch(:asset_type) when 'plate' - PurposeConfig::Plate.new(name, options, store, api, submission_templates, label_templates) + PurposeConfig::Plate.new(name, options, store, api, submission_templates, label_template_config) when 'tube' - PurposeConfig::Tube.new(name, options, store, api, submission_templates, label_templates) + PurposeConfig::Tube.new(name, options, store, api, submission_templates, label_template_config) when 'tube_rack' - PurposeConfig::TubeRack.new(name, options, store, api, submission_templates, label_templates) + PurposeConfig::TubeRack.new(name, options, store, api, submission_templates, label_template_config) else raise "Unknown purpose type #{options.fetch(:asset_type)} for #{name}" end end - def initialize(name, options, store, api, submission_templates, label_templates) + # + # @param name [String] name of the purpose, from the keys from the purposes.yml file + # @param options [Hash] values under the name key from the purposes.yml file + # @param label_template_config [Hash] hash version of the label_template_config.yml file + # + def initialize(name, options, store, api, submission_templates, label_template_config) @name = name @options = options @submission = options.delete(:submission) @store = store @api = api @submission_templates = submission_templates - @label_templates = label_templates + @label_templates = label_template_config.fetch('templates') + @label_template_defaults = label_template_config.fetch('defaults_by_printer_type') @template_name = @options.delete(:label_template) || '' end @@ -44,10 +50,10 @@ def config **default_options, state_changer_class: default_state_changer, submission: submission_options, - label_class: print_option(:label_class), - printer_type: print_option(:printer_type), - pmb_template: print_option(:pmb_template), - sprint_template: print_option(:sprint_template) + label_class: label_template[:label_class], + printer_type: label_template[:printer_type], + pmb_template: label_template[:pmb_template], + sprint_template: label_template[:sprint_template] }.merge(@options) end @@ -127,33 +133,19 @@ def submission_options } end - # NB. Make sure label_templates.yml contains settings for label definitions - # explicity, in order to avoid incorrect results. The options below are used - # for assigning values for missing settings. - def default_printer_options + def label_template + @label_templates.fetch(@template_name.to_s, default_label_template) + end + + def default_label_template + printer_type_key = default_options[:default_printer_type] { - printer_type: default_printer_type, - pmb_template: default_pmb_template, label_class: default_options[:label_class], - sprint_template: default_sprint_template + printer_type: @label_template_defaults.fetch('printer_type_names').fetch(printer_type_key), + pmb_template: @label_template_defaults.fetch('pmb_templates').fetch(printer_type_key), + sprint_template: @label_template_defaults.fetch('sprint_templates').fetch(printer_type_key) } end - - def print_option(option) - @label_templates.fetch(@template_name.to_s, {}).fetch(option, default_printer_options[option]) - end - - def default_printer_type - @label_templates.fetch('default_printer_type_names').fetch(default_options[:default_printer_type]) - end - - def default_pmb_template - @label_templates.fetch('default_pmb_templates').fetch(default_options[:default_printer_type]) - end - - def default_sprint_template - @label_templates.fetch('default_sprint_templates').fetch(default_options[:default_printer_type]) - end end # rubocop:enable Metrics/ParameterLists # rubocop:enable Rails/Output diff --git a/lib/tasks/config.rake b/lib/tasks/config.rake index 66caa2f22..3fa8939e1 100644 --- a/lib/tasks/config.rake +++ b/lib/tasks/config.rake @@ -23,7 +23,7 @@ namespace :config do exit 1 end - label_templates = YAML.load_file(Rails.root.join('config/label_templates.yml')) + label_template_config = YAML.load_file(Rails.root.join('config/label_templates.yml')) puts 'Fetching submission_templates...' submission_templates = api.order_template.all.each_with_object({}) { |st, store| store[st.name] = st.uuid } @@ -34,7 +34,7 @@ namespace :config do purpose_config = ConfigLoader::PurposesLoader.new.config.map do |name, options| - PurposeConfig.load(name, options, all_purposes, api, submission_templates, label_templates) + PurposeConfig.load(name, options, all_purposes, api, submission_templates, label_template_config) end puts 'Preparing purposes...' @@ -77,8 +77,10 @@ namespace :config do configuration[:robots] = ROBOT_CONFIG - %i[default_pmb_templates default_sprint_templates default_printer_type_names].each do |key| - configuration[key] = label_templates[key.to_s] + label_template_defaults = label_template_config['defaults_by_printer_type'] + label_template_defaults.each_key do |key| + # adding 'default_' prefix and converting the key to a symbol, to keep it consistent with how it was before + configuration[:"default_#{key}"] = label_template_defaults[key.to_s] end configuration[:submission_templates] = submission_templates diff --git a/package.json b/package.json index 5b47549d5..a068b777d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "dependencies": { "@babel/preset-env": "^7.20.2", "@rails/webpacker": "5.4.2", - "axios": "^0.21.4", + "axios": "^0.28.0", "babel-loader": "^8.2.5", "bootstrap-vue": "^2.23.1", "cytoscape": "^3.28.1", @@ -28,7 +28,7 @@ "babel-core": "^7.0.0-bridge.0", "babel-eslint": "^10.0.1", "babel-jest": "^27.5.1", - "core-js": "^3.35.0", + "core-js": "^3.35.1", "eslint": "^6.0.0", "eslint-config-prettier": "^8.10.0", "eslint-plugin-jest": "^24.7.0", diff --git a/spec/controllers/exports_controller_spec.rb b/spec/controllers/exports_controller_spec.rb index 47e60416e..9742c21fe 100644 --- a/spec/controllers/exports_controller_spec.rb +++ b/spec/controllers/exports_controller_spec.rb @@ -7,6 +7,9 @@ let(:default_plate_includes) { 'wells' } let(:well_qc_includes) { 'wells.qc_results' } let(:well_qc_sample_includes) { 'wells.qc_results,wells.aliquots.sample.sample_metadata' } + let(:well_with_request_metadata_includes) do + 'wells.qc_results,wells.aliquots.sample.sample_metadata,wells.aliquots.request.poly_metadata' + end let(:well_src_asset_includes) { 'wells.transfer_requests_as_target.source_asset' } let(:plate) { create :v2_plate, barcode_number: 1 } let(:plate_barcode) { 'DN1S' } @@ -99,10 +102,10 @@ it_behaves_like 'a csv view' end - context 'where csv id requested is targeted_nanoseq_pcr_xp_concentrations_for_custom_pooling.csv' do - let(:includes) { well_qc_includes } - let(:csv_id) { 'targeted_nanoseq_pcr_xp_concentrations_for_custom_pooling' } - let(:expected_template) { 'targeted_nanoseq_pcr_xp_concentrations_for_custom_pooling' } + context 'where csv id requested is targeted_nanoseq_pcr_xp_merged_file.csv' do + let(:includes) { well_with_request_metadata_includes } + let(:csv_id) { 'targeted_nanoseq_pcr_xp_merged_file' } + let(:expected_template) { 'targeted_nanoseq_pcr_xp_merged_file' } it_behaves_like 'a csv view' end diff --git a/spec/factories/purpose_config_factories.rb b/spec/factories/purpose_config_factories.rb index 64d09028f..632ab4e3c 100644 --- a/spec/factories/purpose_config_factories.rb +++ b/spec/factories/purpose_config_factories.rb @@ -152,6 +152,21 @@ end end + factory :targeted_nano_seq_customer_csv_file_upload_purpose_config do + csv_file_upload do + { + input_amount_desired_min: 0.0, + input_amount_desired_max: 50.0, + sample_volume_min: 0.2, + sample_volume_max: 50.0, + diluent_volume_min: 0.0, + diluent_volume_max: 50.0, + pcr_cycles_min: 1, + pcr_cycles_max: 20 + } + end + end + # Configuration for an aggregation plate factory :aggregation_purpose_config do state_changer_class { 'StateChangers::AutomaticPlateStateChanger' } diff --git a/spec/factories/request_factories.rb b/spec/factories/request_factories.rb index 942f424e6..41858ad04 100644 --- a/spec/factories/request_factories.rb +++ b/spec/factories/request_factories.rb @@ -81,9 +81,24 @@ end factory :library_request_with_poly_metadata do + # To use this factory create each poly_metadatum individually using the poly_metadatum factory, but don't + # set the metadatable relationship to the request. Then pass them in as an array to this request factory + # as an array of one or more poly_metadata and it sets the relationship here. transient { poly_metadata { [] } } - after(:build) { |request, evaluator| request.poly_metadata = evaluator.poly_metadata } + after(:build) do |request, evaluator| + # initialise the poly_metadata array + request.poly_metadata = [] + + # add each polymetadatum to the request + evaluator.poly_metadata.each do |pm| + # set the relationship between the polymetadatum and the request + pm.relationships.metadatable = request + + # link the polymetadatum to the request + request.poly_metadata.push(pm) + end + end end end diff --git a/spec/fixtures/config/exports/exports.yml b/spec/fixtures/config/exports/exports.yml index 9465c99f3..0d385cfe4 100644 --- a/spec/fixtures/config/exports/exports.yml +++ b/spec/fixtures/config/exports/exports.yml @@ -15,9 +15,9 @@ duplex_seq_pcr_xp_concentrations_for_custom_pooling: targeted_nanoseq_al_lib_concentrations_for_customer: csv: targeted_nanoseq_al_lib_concentrations_for_customer plate_includes: wells.qc_results,wells.aliquots.sample.sample_metadata -targeted_nanoseq_pcr_xp_concentrations_for_custom_pooling: - csv: targeted_nanoseq_pcr_xp_concentrations_for_custom_pooling - plate_includes: wells.qc_results +targeted_nanoseq_pcr_xp_merged_file: + csv: targeted_nanoseq_pcr_xp_merged_file + plate_includes: wells.qc_results,wells.aliquots.sample.sample_metadata,wells.aliquots.request.poly_metadata ancestor_purpose: LTN AL Lib Dil hamilton_aggregate_cherrypick: csv: hamilton_aggregate_cherrypick diff --git a/spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file.csv b/spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file.csv new file mode 100644 index 000000000..d75deef0d --- /dev/null +++ b/spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file.csv @@ -0,0 +1,18 @@ +Plate Barcode,DN2T + +Well,Concentration (nM),Sanger Sample Id,Supplier Sample Name,Input amount available (fmol),Input amount desired,Sample volume,Diluent volume,PCR cycles,Hyb Panel +A1,0.686,1STDY1,test_1,17.150000000000002,0.0,5.0,25.0,14,My Panel +B1,0.623,1STDY2,test_2,15.575,50.0,5.0,25.0,14,My Panel +C1,,,,,,,1STDY, +D1,1.874,1STDY3,test_3,46.85,49.9,5.0,25.0,16,My Panel +E1,1.929,1STDY4,test_4,48.225,0.1,5.0,25.0,12,My Panel +F1,1.700,1STDY5,test_5,42.5,50.0,4.0,26.0,12,My Panel +H1,1.838,1STDY6,test_6,45.95,37.3,5.0,25.0,12,My Panel +A2,1.581,1STDY7,test_7,39.525,50.0,3.2,26.8.0,12,My Panel +B2,1.538,1STDY8,test_8,38.45,34.8,5.0,25.0,12,My Panel +C2,1.560,1STDY9,test_9,39.0,50.0,5.0,25.0,12,My Panel +D2,1.479,1STDY10,test_10,36.975,50.0,5.0,25.0,12,My Panel +E2,0.734,1STDY11,test_11,18.35,50.0,5.0,25.0,14,My Panel +F2,0.000,1STDY12,test_12,0.0,39.2,30.0,0.0,16,My Panel +G2,0.741,1STDY13,test_13,18.525,50.0,5.0,25.0,14,My Panel +H2,0.196,1STDY14,test_14,4.9,50.0,3.621,27.353,16,My Panel diff --git a/spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file_with_bom.csv b/spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file_with_bom.csv new file mode 100644 index 000000000..fb0aa3f54 --- /dev/null +++ b/spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file_with_bom.csv @@ -0,0 +1,18 @@ +Plate Barcode,DN2T + +Well,Concentration (nM),Sanger Sample Id,Supplier Sample Name,Input amount available (fmol),Input amount desired,Sample volume,Diluent volume,PCR cycles,Hyb Panel +A1,0.686,1STDY1,test_1,17.150000000000002,0.0,5.0,25.0,14,My Panel +B1,0.623,1STDY2,test_2,15.575,50.0,5.0,25.0,14,My Panel +C1,,,,,,,1STDY, +D1,1.874,1STDY3,test_3,46.85,49.9,5.0,25.0,16,My Panel +E1,1.929,1STDY4,test_4,48.225,0.1,5.0,25.0,12,My Panel +F1,1.700,1STDY5,test_5,42.5,50.0,4.0,26.0,12,My Panel +H1,1.838,1STDY6,test_6,45.95,37.3,5.0,25.0,12,My Panel +A2,1.581,1STDY7,test_7,39.525,50.0,3.2,26.8.0,12,My Panel +B2,1.538,1STDY8,test_8,38.45,34.8,5.0,25.0,12,My Panel +C2,1.560,1STDY9,test_9,39.0,50.0,5.0,25.0,12,My Panel +D2,1.479,1STDY10,test_10,36.975,50.0,5.0,25.0,12,My Panel +E2,0.734,1STDY11,test_11,18.35,50.0,5.0,25.0,14,My Panel +F2,0.000,1STDY12,test_12,0.0,39.2,30.0,0.0,16,My Panel +G2,0.741,1STDY13,test_13,18.525,50.0,5.0,25.0,14,My Panel +H2,0.196,1STDY14,test_14,4.9,50.0,3.621,27.353,16,My Panel diff --git a/spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file_with_invalid_wells.csv b/spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file_with_invalid_wells.csv new file mode 100644 index 000000000..6010c6025 --- /dev/null +++ b/spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file_with_invalid_wells.csv @@ -0,0 +1,20 @@ +Plate Barcode,DN2T + +Well,Concentration (nM),Sanger Sample Id,Supplier Sample Name,Input amount available (fmol),Input amount desired,Sample volume,Diluent volume,PCR cycles,Hyb Panel +A1,0.686,1STDY1,test_1,17.150000000000002,0.0,5.0,25.0,14,My Panel +B1,0.623,1STDY2,test_2,15.575,50.0,5.0,25.0,14,My Panel +C1,,,,,,,, +D1,1.874,1STDY3,test_3,46.85,49.9,5.0,25.0,12,My Panel +E1,1.929,1STDY4,test_4,48.225,0.1,5.0,25.0,12,My Panel +F1,1.700,1STDY5,test_5,42.5,50.0,4.0,26.0,12,My Panel +H1,1.838,1STDY6,test_6,45.95,37.3,5.0,25.0,12,My Panel +I1,1.838,1STDY6,test_6,45.95,37.3,5.0,25.0,12,My Panel +A2,1.581,1STDY7,test_7,39.525,50.0,3.2,26.8.0,12,My Panel +B2,1.538,1STDY8,test_8,38.45,34.8,5.0,25.0,12,My Panel +C2,1.560,1STDY9,test_9,39.0,50.0,5.0,25.0,12,My Panel +D2,1.479,1STDY10,test_10,36.975,50.0,5.0,25.0,12,My Panel +E2,0.734,1STDY11,test_11,18.35,50.0,5.0,25.0,14,My Panel +F2,0.000,1STDY12,test_12,0.0,39.2,30.0,0.0,16,My Panel +G2,0.741,1STDY13,test_13,18.525,50.0,5.0,25.0,14,My Panel +H2,0.196,1STDY14,test_14,4.9,50.0,3.621,27.353,16,My Panel + diff --git a/spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file_with_missing_values.csv b/spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file_with_missing_values.csv new file mode 100644 index 000000000..fe0c39d68 --- /dev/null +++ b/spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file_with_missing_values.csv @@ -0,0 +1,18 @@ +Plate Barcode,DN2T + +Well,Concentration (nM),Sanger Sample Id,Supplier Sample Name,Input amount available (fmol),Input amount desired,Sample volume,Diluent volume,PCR cycles,Hyb Panel +A1,0.686,1STDY1,test_1,17.150000000000002,,5.0,25.0,14,My Panel +B1,0.623,1STDY2,test_2,15.575,50.0,,25.0,14,My Panel +C1,,,,,,,, +D1,1.874,1STDY3,test_3,46.85,49.9,5.0,,12,My Panel +E1,1.929,1STDY4,test_4,48.225,0.1,5.0,25.0,,My Panel +F1,1.700,1STDY5,test_5,42.5,50.0,4.0,26.0,12,My Panel +H1,1.838,1STDY6,test_6,45.95,37.3,5.0,25.0,12,My Panel +A2,1.581,1STDY7,test_7,39.525,50.0,3.2,26.8.0,12, +B2,1.538,1STDY8,test_8,38.45,34.8,5.0,25.0,12,My Panel +C2,1.560,1STDY9,test_9,39.0,50.0,5.0,25.0,12,My Panel +D2,1.479,1STDY10,test_10,36.975,50.0,5.0,25.0,12,My Panel +E2,0.734,1STDY11,test_11,18.35,50.0,5.0,25.0,14,My Panel +F2,0.000,1STDY12,test_12,0.0,39.2,30.0,0.0,16,My Panel +G2,0.741,1STDY13,test_13,18.525,50.0,5.0,25.0,14,My Panel +H2,0.196,1STDY14,test_14,4.9,50.0,3.621,27.353,16,My Panel diff --git a/spec/models/labware_creators/pcr_cycles_binned_plate/csv_file_spec.rb b/spec/models/labware_creators/pcr_cycles_binned_plate/csv_file_for_duplex_seq_spec.rb similarity index 98% rename from spec/models/labware_creators/pcr_cycles_binned_plate/csv_file_spec.rb rename to spec/models/labware_creators/pcr_cycles_binned_plate/csv_file_for_duplex_seq_spec.rb index 05bce229c..47b61f1f6 100644 --- a/spec/models/labware_creators/pcr_cycles_binned_plate/csv_file_spec.rb +++ b/spec/models/labware_creators/pcr_cycles_binned_plate/csv_file_for_duplex_seq_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe LabwareCreators::PcrCyclesBinnedPlate::CsvFile, with: :uploader do +RSpec.describe LabwareCreators::PcrCyclesBinnedPlate::CsvFileForDuplexSeq, with: :uploader do let(:purpose_config) { create :duplex_seq_customer_csv_file_upload_purpose_config } let(:csv_file_config) { purpose_config.fetch(:csv_file_upload) } diff --git a/spec/models/labware_creators/pcr_cycles_binned_plate/csv_file_for_t_nano_seq_spec.rb b/spec/models/labware_creators/pcr_cycles_binned_plate/csv_file_for_t_nano_seq_spec.rb new file mode 100644 index 000000000..a7cf2dead --- /dev/null +++ b/spec/models/labware_creators/pcr_cycles_binned_plate/csv_file_for_t_nano_seq_spec.rb @@ -0,0 +1,313 @@ +# frozen_string_literal: true + +RSpec.describe LabwareCreators::PcrCyclesBinnedPlate::CsvFileForTNanoSeq, with: :uploader do + let(:purpose_config) { create :targeted_nano_seq_customer_csv_file_upload_purpose_config } + let(:csv_file_config) { purpose_config.fetch(:csv_file_upload) } + + subject { described_class.new(file, csv_file_config, 'DN2T') } + + context 'Valid files' do + let(:expected_well_details) do + { + 'A1' => { + 'concentration' => 0.686, + 'input_amount_available' => 17.150000000000002, + 'input_amount_desired' => 0.0, + 'sample_volume' => 5.0, + 'diluent_volume' => 25.0, + 'pcr_cycles' => 14, + 'hyb_panel' => 'My Panel' + }, + 'B1' => { + 'concentration' => 0.623, + 'input_amount_available' => 15.575, + 'input_amount_desired' => 50.0, + 'sample_volume' => 5.0, + 'diluent_volume' => 25.0, + 'pcr_cycles' => 14, + 'hyb_panel' => 'My Panel' + }, + 'D1' => { + 'concentration' => 1.874, + 'input_amount_available' => 46.85, + 'input_amount_desired' => 49.9, + 'sample_volume' => 5.0, + 'diluent_volume' => 25.0, + 'pcr_cycles' => 16, + 'hyb_panel' => 'My Panel' + }, + 'E1' => { + 'concentration' => 1.929, + 'input_amount_available' => 48.225, + 'input_amount_desired' => 0.1, + 'sample_volume' => 5.0, + 'diluent_volume' => 25.0, + 'pcr_cycles' => 12, + 'hyb_panel' => 'My Panel' + }, + 'F1' => { + 'concentration' => 1.700, + 'input_amount_available' => 42.5, + 'input_amount_desired' => 50.0, + 'sample_volume' => 4.0, + 'diluent_volume' => 26.0, + 'pcr_cycles' => 12, + 'hyb_panel' => 'My Panel' + }, + 'H1' => { + 'concentration' => 1.838, + 'input_amount_available' => 45.95, + 'input_amount_desired' => 37.3, + 'sample_volume' => 5.0, + 'diluent_volume' => 25.0, + 'pcr_cycles' => 12, + 'hyb_panel' => 'My Panel' + }, + 'A2' => { + 'concentration' => 1.581, + 'input_amount_available' => 39.525, + 'input_amount_desired' => 50.0, + 'sample_volume' => 3.2, + 'diluent_volume' => 26.8, + 'pcr_cycles' => 12, + 'hyb_panel' => 'My Panel' + }, + 'B2' => { + 'concentration' => 1.538, + 'input_amount_available' => 38.45, + 'input_amount_desired' => 34.8, + 'sample_volume' => 5.0, + 'diluent_volume' => 25.0, + 'pcr_cycles' => 12, + 'hyb_panel' => 'My Panel' + }, + 'C2' => { + 'concentration' => 1.560, + 'input_amount_available' => 39.0, + 'input_amount_desired' => 50.0, + 'sample_volume' => 5.0, + 'diluent_volume' => 25.0, + 'pcr_cycles' => 12, + 'hyb_panel' => 'My Panel' + }, + 'D2' => { + 'concentration' => 1.479, + 'input_amount_available' => 36.975, + 'input_amount_desired' => 50.0, + 'sample_volume' => 5.0, + 'diluent_volume' => 25.0, + 'pcr_cycles' => 12, + 'hyb_panel' => 'My Panel' + }, + 'E2' => { + 'concentration' => 0.734, + 'input_amount_available' => 18.35, + 'input_amount_desired' => 50.0, + 'sample_volume' => 5.0, + 'diluent_volume' => 25.0, + 'pcr_cycles' => 14, + 'hyb_panel' => 'My Panel' + }, + 'F2' => { + 'concentration' => 0.000, + 'input_amount_available' => 0.0, + 'input_amount_desired' => 39.2, + 'sample_volume' => 30.0, + 'diluent_volume' => 0.0, + 'pcr_cycles' => 16, + 'hyb_panel' => 'My Panel' + }, + 'G2' => { + 'concentration' => 0.741, + 'input_amount_available' => 18.525, + 'input_amount_desired' => 50.0, + 'sample_volume' => 5.0, + 'diluent_volume' => 25.0, + 'pcr_cycles' => 14, + 'hyb_panel' => 'My Panel' + }, + 'H2' => { + 'concentration' => 0.196, + 'input_amount_available' => 4.9, + 'input_amount_desired' => 50.0, + 'sample_volume' => 3.621, + 'diluent_volume' => 27.353, + 'pcr_cycles' => 16, + 'hyb_panel' => 'My Panel' + } + } + end + + context 'Without byte order markers' do + let(:file) do + fixture_file_upload( + 'spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file.csv', + 'sequencescape/qc_file' + ) + end + + describe '#valid?' do + it 'should be valid' do + expect(subject.valid?).to be true + end + end + + describe '#well_details' do + it 'should parse the expected well details' do + expect(subject.well_details).to eq expected_well_details + end + end + end + + context 'With byte order markers' do + let(:file) do + fixture_file_upload( + 'spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file_with_bom.csv', + 'sequencescape/qc_file' + ) + end + + describe '#valid?' do + it 'should be valid' do + expect(subject.valid?).to be true + end + end + + describe '#well_details' do + it 'should parse the expected well details' do + expect(subject.well_details).to eq expected_well_details + end + end + end + end + + context 'something that can not parse' do + let(:file) do + fixture_file_upload( + 'spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file.csv', + 'sequencescape/qc_file' + ) + end + + before { allow(CSV).to receive(:parse).and_raise('Really bad file') } + + describe '#valid?' do + it 'should be invalid' do + expect(subject.valid?).to be false + end + + it 'reports the errors' do + subject.valid? + expect(subject.errors.full_messages).to include('Could not read csv: Really bad file') + end + end + end + + context 'A file which has missing well values' do + let(:file) do + fixture_file_upload( + 'spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file_with_missing_values.csv', + 'sequencescape/qc_file' + ) + end + + describe '#valid?' do + it 'should be invalid' do + expect(subject.valid?).to be false + end + + let(:row4_error) do + 'Transfers input amount desired is empty or contains a value that is out of range (0.0 to 50.0), in row 4 [A1]' + end + + let(:row5_error) do + 'Transfers sample volume is empty or contains a value that is out of range (0.2 to 50.0), in row 5 [B1]' + end + + let(:row6_error) do + 'Transfers diluent volume is empty or contains a value that is out of range (0.0 to 50.0), in row 7 [D1]' + end + + let(:row7_error) do + 'Transfers pcr cycles is empty or contains a value that is out of range (1 to 20), in row 8 [E1]' + end + + let(:row11_error) { 'Transfers hyb panel is empty, in row 11 [A2]' } + + it 'reports the errors' do + subject.valid? + expect(subject.errors.full_messages).to include(row4_error) + expect(subject.errors.full_messages).to include(row5_error) + expect(subject.errors.full_messages).to include(row6_error) + expect(subject.errors.full_messages).to include(row7_error) + expect(subject.errors.full_messages).to include(row11_error) + end + end + end + + context 'An invalid file' do + let(:file) { fixture_file_upload('spec/fixtures/files/test_file.txt', 'sequencescape/qc_file') } + + describe '#valid?' do + it 'should be invalid' do + expect(subject.valid?).to be false + end + + it 'reports the errors' do + subject.valid? + expect(subject.errors.full_messages).to include( + 'Plate barcode header row barcode lbl index could not be found in: \'This is an example file\'' + ) + expect(subject.errors.full_messages).to include( + 'Plate barcode header row plate barcode could not be found in: \'This is an example file\'' + ) + expect(subject.errors.full_messages).to include('Well details header row can\'t be blank') + end + end + end + + context 'An unrecognised well' do + let(:file) do + fixture_file_upload( + 'spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file_with_invalid_wells.csv', + 'sequencescape/qc_file' + ) + end + + describe '#valid?' do + it 'should be invalid' do + expect(subject.valid?).to be false + end + + it 'reports the errors' do + subject.valid? + expect(subject.errors.full_messages).to include('Transfers well contains an invalid well name: row 11 [I1]') + end + end + end + + context 'A parent plate barcode that does not match' do + subject { described_class.new(file, csv_file_config, 'DN1S') } + + let(:file) do + fixture_file_upload( + 'spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file.csv', + 'sequencescape/qc_file' + ) + end + + describe '#valid?' do + it 'should be invalid' do + expect(subject.valid?).to be false + end + + it 'reports the errors' do + subject.valid? + expect(subject.errors.full_messages).to include( + 'Plate barcode header row plate barcode The plate barcode in the file (DN2T) does not match the ' \ + 'barcode of the plate being uploaded to (DN1S), please check you have the correct file.' + ) + end + end + end +end diff --git a/spec/models/labware_creators/pcr_cycles_binned_plate_spec.rb b/spec/models/labware_creators/pcr_cycles_binned_plate_for_duplex_seq_spec.rb similarity index 64% rename from spec/models/labware_creators/pcr_cycles_binned_plate_spec.rb rename to spec/models/labware_creators/pcr_cycles_binned_plate_for_duplex_seq_spec.rb index 622608c94..4861a9273 100644 --- a/spec/models/labware_creators/pcr_cycles_binned_plate_spec.rb +++ b/spec/models/labware_creators/pcr_cycles_binned_plate_for_duplex_seq_spec.rb @@ -4,10 +4,10 @@ require 'labware_creators/base' require_relative 'shared_examples' -RSpec.describe LabwareCreators::PcrCyclesBinnedPlate, with: :uploader do +RSpec.describe LabwareCreators::PcrCyclesBinnedPlateForDuplexSeq, with: :uploader do it_behaves_like 'it only allows creation from plates' - subject { LabwareCreators::PcrCyclesBinnedPlate.new(api, form_attributes) } + subject { LabwareCreators::PcrCyclesBinnedPlateForDuplexSeq.new(api, form_attributes) } it 'should have a custom page' do expect(described_class.page).to eq 'pcr_cycles_binned_plate' @@ -16,7 +16,7 @@ let(:parent_uuid) { 'example-plate-uuid' } let(:plate_size) { 96 } - let(:well_a1) do + let(:parent_well_a1) do create( :v2_well, position: { @@ -27,7 +27,7 @@ outer_request: nil ) end - let(:well_b1) do + let(:parent_well_b1) do create( :v2_well, position: { @@ -38,7 +38,7 @@ outer_request: nil ) end - let(:well_d1) do + let(:parent_well_d1) do create( :v2_well, position: { @@ -49,7 +49,7 @@ outer_request: nil ) end - let(:well_e1) do + let(:parent_well_e1) do create( :v2_well, position: { @@ -60,7 +60,7 @@ outer_request: nil ) end - let(:well_f1) do + let(:parent_well_f1) do create( :v2_well, position: { @@ -71,7 +71,7 @@ outer_request: nil ) end - let(:well_h1) do + let(:parent_well_h1) do create( :v2_well, position: { @@ -82,7 +82,7 @@ outer_request: nil ) end - let(:well_a2) do + let(:parent_well_a2) do create( :v2_well, position: { @@ -93,7 +93,7 @@ outer_request: nil ) end - let(:well_b2) do + let(:parent_well_b2) do create( :v2_well, position: { @@ -104,7 +104,7 @@ outer_request: nil ) end - let(:well_c2) do + let(:parent_well_c2) do create( :v2_well, position: { @@ -115,7 +115,7 @@ outer_request: nil ) end - let(:well_d2) do + let(:parent_well_d2) do create( :v2_well, position: { @@ -126,7 +126,7 @@ outer_request: nil ) end - let(:well_e2) do + let(:parent_well_e2) do create( :v2_well, position: { @@ -137,7 +137,7 @@ outer_request: nil ) end - let(:well_f2) do + let(:parent_well_f2) do create( :v2_well, position: { @@ -148,7 +148,7 @@ outer_request: nil ) end - let(:well_g2) do + let(:parent_well_g2) do create( :v2_well, position: { @@ -159,7 +159,7 @@ outer_request: nil ) end - let(:well_h2) do + let(:parent_well_h2) do create( :v2_well, position: { @@ -177,28 +177,67 @@ barcode_number: '2', size: plate_size, wells: [ - well_a1, - well_b1, - well_d1, - well_e1, - well_f1, - well_h1, - well_a2, - well_b2, - well_c2, - well_d2, - well_e2, - well_f2, - well_g2, - well_h2 + parent_well_a1, + parent_well_b1, + parent_well_d1, + parent_well_e1, + parent_well_f1, + parent_well_h1, + parent_well_a2, + parent_well_b2, + parent_well_c2, + parent_well_d2, + parent_well_e2, + parent_well_f2, + parent_well_g2, + parent_well_h2 ], outer_requests: requests end let(:parent_plate_v1) { json :plate, uuid: parent_uuid, stock_plate_barcode: 2, qc_files_actions: %w[read create] } + # Create child wells in order of the requests they originated from. + # Which is to do with how the binning algorithm lays them out based on the value of PCR cycles. + let(:child_well_A2) { create(:v2_well, location: 'A2', position: { 'name' => 'A2' }, outer_request: requests[0]) } + let(:child_well_B2) { create(:v2_well, location: 'B2', position: { 'name' => 'B2' }, outer_request: requests[1]) } + let(:child_well_A1) { create(:v2_well, location: 'A1', position: { 'name' => 'A1' }, outer_request: requests[2]) } + let(:child_well_A3) { create(:v2_well, location: 'A3', position: { 'name' => 'A3' }, outer_request: requests[3]) } + let(:child_well_B3) { create(:v2_well, location: 'B3', position: { 'name' => 'B3' }, outer_request: requests[4]) } + let(:child_well_C3) { create(:v2_well, location: 'C3', position: { 'name' => 'C3' }, outer_request: requests[5]) } + let(:child_well_D3) { create(:v2_well, location: 'D3', position: { 'name' => 'D3' }, outer_request: requests[6]) } + let(:child_well_E3) { create(:v2_well, location: 'E3', position: { 'name' => 'E3' }, outer_request: requests[7]) } + let(:child_well_F3) { create(:v2_well, location: 'F3', position: { 'name' => 'F3' }, outer_request: requests[8]) } + let(:child_well_G3) { create(:v2_well, location: 'G3', position: { 'name' => 'G3' }, outer_request: requests[9]) } + let(:child_well_C2) { create(:v2_well, location: 'C2', position: { 'name' => 'C2' }, outer_request: requests[10]) } + let(:child_well_B1) { create(:v2_well, location: 'B1', position: { 'name' => 'B1' }, outer_request: requests[11]) } + let(:child_well_D2) { create(:v2_well, location: 'D2', position: { 'name' => 'D2' }, outer_request: requests[12]) } + let(:child_well_C1) { create(:v2_well, location: 'C1', position: { 'name' => 'C1' }, outer_request: requests[13]) } + let(:child_plate) do - create :v2_plate, uuid: 'child-uuid', barcode_number: '3', size: plate_size, outer_requests: requests + # Wells listed in the order here to match the order of the list of original library requests, + # i.e. the rearranged order after binning. Wells will be laid out by location so this has no + # effect on the actual layout of the plate. + create :v2_plate, + uuid: 'child-uuid', + barcode_number: '3', + size: plate_size, + wells: [ + child_well_A2, + child_well_B2, + child_well_A1, + child_well_A3, + child_well_B3, + child_well_C3, + child_well_D3, + child_well_E3, + child_well_F3, + child_well_G3, + child_well_C2, + child_well_B1, + child_well_D2, + child_well_C1 + ] end let(:library_type_name) { 'Test Library Type' } @@ -220,7 +259,7 @@ let(:form_attributes) { { purpose_uuid: child_purpose_uuid, parent_uuid: parent_uuid } } it 'can be created' do - expect(subject).to be_a LabwareCreators::PcrCyclesBinnedPlate + expect(subject).to be_a LabwareCreators::PcrCyclesBinnedPlateForDuplexSeq end end @@ -310,86 +349,86 @@ [ { 'volume' => '5.0', - 'source_asset' => well_a1.uuid, - 'target_asset' => '3-well-A2', + 'source_asset' => parent_well_a1.uuid, + 'target_asset' => child_well_A2.uuid, 'outer_request' => requests[0].uuid }, { 'volume' => '5.0', - 'source_asset' => well_b1.uuid, - 'target_asset' => '3-well-B2', + 'source_asset' => parent_well_b1.uuid, + 'target_asset' => child_well_B2.uuid, 'outer_request' => requests[1].uuid }, { 'volume' => '5.0', - 'source_asset' => well_d1.uuid, - 'target_asset' => '3-well-A1', + 'source_asset' => parent_well_d1.uuid, + 'target_asset' => child_well_A1.uuid, 'outer_request' => requests[2].uuid }, { 'volume' => '5.0', - 'source_asset' => well_e1.uuid, - 'target_asset' => '3-well-A3', + 'source_asset' => parent_well_e1.uuid, + 'target_asset' => child_well_A3.uuid, 'outer_request' => requests[3].uuid }, { 'volume' => '4.0', - 'source_asset' => well_f1.uuid, - 'target_asset' => '3-well-B3', + 'source_asset' => parent_well_f1.uuid, + 'target_asset' => child_well_B3.uuid, 'outer_request' => requests[4].uuid }, { 'volume' => '5.0', - 'source_asset' => well_h1.uuid, - 'target_asset' => '3-well-C3', + 'source_asset' => parent_well_h1.uuid, + 'target_asset' => child_well_C3.uuid, 'outer_request' => requests[5].uuid }, { 'volume' => '3.2', - 'source_asset' => well_a2.uuid, - 'target_asset' => '3-well-D3', + 'source_asset' => parent_well_a2.uuid, + 'target_asset' => child_well_D3.uuid, 'outer_request' => requests[6].uuid }, { 'volume' => '5.0', - 'source_asset' => well_b2.uuid, - 'target_asset' => '3-well-E3', + 'source_asset' => parent_well_b2.uuid, + 'target_asset' => child_well_E3.uuid, 'outer_request' => requests[7].uuid }, { 'volume' => '5.0', - 'source_asset' => well_c2.uuid, - 'target_asset' => '3-well-F3', + 'source_asset' => parent_well_c2.uuid, + 'target_asset' => child_well_F3.uuid, 'outer_request' => requests[8].uuid }, { 'volume' => '5.0', - 'source_asset' => well_d2.uuid, - 'target_asset' => '3-well-G3', + 'source_asset' => parent_well_d2.uuid, + 'target_asset' => child_well_G3.uuid, 'outer_request' => requests[9].uuid }, { 'volume' => '5.0', - 'source_asset' => well_e2.uuid, - 'target_asset' => '3-well-C2', + 'source_asset' => parent_well_e2.uuid, + 'target_asset' => child_well_C2.uuid, 'outer_request' => requests[10].uuid }, { 'volume' => '30.0', - 'source_asset' => well_f2.uuid, - 'target_asset' => '3-well-B1', + 'source_asset' => parent_well_f2.uuid, + 'target_asset' => child_well_B1.uuid, 'outer_request' => requests[11].uuid }, { 'volume' => '5.0', - 'source_asset' => well_g2.uuid, - 'target_asset' => '3-well-D2', + 'source_asset' => parent_well_g2.uuid, + 'target_asset' => child_well_D2.uuid, 'outer_request' => requests[12].uuid }, { 'volume' => '3.621', - 'source_asset' => well_h2.uuid, - 'target_asset' => '3-well-C1', + 'source_asset' => parent_well_h2.uuid, + 'target_asset' => child_well_C1.uuid, 'outer_request' => requests[13].uuid } ] diff --git a/spec/models/labware_creators/pcr_cycles_binned_plate_for_t_nano_seq_spec.rb b/spec/models/labware_creators/pcr_cycles_binned_plate_for_t_nano_seq_spec.rb new file mode 100644 index 000000000..4b604dc5b --- /dev/null +++ b/spec/models/labware_creators/pcr_cycles_binned_plate_for_t_nano_seq_spec.rb @@ -0,0 +1,483 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'labware_creators/base' +require_relative 'shared_examples' + +RSpec.describe LabwareCreators::PcrCyclesBinnedPlateForTNanoSeq, with: :uploader do + it_behaves_like 'it only allows creation from plates' + + subject { LabwareCreators::PcrCyclesBinnedPlateForTNanoSeq.new(api, form_attributes) } + + it 'should have a custom page' do + expect(described_class.page).to eq 'pcr_cycles_binned_plate_for_t_nano_seq' + end + + let(:parent_uuid) { 'parent-plate-uuid' } + let(:plate_size) { 96 } + + let(:parent_well_a1) do + create( + :v2_well, + location: 'A1', + position: { + 'name' => 'A1' + }, + qc_results: create_list(:qc_result_concentration, 1, value: 1.0), + requests_as_source: [requests[0]], + outer_request: nil + ) + end + let(:parent_well_b1) do + create( + :v2_well, + location: 'B1', + position: { + 'name' => 'B1' + }, + qc_results: create_list(:qc_result_concentration, 1, value: 1.0), + requests_as_source: [requests[1]], + outer_request: nil + ) + end + let(:parent_well_d1) do + create( + :v2_well, + location: 'D1', + position: { + 'name' => 'D1' + }, + qc_results: create_list(:qc_result_concentration, 1, value: 1.0), + requests_as_source: [requests[2]], + outer_request: nil + ) + end + let(:parent_well_e1) do + create( + :v2_well, + location: 'E1', + position: { + 'name' => 'E1' + }, + qc_results: create_list(:qc_result_concentration, 1, value: 1.0), + requests_as_source: [requests[3]], + outer_request: nil + ) + end + let(:parent_well_f1) do + create( + :v2_well, + location: 'F1', + position: { + 'name' => 'F1' + }, + qc_results: create_list(:qc_result_concentration, 1, value: 1.0), + requests_as_source: [requests[4]], + outer_request: nil + ) + end + let(:parent_well_h1) do + create( + :v2_well, + location: 'H1', + position: { + 'name' => 'H1' + }, + qc_results: create_list(:qc_result_concentration, 1, value: 1.0), + requests_as_source: [requests[5]], + outer_request: nil + ) + end + let(:parent_well_a2) do + create( + :v2_well, + location: 'A2', + position: { + 'name' => 'A2' + }, + qc_results: create_list(:qc_result_concentration, 1, value: 1.0), + requests_as_source: [requests[6]], + outer_request: nil + ) + end + let(:parent_well_b2) do + create( + :v2_well, + location: 'B2', + position: { + 'name' => 'B2' + }, + qc_results: create_list(:qc_result_concentration, 1, value: 1.0), + requests_as_source: [requests[7]], + outer_request: nil + ) + end + let(:parent_well_c2) do + create( + :v2_well, + location: 'C2', + position: { + 'name' => 'C2' + }, + qc_results: create_list(:qc_result_concentration, 1, value: 1.0), + requests_as_source: [requests[8]], + outer_request: nil + ) + end + let(:parent_well_d2) do + create( + :v2_well, + location: 'D2', + position: { + 'name' => 'D2' + }, + qc_results: create_list(:qc_result_concentration, 1, value: 1.0), + requests_as_source: [requests[9]], + outer_request: nil + ) + end + let(:parent_well_e2) do + create( + :v2_well, + location: 'E2', + position: { + 'name' => 'E2' + }, + qc_results: create_list(:qc_result_concentration, 1, value: 1.0), + requests_as_source: [requests[10]], + outer_request: nil + ) + end + let(:parent_well_f2) do + create( + :v2_well, + location: 'F2', + position: { + 'name' => 'F2' + }, + qc_results: create_list(:qc_result_concentration, 1, value: 1.0), + requests_as_source: [requests[11]], + outer_request: nil + ) + end + let(:parent_well_g2) do + create( + :v2_well, + location: 'G2', + position: { + 'name' => 'G2' + }, + qc_results: create_list(:qc_result_concentration, 1, value: 1.0), + requests_as_source: [requests[12]], + outer_request: nil + ) + end + let(:parent_well_h2) do + create( + :v2_well, + location: 'H2', + position: { + 'name' => 'H2' + }, + qc_results: create_list(:qc_result_concentration, 1, value: 1.0), + requests_as_source: [requests[13]], + outer_request: nil + ) + end + + let(:parent_plate) do + create :v2_plate, + uuid: parent_uuid, + barcode_number: '2', + size: plate_size, + wells: [ + parent_well_a1, + parent_well_b1, + parent_well_d1, + parent_well_e1, + parent_well_f1, + parent_well_h1, + parent_well_a2, + parent_well_b2, + parent_well_c2, + parent_well_d2, + parent_well_e2, + parent_well_f2, + parent_well_g2, + parent_well_h2 + ], + outer_requests: requests + end + + let(:parent_plate_v1) { json :plate, uuid: parent_uuid, stock_plate_barcode: 2, qc_files_actions: %w[read create] } + + # Create child wells in order of the requests they originated from. + # Which is to do with how the binning algorithm lays them out based on the value of PCR cycles. + let(:child_well_A2) { create(:v2_well, location: 'A2', position: { 'name' => 'A2' }, outer_request: requests[0]) } + let(:child_well_B2) { create(:v2_well, location: 'B2', position: { 'name' => 'B2' }, outer_request: requests[1]) } + let(:child_well_A1) { create(:v2_well, location: 'A1', position: { 'name' => 'A1' }, outer_request: requests[2]) } + let(:child_well_A3) { create(:v2_well, location: 'A3', position: { 'name' => 'A3' }, outer_request: requests[3]) } + let(:child_well_B3) { create(:v2_well, location: 'B3', position: { 'name' => 'B3' }, outer_request: requests[4]) } + let(:child_well_C3) { create(:v2_well, location: 'C3', position: { 'name' => 'C3' }, outer_request: requests[5]) } + let(:child_well_D3) { create(:v2_well, location: 'D3', position: { 'name' => 'D3' }, outer_request: requests[6]) } + let(:child_well_E3) { create(:v2_well, location: 'E3', position: { 'name' => 'E3' }, outer_request: requests[7]) } + let(:child_well_F3) { create(:v2_well, location: 'F3', position: { 'name' => 'F3' }, outer_request: requests[8]) } + let(:child_well_G3) { create(:v2_well, location: 'G3', position: { 'name' => 'G3' }, outer_request: requests[9]) } + let(:child_well_C2) { create(:v2_well, location: 'C2', position: { 'name' => 'C2' }, outer_request: requests[10]) } + let(:child_well_B1) { create(:v2_well, location: 'B1', position: { 'name' => 'B1' }, outer_request: requests[11]) } + let(:child_well_D2) { create(:v2_well, location: 'D2', position: { 'name' => 'D2' }, outer_request: requests[12]) } + let(:child_well_C1) { create(:v2_well, location: 'C1', position: { 'name' => 'C1' }, outer_request: requests[13]) } + + let(:child_plate) do + # Wells listed in the order here to match the order of the list of original library requests, + # i.e. the rearranged order after binning. Wells will be laid out by location so this has no + # effect on the actual layout of the plate. + create :v2_plate, + uuid: 'child-uuid', + barcode_number: '3', + size: plate_size, + wells: [ + child_well_A2, + child_well_B2, + child_well_A1, + child_well_A3, + child_well_B3, + child_well_C3, + child_well_D3, + child_well_E3, + child_well_F3, + child_well_G3, + child_well_C2, + child_well_B1, + child_well_D2, + child_well_C1 + ] + end + + let(:library_type_name) { 'Test Library Type' } + + let(:requests) do + Array.new(14) do |i| + create :library_request, state: 'pending', uuid: "request-#{i}", library_type: library_type_name + end + end + + let(:child_purpose_uuid) { 'child-purpose' } + let(:child_purpose_name) { 'Child Purpose' } + + let(:user_uuid) { 'user-uuid' } + + context 'on new' do + has_a_working_api + + let(:form_attributes) { { purpose_uuid: child_purpose_uuid, parent_uuid: parent_uuid } } + + it 'can be created' do + expect(subject).to be_a LabwareCreators::PcrCyclesBinnedPlateForTNanoSeq + end + end + + context '#save' do + has_a_working_api + + let(:file_content) do + content = file.read + file.rewind + content + end + + let(:form_attributes) do + { purpose_uuid: child_purpose_uuid, parent_uuid: parent_uuid, user_uuid: user_uuid, file: file } + end + + let(:stub_upload_file_creation) do + stub_request(:post, api_url_for(parent_uuid, 'qc_files')) + .with( + body: file_content, + headers: { + 'Content-Type' => 'sequencescape/qc_file', + 'Content-Disposition' => 'form-data; filename="targeted_nano_seq_customer_file.csv"' + } + ) + .to_return( + status: 201, + body: json(:qc_file, filename: 'targeted_nano_seq_dil_file.csv'), + headers: { + 'content-type' => 'application/json' + } + ) + end + + let(:stub_parent_request) { stub_api_get(parent_uuid, body: parent_plate_v1) } + + before do + stub_parent_request + + create :targeted_nano_seq_customer_csv_file_upload_purpose_config, + uuid: child_purpose_uuid, + name: child_purpose_name, + library_type_name: library_type_name + + stub_v2_plate( + parent_plate, + stub_search: false, + custom_includes: + 'wells.aliquots,wells.qc_results,wells.requests_as_source.request_type,wells.aliquots.request.request_type' + ) + + stub_v2_plate(child_plate, stub_search: false) + + stub_v2_plate(child_plate, stub_search: false, custom_includes: 'wells.aliquots') + + stub_upload_file_creation + end + + context 'with an invalid file' do + let(:file) { fixture_file_upload('spec/fixtures/files/test_file.txt', 'sequencescape/qc_file') } + + it 'is false' do + expect(subject.save).to be false + end + end + + context 'binning' do + let(:file) do + fixture_file_upload( + 'spec/fixtures/files/targeted_nano_seq/targeted_nano_seq_dil_file.csv', + 'sequencescape/qc_file' + ) + end + + let!(:plate_creation_request) do + stub_api_post( + 'plate_creations', + payload: { + plate_creation: { + parent: parent_uuid, + child_purpose: child_purpose_uuid, + user: user_uuid + } + }, + body: json(:plate_creation) + ) + end + + let!(:api_v2_post) { stub_api_v2_post('Well') } + + let!(:api_v2_post) { stub_api_v2_save('PolyMetadatum') } + + let(:transfer_requests) do + [ + { + 'volume' => '5.0', + 'source_asset' => parent_well_a1.uuid, + 'target_asset' => child_well_A2.uuid, + 'outer_request' => requests[0].uuid + }, + { + 'volume' => '5.0', + 'source_asset' => parent_well_b1.uuid, + 'target_asset' => child_well_B2.uuid, + 'outer_request' => requests[1].uuid + }, + { + 'volume' => '5.0', + 'source_asset' => parent_well_d1.uuid, + 'target_asset' => child_well_A1.uuid, + 'outer_request' => requests[2].uuid + }, + { + 'volume' => '5.0', + 'source_asset' => parent_well_e1.uuid, + 'target_asset' => child_well_A3.uuid, + 'outer_request' => requests[3].uuid + }, + { + 'volume' => '4.0', + 'source_asset' => parent_well_f1.uuid, + 'target_asset' => child_well_B3.uuid, + 'outer_request' => requests[4].uuid + }, + { + 'volume' => '5.0', + 'source_asset' => parent_well_h1.uuid, + 'target_asset' => child_well_C3.uuid, + 'outer_request' => requests[5].uuid + }, + { + 'volume' => '3.2', + 'source_asset' => parent_well_a2.uuid, + 'target_asset' => child_well_D3.uuid, + 'outer_request' => requests[6].uuid + }, + { + 'volume' => '5.0', + 'source_asset' => parent_well_b2.uuid, + 'target_asset' => child_well_E3.uuid, + 'outer_request' => requests[7].uuid + }, + { + 'volume' => '5.0', + 'source_asset' => parent_well_c2.uuid, + 'target_asset' => child_well_F3.uuid, + 'outer_request' => requests[8].uuid + }, + { + 'volume' => '5.0', + 'source_asset' => parent_well_d2.uuid, + 'target_asset' => child_well_G3.uuid, + 'outer_request' => requests[9].uuid + }, + { + 'volume' => '5.0', + 'source_asset' => parent_well_e2.uuid, + 'target_asset' => child_well_C2.uuid, + 'outer_request' => requests[10].uuid + }, + { + 'volume' => '30.0', + 'source_asset' => parent_well_f2.uuid, + 'target_asset' => child_well_B1.uuid, + 'outer_request' => requests[11].uuid + }, + { + 'volume' => '5.0', + 'source_asset' => parent_well_g2.uuid, + 'target_asset' => child_well_D2.uuid, + 'outer_request' => requests[12].uuid + }, + { + 'volume' => '3.621', + 'source_asset' => parent_well_h2.uuid, + 'target_asset' => child_well_C1.uuid, + 'outer_request' => requests[13].uuid + } + ] + end + + let!(:transfer_creation_request) do + stub_api_post( + 'transfer_request_collections', + payload: { + transfer_request_collection: { + user: user_uuid, + transfer_requests: transfer_requests + } + }, + body: '{}' + ) + end + + it 'makes the expected method calls when creating the child plate' do + # NB. because we're mocking the API call for the save of the request metadata we cannot + # check the metadata values on the requests, only that the method was triggered. + # Our child plate has 14 wells with 14 requests, so we expect the method to create metadata + # on the requests to be called 14 times. + expect(subject).to receive(:create_request_metadata).exactly(14).times + expect(subject.save!).to eq true + expect(plate_creation_request).to have_been_made + expect(transfer_creation_request).to have_been_made + end + end + end +end diff --git a/spec/models/presenters/pcr_cycles_binned_plate_using_request_metadata_presenter_spec.rb b/spec/models/presenters/pcr_cycles_binned_plate_using_request_metadata_presenter_spec.rb new file mode 100644 index 000000000..09757b85f --- /dev/null +++ b/spec/models/presenters/pcr_cycles_binned_plate_using_request_metadata_presenter_spec.rb @@ -0,0 +1,156 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'presenters/pcr_cycles_binned_plate_using_request_metadata_presenter' +require_relative 'shared_labware_presenter_examples' + +RSpec.describe Presenters::PcrCyclesBinnedPlateUsingRequestMetadataPresenter do + has_a_working_api + + let(:purpose_name) { 'Limber example purpose' } + let(:title) { purpose_name } + let(:state) { 'pending' } + let(:summary_tab) do + [ + %w[Barcode DN1S], + ['Number of wells', '4/96'], + ['Plate type', purpose_name], + ['Current plate state', state], + ['Input plate barcode', 'DN2T'], + ['PCR Cycles', '10'], + ['Created on', '2019-06-10'] + ] + end + let(:sidebar_partial) { 'default' } + + # Create binning for 4 wells in 3 bins: + # 1 2 3 + # A * * * + # B * + + # well A1 + let(:well_a1_metadata) { build :poly_metadatum, key: 'pcr_cycles', value: '16' } + + let(:well_a1_request) { create :library_request_with_poly_metadata, poly_metadata: [well_a1_metadata] } + let(:well_a1) do + create( + :v2_well, + position: { + 'name' => 'A1' + }, + qc_results: create_list(:qc_result_concentration, 1, value: '0.6'), + outer_request: well_a1_request + ) + end + + # well A2 + let(:well_a2_metadata) { build :poly_metadatum, key: 'pcr_cycles', value: '14' } + + let(:well_a2_request) { create :library_request_with_poly_metadata, poly_metadata: [well_a2_metadata] } + let(:well_a2) do + create( + :v2_well, + position: { + 'name' => 'A2' + }, + qc_results: create_list(:qc_result_concentration, 1, value: '10.0'), + outer_request: well_a2_request + ) + end + + # well B2 + let(:well_b2_metadata) { build :poly_metadatum, key: 'pcr_cycles', value: '14' } + + let(:well_b2_request) { create :library_request_with_poly_metadata, poly_metadata: [well_b2_metadata] } + let(:well_b2) do + create( + :v2_well, + position: { + 'name' => 'B2' + }, + qc_results: create_list(:qc_result_concentration, 1, value: '12.0'), + outer_request: well_b2_request + ) + end + + # well A3 + let(:well_a3_metadata) { build :poly_metadatum, key: 'pcr_cycles', value: '12' } + + let(:well_a3_request) { create :library_request_with_poly_metadata, poly_metadata: [well_a3_metadata] } + let(:well_a3) do + create( + :v2_well, + position: { + 'name' => 'A3' + }, + qc_results: create_list(:qc_result_concentration, 1, value: '20.0'), + outer_request: well_a3_request + ) + end + + let(:labware) do + build :v2_plate, + purpose_name: purpose_name, + state: state, + barcode_number: 1, + pool_sizes: [], + wells: [well_a1, well_a2, well_b2, well_a3], + # outer_requests: requests, + created_at: '2019-06-10 12:00:00 +0100' + end + + # let(:requests) { Array.new(4) { |i| create :library_request, state: 'started', uuid: "request-#{i}" } } + + let(:warnings) { {} } + let(:label_class) { 'Labels::PlateLabel' } + + before do + stub_v2_plate( + labware, + stub_search: false, + custom_includes: 'wells.aliquots,wells.qc_results,wells.aliquots.request.poly_metadata' + ) + end + + subject(:presenter) { Presenters::PcrCyclesBinnedPlateUsingRequestMetadataPresenter.new(api: api, labware: labware) } + + context 'when binning' do + it_behaves_like 'a labware presenter' + + context 'pcr cycles binned plate display' do + it 'should create a key for the bins that will be displayed' do + # NB. contains min/max because just using bins template, but fields not needed in presentation + expected_bins_key = [ + { 'colour' => 1, 'pcr_cycles' => 16 }, + { 'colour' => 2, 'pcr_cycles' => 14 }, + { 'colour' => 3, 'pcr_cycles' => 12 } + ] + + expect(presenter.bins_key).to eq(expected_bins_key) + end + + it 'should create bin details which will be used to colour and annotate the well aliquots' do + expected_bin_details = { + 'A1' => { + 'colour' => 1, + 'pcr_cycles' => 16 + }, + 'A2' => { + 'colour' => 2, + 'pcr_cycles' => 14 + }, + 'A3' => { + 'colour' => 3, + 'pcr_cycles' => 12 + }, + 'B2' => { + 'colour' => 2, + 'pcr_cycles' => 14 + } + } + + expect(presenter.bin_details).to eq(expected_bin_details) + end + end + end +end diff --git a/spec/models/presenters/pcr_cycles_binned_plate_presenter_spec.rb b/spec/models/presenters/pcr_cycles_binned_plate_using_well_metadata_presenter_spec.rb similarity index 92% rename from spec/models/presenters/pcr_cycles_binned_plate_presenter_spec.rb rename to spec/models/presenters/pcr_cycles_binned_plate_using_well_metadata_presenter_spec.rb index d86803cb0..b6e5554bb 100644 --- a/spec/models/presenters/pcr_cycles_binned_plate_presenter_spec.rb +++ b/spec/models/presenters/pcr_cycles_binned_plate_using_well_metadata_presenter_spec.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true require 'rails_helper' -require 'presenters/pcr_cycles_binned_plate_presenter' +require 'presenters/pcr_cycles_binned_plate_using_well_metadata_presenter' require_relative 'shared_labware_presenter_examples' -RSpec.describe Presenters::PcrCyclesBinnedPlatePresenter do +RSpec.describe Presenters::PcrCyclesBinnedPlateUsingWellMetadataPresenter do has_a_working_api let(:purpose_name) { 'Limber example purpose' } @@ -86,7 +86,7 @@ before { stub_v2_plate(labware, stub_search: false, custom_includes: 'wells.aliquots,wells.qc_results') } - subject(:presenter) { Presenters::PcrCyclesBinnedPlatePresenter.new(api: api, labware: labware) } + subject(:presenter) { Presenters::PcrCyclesBinnedPlateUsingWellMetadataPresenter.new(api: api, labware: labware) } context 'when binning' do it_behaves_like 'a labware presenter' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 844d29b03..7fbb54a60 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -170,9 +170,9 @@ .parse_file(Rails.root.join('config/label_templates.yml')) .to_ruby .tap do |label_templates| - Settings.default_pmb_templates = label_templates['default_pmb_templates'] - Settings.default_sprint_templates = label_templates['default_sprint_templates'] - Settings.default_printer_type_names = label_templates['default_printer_type_names'] + Settings.default_pmb_templates = label_templates['defaults_by_printer_type']['pmb_templates'] + Settings.default_sprint_templates = label_templates['defaults_by_printer_type']['sprint_templates'] + Settings.default_printer_type_names = label_templates['defaults_by_printer_type']['printer_type_names'] end end diff --git a/spec/views/exports/targeted_nanoseq_al_lib_concentrations_for_customer.csv.erb_spec.rb b/spec/views/exports/targeted_nanoseq_al_lib_concentrations_for_customer.csv.erb_spec.rb index facb5a6ff..5fad93dca 100644 --- a/spec/views/exports/targeted_nanoseq_al_lib_concentrations_for_customer.csv.erb_spec.rb +++ b/spec/views/exports/targeted_nanoseq_al_lib_concentrations_for_customer.csv.erb_spec.rb @@ -35,26 +35,10 @@ 'Input amount desired', 'Sample volume', 'Diluent volume', - 'PCR cycles', - 'Submit for sequencing (Y/N)?', - 'Sub-Pool', - 'Coverage' + 'Hyb Panel' ], - [ - 'A1', - '1.5', - well_a1_sanger_sample_id, - well_a1_supplier_name, - (1.5 * 25).to_s, - nil, - nil, - nil, - nil, - nil, - nil, - nil - ], - ['B1', '1.5', well_b1_sanger_sample_id, well_b1_supplier_name, (1.5 * 25).to_s, nil, nil, nil, nil, nil, nil, nil] + ['A1', '1.5', well_a1_sanger_sample_id, well_a1_supplier_name, (1.5 * 25).to_s, nil, nil, nil, nil], + ['B1', '1.5', well_b1_sanger_sample_id, well_b1_supplier_name, (1.5 * 25).to_s, nil, nil, nil, nil] ] end diff --git a/spec/views/exports/targeted_nanoseq_pcr_xp_concentrations_for_custom_pooling.csv.erb_spec.rb b/spec/views/exports/targeted_nanoseq_pcr_xp_concentrations_for_custom_pooling.csv.erb_spec.rb deleted file mode 100644 index 78ad3d0ac..000000000 --- a/spec/views/exports/targeted_nanoseq_pcr_xp_concentrations_for_custom_pooling.csv.erb_spec.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'exports/targeted_nanoseq_pcr_xp_concentrations_for_custom_pooling.csv.erb' do - has_a_working_api - - let(:qc_result_options) { { value: 1.5, key: 'concentration', units: 'ng/ul' } } - - let(:well_a1) do - create(:v2_well, position: { 'name' => 'A1' }, qc_results: create_list(:qc_result, 1, qc_result_options)) - end - let(:well_b1) do - create(:v2_well, position: { 'name' => 'B1' }, qc_results: create_list(:qc_result, 1, qc_result_options)) - end - - let(:ancestor_well_a1) do - create( - :v2_well, - position: { - 'name' => 'A1' - }, - qc_results: create_list(:qc_result, 1, qc_result_options), - submit_for_sequencing: true, - sub_pool: 1, - coverage: 15 - ) - end - let(:ancestor_well_b1) do - create( - :v2_well, - position: { - 'name' => 'B1' - }, - qc_results: create_list(:qc_result, 1, qc_result_options), - submit_for_sequencing: false - ) - end - let(:labware) { create(:v2_plate, wells: [well_a1, well_b1], pool_sizes: [1, 1]) } - let(:ancestor_labware) { create(:v2_plate, wells: [ancestor_well_a1, ancestor_well_b1], pool_sizes: [1, 1]) } - - before do - assign(:plate, labware) - assign(:ancestor_plate, ancestor_labware) - end - - let(:expected_content) do - [ - ['Well', 'Concentration (ng/ul)', 'Submit for sequencing (Y/N)?', 'Sub-Pool', 'Coverage'], - %w[A1 1.5 Y 1 15], - ['B1', '1.5', 'N', nil, nil] - ] - end - - it 'renders the expected content' do - expect(CSV.parse(render)).to eq(expected_content) - end -end diff --git a/spec/views/exports/targeted_nanoseq_pcr_xp_merged_file.csv.erb_spec.rb b/spec/views/exports/targeted_nanoseq_pcr_xp_merged_file.csv.erb_spec.rb new file mode 100644 index 000000000..40fe4f7c1 --- /dev/null +++ b/spec/views/exports/targeted_nanoseq_pcr_xp_merged_file.csv.erb_spec.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'exports/targeted_nanoseq_pcr_xp_merged_file.csv.erb' do + has_a_working_api + + let(:qc_result_options_a1) { { value: 1.5, key: 'concentration', units: 'ng/ul' } } + let(:qc_result_options_b1) { { value: 1.2, key: 'concentration', units: 'ng/ul' } } + + let(:req1_pm1) { build :poly_metadatum, key: 'original_plate_barcode', value: 'BC1' } + let(:req1_pm2) { build :poly_metadatum, key: 'original_well_id', value: 'A1' } + let(:req1_pm3) { build :poly_metadatum, key: 'concentration_nm', value: 1.2 } + let(:req1_pm4) { build :poly_metadatum, key: 'input_amount_available', value: 23.1 } + let(:req1_pm5) { build :poly_metadatum, key: 'input_amount_desired', value: 34.2 } + let(:req1_pm6) { build :poly_metadatum, key: 'hyb_panel', value: 'Test hyb panel' } + + let(:request1) do + create :library_request_with_poly_metadata, + poly_metadata: [req1_pm1, req1_pm2, req1_pm3, req1_pm4, req1_pm5, req1_pm6] + end + + let(:req2_pm1) { build :poly_metadatum, key: 'original_plate_barcode', value: 'BC1' } + let(:req2_pm2) { build :poly_metadatum, key: 'original_well_id', value: 'B1' } + let(:req2_pm3) { build :poly_metadatum, key: 'concentration_nm', value: 1.0 } + let(:req2_pm4) { build :poly_metadatum, key: 'input_amount_available', value: 21.6 } + let(:req2_pm5) { build :poly_metadatum, key: 'input_amount_desired', value: 35.7 } + let(:req2_pm6) { build :poly_metadatum, key: 'hyb_panel', value: 'Test hyb panel' } + + let(:request2) do + create :library_request_with_poly_metadata, + poly_metadata: [req2_pm1, req2_pm2, req2_pm3, req2_pm4, req2_pm5, req2_pm6] + end + + let(:well_a1) do + create( + :v2_well, + location: 'A1', + position: { + 'name' => 'A1' + }, + qc_results: create_list(:qc_result, 1, qc_result_options_a1), + outer_request: request1 + ) + end + let(:well_b1) do + create( + :v2_well, + location: 'B1', + position: { + 'name' => 'B1' + }, + qc_results: create_list(:qc_result, 1, qc_result_options_b1), + outer_request: request2 + ) + end + + let(:labware) { create(:v2_plate, wells: [well_a1, well_b1], pool_sizes: [1, 1]) } + + let(:well_a1_sanger_id) { well_a1.aliquots.first.sample.sanger_sample_id } + let(:well_b1_sanger_id) { well_b1.aliquots.first.sample.sanger_sample_id } + + let(:well_a1_supplier_name) { well_a1.aliquots.first.sample.sample_metadata.supplier_name } + let(:well_b1_supplier_name) { well_b1.aliquots.first.sample.sample_metadata.supplier_name } + + before do + assign(:plate, labware) + well_a1.aliquots.first.request = request1 + well_b1.aliquots.first.request = request2 + end + + # NB. poly_metadata values are strings, so all values from poly_metadata will come out as strings in the csv + let(:expected_content) do + [ + [ + 'Original Plate Barcode', + 'Original Well ID', + 'Concentration (nM)', + 'Sanger Sample ID', + 'Supplier Sample Name', + 'Input amount available (fmol)', + 'Input amount desired (fmol)', + 'New Plate Barcode', + 'New Well ID', + 'Concentration (ng/ul)', + 'Hyb Panel' + ], + [ + 'BC1', + 'A1', + '1.2', + well_a1_sanger_id, + well_a1_supplier_name, + '23.1', + '34.2', + labware.human_barcode, + 'A1', + '1.5', + 'Test hyb panel' + ], + [ + 'BC1', + 'B1', + '1.0', + well_b1_sanger_id, + well_b1_supplier_name, + '21.6', + '35.7', + labware.human_barcode, + 'B1', + '1.2', + 'Test hyb panel' + ] + ] + end + + it 'renders the expected content' do + expect(CSV.parse(render)).to eq(expected_content) + end + + context 'when the wells are rearranged by binning, it orders correctly by original plate and well id' do + let(:well_a1) do + create( + :v2_well, + location: 'A1', + position: { + 'name' => 'A1' + }, + qc_results: create_list(:qc_result, 1, qc_result_options_a1), + outer_request: request2 + ) + end + let(:well_b1) do + create( + :v2_well, + location: 'B1', + position: { + 'name' => 'B1' + }, + qc_results: create_list(:qc_result, 1, qc_result_options_b1), + outer_request: request1 + ) + end + + let(:expected_content) do + [ + [ + 'Original Plate Barcode', + 'Original Well ID', + 'Concentration (nM)', + 'Sanger Sample ID', + 'Supplier Sample Name', + 'Input amount available (fmol)', + 'Input amount desired (fmol)', + 'New Plate Barcode', + 'New Well ID', + 'Concentration (ng/ul)', + 'Hyb Panel' + ], + [ + 'BC1', + 'A1', + '1.2', + well_b1_sanger_id, + well_b1_supplier_name, + '23.1', + '34.2', + labware.human_barcode, + 'B1', + '1.2', + 'Test hyb panel' + ], + [ + 'BC1', + 'B1', + '1.0', + well_a1_sanger_id, + well_a1_supplier_name, + '21.6', + '35.7', + labware.human_barcode, + 'A1', + '1.5', + 'Test hyb panel' + ] + ] + end + + before do + well_a1.aliquots.first.request = request2 + well_b1.aliquots.first.request = request1 + end + + it 'renders the expected content' do + expect(CSV.parse(render)).to eq(expected_content) + end + end +end diff --git a/yarn.lock b/yarn.lock index 836511c31..751a82f04 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3226,13 +3226,22 @@ axios-mock-adapter@^1.22.0: fast-deep-equal "^3.1.3" is-buffer "^2.0.5" -axios@^0.21.0, axios@^0.21.4: +axios@^0.21.0: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: follow-redirects "^1.14.0" +axios@^0.28.0: + version "0.28.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.28.0.tgz#801a4d991d0404961bccef46800e1170f8278c89" + integrity sha512-Tu7NYoGY4Yoc7I+Npf9HhUMtEEpV7ZiLH9yndTCoNhcpBH0kwcvFbzYN9/u5QKI5A6uefjsNNWaz5olJVYS62Q== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -4349,10 +4358,10 @@ core-js@^3.16.2: resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.19.0.tgz#9e40098a9bc326c7e81b486abbd5e12b9d275176" integrity sha512-L1TpFRWXZ76vH1yLM+z6KssLZrP8Z6GxxW4auoCj+XiViOzNPJCAuTIkn03BGdFe6Z5clX5t64wRIRypsZQrUg== -core-js@^3.35.0: - version "3.35.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.35.0.tgz#58e651688484f83c34196ca13f099574ee53d6b4" - integrity sha512-ntakECeqg81KqMueeGJ79Q5ZgQNR+6eaE8sxGCx62zMbAIj65q+uYvatToew3m6eAGdU4gNZwpZ34NMe4GYswg== +core-js@^3.35.1: + version "3.35.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.35.1.tgz#9c28f8b7ccee482796f8590cc8d15739eaaf980c" + integrity sha512-IgdsbxNyMskrTFxa9lWHyMwAJU5gXOPP+1yO+K59d50VLVAIDAbs7gIv705KzALModfK3ZrSZTPNpC0PQgIZuw== core-util-is@~1.0.0: version "1.0.2" @@ -5625,6 +5634,11 @@ follow-redirects@^1.0.0, follow-redirects@^1.14.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== +follow-redirects@^1.15.0: + version "1.15.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" + integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -5639,6 +5653,15 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -9043,6 +9066,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"