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 dynamic counts to search filters #5471

Open
wants to merge 71 commits into
base: main
Choose a base branch
from
Open

Conversation

jorg-vr
Copy link
Contributor

@jorg-vr jorg-vr commented Apr 5, 2024

This pull request contains a refactor of search filters. It adds dynamic counts of the expected results to all search filters. When already partially filtered, it also limits the other filters to only show relevant options.

image
image
image

The initial goal was to get rid of some old code that used js functions as part of 'filterCollection' objects to extract the color and paramVal from a filter option. This made most related code quite ugly and there was no longer any need for it.

So with cc610d6 and 810fa47 I made filterCollection a pure map of data objects.

With these changes it became a lot simpler to dynamically update filters.
I tried to write generic concerns that automatically define a dynamic option count when defining a scope. This way the end result should be less specific code.

In the most basic case writing a filter for a given field of a model is as simple as writing filterable_by :field in the model and writing has_filter :field in the controller. In the controller, the @filters variable can then be set by calling filters(elements) on the unfiltered list of elements. @filters is then used to update the shown filter options and counts.
Common cases, such as enums and foreign keys can be filtered using filterable_by :field, is_enum: true and filterable_by :field, model: Model respectively.

Naturally there are always some more complex cases, in which case a custom name_hash function can be written or even a full custom count function. See activity.rb for some examples of this.

The most complex case were course_labels as these required the course as extra argument. For now these are generalized in their own specific reusable function.

@jorg-vr jorg-vr added the feature New feature or request label Apr 5, 2024
@jorg-vr jorg-vr self-assigned this Apr 5, 2024
@jorg-vr jorg-vr requested a review from a team as a code owner April 5, 2024 12:28
@jorg-vr jorg-vr requested review from bmesuere and chvp and removed request for a team April 5, 2024 12:28
app/views/layouts/_searchbar.html.erb Outdated Show resolved Hide resolved
@chvp
Copy link
Member

chvp commented Apr 16, 2024

(There are still some test and linting issues here.)

@jorg-vr jorg-vr marked this pull request as draft April 18, 2024 07:10
Base automatically changed from enhance/search-action-discoverability to main April 22, 2024 09:59
@github-actions github-actions bot removed the deploy naos Request a deployment on naos label May 14, 2024
@jorg-vr
Copy link
Contributor Author

jorg-vr commented May 14, 2024

We might have to reconsider this whole pr :(
I underestimated how hard it would be to optimize 'COUNT' queries...
There is no simple fix such as adding indexes as it seems innoDB does not use these indexes for counting.

The only suggested solution is keeping track of summary tables. But implementing this in a flexible way for all our search pages would add a whole new layer of complexity...

@jorg-vr jorg-vr added the deploy naos Request a deployment on naos label May 14, 2024
@github-actions github-actions bot removed the deploy naos Request a deployment on naos label May 14, 2024
@jorg-vr jorg-vr added the deploy naos Request a deployment on naos label May 14, 2024
@github-actions github-actions bot removed the deploy naos Request a deployment on naos label May 14, 2024
@jorg-vr
Copy link
Contributor Author

jorg-vr commented May 14, 2024

Maybe I overreacted.

The newly added count queries have a similar speed as the existing count query that already ran for pagination.
For most pages it doesn't have a big impact. Most pages only have one or two filters and this results into at most 100ms extra response time on naos, but often a lot less.

I have already removed the counts for the submission pages, as it was unbearably slow there (but these pages where already way to slow on naos anyway).
For reference the pagination count takes 15.8 seconds out of the total 16.7 seconds on the submissions page. Adding up to two extra count queries to this page tripled response time.
Side note: removing the page count on this page would greatly speed up the page.

The activities page is an edge case. With 7 filters the increase in query time can be up to 300ms, which is about 20% of the total time required to render the page. This makes interactivity on naos feel slightly suboptimal.
But our production server is always a bit faster, and the UX improvement is greatest for activity selection, so it might be worth a 20% increase in loading time.
This page also has room for speed ups in other places, such as removing the need for n+1 queries for popularity and activity status.

@jorg-vr jorg-vr marked this pull request as ready for review May 16, 2024 09:18
@jorg-vr jorg-vr requested a review from bmesuere May 16, 2024 09:18
Comment on lines 4 to 5
delegate :filter_options, to: :class
self.filter_options = []
Copy link
Member

Choose a reason for hiding this comment

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

This also seems weird to me. It should be possible to delegate to the controller instance instead of the class, which would be better, IMO.

app/controllers/feedbacks_controller.rb Outdated Show resolved Hide resolved
app/models/application_record.rb Outdated Show resolved Hide resolved
app/models/concerns/filterable.rb Outdated Show resolved Hide resolved
Comment on lines 85 to 97
scope :by_description_languages, lambda { |languages|
by_language = all # allow chaining of scopes
by_language = by_language.where(description_en_present: true) if languages.include? 'en'
by_language = by_language.where(description_nl_present: true) if languages.include? 'nl'
by_language
}
define_singleton_method('description_languages_filter_options') do
scope = unscoped.where(id: select(:id))

[{ id: 'nl', name: I18n.t('js.nl'), count: scope.where(description_nl_present: true).count },
{ id: 'en', name: I18n.t('js.en'), count: scope.where(description_en_present: true).count }]
.filter { |lang| lang[:count].positive? }
end
Copy link
Member

Choose a reason for hiding this comment

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

Can we add an extra method to the concern that handles cases like this (and popularity below). It also occurs in submission, IIUC.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have now added this extra method:

def filter_options_for(name, &block)
  define_singleton_method("#{name}_filter_options", &block)
end

Which does make it a bit more readable. But except for hiding the define_singleton_method it doesn't do much, as these functions don't have much in common

config/locales/models/en.yml Outdated Show resolved Hide resolved
test/javascript/components/search/search_field.test.ts Outdated Show resolved Hide resolved
test/javascript/components/search/search_token.test.ts Outdated Show resolved Hide resolved
@jorg-vr jorg-vr requested a review from chvp May 22, 2024 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants