Skip to content

Comments

feat: Add support to audit and view audit log with Booking CREATED action#25468

Merged
hariombalhara merged 8 commits intomainfrom
booking-audit-viewer-demo
Dec 8, 2025
Merged

feat: Add support to audit and view audit log with Booking CREATED action#25468
hariombalhara merged 8 commits intomainfrom
booking-audit-viewer-demo

Conversation

@hariombalhara
Copy link
Member

@hariombalhara hariombalhara commented Nov 29, 2025

What does this PR do?

This PR introduces the booking audit infrastructure with a viewer service to fetch, enrich, and format audit logs. This is the first iteration that includes only the CREATED action with versioned schema

Note: Additional audit actions, UI improvements to match with Figma will be taken care of in followup PRs, to ensure the PR size remains as small as possible(as it is large enough already)

Demo - Loom - https://www.loom.com/share/22c2f38b052b480b8be3716e1608eaf6

Key Changes

New Booking Audit Package (packages/features/booking-audit/):

  • BookingAuditViewerService - Fetches, enriches, and formats audit logs for display
  • BookingAuditTaskConsumer - Processes audit tasks asynchronously via Tasker
  • CreatedAuditActionService (v1) - Handles RECORD_CREATED action with versioned schema

Repository Layer:

  • BookingAuditRepository - CRUD operations for audit records
  • AuditActorRepository - Actor management (users, guests, system)
  • UserRepository.findByUuid() - User lookup for actor enrichment

UI Components:

  • New page at /booking/logs/[bookinguid] for viewing audit history
  • Filterable audit log list with search, type, and actor filters
  • Expandable log entries showing detailed change information

Infrastructure:

  • bookingAudit Tasker task type for async processing
  • booking-audit feature flag (disabled by default)
  • tRPC endpoint viewer.bookings.getAuditLogs

Updates since last revision

  • Refactored Task class to TaskRepository: Converted all static methods to instance methods following the repository pattern. A singleton Task is exported for backward compatibility, so existing call sites continue to work unchanged.

Important Notes for Reviewers

  • Permission check is TODO: checkPermissions() in BookingAuditViewerService is not yet implemented
  • Only CREATED action supported: Other actions will throw an error - additional actions will be added iteratively in follow-up PRs
  • Feature flag controlled: System is behind booking-audit flag, disabled by default
  • UI strings need i18n: Some UI text in booking-logs-view.tsx may need translation

Human Review Checklist

  • Verify permission enforcement strategy for audit log access
  • Review DI wiring in module files
  • Confirm error handling in BookingAuditTaskConsumer is appropriate for retry scenarios
  • Check if UI strings need to be added to translation files
  • Verify TaskRepository refactoring maintains backward compatibility (singleton export Task should work for all existing call sites)

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. N/A - internal infrastructure
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

Run the integration tests:

TZ=UTC yarn test packages/features/booking-audit/lib/service/BookingAuditViewerService.integration-test.ts
TZ=UTC yarn test packages/features/booking-audit/lib/service/BookingAuditTaskConsumer.integration-test.ts

To test the UI:

  1. Enable the booking-audit feature flag in the database
  2. Create a booking (only CREATED action is supported in this PR)
  3. Navigate to /booking/logs/{bookingUid} to view the audit trail

Checklist

  • I have read the contributing guide
  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • I have checked if my changes generate no new warnings

@vercel
Copy link

vercel bot commented Nov 29, 2025

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

Project Deployment Preview Comments Updated (UTC)
cal-companion Ready Ready Preview Comment Dec 6, 2025 3:22am
2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Dec 6, 2025 3:22am
cal-eu Ignored Ignored Dec 6, 2025 3:22am

@keithwillcode keithwillcode added core area: core, team members only enterprise area: enterprise, audit log, organisation, SAML, SSO labels Nov 29, 2025
Copy link
Member Author

hariombalhara commented Nov 29, 2025

@hariombalhara hariombalhara force-pushed the booking-audit-viewer-demo branch 2 times, most recently from 06bfd36 to 2cb8518 Compare November 29, 2025 11:48
@hariombalhara hariombalhara force-pushed the booking-audit-viewer-demo branch from 2cb8518 to 4f2fba2 Compare November 29, 2025 12:13
@hariombalhara hariombalhara force-pushed the booking-audit-viewer-demo branch from 4f2fba2 to 27ea1e1 Compare November 29, 2025 12:33
@hariombalhara hariombalhara changed the title feat: Add booking audit infrastructure with action services and changeSchemas feat: Add BookingAuditViewerService with CREATED action only Nov 30, 2025
@hariombalhara hariombalhara force-pushed the booking-audit-viewer-demo branch from 27ea1e1 to 7b350fd Compare November 30, 2025 09:08
… features

