Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLDC-3787 Autocomplete address search #2924

Open
wants to merge 45 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
c30fbdb
Prototype
Dinssa Jan 7, 2025
02a14e5
Remove git from dockerfile
kosiakkatrina Jan 16, 2025
7bcff69
UPRN search too
Dinssa Jan 16, 2025
37b4c3a
Merge branch 'update-dockerfile' into CLDC-3787-Autocomplete-address-…
Dinssa Jan 16, 2025
db6f950
Merge branch 'main' into CLDC-3787-OLDAutocomplete-address-search
Dinssa Jan 22, 2025
1c169c7
Revert address client and use uprn client
Dinssa Jan 30, 2025
420990b
Add address search to lettings too
Dinssa Jan 30, 2025
3fb4b59
Updates with lettings logs
Dinssa Jan 30, 2025
e8e3a5e
Update copy
Dinssa Jan 30, 2025
d844c97
Move guidance to partial
Dinssa Jan 30, 2025
ebcdc0d
Fix uprn return
Dinssa Jan 30, 2025
3189330
Merge branch 'main' into CLDC-3787-Autocomplete-address-search
Dinssa Jan 30, 2025
804e19f
Delete new db file, restore old
Dinssa Jan 30, 2025
bcdc32f
Lint
Dinssa Jan 30, 2025
a287e02
Remove old db file
Dinssa Jan 30, 2025
4f12468
Lint
Dinssa Jan 30, 2025
c37ceaa
Add new db file, remove old
Dinssa Jan 30, 2025
b2c90da
JS lint
Dinssa Jan 30, 2025
eec656a
Update schema
Dinssa Jan 30, 2025
3e2ef02
Add manual entry option
Dinssa Jan 30, 2025
03e125d
Update derived variables
Dinssa Jan 30, 2025
b8c86ff
Comment out old version of find address in 2024
Dinssa Jan 30, 2025
d33d2ce
Remove db column
Dinssa Jan 30, 2025
f2f08c6
Add new db columns
Dinssa Jan 30, 2025
069f203
Update guidance partial
Dinssa Jan 30, 2025
27c3f19
Add unless to migration
Dinssa Jan 31, 2025
bcd1e60
Add migration files to remove and readd
Dinssa Feb 3, 2025
5df87a1
authenticate user
Dinssa Feb 3, 2025
061da9c
Remove file
Dinssa Feb 3, 2025
36a40b6
Delete migration files
Dinssa Feb 3, 2025
bbefca7
Add search url
Dinssa Feb 3, 2025
39e00b2
Add search url
Dinssa Feb 3, 2025
391f56f
Merge remote-tracking branch 'origin/CLDC-3787-Autocomplete-address-s…
Dinssa Feb 3, 2025
bfdcbca
Fix onConfirm
Dinssa Feb 3, 2025
22577c8
Add manual entry button instead of change skip link
Dinssa Feb 3, 2025
9f0a211
Revert "Add manual entry button instead of change skip link"
Dinssa Feb 3, 2025
ce8e20d
Revert "Revert "Add manual entry button instead of change skip link""
Dinssa Feb 3, 2025
9173382
Replace uprn question
Dinssa Feb 3, 2025
7dc86f8
Update question copy
Dinssa Feb 3, 2025
c750f4b
Allow changing the address search value
Dinssa Feb 3, 2025
2ae3501
Rename address autocomplete to address search
Dinssa Feb 4, 2025
302a2e4
Add buttons to switch between address questions
Dinssa Feb 4, 2025
f5001bc
Fix controller logic
Dinssa Feb 4, 2025
8d9891e
Enable adding question numbers to page headers
Dinssa Feb 4, 2025
3afb0c0
Update skip links
Dinssa Feb 4, 2025
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
72 changes: 72 additions & 0 deletions app/controllers/address_search_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
class AddressSearchController < ApplicationController
before_action :authenticate_user!

def index
query = params[:query]

if query.match?(/\A\d+\z/) && query.length > 5
# Query is all numbers and greater than 5 digits, assume it's a UPRN
service = UprnClient.new(query)
service.call

if service.error.present?
render json: { error: service.error }, status: :unprocessable_entity
else
render json: [{ address: service.result["ADDRESS"], uprn: service.result["UPRN"] }]
end
elsif query.match?(/[a-zA-Z]/)
# Query contains letters, assume it's an address
service = AddressClient.new(query)
service.call

