diff --git a/app/controllers/dev_controller.rb b/app/controllers/dev_controller.rb new file mode 100644 index 00000000..240aaf50 --- /dev/null +++ b/app/controllers/dev_controller.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# Small, development-only pages for manually exercising UI components. +class DevController < ApplicationController + def index + end + + def autocomplete + end +end diff --git a/app/controllers/names_controller.rb b/app/controllers/names_controller.rb index 99f67a5d..a7c26ee3 100644 --- a/app/controllers/names_controller.rb +++ b/app/controllers/names_controller.rb @@ -53,14 +53,18 @@ class NamesController < ApplicationController # GET /names/autocomplete.json?q=Maco # GET /names/autocomplete.json?q=Allo&rank=genus + # GET /names/autocomplete.json?q=Pseu&minimum_rank=class def autocomplete name = params[:q].downcase rank = params[:rank]&.downcase + minimum_rank = params[:minimum_rank]&.downcase @names = Name.where('LOWER(name) LIKE ?', "#{name}%") .or(Name.where('LOWER(name) LIKE ?', "% #{name}%")) .limit(20) @names = @names.where(rank: rank) if rank + minimum_ranks = Name.ranks_at_or_above(minimum_rank) + @names = @names.where(rank: minimum_ranks) if minimum_ranks @names = @names.where(redirect: nil) end diff --git a/app/javascript/packs/autocomplete.js b/app/javascript/packs/autocomplete.js index 139c6fc6..7490ca85 100644 --- a/app/javascript/packs/autocomplete.js +++ b/app/javascript/packs/autocomplete.js @@ -4,11 +4,13 @@ $(document).on("turbolinks:load", function() { url: function(phrase) { var data = $($(event)[0]["srcElement"]).data(); var what = data["autocomplete"]; - var path = ROOT_PATH + what + "/autocomplete.json?q=" + phrase; - for(let i of ["rank"]) { - if(data[i]) { path = path + "&" + i + "=" + data[i] ; } + var url = new URL(what + "/autocomplete.json", ROOT_PATH); + url.searchParams.set("q", phrase); + if(data["rank"]) { url.searchParams.set("rank", data["rank"]); } + if(data["minimumRank"]) { + url.searchParams.set("minimum_rank", data["minimumRank"]); } - return path; + return url.toString(); }, getValue: "value", template: { @@ -20,4 +22,3 @@ $(document).on("turbolinks:load", function() { input.easyAutocomplete(eac_options); console.log('Autocomplete initialized'); }); - diff --git a/app/models/name.rb b/app/models/name.rb index 532ffa52..790de862 100644 --- a/app/models/name.rb +++ b/app/models/name.rb @@ -224,6 +224,11 @@ def ranks %w[domain phylum class order family genus species subspecies] end + def ranks_at_or_above(rank) + rank_index = ranks.index(rank.to_s.downcase) + ranks.take(rank_index + 1) if rank_index + end + def rank_suffixes { kingdom: 'ati', phylum: 'ota', class: 'ia', order: 'ales', diff --git a/app/views/dev/autocomplete.html.erb b/app/views/dev/autocomplete.html.erb new file mode 100644 index 00000000..d02e6577 --- /dev/null +++ b/app/views/dev/autocomplete.html.erb @@ -0,0 +1,52 @@ +<% content_for(:title, 'Autocomplete development') %> + +
<%= link_to '← Development tools', dev_path %>
+ +
+ Use the prefilled fields to compare rank filters. You can also try searches
+ such as Bacteria, Pseudomonadota, or
+ Escherichia.
+
+ Small development-only pages for manually exercising application components. +
+ + diff --git a/app/views/names/autocomplete.json.jbuilder b/app/views/names/autocomplete.json.jbuilder index 2f21be5d..d3abf716 100644 --- a/app/views/names/autocomplete.json.jbuilder +++ b/app/views/names/autocomplete.json.jbuilder @@ -1,5 +1,10 @@ json.(@names) do |name| json.id(name.id) json.value(name.name) - json.display(name.display) + json.rank(name.inferred_rank) + json.display( + safe_join( + [name.display, tag.small(name.inferred_rank, class: 'text-muted')], ' ' + ) + ) end diff --git a/config/routes.rb b/config/routes.rb index ab2cb581..a24437ec 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,11 @@ Rails.application.routes.draw do mount ActionCable.server => '/cable' + if Rails.env.development? + get 'dev', to: 'dev#index' + get 'dev/autocomplete', to: 'dev#autocomplete' + end + resources :tags # General configuration root(to: 'application#main') diff --git a/test/controllers/names_controller_test.rb b/test/controllers/names_controller_test.rb index 23d0edec..4a63d187 100644 --- a/test/controllers/names_controller_test.rb +++ b/test/controllers/names_controller_test.rb @@ -42,6 +42,27 @@ class NamesControllerTest < ActionDispatch::IntegrationTest assert_equal('Genome', entry['nomenclatural_type']['class']) end + test 'autocomplete filters names to an exact rank' do + get autocomplete_names_url( + format: :json, q: 'Bacill', rank: 'phylum' + ) + + assert_response :success + assert_equal ['Bacillota'], autocomplete_values + entry = JSON.parse(response.body).first + assert_equal 'phylum', entry['rank'] + assert_includes entry['display'], '>phylum' + end + + test 'autocomplete filters names to a minimum rank level' do + get autocomplete_names_url( + format: :json, q: 'Bacill', minimum_rank: 'class' + ) + + assert_response :success + assert_equal %w[Bacilli Bacillota], autocomplete_values.sort + end + test 'add_paratype_strain_commit ignores a tampered name_id' do sign_in(users(:curator)) target_name = names(:unregistered) @@ -62,4 +83,10 @@ class NamesControllerTest < ActionDispatch::IntegrationTest name_paratype = NameParatype.last assert_equal(target_name, name_paratype.name) end + + private + + def autocomplete_values + JSON.parse(response.body).pluck('value') + end end diff --git a/test/fixtures/names.yml b/test/fixtures/names.yml index 1b110973..86001f60 100644 --- a/test/fixtures/names.yml +++ b/test/fixtures/names.yml @@ -49,6 +49,21 @@ luteria_ianthellae: rank: species status: 15 # SeqCode +bacillota: + name: Bacillota + rank: phylum + status: 15 # SeqCode + +bacilli: + name: Bacilli + rank: class + status: 15 # SeqCode + +bacillales: + name: Bacillales + rank: order + status: 15 # SeqCode + type_genome_with_locations: name: Testimonas mappensis rank: species diff --git a/test/models/name_test.rb b/test/models/name_test.rb index 30b26064..83f2b5f8 100644 --- a/test/models/name_test.rb +++ b/test/models/name_test.rb @@ -1,6 +1,14 @@ require 'test_helper' class NameTest < ActiveSupport::TestCase + test 'ranks at or above returns ranks from domain through the given rank' do + assert_equal %w[domain phylum class], Name.ranks_at_or_above('class') + end + + test 'ranks at or above returns nil for an unknown rank' do + assert_nil Name.ranks_at_or_above('strain') + end + test 'add_to_register adds name to a draft register' do name = names(:unregistered) register = registers(:draft)