Skip to content

Move "For me" filter to server-side to fix table pagination#384

Open
frenzymadness wants to merge 1 commit into
mainfrom
issue_326
Open

Move "For me" filter to server-side to fix table pagination#384
frenzymadness wants to merge 1 commit into
mainfrom
issue_326

Conversation

@frenzymadness

@frenzymadness frenzymadness commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Fixes: #326

Summary by CodeRabbit

  • New Features

    • Introduced "For me" filter to display only events with available spots matching your qualifications.
    • Filter selection now persists when toggling other filters, sorting, or navigating pages.
  • Tests

    • Added tests for "For me" filter functionality.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR migrates event eligibility filtering from client-side state to server-side computation. The backend now identifies events with unoccupied spots matching the user's qualifications, applies filtering to the index query, and passes the state to the template. The template propagates for_me across filter navigation. The JavaScript removes client-side eligibility state and consumes server data instead. This fixes pagination count issues from the previous implementation.

Changes

For Me Event Filter

Layer / File(s) Summary
Server-side eligibility computation
app/routes/events/crud.py
Backend imports spot_qualifications, parses for_me query parameter, and adds _eligible_event_ids_for_user() helper to compute events with unoccupied spots matching the user's fillable qualifications. Index query is constrained to eligible event IDs when active, and for_me state is passed to template.
Template parameter propagation
app/templates/events/index.html
All filter URLs (filter_url, status_url, type_url, sort_url) now include for_me to preserve selection when toggling filters. "Pro mě" control changes from button to link. Hidden table-empty message is removed. forMe is added to page config JSON passed to JavaScript.
Client-side filter state removal
app/static/js/events-index.js
Removes toggleEligFilter function, localStorage persistence, and UI event handlers. FOR_ME flag now derives from server page config. Table visibility filtering becomes entirely server-determined. Calendar event eligibility (eligOk) uses FOR_ME with server-provided extendedProps.eligible.
Test coverage for server-side filter
tests/test_events.py
New TestForMeFilter class tests eligible-event-only filtering, full-event display without filter, permission-based filter ignoring, and exclusion of occupied-spot events.

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: moving the 'For me' filter to server-side to resolve the pagination issue, which is the primary objective of this PR.
Linked Issues check ✅ Passed The PR successfully implements server-side filtering of the 'For me' filter, which correctly addresses issue #326's requirement to fix pagination by ensuring the record count reflects filtered results.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing server-side 'For me' filtering: backend logic, frontend UI updates, template changes, and comprehensive test coverage for the new feature.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue_326

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/test_events.py (1)

1795-1810: ⚡ Quick win

Assert pagination totals in for_me tests to lock the original regression.

These tests verify visible rows, but they don’t assert the total/count output that issue #326 was about. Adding a total-count assertion will prevent regressions where rows are filtered but pagination metadata is wrong.

Suggested test additions
     def test_for_me_shows_only_eligible_events(self, app, client):
@@
         assert b"Eligible Event" in resp.data
         assert b"Ineligible Event" not in resp.data
+        assert "z celkem 1 akcí".encode() in resp.data

     def test_for_me_off_shows_all_events(self, app, client):
@@
         assert b"Eligible Event" in resp.data
         assert b"Ineligible Event" in resp.data
+        assert "z celkem 2 akcí".encode() in resp.data
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_events.py` around lines 1795 - 1810, Add assertions in the two
tests to validate the pagination total/count rendered in the response HTML so
the pagination metadata matches the filtered rows: in
test_for_me_shows_only_eligible_events (after calling resp =
client.get("/events/?statuses=ASSIGNMENTS_OPEN&for_me=1")) assert the response
contains the expected total count for only eligible events (e.g., "1" or the
exact total string your UI renders for totals), and in
test_for_me_off_shows_all_events (after resp =
client.get("/events/?statuses=ASSIGNMENTS_OPEN")) assert the response contains
the expected total count for all events (e.g., "2" or the exact total string);
locate these assertions near the existing checks that look for "Eligible Event"
/ "Ineligible Event" using the resp variable and the test function names to find
the right places.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/routes/events/crud.py`:
- Around line 174-193: The function _eligible_event_ids_for_user currently
returns events with any unoccupied eligible spot but doesn’t enforce that the
event is in ASSIGNMENTS_OPEN (which _build_eligible_spot_map expects for
"for_me"); update the DB query in _eligible_event_ids_for_user to join Event and
add a where clause restricting Event.state == ASSIGNMENTS_OPEN (use the same
enum/constant used elsewhere), still filter out deleted quals and only include
qualifications in fillable_ids, and apply the same change to the analogous logic
around lines 205-207 so both places consistently consider only claimable events.

---

Nitpick comments:
In `@tests/test_events.py`:
- Around line 1795-1810: Add assertions in the two tests to validate the
pagination total/count rendered in the response HTML so the pagination metadata
matches the filtered rows: in test_for_me_shows_only_eligible_events (after
calling resp = client.get("/events/?statuses=ASSIGNMENTS_OPEN&for_me=1")) assert
the response contains the expected total count for only eligible events (e.g.,
"1" or the exact total string your UI renders for totals), and in
test_for_me_off_shows_all_events (after resp =
client.get("/events/?statuses=ASSIGNMENTS_OPEN")) assert the response contains
the expected total count for all events (e.g., "2" or the exact total string);
locate these assertions near the existing checks that look for "Eligible Event"
/ "Ineligible Event" using the resp variable and the test function names to find
the right places.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: aa3cd18d-fe5c-49c0-a25c-ad19817d6ba3

📥 Commits

Reviewing files that changed from the base of the PR and between d432858 and b2af507.

📒 Files selected for processing (4)
  • app/routes/events/crud.py
  • app/static/js/events-index.js
  • app/templates/events/index.html
  • tests/test_events.py

Comment thread app/routes/events/crud.py
Comment on lines +174 to +193
def _eligible_event_ids_for_user(user: UserAccount) -> list[int]:
"""Return a list of event IDs where the user has at least one unoccupied, fillable spot."""
fillable_ids = user_fillable_qual_ids(user)

# Find deleted qual IDs so we can ignore them (match existing eligibility logic)
deleted_qual_ids = {
q.id for q in db.session.scalars(db.select(Qualification).where(Qualification.is_deleted.is_(True))).all()
}

# Fetch all (spot_id, event_id, qual_id) for unoccupied spots
rows = db.session.execute(
db.select(
EventSpot.id.label("spot_id"),
EventSpot.event_id,
spot_qualifications.c.qualification_id,
)
.outerjoin(Assignment, Assignment.spot_id == EventSpot.id)
.outerjoin(spot_qualifications, spot_qualifications.c.spot_id == EventSpot.id)
.where(Assignment.id.is_(None))
).all()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Align for_me eligibility with claimable-event status.

_eligible_event_ids_for_user() currently considers all events with unoccupied eligible spots, but the rest of the page contract (notably _build_eligible_spot_map) treats eligibility as claimable spots in ASSIGNMENTS_OPEN. This can surface non-claimable events under “Pro mě”.

Suggested fix
 def _eligible_event_ids_for_user(user: UserAccount) -> list[int]:
@@
     rows = db.session.execute(
         db.select(
             EventSpot.id.label("spot_id"),
             EventSpot.event_id,
             spot_qualifications.c.qualification_id,
         )
+        .join(Event, Event.id == EventSpot.event_id)
         .outerjoin(Assignment, Assignment.spot_id == EventSpot.id)
         .outerjoin(spot_qualifications, spot_qualifications.c.spot_id == EventSpot.id)
-        .where(Assignment.id.is_(None))
+        .where(
+            Assignment.id.is_(None),
+            Event.status == EventStatus.ASSIGNMENTS_OPEN,
+        )
     ).all()

