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

Pattern search forms #2337

Draft
wants to merge 95 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
0052ab8
Pattern Search controllers for every searchable model
nimmolo Aug 26, 2024
57a4aca
names search routes and form
nimmolo Aug 26, 2024
6517d2f
Update pattern_search_controller.rb
nimmolo Aug 26, 2024
7520344
Add some pattern search field helpers
nimmolo Aug 26, 2024
3d3d15d
Merge branch 'main' into pattern-search-controllers
nimmolo Aug 30, 2024
c8cb865
more helpers
nimmolo Aug 30, 2024
efe8ae3
simplify range fields
nimmolo Aug 31, 2024
16a2156
pattern_search form builder
nimmolo Aug 31, 2024
4556505
Pared down
nimmolo Aug 31, 2024
d869385
Panels for search groups
nimmolo Sep 1, 2024
ef5c74a
Start to fix inline text fields
nimmolo Sep 2, 2024
ca9c16f
Regroup and polish names form
nimmolo Sep 2, 2024
3c76fe6
Update _form.erb
nimmolo Sep 2, 2024
3546116
Start obs form
nimmolo Sep 2, 2024
a768223
organize obs search
nimmolo Sep 4, 2024
c5253fc
Refactor pattern search field helpers
nimmolo Sep 4, 2024
b2e7b98
Observation form field groupings
nimmolo Sep 4, 2024
bedbccd
Merge branch 'main' into pattern-search-controllers
nimmolo Sep 5, 2024
54c4152
Merge branch 'main' into pattern-search-controllers
nimmolo Sep 5, 2024
483ffec
Add concern and routes
nimmolo Sep 5, 2024
8a21c44
Smooth out observations
nimmolo Sep 6, 2024
66677ce
Merge branch 'main' into pattern-search-controllers
nimmolo Sep 6, 2024
e43c5e8
permit hidden_id fields
nimmolo Sep 6, 2024
a2ae784
Autocompleters - change hidden field id to match field + "_id"
nimmolo Sep 7, 2024
ed6288a
Update check_box_with_label to accept checked_value
nimmolo Sep 7, 2024
4c1a4f0
Field concatenation!
nimmolo Sep 7, 2024
58c68cd
Update autocompleter_helper.rb
nimmolo Sep 7, 2024
7fc6ef7
Merge branch 'main' into pattern-search-controllers
nimmolo Sep 7, 2024
c3bb665
Update pattern_search_controller.rb
nimmolo Sep 7, 2024
b16abda
Update pattern_search_helper.rb
nimmolo Sep 7, 2024
f005e65
Merge branch 'main' into pattern-search-controllers
nimmolo Sep 12, 2024
56fdeaa
Add help text to pattern search fields
nimmolo Sep 12, 2024
b0ae588
Maybe switch to date selects
nimmolo Sep 13, 2024
54bbfe1
Change "yes field" to select
nimmolo Sep 13, 2024
8220934
Merge branch 'main' into pattern-search-date-selects
nimmolo Sep 14, 2024
1a6bebb
Merge branch 'date-select-option-refinement' into pattern-search-date…
nimmolo Sep 14, 2024
806c0c3
Okay, dates are working
nimmolo Sep 14, 2024
87505c7
Merge branch 'main' into pattern-search-date-selects
nimmolo Sep 14, 2024
bc3e0e0
Merge branch 'pattern-search-date-selects' into pattern-search-contro…
nimmolo Sep 14, 2024
ecdf1c3
Adjust the hidden name of the autocompleters on the form
nimmolo Sep 14, 2024
564bd6b
Use form `SearchFilter` models
nimmolo Sep 16, 2024
7c0061f
Allow blanks in selects
nimmolo Sep 16, 2024
cbf9a4b
Use parse_date_range
nimmolo Sep 16, 2024
3014098
Whoops map!
nimmolo Sep 16, 2024
2e89e5d
Merge branch 'date-select-option-refinement' into pattern-search-cont…
nimmolo Sep 16, 2024
c4e1deb
Merge branch 'date-select-option-refinement' into pattern-search-cont…
nimmolo Sep 16, 2024
df372ae
Update schema.rb
nimmolo Sep 16, 2024
354aeca
Merge branch 'date-select-option-refinement' into pattern-search-cont…
nimmolo Sep 16, 2024
24c855c
Add migration, fix numeric range values
nimmolo Sep 16, 2024
10f769d
Revert to text fields for dates
nimmolo Sep 16, 2024
eb4a51d
Fix ranges, clean
nimmolo Sep 17, 2024
43a1700
guard incoming_string nil
nimmolo Sep 17, 2024
d785c90
spacing for between
nimmolo Sep 17, 2024
0308676
truly improved spacing
nimmolo Sep 17, 2024
6d69750
Set default "" incoming string upstream
nimmolo Sep 17, 2024
10259c5
rank and confidence as ranges, reformat options
nimmolo Sep 17, 2024
db6da28
Handle params nested under filter object, add `fields` methods
nimmolo Sep 18, 2024
ef87b8a
Move fields_with id upstream, symbolize keys
nimmolo Sep 18, 2024
8d88421
Switch to FiltersController
nimmolo Sep 18, 2024
63cee8a
Continue renaming templates, add clear button
nimmolo Sep 18, 2024
ede270f
Collapsed/shown fields, refactor controllers
nimmolo Sep 18, 2024
ea00aa5
Separate type from model in filter args (type is for autocompleter)
nimmolo Sep 19, 2024
e9366f3
Clean up
nimmolo Sep 19, 2024
ebe72a5
More tweaks. Try to avoid margin-right
nimmolo Sep 19, 2024
9eb6651
forms in panels - less margin at bottom
nimmolo Sep 19, 2024
9f17c2e
Fix range fields, search_type
nimmolo Sep 19, 2024
8156917
Clean up and rubocop
nimmolo Sep 19, 2024
a105685
fields_with_requirements - search_type - search_subclass
nimmolo Sep 19, 2024
9d6280a
Merge branch 'main' into pattern-search-controllers
nimmolo Sep 22, 2024
d3aa1b8
Filters controller tests
nimmolo Sep 22, 2024
1a72425
Create pattern_search_integration_test.rb
nimmolo Sep 22, 2024
ad53895
fields_with_requirements is hash
nimmolo Sep 22, 2024
f149a40
Add "more" to panel uncollapse
nimmolo Sep 22, 2024
0275935
Go back to storing strings. Ids needs more work
nimmolo Sep 22, 2024
1536c56
Check that session[:pattern].present?
nimmolo Sep 23, 2024
7a89e5b
Fix prefilling values on panels, and pre-opening if filled
nimmolo Sep 23, 2024
247167a
Re-add pattern fields
nimmolo Sep 23, 2024
92a704f
Merge branch 'main' into pattern-search-controllers
nimmolo Sep 24, 2024
f9c05b9
Merge branch 'main' into pattern-search-controllers
nimmolo Sep 25, 2024
f00564a
Containers for these pages
nimmolo Sep 25, 2024
fc31ac9
Pass obj to location helpers to prefill value
nimmolo Sep 25, 2024
8a2ad5b
Send obj to helpers
nimmolo Sep 25, 2024
e83b9ec
Merge branch 'main' into pattern-search-controllers
nimmolo Sep 26, 2024
bafb9fb
add pattern param and filter attribute
nimmolo Sep 26, 2024
63e1615
region/compass sort of working
nimmolo Sep 26, 2024
d4e796f
fix accessor for value in compass/elevation inputs
nimmolo Sep 26, 2024
b35aba0
fix concat
nimmolo Sep 26, 2024
202ecad
fix capture/concat
nimmolo Sep 26, 2024
c09685b
Merge branch 'main' into pattern-search-controllers
nimmolo Sep 26, 2024
66817c9
Add map ui
nimmolo Sep 26, 2024
4481316
Fix range values, plus string escaping for region
nimmolo Sep 27, 2024
5530063
Use existing page titles
nimmolo Sep 27, 2024
15a41e2
Merge branch 'main' into pattern-search-controllers
nimmolo Sep 28, 2024
f16aecb
Merge branch 'main' into pattern-search-controllers
nimmolo Oct 3, 2024
c3ff609
Merge branch 'main' into pattern-search-controllers
nimmolo Oct 15, 2024
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
6 changes: 6 additions & 0 deletions app/assets/stylesheets/mo/_form_elements.scss
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,9 @@ form {
max-height: 30rem;
overflow-y: auto;
}

