diff --git a/app/models/tutorial/batch.rb b/app/models/tutorial/batch.rb index e115b03d..c1bbc2d5 100644 --- a/app/models/tutorial/batch.rb +++ b/app/models/tutorial/batch.rb @@ -77,7 +77,15 @@ def param_names def ephemeral_names @ephemeral_names ||= param_names.map do |i| - Name.new(i) + name_attributes = i.except('parent', 'incertae_sedis') + placement_attributes = { + parent: i['parent'], incertae_sedis: i['incertae_sedis'], + incertae_sedis_text: i['description'], preferred: true + } + + Name.new(name_attributes).tap do |name| + name.placements.build(placement_attributes.merge(name: name)) + end rescue => e $stderr.puts e nil @@ -113,15 +121,20 @@ def check_ephemeral_names(user) @check_ephemeral_names ||= :pending return ephemeral_names if @check_ephemeral_names == :done - name_names = ephemeral_names.map(&:name) ephemeral_names.each do |name| + placement = name.placements.first + # Is the information complete? Tutorial::Batch.name_keys_base.each do |i| + next if i == :parent + unless name.send(i).present? name.errors.add(i, :missing, message: 'is missing') end end + placement.validate # Populate placement.errors for the review step + # Is the etymology present? unless name.etymology? name.errors.add( @@ -139,8 +152,8 @@ def check_ephemeral_names(user) end # Is the parent already registered? - if name.parent && !name_exists?(name.parent.name) - name.errors.add( + if placement.parent && !name_exists?(placement.parent.name) + placement.errors.add( :parent, :does_not_exist, message: 'does not exist and is not proposed in this list' ) @@ -305,7 +318,8 @@ def batch_step_01(params, user) # Remove foreign keys in first pass par = par_ori.dup %w[ - parent nomenclatural_type_entry nomenclatural_type_type + parent incertae_sedis + nomenclatural_type_entry nomenclatural_type_type nomenclatural_type_id nomenclatural_type ].each { |i| par.delete(i) } @@ -333,12 +347,21 @@ def batch_step_01(params, user) # Parents if par['parent'] - new_par[:parent] = Name.find_by_variants(par['parent'].name) - unless new_par[:parent] - name = Name.new(default_pars.merge(name: par['parent'].name)) - name.save! - new_par[:parent_id] = name.id + parent = Name.find_by_variants(par['parent'].name) + unless parent + parent = Name.new(default_pars.merge(name: par['parent'].name)) + parent.save! end + save_batch_placement( + Name.find_by_variants(par['name']), + parent: parent, incertae_sedis: nil + ) + elsif par['incertae_sedis'] + save_batch_placement( + Name.find_by_variants(par['name']), parent: nil, + incertae_sedis: par['incertae_sedis'], + incertae_sedis_text: par['description'] + ) end # Nomenclatural types @@ -377,4 +400,17 @@ def batch_step_02(params, user) @next_action = [:new_register, tutorial: self] end + private + + def save_batch_placement(name, attributes) + # Reuse matching alternatives and demote the current placement before + # promotion so the name never attempts to save two preferred placements. + placement = name.placements.find_or_initialize_by( + attributes.slice(:parent, :incertae_sedis) + ) + name.placements.where(preferred: true).where.not(id: placement.id) + .find_each { |current| current.update!(preferred: false) } + placement.update!(attributes.merge(preferred: true)) + end + end diff --git a/app/views/tutorials/batch/_01.html.erb b/app/views/tutorials/batch/_01.html.erb index c854a844..2867f341 100644 --- a/app/views/tutorials/batch/_01.html.erb +++ b/app/views/tutorials/batch/_01.html.erb @@ -9,6 +9,7 @@
<% @tutorial.check_ephemeral_names(current_user) %> <% @tutorial.ephemeral_names.each_with_index do |name, idx| %> + <% placement = name&.placements&.first %>
<% id = "batch-names-#{idx}" %> <% if name.nil? || name&.name.nil? %> @@ -32,13 +33,14 @@ @@ -66,11 +68,12 @@ <% end %> <%= modal_button(id, as_anchor: true) do %>Record details<% end %> - <% if name.errors.any? %> + <% if name.errors.any? || placement.errors.any? %>

Issues detected in preliminary check:

    - <% name.errors.full_messages.each do |error| %> + <% (name.errors.full_messages + + placement.errors.full_messages).each do |error| %>
  • <%= error %>
  • <% end %>
@@ -166,4 +169,3 @@ Please note that any errors saved will have to be individually resolved. This process may take up to a couple of minutes, please be patient.