- Introduced FilterSearchField and Select components for better search and filtering options in booking logs.
- Updated state management to handle multiple expanded log entries and JSON data visibility.
- Refactored action icon rendering to use string identifiers for icons instead of JSX elements.
- Improved layout and styling for audit log entries, including a timeline view and enhanced detail display.

These changes improve the user experience by providing more intuitive filtering and a clearer presentation of booking audit logs.
- Added TaskerService to encapsulate tasker functionality and provide a consistent interface for retrieving taskers.
- Updated BookingAuditProducerService to utilize TaskerService for tasker retrieval, enhancing dependency injection.
- Refactored imports in regular-booking.module.ts and bookings.ts to include the new TaskerService.

These changes streamline task management and improve the modularity of the booking audit system.
…mproved structure

- Introduced BookingLogsFilters and BookingLogsTimeline components to separate concerns and improve readability.
- Updated state management for search and filter functionality, allowing for better user interaction.
- Refactored the rendering of audit logs to enhance layout and detail presentation.
- Added type definitions for audit logs and filters to improve type safety.

These changes streamline the booking logs view, making it more modular and user-friendly.
volnei
volnei previously requested changes Dec 5, 2025
Copy link
Contributor

@volnei volnei left a comment

Choose a reason for hiding this comment

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

Let's avoid static methods for repositories 🙏🏼

@github-actions github-actions bot marked this pull request as draft December 5, 2025 17:22
joeauyeung
joeauyeung previously approved these changes Dec 5, 2025
Copy link
Contributor

@joeauyeung joeauyeung left a comment

Choose a reason for hiding this comment

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

Love the architecture here. This consumer and producer pattern, is this something we want to adopt throughout the whole codebase?

All my comments are non-blocking but can be addressed in a follow up.

Copy link
Contributor

Choose a reason for hiding this comment

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

Something to consider while we're working on this is to add a permission check before logging more sensitive data.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I have been thinking about it. So right now I haven;t implemented permission check at all.

I believe audit logs is initially for admins only and they need to see all the data.

Going forward we might need to show audit logs for regular members too, and there we might need to hide certain details.

}

// First try to find by email if email exists
if (normalizedEmail) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should handle the scenario if an actor has both an email and phone values. Right now it'll make 4 possible DB calls because of the two if statements

Copy link
Member Author

Choose a reason for hiding this comment

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

Let me think about how to handle it well.

* data: { startTime, endTime, status }
* });
*/
async queueAudit(bookingUid: string, actor: Actor, organizationId: number | null, actionData: BookingAuditTaskProducerActionData): Promise<void> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we also check to see if the feature flag is enabled here before pushing to the tasker? Would help prevent creating unnecessary tasks

Copy link
Member Author

Choose a reason for hiding this comment

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

I discussed this with Morgan. My suggestion was to have no impact on booking flow's performance at all. Checking feature flag for audit conflicts with that intention.

I was thinking that in future we anyway we would have audit logs enabled for all organizations, so it should be fine.

};
}

