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

Merged
merged 80 commits into from
Nov 13, 2024
Merged

Add dynamic counts to search filters #5471

merged 80 commits into from
Nov 13, 2024

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
@jorg-vr jorg-vr requested a review from chvp May 22, 2024 11:40
@jorg-vr
Copy link
Contributor Author

jorg-vr commented Jul 29, 2024

@coderabbitai review

1 similar comment
@bmesuere
Copy link
Member

@coderabbitai review

@jorg-vr
Copy link
Contributor Author

jorg-vr commented Oct 24, 2024

rebase and request review

Copy link
Contributor

coderabbitai bot commented Nov 13, 2024

Walkthrough

The pull request introduces significant changes across multiple components and models, primarily focusing on enhancing filtering capabilities. Key modifications include the introduction of new filter management properties and methods, the restructuring of existing filters, and updates to localization files. Various components, such as SavedAnnotationList, DropdownFilter, and several controllers, have been updated to utilize these new filtering mechanisms. Additionally, the changes streamline the rendering of search bars and improve the overall functionality of the filtering system throughout the application.

Changes

File Path Change Summary
app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts Added property lastFilters: FilterOptions[] and getter get filters(). Updated render method to call search.updateFilters(this.filters).
app/assets/javascripts/components/search/dropdown_filter.ts Updated class inheritance for DropdownFilter and DropdownFilters. Changed color property type and removed type property. Modified rendering logic to use color directly.
app/assets/javascripts/components/search/filter_collection.ts Introduced FilterCollection class with properties filters: FilterOptions[] and hide: string[]. Added method get visibleFilters().
app/assets/javascripts/components/search/filter_element.ts Updated Label type and added AccentColor. Changed FilterOptions structure and removed paramVal function. Renamed FilterCollectionElement to FilterElement.
app/assets/javascripts/components/search/filter_tabs.ts Updated inheritance for FilterTabs. Removed paramVal method.
app/assets/javascripts/components/search/search_field.ts Changed inheritance for SearchField and SearchFieldSuggestion. Updated filter properties and rendering logic.
app/assets/javascripts/components/search/search_token.ts Updated inheritance for SearchToken and SearchTokens. Changed color property type and added activeFilters and clearAll methods.
app/assets/javascripts/components/search/standalone-dropdown-filter.ts Updated inheritance for StandaloneDropdownFilter. Removed paramVal method.
app/assets/javascripts/i18n/translations.json Removed nested dropdown structures and added new search key with filters and tokens.
app/assets/javascripts/search.ts Added filterCollections: FilterCollection[] and public updateFilters method to manage filter options.
app/assets/javascripts/state/SavedAnnotations.ts Added filtersByURL property and getFilters method to manage filter options by URL.
app/assets/stylesheets/components/dropdown-filter.css.scss Added .disabled modifier class for dropdown styling.
app/assets/stylesheets/components/token.css.scss Added .disabled class for token styling.
app/controllers/activities_controller.rb Included HasFilter module and replaced existing scope definitions with filters.
app/controllers/activity_read_states_controller.rb Simplified logic for fetching @course and adjusted @course_labels logic.
app/controllers/annotations_controller.rb Included HasFilter module and updated filtering logic for questions.
app/controllers/concerns/has_filter.rb Introduced HasFilter module for filtering capabilities.
app/controllers/course_members_controller.rb Included HasFilter module and updated filtering logic.
app/controllers/courses_controller.rb Included HasFilter module and updated filtering logic.
app/controllers/evaluations_controller.rb Included HasFilter module and updated filtering logic.
app/controllers/events_controller.rb Updated filtering mechanism to use HasFilter.
app/controllers/feedbacks_controller.rb Included HasFilter module and updated filtering logic.
app/controllers/saved_annotations_controller.rb Included HasFilter module and updated filtering logic.
app/controllers/series_controller.rb Included HasFilter module and added filtering capabilities.
app/controllers/submissions_controller.rb Included HasFilter module and updated filtering logic.
app/controllers/users_controller.rb Included HasFilter module and updated filtering logic.
app/models/activity.rb Included Filterable module and updated filtering methods.
app/models/activity_read_state.rb Added FilterableByCourseLabels and updated filtering logic.
app/models/annotation.rb Included Filterable module and updated filtering methods.
app/models/concerns/filterable.rb Enhanced filtering capabilities with new methods.
app/models/concerns/filterable_by_course_labels.rb Introduced FilterableByCourseLabels module for filtering by course labels.
app/models/course.rb Updated filtering methods and removed set_search.
app/models/course_membership.rb Included Filterable module and updated filtering methods.
app/models/event.rb Included Filterable module and updated filtering method for event_type.
app/models/question.rb Included Filterable module and added filtering for question_state.
app/models/saved_annotation.rb Included Filterable module and updated filtering methods.
app/models/submission.rb Included Filterable module and added new filter options.
app/models/user.rb Updated filtering methods and associations.
app/policies/annotation_policy.rb Updated query conditions in the resolve method.
app/views/activities/available.js.erb Added dodona.search.updateFilters(filters) method call.
app/views/activities/index.html.erb Simplified parameters passed to search bar partial.
app/views/activities/index.js.erb Added dodona.search.updateFilters(<%= raw @filters.to_json %>) method call.
app/views/activity_read_states/index.html.erb Removed course_labels local variable from search bar partial.
app/views/annotations/question_index.html.erb Removed courses and question_states from search bar locals.
app/views/course_members/index.html.erb Removed course_labels and institutions from search bar locals.
app/views/courses/index.html.erb Removed institutions from search bar locals.
app/views/courses/index.js.erb Added dodona.search.updateFilters(<%= raw @filters.to_json %>) method call.
app/views/courses/new.html.erb Removed institutions from search bar locals.
app/views/courses/scoresheet.html.erb Removed course_labels from search bar locals.
app/views/evaluations/_add_users.html.erb Removed course_labels and institutions from search bar locals.
app/views/evaluations/add_users.js.erb Added dodona.search.updateFilters(filters) method call.
app/views/evaluations/edit.html.erb Removed course_labels and institutions from search bar locals.
app/views/evaluations/show.html.erb Removed course_labels from search bar locals.
app/views/events/index.html.erb Removed event_types from search bar locals.
app/views/feedbacks/edit.html.erb Removed statuses from search bar locals.
app/views/judges/show.html.erb Updated method to count activities by judge.
app/views/layouts/_searchbar.html.erb Enhanced search bar functionality with new local variables and streamlined JavaScript.
app/views/repositories/admins.html.erb Removed institutions from search bar locals.
app/views/repositories/show.html.erb Simplified parameters passed to search bar partial.
app/views/saved_annotations/index.html.erb Removed courses and exercises from search bar locals.
app/views/saved_annotations/new.html.erb Removed courses and exercises from search bar locals.
app/views/series/edit.html.erb Removed multiple local variables from search bar locals.
app/views/series/scoresheet.html.erb Removed course_labels from search bar locals.
app/views/submissions/index.html.erb Removed course_labels and statuses from search bar locals.
app/views/users/index.html.erb Removed institutions from search bar locals.
config/locales/js/en.yml Updated localization keys for search and filter sections.
config/locales/js/nl.yml Updated localization keys for search and filter sections.
test/javascript/components/search/dropdown_filter.test.ts Updated test setup for DropdownFilter component.
test/javascript/components/search/search_field.test.ts Updated imports and filter data structure in tests.
test/javascript/components/search/search_token.test.ts Updated import path for Label.
test/models/activity_test.rb Removed test for filtering by popularity.
test/models/saved_annotation_test.rb Updated filtering method calls to use IDs.

Possibly related PRs

  • Fix filtering on new saved annotation page #5838: The changes in this PR involve filtering logic in the SavedAnnotationsController, which is directly related to the management of filter states in the SavedAnnotationList component modified in the main PR. Both PRs focus on enhancing the filtering capabilities for saved annotations, indicating a strong connection in their objectives.

Suggested labels

bug

Suggested reviewers

  • bmesuere

Warning

Rate limit exceeded

@jorg-vr has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 1 minutes and 2 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 0c2bc88 and 2c4b3cf.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 43

🧹 Outside diff range and nitpick comments (68)
app/views/annotations/question_index.js.erb (1)

2-2: Consider adding error handling for filter updates

The filter update operation might fail if the JSON is malformed or if there are network issues. Consider wrapping the update in a try-catch block and showing appropriate error messages to users.

Here's a suggested implementation:

-dodona.search.updateFilters(<%= @filters.to_json.html_safe %>);
+try {
+  dodona.search.updateFilters(<%= @filters.to_json.html_safe %>);
+} catch (error) {
+  dodona.showError('Failed to update search filters. Please refresh the page.');
+  console.error('Filter update failed:', error);
+}
app/views/course_members/index.js.erb (1)

3-3: Add error handling for filter updates.

The filter update operation might fail if the JSON is malformed or if there are network issues. Consider wrapping it in a try-catch block and showing an appropriate error message to users.

-dodona.search.updateFilters(<%= raw @filters.to_json %>);
+try {
+  dodona.search.updateFilters(<%= raw @filters.to_json %>);
+} catch (error) {
+  console.error('Failed to update filters:', error);
+  dodona.showError('Failed to update search filters. Please refresh the page.');
+}
app/views/activities/available.js.erb (1)

3-3: Add error handling for undefined objects

The code looks good, but consider adding error handling to prevent potential runtime errors.

-dodona.search.updateFilters(<%= raw @filters.to_json %>);
+if (dodona.search && typeof dodona.search.updateFilters === 'function') {
+  dodona.search.updateFilters(<%= raw @filters.to_json %>);
+} else {
+  console.error('Failed to update filters: dodona.search.updateFilters is not available');
+}
app/assets/stylesheets/components/dropdown-filter.css.scss (1)

14-19: Use Bootstrap SCSS variables for consistent theming

The disabled state styling uses hardcoded color values. Consider using Bootstrap's SCSS variables for better maintainability and consistent theming.

  &.disabled {
    .dropdown-toggle {
-      background-color: #f8f9fa;
-      color: #6c757d;
+      background-color: $gray-100;
+      color: $gray-600;
    }
  }
app/views/evaluations/add_users.js.erb (1)

4-4: Add error handling for nil filters.

The code assumes @filters is always present and valid. Consider adding a nil check to prevent potential JavaScript errors.

Apply this diff to add error handling:

-dodona.search.updateFilters(<%= raw @filters.to_json %>);
+<% if @filters %>
+  dodona.search.updateFilters(<%= json_escape(@filters.to_json) %>);
+<% else %>
+  console.warn('No filters data available');
+  dodona.search.updateFilters({});
+<% end %>
app/controllers/events_controller.rb (1)

1-11: Overall implementation feedback

The changes align well with the PR objectives of adding dynamic counts to search filters. The implementation follows a consistent pattern seen across other controllers.

However, there are some key points to consider:

  1. Test coverage is missing for the new filtering functionality
  2. Performance implications need to be monitored, especially given the concerns raised in PR discussions
  3. The implementation correctly maintains authorization checks with policy_scope

Consider extracting filter-related test helpers into a shared context to make it easier to test similar implementations across controllers.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 2-2: app/controllers/events_controller.rb#L2
Added line #L2 was not covered by tests


[warning] 4-4: app/controllers/events_controller.rb#L4
Added line #L4 was not covered by tests


[warning] 8-8: app/controllers/events_controller.rb#L8
Added line #L8 was not covered by tests

app/views/saved_annotations/new.html.erb (1)

Line range hint 8-12: Consider documenting the dynamic filter behavior

The searchbar now handles filters internally as part of the broader refactor. Consider adding a comment explaining how the dynamic counts feature works in this context, particularly for future maintainers.

Add a comment above the searchbar render:

+<%# Searchbar handles filters internally and updates counts dynamically %>
 <%= render partial: 'layouts/searchbar', locals: { refresh_element: "#annotation-container" } %>
app/views/activities/index.html.erb (1)

9-9: Consider performance optimization strategies.

Based on the PR objectives, this page could see increased query times up to 300ms due to the dynamic count queries. Consider implementing:

  1. Caching strategies for filter counts
  2. Lazy loading of counts
  3. Batch loading of counts

Would you like assistance in implementing any of these optimization strategies?

app/assets/javascripts/components/search/filter_collection.ts (2)

6-10: Add JSDoc comments to document the class and its properties

While the code is well-structured, adding documentation would improve maintainability and help other developers understand the purpose of these properties.

Consider adding documentation like this:

+/**
+ * Manages a collection of filters for search components
+ */
 export class FilterCollection extends DodonaElement {
     @property({ type: Array })
+    /** Array of filter options available for this collection */
     filters: FilterOptions[] = [];
     @property({ type: Array })
+    /** Array of filter parameters to hide from display */
     hide: string[] = [];

6-20: Consider adding loading state handling

Given the performance concerns mentioned in the PR objectives regarding count queries, it would be beneficial to add loading state handling to provide better user feedback during slower operations.

Consider adding:

  1. A loading property to track the loading state of filter counts
  2. Methods to update the loading state
  3. Integration with any existing loading indicators in the UI

Example addition:

@property({ type: Boolean })
loading = false;

updateFilters(newFilters: FilterOptions[]): void {
    this.loading = true;
    this.filters = newFilters;
    // After counts are updated
    this.loading = false;
}
app/views/saved_annotations/index.html.erb (1)

14-14: Document the autoSearch parameter

Consider adding a comment explaining the purpose of autoSearch: false to improve maintainability, as this parameter's behavior isn't immediately obvious.

+        <%# Disable automatic search on page load to prevent unnecessary API calls %>
         <%= render partial: 'layouts/searchbar', locals: { autoSearch: false } %>
app/views/annotations/question_index.html.erb (1)

29-29: Consider adding ARIA attributes for dynamic filter counts

Since the filters now include dynamic counts, ensure they're accessible to screen readers.

Add appropriate ARIA attributes to the searchbar partial to announce filter count updates:

-<%= render partial: 'layouts/searchbar', locals: { options: options, refresh_element: "#question-container"} %>
+<%= render partial: 'layouts/searchbar', locals: { 
+  options: options, 
+  refresh_element: "#question-container",
+  aria: { 
+    live: "polite",
+    atomic: true
+  }
+} %>
app/policies/annotation_policy.rb (1)

8-8: Consider adding tests for association queries.

To prevent similar association name mismatches in the future, consider adding tests that verify the policy's scope resolution with various user scenarios (regular user, course admin, zeus).

Example test cases to consider:

  • User viewing their own submission annotations
  • Course admin viewing annotations in their course
  • User attempting to view annotations from other courses
test/models/saved_annotation_test.rb (2)

29-31: LGTM! Consider adding edge case tests.

The test coverage for course_id filtering looks good, verifying nil values, matching courses, and non-matching courses. However, consider adding tests for edge cases:

  • Invalid course IDs
  • Deleted courses
  • Multiple courses with the same filter

41-43: LGTM! Consider reducing test duplication.

The test structure is identical to the course_id filtering test. Consider using a shared example or test helper to reduce duplication while maintaining the same test coverage.

Example refactor:

def assert_filtered_by(scope, field, record)
  filtered = SavedAnnotation.public_send(:"by_#{field}_id", record.id)
  [
    create(:saved_annotation, field => nil),
    create(:saved_annotation, field => record)
  ].each { |s| assert_includes filtered, s }
  
  assert_not_includes filtered, create(:saved_annotation, field => create(scope))
end

test 'filtering by various fields should contain nil values' do
  user = create :user
  assert_filtered_by(:course, :course, create(:course))
  assert_filtered_by(:exercise, :exercise, create(:exercise))
end
app/assets/javascripts/components/search/standalone-dropdown-filter.ts (2)

Line range hint 6-16: Update component documentation to reflect new inheritance

The documentation comment still mentions inheriting from FilterCollectionElement, but the class now extends FilterElement.

Apply this diff to fix the documentation:

 /**
- * This component inherits from FilterCollectionElement.
+ * This component inherits from FilterElement.
  * It represents a dropdown which allows to select one label, with the currently selected label shown as a button
  *
  * @element d-standalone-dropdown-filter

26-26: LGTM! Improved label selection logic

The new selection logic using direct ID comparison is cleaner and more type-safe than the previous string conversion approach using paramVal.

Consider adding type safety by explicitly typing the default property:

    @property({ type: String })
-   default;
+   default: string | undefined;
app/assets/javascripts/components/search/filter_tabs.ts (4)

17-17: Update JSDoc to reflect new inheritance

The class now extends FilterElement, but the JSDoc comment still mentions FilterCollectionElement. Please update the documentation to avoid confusion.

-* This component inherits from FilterCollectionElement.
+* This component inherits from FilterElement.

Line range hint 47-49: Enhance accessibility for count badges

While the count badges are visually clear, screen readers might benefit from more context.

-<span class="badge rounded-pill colored-secondary" id="${label.id}-count">${label.count}</span>
+<span class="badge rounded-pill colored-secondary" 
+      id="${label.id}-count"
+      aria-label="${label.count} items in ${label.name}">
+  ${label.count}
+</span>

Line range hint 1-54: Overall implementation looks solid

The component successfully implements dynamic counts while maintaining good performance and accessibility. The code is clean, well-structured, and follows TypeScript best practices.

Consider implementing a loading state for the count badges if the counts are fetched asynchronously, to prevent layout shifts during updates.


Bootstrap tooltips are not initialized in filter_tabs.ts

Ensure that initTooltips is imported and called in filter_tabs.ts to properly initialize Bootstrap tooltips.

  • File: app/assets/javascripts/components/search/filter_tabs.ts
🔗 Analysis chain

Line range hint 43-44: Verify Bootstrap tooltip initialization

The code uses Bootstrap tooltips with data-bs-toggle="tooltip". Ensure that tooltips are properly initialized, as they typically require manual initialization.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for tooltip initialization code
rg -l "tooltip.*initialize|bootstrap.*tooltip" --type ts

Length of output: 58


Script:

#!/bin/bash
# Search for any Bootstrap tooltip initialization in TypeScript files
rg -i "bootstrap\.tooltip\(|\$\(.*\).tooltip\(" --type ts

Length of output: 269


Script:

#!/bin/bash
# Search for tooltip-related data attributes and associated initialization
rg -i "data-bs-toggle\s*=\s*['\"]tooltip['\"]" --type ts

Length of output: 1815


Script:

#!/bin/bash
# Search for initializing tooltips within DOMContentLoaded or similar event listeners
rg -i "addEventListener\(['\"]DOMContentLoaded['\"].*bootstrap\.Tooltip" --type ts

Length of output: 88


Script:

#!/bin/bash
# Search for any tooltip method invocations in TypeScript files
rg -i "\.tooltip\s*\(" --type ts

Length of output: 245


Script:

#!/bin/bash
# Search for global tooltip initialization calls
rg -i "initializeTooltip\(" --type ts

Length of output: 39


Script:

#!/bin/bash
# Search for tooltip initialization within common initialization functions
rg -i "init.*tooltip" --type ts

Length of output: 2334


Script:

#!/bin/bash
# Check if initTooltips is imported and called in filter_tabs.ts
rg -i "initTooltips\s*\(" app/assets/javascripts/components/search/filter_tabs.ts --type ts

Length of output: 93


Script:

#!/bin/bash
# Check for import statement of initTooltips in filter_tabs.ts
rg -i "import\s+{?\s*initTooltips\s*}?\s+from\s+['\"]utilities['\"]" app/assets/javascripts/components/search/filter_tabs.ts --type ts

Length of output: 291


Script:

#!/bin/bash
# Check for import statement of initTooltips in filter_tabs.ts
rg -i "import\s+{\s*initTooltips\s*}\s+from\s+['\"]utilities['\"]" app/assets/javascripts/components/search/filter_tabs.ts --type ts

Length of output: 287


Script:

#!/bin/bash
# Check if initTooltips is called in filter_tabs.ts
rg -i "initTooltips\s*\(" app/assets/javascripts/components/search/filter_tabs.ts --type ts

Length of output: 93

app/models/course_membership.rb (2)

15-15: Good architectural choice!

Including the Filterable module aligns well with the goal of centralizing and simplifying filter logic. This promotes better maintainability and reduces code duplication across the application.

Consider documenting the available filter options in the model's documentation to help other developers understand the filtering capabilities.


34-35: Consider performance optimization strategies

Given the PR's concerns about count query performance:

  1. Consider adding database indexes to optimize the join paths:
    • course_labels.name
    • institutions.id
  2. Evaluate caching strategies for the count queries, especially for frequently accessed filters
  3. Monitor query execution plans in production to identify potential bottlenecks

Would you like help designing the appropriate database indexes or implementing a caching strategy?

app/models/question.rb (1)

Line range hint 37-44: Enhance error handling in reset_in_progress method.

While the method includes a basic existence check, consider adding error logging and handling for edge cases:

 def self.reset_in_progress(id)
-  return unless exists?(id)
+  begin
+    return unless exists?(id)
 
-  question = find(id)
-  return unless question.question_state == 'in_progress'
+    question = find(id)
+    return unless question.question_state == 'in_progress'
 
-  question.update(question_state: 'unanswered')
+    question.update!(question_state: 'unanswered')
+  rescue ActiveRecord::RecordNotFound => e
+    Rails.logger.info("Question #{id} was deleted before reset_in_progress could run")
+  rescue => e
+    Rails.logger.error("Failed to reset question #{id}: #{e.message}")
+    raise
+  end
 end
app/assets/stylesheets/components/token.css.scss (1)

40-44: LGTM! Consider adding ARIA attributes for better accessibility.

The disabled state implementation follows best practices with appropriate opacity, cursor, and pointer-events. The opacity value of 0.38 aligns well with Material Design standards for disabled states.

Consider adding a note in the code comments to remind developers to include appropriate ARIA attributes (e.g., aria-disabled="true") in the HTML markup when using this class. This would enhance accessibility for screen readers. Example:

  // Remember to add aria-disabled="true" when using this class
  &.disabled {
    opacity: 0.38;
    cursor: initial;
    pointer-events: none;
  }
app/models/activity_read_state.rb (1)

Line range hint 14-45: Document filtering behavior and performance characteristics

Consider adding documentation to clarify:

  1. The interaction between FilterableByCourseLabels and existing scopes
  2. Performance implications of dynamic counts
  3. Cache invalidation impact on filtered results

Add documentation above the module includes:

+ # Enables dynamic filtering and counting of activities by course labels
+ # Performance note: Counting results may impact response times for large datasets
app/controllers/annotations_controller.rb (2)

2-2: Consider consolidating filtering approaches.

The controller is using both has_scope and has_filter declarations. While this works, having two different filtering approaches might make the code harder to maintain.

Consider migrating the remaining has_scope declarations to has_filter for consistency:

- has_scope :by_submission, as: :submission_id
- has_scope :by_user, as: :user_id
+ has_filter :submission_id
+ has_filter :user_id

Also applies to: 8-9


Line range hint 26-34: Simplify complex conditional logic.

The conditional logic for handling unfiltered questions for course admins could be extracted into a private method for better readability.

Consider refactoring to:

+    def filter_by_admin_courses?(unfiltered)
+      unfiltered && 
+      current_user&.a_course_admin? && 
+      !ActiveRecord::Type::Boolean.new.deserialize(params[:everything])
+    end

     # In question_index
-    if @unfiltered && current_user&.a_course_admin? && !ActiveRecord::Type::Boolean.new.deserialize(params[:everything])
+    if filter_by_admin_courses?(@unfiltered)
       @questions = @questions.where(
         course_id: current_user.administrating_courses.map(&:id)
       )
     end
app/views/evaluations/show.html.erb (1)

41-41: Document the new search filter behavior.

Since this change is part of a larger refactor to add dynamic counts to search filters, consider adding a comment explaining how the search functionality works in this view.

Add a comment above the searchbar render:

+<%# Search functionality with dynamic filter counts - see app/views/layouts/_searchbar for implementation %>
<%= render partial: 'layouts/searchbar' %>
app/controllers/feedbacks_controller.rb (1)

3-3: LGTM! Consider adding documentation.

The inclusion of HasFilter aligns with the PR objectives to simplify filter definitions. Consider adding a comment explaining the module's purpose and available filter methods for better maintainability.

+ # Include HasFilter to enable simplified filter definitions and dynamic counts
  include HasFilter
app/assets/javascripts/components/search/dropdown_filter.ts (1)

57-63: Consider i18n and performance optimizations

A few suggestions for improvement:

  1. The count display could benefit from i18n formatting (e.g., thousands separator)
  2. For large lists, consider memoizing the sorted results to avoid unnecessary re-sorting on each render
- ${this.filteredLabels.sort((a, b) => b.count - a.count).map(s => html`
+ ${this.memoizedSortedLabels().map(s => html`
       <li><span class="dropdown-item-text ">
           <div class="form-check">
               <input class="form-check-input" type="${this.multi?"checkbox":"radio"}" .checked=${this.isSelected(s)} @click="${() => this.toggle(s)}" id="check-${this.param}-${s.id}">
               <label class="form-check-label" for="check-${this.param}-${s.id}">
                   ${s.name}
-                  ${s.count ? html`<span class="text-muted float-end ms-4">${s.count}</span>` : ""}
+                  ${s.count ? html`<span class="text-muted float-end ms-4">${i18n.t('js.numbers.format', { number: s.count })}</span>` : ""}
               </label>
           </div>
       </span></li>

Add this method to the class:

private memoizedSortedLabels = memoize(() => {
  return this.filteredLabels.sort((a, b) => b.count - a.count);
});
app/views/repositories/show.html.erb (1)

69-69: Consider adding a loading indicator for filter counts.

Since the PR objectives mention a potential ~300ms increase in query time for the activities page due to count queries, consider adding a loading indicator while filter counts are being fetched.

-<%= render(partial: 'layouts/searchbar', locals: {baseUrl: activities_path(repository_id: @repository.id), eager: true, no_filter_for: ['repository_id']}) %>
+<%= render(partial: 'layouts/searchbar', locals: {
+  baseUrl: activities_path(repository_id: @repository.id),
+  eager: true,
+  no_filter_for: ['repository_id'],
+  show_loading_indicator: true
+}) %>
app/controllers/activity_read_states_controller.rb (1)

96-102: Consider enhancing filter initialization with error handling and documentation.

While the filter initialization logic is well-structured, consider these improvements:

  1. The filter options query might be expensive based on PR comments about count performance. Consider adding a comment documenting the performance characteristics.
  2. The method course_labels_filter_options could fail if the association is invalid.

Add error handling and documentation:

 if @course.present? && @user.blank? && current_user&.course_admin?(@course)
+  # Fetch course label filter options with counts. Note: This may impact performance
+  # on courses with many activities due to COUNT queries.
+  filter_options = begin
+    @read_states.course_labels_filter_options(@course.id)
+  rescue ActiveRecord::StatementInvalid => e
+    Rails.logger.error("Failed to fetch course label filter options: #{e.message}")
+    []
+  end
+
   @filters = [{
     param: 'course_labels',
     multi: true,
-    data: @read_states.course_labels_filter_options(@course.id)
+    data: filter_options
   }]
 end
app/views/series/edit.html.erb (1)

90-90: Add documentation about the new filter behavior.

Consider adding a comment above the searchbar partial to document that filters are now dynamically populated. This will help other developers understand the new behavior.

Add this comment above the searchbar:

+<%# Filters are dynamically populated based on the current filtering state %>
 <%= render partial: 'layouts/searchbar', locals: {baseUrl: available_activities_series_path(@series), eager: true, local_storage_key: 'seriesActivitySearch'} %>
app/models/annotation.rb (1)

Line range hint 25-61: Consider caching strategies for expensive filter counts.

Given the PR objectives and the concerns about COUNT query performance, consider implementing caching strategies for filter counts, especially for:

  1. Frequently accessed combinations of filters
  2. Filters on the submissions association, which was noted as particularly slow

Some suggestions:

  1. Use counter caches for common filter combinations
  2. Consider implementing Russian Doll caching for nested filter counts
  3. Add monitoring for filter query performance in production
  4. Document any performance limitations or recommendations in the model

Would you like help implementing any of these suggestions?

app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts (1)

60-60: Consider performance optimization for filter updates.

The call to search.updateFilters on every render might impact performance, especially given the concerns about count queries mentioned in the PR discussion. Consider:

  1. Adding a check to prevent unnecessary updates
  2. Implementing debouncing for the filter updates

Here's a suggested improvement:

 render(): TemplateResult {
-    search.updateFilters(this.filters);
+    // Only update filters if they've changed
+    if (!this.arraysEqual(this.lastFilters, this.filters)) {
+        search.updateFilters(this.filters);
+    }
     return this.savedAnnotations.length > 0 ? html`

Add this helper method to the class:

private arraysEqual(a: FilterOptions[], b: FilterOptions[]): boolean {
    return JSON.stringify(a) === JSON.stringify(b);
}
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 60-60: app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts#L60
Added line #L60 was not covered by tests

app/controllers/saved_annotations_controller.rb (1)

Line range hint 46-52: Consider optimizing database queries.

The implementation looks good but consider these optimizations:

  1. The separate queries for submissions, courses, and exercises could be combined
  2. Consider using a single query with joins instead of multiple pluck operations

Example optimization:

submissions_with_associations = Submission
  .joins(:course, :exercise)
  .where(id: @annotations.pluck(:submission_id).uniq)
  .select('submissions.*, courses.*, activities.*')

@courses = submissions_with_associations.map(&:course).uniq
@exercises = submissions_with_associations.map(&:exercise).uniq
app/assets/javascripts/search.ts (2)

17-17: Add documentation for the filterCollections property

Consider adding JSDoc comments to explain the purpose and usage of this property, especially since it's part of a significant refactor of the filtering system.

+    /** Collection of filter instances used to manage search filter state and UI */
     filterCollections: FilterCollection[] = [];

131-138: Consider implementing filter update optimizations

Given the performance concerns mentioned in the PR objectives regarding count queries, consider implementing:

  1. Debouncing for filter updates to prevent excessive re-renders
  2. Caching of filter states to reduce unnecessary updates
  3. Batch updates when multiple filters change simultaneously

Example implementation:

private filterUpdateDelayer = createDelayer();
private cachedFilters: Map<string, FilterOptions[]> = new Map();

public updateFilters(filterOptions: FilterOptions[] | undefined): void {
    const cacheKey = JSON.stringify(filterOptions);
    if (this.cachedFilters.has(cacheKey)) {
        return;
    }
    
    this.filterUpdateDelayer(() => {
        // ... existing implementation ...
        this.cachedFilters.set(cacheKey, coloredFilterOptions);
    }, 150);
}
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 131-131: app/assets/javascripts/search.ts#L131
Added line #L131 was not covered by tests


[warning] 133-133: app/assets/javascripts/search.ts#L133
Added line #L133 was not covered by tests


[warning] 135-136: app/assets/javascripts/search.ts#L135-L136
Added lines #L135 - L136 were not covered by tests

app/assets/javascripts/state/SavedAnnotations.ts (1)

37-37: Consider implementing response caching for filter data

Given the performance concerns mentioned in the PR discussion about count queries, consider these optimizations:

  1. Implement client-side caching with a TTL for filter data
  2. Add a mechanism to invalidate filter cache when relevant data changes
  3. Consider batch loading filters for common parameter combinations

This would help mitigate the performance impact while maintaining the dynamic nature of the filters.

Also applies to: 96-104

test/javascript/components/search/search_field.test.ts (2)

67-88: Consider enhancing test coverage with diverse filter configurations

The new filter structure is cleaner and more maintainable. However, consider adding test cases for:

  • Filters with multi: true
  • Different color values
  • Empty data arrays

Example enhancement:

 const filters: FilterOptions[] = [
     {
         param: "foo",
         data: [
             { name: "fool", id: "1" },
             { name: "bar", id: "2" },
             { name: "baz", id: "3" },
         ],
         multi: false,
         color: "red",
     },
     {
         param: "bar",
         data: [
             { name: "food", id: "1" },
             { name: "barn", id: "2" },
             { name: "baz", id: "3" },
         ],
         multi: false,
         color: "red",
+    },
+    {
+        param: "tags",
+        data: [
+            { name: "important", id: "1" },
+            { name: "urgent", id: "2" }
+        ],
+        multi: true,
+        color: "blue",
+    },
+    {
+        param: "empty",
+        data: [],
+        multi: false,
+        color: "gray",
     }
 ];

Add tests for dynamic counts and performance

Current test suite lacks coverage for dynamic count updates and performance metrics. Please implement:

  1. Tests for dynamic count updates as outlined in PR objectives.
  2. Performance tests to assess:
    • Multiple concurrent filter updates
    • Handling large data sets
    • Adherence to response time thresholds
🔗 Analysis chain

Line range hint 1-143: Consider adding tests for dynamic counts and performance

While the existing tests cover the basic functionality well, consider adding:

  1. Tests for the dynamic count updates mentioned in PR objectives
  2. Performance tests to validate the concerns about count queries, especially for:
    • Multiple concurrent filter updates
    • Large data sets
    • Response time thresholds

Let's check if there are any existing performance tests:

Consider implementing performance monitoring in the test suite using browser performance APIs or similar tools to catch potential performance regressions early.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for performance-related tests
rg -i "performance|benchmark|timing" --type ts

Length of output: 286

app/controllers/evaluations_controller.rb (3)

4-4: Consider migrating all scopes to the new filter system.

While the has_filter :institution_id change aligns with the PR objectives, mixing has_filter and has_scope could lead to maintenance challenges. Consider migrating the remaining scopes to use the new filter system for consistency.

The complex conditional in by_course_labels scope might be better handled within the filter system, which is designed for such scenarios.

Also applies to: 9-17


Line range hint 88-98: Extract duplicated filter and query logic.

The filter initialization and query chain are identical to the edit action. Consider extracting this common logic into a private method to follow the DRY principle:

+ private
+
+ def set_course_membership_filters
+   @filters = filters(@course.course_memberships)
+   @filters << {
+     param: 'course_labels',
+     multi: true,
+     data: apply_scopes(@course.course_memberships).course_labels_filter_options
+   }
+ end
+
+ def load_course_memberships
+   @course_memberships = apply_scopes(@course.course_memberships)
+                         .includes(:course_labels, user: [:institution])
+                         .order(status: :asc)
+                         .order(Arel.sql('users.permission ASC'))
+                         .order(Arel.sql('users.last_name ASC'), Arel.sql('users.first_name ASC'))
+                         .where(status: %i[course_admin student])
+                         .paginate(page: parse_pagination_param(params[:page]), per_page: 15)
+ end

Then use these methods in both actions:

 def edit
   @should_confirm = params[:confirm].present?
   @course = @evaluation.series.course
-  @filters = filters(@course.course_memberships)
-  @filters << {
-    param: 'course_labels',
-    multi: true,
-    data: apply_scopes(@course.course_memberships).course_labels_filter_options
-  }
-  @course_memberships = apply_scopes(@course.course_memberships)...
+  set_course_membership_filters
+  load_course_memberships
   # rest of the method
 end

Potential N+1 Queries Detected.

The @course_memberships.each loop in app/views/course_members/index.csv.erb accesses methods like correct_exercises and attempted_exercises on cm.user, which may trigger additional database queries. Review and optimize these method calls to prevent N+1 query issues.

🔗 Analysis chain

Line range hint 58-67: Consider optimizing the query chain.

While the filter initialization looks good, the subsequent query chain is complex and might benefit from optimization:

  1. Consider extracting the query chain into a scope or class method for reusability
  2. Verify if additional eager loading is needed to prevent N+1 queries
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for potential N+1 queries in course memberships
rg -A 2 "course_memberships\." app/models/

Length of output: 925


Script:

#!/bin/bash
# Search for iterations over @course_memberships in controllers and views
rg "@course_memberships\.each" app/controllers/ app/views/

Length of output: 138


Script:

#!/bin/bash
# Search for method calls on cm in the course_memberships view
rg "cm\.[a-zA-Z_]+" app/views/course_members/index.csv.erb

Length of output: 289

app/controllers/submissions_controller.rb (2)

Line range hint 172-209: Consider extracting filter logic for better maintainability.

The set_submissions method handles multiple concerns (filtering, scoping, ordering). Consider extracting the filter initialization and course labels logic into separate methods:

 def set_submissions
   @submissions = policy_scope(Submission)
   if params[:user_id]
     @user = User.find(params[:user_id])
     @submissions = @submissions.of_user(@user)
   end

   set_filter_context
   apply_context_filters
   initialize_filters
   
   @course_membership = CourseMembership.find_by(user: @user, course: @course) if @user.present? && @course.present?
   apply_most_recent_filter if params[:most_recent_per_user]
 end

+private
+
+def set_filter_context
+  @course = Course.find(params[:course_id]) if params[:course_id]
+  @series = Series.find(params[:series_id]) if params[:series_id]
+  activity_id = params[:activity_id] || params[:exercise_id]
+  @activity = Exercise.find(activity_id) if activity_id
+  @judge = Judge.find(params[:judge_id]) if params[:judge_id]
+end
+
+def apply_context_filters
+  if @activity
+    @submissions = @submissions.of_exercise(@activity)
+    if @course
+      @submissions = @submissions.in_course(@course)
+    elsif @series
+      @submissions = @submissions.in_course(@series.course)
+    end
+  elsif @series
+    @submissions = @submissions.in_series(@series)
+  elsif @course
+    @submissions = @submissions.in_course(@course)
+  elsif @judge
+    @submissions = @submissions.of_judge(@judge)
+  end
+end
+
+def initialize_filters
+  @filters = filters(@submissions)
+  @submissions = apply_scopes(@submissions)
+  add_course_labels_filter if @course.present? && @user.blank? && current_user&.course_admin?(@course)
+end
+
+def add_course_labels_filter
+  @filters << {
+    param: 'course_labels',
+    multi: true,
+    data: @submissions.course_labels_filter_options(@course.id)
+  }
+end
+
+def apply_most_recent_filter
+  @submissions = @submissions.most_recent_per_user
+
+  if params[:order_by].present?
+    @submissions = Submission.where(id: @submissions)
+    @submissions = apply_scopes(@submissions, { order_by: params[:order_by] })
+  else
+    @submissions = @submissions.reorder(id: :desc)
+  end
+end

200-201: Document performance considerations for filter initialization.

Based on the PR objectives, there were performance concerns with count queries on the submissions table. Consider adding a comment explaining that counts were intentionally removed from the submissions page due to performance impact.

+  # Note: Count queries for filters were removed from submissions page due to
+  # significant performance impact (tripled response time in testing).
   @filters = filters(@submissions)
   @submissions = apply_scopes(@submissions)
config/locales/js/en.yml (1)

210-212: Consider standardizing remaining filter key names.

Some keys don't follow the established naming pattern:

  • course_labels could be renamed to course_label for consistency
  • status could be renamed to status_id if it references a foreign key
app/controllers/activities_controller.rb (3)

74-74: Monitor performance impact of filter count queries.

The initialization of @filters before pagination is correctly placed. However, based on the PR discussion, these count queries might impact performance, especially on pages with multiple filters.

Consider:

  1. Adding performance monitoring for these filter count queries
  2. Implementing caching for filter counts if they become a bottleneck
  3. Adding database indexes to optimize the count queries

Would you like help setting up monitoring or implementing caching for these queries?

Also applies to: 78-81


106-108: Ensure consistent performance optimization across actions.

The filter initialization in the available action mirrors the index action. Any performance optimizations implemented for the index action should also be applied here for consistency.

Consider extracting the filter initialization and performance optimization logic into a shared method to ensure consistent behavior across both actions.

+  private
+
+  def initialize_filters(scope)
+    @filters = filters(scope)
+    scope = apply_scopes(scope)
+    scope
+  end

Missing filterable_by declarations in Activity model.

  • Add filterable_by :popularity to app/models/activity.rb.
  • Add filterable_by :description_languages to app/models/activity.rb.
🔗 Analysis chain

Line range hint 5-29: LGTM! Verify filter behavior with multiple selections.

The migration from has_scope to has_filter looks good and aligns with the PR objectives. The multi-selection capability is properly configured for labels and description_languages.

Let's verify the filter declarations match their model counterparts:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that the model supports these filterable fields
# Expected: Find corresponding filterable_by declarations in the Activity model

ast-grep --pattern 'class Activity < ApplicationRecord
  $$$
  filterable_by :programming_language
  $$$
  filterable_by :type
  $$$
  filterable_by :judge_id
  $$$
  filterable_by :labels
  $$$
  filterable_by :repository_id
  $$$
  filterable_by :draft
  $$$
  filterable_by :popularity
  $$$
  filterable_by :description_languages
  $$$
'

Length of output: 343


Script:

#!/bin/bash
# Description: Search for filterable_by declarations in the Activity model
# Expected: List all instances of filterable_by in app/models/activity.rb

rg 'filterable_by\s+:.*' app/models/activity.rb

Length of output: 707

config/locales/js/nl.yml (2)

212-212: Resolve duplicate translations for type

Lines 212 and 220 both translate to "type" which could cause confusion. Consider making these more specific:

  • type: "Type"
  • event_type: "Event type" or "Gebeurtenistype"

Also applies to: 220-220


206-218: Consider standardizing translation verbosity

Some translations use abbreviated forms while others use full words:

  • description_languages: "Talen" (abbreviated)
  • programming_language: "Programmeertaal" (full word)
  • institution_id: "Onderwijsinstelling" (full word)

Consider standardizing the verbosity level across all translations for better consistency.

app/models/submission.rb (2)

72-75: Consider caching filter options for better performance

While the implementation correctly maps status enums to filter options, the comment suggests performance concerns. Since the status options are static (defined by the enum), consider caching the result of this mapping to avoid regenerating it on each request.

-  filter_options_for :status do
-    # Filter options without count as this is a very expensive operation
-    statuses.map { |key, _| { id: key.to_s, name: human_enum_name(:status, key) } }
-  end
+  filter_options_for :status do
+    @status_filter_options ||= statuses.map { |key, _| 
+      { id: key.to_s, name: human_enum_name(:status, key) }
+    end
+  end

Add necessary indexes to the submissions table to improve query performance.

Ensure that frequently filtered columns in the submissions table are properly indexed to address the performance concerns.

🔗 Analysis chain

Line range hint 72-89: Consider database optimizations for filter performance

Based on the PR objectives mentioning performance impacts (up to 300ms increase in response time), consider the following optimizations:

  1. Add composite indexes for frequently filtered columns
  2. Implement result set pagination
  3. Consider materialized views for complex aggregations
  4. Implement request-level caching for filter results
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check existing indexes on the submissions table
rg -A 5 "create_table.*submissions" db/schema.rb
rg "add_index.*submissions" db/schema.rb

# Check if there are other models implementing similar filtering
ast-grep --pattern 'include Filterable'

Length of output: 937

app/controllers/courses_controller.rb (1)

54-54: Consider caching filter results for performance

The PR comments mention performance concerns with count queries. Since @filters = filters(@courses) is called before pagination and could be expensive for large datasets, consider caching the results, especially for institution-specific views.

Consider implementing caching:

def index
  # ...
  @filters = Rails.cache.fetch("course_filters_#{cache_key}", expires_in: 1.hour) do
    filters(@courses)
  end
  # ...
end

Also applies to: 68-68

app/assets/javascripts/i18n/translations.json (1)

333-354: Maintain consistent capitalization in filter labels.

Some filter labels have inconsistent capitalization:

  • "Type", "Programming Language", "Status" start with capitals
  • While "event_type" uses lowercase

Consider standardizing the capitalization across all filter labels for better consistency.

  "filter": {
    "course_id": "Course",
    "course_labels": "Member labels",
    "description_languages": "Languages",
    "draft": "Draft",
-   "event_type": "Type",
+   "event_type": "Event Type",
    "exercise_id": "Exercise",
    "institution_id": "Institution",
    "judge_id": "Judge",
    "labels": "Labels",
    "popularity": "Popularity",
    "programming_language": "Programming Language",
    "question_state": "Status",
    "repository_id": "Repository",
    "status": "Status",
    "type": "Type"
  }
app/controllers/concerns/has_filter.rb (2)

21-21: Remove unnecessary RuboCop directive

The # rubocop:disable Naming/PredicateName comment is unnecessary here, as the method name has_filter does not violate any naming conventions. Removing this directive will clean up the code.

Apply this diff to remove the unnecessary directive:

-def has_filter(name, multi: false) # rubocop:disable Naming/PredicateName
+def has_filter(name, multi: false)
🧰 Tools
🪛 rubocop

[warning] 21-21: Unnecessary disabling of Naming/PredicateName.

(Lint/RedundantCopDisableDirective)


13-13: Use public_send instead of send for safer method invocation

Using public_send instead of send ensures that only public methods are called, preventing unintended access to private methods. This enhances the security and robustness of the code.

Apply this diff to replace send with public_send:

-              data: apply_scopes(target, scope_params).send("#{name}_filter_options"),
+              data: apply_scopes(target, scope_params).public_send("#{name}_filter_options"),
app/models/concerns/filterable_by_course_labels.rb (2)

16-20: Simplify the scope by removing redundant unscoped.where(id: select(:id)).

The use of unscoped.where(id: select(:id)) is redundant, as it effectively selects the same records. You can simplify the scope without altering its functionality by removing this portion.

Apply this diff to simplify the scope:

-def filterable_by_course_labels(through_user: false)
+def filterable_by_course_labels(through_user: false)
   if through_user
     has_many :course_memberships, through: :user
     has_many :course_labels, through: :course_memberships
   end

   scope :by_course_labels, lambda { |labels, course_id|
-    unscoped.where(id: select(:id))
-            .joins(:course_memberships)
-            .where(course_memberships: { id: CourseMembership.where(course_id: course_id).by_course_labels(labels) })
+    joins(:course_memberships)
+      .where(course_memberships: { id: CourseMembership.where(course_id: course_id).by_course_labels(labels) })
   }

29-30: Use reject instead of filter for clarity.

Since you're excluding options where option[:name] is not present, using reject with blank? can improve readability.

Apply this diff:

 count.map { |key, value| { id: key.to_s, name: key.to_s, count: value } }
-     .filter { |option| option[:name].present? }
+     .reject { |option| option[:name].blank? }
app/views/layouts/_searchbar.html.erb (1)

15-18: Verify boolean attributes are correctly interpreted

When setting attributes like eager, which are expected to be boolean, ensure that passing eager="<%= local_assigns.fetch :eager, false %>" results in the correct behavior. Since HTML attributes are strings, the component should correctly parse the string values "true" or "false" to boolean.

app/assets/javascripts/components/search/search_token.ts (2)

53-56: Ensure JSDoc comments for SearchTokens are up to date with recent changes.

The documentation should reflect that SearchTokens now extends FilterCollection and accurately describe its properties. Keeping comments synchronized with the code improves clarity and helps in future maintenance.

Apply this diff to update the comments:

 /**
  * This component represents a list of dismissible tokens.
  *
  * @element d-search-tokens
  *
  * @prop {FilterOptions[]} filters - the list of filter options for which tokens could be created
  * @prop {string[]} hide - the list of filter params that should never be shown
  */

58-68: Optimize the activeFilters getter for improved readability and performance.

Currently, the method iterates over this.visibleFilters and performs multiple lookups in searchQueryState. Consider refactoring to reduce redundancy and enhance performance. For example:

  • Accumulate the count using a reducer function.
  • Cache frequently accessed values if possible.
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 58-60: app/assets/javascripts/components/search/search_token.ts#L58-L60
Added lines #L58 - L60 were not covered by tests


[warning] 63-63: app/assets/javascripts/components/search/search_token.ts#L63
Added line #L63 was not covered by tests


[warning] 67-67: app/assets/javascripts/components/search/search_token.ts#L67
Added line #L67 was not covered by tests

app/models/concerns/filterable.rb (3)

20-20: Optimize the by_filter scope for performance

The by_filter scope splits the filter string and iteratively builds the query using inject. This could lead to performance issues with large datasets. Consider refactoring to use full-text search capabilities or indexing on the search column to improve query performance.


7-10: Enhance documentation for better maintainability

The class and its methods are complex. Adding more detailed documentation and examples for methods like search_by and filterable_by would improve code readability and maintainability.


51-60: Avoid potential issues with dynamic pluralization

In line 53, you're using column.to_s.pluralize.to_s, which relies on Rails' inflection to pluralize column names. This might not work as expected for irregular nouns. Consider specifying the exact enumeration name or handling irregular cases explicitly.

app/models/activity.rb (1)

92-96: Use select instead of filter for broader compatibility

While .filter is an alias for .select in Ruby 2.6 and later, using .select improves compatibility with earlier Ruby versions and enhances code readability.

Apply this diff to make the change:

 filter_options_for :description_languages do
   [{ id: 'nl', name: I18n.t('js.nl'), count: where(description_nl_present: true).count },
    { id: 'en', name: I18n.t('js.en'), count: where(description_en_present: true).count }]
-    .filter { |lang| lang[:count].positive? }
+    .select { |lang| lang[:count].positive? }
 end
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b7a244a and 1f11ba3.

📒 Files selected for processing (82)
  • app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts (2 hunks)
  • app/assets/javascripts/components/search/dropdown_filter.ts (4 hunks)
  • app/assets/javascripts/components/search/filter_collection.ts (1 hunks)
  • app/assets/javascripts/components/search/filter_element.ts (3 hunks)
  • app/assets/javascripts/components/search/filter_tabs.ts (2 hunks)
  • app/assets/javascripts/components/search/search_field.ts (6 hunks)
  • app/assets/javascripts/components/search/search_token.ts (3 hunks)
  • app/assets/javascripts/components/search/standalone-dropdown-filter.ts (2 hunks)
  • app/assets/javascripts/i18n/translations.json (4 hunks)
  • app/assets/javascripts/search.ts (3 hunks)
  • app/assets/javascripts/state/SavedAnnotations.ts (4 hunks)
  • app/assets/stylesheets/components/dropdown-filter.css.scss (1 hunks)
  • app/assets/stylesheets/components/token.css.scss (1 hunks)
  • app/controllers/activities_controller.rb (4 hunks)
  • app/controllers/activity_read_states_controller.rb (2 hunks)
  • app/controllers/annotations_controller.rb (3 hunks)
  • app/controllers/concerns/has_filter.rb (1 hunks)
  • app/controllers/course_members_controller.rb (2 hunks)
  • app/controllers/courses_controller.rb (5 hunks)
  • app/controllers/evaluations_controller.rb (4 hunks)
  • app/controllers/events_controller.rb (1 hunks)
  • app/controllers/feedbacks_controller.rb (2 hunks)
  • app/controllers/saved_annotations_controller.rb (3 hunks)
  • app/controllers/series_controller.rb (2 hunks)
  • app/controllers/submissions_controller.rb (4 hunks)
  • app/controllers/users_controller.rb (2 hunks)
  • app/models/activity.rb (2 hunks)
  • app/models/activity_read_state.rb (2 hunks)
  • app/models/annotation.rb (2 hunks)
  • app/models/concerns/filterable.rb (1 hunks)
  • app/models/concerns/filterable_by_course_labels.rb (1 hunks)
  • app/models/course.rb (2 hunks)
  • app/models/course_membership.rb (2 hunks)
  • app/models/event.rb (1 hunks)
  • app/models/question.rb (1 hunks)
  • app/models/saved_annotation.rb (2 hunks)
  • app/models/submission.rb (3 hunks)
  • app/models/user.rb (3 hunks)
  • app/policies/annotation_policy.rb (1 hunks)
  • app/views/activities/available.js.erb (1 hunks)
  • app/views/activities/index.html.erb (1 hunks)
  • app/views/activities/index.js.erb (1 hunks)
  • app/views/activity_read_states/index.html.erb (1 hunks)
  • app/views/annotations/question_index.html.erb (1 hunks)
  • app/views/annotations/question_index.js.erb (1 hunks)
  • app/views/course_members/index.html.erb (0 hunks)
  • app/views/course_members/index.js.erb (1 hunks)
  • app/views/courses/index.html.erb (1 hunks)
  • app/views/courses/index.js.erb (1 hunks)
  • app/views/courses/new.html.erb (1 hunks)
  • app/views/courses/scoresheet.html.erb (0 hunks)
  • app/views/courses/scoresheet.js.erb (1 hunks)
  • app/views/evaluations/_add_users.html.erb (0 hunks)
  • app/views/evaluations/add_users.js.erb (1 hunks)
  • app/views/evaluations/edit.html.erb (0 hunks)
  • app/views/evaluations/edit.js.erb (1 hunks)
  • app/views/evaluations/show.html.erb (1 hunks)
  • app/views/evaluations/show.js.erb (1 hunks)
  • app/views/events/index.html.erb (1 hunks)
  • app/views/events/index.js.erb (1 hunks)
  • app/views/feedbacks/edit.html.erb (1 hunks)
  • app/views/feedbacks/edit.js.erb (1 hunks)
  • app/views/judges/show.html.erb (1 hunks)
  • app/views/layouts/_searchbar.html.erb (2 hunks)
  • app/views/repositories/admins.html.erb (1 hunks)
  • app/views/repositories/show.html.erb (1 hunks)
  • app/views/saved_annotations/index.html.erb (1 hunks)
  • app/views/saved_annotations/new.html.erb (1 hunks)
  • app/views/saved_annotations/new.js.erb (1 hunks)
  • app/views/series/edit.html.erb (1 hunks)
  • app/views/series/scoresheet.html.erb (0 hunks)
  • app/views/submissions/index.html.erb (1 hunks)
  • app/views/submissions/index.js.erb (1 hunks)
  • app/views/users/index.html.erb (1 hunks)
  • app/views/users/index.js.erb (1 hunks)
  • config/locales/js/en.yml (1 hunks)
  • config/locales/js/nl.yml (1 hunks)
  • test/javascript/components/search/dropdown_filter.test.ts (1 hunks)
  • test/javascript/components/search/search_field.test.ts (2 hunks)
  • test/javascript/components/search/search_token.test.ts (1 hunks)
  • test/models/activity_test.rb (0 hunks)
  • test/models/saved_annotation_test.rb (2 hunks)
💤 Files with no reviewable changes (6)
  • app/views/course_members/index.html.erb
  • app/views/courses/scoresheet.html.erb
  • app/views/evaluations/_add_users.html.erb
  • app/views/evaluations/edit.html.erb
  • app/views/series/scoresheet.html.erb
  • test/models/activity_test.rb
🧰 Additional context used
🪛 GitHub Check: codecov/patch
app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts

[warning] 9-9: app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts#L9
Added line #L9 was not covered by tests


[warning] 48-50: app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts#L48-L50
Added lines #L48 - L50 were not covered by tests


[warning] 53-53: app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts#L53
Added line #L53 was not covered by tests


[warning] 55-56: app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts#L55-L56
Added lines #L55 - L56 were not covered by tests


[warning] 60-60: app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts#L60
Added line #L60 was not covered by tests

app/assets/javascripts/components/search/dropdown_filter.ts

[warning] 90-90: app/assets/javascripts/components/search/dropdown_filter.ts#L90
Added line #L90 was not covered by tests

app/assets/javascripts/components/search/filter_element.ts

[warning] 55-55: app/assets/javascripts/components/search/filter_element.ts#L55
Added line #L55 was not covered by tests

app/assets/javascripts/components/search/search_token.ts

[warning] 58-60: app/assets/javascripts/components/search/search_token.ts#L58-L60
Added lines #L58 - L60 were not covered by tests


[warning] 63-63: app/assets/javascripts/components/search/search_token.ts#L63
Added line #L63 was not covered by tests


[warning] 67-67: app/assets/javascripts/components/search/search_token.ts#L67
Added line #L67 was not covered by tests


[warning] 70-71: app/assets/javascripts/components/search/search_token.ts#L70-L71
Added lines #L70 - L71 were not covered by tests


[warning] 73-75: app/assets/javascripts/components/search/search_token.ts#L73-L75
Added lines #L73 - L75 were not covered by tests


[warning] 86-86: app/assets/javascripts/components/search/search_token.ts#L86
Added line #L86 was not covered by tests


[warning] 98-98: app/assets/javascripts/components/search/search_token.ts#L98
Added line #L98 was not covered by tests


[warning] 102-102: app/assets/javascripts/components/search/search_token.ts#L102
Added line #L102 was not covered by tests

app/assets/javascripts/search.ts

[warning] 131-131: app/assets/javascripts/search.ts#L131
Added line #L131 was not covered by tests


[warning] 133-133: app/assets/javascripts/search.ts#L133
Added line #L133 was not covered by tests


[warning] 135-136: app/assets/javascripts/search.ts#L135-L136
Added lines #L135 - L136 were not covered by tests

app/assets/javascripts/state/SavedAnnotations.ts

[warning] 48-48: app/assets/javascripts/state/SavedAnnotations.ts#L48
Added line #L48 was not covered by tests


[warning] 96-98: app/assets/javascripts/state/SavedAnnotations.ts#L96-L98
Added lines #L96 - L98 were not covered by tests


[warning] 100-100: app/assets/javascripts/state/SavedAnnotations.ts#L100
Added line #L100 was not covered by tests


[warning] 103-103: app/assets/javascripts/state/SavedAnnotations.ts#L103
Added line #L103 was not covered by tests

app/controllers/events_controller.rb

[warning] 2-2: app/controllers/events_controller.rb#L2
Added line #L2 was not covered by tests


[warning] 4-4: app/controllers/events_controller.rb#L4
Added line #L4 was not covered by tests


[warning] 8-8: app/controllers/events_controller.rb#L8
Added line #L8 was not covered by tests

app/controllers/feedbacks_controller.rb

[warning] 47-47: app/controllers/feedbacks_controller.rb#L47
Added line #L47 was not covered by tests


[warning] 49-50: app/controllers/feedbacks_controller.rb#L49-L50
Added lines #L49 - L50 were not covered by tests

app/models/concerns/filterable.rb

[warning] 72-72: app/models/concerns/filterable.rb#L72
Added line #L72 was not covered by tests

🪛 rubocop
app/controllers/concerns/has_filter.rb

[warning] 21-21: Unnecessary disabling of Naming/PredicateName.

(Lint/RedundantCopDisableDirective)

🔇 Additional comments (105)
app/views/activities/index.js.erb (1)

1-2: Verify performance impact of filter updates

Based on the PR objectives, the activities page could experience increased load times of up to 300ms due to additional count queries. Let's verify the performance impact.

app/views/feedbacks/edit.js.erb (1)

2-2: Verify performance impact of filter updates

Based on the PR objectives, there were concerns about the performance impact of count queries. Let's verify the implementation in this controller.

✅ Verification successful

Filter update implementation is properly optimized

The implementation uses efficient caching mechanisms and optimized queries:

  • Filter updates only refresh UI state with JSON data
  • Underlying queries leverage counter caches and Rails.cache
  • Scoped queries avoid expensive count operations
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for potential performance bottlenecks in the feedback filters implementation

# Check if FeedbacksController implements count optimization or caching
echo "Checking FeedbacksController implementation..."
rg -A 10 "class FeedbacksController" app/controllers/

# Look for any filter-related queries in the controller
echo "Checking filter-related queries..."
rg -l "has_filter|filterable_by" app/controllers/feedbacks_controller.rb

# Check if there are any performance-related comments or TODOs
echo "Checking for performance-related comments..."
rg -i "todo|performance|slow|optimize" app/controllers/feedbacks_controller.rb

Length of output: 1301


Script:

#!/bin/bash
# Description: Further investigate performance optimizations related to filter updates

# Search for caching implementations in the codebase
echo "Searching for caching implementations..."
rg "Rails.cache|cache\.fetch|cache_store" .

# Search for count queries in FeedbacksController and related models
echo "Searching for count queries in FeedbacksController..."
rg "count" app/controllers/feedbacks_controller.rb

echo "Searching for count queries in models..."
rg "count" app/models/

Length of output: 17440


Script:

#!/bin/bash
# Description: Trace the usage of count queries and verify if they are cached

# Find where count queries in FeedbacksController are used
echo "Tracing count queries usage in FeedbacksController..."
rg "count" app/controllers/feedbacks_controller.rb --context 5

# Find where count queries in models are used and if they are cached
echo "Tracing count queries usage in models with caching..."
rg "count" app/models/ | while read -r file; do
  echo "In file: $file"
  rg "count" "$file" --context 3 | rg -B 2 "Rails.cache"
done

# Search for performance benchmarks or logs related to count queries
echo "Searching for performance benchmarks or logs related to count queries..."
rg -i "benchmark|log|performance" .

Length of output: 120339


Script:

#!/bin/bash
# Description: Identify count queries and verify if they are optimized with caching

# List all files in app/controllers/ and app/models/ that contain the word "count"
echo "Listing files with 'count' queries in app/controllers/ and app/models/..."
files=$(rg -l "count" app/controllers/ app/models/)

# Iterate over each file and search for 'count' queries and associated Rails.cache usage
for file in $files; do
  echo "Analyzing file: $file"
  
  # Search for 'count' in the file
  count_lines=$(rg "count" "$file")
  
  # Check if 'Rails.cache' is used near 'count' queries
  echo "$count_lines" | while IFS= read -r line; do
    # Get the line number of the 'count' query
    line_number=$(echo "$line" | grep -oP '^\S+:\K\d+')
    
    # Define a range of lines to search around the 'count' query for 'Rails.cache'
    start_line=$((line_number - 2))
    end_line=$((line_number + 2))
    
    # Search for 'Rails.cache' within the specified range
    if rg -N $start_line -N $end_line "Rails.cache" "$file" > /dev/null; then
      echo "  [OPTIMIZED] 'count' query at line $line_number is optimized with Rails.cache."
    else
      echo "  [NOT OPTIMIZED] 'count' query at line $line_number is NOT optimized with Rails.cache."
    fi
  done
done

# Search for performance benchmarks or logs related to 'count' queries
echo "Searching for performance benchmarks or logs related to 'count' queries..."
rg -i "benchmark|log|performance" .

Length of output: 221383

app/views/users/index.js.erb (2)

2-2: Verify performance impact of filter updates

Based on the PR objectives, there were concerns about performance impact from count queries. While the users page wasn't specifically mentioned as problematic (unlike submissions), we should verify the performance implications.

Consider implementing the following optimizations if performance becomes an issue:

  1. Implement caching for filter counts
  2. Consider lazy loading of counts
  3. Add appropriate database indexes for commonly filtered columns

Would you like assistance in implementing any of these optimizations?


2-2: Verify the security of using raw with @filters

Using raw with to_json could potentially expose the application to XSS attacks if @filters contains user-provided input. Please ensure that the data is properly sanitized before reaching this point.

app/views/saved_annotations/new.js.erb (1)

1-2: Verify JavaScript integration and error handling.

The code assumes the existence of dodona.search.updateFilters method. Let's verify its implementation and error handling.

app/views/activities/available.js.erb (1)

3-3: Verify the structure of @filters object

Let's verify the structure and content of the @filters object to ensure it matches the expected format.

app/assets/stylesheets/components/dropdown-filter.css.scss (1)

6-6: Verify layout impact of min-width setting

The min-width: max-content ensures filter labels don't wrap, which improves readability. However, this could potentially cause layout issues with very long filter options.

Let's check for potential layout issues with long filter labels:

✅ Verification successful

Verified no layout issues found with the min-width: max-content setting

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for long text content in filter options that might cause layout issues

# Look for long text strings in translation files that might be used as filter labels
rg -g '*.yml' -A 1 'filter.*:' config/locales/

# Look for filter definitions in models and controllers to understand what content might appear
rg -g '*.rb' '(filterable_by|has_filter)' app/

Length of output: 6669

app/controllers/events_controller.rb (3)

2-2: Add test coverage for HasFilter integration

The HasFilter module integration is a critical change that affects the controller's filtering behavior. However, this change lacks test coverage.

Let's verify existing test coverage:

#!/bin/bash
# Check for existing controller tests
rg -l "describe EventsController" "spec/"

# Check for filter-related tests in other controllers
rg "has_filter" "spec/controllers/"

Would you like me to help generate test cases for the HasFilter integration?

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 2-2: app/controllers/events_controller.rb#L2
Added line #L2 was not covered by tests


8-8: Review performance impact of filter counts

Based on the PR objectives, filter counts have shown performance impact on some pages. Consider the following:

  1. The filters method will generate count queries for each filter option
  2. These counts are executed on the policy-scoped collection, which is correct for permission handling

Consider these optimizations:

  • Cache filter counts if they don't need real-time accuracy
  • Add database indexes for commonly filtered columns
  • Monitor query performance in production for this endpoint

Let's check for similar implementations and existing optimizations:

#!/bin/bash
# Look for other controllers using filters method
rg "filters\(.*policy_scope" "app/controllers/"

# Check for existing caching implementations
rg "Rails.cache.*filters" "app/controllers/"
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 8-8: app/controllers/events_controller.rb#L8
Added line #L8 was not covered by tests


4-4: Verify event_type filter implementation and performance

The transition from has_scope to has_filter for event_type needs verification:

  1. Ensure the Event model supports event_type filtering
  2. Consider performance implications of dynamic counts mentioned in PR objectives

Let's verify the implementation:

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 4-4: app/controllers/events_controller.rb#L4
Added line #L4 was not covered by tests

app/views/evaluations/edit.js.erb (2)

4-4: Verify proper JSON sanitization

The use of raw with @filters.to_json could potentially lead to XSS vulnerabilities if the filter data contains malicious content. Ensure that all user-provided data within @filters is properly sanitized before JSON conversion.


4-4: Verify performance impact of filter updates

Based on the PR objectives, some pages experienced significant slowdowns due to the dynamic count queries. We should verify if the evaluations page is affected.

app/views/courses/scoresheet.js.erb (1)

2-2: Verify the performance impact of filter updates.

Given the PR's context about performance concerns with count queries, we should verify the size and complexity of the filters data being transmitted.

app/views/evaluations/show.js.erb (1)

Line range hint 1-5: LGTM!

The existing HTML update logic is well-structured and includes proper conditional rendering.

app/views/feedbacks/edit.html.erb (2)

Line range hint 9-13: Confirm performance optimization for submissions table.

Since this view includes a submissions table and the PR mentioned performance issues with submission counts, we should verify that the necessary optimizations are in place.


9-9: Verify that the searchbar component can still access submission statuses.

The removal of the statuses local variable suggests that the searchbar component now handles submission statuses internally. Let's verify this implementation.

app/views/saved_annotations/new.html.erb (1)

8-8: Verify searchbar functionality after variable removal

The removal of courses and exercises variables from the searchbar partial suggests these are now handled internally. Let's verify this change doesn't break existing functionality.

✅ Verification successful

Searchbar functionality verified after variable removal

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the searchbar partial has been updated to handle filters internally

# Check for filter-related changes in the searchbar partial
echo "Checking searchbar partial implementation:"
rg -A 10 "def.*searchbar" app/views/layouts/

Length of output: 141


Script:

#!/bin/bash
# Description: Verify if 'courses' and 'exercises' variables are used within the searchbar partial

echo "Checking for usage of 'courses' and 'exercises' in the searchbar partial:"
rg "courses|exercises" app/views/layouts/_searchbar.html.erb

Length of output: 231


Script:

#!/bin/bash
# Description: Search for 'courses' and 'exercises' in controller files

echo "Checking for definitions and usages of 'courses' and 'exercises' in controller files:"
rg "courses|exercises" app/controllers/

Length of output: 13445

app/views/activities/index.html.erb (1)

9-9: Verify the simplified searchbar implementation.

The searchbar partial now receives fewer parameters, relying on @labels instead of individual variables. Let's verify this refactoring doesn't miss any required functionality.

✅ Verification successful

Searchbar refactoring verified successfully.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining references to old parameter names in the searchbar partial
# and verify the structure of @labels in the controller

# Check searchbar partial for any references to old parameters
rg -A 5 "programming_languages|repositories|activity_types|description_languages|judges|popularity|draft" app/views/layouts/_searchbar*

# Check controller for @labels structure
ast-grep --pattern 'def index
  $$$
  @labels = $_
  $$$
end' app/controllers/activities_controller.rb

Length of output: 3923

app/models/event.rb (1)

21-21: Verify query performance impact of filterable_by.

The configuration looks correct, but given the PR's concerns about COUNT queries performance:

  1. Ensure that filtering by enum doesn't introduce N+1 queries
  2. Verify if the counts are cached appropriately
#!/bin/bash
# Description: Look for performance optimizations in the Filterable implementation
# Expected: Find evidence of query optimization or caching

# Search for caching mechanisms
rg -t ruby "cache|index|counter" --glob "app/models/concerns/filterable.rb"

# Look for eager loading or join optimizations
ast-grep --pattern 'joins($_) { $$ }'

# Check if there are other models using filterable_by with enums
rg -t ruby "filterable_by.*is_enum: true"

Consider implementing counter caching if the Event counts are frequently accessed in filters. This could help mitigate the performance impact of COUNT queries.

app/views/users/index.html.erb (1)

13-13: LGTM! Verify query performance impact.

The removal of institutions: Institution.all aligns with the PR's goal of dynamic filter counts and simplified filter definitions. However, let's verify that this change doesn't introduce N+1 queries.

Let's check how institutions are loaded in the searchbar partial:

app/assets/javascripts/components/search/filter_collection.ts (2)

1-4: LGTM: Imports are well-organized and properly scoped

The imports are clean and each imported entity is utilized within the code.


17-19: LGTM: Clean and efficient implementation of visible filters

The getter provides a clear and efficient way to access filtered results. The implementation correctly uses array methods and maintains good readability.

app/views/saved_annotations/index.html.erb (1)

14-14: Verify the removal of courses and exercises variables

The removal of courses and exercises local variables from the searchbar partial aligns with the PR's objective to refactor search filters. However, we should verify that these variables are now handled by the new dynamic filtering system.

✅ Verification successful

Verified removal of courses and exercises variables

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that courses and exercises are handled by the new filtering system
# and that this change is consistent across other views

# Check if other views still pass these variables to the searchbar
echo "Checking other views using the searchbar partial:"
rg -l "render.*layouts/searchbar.*courses.*exercises" "app/views"

# Check if the new filtering system handles these in the controller
echo "\nChecking for filter definitions in the controller:"
rg "has_filter.*:course|has_filter.*:exercise" "app/controllers/saved_annotations_controller.rb"

# Verify the model has the filterable declarations
echo "\nChecking for filterable declarations in the model:"
rg "filterable_by.*:course|filterable_by.*:exercise" "app/models/saved_annotation.rb"

Length of output: 773

app/views/repositories/admins.html.erb (2)

20-20: LGTM! Removal of institutions aligns with dynamic filtering approach.

The removal of the institutions local variable from the searchbar partial is a positive change that aligns with the PR's goal of implementing dynamic filters. This should improve initial page load time by avoiding eager loading of all institutions.


20-20: Verify compatibility with initAdminsEdit()

Please ensure that the dodona.initAdminsEdit() function is compatible with the new dynamic filtering system, particularly since the searchbar is initialized with eager: true.

Also applies to: 28-32

app/models/saved_annotation.rb (2)

28-29: Review performance implications of new filter declarations

The new filterable_by declarations with always_match_nil: true replace the previous scopes while maintaining similar functionality. However, there are a few points to consider:

  1. Including nil values in filters could impact performance on the submissions page, which was mentioned as a concern in the PR objectives.
  2. The relationship with Exercise and Course models needs verification.

Let's verify the implementation and its impact:

#!/bin/bash
# Description: Verify model relationships and potential performance impacts

# Check for existing indexes on exercise_id and course_id
rg "add_index.*saved_annotations.*(exercise_id|course_id)" "db/migrate/"

# Verify Exercise and Course model relationships
rg "has_many.*saved_annotations" "app/models/{exercise,course}.rb"

# Look for existing usage patterns that might be affected
rg "by_course|by_exercise" "app/controllers/" "app/views/" "spec/"

Consider adding database indexes if not present:

add_index :saved_annotations, :exercise_id
add_index :saved_annotations, :course_id

16-16: Verify test coverage for Filterable module integration

The inclusion of the Filterable module aligns well with the PR objectives to enhance filtering capabilities.

Let's verify the test coverage:

app/views/courses/index.html.erb (2)

28-28: Review the usage of eager parameter

The eager: true parameter is still present while other eager loading was removed. This might need adjustment to align with the new dynamic filtering system.


28-28: Verify institutions data loading mechanism

The removal of institutions: Institution.all suggests that institutions are now loaded dynamically. Let's verify the new data loading mechanism to ensure it's properly implemented.

app/policies/annotation_policy.rb (1)

8-8: LGTM! Association name fixed.

The change from submission to submissions in the query conditions correctly aligns with Rails' convention for has_many/belongs_to associations.

Let's verify the association name in the Annotation model:

app/views/activity_read_states/index.html.erb (1)

37-37: Ensure alignment with the new dynamic counts feature.

Since this PR introduces dynamic counts for search filters, verify that the searchbar partial is properly integrated with the new filtering system.

#!/bin/bash
# Description: Verify integration with the new filtering system
# Look for filter-related JavaScript and partial implementations

# Check for filter update handling in JavaScript
echo "Checking for filter update handling..."
rg 'updateFilters|initializeFilters' 'app/javascript'

# Look for searchbar partial implementation
echo "Checking searchbar partial implementation..."
fd searchbar.html.erb -t f --exec cat {}
app/assets/javascripts/components/search/filter_tabs.ts (1)

3-3: LGTM: Import changes align with new architecture

The switch to importing FilterElement aligns with the PR's objective to simplify the filtering system.

app/models/course_membership.rb (1)

34-35: Verify query performance and access control

The filter declarations look functionally correct, but there are some considerations:

  1. Performance: The nested associations (especially for institution_id) could impact query performance when combined with count queries mentioned in the PR objectives.
  2. Security: Ensure proper access control is in place when filtering by institution_id to prevent unauthorized access across institutions.

Let's verify the query performance and access control:

app/models/question.rb (1)

31-32: Consider adding an index for question_state to optimize filtering.

The implementation correctly defines the question states and enables filtering. However, given the concerns about performance mentioned in the PR objectives, especially regarding COUNT queries, consider adding a database index for the question_state column to optimize filtering operations.

Let's verify the current schema and existing indexes:

#!/bin/bash
# Description: Check schema and indexes for question_state column

# Check for existing indexes on question_state
rg -A 10 "create_table.*annotations" db/schema.rb
rg "add_index.*annotations.*question_state" db/migrate/

# Check if question_state is used in complex queries that might benefit from an index
rg -A 5 "\.where.*question_state" app/
test/javascript/components/search/search_token.test.ts (2)

7-7: LGTM! Import path update aligns with the codebase restructuring.

The change from filter_collection_element to filter_element matches the PR's objective of simplifying the filter-related components.


7-7: Verify consistent import path updates across the codebase.

Let's ensure all imports of the Label class have been updated consistently.

✅ Verification successful

Consistent import path updates verified across the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining imports from the old path and verify new imports
# are used consistently across the codebase

echo "Checking for any remaining old imports..."
rg "from ['\"]components/search/filter_collection_element['\"]"

echo "Verifying new imports..."
rg "from ['\"]components/search/filter_element['\"]"

Length of output: 1620

app/models/activity_read_state.rb (2)

14-15: Verify integration with existing filtering scopes

The inclusion of FilterableByCourseLabels and Filterable modules enhances filtering capabilities. However, we should verify that these don't conflict with existing scopes, particularly by_filter and other filtering methods.

✅ Verification successful

Module inclusions do not conflict with existing filtering scopes

The inclusion of FilterableByCourseLabels and Filterable modules does not introduce any conflicts with existing scopes. Filtering behavior remains consistent and unaffected.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for potential method conflicts and usage patterns

# Check for any method conflicts between modules
ast-grep --pattern 'module FilterableByCourseLabels
  $$$
  def $method($$$) {
    $$$
  }
  $$$
}'

# Look for existing usages of filtering methods
rg -A 3 'by_filter|by_course_labels' --type ruby

Length of output: 428


Script:

#!/bin/bash
# Description: Check for method definitions within FilterableByCourseLabels and Filterable modules to identify potential conflicts

# Search for method definitions in FilterableByCourseLabels
ast-grep --pattern 'module FilterableByCourseLabels {
  $$$
  def $_($_)
    $$$
  end
}' 

# Search for method definitions in Filterable
ast-grep --pattern 'module Filterable {
  $$$
  def $_($_)
    $$$
  end
}'

# Look for existing usages of filtering methods to assess integration
rg 'by_filter|by_course_labels' --type ruby

Length of output: 5328


45-45: Consider performance implications of dynamic counts

The filterable_by_course_labels through_user: true declaration adds dynamic counting functionality. Given the PR's concerns about COUNT query performance:

  1. Consider adding database indexes to optimize the course labels filtering
  2. Monitor query execution time, especially when filtering across multiple dimensions
  3. Consider implementing result caching for frequently accessed counts
app/views/submissions/index.html.erb (2)

60-60: LGTM! Performance optimization implemented as discussed.

The removal of course_labels and statuses from the searchbar partial aligns with the decision to exclude count queries on the submissions page, addressing the performance concerns raised in the PR discussion where these queries were causing significant slowdowns.


60-60: Verify that course and status filters remain functional.

While the course_labels and statuses parameters have been removed for performance reasons, we should ensure these filters still work through the new dynamic filtering system.

app/assets/javascripts/components/search/filter_element.ts (4)

5-5: LGTM: Label type enhancement supports dynamic counts

The addition of the optional count property aligns well with the PR objective of displaying dynamic counts in filters.


6-7: Optimize color type definition

The current implementation can be simplified by leveraging TypeScript's type inference.

Apply this diff to prevent repeating the color values:

-export const ACCENT_COLORS = ["pink", "deep-purple", "teal", "orange", "brown", "blue-gray", "red", "purple", "indigo"];
-export type AccentColor = typeof ACCENT_COLORS[number];
+export const ACCENT_COLORS = ["pink", "deep-purple", "teal", "orange", "brown", "blue-gray", "red", "purple", "indigo"] as const;
+export type AccentColor = typeof ACCENT_COLORS[number];

Line range hint 26-94: LGTM: Simplified filter implementation

The refactoring improves code maintainability by:

  • Removing the unnecessary paramVal indirection
  • Using direct label.id access
  • Maintaining clear separation of single/multi select logic
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 55-55: app/assets/javascripts/components/search/filter_element.ts#L55
Added line #L55 was not covered by tests


55-55: Add test coverage for multiUnSelect method

The multiUnSelect method lacks test coverage, which is crucial for maintaining filter functionality.

Let's verify the current test coverage:

Would you like me to help generate test cases for this method? The tests should cover:

  • Removing a selected label
  • Handling non-existent labels
  • Preserving other selected labels
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 55-55: app/assets/javascripts/components/search/filter_element.ts#L55
Added line #L55 was not covered by tests

app/controllers/annotations_controller.rb (2)

41-41: Verify necessity of preloaded associations.

While eager loading is good for performance, let's verify if all these associations are necessary for the current view.

✅ Verification successful

All preloaded associations are utilized appropriately in the view templates.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Look for view templates that use these associations
rg -A 5 "question\.(user|last_updated_by|submission)" app/views/

# Check if these associations are used in any partials
fd -e erb . app/views/questions/ -x rg -l "question\.(user|last_updated_by|submission)"

Length of output: 3417


Script:

#!/bin/bash
# Search for usage of the associations in all view templates
rg -A 5 "question\.(user|last_updated_by|submission)" app/views/

# Find all partials that include questions and check for association usage
rg -l "render.*question" app/views/ | xargs -I{} rg "question\.(user|last_updated_by|submission)" {}

Length of output: 3326


36-38: 🛠️ Refactor suggestion

Review filter ordering and performance impact.

There are a few concerns with the current implementation:

  1. The @filters assignment occurs before applying all filters, which might lead to inaccurate counts.
  2. The question_state filter is applied separately after other scopes, which could impact query performance.

Let's verify the impact on query performance:

Consider refactoring to:

-    @filters = filters(@questions)
-    @questions = @questions.merge(apply_scopes(Question).all)
-    @questions = @questions.where(question_state: params[:question_state]) if params[:question_state]
+    # Apply all filters first
+    @questions = @questions.merge(apply_scopes(Question).all)
+    @questions = @questions.where(question_state: params[:question_state]) if params[:question_state]
+    # Calculate filters after all conditions are applied
+    @filters = filters(@questions)
app/controllers/users_controller.rb (1)

