feat(audit-log): resolve user + entity names server-side so the SPA can display them #152

Open
YousifShkara wants to merge 1 commit from feat/audit-log-resolve-names into main
Owner

The audit log table in mokosh-apps was the last surface in the UI showing raw UUIDs to the user (the 8-char "Entity ID" + "User" columns and the full UUIDs in the expanded detail row). Resolving them client-side would require either an N+1 of GET-by-id calls per page render or a pile of per-entity-type caches; doing the join server-side keeps the response shape flat and the SPA dumb.

Schema-touching is a single SQL change on the LIST query in AuditService::list. The append/INSERT path is unchanged.

AuditLogEntryResponse gains two new optional fields:

  • user_name: from a LEFT JOIN against users on user_id. Composed as NULLIF(TRIM(first_name || ' ' || last_name), '') so a JIT-created user whose names are still the synthetic email-derived placeholders (and whose last_name is empty) collapses to NULL instead of returning a half-formed " " string. The SPA renders "System" when null.

  • entity_name: a CASE entity_type WHEN ... THEN (SELECT ... FROM ... WHERE id = al.entity_id) per known entity_type. Each subselect is a single PK lookup, so a 25-row page costs 25 trivial index hits. Covers every entity_type emitted by current audit_write callsites: companies, contacts (first_name || last_name with the same NULLIF as users), sites, contracts, contract_items, rate_cards, invoices, tickets (formatted as T<ticket_number>), projects, assets, credential_vault, payment_gateway_configs, tax_rates, plus the synthetic auth entity_type (whose entity_id is itself a user_id, resolved against users the same way user_name is). Unknown future entity_types fall through to ELSE NULL; the SPA renders the short UUID in that case so the row still carries something the reader can correlate against.

WHERE-clause condition strings now carry the al. alias since the JOIN puts a second id/user_id/etc. in scope, and the count query also aliases audit_log al to keep the shared where_clause valid against both queries without re-templating.

AuditRow and the From<AuditRow> for AuditLogEntryResponse mapping pick the two new columns; the LIST handler signature is unchanged.

Backwards-compat: the new fields are Option<String>. An older SPA build hitting a newer server simply ignores them via serde's drop-unknown behaviour. A newer SPA build hitting an older server decodes the missing fields as None via #[serde(default)] on the client and falls back to the short-UUID rendering, so neither half of the rollout breaks the audit log page.

Pairs with mokosh-apps feat/audit-log-show-names.

The audit log table in mokosh-apps was the last surface in the UI showing raw UUIDs to the user (the 8-char "Entity ID" + "User" columns and the full UUIDs in the expanded detail row). Resolving them client-side would require either an N+1 of GET-by-id calls per page render or a pile of per-entity-type caches; doing the join server-side keeps the response shape flat and the SPA dumb. Schema-touching is a single SQL change on the LIST query in `AuditService::list`. The append/INSERT path is unchanged. `AuditLogEntryResponse` gains two new optional fields: - `user_name`: from a LEFT JOIN against `users` on `user_id`. Composed as `NULLIF(TRIM(first_name || ' ' || last_name), '')` so a JIT-created user whose names are still the synthetic email-derived placeholders (and whose `last_name` is empty) collapses to NULL instead of returning a half-formed " " string. The SPA renders "System" when null. - `entity_name`: a `CASE entity_type WHEN ... THEN (SELECT ... FROM ... WHERE id = al.entity_id)` per known entity_type. Each subselect is a single PK lookup, so a 25-row page costs 25 trivial index hits. Covers every entity_type emitted by current audit_write callsites: companies, contacts (`first_name || last_name` with the same NULLIF as users), sites, contracts, contract_items, rate_cards, invoices, tickets (formatted as `T<ticket_number>`), projects, assets, credential_vault, payment_gateway_configs, tax_rates, plus the synthetic `auth` entity_type (whose entity_id is itself a user_id, resolved against users the same way `user_name` is). Unknown future entity_types fall through to `ELSE NULL`; the SPA renders the short UUID in that case so the row still carries something the reader can correlate against. WHERE-clause condition strings now carry the `al.` alias since the JOIN puts a second `id`/`user_id`/etc. in scope, and the count query also aliases `audit_log al` to keep the shared `where_clause` valid against both queries without re-templating. `AuditRow` and the `From<AuditRow> for AuditLogEntryResponse` mapping pick the two new columns; the LIST handler signature is unchanged. Backwards-compat: the new fields are `Option<String>`. An older SPA build hitting a newer server simply ignores them via serde's drop-unknown behaviour. A newer SPA build hitting an older server decodes the missing fields as None via `#[serde(default)]` on the client and falls back to the short-UUID rendering, so neither half of the rollout breaks the audit log page. Pairs with mokosh-apps `feat/audit-log-show-names`.
feat(audit-log): resolve user + entity names server-side so the SPA can display them
All checks were successful
E2E (staging) / Playwright against staging (pull_request) Successful in 33s
Check / fmt + clippy + compile + tests (pull_request) Successful in 1m21s
Build OCI container / Build and push mokosh-api image (push) Successful in 3m20s
579bee797e
The audit log table in mokosh-apps was the last surface in the UI showing raw UUIDs to the user (the 8-char "Entity ID" + "User" columns and the full UUIDs in the expanded detail row). Resolving them client-side would require either an N+1 of GET-by-id calls per page render or a pile of per-entity-type caches; doing the join server-side keeps the response shape flat and the SPA dumb.

