-
Notifications
You must be signed in to change notification settings - Fork 8
Open
Description
Hey! I'm trying to decode elastic URL queries from elastic's search-ui package. I've written a lot of manual code to convert a query string to an app search compatible filter/query combo. I based most of it off of the urlToState in URLManager from search-ui
I was wondering if the team would be open to including an official version of this parsing logic in this library, so that we can parse search-ui query strings as app search queries from ruby
Code is below – if you think it's a good idea, I can make a PR to incorporate this into the codebase, or if one of y'all would like to tackle this from the ground up, that would work too – thanks!
# typed: true
module ElasticService
class Util
extend T::Sig
# TODO: Probably need to support nested filters?
sig { params(query_string: String).returns(T.untyped) }
def self.query_string_to_app_search_query(query_string)
query_object_raw = raw_nested_query_to_elastic_query(
Rack::Utils.parse_nested_query(query_string.delete_prefix("?"))
)
cleaned_filters = query_object_raw['filters'].map do |filter_entry|
{
"any" => filter_entry["values"].map do |sub_filter|
cleaned_sub_filter = sub_filter
if cleaned_sub_filter.class == Hash
# Elastic gets angry if we add extra fields (:
cleaned_sub_filter = cleaned_sub_filter.except("name")
end
{filter_entry["field"] => cleaned_sub_filter}
end
}
end
filters = {
"all" => cleaned_filters
}
{
"query" => query_object_raw['q'] || '',
"filters" => filters,
}
end
def self.parse_escaped_string(value_raw)
value = CGI.unescape(value_raw)
if /^n_.*_n$/.match(value)
# Remove first and last 2 characters from string
return value.delete_prefix("n_").delete_suffix("_n").to_f
end
if /^b_.*_b$/.match(value)
# Remove first and last 2 characters from string
return value.delete_prefix("b_").delete_suffix("_b").to_bool
end
# Need rfc3339 for elastic to be happy
# but JSON stringifies to iso8601
return Date.iso8601(value).rfc3339 rescue value
end
# Rack's parse_nested_query doesn't convert array-like things to arrays!
# This handles that, so that we can manipulate the list of filters without
# introducing "holes" into our filter array
# Also handles elastic primitive encoding (i.e. "n_123_n") and converts date strings properly
def self.raw_nested_query_to_elastic_query(nested_query, key_context = nil)
if nested_query.class == Hash
nested_query = T.let(nested_query, Hash)
# If every key is an int, convert to array
if nested_query.keys.all? {|k| T.let(Integer(k, exception: false), T.nilable(Integer)) && k.to_i >= 0}
max_index = nested_query.keys.map(&:to_i).max
backing_array = Array.new(max_index + 1)
nested_query.each do |k, v|
backing_array[k.to_i] = v
end
# Recurse now that it's been transformed to an array
return raw_nested_query_to_elastic_query(backing_array)
else
return nested_query.map do |k, v|
[
k,
raw_nested_query_to_elastic_query(v, k)
]
end.to_h
end
elsif nested_query.class == Array
return nested_query.map {|el| raw_nested_query_to_elastic_query(el)}
elsif nested_query.class == String
return parse_escaped_string(nested_query)
else
return nested_query
end
end
end
end
Metadata
Metadata
Assignees
Labels
No labels