function buildBookingCreatedPayload({
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm hesitant about having this here in RegularBookingService it feels like it should live as a method in another service.

Copy link
Member Author

Choose a reason for hiding this comment

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

So, this is building all the data that is needed to fire an event(bookingCreated). This is usually the responsibility of the calling site itself because it knows where to get the data from and then only thing left is to properly place it as required by event handler signature. Type safety ensures they are passed correctly.

} else {
await bookingEventHandler.onBookingCreated(bookingCreatedPayload);
// TODO: We need to check session in booking flow and accordingly create USER actor if applicable.
const auditActor = makeGuestActor({ email: bookerEmail, name: fullName });
Copy link
Contributor

Choose a reason for hiding this comment

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

Likewise should this be handled in another service? Maybe the one on the line below? I want to avoid needing people to call these specific functions in different features.

Copy link
Member Author

@hariombalhara hariombalhara Dec 6, 2025

Choose a reason for hiding this comment

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

I am not so sure about this as well. If you look at this definition, it isn't doing much at all.
My reasoning was that everywhere one would need to define these actors, and it would be a lot of places. Either we pass the as a POJO or we have a well defined utility fn to create it, so that it is difficult to make mistakes

Screenshot 2025-12-06 at 8 31 34 AM

- Renamed Task class to TaskRepository
- Converted all static methods to instance methods
- Added Dependencies type with prismaClient injection
- Exported singleton 'Task' for backward compatibility
- All existing call sites continue to work unchanged

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 53 files

Prompt for AI agents (all 2 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="apps/web/app/(use-page-wrapper)/(main-nav)/booking/[uid]/logs/page.tsx">

<violation number="1" location="apps/web/app/(use-page-wrapper)/(main-nav)/booking/[uid]/logs/page.tsx:3">
P3: Typo in comment: &quot;TOOD&quot; should be &quot;TODO&quot;.</violation>
</file>

<file name="packages/features/booking-audit/lib/repository/PrismaAuditActorRepository.ts">

<violation number="1" location="packages/features/booking-audit/lib/repository/PrismaAuditActorRepository.ts:65">
P1: Potential unique constraint violation when email and phone belong to different existing records. The method finds by email first and updates with phone (or vice versa), but doesn&#39;t verify if the new phone/email is already used by a different record. Consider checking for conflicts before updating, or using a transaction to merge records when both identifiers exist on separate actors.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

@@ -0,0 +1,48 @@
// Added as a separate route for now to ease the testing of the audit logs feature
// It partially matches the figma design - https://www.figma.com/design/wleA2SR6rn60EK7ORxAfMy/Cal.com-New-Features?node-id=5641-6732&p=f
// TOOD: Move it to the booking page side bar later
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 6, 2025

Choose a reason for hiding this comment

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

P3: Typo in comment: "TOOD" should be "TODO".

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/(use-page-wrapper)/(main-nav)/booking/[uid]/logs/page.tsx, line 3:

<comment>Typo in comment: &quot;TOOD&quot; should be &quot;TODO&quot;.</comment>

<file context>
@@ -0,0 +1,48 @@
+// Added as a separate route for now to ease the testing of the audit logs feature
+// It partially matches the figma design - https://www.figma.com/design/wleA2SR6rn60EK7ORxAfMy/Cal.com-New-Features?node-id=5641-6732&amp;p=f
+// TOOD: Move it to the booking page side bar later 
+import { ShellMainAppDir } from &quot;app/(use-page-wrapper)/(main-nav)/ShellMainAppDir&quot;;
+import type { PageProps } from &quot;app/_types&quot;;
</file context>
Suggested change
// TOOD: Move it to the booking page side bar later
// TODO: Move it to the booking page side bar later
Fix with Cubic


if (existingByEmail) {
// Update existing record found by email
return this.deps.prismaClient.auditActor.update({
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Dec 6, 2025

Choose a reason for hiding this comment

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

P1: Potential unique constraint violation when email and phone belong to different existing records. The method finds by email first and updates with phone (or vice versa), but doesn't verify if the new phone/email is already used by a different record. Consider checking for conflicts before updating, or using a transaction to merge records when both identifiers exist on separate actors.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/features/booking-audit/lib/repository/PrismaAuditActorRepository.ts, line 65:

<comment>Potential unique constraint violation when email and phone belong to different existing records. The method finds by email first and updates with phone (or vice versa), but doesn&#39;t verify if the new phone/email is already used by a different record. Consider checking for conflicts before updating, or using a transaction to merge records when both identifiers exist on separate actors.</comment>

<file context>
@@ -25,5 +25,87 @@ export class PrismaAuditActorRepository implements IAuditActorRepository {
+
+            if (existingByEmail) {
+                // Update existing record found by email
+                return this.deps.prismaClient.auditActor.update({
+                    where: { email: normalizedEmail },
+                    data: {
</file context>
Fix with Cubic

Copy link
Contributor

@ThyMinimalDev ThyMinimalDev left a comment

Choose a reason for hiding this comment

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

API v2 side looks good

@hariombalhara hariombalhara merged commit 0fc26f7 into main Dec 8, 2025
42 of 45 checks passed
@hariombalhara hariombalhara deleted the booking-audit-viewer-demo branch December 8, 2025 12:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core area: core, team members only enterprise area: enterprise, audit log, organisation, SAML, SSO ❗️ migrations contains migration files ready-for-e2e size/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants