Skip to content

Add proxied full-stack e2e (docker images + nginx) and fix DISTINCT sort crash#327

Open
felixevers wants to merge 10 commits into
mainfrom
claude/filter-sort-e2e-tests-it1uyg
Open

Add proxied full-stack e2e (docker images + nginx) and fix DISTINCT sort crash#327
felixevers wants to merge 10 commits into
mainfrom
claude/filter-sort-e2e-tests-it1uyg

Conversation

@felixevers

Copy link
Copy Markdown
Member

Full-stack e2e behind the nginx proxy + a real backend sort crash it caught

Follow-up to #319: the mock-based e2e can't see routing or database-level problems, so this adds a second e2e tier that runs against the real stack — the docker images built for the current commit, wired together exactly like production (nginx proxy in front of web, backend and Keycloak).

Proxied full-stack e2e

  • docker-compose.e2e.yml — production-like stack (proxy → web / backend / Keycloak / Postgres / Redis / InfluxDB) with parameterized image references (BACKEND_IMAGE / WEB_IMAGE / PROXY_IMAGE) so CI runs this commit's images; ephemeral volumes, restart: on-failure on the backend so migrations retry until Postgres accepts connections.
  • tests/e2e/proxy-fullstack.spec.ts (gated behind E2E_PROXY_TARGET=1, skipped in the mock-based runs):
    • proxy routing: / → web, /keycloak/... → Keycloak (issuer under the subpath), /graphql → backend (JSON, never the frontend catch-all)
    • authentication through the proxied Keycloak: password grant for API seeding and the real browser login flow (redirect → login form → /auth/callback → app)
    • seeds patients + tasks over the proxied GraphQL API, then asserts server-side filtering and sorting from the real query engine: name asc/desc, birthdate, NOT_CONTAINS, update-date sort, and sorting tasks by patient asc/desc (issue Sorting of my tasks list based on patients not possible #213)
    • UI: logs in, searches, and sorts the patient list through the whole stack
  • e2e-proxy CI job (tests.yml) — builds the backend/web/proxy images for the commit with Buildx (cache-from/to: type=gha, same scopes now written by the Docker Build workflow, so layers are shared with the images built earlier), starts the compose stack, waits for readiness through the proxy, runs the spec, and uploads the report + stack logs on failure. Added to the required CI gate.
  • E2E_TESTING.md documents how to run this locally.

Real bug the proxied tests caught (issue #213)

Sorting my tasks by patient crashed on PostgreSQL:

(sqlalchemy) for SELECT DISTINCT, ORDER BY expressions must appear in select list

The tasks/patients base queries use SELECT DISTINCT (location-scope joins), and every sort that orders by a joined or correlated expression — tasks by patient or assignee, patients by update date, ward/room/bed, clinic or position — produced an ORDER BY outside the select list and 500ed. This is invisible to the mock-based suite and to SQLite-backed tests; it reproduced immediately against the real Postgres backend through the proxy.

Fix: apply_unified_query now rewraps DISTINCT statements into a plain select over deduplicated ids before applying sorts (and clears stale join memos so sort adapters re-join the rewrapped statement). This also covers the pre-existing case where a property filter (needs_distinct) was combined with a position/clinic sort.

Verification

  • All 5 proxied tests pass locally against the compose stack built from this commit (they fail on main with the DISTINCT crash when sorting tasks by patient).
  • Backend: ruff clean; unit tests show no new failures.
  • The spec is skipped without E2E_PROXY_TARGET=1, so the existing mock-based e2e job is unaffected.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WCSmjBuS6NbcB6oRmKvQgT


Generated by Claude Code

felixevers and others added 10 commits July 5, 2026 10:42
* feat: backend-driven CSV/XLSX exports for task and patient table views

Virtualized infinite scrolling made DOM-based printing unusable for large
lists. Table views can now be exported server-side with the complete result
set of the current view:

- POST /export/{tasks|patients}: authenticated via the existing Keycloak
  context, reuses the GraphQL list resolvers (same visibility, filters,
  sorts, search and scope as the UI), capped by EXPORT_MAX_ROWS
- CSV: semicolon-separated, UTF-8 BOM, CRLF, locale-aware decimals, safe
  against formula injection
- XLSX (openpyxl): print-ready ward list template with title block, styled
  repeated header row, zebra rows, freeze pane, auto filter, A4 landscape
  fit-to-width and page footer; dates as real cells with locale formats
- German/timezone handling: UTC values converted to the requested timezone
  (default Europe/Berlin), date-only due-date sentinel kept as calendar
  date, localized enum/boolean labels, UI-parity aggregated cells
  (assignees, ward/room/bed from position ancestors, task progress, age)
- Frontend: export menu next to the column switcher sends the visible
  columns in user order with localized headers plus the current
  filter/sort/search/scope state; wired into patients, my tasks and saved
  task views; new translations for all six locales

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHhpBYXJpVeACm3JMWNFyA

* feat: export refinements — exporting user, select labels, row heights, patient todos tab

- XLSX header block and print footer now include the exporting user
  ("Erstellt am … von <name>")
- select / multi-select property values resolve their option keys
  (<definitionId>-opt-<index>) to the definition's option labels, matching
  PropertyCell.tsx; multi-select values are joined inline with a comma
- taller XLSX rows: 26pt header, data rows scale with wrapped content
  (minimum 22pt); print orientation remains A4 landscape by default
- the patient detail tasks tab exports the patient's todo list
  (done/title/description/due date/assignee, sorted open-first by due date)
  via the patientId export scope

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BHhpBYXJpVeACm3JMWNFyA

---------

Co-authored-by: Claude <noreply@anthropic.com>
#318)

* Add per-field update timestamps for patient state, clinic and position

Adds state_updated_at, clinic_updated_at and position_updated_at columns
to patients, stamped when the respective value actually changes. The
timestamps are exposed as stateUpdateDate, clinicUpdateDate and
positionUpdateDate on PatientType, filterable and sortable via the
queryable fields API, included in table exports, and shown as patient
list columns that are hidden by default and can be enabled through the
column switcher.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTFvWu2rNPeV3Q8wXKoht1

* Add patient field update timestamps to e2e mock backend

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTFvWu2rNPeV3Q8wXKoht1

---------

Co-authored-by: Claude <noreply@anthropic.com>
Adds a row of small preset buttons below the due date input in the
task creation drawer: Now, +1/+3/+6/+12/+24 hours set a datetime due
date, while Today, Tomorrow, +2/+4 days set a date-only due date
(end-of-day sentinel). The due date stays empty until a preset is
clicked or a value is entered manually. Presets are computed in the
configured app timezone and the buttons wrap on narrow viewports.


Claude-Session: https://claude.ai/code/session_011FUW7ckxf9AFki2eubR3sX

Co-authored-by: Claude <noreply@anthropic.com>
E2E tests (tests/e2e):
- filter-sort-patients.spec.ts: sorting (name, birthdate, text property),
  text/tag filters, "does not contain", server-side search
- filter-sort-tasks.spec.ts: default order, sorting by patient/title/priority,
  text/tag filters, search (guards issue #213)
- filter-sort-views.spec.ts: saved views apply stored filters/sorting,
  legacy serializations, save/overwrite/discard, related panels
- support/queryEngine.ts + extended mockBackend.ts: the mock now honours
  filters/sorts/search/pagination with the same semantics as the backend
  query engine, serves queryable-field metadata and saved views

Fixes:
- "does not contain"/"not between" filters were sent as NEQ and silently
  behaved like "not equals": new NOT_CONTAINS/NOT_BETWEEN query operators
  in the backend, mapped from the table filter state (issue #227 class)
- saved views: legacy searchTags-based tag filters were dropped on
  deserialization, silently disabling the stored filter
- related panels (custom views): tag filters with the "equals" operator
  never matched because the selection is stored in uuidValue; assignee
  filters ignored the selected user id
- related patients panel: sorting by "Updated" sorted by name instead of
  the update date (now derived from the latest related task update)
- related panels: state ordering (WAIT<ADMITTED<DISCHARGED<DEAD), priority
  ordering (P1..P4), and due-date null ordering now match the backend;
  added firstname/lastname and ward/room/bed filter+sort support


Claude-Session: https://claude.ai/code/session_01WCSmjBuS6NbcB6oRmKvQgT

Co-authored-by: Claude <noreply@anthropic.com>
- Stop infinite re-render when opening the Tasks tab on a saved patient
  view: sync related table state from memoized baselines and only when the
  persisted view definition key changes.
- Fix virtual derived singleTag equals matching to use uuidValue like
  hightide and reject empty selections instead of matching every row.
- Harden filter/sort E2E helpers for tabbed view pages (visible toolbar
  controls and dialog-scoped equals tag filters).
- Move Playwright config to tests/playwright.config.ts with correct paths
  so CI/local runs pick up baseURL, retries, and webServer settings.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…ort crash

Full-stack e2e against the real backend behind the nginx proxy:
- docker-compose.e2e.yml: production-like stack (proxy -> web/backend/
  keycloak/postgres/redis/influxdb) with parameterized image references
  so CI runs the images built for the current commit; ephemeral volumes
- tests/e2e/proxy-fullstack.spec.ts (gated by E2E_PROXY_TARGET=1):
  verifies proxy routing (/, /graphql, /keycloak), authenticates via the
  proxied keycloak (password grant + browser login flow), seeds patients
  and tasks over the proxied GraphQL API, and asserts server-side
  filtering/sorting from the real query engine, including sorting tasks
  by patient (issue #213) and the NOT_CONTAINS operator
- e2e-proxy CI job in tests.yml: builds backend/web/proxy images for the
  commit with a shared buildx layer cache (docker-build.yml now writes
  the same gha cache scopes), starts the compose stack and runs the
  proxied tests against http://localhost; required in the CI gate

Backend fix uncovered by the proxied tests:
- sorting over DISTINCT base queries crashed on PostgreSQL ("for SELECT
  DISTINCT, ORDER BY expressions must appear in select list") whenever a
  sort ordered by a joined/correlated expression - tasks by patient or
  assignee (issue #213), patients by update date, ward/room/bed, clinic
  or position. The unified query engine now rewraps DISTINCT statements
  into a plain select over deduplicated ids before applying sorts, and
  clears stale join memos so sort adapters re-join the rewrapped query.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCSmjBuS6NbcB6oRmKvQgT
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.

2 participants