Skip to content

ECHO-565 add audit logging#358

Merged
spashii merged 3 commits intomainfrom
ECHO-565-audit-logs
Nov 13, 2025
Merged

ECHO-565 add audit logging#358
spashii merged 3 commits intomainfrom
ECHO-565-audit-logs

Conversation

@spashii
Copy link
Copy Markdown
Member

@spashii spashii commented Nov 13, 2025

Note

Adds a Settings audit logs viewer with filterable, paginated results and CSV/JSON export, and streamlines the Two‑Factor settings layout.

  • Settings UI
    • Audit Logs: New AuditLogsCard in routes/settings/UserSettingsRoute.tsx displaying Directus activities with filtering (action, collection), pagination, refresh, and export to CSV/JSON.
    • Two‑Factor Auth: Simplifies header/layout and shows "Recommended apps" only when 2FA is disabled in TwoFactorSettingsCard.tsx.
  • Data Layer
    • Hooks: Adds useAuditLogsQuery.ts with types, paginated fetch via readActivities, metadata aggregations, and export utilities; re-exported in hooks/index.ts.

Written by Cursor Bugbot for commit ce4a992. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features

    • Added a comprehensive Audit Logs viewer in Settings with filters (action, collection), pagination, refresh, and CSV/JSON export.
    • Audit entries show action, timestamp, collection, target, actor, IP, and user agent.
  • UI/UX Improvements

    • Two‑Factor Settings header/layout refined; "Recommended apps" shown only when 2FA is disabled.
    • Audit Logs integrated into the user settings page.

@linear
Copy link
Copy Markdown

linear bot commented Nov 13, 2025

ECHO-565 Audit Logs

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Nov 13, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a full audit-logs feature to the settings UI: new AuditLogsCard component, supporting hooks for querying, metadata, and export (CSV/JSON), integrated into the user settings route; also a small TwoFactorSettingsCard header/layout adjustment. LGTM.

Changes

Cohort / File(s) Summary
Audit logs UI
echo/frontend/src/components/settings/AuditLogsCard.tsx
New React component rendering audit logs table with filters (action, collection), pagination, loading/error/empty states, export controls (CSV/JSON), and data formatting/UX (tooltips, badges, accessible labels).
Audit logs hooks & export logic
echo/frontend/src/components/settings/hooks/useAuditLogsQuery.ts
New hook module: domain types for audit logs, paginated fetch (fetchAuditLogsPage), metadata/options fetch, batched export fetching (fetchAuditLogsForExport), CSV/JSON formatting, and three exported hooks: useAuditLogsQuery, useAuditLogMetadata, useAuditLogsExport.
Hooks re-export
echo/frontend/src/components/settings/hooks/index.ts
Re-exports new audit logs hook(s) (export * from "./useAuditLogsQuery").
Settings route integration
echo/frontend/src/routes/settings/UserSettingsRoute.tsx
Imports and renders AuditLogsCard in the user settings page with surrounding dividers.
Two-factor settings layout
echo/frontend/src/components/settings/TwoFactorSettingsCard.tsx
Adjusts header composition (Stack/Group layout) and conditionally shows "Recommended apps" only when 2FA is disabled.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Areas needing extra attention:
    • useAuditLogsQuery.ts: pagination, batching/export streaming, types and null/edge-case handling.
    • AuditLogsCard.tsx: pagination state sync, export blob generation/download, and UI conditional branches (loading/error/empty).
    • Integration in UserSettingsRoute.tsx: ensure styling/layout parity and no regressions with surrounding components.

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title clearly summarizes the main change: adding audit logging functionality to the application.
Linked Issues check ✅ Passed PR implements audit logs card with filtering, pagination, and export as indicated by ECHO-565 issue objective.
Out of Scope Changes check ✅ Passed All changes are in-scope: audit logs feature, hooks, UI components, and integration into settings route align with audit logging objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ECHO-565-audit-logs

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 97c56bf and ce4a992.

📒 Files selected for processing (1)
  • echo/frontend/src/components/settings/hooks/useAuditLogsQuery.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-19T10:22:55.323Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 266
