Skip to content

Commit

Permalink
Merge pull request #240 from citizensadvice/search-volunteering-oppor…
Browse files Browse the repository at this point in the history
…tunities-by-member

allow searching for volunteering opportunities by member ID (used for local office minisites)
  • Loading branch information
cnorthwood authored Jan 30, 2025
2 parents 16e948f + fe135f9 commit 7d1f75a
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 17 deletions.
30 changes: 27 additions & 3 deletions app/controllers/api/v2/search_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ def offices

def volunteering_opportunities
if search_criteria_specified? && search_q_is_valid?
render json: volunteering_search_response(params[:q])
query_type = params[:query_type].presence || "location"
volunteering_search_by_query_type(query_type)
else
render status: :bad_request, json: missing_search_param_json
render status: :bad_request, json: missing_search_type_json
end
end

Expand Down Expand Up @@ -54,7 +55,18 @@ def office_search_response(query)
{ match_type: normalised_location.nil? ? "fuzzy" : "exact", results: offices.map { |office| office_as_search_result_json(office) } }
end

def volunteering_search_response(query)
def volunteering_search_by_query_type(query_type)
case query_type
when "location"
render json: volunteering_location_search_response(params[:q])
when "member"
render json: volunteering_member_search_response(params[:q])
else
render status: :bad_request, json: missing_search_param_json
end
end

def volunteering_location_search_response(query)
offices, normalised_location = OfficeSearch.by_location query, only_with_vacancies: true
rescue OfficeSearch::UnknownLocationError
{ match_type: "unknown", results: [] }
Expand All @@ -66,9 +78,21 @@ def volunteering_search_response(query)
end }
end

def volunteering_member_search_response(member_id)
member = Office.find_by(id: member_id, office_type: "member")
return { match_type: "unknown", results: [] } if member.nil?

offices_with_vacancies = member.children.where.not(volunteer_roles: [])
{ match_type: "member", results: offices_with_vacancies.map { |office| volunteering_opportunity_as_search_result_json(office) } }
end

def missing_search_param_json
{ type: "https://local-office-search.citizensadvice.org.uk/schemas/v2/errors#missing-param", status: 400, title: "Required parameter (q) missing" }
end

def missing_search_type_json
{ type: "https://local-office-search.citizensadvice.org.uk/schemas/v2/errors#missing-param", status: 400, title: "Required parameter (query_type) set to invalid value" }
end
end
end
end
9 changes: 5 additions & 4 deletions app/controllers/api/v2/serialisers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,14 @@ def volunteering_opportunity_as_json(office)
}
end

def volunteering_opportunity_as_search_result_json(office, distance_from)
{
def volunteering_opportunity_as_search_result_json(office, distance_from = nil)
result = {
id: office.id,
office: office_as_search_result_json(office),
roles: office.volunteer_roles,
distance: distance_in_miles(distance_from, office.location)
roles: office.volunteer_roles
}
result[:distance] = distance_in_miles(distance_from, office.location) unless distance_from.nil?
result
end

private
Expand Down
7 changes: 4 additions & 3 deletions spec/requests/v2/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ module ApiV2Schema
distance: { type: %i[number null] },
roles: VOLUNTEER_ROLES
},
required: %i[id office distance roles],
required: %i[id office roles],
additionalProperties: false
}.freeze

