Make forced-delete dependency chips clickable to pivot the audit log

Task #134: Admins investigating a forced deletion can now click any
dependency chip on a force_delete row to jump to a pre-filtered audit log
view of the related history.

Backend (artifacts/api-server, lib/api-spec):
- Added targetType + targetId query params to GET /api/admin/audit-logs
  and its CSV export. targetId is validated as a positive integer (400
  on bad input). Codegen regenerated for the React API client.

Frontend (artifacts/tx-os/src/pages/admin.tsx):
- Dependency chips on forced-delete rows are now real <button> elements
  with aria-labels and keyboard focus. Non-deletion rows are unchanged.
- Chip → pivot mapping: groupCount→targetType=group,
  memberCount→targetType=user, appCount→targetType=app,
  roleCount→targetType=role; cascade chips (orderCount, messageCount,
  noteCount, etc.) pivot to the parent (targetType+targetId).
- Active filter is reflected in the URL hash (deep-linkable + reload
  safe) and shown as a dismissible pill in the panel; the pill includes
  a Clear button. Switching audit sub-section drops the filter.
- Section sync logic preserves hash params and uses a one-shot
  skipNextSectionSync ref so initial deep-linked hashes aren't clobbered.
- New i18n keys in en.json and ar.json for filter labels and chip
  aria-labels.

Tests:
- New backend tests in artifacts/api-server/tests/audit-logs-target-filter.test.mjs
  cover targetType narrowing, targetType+targetId narrowing, invalid
  targetId rejection, combination with forcedOnly, and CSV export
  honoring the new filters (7 tests, all passing).
- Verified end-to-end via the browser testing skill: chip click,
  filter pill, clear, deep-link reload all behave correctly.

Pre-existing unrelated failures (not touched): two PDF archive tests in
executive-meetings.test.mjs and the matching typecheck errors in
executive-meetings.ts.

Replit-Task-Id: 46f972ef-2874-4fc3-95c5-53d0ff0732e9
This commit is contained in:
riyadhafraa
2026-04-30 07:25:16 +00:00
parent d72c5cc851
commit 058cb8b817
8 changed files with 560 additions and 20 deletions
@@ -1097,6 +1097,21 @@ contains `force: true`. Overrides the `action` filter when set.
* End date (inclusive, YYYY-MM-DD UTC).
*/
to?: string;
/**
* Restrict results to entries whose `target_type` exactly matches
(e.g. "group", "app", "role", "user", "service", "settings").
Combines with `targetId` and the other filters.
*/
targetType?: string;
/**
* Restrict results to entries whose `target_id` exactly matches.
Typically used together with `targetType` to focus on the history
of a single entity.
* @minimum 1
*/
targetId?: number;
/**
* @minimum 1
* @maximum 200
@@ -1128,4 +1143,16 @@ Overrides the `action` filter when set.
* End date (inclusive, YYYY-MM-DD UTC).
*/
to?: string;
/**
* Restrict the export to entries whose `target_type` exactly matches.
Combines with `targetId` and the other filters.
*/
targetType?: string;
/**
* Restrict the export to entries whose `target_id` exactly matches.
* @minimum 1
*/
targetId?: number;
};
+35
View File
@@ -2035,6 +2035,25 @@ paths:
type: string
format: date
description: End date (inclusive, YYYY-MM-DD UTC).
- in: query
name: targetType
required: false
schema:
type: string
description: |
Restrict results to entries whose `target_type` exactly matches
(e.g. "group", "app", "role", "user", "service", "settings").
Combines with `targetId` and the other filters.
- in: query
name: targetId
required: false
schema:
type: integer
minimum: 1
description: |
Restrict results to entries whose `target_id` exactly matches.
Typically used together with `targetType` to focus on the history
of a single entity.
- in: query
name: limit
required: false
@@ -2106,6 +2125,22 @@ paths:
type: string
format: date
description: End date (inclusive, YYYY-MM-DD UTC).
- in: query
name: targetType
required: false
schema:
type: string
description: |
Restrict the export to entries whose `target_type` exactly matches.
Combines with `targetId` and the other filters.
- in: query
name: targetId
required: false
schema:
type: integer
minimum: 1
description: |
Restrict the export to entries whose `target_id` exactly matches.
responses:
"200":
description: CSV file download
+26
View File
@@ -2434,6 +2434,19 @@ export const ListAuditLogsQueryParams = zod.object({
.optional()
.describe("Start date (inclusive, YYYY-MM-DD UTC)."),
to: zod.date().optional().describe("End date (inclusive, YYYY-MM-DD UTC)."),
targetType: zod.coerce
.string()
.optional()
.describe(
'Restrict results to entries whose `target_type` exactly matches\n(e.g. \"group\", \"app\", \"role\", \"user\", \"service\", \"settings\").\nCombines with `targetId` and the other filters.\n',
),
targetId: zod.coerce
.number()
.min(1)
.optional()
.describe(
"Restrict results to entries whose `target_id` exactly matches.\nTypically used together with `targetType` to focus on the history\nof a single entity.\n",
),
limit: zod.coerce
.number()
.min(1)
@@ -2504,4 +2517,17 @@ export const ExportAuditLogsCsvQueryParams = zod.object({
.optional()
.describe("Start date (inclusive, YYYY-MM-DD UTC)."),
to: zod.date().optional().describe("End date (inclusive, YYYY-MM-DD UTC)."),
targetType: zod.coerce
.string()
.optional()
.describe(
"Restrict the export to entries whose `target_type` exactly matches.\nCombines with `targetId` and the other filters.\n",
),
targetId: zod.coerce
.number()
.min(1)
.optional()
.describe(
"Restrict the export to entries whose `target_id` exactly matches.\n",
),
});