File: echo/frontend/src/components/conversation/ConversationAccordion.tsx:675-678
Timestamp: 2025-08-19T10:22:55.323Z
Learning: In echo/frontend/src/components/conversation/hooks/index.ts, the useConversationsCountByProjectId hook uses regular useQuery (not useSuspenseQuery), which means conversationsCountQuery.data can be undefined during loading states. When using Number(conversationsCountQuery.data) ?? 0, this creates NaN because Number(undefined) = NaN and NaN is not nullish, so the fallback doesn't apply. The correct pattern is Number(conversationsCountQuery.data ?? 0) to ensure the fallback happens before type conversion.

Applied to files:

  • echo/frontend/src/components/settings/hooks/useAuditLogsQuery.ts
📚 Learning: 2025-10-24T08:08:23.128Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 349
File: echo/frontend/src/components/report/hooks/index.ts:51-71
Timestamp: 2025-10-24T08:08:23.128Z
Learning: In echo/frontend/src/components/report/hooks/index.ts, the useGetProjectParticipants hook uses Directus aggregate which always returns count as a string (e.g., "0", "1", "2"). The pattern `Number.parseInt(result[0]?.count ?? "0", 10) || 0` is the correct way to handle this, with the string fallback "0" ensuring parseInt receives a valid string input.
<!--

Applied to files:

  • echo/frontend/src/components/settings/hooks/useAuditLogsQuery.ts
📚 Learning: 2025-08-19T10:22:55.323Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 266
File: echo/frontend/src/components/conversation/ConversationAccordion.tsx:675-678
Timestamp: 2025-08-19T10:22:55.323Z
Learning: In echo/frontend/src/components/conversation/hooks/index.ts, the useConversationsCountByProjectId hook uses useSuspenseQuery with Directus aggregate, which always returns string numbers like "0", "1", "2" and suspends during loading instead of returning undefined. Therefore, Number(conversationsCountQuery.data) ?? 0 is safe and the Number() conversion is necessary for type conversion from string to number.

Applied to files:

  • echo/frontend/src/components/settings/hooks/useAuditLogsQuery.ts
🧬 Code graph analysis (1)
echo/frontend/src/components/settings/hooks/useAuditLogsQuery.ts (2)
echo/frontend/src/lib/typesDirectusContent.ts (2)
  • CustomDirectusTypes (1127-1234)
  • DirectusActivity (386-398)
