Skip to content

Improve audit logs#228

Merged
arjunkomath merged 2 commits intomainfrom
feat/improve-audit-logs
Feb 28, 2026
Merged

Improve audit logs#228
arjunkomath merged 2 commits intomainfrom
feat/improve-audit-logs

Conversation

@arjunkomath
Copy link
Member

@arjunkomath arjunkomath commented Feb 28, 2026

Summary by CodeRabbit

  • New Features

    • Activity detail panel showing event ID, timestamps (local/UTC/ISO), actor info, and before/after change details
    • Activity items are now clickable to open a compact detail view
    • Events include unique event identifiers and captured metadata (IP, user agent, email when available)
    • Improved event labels and human-readable descriptions; richer datetime formatting
  • Refactor

    • Activity feed layout redesigned for compact, full-width list with inline author info

@vercel
Copy link

vercel bot commented Feb 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
manage Ready Ready Preview Feb 28, 2026 10:43pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Feb 28, 2026

📝 Walkthrough

Walkthrough

Adds a client-side ActivityDetailPanel, backend traceability for activities (eventId + metadata), TRPC getActivityById, expanded actor email, logging of request metadata, event-labeling utilities, and ActivityFeed UI changes to support on-demand detail viewing.

Changes

Cohort / File(s) Summary
Activity Detail UI
components/project/activity/activity-detail-panel.tsx, components/project/activity/activity-feed.tsx
New client component ActivityDetailPanel that fetches activity by ID and renders multi-section details; ActivityFeed updated to use clickable ActivityItem, wire selection state (useQueryState), call onSelect(id), and render ActivityDetailPanel. Replaced getActionConfig with getActionIcon and adjusted item layout, author display, and time formatting.
Database Schema & Types
drizzle/schema.ts, drizzle/types.ts
Added eventId (text) and metadata (jsonb) columns to activity table. Widened ActivityWithActor.actor to include email.
Activity Logging
lib/activity/index.ts
logActivity now gathers request headers, builds metadata (ipAddress, userAgent, userEmail), generates eventId via crypto.randomUUID(), and persists eventId and metadata with the activity insert.
Activity Messaging Utilities
lib/activity/message.ts
Added getActionIcon, formatEventTypeLabel, getEventDescription, enhanced date handling and field label mapping for human-friendly labels and icons used by UI.
Backend API / Router
trpc/routers/projects.ts
Added protectedProcedure getActivityById({ id, projectId }) with access check (canViewProject), returns activity including actor.email; getActivities now includes actor.email in results.
Date Utilities
lib/utils/date.ts
Added toFullDateTimeString(date, timeZone) returning a full locale-formatted date/time string.

Sequence Diagram

sequenceDiagram
    actor User
    participant Feed as Activity Feed (Client)
    participant Detail as ActivityDetailPanel (Client)
    participant TRPC as TRPC Server
    participant DB as Database

    User->>Feed: Click activity item
    Feed->>Feed: setSelectedActivityId(id)
    Feed->>Detail: Render with activityId, projectId
    Detail->>TRPC: call getActivityById(id, projectId)
    TRPC->>DB: SELECT activity WITH actor (+email) and metadata WHERE id & projectId
    DB-->>TRPC: activity record (including eventId, metadata, actor.email)
    TRPC-->>Detail: activity data
    Detail->>Detail: render header, timestamps, actor, what/old→new
    Detail-->>User: display detail panel
    User->>Detail: Click close
    Detail->>Feed: onClose()
    Feed->>Feed: clear selectedActivityId
    Feed-->>User: detail panel removed
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I nibbled logs and found a spark,

eventIds glowing in the dark.
Metadata whispers where we trod,
Details bloom — a tidy nod.
Hooray for traces, crisp and bright! 🥕✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Improve audit logs' is vague and generic. While it relates to the changeset, it uses a non-descriptive term ('Improve') that doesn't convey specific meaningful information about what was actually changed or added. Consider a more specific title that captures the main change, such as 'Add activity detail panel and enhance audit logging with event metadata' or 'Add detailed activity view with event IDs and metadata tracking'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/improve-audit-logs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
components/project/activity/activity-detail-panel.tsx (1)

24-43: Extract getActionIcon into a shared activity UI helper.

This mapping is duplicated in components/project/activity/activity-feed.tsx; centralizing it prevents drift and keeps behavior consistent.

As per coding guidelines, **/*.{ts,tsx,js,jsx}: "Check if there are existing libraries, helpers, or utils that can be used before writing new code", and **/*.{ts,tsx}: "Write modular and reusable code; check if a component can be reused before creating a new one".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/project/activity/activity-detail-panel.tsx` around lines 24 - 43,
Extract the duplicated getActionIcon mapping into a shared helper (e.g., export
getActionIcon from a new module like activityIcons or activityUI helper) and
replace the inline function in both activity-detail-panel.tsx and
activity-feed.tsx with an import of that helper; ensure the helper exports the
same function signature and return shape (icon, color, bg) so existing callers
(getActionIcon in activity-detail-panel and the duplicate in activity-feed)
continue to work without other changes, then remove the duplicated
implementation from both files and update their imports to use the shared
helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/project/activity/activity-detail-panel.tsx`:
- Around line 146-152: The close button rendering in ActivityDetailPanel (the
<button> with onClick={onClose} that contains the X icon) is missing an
accessible label; update that button to include an aria-label (e.g.,
aria-label="Close" or aria-label={t('Close')} if using i18n) so screen readers
can announce its purpose, keeping the onClick handler (onClose) and the X icon
component intact.
- Around line 106-127: The loader shows indefinitely on failures and the query
key omits projectId; update the useQuery call for projects.getActivityById to
include projectId in the queryKey (e.g. ["projects","getActivityById",
projectId, activityId]) and read the query's error state (useQuery return:
isPending/isLoading, error, data/item) instead of relying solely on !item; in
the render replace the unconditional SpinnerWithSpacing for the !item case with
branching that shows SpinnerWithSpacing when loading (isPending/isLoading), an
error message/UI when error is present (use error from useQuery), and the item
details otherwise, using the existing symbols useQuery, queryKey,
projects.getActivityById, isPending/isLoading, error, item, SpinnerWithSpacing,
and Panel to locate and fix the logic.

In `@lib/activity/index.ts`:
- Around line 44-57: The code currently assigns the raw sessionId from
auth.api.getSession into metadata.sessionId; change this to avoid persisting raw
session identifiers by replacing sessionId with a non-reversible fingerprint
before adding it to activity.metadata (e.g., compute a SHA256/HMAC of sessionId
with an application secret or salt), keep the variable sessionId for fetching
but set metadata.sessionId = sessionId ? fingerprint(sessionId) : null, and
ensure any references to activity.metadata read the fingerprint rather than the
raw id.

In `@trpc/routers/projects.ts`:
- Around line 468-505: The getActivityById protectedProcedure currently returns
activity.metadata to any viewer; change it to restrict or redact that field for
non-admins by either (a) changing the DB query in
ctx.db.query.activity.findFirst to exclude the metadata column for
non-privileged users, or (b) after fetching activityItem, check an admin/owner
helper (e.g., isProjectAdmin(ctx, input.projectId) or ctx.session.user.role ===
'admin') and if the caller is not privileged, remove or replace
activityItem.metadata with a redacted value before returning; update references
to activityItem.metadata and ensure you still throw NOT_FOUND and FORBIDDEN as
before.

---

Nitpick comments:
In `@components/project/activity/activity-detail-panel.tsx`:
- Around line 24-43: Extract the duplicated getActionIcon mapping into a shared
helper (e.g., export getActionIcon from a new module like activityIcons or
activityUI helper) and replace the inline function in both
activity-detail-panel.tsx and activity-feed.tsx with an import of that helper;
ensure the helper exports the same function signature and return shape (icon,
color, bg) so existing callers (getActionIcon in activity-detail-panel and the
duplicate in activity-feed) continue to work without other changes, then remove
the duplicated implementation from both files and update their imports to use
the shared helper.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98267f9 and e27740b.

📒 Files selected for processing (7)
  • components/project/activity/activity-detail-panel.tsx
  • components/project/activity/activity-feed.tsx
  • drizzle/schema.ts
  • drizzle/types.ts
  • lib/activity/index.ts
  • lib/activity/message.ts
  • trpc/routers/projects.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
components/project/activity/activity-detail-panel.tsx (1)

104-110: ⚠️ Potential issue | 🟡 Minor

Add an accessible name to the icon-only close button.

The close button still needs an aria-label so screen readers can announce its purpose.

♿ Suggested fix
 						<button
 							type="button"
 							onClick={onClose}
+							aria-label="Close activity details"
 							className="p-1 rounded-md hover:bg-muted transition-colors"
 						>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/project/activity/activity-detail-panel.tsx` around lines 104 -
110, The icon-only close <button> in ActivityDetailPanel needs an accessible
name: add an aria-label (e.g., aria-label="Close") to the button that invokes
onClose and mark the decorative <X /> icon as aria-hidden="true" so screen
readers announce the button purpose but ignore the visual icon; update the JSX
for the button containing onClick={onClose} and the <X /> element accordingly.
🧹 Nitpick comments (2)
lib/activity/index.ts (1)

45-48: Don’t silently swallow session enrichment failures.

The empty catch {} on Lines 45-48 hides real errors and makes audit enrichment failures hard to diagnose.

🛠️ Suggested improvement
-	} catch {}
+	} catch (error) {
+		console.warn("Failed to enrich activity metadata from session", { error });
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/activity/index.ts` around lines 45 - 48, The try/catch around
auth.api.getSession in lib/activity/index.ts silently swallows errors; change
the empty catch to handle failures by logging the error and context and
preserving normal flow: catch the exception thrown by auth.api.getSession, call
the existing logger (or create one) to record the error and the request
headers/context, and then set userEmail = null (as currently intended) so
enrichment failures are visible but non-fatal; reference the auth.api.getSession
call and the userEmail assignment when applying the fix.
lib/activity/message.ts (1)

