-
-
Notifications
You must be signed in to change notification settings - Fork 408
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 proposal filters #474
Add proposal filters #474
Conversation
@beagleknight, thanks for your PR! By analyzing the history of the files in this pull request, we identified @josepjaume, @oriolgual and @divins to be potential reviewers. |
Current coverage is 60.18% (diff: 94.91%)@@ master #474 diff @@
==========================================
Files 2824 2833 +9
Lines 121487 121685 +198
Methods 0 0
Messages 0 0
Branches 0 0
==========================================
+ Hits 73048 73231 +183
- Misses 48439 48454 +15
Partials 0 0
|
@random_seed = @search.random_seed | ||
@search = ProposalSearch.new(search_params.merge(context_params)) | ||
@proposals = @search.results | ||
@random_seed = @search.random_seed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing whitespace detected.
@@ -0,0 +1,36 @@ | |||
# frozen_string_literal: true | |||
module Decidim | |||
class ResourceSearch < Searchlight::Search |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing top-level class documentation comment.
@proposals = @search.proposals | ||
@random_seed = @search.random_seed | ||
@proposals = search.results | ||
@random_seed = search.random_seed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing whitespace detected.
@proposals = @search.proposals | ||
@random_seed = @search.random_seed | ||
@proposals = search.results | ||
@random_seed = search.random_seed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing whitespace detected.
def filter | ||
@filter ||= Filter.new(params[:filter]) | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing whitespace detected.
def filter | ||
@filter ||= Filter.new(params[:filter]) | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing whitespace detected.
end | ||
|
||
def respond_to_missing?(method_name, include_private = false) | ||
@filter.present? && @filter.has_key?(method_name) || super |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Hash#key? instead of Hash#has_key?.
end | ||
|
||
def respond_to_missing?(method_name, include_private = false) | ||
@filter.present? && @filter.has_key?(method_name) || super |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Hash#key? instead of Hash#has_key?.
end | ||
|
||
def method_missing(method_name, *arguments, &block) | ||
@filter.present? && @filter.has_key?(method_name) ? @filter[method_name] : "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Hash#key? instead of Hash#has_key?.
end | ||
|
||
def method_missing(method_name, *arguments, &block) | ||
@filter.present? && @filter.has_key?(method_name) ? @filter[method_name] : "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Hash#key? instead of Hash#has_key?.
@filter = filter | ||
end | ||
|
||
def method_missing(method_name, *arguments, &block) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When using method_missing, fall back on super.
Unused method argument - arguments. If it's necessary, use _ or _arguments as an argument name to indicate that it won't be used.
Unused method argument - block. If it's necessary, use _ or _block as an argument name to indicate that it won't be used.
@filter = filter | ||
end | ||
|
||
def method_missing(method_name, *arguments, &block) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When using method_missing, fall back on super.
Unused method argument - arguments. If it's necessary, use _ or _arguments as an argument name to indicate that it won't be used.
Unused method argument - block. If it's necessary, use _ or _block as an argument name to indicate that it won't be used.
@beagleknight, thanks for your PR! By analyzing the history of the files in this pull request, we identified @mrcasals, @josepjaume and @oriolgual to be potential reviewers. |
end | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Final newline missing.
@template.content_tag(:fieldset) do | ||
@template.content_tag(:legend) do | ||
@template.content_tag(:h6, legend_title, class: 'heading6') | ||
end + block.call |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use yield instead of block.call.
end | ||
end | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Final newline missing.
@template.content_tag(:div, '', class: "filters__section") do | ||
@template.content_tag(:fieldset) do | ||
@template.content_tag(:legend) do | ||
@template.content_tag(:h6, legend_title, class: 'heading6') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
@template.content_tag(:fieldset) do | ||
@template.content_tag(:legend) do | ||
@template.content_tag(:h6, legend_title, class: 'heading6') | ||
end + block.call |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use yield instead of block.call.
|
||
# Private: Renders a custom fieldset and execute the given block. | ||
def fieldset_wrapper(legend_title, &block) | ||
@template.content_tag(:div, '', class: "filters__section") do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
@template.content_tag(:div, '', class: "filters__section") do | ||
@template.content_tag(:fieldset) do | ||
@template.content_tag(:legend) do | ||
@template.content_tag(:h6, legend_title, class: 'heading6') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
fieldset_wrapper options[:legend_title] do | ||
super(method, collection, value_method, label_method, options, html_options) do |b| | ||
if block_given? | ||
block.call b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use yield instead of block.call.
|
||
# Private: Renders a custom fieldset and execute the given block. | ||
def fieldset_wrapper(legend_title, &block) | ||
@template.content_tag(:div, '', class: "filters__section") do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
|
||
# Wrap the check_boxes collection in a custom fieldset. | ||
# It also renders the inputs inside its labels. | ||
def collection_check_boxes(method, collection, value_method, label_method, options = {}, html_options = {}, &block) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid parameter lists longer than 5 parameters.
fieldset_wrapper options[:legend_title] do | ||
super(method, collection, value_method, label_method, options, html_options) do |b| | ||
if block_given? | ||
block.call b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use yield instead of block.call.
end | ||
|
||
# Wrap the check_boxes collection in a custom fieldset. | ||
# It also renders the inputs inside its labels. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing whitespace detected.
|
||
# Wrap the check_boxes collection in a custom fieldset. | ||
# It also renders the inputs inside its labels. | ||
def collection_check_boxes(method, collection, value_method, label_method, options = {}, html_options = {}, &block) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid parameter lists longer than 5 parameters.
fieldset_wrapper options[:legend_title] do | ||
super(method, collection, value_method, label_method, options, html_options) do |b| | ||
if block_given? | ||
block.call b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use yield instead of block.call.
b7b1f03
to
3e3989e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good job!
# Creates the SearchLight base query. | ||
# Check if the option feature was provided. | ||
def base_query | ||
# raise order_start_time.inspect |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol xD Forgot to erase that line of RDD (Raise-driven development)
# Initialize the Searchlight::Search base class with the options provided. | ||
# | ||
# scope - The scope used to create the base query | ||
# options - A hash of options to modify the search. These options will be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you document the options not used by searchlight? Like the paginate ones and other if there are any
fieldset_wrapper options[:legend_title] do | ||
super(method, collection, value_method, label_method, options, html_options) do |b| | ||
if block_given? | ||
yield b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OriolCI: Avoid variables with meaningless names
fieldset_wrapper options[:legend_title] do | ||
super(method, collection, value_method, label_method, options, html_options) do |b| | ||
if block_given? | ||
yield b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto
allow(helper).to receive(:dummies_path) | ||
end | ||
|
||
it "should wrap everything in a div with class 'filters'" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it wraps
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok but can you explain why you don't like 'should'?
end | ||
end | ||
|
||
it "should call form_for helper with specific arguments" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it calls
let(:builder) { FilterFormBuilder.new(:resource, resource, helper, {}) } | ||
|
||
shared_examples "fieldset_wrapper" do | ||
it "should wrap fields in a fieldset inside a div with class 'filters__section'" do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove should everywhere xD
search_text: "", | ||
origin: "all", | ||
category_id: "", | ||
random_seed: params[:random_seed] || @random_seed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
??? You're passing @random_seed to search
but @random_seed it's a value from search
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree! XD
# Handle the search_text filter | ||
def search_search_text | ||
query | ||
.where("title ILIKE ?", "%#{search_text}%") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add an index for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm agree. Also... we may need to add a better text search in the future.
const { DecidimMeetings: { MeetingsFormFilterComponent } } = exports; | ||
const meetingsFormFilter = new MeetingsFormFilterComponent('form#new_filter'); | ||
const { Decidim: { FormFilterComponent } } = exports; | ||
const formFilter = new FormFilterComponent('form#new_filter'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I like using global DOM ids here. Would it work if we accept a class instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the form is created using the Rails Form Builder it's a trivial change.
@@ -1,12 +1,15 @@ | |||
/* eslint-disable no-div-regex, no-useless-escape, no-param-reassign */ | |||
|
|||
/** | |||
* A component that handles the meetings filter form. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would clarify: "A plain JavaScript component" in order not to get confused with any other framework's components.
*/ | ||
_parseLocationFilterValues() { | ||
// Every location param is constructed like this: filter[key]=value | ||
let regexpResult = decodeURIComponent(this._getLocation()).match(/filter\[([^\]]*)\](?:\[\])?=([^&]*)/g); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't there any other way to parse GET params?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no native way to handle query params. You can find a lot of jQuery plugins or npm packages to do that but I think there is no need of that if you can use plain regular expressions. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey, I'm JavaScript, I know all about the DOM and I know about CORS and x-frame-options but let's not handle URLs properly.
yield form | ||
end | ||
end | ||
filters_container + javascript_include_tag("decidim/filters") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You sure this won't break with turbolinks? Does it work if we navigate back / on again?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't find any issue so far. We are dealing with turbolinks events to bind and unbind change form events. We also use history API to handle the back and forward buttons and update the form values as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it doesn't break because we're using delegation. Cool.
|
||
# Handle the category_id filter | ||
def search_category_id | ||
query.where(decidim_category_id: category_ids) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice although not sure if we should make the assumption that category_id
will be available in all resources. Maybe we could separate common searches into Query
classes if we want to share them amongst resources.
In this scenario, something like:
def search_category_ids
CategoryFilterQuery.new(query, categories).query
end
or even, if we want to go for the rectify composition way:
def query
ResourceSearch::BaseQuery.new(@scope)
end
def search_category_ids
query | CategoryFilterQuery.new(categories)
end
I feel like this is a good place to use rectify query composition. I would even consider removing Searchlight
, as it feels like we have some functionality overlap and we could implement this filter ourselves.
Check out Rectify query composition's documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think combining Rectify with Searchlight is very useful for complex queries like the category_id
filter. I think Searchlight provides us with a simple mechanism for checking the filter properties and adding conditions to the query.
And related to having resources without category_id is not a big deal since the method search_category_id
will not be called if the filter doesn't contain a category_id
attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@josepjaume Rectify and Searchlight doesn't play nice together... I will not implement this yet but in the future we can rethink about the approach to drop Searchlight in favor of Rectify 😞
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@beagleknight I KNEW IT HAHAHAHA 💥
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't send the category_id
param, then this method doesn't get executed and will not break, so there's no need to worry about that I guess 😄
fieldset_wrapper options[:legend_title] do | ||
super(method, collection, value_method, label_method, options, html_options) do |b| | ||
if block_given? | ||
yield b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like yield is never being used (coverage reports).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not used because I'm not using the block in my filters but I thought it is a good practise to handle that edge case (since I'm overriding a form builder method).
fieldset_wrapper options[:legend_title] do | ||
super(method, collection, value_method, label_method, options, html_options) do |b| | ||
if block_given? | ||
yield b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this isn't tested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto
if origin == "official" | ||
query.where(decidim_author_id: nil) | ||
elsif origin == "citizenship" | ||
query.where.not(decidim_author_id: nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like citizenship filter isn't being tested (coverage)
🎩 What? Why?
I'm adding a filter form for proposals. I factor out the code created in #399 so it can be used for both resources.
I'm exposing the API as a single helper:
The filter form receives a filter object which is created if the controller uses the following concern:
The controller needs a
DummySearch
class that will be used to search and filter resources. This class must inheritResourceSearch
base class which includes the default behavior for all search classes.📌 Related Issues
📋 Subtasks
📷 Screenshots (optional)
👻 GIF