.panel-body {
.form-group:last-child {
margin-bottom: 0;
}
}
4 changes: 4 additions & 0 deletions app/assets/stylesheets/mo/_utilities.scss
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@
object-fit: contain;
}

.flex-grow-1 {
flex-grow: 1 !important;
}

.text-larger {
font-size: larger !important;
}
Expand Down
83 changes: 82 additions & 1 deletion app/classes/pattern_search/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@
module PatternSearch
# Base class for PatternSearch; handles everything but build_query
class Base
attr_accessor :errors, :parser, :flavor, :args, :query
attr_accessor :errors, :parser, :flavor, :args, :query, :form_params

def initialize(string)
self.errors = []
self.parser = PatternSearch::Parser.new(string)
self.form_params = make_terms_available_to_faceted_form
build_query
self.query = Query.lookup(model.name.to_sym, flavor, args)
rescue Error => e
errors << e
end

# rubocop:disable Metrics/AbcSize
def build_query
self.flavor = :all
self.args = {}
Expand All @@ -33,6 +35,7 @@ def build_query
end
end
end
# rubocop:enable Metrics/AbcSize

def help_message
"#{:pattern_search_terms_help.l}\n#{self.class.terms_help}"
Expand All @@ -54,5 +57,83 @@ def lookup_param(var)
end
nil
end