if service.error.present?
render json: { error: service.error }, status: :unprocessable_entity
else
render json: service.result.map { |result| { address: result["ADDRESS"], uprn: result["UPRN"] } }
end
else
# Query is ambiguous, use both APIs and merge results
address_service = AddressClient.new(query)
uprn_service = UprnClient.new(query)

address_service.call
uprn_service.call

results = (address_service.result || []) + (uprn_service.result || [])

if address_service.error.present? && uprn_service.error.present?
render json: { error: "Address and UPRN are not recognised. Check the input." }, status: :unprocessable_entity
else
render json: results.map { |result| { address: result["ADDRESS"], uprn: result["UPRN"] } }
end
end
end

def manual_input
log = params[:log_type] == "lettings_log" ? LettingsLog.find(params[:log_id]) : SalesLog.find(params[:log_id])
log.update!(uprn: nil, uprn_known: 0, uprn_confirmed: nil, address_search: nil)
redirect_to manual_address_link(log)
end

def search_input
log = params[:log_type] == "lettings_log" ? LettingsLog.find(params[:log_id]) : SalesLog.find(params[:log_id])
if log.log_type == "lettings_log"
log.update!(uprn: nil, uprn_known: 0, uprn_confirmed: nil, address_search: nil, address_line1: nil, address_line2: nil, town_or_city: nil, county: nil, postcode_full: nil, is_la_inferred: false)
else
log.update!(uprn: nil, uprn_known: 0, uprn_confirmed: nil, address_search: nil, address_line1: nil, address_line2: nil, town_or_city: nil, county: nil, postcode_full: nil, is_la_inferred: false, pcode1: nil, pcode2: nil)
end
redirect_to search_address_link(log)
end

private

def manual_address_link(log)
base_url = send("#{log.log_type}_url", log)
"#{base_url}/address"
end

def search_address_link(log)
base_url = send("#{log.log_type}_url", log)
"#{base_url}/address-search"
end
end
64 changes: 64 additions & 0 deletions app/frontend/controllers/address_search_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Controller } from '@hotwired/stimulus'
import accessibleAutocomplete from 'accessible-autocomplete'
import 'accessible-autocomplete/dist/accessible-autocomplete.min.css'

const options = []

const fetchOptions = async (query, searchUrl) => {
const response = await fetch(`${searchUrl}?query=${encodeURIComponent(query)}`)
const data = await response.json()
console.log(data)
console.log("Test 004")
return data
}

const fetchAndPopulateSearchResults = async (query, populateResults, searchUrl, populateOptions, selectEl) => {
if (/\S/.test(query)) {
const results = await fetchOptions(query, searchUrl)
console.log(results) // address and uprn keys returned per result
populateOptions(results, selectEl)
populateResults(Object.values(results).map((o) => o.address))
}
}

const populateOptions = (results, selectEl) => {
selectEl.innerHTML = ''

results.forEach((result) => {
const option = document.createElement('option')
option.value = result.uprn
option.innerHTML = result.address
option.setAttribute('address', result.address)
selectEl.appendChild(option)
options.push(option)
})
}

export default class extends Controller {
connect () {
const searchUrl = JSON.parse(this.element.dataset.info).search_url
const selectEl = this.element

accessibleAutocomplete.enhanceSelectElement({
defaultValue: '',
selectElement: selectEl,
minLength: 1,
source: (query, populateResults) => {
fetchAndPopulateSearchResults(query, populateResults, searchUrl, populateOptions, selectEl)
},
autoselect: true,
showNoOptionsFound: true,
placeholder: 'Start typing to search',
templates: { suggestion: (value) => value },
onConfirm: (val) => {
console.log({val})
const selectedResult = Array.from(selectEl.options).find(option => option.text === val)

console.log({selectedResult})
if (selectedResult) {
selectedResult.selected = true
}
}
})
}
}
3 changes: 3 additions & 0 deletions app/frontend/controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import FilterLayoutController from './filter_layout_controller.js'

import TabsController from './tabs_controller.js'

import AddressSearchController from './address_search_controller.js'

application.register('accessible-autocomplete', AccessibleAutocompleteController)
application.register('conditional-filter', ConditionalFilterController)
application.register('conditional-question', ConditionalQuestionController)
Expand All @@ -27,3 +29,4 @@ application.register('numeric-question', NumericQuestionController)
application.register('filter-layout', FilterLayoutController)
application.register('search', SearchController)
application.register('tabs', TabsController)
application.register('address-search', AddressSearchController)
19 changes: 19 additions & 0 deletions app/models/derived_variables/lettings_log_variables.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ def set_derived_fields!
self.postcode_known = nil
self.postcode_full = nil
self.la = nil
self.address_line1_input = nil
self.postcode_full_input = nil
end
end

Expand All @@ -112,6 +114,23 @@ def set_derived_fields!
self.previous_la_known = nil if is_renewal?
end

if address_search
self.uprn = address_search
if uprn_known_was == 1
self.address_line1 = nil
self.address_line2 = nil
self.town_or_city = nil
self.county = nil
self.postcode_known = nil
self.postcode_full = nil
self.la = nil
self.address_line1_input = nil
self.postcode_full_input = nil
end
self.uprn_known = 1
self.uprn_confirmed = 1
end

if is_renewal?
self.underoccupation_benefitcap = 2 if collection_start_year == 2021
self.voiddate = startdate
Expand Down
21 changes: 21 additions & 0 deletions app/models/derived_variables/sales_log_variables.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ def set_derived_fields!
self.pcodenk = nil
self.postcode_full = nil
self.la = nil
self.address_line1_input = nil
self.postcode_full_input = nil
end
end

Expand All @@ -75,6 +77,25 @@ def set_derived_fields!
self.pcodenk = nil
self.postcode_full = nil
self.la = nil
self.address_line1_input = nil
self.postcode_full_input = nil
end

if address_search
self.uprn = address_search
if uprn_known_was == 1
self.address_line1 = nil
self.address_line2 = nil
self.town_or_city = nil
self.county = nil
self.pcodenk = nil
self.postcode_full = nil
self.la = nil
self.address_line1_input = nil
self.postcode_full_input = nil
end
self.uprn_known = 1
self.uprn_confirmed = 1
end

if form.start_year_2025_or_later? && is_bedsit?
Expand Down
3 changes: 3 additions & 0 deletions app/models/form/lettings/pages/address_fallback.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def initialize(id, hsh, subsection)
{ "is_supported_housing?" => false, "uprn_known" => 0, "address_options_present?" => false },
{ "is_supported_housing?" => false, "uprn_confirmed" => 0, "address_options_present?" => false },
]
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

def questions
Expand All @@ -22,4 +23,6 @@ def questions
Form::Lettings::Questions::PostcodeForFullAddress.new(nil, nil, self),
]
end

QUESTION_NUMBER_FROM_YEAR = { 2024 => 13, 2025 => 13 }.freeze
end
21 changes: 21 additions & 0 deletions app/models/form/lettings/pages/address_search.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class Form::Lettings::Pages::AddressSearch < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "address_search"
@depends_on = [
{ "address_manually_entered?" => false },
]
end

def questions
@questions ||= [
Form::Lettings::Questions::AddressSearch.new(nil, nil, self),
]
end

def skip_href(log = nil)
return unless log

"/#{log.log_type.dasherize}s/#{log.id}/first-time-property-let-as-social-housing"
end
end
48 changes: 48 additions & 0 deletions app/models/form/lettings/questions/address_search.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
class Form::Lettings::Questions::AddressSearch < ::Form::Question
def initialize(id, hsh, page)
super
@id = "address_search"
@type = "address_search"
@plain_label = true
@bottom_guidance_partial = "address_search"
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
@hide_question_number_on_page = true
end

def answer_options(log = nil, _user = nil)
return {} unless ActiveRecord::Base.connected?
return {} unless log&.address_options

answer_opts = {}

(0...[log.address_options.count, 10].min).each do |i|
answer_opts[log.address_options[i][:uprn]] = { "value" => log.address_options[i][:address] }
end

answer_opts["divider"] = { "value" => true }
answer_opts
end

def get_extra_check_answer_value(log)
return unless log.uprn_known == 1

