Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions app/controllers/dev_controller.rb
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions app/controllers/names_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 6 additions & 5 deletions app/javascript/packs/autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -20,4 +22,3 @@ $(document).on("turbolinks:load", function() {
input.easyAutocomplete(eac_options);
console.log('Autocomplete initialized');
});

5 changes: 5 additions & 0 deletions app/models/name.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
52 changes: 52 additions & 0 deletions app/views/dev/autocomplete.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<% content_for(:title, 'Autocomplete development') %>

<p><%= link_to '← Development tools', dev_path %></p>

<h1>Name autocomplete</h1>

<p class="text-muted">
Use the prefilled fields to compare rank filters. You can also try searches
such as <code>Bacteria</code>, <code>Pseudomonadota</code>, or
<code>Escherichia</code>.
</p>

<div class="row">
<div class="col-lg-6">
<div class="form-group">
<label for="autocomplete-any-rank">Any rank</label>
<input id="autocomplete-any-rank" class="form-control" type="text"
value="Escher" data-behavior="autocomplete"
data-autocomplete="names">
<small class="form-text text-muted">
Includes names at every rank, including phylum.
</small>
</div>

<div class="form-group">
<label for="autocomplete-phylum">Phylum only</label>
<input id="autocomplete-phylum" class="form-control" type="text"
value="Pseu" data-behavior="autocomplete"
data-autocomplete="names" data-rank="phylum">
</div>
</div>

<div class="col-lg-6">
<div class="form-group">
<label for="autocomplete-class-or-above">Class or above</label>
<input id="autocomplete-class-or-above" class="form-control" type="text"
value="Bac" data-behavior="autocomplete"
data-autocomplete="names" data-minimum-rank="class">
<small class="form-text text-muted">Domain, phylum, or class.</small>
</div>

<div class="form-group">
<label for="autocomplete-genus-or-above">Genus or above</label>
<input id="autocomplete-genus-or-above" class="form-control" type="text"
value="Escher" data-behavior="autocomplete"
data-autocomplete="names" data-minimum-rank="genus">
<small class="form-text text-muted">
Domain through genus; excludes species and subspecies.
</small>
</div>
</div>
</div>
17 changes: 17 additions & 0 deletions app/views/dev/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<% content_for(:title, 'Development tools') %>

<h1>Development tools</h1>

<p class="text-muted">
Small development-only pages for manually exercising application components.
</p>

<div class="list-group">
<a href="<%= dev_autocomplete_path %>"
class="list-group-item list-group-item-action">
<strong class="d-block">Name autocomplete</strong>
<span class="text-muted">
Compare unrestricted, exact-rank, and minimum-rank name searches.
</span>
</a>
</div>
7 changes: 6 additions & 1 deletion app/views/names/autocomplete.json.jbuilder
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -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')
Expand Down
27 changes: 27 additions & 0 deletions test/controllers/names_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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</small>'
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)
Expand All @@ -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
15 changes: 15 additions & 0 deletions test/fixtures/names.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions test/models/name_test.rb
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
Loading