Skip to content

FRD-033: Audit Log Viewer

FieldValue
OwnerDavid Holmes
StatusDraft
Last Updated2026-05-26
RelatedADR-027 (Default Tech Stack)
Target Releasev2.0.0
TypeWidget
SizeM
PriorityP1 — SaaS Widget

Document Summary

Build an Audit Log Viewer widget providing a searchable, filterable DataGrid with actor, action, resource, and timestamp columns. The widget supports payload preview in a detail panel, export capability, and standardized loading/empty/error states. It complements the existing activity-feed-widget.tsx by providing a structured, tabular audit format for compliance and debugging use cases.


Introduction

Overview

SaaS applications need audit logs for compliance, debugging, and security investigation. The design system provides ActivityFeedWidget for informal, chronological activity feeds, but lacks a structured tabular audit log with search, filtering, column sorting, and payload inspection. Teams building admin panels, compliance dashboards, and incident review tools need this pattern.

Goals

  • Provide an AuditLogViewer widget with actor, action, resource, timestamp columns as a structured DataGrid.
  • Support full-text search across actor, action, and resource fields.
  • Support column-level filtering (action type, actor, date range).
  • Provide a detail panel for payload/diff preview when a row is selected.
  • Support export (CSV/JSON) of visible or selected log entries.
  • Handle loading, empty, and error states.

Non-Goals

  • Log ingestion or storage (backend concern).
  • Real-time streaming of new log entries (future enhancement).
  • Log retention policy management.

Scope

In Scope

ItemDescription
AuditLogViewer widgetDataGrid-based table with actor, action, resource, timestamp, and optional metadata columns.
SearchFull-text search across actor name, action type, and resource identifier.
FiltersAction type filter (dropdown), actor filter (search/select), date range filter (DateRangePicker).
Detail panelSlideOutPanel or expandable row showing the full event payload as formatted JSON or key-value pairs.
Export”Export” button exporting visible rows as CSV or JSON; consumer provides the export handler.
State handlingLoading skeleton, empty state, error state with retry.
Stories and testsStorybook stories for all states; unit tests for search, filter, and detail panel.

Out of Scope

ItemReason
Log ingestionBackend concern; widget consumes an array of log entries.
Real-time streamingFuture enhancement; initial version works with a static or periodically refreshed dataset.
Log retention/archivalAdministrative concern outside the widget.

Users and Pain Points

UserPain Point
Compliance officersNeed structured audit logs for SOC 2, GDPR, and HIPAA compliance reviews; current ActivityFeedWidget is informal.
DevOps engineersDebugging production incidents requires searching through structured logs by actor, action, and time range.
Security teamsInvestigating access patterns requires filtering by actor and action type with payload inspection.
Admin panel developersBuild custom audit log tables from scratch for every SaaS product.

Definitions

TermDefinition
Audit log entryA structured record of a system event: who (actor), what (action), on which resource, when (timestamp), and optionally what changed (payload).
ActorThe user or system that performed the action (name, email, avatar).
ActionThe type of event (e.g., “user.created”, “key.revoked”, “role.updated”).
ResourceThe entity affected by the action (type + identifier, e.g., “API Key: production-key-1”).
PayloadThe structured data associated with the event (e.g., before/after diff, request parameters).

Current State

  • ActivityFeedWidget (src/components/widgets/activity-feed-widget.tsx): Informal chronological feed with avatar, event description, and timestamp. Supports max items, refresh, and loading state. Not structured for search/filter/export.
  • DataGrid (src/components/ui/data-grid.tsx): Full-featured table with sort, filter, paginate, row selection.
  • ListToolbar: Toolbar with search and filter controls.
  • DateRangePicker: Available for date range filtering.
  • SlideOutPanel: Available for detail panel display.
  • No audit log viewer widget exists.

Proposed Solution

Build src/components/widgets/audit-log-viewer.tsx:

Table Structure

ColumnContentSortableFilterable
TimestampRelative time with absolute tooltipYes (default desc)Yes (date range)
ActorAvatar + name + emailYesYes (search/select)
ActionAction type badge (color-coded by category)YesYes (dropdown)
ResourceResource type + identifierYesNo (search covers this)
StatusSuccess/failure badgeYesYes (dropdown)

A toolbar search input filters across actor name, action type, resource identifier, and resource type. Debounced at 200ms.

Filters

  • Action type: Multi-select dropdown with consumer-provided action types.
  • Actor: Searchable select with consumer-provided actor list.
  • Date range: DateRangePicker with preset ranges (Last hour, Last 24h, Last 7 days, Custom).
  • Status: Success/failure toggle.

Detail Panel

Clicking a row opens a SlideOutPanel showing:

  • Full event metadata (actor, action, resource, timestamp, IP address, user agent).
  • Payload as formatted JSON with syntax highlighting.
  • Optional before/after diff view for update events.

Export

An “Export” button in the toolbar triggers onExport(format, entries) callback. The widget provides the visible/filtered entries; the consumer handles file generation.


Requirements

Requirement Priorities

  • Must Have: Table with core columns, search, sort, loading/empty/error states.
  • Should Have: Action type and date range filters, detail panel, export.
  • Could Have: Actor filter, before/after diff view, column customization.

Functional Requirements

IDRequirementPriority
FR-01Widget renders a DataGrid with timestamp, actor, action, resource, and status columns.Must
FR-02Timestamp column sorts descending by default; all columns support sorting.Must
FR-03Search input filters across actor, action, and resource fields with 200ms debounce.Must
FR-04Action type multi-select filter narrows visible entries.Should
FR-05Date range filter using DateRangePicker with preset ranges.Should
FR-06Clicking a row opens a detail panel showing full event metadata and payload.Should
FR-07Payload renders as formatted JSON with monospace font.Should
FR-08Export button triggers onExport callback with format and visible entries.Should
FR-09Loading state renders skeleton rows.Must
FR-10Empty state renders “No audit log entries” with configurable message.Must
FR-11Error state renders error message with retry button.Must
FR-12Action type badges are color-coded by category (consumer provides category-color mapping).Should

Non-Functional Requirements

IDRequirementTarget
NFR-01Renders 500 log entries in < 100ms.Measured via React Profiler.
NFR-02Bundle size< 8 KB gzipped (excluding shared DataGrid and SlideOutPanel deps).
NFR-03Search responsiveness< 50ms perceived filter latency after debounce.
NFR-04Dark modeFull token-based dark mode support.
NFR-05Payload displayFormatted JSON with proper indentation; no horizontal overflow.

API/Interface Requirements

interface AuditLogEntry {
id: string;
timestamp: string; // ISO 8601 datetime
actor: {
id: string;
name: string;
email?: string;
avatarSrc?: string;
};
action: string; // e.g. "user.created", "key.revoked"
actionCategory?: string; // e.g. "auth", "billing", "admin"
resource: {
type: string; // e.g. "API Key", "User", "Project"
id: string;
label?: string; // human-readable name
};
status?: "success" | "failure";
payload?: Record<string, unknown>;
metadata?: Record<string, string>; // IP, user agent, etc.
}
interface AuditLogViewerProps {
entries: AuditLogEntry[];
loading?: boolean;
error?: string;
onRetry?: () => void;
actionTypes?: string[]; // available action types for filter
actionCategoryColors?: Record<string, string>; // category → badge color
onExport?: (format: "csv" | "json", entries: AuditLogEntry[]) => void;
onEntryClick?: (entry: AuditLogEntry) => void;
emptyTitle?: string;
emptyDescription?: string;
className?: string;
}

Accessibility Requirements

IDRequirement
A11Y-01DataGrid follows proper grid semantics with sortable column headers.
A11Y-02Detail panel is announced as a dialog/region and traps focus.
A11Y-03Search input has aria-label="Search audit logs".
A11Y-04Filter controls are keyboard accessible and announce their current state.
A11Y-05Status badges include aria-label for screen readers (not just color-coded).
A11Y-06All components pass axe-core automated checks with zero violations.

Content and Documentation Requirements

IDRequirement
DOC-01Storybook docs page with usage guidelines, prop table, and interactive examples.
DOC-02Recipe showing AuditLogViewer with TanStack Query for paginated server-side log fetching.
DOC-03Guide on structuring audit log entries for consistency across services.

Dependencies

DependencyTypeRisk
src/components/ui/data-grid.tsxInternalLow — core table primitive.
src/components/ui/list-toolbar.tsxInternalLow — search and filter toolbar.
src/components/ui/slide-out-panel.tsxInternalLow — detail panel.
src/components/ui/date-range-picker.tsxInternalLow — date range filter.
src/components/ui/badge.tsxInternalLow — action type badges.
src/components/ui/avatar.tsxInternalLow — actor display.
src/components/ui/status-card.tsxInternalLow — empty/error states.

Risks and Tradeoffs

RiskLikelihoodImpactMitigation
Large log datasets (10K+ entries) cause client-side performance issuesMediumHighDocument that server-side pagination is recommended for 1K+ entries; provide onPageChange callback pattern.
Payload JSON rendering is slow for deeply nested objectsLowMediumLimit initial render depth; expand on demand.
Action type taxonomy varies widely across consumer applicationsMediumLowConsumer provides their own action types; widget is taxonomy-agnostic.

Open Questions

#QuestionOwnerStatus
OQ-01Should the detail panel support a before/after diff view for update events?David HolmesOpen
OQ-02Should the widget support server-side pagination via onPageChange callback?David HolmesOpen
OQ-03Should the export handle file generation or just pass data to the consumer?David HolmesOpen

Acceptance Criteria

#Criterion
AC-01Widget renders a table with timestamp, actor, action, resource, and status columns.
AC-02Search filters entries across actor, action, and resource fields.
AC-03Action type and date range filters narrow visible entries.
AC-04Clicking a row opens a detail panel with full event metadata and formatted payload.
AC-05Export button triggers the onExport callback with the current visible entries.
AC-06Loading, empty, and error states render appropriate visual treatments.
AC-07All components pass axe-core checks with zero violations.
AC-08Storybook stories exist for all states and interaction flows.
AC-09pnpm typecheck and pnpm vitest run --project unit pass with zero errors.

LLM Handoff Instructions

When implementing this FRD:

  1. Create src/components/widgets/audit-log-viewer.tsx. Follow the widget composition pattern from activity-feed-widget.tsx and people-table.tsx.
  2. DataGrid columns: Timestamp (relative time cell), Actor (Avatar + name + email cell), Action (Badge cell), Resource (type + label cell), Status (Badge cell).
  3. Toolbar: Use ListToolbar with search input. Add action type multi-select and date range filter as additional toolbar items.
  4. Detail panel: Use SlideOutPanel. Render metadata as a key-value list and payload as a &lt;pre&gt; with monospace font and proper indentation (JSON.stringify(payload, null, 2)).
  5. Export: Toolbar button triggers onExport(format, visibleEntries). The consumer generates the file.
  6. State handling: Same pattern as ResourceTable — loading skeleton, empty StatusCard, error StatusCard with retry.
  7. Stories in src/components/widgets/audit-log-viewer.stories.tsx. Include: Default, Loading, Empty, Error, WithFilters, DetailPanel, Export.
  8. Tests in src/components/widgets/audit-log-viewer.test.tsx.

Key files:

  • src/components/widgets/activity-feed-widget.tsx — informal feed pattern (contrast with structured table).
  • src/components/ui/data-grid.tsx — table primitive.
  • src/components/ui/slide-out-panel.tsx — detail panel.
  • src/components/ui/date-range-picker.tsx — date filter.

Decision Log

DateDecisionRationale
2026-05-26Table format rather than timeline/feed format.Audit logs need sortable, filterable columns for compliance; feed format (ActivityFeedWidget) is for informal activity.
2026-05-26Client-side filtering with server-side pagination recommendation for large datasets.Keeps widget simple; server-side pagination is documented as a consumer pattern.
2026-05-26Export delegates to consumer via callback rather than generating files internally.File generation (CSV library, download trigger) is a consumer concern; widget provides the data.

Document History

VersionDateAuthorChanges
0.12026-05-26David HolmesInitial draft.