value = [
log.address_line1,
log.address_line2,
log.town_or_city,
log.county,
log.postcode_full,
(LocalAuthority.find_by(code: log.la)&.name if log.la.present?),
].select(&:present?)

return unless value.any?

"\n\n#{value.join("\n")}"
end

def displayed_answer_options(log, user = nil)
answer_options(log, user).transform_values { |value| value["value"] } || {}
end

QUESTION_NUMBER_FROM_YEAR = { 2024 => 12, 2025 => 12 }.freeze
end
2 changes: 1 addition & 1 deletion app/models/form/lettings/questions/uprn_confirmation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ def notification_banner(log = nil)
end

def hidden_in_check_answers?(log, _current_user = nil)
log.uprn_known != 1 || log.uprn_confirmed.present?
log.uprn_known != 1 || log.uprn_confirmed.present? || log.address_search.present?
end
end
11 changes: 6 additions & 5 deletions app/models/form/lettings/subsections/property_information.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ def pages
def uprn_questions
if form.start_year_2024_or_later?
[
Form::Lettings::Pages::Uprn.new(nil, nil, self),
Form::Lettings::Pages::UprnConfirmation.new(nil, nil, self),
Form::Lettings::Pages::AddressMatcher.new(nil, nil, self),
Form::Lettings::Pages::NoAddressFound.new(nil, nil, self), # soft validation
Form::Lettings::Pages::UprnSelection.new(nil, nil, self),
# Form::Lettings::Pages::Uprn.new(nil, nil, self),
# Form::Lettings::Pages::UprnConfirmation.new(nil, nil, self),
Form::Lettings::Pages::AddressSearch.new(nil, nil, self),
# Form::Lettings::Pages::AddressMatcher.new(nil, nil, self),
# Form::Lettings::Pages::NoAddressFound.new(nil, nil, self), # soft validation
# Form::Lettings::Pages::UprnSelection.new(nil, nil, self),
Form::Lettings::Pages::AddressFallback.new(nil, nil, self),
]
else
Expand Down
3 changes: 2 additions & 1 deletion app/models/form/page.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class Form::Page
attr_accessor :id, :header_partial, :description, :questions, :depends_on, :title_text,
:informative_text, :subsection, :hide_subsection_label, :next_unresolved_page_id,
:skip_text, :interruption_screen_question_ids, :submit_text
:skip_text, :interruption_screen_question_ids, :submit_text, :question_number

def initialize(id, hsh, subsection)
@id = id
Expand All @@ -11,6 +11,7 @@ def initialize(id, hsh, subsection)
@header_partial = hsh["header_partial"]
@description = hsh["description"]
@questions = hsh["questions"].map { |q_id, q| Form::Question.new(q_id, q, self) }
@question_number = hsh["question_number"]
@depends_on = hsh["depends_on"]
@title_text = hsh["title_text"]
@informative_text = hsh["informative_text"]
Expand Down
3 changes: 3 additions & 0 deletions app/models/form/sales/pages/address_fallback.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def initialize(id, hsh, subsection)
{ "uprn_known" => 0, "address_options_present?" => false },
{ "uprn_confirmed" => 0, "address_options_present?" => false },
]
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end

def questions
Expand All @@ -22,4 +23,6 @@ def questions
Form::Sales::Questions::PostcodeForFullAddress.new(nil, nil, self),
]
end

QUESTION_NUMBER_FROM_YEAR = { 2024 => 16, 2025 => 16 }.freeze
end
21 changes: 21 additions & 0 deletions app/models/form/sales/pages/address_search.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class Form::Sales::Pages::AddressSearch < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "address_search"
@depends_on = [
{ "address_manually_entered?" => false },
]
end

def questions
@questions ||= [
Form::Sales::Questions::AddressSearch.new(nil, nil, self),
]
end

def skip_href(log = nil)
return unless log

"/#{log.log_type.dasherize}s/#{log.id}/property-number-of-bedrooms"
end
end
1 change: 0 additions & 1 deletion app/models/form/sales/pages/no_address_found.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ def initialize(id, hsh, subsection)
{ "uprn_known" => nil, "address_options_present?" => false },
{ "uprn_known" => 0, "address_options_present?" => false },
{ "uprn_confirmed" => 0, "address_options_present?" => false },

]
end

Expand Down
Loading
Loading