126-127: Consider performance optimizations for the query chain

The query includes multiple order clauses and pagination which could impact performance, especially with the new filter counts. Consider:

  1. Verifying that appropriate indexes exist for the ordering columns (permission, last_name, first_name)
  2. Evaluating if all order clauses are necessary
  3. Monitoring the query performance with the new filter counts
✅ Verification successful

Query performance is optimized with appropriate indexing

The users table includes necessary indexes on email, username, and token. No similar complex order chains found in other controllers.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for existing indexes on User model and look for similar patterns
# in other controllers that might benefit from optimization

echo "Checking User model for index definitions:"
rg -A 5 "add_index.*users" "db/migrate/"

echo -e "\nChecking for similar complex order chains in other controllers:"
ast-grep --pattern 'order\(.*:.*:.*\)\.paginate'

Length of output: 1117


Script:

#!/bin/bash
# Description: Check for existing indexes on User model and look for similar complex order chains in other controllers.

echo "Checking User model for index definitions:"
rg -A 5 "add_index.*users" "db/migrate/"

echo -e "\nChecking for similar complex order chains in other controllers:"
ast-grep --pattern 'order\(.*:.*:.*\)\.paginate'

Length of output: 1261

app/controllers/feedbacks_controller.rb (2)

47-48: LGTM! Query chain looks correct.

The scoping of submissions to the specific series and user is properly implemented.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 47-47: app/controllers/feedbacks_controller.rb#L47
Added line #L47 was not covered by tests


49-50: 🛠️ Refactor suggestion

Add test coverage and consider performance implications.

  1. These lines lack test coverage according to static analysis.
  2. Given the PR objectives mentioning performance concerns with count queries, we should ensure this implementation is optimized.
#!/bin/bash
# Description: Check for existing filter tests and related performance specs

# Look for existing filter tests
rg -l "describe.*filters" spec/controllers/

# Check for performance-related specs
rg -l "performance|benchmark" spec/

# Look for similar filter implementations in other controllers
ast-grep --pattern 'def edit
  $$$
  @filters = filters($_)
  $$$
end'

Consider adding the following:

  1. Controller specs to cover the filter implementation
  2. Performance specs to ensure acceptable query times
  3. Documentation for the filters method usage

Would you like me to help generate the test cases?

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 49-50: app/controllers/feedbacks_controller.rb#L49-L50
Added lines #L49 - L50 were not covered by tests

app/assets/javascripts/components/search/dropdown_filter.ts (3)

3-7: LGTM! Clean inheritance refactor

The change from FilterCollectionElement to FilterElement aligns well with the codebase simplification goals mentioned in the PR objectives.

Also applies to: 23-25


83-98: Add test coverage for DropdownFilters rendering

The static analysis indicates that the template rendering logic lacks test coverage. Please add tests to verify:

  1. Rendering with empty filters
  2. Rendering with visible filters
  3. Rendering with hidden filters
#!/bin/bash
# Check existing test coverage for DropdownFilters
rg -A 5 "describe.*DropdownFilters" --type ts
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 90-90: app/assets/javascripts/components/search/dropdown_filter.ts#L90
Added line #L90 was not covered by tests


38-40: Verify disabled state edge cases

The disabled state logic looks good for basic cases, but let's verify it handles all scenarios correctly:

  1. When labels are empty (length === 0)
  2. When there's only one option in single-select mode (!multi && length === 1)
