Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
750c1f2
Add changes for generating screenshots
yoldas Feb 18, 2026
918d412
Update worksheet screenshot based on feedback
yoldas Feb 18, 2026
60301fc
Add poly_metadata association to Batch for storing buffer_volume_for_…
yoldas Feb 19, 2026
e9a372d
Add buffer volume for empty wells option to params for pass through
yoldas Feb 19, 2026
4b7b7e7
Remove pry
yoldas Feb 19, 2026
6a0d6db
Add HasPolyMetadata concent to set the option on batch
yoldas Feb 19, 2026
c5244aa
Call set_poly_metadata on the batch in cherrypick handler
yoldas Feb 19, 2026
f2d190d
Update worksheet with buffer_volume_for_empty_wells option from batch…
yoldas Feb 19, 2026
0247d43
Add buffer steps for empty wells in tecan behaviour
yoldas Feb 19, 2026
03927d5
Add buffer steps for empty wells in TecanV3 driver file generator
yoldas Feb 19, 2026
13e85a3
Check if the destination well is empty, in case of partial plate
yoldas Feb 19, 2026
e755668
Add guard for generator test without batch
yoldas Feb 19, 2026
521a3dd
Add the word volume to the note and legend in worksheet
yoldas Feb 20, 2026
0d0787c
Add Automatic buffer addition for empty wells required? checkbox
yoldas Feb 20, 2026
7349453
Add script to toggle buffer_volume_for_empty_wells textbox based on a…
yoldas Feb 20, 2026
df31627
Add code documentation about new data_object generation
yoldas Feb 20, 2026
416ab29
Add comment to explain why src_well is checked
yoldas Feb 20, 2026
85a6b2a
Add batch behaviour to handle poly_metadata for cherrypick options
yoldas Feb 20, 2026
6177332
Add task helper to set form params in batch polymetadata
yoldas Feb 20, 2026
e106709
Add PolyMetadataBehaviour to batch
yoldas Feb 20, 2026
0dad66a
Add option handling in pick helpers
yoldas Feb 20, 2026
0655cfe
Add pass through for automatic_buffer_addition
yoldas Feb 20, 2026
e70a38e
Tidy up the worksheet ERB
yoldas Feb 20, 2026
4ca4650
Use ERB comments instead of HTML comments
yoldas Feb 20, 2026
4f00987
Revert redundant change
yoldas Feb 20, 2026
b7183cd
Remove extra closing ERB tag
yoldas Feb 20, 2026
213445a
Handle checkbox value as 1 or on; passing through hidden form fields
yoldas Feb 20, 2026
c66d6ef
Fix broken TecanV3 tests
yoldas Feb 20, 2026
733ebc9
Fix broken Tecan generator tests
yoldas Feb 20, 2026
a37e275
Merge branch 'develop' into y26-012-automating-buffer-addition
andrewsparkes Feb 20, 2026
5656a8b
Merge branch 'y26-012-automating-buffer-addition' into Y26-680-automa…
andrewsparkes Feb 23, 2026
e897666
Merge branch 'develop' into Y26-680-automated-buffer-addition
andrewsparkes Feb 24, 2026
2226ad5
added jest and tests for cherrypick_strategies
andrewsparkes Feb 24, 2026
fb35abe
added tests for buffer volume for empty wells option module
andrewsparkes Feb 25, 2026
70d3e84
added tests for the check in buffers method for src_well
andrewsparkes Feb 25, 2026
9c6fa34
added tests for tecan default changes
andrewsparkes Feb 25, 2026
c656849
added tests for buffer addition, fixed plate barcodes in all test fil…
andrewsparkes Mar 2, 2026
f1c20a6
fix to pass eslint
andrewsparkes Mar 2, 2026
9650b30
linted
andrewsparkes Mar 2, 2026
adbaa8b
added tests for has poly metadata
andrewsparkes Mar 2, 2026
7a9c636
Merge branch 'develop' into Y26-680-automated-buffer-addition
andrewsparkes Mar 2, 2026
fe0d527
Merge branch 'develop' into Y26-680-automated-buffer-addition
andrewsparkes Mar 11, 2026
294edd1
changes after code review
andrewsparkes Mar 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions app/frontend/entrypoints/cherrypick_strategies.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
// Toggle buffer_volume_for_empty_wells input based on automatic_buffer_addition checkbox
document.addEventListener("DOMContentLoaded", function () {
const bufferInput = document.getElementById("buffer_volume_for_empty_wells");
const autoBufferCheckbox = document.getElementById("automatic_buffer_addition");
if (bufferInput && autoBufferCheckbox) {
function toggleBufferInput() {
bufferInput.disabled = !autoBufferCheckbox.checked;
}
autoBufferCheckbox.addEventListener("change", toggleBufferInput);
toggleBufferInput(); // Set initial state
}
});
// apply a border highlight to the card, based on the cherrypick strategy selected
// this is admittedly a gratuitous addition to the user experience, but it's a nice touch