echo/frontend/src/lib/directus.ts (1)
  • directus (6-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ci-check-server
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (10)
echo/frontend/src/components/settings/hooks/useAuditLogsQuery.ts (10)

1-3: Clean imports.

All necessary dependencies properly scoped. Standard React Query + Directus SDK pattern.


5-60: Solid type definitions.

Domain models are well-structured with proper null handling. Export types clearly separate concerns between format, args, and result.


62-75: Good constant definitions.

Field selection properly includes nested user data. Batch size of 500 is solid for export operations without overwhelming the API.


77-85: Type aliases handle Directus quirks correctly.

ActivityResponse properly models the extended array with metadata. filter_count typed as number | string | null aligns with Directus behavior.


87-109: Filter construction logic is clean.

Proper use of _in operator for array filters. Returns undefined when no filters to keep queries clean.


111-126: normalizeCount handles Directus count variations correctly.

Properly parses string counts from Directus aggregate (per learnings), handles nested objects, and safely falls back to 0 for unparseable values.


194-214: toOptions helper cleanly filters and transforms aggregate results.

Properly handles null values, empty strings, and normalizes counts from Directus aggregate format.


222-256: Batched export logic is solid.

Intentional infinite loop with explicit break is clean. Batches at 500 records to avoid overwhelming the API. eslint-disable is appropriate here.


258-311: Export formatting is production-ready.

CSV escaping follows RFC 4180 (quote doubling, field wrapping). User fallback gracefully handles missing data. MIME types are correct for both formats.


313-358: React Query integration is chef's kiss.

keepPreviousData prevents pagination flicker. Metadata staleTime balances freshness vs performance. Export as mutation is the right pattern. Query keys properly namespaced.


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
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on December 5

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.


return {
items,
total: normalizedTotal > 0 ? normalizedTotal : page * pageSize + items.length,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Pagination Error Hides Available Content

The fallback total calculation page * pageSize + items.length produces incorrect results for pagination. When on page 1 (second page) with 25 items per page and 25 items returned, it calculates 50 total items, suggesting only 2 pages exist. If additional pages exist beyond this, users cannot navigate to them because the pagination component receives an incorrect total page count.

Fix in Cursor Fix in Web

Copy link
Copy Markdown
Contributor

@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: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between dd32f45 and 97c56bf.

📒 Files selected for processing (5)
  • echo/frontend/src/components/settings/AuditLogsCard.tsx (1 hunks)
  • echo/frontend/src/components/settings/TwoFactorSettingsCard.tsx (2 hunks)
  • echo/frontend/src/components/settings/hooks/index.ts (1 hunks)
  • echo/frontend/src/components/settings/hooks/useAuditLogsQuery.ts (1 hunks)
  • echo/frontend/src/routes/settings/UserSettingsRoute.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-08T10:39:31.114Z
Learnt from: ussaama
Repo: Dembrane/echo PR: 259
File: echo/frontend/src/components/layout/ParticipantLayout.tsx:33-33
Timestamp: 2025-08-08T10:39:31.114Z
Learning: In echo/frontend/src/components/layout/ParticipantLayout.tsx, prefer using simple pathname.includes("start") and pathname.includes("finish") to control the settings button visibility. No need to switch to segment-based matching or add a useEffect to auto-close the modal for these routes, per ussaama’s preference in PR #259.

Applied to files:

  • echo/frontend/src/routes/settings/UserSettingsRoute.tsx
  • echo/frontend/src/components/settings/TwoFactorSettingsCard.tsx
🧬 Code graph analysis (3)
echo/frontend/src/routes/settings/UserSettingsRoute.tsx (1)
echo/frontend/src/components/settings/AuditLogsCard.tsx (1)
  • AuditLogsCard (75-469)
echo/frontend/src/components/settings/AuditLogsCard.tsx (2)
echo/frontend/src/components/settings/hooks/useAuditLogsQuery.ts (7)
  • AuditLogEntry (12-21)
  • AuditLogOption (39-43)
  • AuditLogFilters (23-26)
  • useAuditLogsQuery (309-320)
  • useAuditLogMetadata (322-333)
  • useAuditLogsExport (335-354)
  • AuditLogExportFormat (50-50)
echo/frontend/src/components/common/Toaster.tsx (1)
  • toast (34-34)
echo/frontend/src/components/settings/hooks/useAuditLogsQuery.ts (1)
echo/frontend/src/lib/directus.ts (1)
  • directus (6-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: ci-check-server
🔇 Additional comments (2)
echo/frontend/src/components/settings/TwoFactorSettingsCard.tsx (2)

284-298: LGTM - clean layout refactor.

Nesting the icon/title in a Group within a Stack and pulling the description text out as a sibling is solid structural improvement. Better spacing control, cleaner component hierarchy. Ships.


315-337: LGTM - smart UX decision.

Hiding the recommended apps list when 2FA is already enabled is the right call. Users don't need authenticator app suggestions once they're already set up. Clean conditional logic, proper fragment usage. This is the way.

Comment on lines +155 to +159
return {
items,
total: normalizedTotal > 0 ? normalizedTotal : page * pageSize + items.length,
};
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep pagination working without filter_count.

If Directus runs with QUERY_TRACK_TOTAL=false or the role can’t read counts, response.meta.filter_count comes back undefined. Our fallback collapses total to the current page slice, so Mantine renders a single page and you can never reach the next batch even though the API still has data. Harden the fallback so a completely filled page still advertises the next page.

@@
-	const normalizedTotal = normalizeCount(metaTotal);
-
-	return {
-		items,
-		total:
-			normalizedTotal > 0 ? normalizedTotal : page * pageSize + items.length,
-	};
+	const normalizedTotal = normalizeCount(metaTotal);
+	const fallbackTotal =
+		items.length < pageSize
+			? page * pageSize + items.length
+			: (page + 1) * pageSize + 1;
+
+	return {
+		items,
+		total: normalizedTotal > 0 ? normalizedTotal : fallbackTotal,
+	};

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In echo/frontend/src/components/settings/hooks/useAuditLogsQuery.ts around lines
155 to 159, the fallback for total when response.meta.filter_count is missing
collapses pagination; change the fallback so that if filter_count is undefined
and the returned items length equals pageSize (a full page) you add 1 to the
computed total to indicate there is at least one more page, otherwise keep total
as page * pageSize + items.length; implement this conditional fallback so
Mantine can render the next page when counts are unavailable.

Comment on lines +170 to +191
aggregate: {
count: "*",
},
groupBy: ["action"],
sort: ["action"],
},
),
requestActivities<
Array<{
collection: string | null;
count: number;
}>
>(
{
aggregate: {
count: "*",
},
groupBy: ["collection"],
sort: ["collection"],
},
),
]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Stop truncating filter metadata.