- diff --git a/test/fixtures/files/SCR_UploadBatch-Luteria-Incertae_sedis.xlsx b/test/fixtures/files/SCR_UploadBatch-Luteria-Incertae_sedis.xlsx new file mode 100644 index 00000000..25682a22 Binary files /dev/null and b/test/fixtures/files/SCR_UploadBatch-Luteria-Incertae_sedis.xlsx differ diff --git a/test/fixtures/files/SCR_UploadBatch-Nanoclepta.xlsx b/test/fixtures/files/SCR_UploadBatch-Nanoclepta.xlsx new file mode 100644 index 00000000..49207a08 Binary files /dev/null and b/test/fixtures/files/SCR_UploadBatch-Nanoclepta.xlsx differ diff --git a/test/fixtures/names.yml b/test/fixtures/names.yml index 45acddfb..1b110973 100644 --- a/test/fixtures/names.yml +++ b/test/fixtures/names.yml @@ -34,6 +34,21 @@ bacillus_subtilis: rank: species status: 20 # ICNP +nanobdellaceae: + name: Nanobdellaceae + rank: family + status: 15 # SeqCode + +nanoclepta_minutus: + name: Nanoclepta minutus + rank: species + status: 15 # SeqCode + +luteria_ianthellae: + name: Luteria ianthellae + rank: species + status: 15 # SeqCode + type_genome_with_locations: name: Testimonas mappensis rank: species diff --git a/test/models/tutorial_test.rb b/test/models/tutorial_test.rb index 175c27d2..77e84a9c 100644 --- a/test/models/tutorial_test.rb +++ b/test/models/tutorial_test.rb @@ -1,7 +1,125 @@ require 'test_helper' class TutorialTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end + test 'batch parses a name' do + tutorial = upload_batch_spreadsheet('SCR_UploadBatch-Nanoclepta.xlsx') + name = tutorial.ephemeral_names.first + placement = name.placements.first + + assert_equal 1, tutorial.step + assert_empty tutorial.value(:genomes) + assert_equal 1, tutorial.value(:names).size + assert_equal 'Nanoclepta', tutorial.value(:names).first['name'] + assert_equal 'Nanobdellaceae', tutorial.value(:names).first['parent'] + assert_nil name.parent + assert_nil name[:incertae_sedis] + assert_equal 'Nanobdellaceae', placement.parent.name + assert_nil placement.incertae_sedis + end + + test 'batch parses an incertae sedis name' do + tutorial = upload_batch_spreadsheet( + 'SCR_UploadBatch-Luteria-Incertae_sedis.xlsx' + ) + name = tutorial.ephemeral_names.first + placement = name.placements.first + + assert_equal 1, tutorial.step + assert_empty tutorial.value(:genomes) + assert_equal 1, tutorial.value(:names).size + assert_equal 'Luteria', tutorial.value(:names).first['name'] + assert_equal( + 'incertae sedis (Bacteria)', tutorial.value(:names).first['parent'] + ) + assert_nil name.parent + assert_nil name[:incertae_sedis] + assert_nil placement.parent + assert_equal 'incertae sedis (Bacteria)', placement.incertae_sedis + end + + test 'batch creates a parent placement' do + tutorial = upload_batch_spreadsheet('SCR_UploadBatch-Nanoclepta.xlsx') + parent = names(:nanobdellaceae) + + assert tutorial.batch_step_01({}, users(:contributor)) + + name = Name.find_by!(name: 'Nanoclepta') + placement = name.placement + + assert_not_nil placement + assert_predicate placement, :preferred? + assert_equal parent, placement.parent + assert_equal parent, name.parent + assert_nil placement.incertae_sedis + end + + test 'batch updates a claimable name with a preferred placement' do + parent = names(:nanobdellaceae) + name = Name.create!( + name: 'Nanoclepta', rank: 'genus', status: 5, + created_by: users(:contributor), parent: parent + ) + placement = name.placement + tutorial = upload_batch_spreadsheet('SCR_UploadBatch-Nanoclepta.xlsx') + + assert_no_difference('Placement.count') do + assert tutorial.batch_step_01({}, users(:contributor)) + end + + assert_equal placement, name.reload.placement + assert_equal parent, name.parent + end + + test 'batch replaces a claimable name preferred placement' do + old_parent = Name.create!( + name: 'Nanoarchaeaceae', rank: 'family', status: 15 + ) + name = Name.create!( + name: 'Nanoclepta', rank: 'genus', status: 5, + created_by: users(:contributor), parent: old_parent + ) + old_placement = name.placement + tutorial = upload_batch_spreadsheet('SCR_UploadBatch-Nanoclepta.xlsx') + + assert_difference('Placement.count', 1) do + assert tutorial.batch_step_01({}, users(:contributor)) + end + + name = Name.find(name.id) + assert_equal names(:nanobdellaceae), name.placement.parent + assert_not old_placement.reload.preferred? + assert_includes name.alt_placements, old_placement + end + + test 'batch creates an incertae sedis placement' do + tutorial = upload_batch_spreadsheet( + 'SCR_UploadBatch-Luteria-Incertae_sedis.xlsx' + ) + explanation = tutorial.value(:names).first['description'] + + assert tutorial.batch_step_01({}, users(:contributor)) + + name = Name.find_by!(name: 'Luteria') + placement = name.placement + + assert_not_nil placement + assert_predicate placement, :preferred? + assert_nil placement.parent + assert_nil name.parent + assert_equal 'incertae sedis (Bacteria)', placement.incertae_sedis + assert_equal explanation, placement.incertae_sedis_text.to_plain_text + assert_nil name[:incertae_sedis] + end + + private + + def upload_batch_spreadsheet(filename) + tutorial = Tutorial.create!( + pipeline: 'batch', user: users(:contributor), step: 0, ongoing: true + ) + File.open(Rails.root.join('test/fixtures/files', filename)) do |file| + assert tutorial.batch_step_00({ file: file }, users(:contributor)) + end + tutorial + end end diff --git a/test/system/tutorials_test.rb b/test/system/tutorials_test.rb index a83f3c6c..cd521986 100644 --- a/test/system/tutorials_test.rb +++ b/test/system/tutorials_test.rb @@ -1,8 +1,102 @@ +# frozen_string_literal: true + require 'application_system_test_case' class TutorialsTest < ApplicationSystemTestCase + include Warden::Test::Helpers + setup do - @tutorial = tutorials(:one) + @user = users(:contributor) + @tutorial = Tutorial.create!( + pipeline: 'batch', user: @user, step: 0, ongoing: true + ) + + login_as(@user, scope: :user) + end + + teardown { Warden.test_reset! } + + test 'batch uploading a single genus' do + family = names(:nanobdellaceae) + type_species = names(:nanoclepta_minutus) + visit tutorial_url(@tutorial) + + assert_text 'Step 0: Upload spreadsheet' + + upload_batch_spreadsheet('SCR_UploadBatch-Nanoclepta.xlsx') + assert_batch_review(name: 'Nanoclepta', parent: 'Nanobdellaceae') + create_batch_entries + + genus = Name.find_by!(name: 'Nanoclepta') + @tutorial.reload + + assert_equal 'genus', genus.rank + assert_equal family, genus.parent + assert_equal type_species, genus.nomenclatural_type + assert_equal @tutorial, genus.tutorial + assert_equal ['Nanoclepta'], @tutorial.names.pluck(:name) + + complete_batch_tutorial + end + + test 'batch uploading a single incertae sedis genus' do + type_species = names(:luteria_ianthellae) + visit tutorial_url(@tutorial) + + upload_batch_spreadsheet('SCR_UploadBatch-Luteria-Incertae_sedis.xlsx') + incertae_sedis = 'incertae sedis (Bacteria)' + assert_batch_review(name: 'Luteria', parent: incertae_sedis) + + create_batch_entries + + genus = Name.find_by!(name: 'Luteria') + @tutorial.reload + + assert_equal 'genus', genus.rank + assert_nil genus.parent + assert_equal incertae_sedis, genus.incertae_sedis + assert_equal type_species, genus.nomenclatural_type + assert_equal @tutorial, genus.tutorial + assert_equal ['Luteria'], @tutorial.names.pluck(:name) + + complete_batch_tutorial + end + + private + + def complete_batch_tutorial + click_button 'Continue' + + assert_current_path new_register_path(tutorial: @tutorial) + assert_not @tutorial.reload.ongoing? + end + + def upload_batch_spreadsheet(filename) + attach_file( + 'Batch spreadsheet', + Rails.root.join('test/fixtures/files', filename) + ) + click_button 'Continue' end + def assert_batch_review(name:, parent:) + assert_text 'Step 1: Review parsed data' + within '#batch-names-0-h' do + assert_text name + assert_text 'genus of' + assert_text parent + end + assert_no_selector '#batch-names-1-h' + + assert_no_selector '#batch-genomes-0-h' + end + + def create_batch_entries + assert_difference('Name.count', 1) do + assert_no_difference('Genome.count') do + click_button 'Continue' + assert_text 'Step 2: Validation list' + end + end + end end