Skip to content

Feat/sortable sessions table headers (Visits, Views, and Last Seen Columns)#4100

Open
superham wants to merge 6 commits intoumami-software:masterfrom
superham:feat/sortable-sessions
Open

Feat/sortable sessions table headers (Visits, Views, and Last Seen Columns)#4100
superham wants to merge 6 commits intoumami-software:masterfrom
superham:feat/sortable-sessions

Conversation

@superham
Copy link
Copy Markdown

@superham superham commented Mar 23, 2026

This PR adds server-side sorting to the sessions table for Visits, Views, and Last Seen Columns. Clicking a sortable header cycles through descending -> ascending > default sort order

The motivation behind this is that there is no way to currently sort session table data by metrics (views, visits) which is something I find myself wanting to do.

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 23, 2026

@superham is attempting to deploy a commit to the Umami Software Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 23, 2026

Greptile Summary

This PR adds clickable, three-state sortable column headers (descending → ascending → clear) to the Sessions table for the visits, views, and lastSeen columns, backed by a new orderBy/sortDescending query parameter pair that flows from the URL through the API schema to the SQL ORDER BY clause. SQL injection is correctly avoided via a server-side SORT_COLUMNS whitelist that only ever interpolates hardcoded strings into the query.

Key findings:

  • Global hook pollution (P1): usePagedQuery is a shared hook used by 11+ query hooks across the app. Adding orderBy/sortDescending extraction here means every paged query (events, users, teams, websites, etc.) now reads these params from the URL and includes them in its cache key, even though no other API route consumes them. These params should be scoped to useWebsiteSessionsQuery instead.
  • Duplicated SORT_COLUMNS whitelist (P2): The column whitelist is defined identically inside both relationalQuery and clickhouseQuery, risking divergence when new sortable columns are added. A shared module-level constant would prevent this.
  • Loose schema validation (P2): orderBy is validated as z.string().optional(), accepting any string and silently falling back to createdAt for invalid values. Replacing this with z.enum([...]) would make the API contract explicit and return a proper 400 for unknown columns.
  • lastAt/createdAt naming inconsistency (P2): The "Last Seen" SortableLabel uses column="createdAt" while the DataColumn id is "lastAt", and the SQL redundantly selects max(created_at) twice under both names. Aligning on lastAt throughout would be cleaner.

Confidence Score: 3/5

  • The feature is functionally correct for sessions, but the shared hook change has unintended side-effects on all other paged queries in the app that should be addressed before merging.
  • The SQL injection guard, the 3-state sort cycle, page reset logic, and Zod transform are all implemented correctly. However, modifying usePagedQuery — a widely-shared hook — to unconditionally extract and forward orderBy/sortDescending from the URL affects the cache keys and request payloads of every other paged query in the codebase (events, users, websites, teams, etc.). While those endpoints silently strip the unknown params, this is a leaky abstraction that creates unintended coupling. The P2 items (duplicate whitelist, loose schema, naming inconsistency) are clean-up work but not blockers on their own.
  • src/components/hooks/usePagedQuery.ts — the shared hook change is the riskiest part of this PR and should be scoped to the sessions query only.

Important Files Changed

Filename Overview
src/components/hooks/usePagedQuery.ts Sorting params added globally to all paged queries, not just sessions; leaks URL state into unrelated query cache keys.
src/queries/sql/sessions/getWebsiteSessions.ts SORT_COLUMNS whitelist correctly sanitizes orderBy before SQL interpolation; whitelist is duplicated across relational and ClickHouse backends.
src/lib/schema.ts sortDescending correctly validated/transformed; orderBy uses loose z.string() instead of an enum, so invalid values silently fall back instead of returning a 400.
src/components/common/SortableLabel.tsx New sortable column header component; 3-state click cycle (desc → asc → clear) is correctly implemented and resets page to 1.
src/app/(main)/websites/[websiteId]/sessions/SessionsTable.tsx SortableLabel wired to visits, views, and lastAt columns; lastAt uses column="createdAt" which is a same-value duplicate of lastAt, creating a minor naming inconsistency.
src/app/api/websites/[websiteId]/sessions/route.ts sortingParams correctly spread into the request schema alongside the existing filter, paging, and search params.
cypress/e2e/api-sessions-sort.cy.ts Good API-level coverage of valid/invalid sort params and combinations with search/pagination; tests verify response shape but not actual sort ordering of returned rows.
cypress/e2e/sessions-sort.cy.ts UI tests correctly verify 3-state sort cycle URL behavior and column switching; page-reset test is present.

Sequence Diagram

sequenceDiagram
    participant U as User (Browser)
    participant SL as SortableLabel
    participant PQ as usePagedQuery
    participant API as /api/websites/[id]/sessions
    participant DB as getWebsiteSessions (SQL)

    U->>SL: Click column header
    SL->>SL: Cycle state: none→desc→asc→none
    SL->>U: router.push(updateParams({orderBy, sortDescending, page:1}))
    Note over U: URL: ?orderBy=visits&sortDescending=true&page=1

    U->>PQ: Re-render (URL changed)
    PQ->>PQ: Read orderBy, sortDescending from URL
    PQ->>API: GET /sessions?orderBy=visits&sortDescending=true

    API->>API: Zod schema: validate & transform sortDescending→boolean
    API->>DB: getWebsiteSessions(websiteId, filters)

    DB->>DB: Lookup SORT_COLUMNS["visits"] → '"visits"'
    DB->>DB: Build sortedFilters {orderBy: '"visits"', sortDescending: true}
    DB->>DB: pagedRawQuery(...ORDER BY "visits" desc)
    DB-->>API: {data, count, page, pageSize, orderBy}
    API-->>U: JSON response
Loading

Reviews (1): Last reviewed commit: "add cypress tests for sorting" | Re-trigger Greptile

Comment thread src/components/hooks/usePagedQuery.ts
Comment thread src/queries/sql/sessions/getWebsiteSessions.ts Outdated
Comment thread src/lib/schema.ts
Comment thread src/app/(main)/websites/[websiteId]/sessions/SessionsTable.tsx
@superham superham changed the title Feat/sortable sessions Feat/sortable sessions table headers Mar 23, 2026
@superham superham changed the title Feat/sortable sessions table headers Feat/sortable sessions table headers (Visits, Views, and Last Seen Columns) Mar 23, 2026
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.

1 participant