app/views/courses/new.html.erb (1)

72-72: Improved performance by removing eager loading of all institutions.

The removal of institutions: Institution.all from the searchbar partial is a good optimization, preventing unnecessary loading of all institution records.

app/controllers/activity_read_states_controller.rb (2)

78-78: LGTM! Code simplification improves readability.

The simplified course assignment removes unnecessary conditional complexity while maintaining the same functionality.


96-102: Verify query performance impact of filter options.

Based on PR discussions, the COUNT queries for filter options can impact performance. The activities page showed increases of up to 300ms in response time.

Let's analyze the query complexity:

app/views/series/edit.html.erb (1)

90-90: LGTM! Verify dynamic filter updates.

The simplified searchbar partial call aligns with the PR's objective of implementing dynamic filter counts. The removal of static filter parameters (labels, programming_languages, etc.) suggests these will now be populated dynamically.

Let's verify the dynamic filter updates are working as expected:

test/javascript/components/search/dropdown_filter.test.ts (2)

17-17: LGTM! Property assignment simplified.

The change from a function-based color property to a direct string value simplifies the code while maintaining the same functionality.


17-17: Verify the TypeScript interface for color property.

Let's ensure this change is consistent with the component's TypeScript interface definition.

app/models/annotation.rb (2)

60-61: Verify index coverage for filter queries.

The new filter declarations look good, but since they involve joins and foreign key relationships, ensure optimal performance by verifying index coverage:

  1. course_id filter should use the index on course_id
  2. exercise_id filter joins through submissions table and uses submissions.exercise_id
#!/bin/bash
# Check for proper index definitions
echo "Checking schema for required indexes..."
rg -A 5 "create_table.*annotations" "db/schema.rb"
rg -A 5 "add_index.*annotations" "db/schema.rb"

echo "Checking for existing migrations that add indexes..."
fd -e rb . "db/migrate" --exec rg -l "add_index.*(course_id|exercise_id)"

Consider adding the following to optimize query performance:

  1. Ensure course_id has an index if not already present
  2. Consider adding a composite index if frequently filtering by both course and exercise

25-25: Verify existing filter functionality and add documentation.

The inclusion of Filterable is part of the filtering system refactor. Since this model has complex relationships and multiple scopes, ensure that:

  1. All existing filtering scenarios are preserved
  2. The performance impact is acceptable, especially for queries involving multiple associations

Consider adding documentation about the new filtering capabilities and any performance considerations, especially for queries involving multiple associations.

app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts (2)

8-9: LGTM: Imports are appropriate for the new filtering functionality.

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 9-9: app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts#L9
Added line #L9 was not covered by tests


48-57: 🛠️ Refactor suggestion

Add test coverage for the new filter management code.

The new filter management code follows the established pattern but lacks test coverage. Consider adding tests for:

  • Initial state of lastFilters
  • Behavior when filters are undefined
  • Filter update scenarios

Consider adding type assertions to make the code more robust:

 get filters(): FilterOptions[] {
-    const filters = savedAnnotationState.getFilters(this.queryParams, this.arrayQueryParams);
+    const filters: FilterOptions[] | undefined = savedAnnotationState.getFilters(
+        this.queryParams,
+        this.arrayQueryParams
+    );
     if (filters === undefined) {
         return this.lastFilters;
     }
     this.lastFilters = filters;
     return filters;
 }
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 48-50: app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts#L48-L50
Added lines #L48 - L50 were not covered by tests


[warning] 53-53: app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts#L53
Added line #L53 was not covered by tests


[warning] 55-56: app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts#L55-L56
Added lines #L55 - L56 were not covered by tests

app/controllers/saved_annotations_controller.rb (3)

3-5: LGTM: Module inclusion and filter headers setup looks good!

The inclusion of HasFilter and configuration of filter headers aligns well with the PR's objective of adding dynamic counts to search filters.


8-11: Consider converting remaining scopes to filters for consistency.

While the conversion of course and exercise filtering to has_filter looks good, consider converting by_user and by_filter scopes as well for consistency across the codebase.


19-21: Verify performance impact of filter counts.

The implementation looks correct, but given the performance concerns raised in the PR discussion about count queries:

  1. Consider adding database indexes if not present
  2. Monitor query execution time in production
  3. Consider implementing caching for filter counts if needed
app/controllers/course_members_controller.rb (1)

2-2: LGTM! Verify filter module integration.

The migration from scopes to filters aligns well with the PR objectives. The multi-selection support for course labels is a good enhancement.

Let's verify the HasFilter module integration and its impact:

Also applies to: 8-9

✅ Verification successful

Filter module integration verified successfully.
All relevant changes are properly implemented in app/controllers/course_members_controller.rb.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify HasFilter module integration and usage patterns

# Check if other controllers are consistently using HasFilter
echo "Checking HasFilter usage patterns across controllers:"
rg -l "include HasFilter" "app/controllers"

# Verify if any old scope-based filtering remains
echo "Checking for remaining scope-based filtering:"
rg "by_institution|by_course_labels" "app/controllers"

Length of output: 2242

app/assets/javascripts/search.ts (1)

5-6: LGTM: New imports support filter enhancements

The added imports align well with the PR's objective of enhancing search filter functionality.

app/assets/javascripts/state/SavedAnnotations.ts (3)

8-8: LGTM: Clean integration of filter management

The new import and state property follow the established patterns in the codebase, maintaining type safety and consistency with existing state management approach.

Also applies to: 37-37


48-48: Add test coverage for filter header parsing

The new filter parsing from response headers lacks test coverage. Since this is crucial for the dynamic filter counts feature, we should ensure it handles various scenarios correctly (valid JSON, missing headers, malformed data).

Would you like help creating test cases for:

  • Successful filter parsing
  • Missing X-Filters header
  • Malformed JSON in X-Filters header
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 48-48: app/assets/javascripts/state/SavedAnnotations.ts#L48
Added line #L48 was not covered by tests


96-104: ⚠️ Potential issue

Fix incorrect condition in getFilters method

There's a critical bug in the implementation. The method checks paginationByURL instead of filtersByURL in the condition, which could lead to unnecessary API calls.

Apply this fix:

    getFilters(params?: Map<string, string>, arrayParams?: Map<string, string[]>): FilterOptions[] {
        const url = addParametersToUrl(`${this.url}.json`, params, arrayParams);
        delayerByURL.get(url)(() => {
-           if (!this.paginationByURL.has(url)) {
+           if (!this.filtersByURL.has(url)) {
                this.fetchList(url);
            }
        }, 200);
        return this.filtersByURL.get(url);
    }

Additionally, this method needs test coverage to ensure proper functionality.

Run the following to check for similar patterns that might have the same issue:

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 96-98: app/assets/javascripts/state/SavedAnnotations.ts#L96-L98
Added lines #L96 - L98 were not covered by tests


[warning] 100-100: app/assets/javascripts/state/SavedAnnotations.ts#L100
Added line #L100 was not covered by tests


[warning] 103-103: app/assets/javascripts/state/SavedAnnotations.ts#L103
Added line #L103 was not covered by tests

test/javascript/components/search/search_field.test.ts (2)

6-6: LGTM: Import change aligns with refactor objectives

The change from FilterCollection to FilterOptions reflects the codebase simplification mentioned in the PR objectives.


92-92: LGTM: Template binding correctly updated

The binding syntax for the new filter structure is correct and consistent with Lit's property binding syntax.

app/controllers/evaluations_controller.rb (1)

26-34: LGTM! Filter initialization and structure look good.

The filter initialization and course labels filter addition are well-structured and maintain proper separation of concerns.

config/locales/js/en.yml (2)

200-203: LGTM! Well-structured i18n implementation.

The new tokens section follows i18n best practices with proper pluralization support using smart_count.


206-209: LGTM! Consistent naming convention for filter keys.

The renaming of filter keys follows a clear pattern:

  • Singular form for all keys
  • Added _id suffix for foreign key references (e.g., judge_id, repository_id)

Also applies to: 214-217

app/controllers/series_controller.rb (2)

195-199: Verify performance impact of course_labels_filter_options

The filter configuration looks correct, but given the performance concerns mentioned in the PR discussion:

  1. The course_labels_filter_options call might introduce additional queries
  2. This could impact the scoresheet page load time, especially with large datasets

Consider these optimizations if not already implemented:

  1. Eager loading of course labels
  2. Caching the filter options if they don't change frequently
  3. Limiting the number of options if the dataset is large

4-4: Verify HasFilter module integration with existing scopes

The HasFilter module inclusion aligns with the filtering system refactor. However, let's verify the interaction between HasFilter and existing has_scope declarations.

✅ Verification successful

HasFilter module integration confirmed with existing scopes
The HasFilter module is properly integrated with the has_scope declarations in SeriesController, aligning with the filtering system refactor.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for potential conflicts between HasFilter and has_scope
# Look for other controllers using both HasFilter and has_scope

# Search for controllers using both HasFilter and has_scope
echo "Controllers using both HasFilter and has_scope:"
rg "include HasFilter" -l | xargs rg "has_scope" -l

# Check HasFilter module implementation
echo "\nHasFilter module implementation:"
fd -e rb -E node_modules | xargs rg "module HasFilter" -A 10

Length of output: 339


Script:

#!/bin/bash
# Description: Retrieve the implementation of the HasFilter module

echo "Searching for the HasFilter module implementation..."
fd -e rb -E node_modules | xargs rg "module HasFilter" --context 10

echo "\nChecking for has_scope usage within SeriesController..."
rg "has_scope" app/controllers/series_controller.rb --context 5

echo "\nSearching for any controllers that include both HasFilter and has_scope..."
rg "include HasFilter" -l | xargs -I {} sh -c 'echo "Controller: {}"; rg "has_scope" {} || echo "No has_scope found in {}"'

Length of output: 4884

config/locales/js/nl.yml (2)

200-203: LGTM: New translations for dynamic filter counts

The translations for active filters count and clear action are well-structured and correctly handle plural forms using the smart_count variable.


200-220: Overall translation changes look good

The translation changes successfully support the new dynamic filter counts feature while maintaining good localization practices. The structure is clean and the translations are clear, with only minor consistency improvements suggested above.

app/models/course.rb (2)

29-29: Verify Filterable module implementation

The inclusion of the Filterable module aligns with the PR's objective to simplify filtering. However, we should verify its implementation.

✅ Verification successful

Further verify Filterable module existence


Filterable module correctly implemented

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the Filterable module implementation
# Expected: Find the module definition and its core filtering methods

# Look for the Filterable module definition
echo "Searching for Filterable module definition..."
rg -p "module(\s+)Filterable" --type ruby

# Look for core filtering methods
echo "Searching for core filtering methods..."
ast-grep --pattern 'module Filterable
  $$$
  def self.included($_) {
    $$$
  }
  $$$
}'

Length of output: 666


Script:

#!/bin/bash
# Description: Enhanced search for the Filterable module implementation

# Search for the Filterable module definition with a more flexible pattern
echo "Searching for module Filterable definition..."
rg 'module\s+Filterable' --type ruby

# Search for any occurrences of Filterable to identify its usage and potential definitions
echo "Searching for any references to Filterable..."
rg 'Filterable' --type ruby

Length of output: 1259


164-167: Verify database indexes for search fields

The search and filter functionality relies heavily on database queries. Let's verify the existence of appropriate indexes.

app/models/submission.rb (1)

23-23: LGTM: Filterable module inclusion aligns with standardization goals

The inclusion of the Filterable module aligns with the PR's objective of standardizing filter implementations across models.

app/controllers/courses_controller.rb (1)

233-237: Verify course_labels_filter_options implementation

The implementation looks clean, but let's verify the existence and implementation of the course_labels_filter_options method to ensure it provides the correct data structure for the filter.

✅ Verification successful

course_labels_filter_options implemented correctly

The course_labels_filter_options method is properly defined in a shared concern and utilized across relevant controllers, ensuring the filter functions as intended.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify course_labels_filter_options implementation
# Test: Look for the method definition and its usage

# Check for method definition
echo "Checking for course_labels_filter_options method definition..."
ast-grep --pattern 'def course_labels_filter_options'

# Check for other usages of the method
echo "Checking for other usages of course_labels_filter_options..."
rg "course_labels_filter_options"

Length of output: 1605


Script:

#!/bin/bash
# Description: Re-verify course_labels_filter_options implementation
# Test: Look for the method definition and its usages comprehensively

# Search for the exact method definition
echo "Searching for exact 'course_labels_filter_options' method definition..."
ast-grep --pattern 'def course_labels_filter_options'

# Search for any method definitions containing 'course_labels_filter'
echo "Searching for method definitions containing 'course_labels_filter'..."
ast-grep --pattern 'def course_labels_filter_'

# Search for usages of methods that include 'course_labels_filter' in their name
echo "Searching for usages of methods related to 'course_labels_filter'..."
rg 'course_labels_filter'

# Check for any potential typos or similar method names
echo "Searching for similar method names that might be misspelled..."
rg 'course_label_filter_options'

Length of output: 1781

app/models/user.rb (4)

121-121: LGTM: Association follows Rails conventions.

The has_many :course_labels, through: :course_memberships association is properly defined and aligns with the filtering enhancements described in the PR objectives.


139-139: Verify the impact of search changes.

The new search_by implementation with multiple fields replaces the previous set_search method. While this change simplifies the search functionality, we should verify that all existing search features continue to work as expected.

#!/bin/bash
# Check for existing search usage that might be affected
echo "Checking for previous search method usage..."
rg "set_search" 

echo "Checking for current search usage..."
rg "search_by.*:username.*:first_name.*:last_name"

echo "Checking for search-related tests..."
fd ".*_test\.rb" | xargs rg "search"

142-142: Consider performance implications of dynamic counts.

The new filter implementation with filterable_by :institution_id and filterable_by_course_labels aligns with the PR objectives. However, based on the PR comments about performance impact:

  1. The submissions page experienced significant slowdowns (3x response time)
  2. The activities page showed increased query time (up to 300ms)

