Task #194: Show deleted user's full name in admin Audit Log
Original task
-------------
The admin Audit Log row for a user.delete entry showed
"Target: user #1234 · @ahmed" — i.e. the @username — even though
admins recognise people by name. Surface displayNameEn / displayNameAr
in that line and fall back to @username only when no display name
exists.
Backend
-------
artifacts/api-server/src/routes/users.ts
The user.delete handler already persists `displayNameEn` and
`displayNameAr` in audit metadata (added in commit eb7d15c). No
code change was needed on the API side. Verified the metadata
shape via the existing audit-log-coverage test.
Frontend
--------
artifacts/tx-os/src/pages/admin.tsx
- Renamed `forceDeletedEntityName` to `deletedEntityName` and
updated the doc-comment.
- Relaxed its gate so it returns a name not only for force-delete
entries but also for plain `user.delete` entries. Other delete
actions (app.delete, group.delete) still gate on force-delete
to avoid changing behaviour outside this task's scope.
- Updated the single call-site to use the new function name.
Effect: a non-force user.delete row now renders a "Target: user
#1234 · Ahmed Al-Saleh" line beneath the summary, with locale-aware
fallback (Ar -> En -> @username).
Verification
------------
- pnpm api-spec codegen + tx-os tsc --noEmit: clean.
- artifacts/api-server `node --test` for audit-log-coverage,
audit-logs-actor-filter, audit-logs-forced-only-filter,
audit-logs-target-filter: 43 / 43 tests pass (CSV export tests
included).
- e2e test via runTest: created a user with displayName
"Ahmed Test User" / "أحمد مستخدم", deleted without force,
opened /#section=audit-log, and confirmed the new row's
summary plus the secondary "Target: user #19630 · أحمد مستخدم"
line render correctly (admin's Arabic locale was honoured).
Replit-Task-Id: fc3fc8f9-082a-49e4-b223-17baee11d268
This commit is contained in:
@@ -6406,15 +6406,18 @@ function isForceDeleteEntry(entry: AuditLogEntry): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pulls a human-readable display name for a force-deleted entity from its
|
||||
// audit metadata so the row can show "service #4821 · My Service" inline
|
||||
// without admins having to expand the JSON. Returns null when the row is not
|
||||
// a forced deletion or the metadata lacks any usable name field.
|
||||
function forceDeletedEntityName(
|
||||
// Pulls a human-readable display name for a deleted entity from its audit
|
||||
// metadata so the row can show "service #4821 · My Service" or "user #1234 ·
|
||||
// Ahmed Al-Saleh" inline without admins having to expand the JSON. Applies
|
||||
// to every force-delete row (where the dep counts are also useful) and to
|
||||
// non-force `user.delete` rows so admins can recognise the deleted person
|
||||
// by name even when the deletion was clean. Returns null when there is no
|
||||
// usable name field in the metadata.
|
||||
function deletedEntityName(
|
||||
entry: AuditLogEntry,
|
||||
lang: string,
|
||||
): string | null {
|
||||
if (!isForceDeleteEntry(entry)) return null;
|
||||
if (!isForceDeleteEntry(entry) && entry.action !== "user.delete") return null;
|
||||
const meta = asRecord(entry.metadata);
|
||||
const en =
|
||||
asString(meta.nameEn) ??
|
||||
@@ -6474,7 +6477,7 @@ function AuditLogRow({
|
||||
const summary = formatAuditSummary(entry, t, lang);
|
||||
const isForced = isForceDeleteEntry(entry);
|
||||
const chips = isForced ? dependencyChips(entry, t) : [];
|
||||
const deletedName = forceDeletedEntityName(entry, lang);
|
||||
const deletedName = deletedEntityName(entry, lang);
|
||||
const showDeletedTarget = deletedName != null && entry.targetId != null;
|
||||
return (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user