# Build a hash so we can populate the form fields with from the values from
# the saved search string. Turn ranges into ranges, and dates into dates.
# NOTE: The terms may be translated! We have to look up the param names that
# the translations map to.
# rubocop:disable Metrics/AbcSize
def make_terms_available_to_faceted_form
parser.terms.each_with_object({}) do |term, hash|
# term is what the user typed in, not the parsed value.
param = lookup_param_name(term.var)
if fields_with_dates.include?(param)
start, range = check_for_date_range(term)
hash[param] = start
hash[:"#{param}_range"] = range if range
elsif fields_with_numeric_range.include?(param)
start, range = check_for_numeric_range(term)
hash[param] = start
hash[:"#{param}_range"] = range if range
else
hash[param] = term.vals.join(", ")
end
end
end
# rubocop:enable Metrics/AbcSize

def lookup_param_name(var)
# See if this var matches an English parameter name first.
return var if params[var].present?

# Then check if any of the translated parameter names match.
params.each_key do |key|
return key if var.to_s == :"search_term_#{key}".l.tr(" ", "_")
end
nil
end

# The string could be a date string like "2010-01-01", or a range string
# like "2010-01-01-2010-01-31", or "2023-2024", or "08-10".
# If it is a range, return the two dates.
# Try for fidelity to the stored string, eg only years.
def check_for_date_range(term)
bits = term.vals[0].split("-")
case bits.size
when 1
start = bits[0]
range = nil
when 2
start, range = bits
when 4
start = "#{bits[0]}-#{bits[1]}"
range = "#{bits[2]}-#{bits[3]}"
else
start, range = term.parse_date_range
end

range = nil if start == range

[start, range]
end

def check_for_numeric_range(term)
bits = term.vals[0].split("-")

if bits.size == 2
bits.map(&:to_i)
else
[term.vals[0], nil]
end
end

# These are set in the subclasses, but seem stable enough to be here.
def fields_with_dates
[:when, :created, :modified].freeze
end

def fields_with_numeric_range
[:confidence].freeze
end
end
end
25 changes: 25 additions & 0 deletions app/classes/pattern_search/name.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,31 @@ def self.params
PARAMS
end

# List of fields that are displayed in the search form.
# Autocompleters have id fields, and range fields are concatenated.
def self.fields
params.keys + [
:created_range, :modified_range, :rank_range, :pattern
]
end

def self.fields_with_dates
[:created, :modified]
end

def self.fields_with_range
[:created, :modified, :rank]
end

def self.fields_with_ids
[]
end

# hash of required: fields
def self.fields_with_requirements
{}
end

def params
self.class.params
end
Expand Down
30 changes: 29 additions & 1 deletion app/classes/pattern_search/observation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Observation < Base
created: [:created_at, :parse_date_range],
modified: [:updated_at, :parse_date_range],

# names
# names. note that the last four require the first one to be present
name: [:names, :parse_list_of_names],
exclude_consensus: [:exclude_consensus, :parse_boolean], # of_look_alikes
include_subtaxa: [:include_subtaxa, :parse_boolean],
Expand Down Expand Up @@ -53,6 +53,34 @@ def self.params
PARAMS
end

# List of fields that are displayed in the search form.
# Autocompleters have id fields, and range fields are concatenated.
def self.fields
params.keys + [
:name_id, :location_id, :user_id, :herbarium_id, :list_id, :project_id,
:project_lists_id, :when_range, :created_range, :modified_range,
:rank_range, :confidence_range, :pattern
]
end

def self.fields_with_dates
[:when, :created, :modified]
end

def self.fields_with_range
[:when, :created, :modified, :rank, :confidence]
end

def self.fields_with_ids
[:name, :location, :user, :herbarium, :list, :project, :species_list]
end

# hash of required: fields
def self.fields_with_requirements
{ name: [:exclude_consensus, :include_subtaxa, :include_synonyms,
:include_all_name_proposals] }
end

def params
self.class.params
end
Expand Down
2 changes: 1 addition & 1 deletion app/classes/pattern_search/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class Parser
/x

def initialize(string)
self.incoming_string = string
self.incoming_string = string || ""
self.terms = parse_incoming_string
end

Expand Down
150 changes: 150 additions & 0 deletions app/controllers/concerns/filterable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# frozen_string_literal: true

#
# = Filterable Concern
#
# This is a module of reusable methods included by controllers that handle
# "faceted" pattern searches per model, with separate inputs for each keyword.
#
# We're translating the params hash into the format that the user would have
# typed into the search box if they knew how to do that, because that's what
# the PatternSearch class expects to parse. The PatternSearch class then
# unpacks, validates and re-translates all these params into the actual params
# used by the Query class. This may seem roundabout: of course we do know the
# Query param names in advance, so we could theoretically just pass the values
# directly into Query and render the index. But we'd still have to be able to
# validate the input, and give messages for all the possible errors there.
# PatternSearch class handles all that.
#
################################################################################

module Filterable
extend ActiveSupport::Concern

# Rubocop is incorrect here. This is a concern, not a class.
# rubocop:disable Metrics/BlockLength
included do
def search_subclass
PatternSearch.const_get(@filter.class.search_type.capitalize)
end

def formatted_pattern_search_string
sift_and_restructure_form_params
keyword_strings = @sendable_params.map do |key, value|
"#{key}:#{value}"
end
keyword_strings.join(" ")
end

# One oddball is `confidence` - the string "0" should not count as a value.
def sift_and_restructure_form_params
@keywords = @filter.attributes.to_h.compact_blank.symbolize_keys

remove_invalid_field_combinations
concatenate_range_fields

@sendable_params = remove_ids_and_format_strings(@keywords)
# @storable_params = configure_storable_params(@keywords)
end

# Passing some fields will raise an error if the required field is missing,
# so just toss them.
def remove_invalid_field_combinations
return unless search_subclass.respond_to?(:fields_with_requirements)

search_subclass.fields_with_requirements.each do |req, fields|
next if @keywords[req].present?

fields.each { |field| @keywords.delete(field) }
end
end

# Check for `fields_with_range`, and concatenate them if range val present,
# removing the `_range` field.
def concatenate_range_fields
return unless search_subclass.respond_to?(:fields_with_range)

search_subclass.fields_with_range.each do |key|
next if @keywords[:"#{key}_range"].blank?

@keywords[key] = [@keywords[key].to_s.strip,
@keywords[:"#{key}_range"].to_s.strip].join("-")
@keywords.delete(:"#{key}_range")
end
end

# SENDABLE_PARAMS - params with ids can be sent to index and query.
#
# This method deletes the strings typed in the form and sends ids, saving a
# lookup at the receiver. However, we still want a legible string saved in
# the session, so we can repopulate the form with legible values - plus
# maybe in the url. Could send the id versions as separate `filter` param?
#
# Need to modify autocompleters to check for record id on load if prefilled.
def substitute_strings_with_ids(keywords)
search_subclass.fields_with_ids.each do |key|
next if keywords[:"#{key}_id"].blank?

keywords[key] = keywords[:"#{key}_id"]
keywords.delete(:"#{key}_id")
end
keywords
end

# STORABLE_PARAMS - params for the pattern string in session.
# These methods don't modify the original @keywords.
#
# Ideally we'd store full strings for all values, including names and
# locations, so we can repopulate the form with the same values.
def remove_ids_and_format_strings(keywords)
escape_strings_and_remove_ids(keywords)
escape_locations_and_remove_ids(keywords)
end

# Escape-quote the strings, the way the short form requires.
# rubocop:disable Metrics/AbcSize
def escape_strings_and_remove_ids(keywords)
search_subclass.fields_with_ids.each do |key|
# location, region handled separately
next if keywords[key].blank? || strings_with_commas.include?(key.to_sym)

list = keywords[key].split(",").map(&:strip)
list = list.map { |name| "\"#{name}\"" }
keywords[key] = list.join(",")
next if keywords[:"#{key}_id"].blank?

keywords.delete(:"#{key}_id")
end
keywords
end
# rubocop:enable Metrics/AbcSize

# Escape-quote the locations and their commas. We'd prefer to have legible
# strings in the url, but the comma handling is difficult.
def escape_locations_and_remove_ids(keywords)
if keywords[:location].present?
list = keywords[:location].split("\n").map(&:strip)
list = list.map { |location| escape_location_string(location) }
keywords[:location] = list.join(",")
end
keywords.delete(:location_id) if keywords[:location_id].present?
escape_region_string(keywords)
end

def escape_region_string(keywords)
return keywords if keywords[:region].blank?

keywords[:region] = escape_location_string(keywords[:region].strip)
keywords
end

def escape_location_string(location)
"\"#{location.tr(",", "\\,")}\""
end

def strings_with_commas
[:location, :region].freeze
end
end
# rubocop:enable Metrics/BlockLength
end
Loading