Browse Source

add synonyms, appended text and hint text to the autocomplete (#739)

* add synonyms, appended text and hint text to the autocomplete

* update test part 1

* refactor synonym, append and hint methods to take in resource

* Fix tests and lint

* Change min autocomplete character length to 1

* change test
pull/701/head
kosiakkatrina 2 years ago committed by GitHub
parent
commit
8a6148f9d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/frontend/controllers/accessible_autocomplete_controller.js
  2. 28
      app/frontend/modules/search.js
  3. 24
      app/models/form/question.rb
  4. 4
      app/models/form/setup/questions/scheme_id.rb
  5. 12
      app/models/scheme.rb
  6. 25
      app/views/form/_select_question.html.erb
  7. 27
      spec/features/form/accessible_autocomplete_spec.rb
  8. 2
      spec/models/form/setup/questions/scheme_id_spec.rb

2
app/frontend/controllers/accessible_autocomplete_controller.js

@ -15,7 +15,7 @@ export default class extends Controller {
accessibleAutocomplete.enhanceSelectElement({
defaultValue: '',
selectElement: selectEl,
minLength: 2,
minLength: 1,
source: (query, populateResults) => {
if (/\S/.test(query)) {
populateResults(sort(query, options))

28
app/frontend/modules/search.js

@ -12,9 +12,13 @@ const clean = (text) =>
.toLowerCase()
const cleanseOption = (option) => {
const synonyms = (option.synonyms || []).map(clean)
option.clean = {
name: clean(option.name),
nameWithoutStopWords: removeStopWords(option.name),
synonyms,
synonymsWithoutStopWords: synonyms.map(removeStopWords),
boost: option.boost || 1
}
@ -41,19 +45,34 @@ const startsWith = (word, query) => word.search(startsWithRegExp(query)) === 0
const wordsStartsWithQuery = (word, regExps) =>
regExps.every((regExp) => word.search(regExp) >= 0)
const calculateWeight = ({ name, nameWithoutStopWords }, query) => {
const anyMatch = (words, query, evaluatorFunc) => words.some((word) => evaluatorFunc(word, query))
const synonymsExactMatch = (synonyms, query) => anyMatch(synonyms, query, exactMatch)
const synonymsStartsWith = (synonyms, query) => anyMatch(synonyms, query, startsWith)
const wordInSynonymStartsWithQuery = (synonyms, startsWithQueryWordsRegexes) =>
anyMatch(synonyms, startsWithQueryWordsRegexes, wordsStartsWithQuery)
const calculateWeight = ({ name, synonyms, nameWithoutStopWords, synonymsWithoutStopWords }, query) => {
const queryWithoutStopWords = removeStopWords(query)
if (exactMatch(name, query)) return 100
if (exactMatch(nameWithoutStopWords, queryWithoutStopWords)) return 95
if (synonymsExactMatch(synonyms, query)) return 75
if (synonymsExactMatch(synonymsWithoutStopWords, queryWithoutStopWords)) return 70
if (startsWith(name, query)) return 60
if (startsWith(nameWithoutStopWords, queryWithoutStopWords)) return 55
if (synonymsStartsWith(synonyms, query)) return 50
if (synonymsStartsWith(synonyms, queryWithoutStopWords)) return 40
const startsWithRegExps = queryWithoutStopWords
.split(/\s+/)
.map(startsWithRegExp)
if (wordsStartsWithQuery(nameWithoutStopWords, startsWithRegExps)) return 25
if (wordInSynonymStartsWithQuery(synonymsWithoutStopWords, startsWithRegExps)) return 10
return 0
}
@ -91,8 +110,8 @@ export const sort = (query, options) => {
export const suggestion = (value, options) => {
const option = options.find((o) => o.name === value)
if (option) {
const html = `<span>${value}</span>`
return html
const html = option.append ? `<span>${value}</span> <span class="autocomplete__option__append">${option.append}</span>` : `<span>${value}</span>`
return option.hint ? `${html}<div class="autocomplete__option__hint">${option.hint}</div>` : html
} else {
return '<span>No results found</span>'
}
@ -101,6 +120,9 @@ export const suggestion = (value, options) => {
export const enhanceOption = (option) => {
return {
name: option.label,
synonyms: (option.getAttribute('data-synonyms') ? option.getAttribute('data-synonyms').split('|') : []),
append: option.getAttribute('data-append'),
hint: option.getAttribute('data-hint'),
boost: parseFloat(option.getAttribute('data-boost')) || 1
}
}

24
app/models/form/question.rb

@ -136,7 +136,11 @@ class Form::Question
labels = answer_options[value.to_s]
labels["value"] if labels
when "select"
answer_options[value.to_s]
if answer_options[value.to_s].respond_to?(:service_name)
answer_options[value.to_s].service_name
else
answer_options[value.to_s]
end
else
value.to_s
end
@ -191,6 +195,24 @@ class Form::Question
label
end
def answer_option_synonyms(resource)
return unless resource.respond_to?(:synonyms)
resource.synonyms
end
def answer_option_append(resource)
return unless resource.respond_to?(:appended_text)
resource.appended_text
end
def answer_option_hint(resource)
return unless resource.respond_to?(:hint)
resource.hint
end
private
def selected_answer_option_is_derived?(case_log)

4
app/models/form/setup/questions/scheme_id.rb

@ -13,8 +13,8 @@ class Form::Setup::Questions::SchemeId < ::Form::Question
answer_opts = {}
return answer_opts unless ActiveRecord::Base.connected?
Scheme.select(:id, :service_name).each_with_object(answer_opts) do |scheme, hsh|
hsh[scheme.id.to_s] = scheme.service_name
Scheme.select(:id, :service_name, :primary_client_group, :secondary_client_group).each_with_object(answer_opts) do |scheme, hsh|
hsh[scheme.id.to_s] = scheme
hsh
end
end

12
app/models/scheme.rb

@ -142,4 +142,16 @@ class Scheme < ApplicationRecord
{ name: "Intended length of stay", value: intended_stay },
]
end
def synonyms
locations.map(&:postcode).join(",")
end
def appended_text
"(#{locations.count} locations)"
end
def hint
[primary_client_group, secondary_client_group].filter(&:present?).join(", ")
end
end

25
app/views/form/_select_question.html.erb

@ -1,13 +1,18 @@
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.guidance_partial %>
<% selected = @case_log.public_send(question.id) || "" %>
<% answers = question.displayed_answer_options(@case_log).map { |key, value| OpenStruct.new(id: key, name: value) } %>
<%= f.govuk_collection_select question.id.to_sym,
answers,
:id,
:name,
caption: caption(caption_text, page_header, conditional),
label: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe },
options: { disabled: [""], selected: },
"data-controller": "accessible-autocomplete" %>
<% answers = question.displayed_answer_options(@case_log).map { |key, value| OpenStruct.new(id: key, name: value.respond_to?(:service_name) ? value.service_name : nil, resource: value) } %>
<%= f.govuk_select(question.id.to_sym,
label: legend(question, page_header, conditional),
"data-controller": "accessible-autocomplete",
caption: caption(caption_text, page_header, conditional),
hint: { text: question.hint_text&.html_safe }) do %>
<% answers.each do |answer| %>
<option value="<%= answer.id %>"
data-synonyms="<%= question.answer_option_synonyms(answer.resource) %>"
data-append="<%= question.answer_option_append(answer.resource) %>"
data-hint="<%= question.answer_option_hint(answer.resource) %>"
<%= @case_log[question.id] == answer.name || @case_log[question.id] == answer.resource || @case_log[question.id].to_s == answer.id ? "selected" : "" %>
<%= answer.id == "" ? "disabled" : "" %>><%= answer.name || answer.resource %></option>
<% end %>
<% end %>

27
spec/features/form/accessible_autocomplete_spec.rb

@ -14,6 +14,7 @@ RSpec.describe "Accessible Automcomplete" do
is_la_inferred: false,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
created_by: user,
)
end
@ -58,6 +59,32 @@ RSpec.describe "Accessible Automcomplete" do
end
end
context "when searching schemes" do
let(:scheme) { FactoryBot.create(:scheme, owning_organisation_id: case_log.created_by.organisation_id, primary_client_group: "Q", secondary_client_group: "P") }
before do
FactoryBot.create(:location, scheme:, postcode: "W6 0ST")
FactoryBot.create(:location, scheme:, postcode: "SE6 1LB")
case_log.update!(needstype: 2)
visit("/logs/#{case_log.id}/scheme")
end
it "can match on synonyms", js: true do
find("#case-log-scheme-id-field").click.native.send_keys("w", "6", :down, :enter)
expect(find("#case-log-scheme-id-field").value).to eq(scheme.service_name)
end
it "displays appended text next to the options", js: true do
find("#case-log-scheme-id-field").click.native.send_keys("w", "6", :down, :enter)
expect(find(".autocomplete__option__append", visible: :hidden, text: /(2 locations)/)).to be_present
end
it "displays hint text under the options", js: true do
find("#case-log-scheme-id-field").click.native.send_keys("w", "6", :down, :enter)
expect(find(".autocomplete__option__hint", visible: :hidden, text: /Young people at risk, Young people leaving care/)).to be_present
end
end
it "has the correct option selected if one has been saved" do
case_log.update!(postcode_known: 0, previous_la_known: 1, prevloc: "E07000178")
visit("/logs/#{case_log.id}/accessible-select")

2
spec/models/form/setup/questions/scheme_id_spec.rb

@ -51,7 +51,7 @@ RSpec.describe Form::Setup::Questions::SchemeId, type: :model do
end
it "has the correct answer_options based on the schemes the user's organisation owns or manages" do
expected_answer = { scheme.id.to_s => scheme.service_name }
expected_answer = { scheme.id.to_s => scheme }
expect(question.displayed_answer_options(case_log)).to eq(expected_answer)
end
end

Loading…
Cancel
Save