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

Add support for geobox searches to GraphQL #792

Merged
merged 2 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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/graphql/types/geobox_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module Types
class GeoboxType < Types::BaseInputObject
description 'Search within a box specified by pairs of latitudes and longitudes. Their order should be left, ' \
'bottom, right, top'
argument :min_longitude, Float, description: 'A decimal between -180.0 and 180.0 (Western hemisphere is negative)'
argument :min_latitude, Float, description: 'A decimal between -90.0 and 90.0 (Southern hemisphere is negative)'
argument :max_longitude, Float, description: 'A decimal between -180.0 and 180.0 (Western hemisphere is negative)'
argument :max_latitude, Float, description: 'A decimal between -90.0 and 90.0 (Southern hemisphere is negative)'
end
end
13 changes: 8 additions & 5 deletions app/graphql/types/query_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ def record_id(id:, index:)
'award name, etc.'
argument :geodistance, GeodistanceType, required: false, default_value: nil,
description: 'Search within a certain distance of a specific location'
argument :geobox, GeoboxType, required: false, default_value: nil,
description: 'Search within a specified box'
argument :identifiers, String, required: false, default_value: nil,
description: 'Search by unique indentifier; e.g., ISBN, DOI, etc.'
argument :locations, String, required: false, default_value: nil, description: 'Search by locations'
Expand Down Expand Up @@ -79,9 +81,9 @@ def record_id(id:, index:)
'for a list of possible values'
end

def search(searchterm:, citation:, contributors:, funding_information:, geodistance:, identifiers:, locations:,
subjects:, title:, index:, source:, from:, **filters)
query = construct_query(searchterm, citation, contributors, funding_information, geodistance, identifiers,
def search(searchterm:, citation:, contributors:, funding_information:, geodistance:, geobox:, identifiers:,
locations:, subjects:, title:, index:, source:, from:, **filters)
query = construct_query(searchterm, citation, contributors, funding_information, geodistance, geobox, identifiers,
locations, subjects, title, source, filters)

results = Opensearch.new.search(from, query, Timdex::OSClient, highlight_requested?, index)
Expand Down Expand Up @@ -111,14 +113,15 @@ def inject_hits_fields_into_source(hits)
modded_sources
end

def construct_query(searchterm, citation, contributors, funding_information, geodistance, identifiers, locations,
subjects, title, source, filters)
def construct_query(searchterm, citation, contributors, funding_information, geodistance, geobox, identifiers,
locations, subjects, title, source, filters)
query = {}
query[:q] = searchterm
query[:citation] = citation
query[:contributors] = contributors
query[:funding_information] = funding_information
query[:geodistance] = geodistance
query[:geobox] = geobox
query[:identifiers] = identifiers
query[:locations] = locations
query[:subjects] = subjects
Expand Down
182 changes: 182 additions & 0 deletions test/controllers/graphql_controller_v2_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,188 @@ def setup
end
end

test 'graphqlv2 geobox search alone' do
VCR.use_cassette('graphqlv2 geobox') do
post '/graphql', params: { query: '{
search(geobox: {
minLongitude: -73.507,
minLatitude: 41.239,
maxLongitude: -69.928,
maxLatitude: 42.886
}) {
hits
records {
title
locations {
geoshape
kind
value
}
}
}
}' }
assert_equal(200, response.status)
json = JSON.parse(response.body)

assert_nil(json['errors'])
assert(json['data']['search']['hits'].positive?)
end
end

test 'graphqlv2 geobox search required arguments' do
post '/graphql', params: { query: '{
search(geobox: {
minLongitude: -73.507,
minLatitude: 41.239,
maxLongitude: -69.928,
}) {
hits
records {
title
locations {
geoshape
kind
value
}
}
}
}' }
assert_equal(200, response.status)
json = JSON.parse(response.body)

assert(json['errors'].length.positive?)
assert_equal(
"Argument 'maxLatitude' on InputObject 'Geobox' is required. Expected type Float!",
json['errors'].first['message']
)
end

test 'graphqlv2 geobox search longitude order matters' do
# This is fragile to our collection having an equal number of records in both hemispheres.
eastern_hits = 0
western_hits = 0
VCR.use_cassette('graphqlv2 geobox eastern hemisphere') do
post '/graphql', params: { query: '{
search(geobox: {
minLongitude: 0,
minLatitude: -90,
maxLongitude: 180,
maxLatitude: 90
}) {
hits
records {
title
locations {
geoshape
kind
value
}
}
}
}' }
assert_equal(200, response.status)
json = JSON.parse(response.body)

assert_nil(json['errors'])
eastern_hits = json['data']['search']['hits']
assert(eastern_hits.positive?)
end
VCR.use_cassette('graphqlv2 geobox western hemisphere') do
post '/graphql', params: { query: '{
search(geobox: {
minLongitude: 180,
minLatitude: -90,
maxLongitude: 0,
maxLatitude: 90
}) {
hits
records {
title
locations {
geoshape
kind
value
}
}
}
}' }
assert_equal(200, response.status)
json = JSON.parse(response.body)

assert_nil(json['errors'])
western_hits = json['data']['search']['hits']
assert(western_hits.positive?)
end
refute_equal(eastern_hits, western_hits)
end

test 'graphqlv2 geobox search with keyword search' do
VCR.use_cassette('graphqlv2 geobox with keyword') do
post '/graphql', params: { query: '{
search(
searchterm: "train stations",
geobox: {
minLongitude: -73.507,
minLatitude: 41.239,
maxLongitude: -69.928,
maxLatitude: 42.886
}
) {
hits
records {
title
locations {
geoshape
kind
value
}
}
}
}' }
assert_equal(200, response.status)
json = JSON.parse(response.body)

assert_nil(json['errors'])
assert(json['data']['search']['hits'].positive?)
end
end

test 'graphqlv2 geobox search with geodistance search' do
# Should this work? It does.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a slightly better comment might point out that we don't intend this to be a feature, but we aren't going to prevent people from doing it maybe? I guess we could suggest in the docs that only one geo query at a time is supported.

From my exploration around this I think it ANDs the results together but our data is difficult to reason with at times as we have a lot of things that show up effectively globally that feel like they shouldn't so it's hard to know if our queries are wonky or the data is wonky. For sure I get a smaller set of results when I search for a geobox around Boston and combine with with a geodistance search around the center of India than either of those searches independently which feels correct (even if some of the results are questionable... like "Rivers of Alaska" I'd expect not to be in that search but it is)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated this code comment - will be merging now. Thanks!

VCR.use_cassette('graphqlv2 geobox with geodistance') do
post '/graphql', params: { query: '{
search(
geodistance: {
distance: "1km",
latitude: 0,
longitude: 0
},
geobox: {
minLongitude: -73.507,
minLatitude: 41.239,
maxLongitude: -69.928,
maxLatitude: 42.886
}
) {
hits
records {
title
locations {
geoshape
kind
value
}
}
}
}' }
assert_equal(200, response.status)
json = JSON.parse(response.body)

assert_nil(json['errors'])
assert(json['data']['search']['hits'].positive?)
end
end

test 'graphqlv2 search aggregations' do
VCR.use_cassette('graphql v2 search data') do
post '/graphql', params: { query: '{
Expand Down
77 changes: 77 additions & 0 deletions test/vcr_cassettes/graphqlv2_geobox.yml

Large diffs are not rendered by default.

Loading
Loading