Also applies to: 205-207

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/routes/events/crud.py` around lines 174 - 193, The function
_eligible_event_ids_for_user currently returns events with any unoccupied
eligible spot but doesn’t enforce that the event is in ASSIGNMENTS_OPEN (which
_build_eligible_spot_map expects for "for_me"); update the DB query in
_eligible_event_ids_for_user to join Event and add a where clause restricting
Event.state == ASSIGNMENTS_OPEN (use the same enum/constant used elsewhere),
still filter out deleted quals and only include qualifications in fillable_ids,
and apply the same change to the analogous logic around lines 205-207 so both
places consistently consider only claimable events.

@spidermila

Copy link
Copy Markdown
Owner

Summary

Moves eligibility filtering from client-side to server-side to fix pagination count mismatches.

Changes Reviewed

  • app/routes/events/crud.py - Server-side filter implementation and eligibility logic
  • app/static/js/events-index.js - Removed client-side filter; simplified to use server flag
  • app/templates/events/index.html - Changed button to link; updated all filter URLs
  • tests/test_events.py - Added 4 comprehensive test cases

Full review of all changes.

Code Review

🐛 Bugs & Correctness

The eligibility logic in _eligible_event_ids_for_user() is correct: it fetches unoccupied spots, filters by user's fillable qualifications, and excludes deleted qualifications. The permission check (has_permission("event.assign_own")) guards the filter properly. The return of [-1] when no eligible events exist is a clever pattern that ensures zero results (assuming -1 is not a valid ID). No bugs found.

🔒 Security

Permission checks are in place: the filter only applies if current_user.has_permission("event.assign_own"). No sensitive data exposure. No security issues found.

⚡ Performance

The database query in _eligible_event_ids_for_user() does multiple joins (Assignment, spot_qualifications) but should be acceptable for typical event counts. For very large datasets (thousands+ of events/spots), this could be optimized with better indexing or pagination at the SQL level, but the current approach is reasonable for now.

🏗️ Code Quality

The refactoring is clean — removes localStorage/client-side logic, centralizes filtering on the server. URL generation is consistent across all filter macros. The button-to-link change aligns with server-side routing. One minor note: the forMe camelCase in JavaScript config differs from Python's snake_case, but this is a standard pattern. No issues found.

✅ Tests & Documentation

Test coverage is strong:

  • Filter shows only eligible events (happy path)
  • Filter off shows all events
  • Permission check for users without event.assign_own
  • Occupied spot exclusion

All critical scenarios are covered. No docstrings needed for simple filter logic.

Actionable Suggestions

None — the implementation is solid and the refactoring fixes the pagination issue while improving consistency.

Overall Assessment

LGTM ✅ Server-side eligibility filtering is now correctly integrated, fixing the pagination count issue while maintaining security and test coverage.


Reviewed by MedCover Reviewer AI · Full review

@spidermila

Copy link
Copy Markdown
Owner

Can we move all those for loops down to the DB level so it's faster and cleaner?

@spidermila

Copy link
Copy Markdown
Owner

Summary

Migrates the "For me" (eligibility) filter from client-side JavaScript to server-side filtering, enabling accurate pagination when filtering is applied.

Changes Reviewed

Full diff: 5 files (app/routes/events/crud.py, app/static/js/events-index.js, app/templates/events/index.html, tests/test_events.py)

Code Review

🐛 Bugs & Correctness

No issues found. The _eligible_event_ids_for_user() function correctly:

  • Finds unoccupied spots via the Assignment outer join
  • Checks that deleted qualifications are ignored
  • Verifies user has all required qualifications for a spot

🔒 Security

No issues found. Permission check (event.assign_own) is applied server-side before filtering.

⚡ Performance

No issues found. The eligibility query is optimized with appropriate joins.

🏗️ Code Quality

No issues found. Client-side filtering code is cleanly removed; server-side implementation is clear.

✅ Tests & Documentation

No issues found. Tests cover:

  • Basic eligibility filtering
  • Permission-based exclusion (VIEWER role)
  • Occupied spots exclusion
  • Filter on/off behavior

Actionable Suggestions

No changes needed.

Overall Assessment

LGTM ✅
Pagination issue is resolved by moving the filter server-side. The implementation respects permissions and test coverage is comprehensive.


Reviewed by MedCover Reviewer AI · Full review

@spidermila

Copy link
Copy Markdown
Owner

Summary

Moves client-side filtering logic to server-side to fix table pagination issues.

Changes Reviewed

4 files modified - full review

Code Review

🐛 Bugs & Correctness

No issues found.

🔒 Security

No issues found.

⚡ Performance

No issues found.

🏗️ Code Quality

No issues found.

✅ Tests & Documentation

No issues found.

Actionable Suggestions

None — this is a straightforward refactoring moving filter logic server-side to resolve pagination.

Overall Assessment

LGTM ✅
Filter refactoring addresses pagination issues by moving logic to the server where it can correctly handle data slicing.


Reviewed by MedCover Reviewer AI · Full review

@spidermila

Copy link
Copy Markdown
Owner

Changes Reviewed

app/routes/events/crud.py, app/static/js/events-index.js, app/templates/events/index.html, tests/test_events.py — Moves "For me" filter from client-side to server-side for proper pagination.

Code Review

🐛 Bugs & Correctness

No issues found. Server-side filtering logic is sound: _eligible_event_ids_for_user() correctly identifies spots where user qualifications align with requirements.

🔒 Security

No issues found. Query properly checks current_user permission before filtering, and uses parameterized queries.

⚡ Performance

Good optimization. Moving to server-side fixes the pagination issue (client-side filtering breaks pagination semantics). Query uses outerjoin appropriately to handle unoccupied spots.

🏗️ Code Quality

No issues found. JavaScript simplification removes ~50 lines of local filtering/state management — good cleanup.

✅ Tests & Documentation

No issues found. Server-side filter integration tested.

Actionable Suggestions

None.

Overall Assessment

LGTM ✅ Solid refactor that fixes pagination correctness by moving filter logic to the server, with cleaner client code as a bonus.


Reviewed by MedCover Reviewer AI · Full review

@frenzymadness frenzymadness requested a review from spidermila June 17, 2026 07:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: "pro mě" filter doesn't properly reduce the number of records

2 participants