-
Notifications
You must be signed in to change notification settings - Fork 543
[Dashboard] Add audit log feature for Scale plan teams #7300
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Dashboard] Add audit log feature for Scale plan teams #7300
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
|
""" WalkthroughThis update introduces a complete "Audit Log" feature to the team dashboard. It adds API integration, React components for listing and displaying audit log entries, pagination, and a conditional upsell wrapper that restricts access based on billing plan. Navigation is updated to include the new audit log tab, and supporting utilities for search parameter handling are added. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI (AuditLog Page)
participant API (getAuditLogs)
participant Server
User->>UI (AuditLog Page): Navigates to Audit Log tab
UI (AuditLog Page)->>API (getAuditLogs): Calls getAuditLogs(teamSlug, cursor?)
API (getAuditLogs)->>Server: Fetch /api/teams/{teamSlug}/audit-log?after={cursor}
Server-->>API (getAuditLogs): Returns audit log data or error
API (getAuditLogs)-->>UI (AuditLog Page): Returns parsed data or error
UI (AuditLog Page)->>UI (AuditLogList): Renders entries and pagination
User->>UI (AuditLogList): Clicks Next/Previous for pagination
UI (AuditLogList)->>API (getAuditLogs): Fetches next/previous page using cursor
sequenceDiagram
participant UI (AuditLog Page)
participant UpsellWrapper
UI (AuditLog Page)->>UpsellWrapper: Render audit log content
UpsellWrapper-->>UI (AuditLog Page): If currentPlan < requiredPlan, show upsell overlay
UpsellWrapper-->>UI (AuditLog Page): Else, show children (audit log)
Suggested reviewers
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (10)
💤 Files with no reviewable changes (1)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (7)
⏰ Context from checks skipped due to timeout of 90000ms (8)
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #7300 +/- ##
=======================================
Coverage 55.57% 55.57%
=======================================
Files 909 909
Lines 58673 58673
Branches 4158 4158
=======================================
Hits 32607 32607
Misses 25959 25959
Partials 107 107
🚀 New features to boost your workflow:
|
09e4663
to
960019a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Caution
Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 6
🧹 Nitpick comments (5)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/audit-log/layout.tsx (2)
12-15
: Consider more specific redirect path.While the error handling is good, redirecting to
/team
might be too generic. Consider redirecting to a more specific path like/team/${params.team_slug}
to maintain context, or handle the case where the team slug itself might be invalid.
18-23
: Billing plan gating logic is correct, but consider simplifying optional chaining.The billing plan logic correctly unlocks the feature for both "scale" and "pro" plans. However, since you already verify
team
exists above, the optional chaining inteam?.billingPlan
on line 23 is unnecessary.- currentPlan={team?.billingPlan} + currentPlan={team.billingPlan}apps/dashboard/src/@/api/audit-log.ts (2)
57-58
: Consider making page size configurable.The page size is hard-coded to 15. Consider making this configurable through environment variables or function parameters for better flexibility.
+const DEFAULT_PAGE_SIZE = process.env.AUDIT_LOG_PAGE_SIZE || "15"; + export async function getAuditLogs(teamSlug: string, cursor?: string) { // ... existing code ... - // artifically limit page size to 15 for now - url.searchParams.set("take", "15"); + url.searchParams.set("take", DEFAULT_PAGE_SIZE);
86-86
: Consider adding runtime type validation for API responses.The response is type-cast without validation. Consider adding runtime validation to ensure data integrity.
- const data = (await response.json()) as AuditLogApiResponse; + const responseData = await response.json(); + // Add validation here if needed + const data = responseData as AuditLogApiResponse;apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/audit-log/_components/entry.tsx (1)
80-83
: Consider making wallet address masking configurable.The wallet address masking (6 characters + 4 characters) is hardcoded. Consider making this configurable for different address formats.
+const maskWallet = (wallet: string, prefixLength = 6, suffixLength = 4) => + `${wallet.slice(0, prefixLength)}...${wallet.slice(-suffixLength)}`; + {entry.who.metadata?.wallet && ( <span className="font-mono"> - {entry.who.metadata.wallet.slice(0, 6)}... - {entry.who.metadata.wallet.slice(-4)} + {maskWallet(entry.who.metadata.wallet)} </span> )}
🛑 Comments failed to post (6)
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/audit-log/page.tsx (1)
19-19:
⚠️ Potential issueFix the redirect path for authentication validation.
The
getValidAccount
call uses a hardcoded redirect path/team/${params.team_slug}/~/usage
that doesn't match the current audit log route.- getValidAccount(`/team/${params.team_slug}/~/usage`), + getValidAccount(`/team/${params.team_slug}/~/audit-log`),🤖 Prompt for AI Agents
In apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/audit-log/page.tsx at line 19, the redirect path in the getValidAccount call is incorrectly set to /team/${params.team_slug}/~/usage, which does not correspond to the current audit log route. Update the redirect path to match the audit log route, likely /team/${params.team_slug}/~/audit-log, to ensure proper authentication validation and redirection.
apps/dashboard/src/@/api/audit-log.ts (1)
77-78: 🛠️ Refactor suggestion
Replace console.log with proper error logging.
Using
console.log
in production code is not ideal for error tracking and debugging.- const body = await response.text(); - console.log(body); + const body = await response.text(); + console.error("Audit log API error:", { status: response.status, body });📝 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.const body = await response.text(); console.error("Audit log API error:", { status: response.status, body });
🤖 Prompt for AI Agents
In apps/dashboard/src/@/api/audit-log.ts at lines 77 to 78, replace the console.log statement used for outputting the response body with a proper error logging mechanism. Identify the appropriate logger used in the project (e.g., a logging library or a custom logger) and use it to log the response body, ensuring the log level is suitable for error or debug information instead of using console.log.
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/audit-log/_components/entry.tsx (1)
99-99: 🛠️ Refactor suggestion
Add error handling for date parsing.
The
formatDistanceToNow
function could fail ifentry.when
is not a valid date string.- <span className="whitespace-nowrap text-muted-foreground text-xs"> - {formatDistanceToNow(entry.when, { addSuffix: true })} - </span> + <span className="whitespace-nowrap text-muted-foreground text-xs"> + {(() => { + try { + return formatDistanceToNow(new Date(entry.when), { addSuffix: true }); + } catch { + return entry.when; + } + })()} + </span>📝 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.<span className="whitespace-nowrap text-muted-foreground text-xs"> {(() => { try { return formatDistanceToNow(new Date(entry.when), { addSuffix: true }); } catch { return entry.when; } })()} </span>
🤖 Prompt for AI Agents
In apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/audit-log/_components/entry.tsx at line 99, the call to formatDistanceToNow(entry.when, { addSuffix: true }) may throw an error if entry.when is not a valid date. To fix this, add error handling by validating or safely parsing entry.when before passing it to formatDistanceToNow. If the date is invalid, display a fallback string or handle the error gracefully to prevent the component from crashing.
apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/audit-log/_components/list.tsx (2)
87-89: 🛠️ Refactor suggestion
Potential key collision in buildAuditLogEntryKey function.
The key generation might not be unique enough if multiple entries have the same type, action, path, and timestamp. Consider including more unique identifiers.
function buildAuditLogEntryKey(entry: AuditLogEntry) { - return `${entry.who.type}-${entry.what.action}-${entry.what.path ?? ""}-${entry.when}`; + return `${entry.who.type}-${entry.what.action}-${entry.what.path ?? ""}-${entry.when}-${entry.who.text}-${entry.what.resourceType}`; }📝 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.function buildAuditLogEntryKey(entry: AuditLogEntry) { return `${entry.who.type}-${entry.what.action}-${entry.what.path ?? ""}-${entry.when}-${entry.who.text}-${entry.what.resourceType}`; }
🤖 Prompt for AI Agents
In apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/audit-log/_components/list.tsx around lines 87 to 89, the buildAuditLogEntryKey function generates keys that may not be unique if entries share the same type, action, path, and timestamp. To fix this, enhance the key by including additional unique identifiers from the AuditLogEntry object, such as a unique ID or a combination of other distinct properties, ensuring each key is truly unique to prevent collisions.
51-53: 🛠️ Refactor suggestion
Previous button navigation may be unreliable with cursor pagination.
Using
window.history.back()
for the previous button might not work correctly with cursor-based pagination, especially if users navigate directly to a paginated URL or refresh the page.Consider implementing a proper previous cursor or using a different approach:
<Button variant="outline" size="sm" onClick={() => { - window.history.back(); + setAfter(null); // Go back to first page }} disabled={isPending || !after} >Alternatively, implement a stack-based approach to track previous cursors.
📝 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.<Button variant="outline" size="sm" onClick={() => { setAfter(null); // Go back to first page }} disabled={isPending || !after} > Previous </Button>
🤖 Prompt for AI Agents
In apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/audit-log/_components/list.tsx around lines 51 to 53, replace the use of window.history.back() for the previous button because it can be unreliable with cursor pagination. Instead, implement navigation using the stored previous cursor value to fetch the correct previous page of data, or maintain a stack of cursors to track navigation history and use that to determine the previous page. Update the onClick handler to use this cursor-based navigation logic rather than relying on browser history.
apps/dashboard/src/@/components/blocks/upsell-wrapper.tsx (1)
70-74:
⚠️ Potential issueFix hardcoded plan value to use dynamic prop.
The
TeamPlanBadge
uses a hardcoded "scale" plan, but the component receivesrequiredPlan
as a prop. This creates an inconsistency where the badge might show a different plan than what's actually required.<TeamPlanBadge - plan="scale" + plan={requiredPlan} teamSlug={teamSlug} postfix=" Feature" />📝 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.<TeamPlanBadge plan={requiredPlan} teamSlug={teamSlug} postfix=" Feature" />
🤖 Prompt for AI Agents
In apps/dashboard/src/@/components/blocks/upsell-wrapper.tsx around lines 70 to 74, replace the hardcoded "scale" string passed as the plan prop to TeamPlanBadge with the dynamic requiredPlan prop received by the component. This ensures the badge correctly reflects the actual required plan instead of always showing "scale".
size-limit report 📦
|
960019a
to
ea469c9
Compare
ea469c9
to
21759c3
Compare
Add Audit Log Feature for Scale Plan
This PR adds a new Audit Log feature for teams on the Scale plan, allowing them to monitor and review actions performed within their team.
Features
Implementation Details
The feature is accessible via the team navigation sidebar and requires the Scale plan to use.
Summary by CodeRabbit
New Features
Bug Fixes
Other Changes
PR-Codex overview
This PR focuses on implementing an
Audit Log
feature for team management, allowing users to monitor and review actions performed within their teams. It includes new components, API integrations, and layout modifications to support this functionality.Detailed summary
"chainsaw"
from theChainService
type inchain.ts
.Audit Log
entry in thelayout.tsx
file.searchParams
andsearchParamLoader
insearch-params.ts
.Layout
component to handle team verification and display.Page
component to fetch and display audit logs.AuditLogList
andAuditLogEntryComponent
for rendering logs.UpsellWrapper
component for feature promotion based on billing plans.audit-log.ts
.