Expand All @@ -180,9 +180,10 @@ module ApiV2Schema
"$id": "https://local-office-search.citizensadvice.org.uk/schemas/v2/results/volunteering-opportunity",
type: :object,
properties: {
match_type: { type: :string, enum: %w[all exact fuzzy unknown out_of_area_scotland out_of_area_ni],
match_type: { type: :string, enum: %w[member exact fuzzy unknown out_of_area_scotland out_of_area_ni],
description: %(
* `all` means no search term was specified so all volunteering opportunities are returned.
* `member` means the search terms matched a member ID and the results are all the results for this
member (might be none).
* `exact` means the search term matched an exact location, so volunteering opportunities are ordered around
this location
* `fuzzy` means the search term matched a wider locality and not an individual point, so the results may
Expand Down
58 changes: 58 additions & 0 deletions spec/requests/v2/search_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,20 @@
produces "application/json"
parameter name: :q, in: :query, type: :string, required: true,
description: "the search terms to use"
parameter name: :query_type, in: :query, required: true,
enum: {
location: "treat q as a location to search around",
member: "treat q as a member ID to return all the opportunities for"
},
default: "location",
description: "the search terms to use"

response "200", "a list of search results when a query string is specified" do
schema ApiV2Schema::VOLUNTEERING_SEARCH_RESULTS

context "when a fuzzy search is done" do
let(:q) { "Testshire" }
let(:query_type) { "location" }

let(:office) do
Office.new id: generate_salesforce_id,
Expand All @@ -217,6 +225,7 @@

context "when there are offices with no volunteering opportunities" do
let(:q) { "Testshire" }
let(:query_type) { "location" }

let(:office) do
Office.new id: generate_salesforce_id,
Expand All @@ -237,6 +246,7 @@

context "when an exact search is done" do
let(:q) { "AA1 1AA" }
let(:query_type) { "location" }

let(:nearest_office) do
Office.new(id: generate_salesforce_id,
Expand Down Expand Up @@ -266,12 +276,60 @@
expect_result_ids_in_response response, "exact", [nearest_office.id, far_office.id]
end
end

context "when a search by member is done and results match" do
let(:q) { member.id }
let(:query_type) { "member" }

let(:member) do
Office.new(id: generate_salesforce_id,
office_type: :member,
name: "Testshire Citizens Advice")
end

let(:member_office) do
Office.new(id: generate_salesforce_id,
parent_id: member.id,
office_type: :office,
name: "Testshire Citizens Advice",
volunteer_roles: ["admin_and_customer_service"],
location: "POINT(2.0 2.0)")
end

before do
member.save!
member_office.save!
other_member = Office.create(id: generate_salesforce_id,
office_type: :member,
name: "Othertown Citizens Advice")
Office.create(id: generate_salesforce_id,
parent_id: other_member.id,
office_type: :office,
name: "Othertown Citizens Advice",
volunteer_roles: ["admin_and_customer_service"],
location: "POINT(3.0 3.0)")
end

run_test! do |response|
expect_result_ids_in_response response, "member", [member_office.id]
end
end

context "when a search by member is done and there is no member with that ID" do
let(:q) { generate_salesforce_id }
let(:query_type) { "member" }

run_test! do |response|
expect_result_ids_in_response response, "unknown", []
end
end
end

response "400", "If query is not specified" do
schema ApiV2Schema::JSON_PROBLEM

let(:q) { "" }
let(:query_type) { "location" }

run_test!
end
Expand Down
23 changes: 16 additions & 7 deletions swagger/v2/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,16 @@ paths:
description: the search terms to use
schema:
type: string
- name: query_type
in: query
required: true
enum:
location: treat q as a location to search around
member: treat q as a member ID to return all the opportunities for
default: location
description: "the search terms to use:\n * `location` treat q as a location
to search around\n * `member` treat q as a member ID to return all the opportunities
for\n "
responses:
'200':
description: a list of search results when a query string is specified
Expand All @@ -561,17 +571,17 @@ paths:
match_type:
type: string
enum:
- all
- member
- exact
- fuzzy
- unknown
- out_of_area_scotland
- out_of_area_ni
description: "\n * `all` means
no search term was specified so all volunteering opportunities
are returned.\n * `exact` means
the search term matched an exact location, so volunteering opportunities
are ordered around\n this
description: "\n * `member` means
the search terms matched a member ID and the results are all
the results for this member (might be none).\n *
`exact` means the search term matched an exact location, so
volunteering opportunities are ordered around\n this
location\n * `fuzzy` means the
search term matched a wider locality and not an individual point,
so the results may\n only
Expand Down Expand Up @@ -652,7 +662,6 @@ paths:
required:
- id
- office
- distance
- roles
additionalProperties: false
required:
Expand Down

0 comments on commit 7d1f75a

Please sign in to comment.