Expand Down
33 changes: 33 additions & 0 deletions app/frontend/entrypoints/cherrypick_strategies.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// cherrypick_strategies.test.js
// Tests for cherrypick_strategies.js buffer input toggle logic

/* global describe, it, expect, beforeEach, jest */
/* @jest-environment jsdom */

describe("Buffer input toggle", () => {
let bufferInput, autoBufferCheckbox;

beforeEach(() => {
document.body.innerHTML = `
<input id="buffer_volume_for_empty_wells" type="number" />
<input id="automatic_buffer_addition" type="checkbox" />
`;
// Re-require the script to attach event listeners
jest.resetModules();
require("./cherrypick_strategies.js");
bufferInput = document.getElementById("buffer_volume_for_empty_wells");
autoBufferCheckbox = document.getElementById("automatic_buffer_addition");
});

it("disables buffer input when checkbox is unchecked", () => {
autoBufferCheckbox.checked = false;
document.dispatchEvent(new Event("DOMContentLoaded"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: the test may be OK because after DOMContentLoaded event, it registers a change event and then calls toggleBufferInput once to do an initial toggle. However, the toggle behaviour could be tested on the registered change event on the checkbox element automatic_buffer_addition by a dispatch as in autoBufferCheckbox.dispatchEvent(new Event("change"));

note: this PR adds JS testing (package.json) although there is no yarn test in CI workflows yet.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tried changing:

autoBufferCheckbox.checked = false;
document.dispatchEvent(new Event("DOMContentLoaded"));
expect(bufferInput.disabled).toBe(true);

to:

autoBufferCheckbox.checked = false;
autoBufferCheckbox.dispatchEvent(new Event("change"));
expect(bufferInput.disabled).toBe(true);

but test fails. expect is false. I'm missing something.

Copy link
Member

@yoldas yoldas Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant the following, which can be added as well.

it("enables/disables buffer input when checkbox is toggled on/off", () => {
   // register the change event handler and initially disable bufferInput 
   // because  autoBufferCheckbox is unchecked 
   
    document.dispatchEvent(new Event("DOMContentLoaded")); 

   // You can set a value and trigger the change event on autoBufferCheckbox, 
    // or just click it instead.
    
    // autoBufferCheckbox.checked = true;
    // autoBufferCheckbox.dispatchEvent(new Event("change"));
    autoBufferCheckbox.click(); // unchecked to checked
    expect(bufferInput.disabled).toBe(false);

    // You can set a value and trigger the change event on autoBufferCheckbox, 
    // or just click it instead.
    
    // autoBufferCheckbox.checked = false;
    // autoBufferCheckbox.dispatchEvent(new Event("change"));
    autoBufferCheckbox.click(); // checked to unchecked
    expect(bufferInput.disabled).toBe(true);
  });

expect(bufferInput.disabled).toBe(true);
});

it("enables buffer input when checkbox is checked", () => {
autoBufferCheckbox.checked = true;
document.dispatchEvent(new Event("DOMContentLoaded"));
expect(bufferInput.disabled).toBe(false);
});
});
13 changes: 7 additions & 6 deletions app/models/batch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class Batch < ApplicationRecord # rubocop:todo Metrics/ClassLength
include ::Batch::PipelineBehaviour
include ::Batch::StateMachineBehaviour
include UnderRepWellCommentsToBroadcast
# Added for storing buffer_volume_for_empty_wells option on Cherrypick batches.
include HasPolyMetadata
include ::Batch::PolyMetadataBehaviour
extend EventfulRecord

# The three states of {Batch} Also @see {SequencingQcBatch}
Expand Down Expand Up @@ -481,18 +484,16 @@ def swap(current_user, batch_info = {}) # rubocop:todo Metrics/CyclomaticComplex
# Finally record the fact that the batch was swapped
batch_request_left.batch.lab_events.create!(
description: 'Lane swap',
# rubocop:todo Layout/LineLength
message:
"Lane #{batch_request_right.position} moved to #{batch_request_left.batch_id} lane #{batch_request_left.position}",
# rubocop:enable Layout/LineLength
"Lane #{batch_request_right.position} moved to #{batch_request_left.batch_id} " \
"lane #{batch_request_left.position}",
user_id: current_user.id
)
batch_request_right.batch.lab_events.create!(
description: 'Lane swap',
# rubocop:todo Layout/LineLength
message:
"Lane #{batch_request_left.position} moved to #{batch_request_right.batch_id} lane #{batch_request_right.position}",
# rubocop:enable Layout/LineLength
"Lane #{batch_request_left.position} moved to #{batch_request_right.batch_id} " \
"lane #{batch_request_right.position}",
user_id: current_user.id
)
end
Expand Down
16 changes: 16 additions & 0 deletions app/models/batch/poly_metadata_behaviour.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true
module Batch::PolyMetadataBehaviour
# Returns whether the Cherrypick automatic_buffer_addition option is enabled
# @return [Boolean] whether the automatic_buffer_addition option is enabled
def automatic_buffer_addition?
# tests 1 and on to cover both visible and hidden option in the forms of successive pages
%w[1 on].include?(get_poly_metadata(:automatic_buffer_addition))
end

# Returns the Cherrypick buffer_volume_for_empty_wells option value if
# automatic_buffer_addition is enabled, nil otherwise.
# @return [Float, nil] the buffer_volume_for_empty_wells value
def buffer_volume_for_empty_wells
get_poly_metadata(:buffer_volume_for_empty_wells).to_f if automatic_buffer_addition?
end
end
25 changes: 25 additions & 0 deletions app/models/cherrypick/task/buffer_volume_for_empty_wells_option.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module Cherrypick::Task::BufferVolumeForEmptyWellsOption
def create_buffer_volume_for_empty_wells_option(params)
return unless @batch

key = :automatic_buffer_addition
# The checkbox value is either "1", or nil if not checked.
@batch.set_poly_metadata(key, params[key])

return unless %w[1 on].include?(params[key])

# If automatic buffer addition for empty wells is required, check and
# set the required buffer volume in the batch polymetadata.
key = :buffer_volume_for_empty_wells

# method valid_float_param? is defined in Cherrypick::Task::PickHelpers
unless valid_float_param?(params[key])
raise Cherrypick::VolumeError,
"Invalid buffer volume for empty wells: #{params[key]}"
end

@batch.set_poly_metadata(key, params[key])
end
end
1 change: 1 addition & 0 deletions app/models/cherrypick/task/pick_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ def self.included(base)
include Cherrypick::Task::PickByNanoGramsPerMicroLitre
include Cherrypick::Task::PickByNanoGrams
include Cherrypick::Task::PickByMicroLitre
include Cherrypick::Task::BufferVolumeForEmptyWellsOption
end
end

Expand Down
38 changes: 38 additions & 0 deletions app/models/concerns/has_poly_metadata.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

module HasPolyMetadata
extend ActiveSupport::Concern

included do
has_many :poly_metadata, as: :metadatable, dependent: :destroy, inverse_of: :metadatable
end

# Sets a PolyMetaDatum for the given key and value.
# If value is present, it will create or update the PolyMetaDatum with the
# given key and value, otherwise it will destroy the PolyMetaDatum with the
# given key if that exists.
# NB: this is because PolyMetaDatum validations prevent key duplication and blank values,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# NB: this is because PolyMetaDatum validations prevent key duplication and blank values,
# NB: this is because PolyMetaDatum model validations prevent key duplication and blank values,

Thanks for the explanation. I still had a minor confusion, hence the suggestion.

# although there are no such DB constraints.
# @param key [String] The key of the PolyMetaDatum to set.
# @param value [String] The value of the PolyMetaDatum to set. If nil or empty, the PolyMetaDatum will be destroyed.
# @return [void]
def set_poly_metadata(key, value)
record = poly_metadata.find_by(key:)
if value.present?
if record
record.update!(value:)
else
poly_metadata.create!(key:, value:)
end
else
record&.destroy!
end
end

# Returns the value of the PolyMetaDatum with the given key.
# @param key [String] The key of the PolyMetaDatum to retrieve.
# @return [String, nil] The value of the PolyMetaDatum, or nil if it does not exist.
def get_poly_metadata(key)
poly_metadata.find_by(key:)&.value
end
end
2 changes: 2 additions & 0 deletions app/models/map.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ def self.plate_length(plate_size)
PLATE_DIMENSIONS[plate_size].last
end

# well number counting by columns, length is the number of rows in the plate
# e.g. B5 sends this 34 and 8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I remember adding a similar comment elsewhere 😅

def self.vertical_position_to_description(well_position, length)
desc_letter = (((well_position - 1) % length) + 65).chr
desc_number = ((well_position - 1) / length) + 1
Expand Down
93 changes: 90 additions & 3 deletions app/models/robot/generator/behaviours/tecan_default.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

# Module with the file generation functionality for Tecan robots
module Robot::Generator::Behaviours::TecanDefault
module Robot::Generator::Behaviours::TecanDefault # rubocop:disable Metrics/ModuleLength
def mapping(data_object: picking_data)
raise ArgumentError, 'Data object not present for Tecan mapping' if data_object.nil?

Expand Down Expand Up @@ -70,10 +70,13 @@ def buffer_separator
'C;'
end

def buffers(data_object)
def buffers(data_object) # rubocop:disable Metrics/AbcSize
data_object = data_object_for_buffers(data_object)
buffer = []
each_mapping(data_object) do |mapping, dest_plate_barcode, plate_details|
next unless total_volume > mapping['volume']
# src_well is checked to distinguish between buffer for sample wells
# and buffer for empty wells.
next if mapping.key?('src_well') && total_volume <= mapping['volume']

dest_name = data_object['destination'][dest_plate_barcode]['name']
volume = mapping['buffer_volume']
Expand Down Expand Up @@ -113,4 +116,88 @@ def sorted_destination_plates
def description_to_column_index(well_name, plate_size)
Map::Coordinate.description_to_vertical_plate_position(well_name, plate_size)
end

def column_index_to_description(index, plate_size)
Map::Coordinate.vertical_plate_position_to_description(index, plate_size)
end

# Returns a new data object with buffer entries added for empty destination
# wells, if the option is enabled; otherwise returns the original data object.
# Only the fields used by the buffer steps are added to the new data object.
# @param data_object [Hash] the original data object
# @return [Hash] the new data object with buffer entries for empty wells,
# or the original data object if the option is not enabled
# @example input data_object
# {"destination" =>
# {"SQPD-9101" =>
# {"name" => "ABgene 0765",
# "plate_size" => 96,
# "control" => false,
# "mapping" =>
# [{"src_well" => ["SQPD-9089", "A1"], "dst_well" => "A1", "volume" => 100.0, "buffer_volume" => 0.0},
# {"src_well" => ["SQPD-9089", "A2"], "dst_well" => "B1", "volume" => 100.0, "buffer_volume" => 0.0}]},
# "source" =>
# {"SQPD-9089" => {"name" => "ABgene 0800", "plate_size" => 96, "control" => false},
# "SQPD-9090" => {"name" => "ABgene 0800", "plate_size" => 96, "control" => false}},
# "time" => Thu, 19 Feb 2026 15:20:20.785717000 GMT +00:00,
# "user" => "admin"}
#
# @example output data_object
# {"destination" =>
# {"SQPD-9101" =>
# {"name" => "ABgene 0765",
# "plate_size" => 96,
# "control" => false,
# "mapping" =>
# [{"src_well" => ["SQPD-9089", "A1"], "dst_well" => "A1", "volume" => 100.0, "buffer_volume" => 0.0},
# {"src_well" => ["SQPD-9089", "A2"], "dst_well" => "B1", "volume" => 100.0, "buffer_volume" => 0.0},
# {"dst_well" => "C1", "buffer_volume" => 120.0}]},
# {"dst_well" => "D1", "buffer_volume" => 120.0}]},
# ...
# ]},
# }
# }
def data_object_for_buffers(data_object) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength,Metrics/CyclomaticComplexity
buffer_volume_for_empty_wells = @batch&.buffer_volume_for_empty_wells
return data_object unless buffer_volume_for_empty_wells

obj = { 'destination' => {} }
data_object['destination'].each do |dest_plate_barcode, plate_details|
plate = Plate.find_by_barcode(dest_plate_barcode)
plate_size = plate_details['plate_size']
# Initialise the destination section
obj['destination'][dest_plate_barcode] = {
'name' => plate_details['name'],
'plate_size' => plate_size
}
# Create a hash of column index to the existing mapping entries
index_to_mapping = plate_details['mapping'].index_by do |entry|
description_to_column_index(entry['dst_well'], plate_size)
end

# Loop through the column order and generate new mapping entries
# Add existing mappings if present and skip non-empty wells in case it is partial plate.
mapping = []
(1..plate_size).each do |index|
# Add existing mapping if present for this column index.
if index_to_mapping.key?(index)
mapping << index_to_mapping[index]
next
end

# Check if the destination well empty, in case of partial plate.
dst_well = column_index_to_description(index, plate_size) # A1, B1, etc.
well = plate.find_well_by_name(dst_well) # Well object or nil
next if well.present? && !well.empty? # Skip non-empty wells

# Add buffer for empty well
mapping << {
'dst_well' => dst_well,
'buffer_volume' => buffer_volume_for_empty_wells
}
end
obj['destination'][dest_plate_barcode]['mapping'] = mapping
end
obj
end
end
5 changes: 4 additions & 1 deletion app/models/robot/generator/tecan_v3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ class Robot::Generator::TecanV3 < Robot::Generator::TecanV2
# @see Robot::Generator::Behaviours::TecanDefault#buffers
# rubocop:disable Metrics/AbcSize,Metrics/MethodLength
def buffers(data_object)
data_object = data_object_for_buffers(data_object)
groups = Hash.new { |h, k| h[k] = [] } # channel => [steps]
each_mapping(data_object) do |mapping, dest_plate_barcode, plate_details|
next unless total_volume > mapping['volume']
# src_well is checked to distinguish between buffer for sample wells
# and buffer for empty wells.
next if mapping.key?('src_well') && total_volume <= mapping['volume']

dest_name = data_object['destination'][dest_plate_barcode]['name']
volume = mapping['buffer_volume']
Expand Down
6 changes: 6 additions & 0 deletions app/models/tasks/cherrypick_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ def setup_input_params_for_pass_through # rubocop:todo Metrics/AbcSize
else
raise StandardError, "Invalid cherrypicking strategy '#{params[:cherrypick][:strategy]}'"
end
# Add buffer volume for empty wells option to params for pass through
@automatic_buffer_addition = params[:automatic_buffer_addition]
@buffer_volume_for_empty_wells = params[:buffer_volume_for_empty_wells]
@plate_purpose_id = params[:plate_purpose_id]
@fluidigm_barcode = params[:fluidigm_plate]
end
Expand Down Expand Up @@ -129,6 +132,9 @@ def do_cherrypick_task(_task, params) # rubocop:todo Metrics/CyclomaticComplexit
raise StandardError, "Invalid cherrypicking type #{params[:cherrypick_strategy]}"
end

# Store the buffer volume for empty wells option in the batch's poly_metadata
create_buffer_volume_for_empty_wells_option(params)

# We can preload the well locations so that we can do efficient lookup later.
well_locations =
Map
Expand Down
4 changes: 4 additions & 0 deletions app/models/well.rb
Original file line number Diff line number Diff line change
Expand Up @@ -365,4 +365,8 @@ def name
def library_name
nil
end

def empty?
aliquots.blank?
end
end
15 changes: 14 additions & 1 deletion app/views/batches/_cherrypick_single_worksheet.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
<%# see Robot::Verification::Base#pick_number_to_expected_layout for structure of robot_plate_layout %>
<% destination_layout, source_layout, control_layout = robot_plate_layout %>
<% source_plate_colour = source_layout.transform_values { |sort_order| "colour#{sort_order%12}" } %>
<%# Get buffer_volume_for_empty_wells from batch as float or nil -%>
<% buffer_volume_for_empty_wells = batch.buffer_volume_for_empty_wells %>

<div id="plate_details">
<%= render partial: 'cherrypick_worksheet_plate_list', locals: { section_name: 'Source plates', plates: source_layout, bed_prefix: 'SCRC' } %>
Expand Down Expand Up @@ -48,6 +50,8 @@
<td><strong><%= rowchar %></strong></td>
<% (num_columns).times do |column| -%>
<% well = plate_wells[row*num_columns+column] -%>
<%# Flag to check if well is empty and needs to be represented on the chart with 'e' followed by the buffer volume for empty wells, for example e120.00 -%>
<% empty_well = true %>
<% if well.present? -%>
<% request = indexed_requests[well.id] %>
<% source_well = request&.asset %>
Expand All @@ -62,6 +66,8 @@
<% else %>
<td>
<% end -%>
<%# Flag to set if the well is not empty -%>
<% empty_well = false %>
<%= source_well.map_description %>
<%= source_well.plate.barcode_number %>
v<%= "%.#{configatron.tecan_precision}f" % well.get_picked_volume %> b<%= "%.#{configatron.tecan_precision}f" % well.get_buffer_volume %>
Expand All @@ -74,6 +80,9 @@
<% else %>
<td>
<% end -%>
<% if empty_well && buffer_volume_for_empty_wells.present? -%>
e<%= "%.#{configatron.tecan_precision}f" % buffer_volume_for_empty_wells %>
<% end %>
</td>
<% end -%>
<td><strong><%= rowchar %></strong></td>
Expand All @@ -92,7 +101,11 @@
</div>

<div id="footer">
v = picking volume µl; b = buffer volume µl<br>
<%# Add buffer volume for empty wells to footer if the option was set -%>
<% if buffer_volume_for_empty_wells.present? -%>
Buffer volume for empty wells: <%= "%.#{configatron.tecan_precision}f" % buffer_volume_for_empty_wells %> µl<br>
<% end -%>
v = picking volume µl; b = buffer volume µl<% if buffer_volume_for_empty_wells.present? -%>; e = buffer volume for empty wells µl<br><% end -%>
Created: <%= batch.updated_at.strftime("%I:%M %p on %A %d %B, %Y") %> for <%= batch.user.login %><br>
Printed: <%= Time.now.strftime("%I:%M %p on %A %d %B, %Y") %> for <%= current_user.login %>
</div>
3 changes: 3 additions & 0 deletions app/views/workflows/_cherrypick_batches.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
<%= hidden_field_tag 'robot_id', @robot_id %>
<%= hidden_field_tag 'cherrypick_strategy', @cherrypick_strategy %>
<%= hidden_field_tag 'plate_type', @plate_type %>
<%# Add hidden fields for the buffer volume for empty wells option to carry over -%>
<%= hidden_field_tag 'automatic_buffer_addition', @automatic_buffer_addition %>
<%= hidden_field_tag 'buffer_volume_for_empty_wells', @buffer_volume_for_empty_wells %>

<%= render(partial: 'next_stage_submit', locals: { check_selection: true }) %>
<% end %>
Expand Down
Loading
Loading