elastic / elastic/enterprise-search-ruby

[Request] Search-UI URL Parsing in Ruby

Open
#417 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Ruby
Stars
20
Forks
8
PR merge metrics
No merged PRs in 30d

Description

Hey! I'm trying to decode elastic URL queries from elastic's [search-ui package](https://github.com/elastic/search-ui/). 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](https://github.com/elastic/search-ui/blob/main/packages/search-ui/src/URLManager.ts#L187C3-L187C13) 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!

```ruby
# 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
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.