Consider implementing the following optimizations:

  • Add database indexes for frequently filtered columns
  • Implement caching for filter counts
  • Consider batch loading or lazy loading of counts
#!/bin/bash
# Check for existing indexes on filtered columns
echo "Checking for existing indexes in schema..."
rg "add_index.*institution_id" 
rg "add_index.*course_.*labels"

# Check for other filter implementations that might provide optimization patterns
echo "Checking for other filterable implementations..."
rg "filterable_by"

Also applies to: 145-145


31-32: Verify the module dependencies and their order.

The inclusion of both FilterableByCourseLabels and Filterable modules might lead to method conflicts. Ensure that FilterableByCourseLabels is intended to complement rather than replace Filterable.

✅ Verification successful

No Method Conflicts Detected Between FilterableByCourseLabels and Filterable Modules

The modules FilterableByCourseLabels and Filterable define distinct methods without overlapping names, ensuring they complement each other without causing any method conflicts.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for potential method conflicts between the modules
echo "Checking FilterableByCourseLabels module..."
rg -A 10 "module FilterableByCourseLabels" 

echo "Checking Filterable module..."
rg -A 10 "module Filterable"

Length of output: 3265

app/assets/javascripts/i18n/translations.json (1)

196-196: LGTM: Search dropdown translations are consistent.

The English ("Search") and Dutch ("Zoeken") translations for the dropdown search functionality are properly implemented.

Also applies to: 691-691

app/views/layouts/_searchbar.html.erb (1)

4-8: Variables are correctly initialized and combined

The variables no_filter_for and no_dropdown_for are appropriately fetched from local_assigns and combined as intended.

app/assets/javascripts/components/search/search_token.ts (2)

70-77: Verify that clearAll() correctly updates the query parameters and triggers updates.

Ensure that directly setting searchQueryState.arrayQueryParams and searchQueryState.queryParams correctly updates the application state and UI. If there are specific methods or observers that need to be notified, consider invoking those to maintain consistency.

Would you like to confirm that the clearAll() method functions as intended across the application?

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 70-71: app/assets/javascripts/components/search/search_token.ts#L70-L71
Added lines #L70 - L71 were not covered by tests


[warning] 73-75: app/assets/javascripts/components/search/search_token.ts#L73-L75
Added lines #L73 - L75 were not covered by tests


18-24: 🛠️ Refactor suggestion

Update the JSDoc comments to reflect the current class inheritance and properties.

The JSDoc comments still mention that SearchToken inherits from FilterCollectionElement, but it now extends FilterElement. Additionally, properties like paramVal may no longer be applicable due to the recent changes. Updating the documentation will enhance maintainability and aid other developers in understanding the current code structure.

Apply this diff to update the comments:

 /**
- * This component inherits from FilterCollectionElement.
+ * This component inherits from FilterElement.
  * It represents a list of dismissible tokens, one for each active label
  *
  * @element d-search-token
  *
  * @prop {AccentColor} color - the color associated with the filter
  * @prop {string} param - the searchQuery param to be used for this filter
  * @prop {boolean} multi - whether one or more labels can be selected at the same time
- * @prop {(l: Label) => string} paramVal - a function that extracts the value that should be used in a searchQuery for a selected label
  * @prop {[Label]} labels - all labels that could potentially be selected
  */

Likely invalid or redundant comment.

app/models/concerns/filterable.rb (3)

15-17: Ensure set_search handles nil values appropriately

In the set_search method, you're concatenating column values, which may be nil. Ensure that send(column) || '' correctly handles all possible nil scenarios to prevent unexpected results in the search attribute.


85-88: Handle potential issues with nil counts and option names

In the construction of result, ensure that names[key] and option[:name] handle cases where the name might be nil or empty. This prevents potential errors when displaying filter options.


69-69: ⚠️ Potential issue

Use of unscoped may have unintended side effects

Using unscoped removes all default scopes, which might lead to records being included or excluded unexpectedly, especially if default scopes are used for soft deletes or multi-tenancy. Review the necessity of unscoped here and consider alternatives.

Run the following script to identify models using default scopes that could be affected:

✅ Verification successful

Unintended Side Effects of unscoped Not Detected

The use of unscoped in app/models/concerns/filterable.rb does not interfere with default scopes related to data inclusion or exclusion.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for models with default scopes that might be impacted by `unscoped`.

# Find models with default scopes
grep -r "default_scope" app/models/*.rb

Length of output: 1964

app/assets/javascripts/components/search/search_field.ts (8)

6-6: Approved: Updated import statement for FilterElement and Label

The import statement correctly includes FilterElement and Label from "components/search/filter_element", which are necessary for the updated class definitions.


10-10: Approved: Importing FilterCollection

Importing FilterCollection from "components/search/filter_collection" is appropriate, as it is required for the SearchField class that now extends FilterCollection.


54-54: Approved: Dynamic dropdown header using i18n

The dropdown header now utilizes internationalization by incorporating i18n.t with the param property. This enhances the localization support for the dropdown.


59-59: Approved: Conditional display of label counts

Adding the conditional rendering of label.count improves the user interface by showing the count of items associated with each label when available.


74-75: Approved: Addition of filters and hide properties

The new @property {FilterOptions[]} filters and @property {string[]} hide properties allow better control over which filters are displayed or hidden, enhancing the configurability of the SearchField component.


150-150: Approved: Checking for undefined visibleFilters

The conditional check if (!this.visibleFilters) ensures that the component handles cases where visibleFilters might be undefined or null.


166-166: Approved: Rendering suggestions based on visible filters

Mapping over this.visibleFilters to render the suggestion fields ensures that only the intended filters are displayed to the user.


79-79: Verify change of base class to FilterCollection

The SearchField class now extends FilterCollection instead of DodonaElement. This may impact inherited functionality.

Please ensure that all necessary functionalities from DodonaElement are either not needed or have been appropriately migrated. Run the following script to check for any missing methods or properties:

#!/bin/bash
# Description: Verify that extending FilterCollection provides necessary methods previously inherited from DodonaElement

# Extract methods used in SearchField
rg --no-heading -o 'this\.\w+' app/assets/javascripts/components/search/search_field.ts | sort | uniq > methods_used.txt

# Extract methods defined in FilterCollection
rg --no-heading -o '^\s*\w+\(' app/assets/javascripts/components/search/filter_collection.ts | sed 's/^\s*\(\w\+\)(.*/this.\1/' | sort | uniq > methods_in_filtercollection.txt

# Extract methods defined in DodonaElement
rg --no-heading -o '^\s*\w+\(' app/assets/javascripts/components/dodona_element.ts' | sed 's/^\s*\(\w\+\)(.*/this.\1/' | sort | uniq > methods_in_dodonaelement.txt

# Identify methods that were available in DodonaElement but not in FilterCollection
comm -23 methods_in_dodonaelement.txt methods_in_filtercollection.txt > missing_methods.txt

# Check if any of these methods are used in SearchField
grep -F -f missing_methods.txt methods_used.txt > methods_potentially_missing.txt

echo "Methods potentially missing due to the change of base class:"
cat methods_potentially_missing.txt
app/models/activity.rb (3)

36-36: Inclusion of the Filterable module enhances filtering capabilities

Including the Filterable module allows the Activity model to leverage shared filtering logic, improving code maintainability and consistency across models.


77-85: Verify the filterable_by options for correctness

Please ensure that the options passed to the filterable_by methods are accurate:

  • Confirm that the associations option correctly references existing associations in the Activity model.
  • Check that the column names correspond to the correct database columns.
  • For filterable_by :type, verify that the name_hash returns the intended human-readable names.

103-112: Avoid using raw SQL in query construction

Using raw SQL strings can lead to potential SQL injection risks and reduces code maintainability. Consider refactoring the query using ActiveRecord or Arel methods to construct the SQL in a safer and more readable manner.

app/views/events/index.js.erb Outdated Show resolved Hide resolved
app/views/events/index.js.erb Outdated Show resolved Hide resolved
app/views/activities/index.js.erb Outdated Show resolved Hide resolved
app/views/feedbacks/edit.js.erb Outdated Show resolved Hide resolved
app/views/annotations/question_index.js.erb Outdated Show resolved Hide resolved
app/models/concerns/filterable.rb Show resolved Hide resolved
app/models/concerns/filterable.rb Show resolved Hide resolved
app/models/concerns/filterable.rb Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (2)
config/locales/js/nl.yml (1)

204-217: LGTM with minor casing inconsistency.

The filter translations are well-structured and properly renamed to singular form. However, there's an inconsistency in casing that should be fixed.

Fix the casing inconsistency:

-        type: Type
+        type: type
app/assets/javascripts/i18n/translations.json (1)

834-834: Maintain consistent capitalization in translations.

The Dutch translation for event_type uses lowercase "type" while the English version uses uppercase "Type". Consider maintaining consistent capitalization:

-          "event_type": "type",
+          "event_type": "Type",
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a5c64f2 and e7a4ac7.

📒 Files selected for processing (2)
  • app/assets/javascripts/i18n/translations.json (4 hunks)
  • config/locales/js/nl.yml (1 hunks)
🔇 Additional comments (5)
config/locales/js/nl.yml (2)

200-203: LGTM! Well-structured translations for filter tokens.

The new translations for active filters count and clear action are properly implemented with correct plural forms and Dutch grammar.


212-212: Verify potential confusion between similar keys.

The presence of both type and event_type with similar translations could lead to confusion. Consider either:

  1. Using more specific names to differentiate their purposes
  2. Consolidating them if they serve the same purpose

Let's check if these keys are used distinctly in the codebase:

Also applies to: 219-219

app/assets/javascripts/i18n/translations.json (3)

196-196: LGTM!

The addition of the search text in the dropdown section is appropriate and aligns with the PR objectives.


333-354: LGTM! Well-structured search translations.

The search section is well-organized with comprehensive filter types and proper pluralization handling.


333-354: LGTM! Translations are complete and consistent.

All new translation keys are properly implemented in both English and Dutch with consistent structure and proper pluralization handling.

Also applies to: 828-850

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (1)
app/controllers/courses_controller.rb (1)

232-236: Consider adding error handling for filter options computation

The course labels filter implementation looks good. However, consider adding error handling for the course_labels_filter_options call to gracefully handle any potential issues.

Consider adding error handling:

       @filters = [{
         param: 'course_labels',
         multi: true,
-        data: @users.course_labels_filter_options(@course.id)
+        data: @users.course_labels_filter_options(@course.id) rescue []
       }]
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between e7a4ac7 and 0c2bc88.

📒 Files selected for processing (1)
  • app/controllers/courses_controller.rb (4 hunks)

app/controllers/courses_controller.rb Show resolved Hide resolved
app/controllers/courses_controller.rb Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (1)
app/controllers/auth/authentication_controller.rb (1)

Line range hint 8-47: Consider optimizing provider initialization.

The current implementation makes multiple individual find_by queries. Consider using a single query to fetch all providers and then group them in memory:

  def sign_in
+   # Fetch all providers in a single query with required attributes
+   all_providers = Provider.where(
+     entity_id: [
+       'https://identity.ugent.be/simplesaml/saml2/idp/metadata.php',
+       'urn:mace:kuleuven.be:kulassoc:kuleuven.be',
+       'https://idp.uhasselt.be:443/idp/shibboleth'
+     ].compact
+   ).or(
+     Provider.where(
+       identifier: [
+         '792e08fb-2d54-4a8e-af72-202548136ef6',
+         '695b7ca8-2da8-4545-a2da-42d03784e585',
+         # ... other identifiers ...
+       ].compact
+     )
+   ).index_by { |p| [p.entity_id, p.identifier].compact.first }
+
+   # Use the indexed providers
-   @universities = [
-     Provider.find_by(entity_id: 'https://identity.ugent.be/simplesaml/saml2/idp/metadata.php'),
-     # ... other providers ...
-   ].compact
+   @universities = [
+     all_providers['https://identity.ugent.be/simplesaml/saml2/idp/metadata.php'],
+     # ... other providers ...
+   ].compact

This optimization:

  1. Reduces the number of database queries
  2. Improves page load performance
  3. Maintains the same functionality
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0c2bc88 and 2c4b3cf.

📒 Files selected for processing (2)
  • app/controllers/auth/authentication_controller.rb (1 hunks)
  • app/models/provider.rb (1 hunks)
🔇 Additional comments (4)
app/controllers/auth/authentication_controller.rb (2)

Line range hint 49-56: LGTM! Query structure follows best practices.

The OAuth providers query:

  • Uses proper eager loading to avoid N+1 queries
  • Has clear, maintainable filtering conditions
  • Integrates well with the new dynamic filtering system

4-4: LGTM! Scope name change improves consistency.

The rename from by_institution to by_institution_id better reflects the underlying database column while maintaining backward compatibility through the as: parameter.

Let's verify the related model defines this scope correctly:

✅ Verification successful

Scope name change is correctly implemented and consistent

The verification confirms:

  • Provider model correctly defines the by_institution_id scope with proper implementation
  • Both the model and controller are aligned in using the new scope name
  • No traces of the old by_institution scope remain
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the Provider model defines the new scope name
# Expected: Find the scope definition in the Provider model

# Search for the old and new scope definitions
echo "Checking for scope definitions..."
rg "scope\s+:by_institution" --type ruby
rg "scope\s+:by_institution_id" --type ruby

# Search for related specs to ensure they're updated
echo "Checking for related specs..."
rg "by_institution(_id)?" spec/ --type ruby

Length of output: 744

app/models/provider.rb (2)

38-38: LGTM! Improved scope naming.

The rename from by_institution to by_institution_id better reflects the parameter type and matches the database column name, improving code clarity.


38-38: Verify callers of the renamed scope.

The scope rename requires updates to all existing callers. Let's verify the impact across the codebase.

@bmesuere bmesuere added the deploy naos Request a deployment on naos label Nov 13, 2024
@github-actions github-actions bot removed the deploy naos Request a deployment on naos label Nov 13, 2024
@jorg-vr jorg-vr merged commit 214ff5a into main Nov 13, 2024
16 checks passed
@jorg-vr jorg-vr deleted the chore/simplify-search branch November 13, 2024 16:39
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.

3 participants