148-188: Hoist event descriptions to module scope for reuse.

descriptions is rebuilt on every getEventDescription call. Move it to a top-level constant to reduce allocations and keep mapping data reusable.

♻️ Proposed refactor
+const eventDescriptions: Record<string, Record<string, string>> = {
+	task: {
+		created: "A new task was added to the project.",
+		updated: "An existing task was modified.",
+		deleted: "A task was removed from the project.",
+	},
+	// ...rest unchanged
+};
+
 export function getEventDescription(type: string, action: string): string {
-	const descriptions: Record<string, Record<string, string>> = {
-		// ...
-	};
-
-	return descriptions[type]?.[action] || `${typeLabels[type] || type} was ${action}.`;
+	return (
+		eventDescriptions[type]?.[action] ||
+		`${typeLabels[type] || type} was ${action}.`
+	);
 }

As per coding guidelines, "Write modular and reusable code; check if a component can be reused before creating a new one".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/activity/message.ts` around lines 148 - 188, The descriptions map is
currently recreated on every call inside getEventDescription; extract it to a
top-level constant (e.g., const EVENT_DESCRIPTIONS = { ... }) at module scope
and have getEventDescription reference EVENT_DESCRIPTIONS instead of the local
descriptions variable; ensure you keep the same keys and fallback logic with
typeLabels and update any imports/exports if needed so the mapping can be reused
across the module.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/activity/index.ts`:
- Around line 50-54: The activity metadata currently stores raw ipAddress,
userAgent and userEmail in the metadata object (see metadata and
activity.metadata); replace those raw values with privacy-safe alternatives: do
not persist userEmail or raw ipAddress/userAgent — instead persist either an
irreversible hash/fingerprint (e.g., hash(ipAddress) and hash(userAgent)) or
high-level anonymized attributes (e.g., country/region flag, deviceType, or a
boolean isInternalUser), and ensure any code that returns activity.metadata to
clients strips or redacts sensitive fields. Update all places that build
metadata (the metadata variable and the other occurrence referenced) and add a
small helper (e.g., anonymizeMetadata) to centralize hashing/redaction so
activity writers/readers use the sanitized values only.

---

Duplicate comments:
In `@components/project/activity/activity-detail-panel.tsx`:
- Around line 104-110: The icon-only close <button> in ActivityDetailPanel needs
an accessible name: add an aria-label (e.g., aria-label="Close") to the button
that invokes onClose and mark the decorative <X /> icon as aria-hidden="true" so
screen readers announce the button purpose but ignore the visual icon; update
the JSX for the button containing onClick={onClose} and the <X /> element
accordingly.

---

Nitpick comments:
In `@lib/activity/index.ts`:
- Around line 45-48: The try/catch around auth.api.getSession in
lib/activity/index.ts silently swallows errors; change the empty catch to handle
failures by logging the error and context and preserving normal flow: catch the
exception thrown by auth.api.getSession, call the existing logger (or create
one) to record the error and the request headers/context, and then set userEmail
= null (as currently intended) so enrichment failures are visible but non-fatal;
reference the auth.api.getSession call and the userEmail assignment when
applying the fix.

In `@lib/activity/message.ts`:
- Around line 148-188: The descriptions map is currently recreated on every call
inside getEventDescription; extract it to a top-level constant (e.g., const
EVENT_DESCRIPTIONS = { ... }) at module scope and have getEventDescription
reference EVENT_DESCRIPTIONS instead of the local descriptions variable; ensure
you keep the same keys and fallback logic with typeLabels and update any
imports/exports if needed so the mapping can be reused across the module.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e27740b and 0734056.

📒 Files selected for processing (5)
  • components/project/activity/activity-detail-panel.tsx
  • components/project/activity/activity-feed.tsx
  • lib/activity/index.ts
  • lib/activity/message.ts
  • lib/utils/date.ts

@arjunkomath arjunkomath merged commit 92d94bf into main Feb 28, 2026
4 checks passed
@arjunkomath arjunkomath deleted the feat/improve-audit-logs branch February 28, 2026 22:46
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