Schema-touching is a single SQL change on the LIST query in `AuditService::list`. The append/INSERT path is unchanged.

`AuditLogEntryResponse` gains two new optional fields:

- `user_name`: from a LEFT JOIN against `users` on `user_id`. Composed as `NULLIF(TRIM(first_name || ' ' || last_name), '')` so a JIT-created user whose names are still the synthetic email-derived placeholders (and whose `last_name` is empty) collapses to NULL instead of returning a half-formed " " string. The SPA renders "System" when null.

- `entity_name`: a `CASE entity_type WHEN ... THEN (SELECT ... FROM ... WHERE id = al.entity_id)` per known entity_type. Each subselect is a single PK lookup, so a 25-row page costs 25 trivial index hits. Covers every entity_type emitted by current audit_write callsites: companies, contacts (`first_name || last_name` with the same NULLIF as users), sites, contracts, contract_items, rate_cards, invoices, tickets (formatted as `T<ticket_number>`), projects, assets, credential_vault, payment_gateway_configs, tax_rates, plus the synthetic `auth` entity_type (whose entity_id is itself a user_id, resolved against users the same way `user_name` is). Unknown future entity_types fall through to `ELSE NULL`; the SPA renders the short UUID in that case so the row still carries something the reader can correlate against.

WHERE-clause condition strings now carry the `al.` alias since the JOIN puts a second `id`/`user_id`/etc. in scope, and the count query also aliases `audit_log al` to keep the shared `where_clause` valid against both queries without re-templating.

`AuditRow` and the `From<AuditRow> for AuditLogEntryResponse` mapping pick the two new columns; the LIST handler signature is unchanged.

Backwards-compat: the new fields are `Option<String>`. An older SPA build hitting a newer server simply ignores them via serde's drop-unknown behaviour. A newer SPA build hitting an older server decodes the missing fields as None via `#[serde(default)]` on the client and falls back to the short-UUID rendering, so neither half of the rollout breaks the audit log page.

Pairs with mokosh-apps `feat/audit-log-show-names`.
All checks were successful
E2E (staging) / Playwright against staging (pull_request) Successful in 33s
Check / fmt + clippy + compile + tests (pull_request) Successful in 1m21s
Build OCI container / Build and push mokosh-api image (push) Successful in 3m20s
This pull request can be merged automatically.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/audit-log-resolve-names:feat/audit-log-resolve-names
git switch feat/audit-log-resolve-names
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
psa-systems/mokosh-server!152
No description provided.