Directus caps grouped queries at 100 rows unless we override limit. Tenants with more than 100 collections/actions will silently lose everything past the cutoff, so the dropdown can’t target those logs. Force limit: -1 on both aggregates so we always fetch the full universe.

@@
 			aggregate: {
 				count: "*",
 			},
 			groupBy: ["action"],
 			sort: ["action"],
+			limit: -1,
 		},
@@
 			aggregate: {
 				count: "*",
 			},
 			groupBy: ["collection"],
 			sort: ["collection"],
+			limit: -1,
 		},
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
aggregate: {
count: "*",
},
groupBy: ["action"],
sort: ["action"],
},
),
requestActivities<
Array<{
collection: string | null;
count: number;
}>
>(
{
aggregate: {
count: "*",
},
groupBy: ["collection"],
sort: ["collection"],
},
),
]);
aggregate: {
count: "*",
},
groupBy: ["action"],
sort: ["action"],
limit: -1,
},
),
requestActivities<
Array<{
collection: string | null;
count: number;
}>
>(
{
aggregate: {
count: "*",
},
groupBy: ["collection"],
sort: ["collection"],
limit: -1,
},
),
🤖 Prompt for AI Agents
In echo/frontend/src/components/settings/hooks/useAuditLogsQuery.ts around lines
170 to 191, Directus grouped aggregate queries are being truncated at the
default 100-row cap; add a limit: -1 to both aggregate request objects (the one
grouping by "action" and the one grouping by "collection") so the queries return
the full result set instead of only the first 100 rows. Ensure the limit: -1
property is placed at the top level of each request payload alongside
aggregate/groupBy/sort.

@spashii spashii added this pull request to the merge queue Nov 13, 2025
Merged via the queue into main with commit e9ed8ed Nov 13, 2025
12 checks passed
@spashii spashii deleted the ECHO-565-audit-logs branch November 13, 2025 13:21
spashii added a commit that referenced this pull request Nov 18, 2025
<!-- CURSOR_SUMMARY -->
> [!NOTE]
> Adds a Settings audit logs viewer with filterable, paginated results
and CSV/JSON export, and streamlines the Two‑Factor settings layout.
> 
> - **Settings UI**
> - **Audit Logs**: New `AuditLogsCard` in
`routes/settings/UserSettingsRoute.tsx` displaying Directus activities
with filtering (`action`, `collection`), pagination, refresh, and export
to `CSV`/`JSON`.
> - **Two‑Factor Auth**: Simplifies header/layout and shows "Recommended
apps" only when 2FA is disabled in `TwoFactorSettingsCard.tsx`.
> - **Data Layer**
> - **Hooks**: Adds `useAuditLogsQuery.ts` with types, paginated fetch
via `readActivities`, metadata aggregations, and export utilities;
re-exported in `hooks/index.ts`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
ce4a992. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a comprehensive Audit Logs viewer in Settings with filters
(action, collection), pagination, refresh, and CSV/JSON export.
* Audit entries show action, timestamp, collection, target, actor, IP,
and user agent.

* **UI/UX Improvements**
* Two‑Factor Settings header/layout refined; "Recommended apps" shown
only when 2FA is disabled.
  * Audit Logs integrated into the user settings page.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@coderabbitai coderabbitai bot mentioned this pull request Nov 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants