Skip to content

Commit 75d611c

Browse files
chore: Integrate creation/rescheduling booking audit for Recurring/regular booking/seated bookings (#26046)
* Integrate creation/rescheduling booking audit * fix: add missing hostUserUuid to booking audit test data Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix-ci * feat: enhance booking audit with seat reference - Added support for seat reference in booking audit actions. - Updated localization for booking creation to include seat information. - Modified relevant services to pass attendee seat ID during booking creation. * fix: update test data to match schema requirements - Add seatReferenceUid: null to default mock audit log data - Add seatReferenceUid: null to multiple audit logs test case - Convert SEAT_RESCHEDULED test data to use numeric timestamps instead of ISO strings Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * Allow nullish seatReferenceUid * feat: enhance booking audit to support rescheduledBy information - Updated booking audit actions to include rescheduledBy details, allowing tracking of who rescheduled a booking. - Refactored related services to accommodate the new rescheduledBy parameter in booking events. - Adjusted type definitions and function signatures to reflect the changes in the booking audit context. * Avoid possible run time issue * Fix imoport path * fix failing test due to merge from main\ * Pass useruuid --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 0408ce0 commit 75d611c

36 files changed

+1198
-453
lines changed

apps/api/v2/src/ee/bookings/2024-04-15/controllers/bookings.controller.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ export class BookingsController_2024_04_15 {
349349
platformBookingLocation: bookingRequest.platformBookingLocation,
350350
noEmail: bookingRequest.body.noEmail,
351351
},
352+
creationSource: "API_V2",
352353
});
353354

354355
createdBookings.forEach(async (booking) => {

apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import { BadRequestException } from "@nestjs/common";
3737
import { Request } from "express";
3838
import { DateTime } from "luxon";
3939
import { z } from "zod";
40-
4140
export const BOOKING_REASSIGN_PERMISSION_ERROR = "You do not have permission to reassign this booking";
4241

4342
import {
@@ -116,7 +115,7 @@ export class BookingsService_2024_08_13 {
116115
private readonly recurringBookingService: RecurringBookingService,
117116
private readonly instantBookingCreateService: InstantBookingCreateService,
118117
private readonly eventTypeAccessService: EventTypeAccessService
119-
) {}
118+
) { }
120119

121120
async createBooking(request: Request, body: CreateBookingInput, authUser: AuthOptionalUser) {
122121
let bookingTeamEventType = false;
@@ -322,8 +321,7 @@ export class BookingsService_2024_08_13 {
322321
const allowedOptionValues = eventTypeBookingField.options.map((opt) => opt.value);
323322
if (!this.isValidSingleOptionValue(submittedValue, allowedOptionValues)) {
324323
throw new BadRequestException(
325-
`Invalid option '${submittedValue}' for booking field '${
326-
eventTypeBookingField.name
324+
`Invalid option '${submittedValue}' for booking field '${eventTypeBookingField.name
327325
}'. Allowed options are: ${allowedOptionValues.join(", ")}.`
328326
);
329327
}
@@ -337,8 +335,7 @@ export class BookingsService_2024_08_13 {
337335
const allowedOptionValues = eventTypeBookingField.options.map((opt) => opt.value);
338336
if (!this.areValidMultipleOptionValues(submittedValues, allowedOptionValues)) {
339337
throw new BadRequestException(
340-
`One or more invalid options for booking field '${
341-
eventTypeBookingField.name
338+
`One or more invalid options for booking field '${eventTypeBookingField.name
342339
}'. Allowed options are: ${allowedOptionValues.join(", ")}.`
343340
);
344341
}
@@ -352,8 +349,7 @@ export class BookingsService_2024_08_13 {
352349
const allowedOptionValues = eventTypeBookingField.options.map((opt) => opt.value);
353350
if (!this.areValidMultipleOptionValues(submittedValues, allowedOptionValues)) {
354351
throw new BadRequestException(
355-
`One or more invalid options for booking field '${
356-
eventTypeBookingField.name
352+
`One or more invalid options for booking field '${eventTypeBookingField.name
357353
}'. Allowed options are: ${allowedOptionValues.join(", ")}.`
358354
);
359355
}
@@ -368,8 +364,7 @@ export class BookingsService_2024_08_13 {
368364
const allowedOptionValues = eventTypeBookingField.options.map((opt) => opt.value);
369365
if (!this.isValidSingleOptionValue(submittedValue, allowedOptionValues)) {
370366
throw new BadRequestException(
371-
`Invalid option '${submittedValue}' for booking field '${
372-
eventTypeBookingField.name
367+
`Invalid option '${submittedValue}' for booking field '${eventTypeBookingField.name
373368
}'. Allowed options are: ${allowedOptionValues.join(", ")}.`
374369
);
375370
}
@@ -465,6 +460,7 @@ export class BookingsService_2024_08_13 {
465460
noEmail: bookingRequest.noEmail,
466461
areCalendarEventsEnabled: bookingRequest.areCalendarEventsEnabled,
467462
},
463+
creationSource: "API_V2",
468464
});
469465
const ids = bookings.map((booking) => booking.id || 0);
470466
return this.outputService.getOutputRecurringBookings(ids);
@@ -489,6 +485,7 @@ export class BookingsService_2024_08_13 {
489485
platformBookingLocation: bookingRequest.platformBookingLocation,
490486
areCalendarEventsEnabled: bookingRequest.areCalendarEventsEnabled,
491487
},
488+
creationSource: "API_V2",
492489
});
493490
return this.outputService.getOutputCreateRecurringSeatedBookings(
494491
bookings.map((booking) => ({ uid: booking.uid || "", seatUid: booking.seatReferenceUid || "" })),
Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
11
import { RegularBookingModule } from "@/lib/modules/regular-booking.module";
22
import { RecurringBookingService } from "@/lib/services/recurring-booking.service";
33
import { Module } from "@nestjs/common";
4-
4+
import { BookingEventHandlerService } from "@/lib/services/booking-event-handler.service";
5+
import { Logger } from "@/lib/logger.bridge";
6+
import { Scope } from "@nestjs/common";
7+
import { BookingAuditProducerService } from "@/lib/services/booking-audit-producer.service";
8+
import { HashedLinkService } from "@/lib/services/hashed-link.service";
9+
import { TaskerService } from "@/lib/services/tasker.service";
510
@Module({
611
imports: [RegularBookingModule],
7-
providers: [RecurringBookingService],
12+
providers: [
13+
RecurringBookingService,
14+
BookingEventHandlerService,
15+
/** Required by BookingEventHandlerService - Starts **/
16+
HashedLinkService,
17+
{
18+
provide: Logger,
19+
useFactory: () => {
20+
return new Logger();
21+
},
22+
scope: Scope.TRANSIENT,
23+
},
24+
BookingAuditProducerService,
25+
TaskerService,
26+
/** Required by BookingEventHandlerService - Ends **/
27+
],
828
exports: [RecurringBookingService],
929
})
10-
export class RecurringBookingModule {}
30+
export class RecurringBookingModule { }
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import { RegularBookingService } from "@/lib/services/regular-booking.service";
2+
import { BookingEventHandlerService } from "@/lib/services/booking-event-handler.service";
23
import { Injectable } from "@nestjs/common";
34

45
import { RecurringBookingService as BaseRecurringBookingService } from "@calcom/platform-libraries/bookings";
56

67
@Injectable()
78
export class RecurringBookingService extends BaseRecurringBookingService {
8-
constructor(regularBookingService: RegularBookingService) {
9+
constructor(
10+
regularBookingService: RegularBookingService,
11+
bookingEventHandler: BookingEventHandlerService
12+
) {
913
super({
1014
regularBookingService,
15+
bookingEventHandler,
1116
});
1217
}
1318
}

apps/web/pages/api/book/event.ts

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -58,24 +58,7 @@ async function handler(req: NextApiRequest & { userId?: number; traceContext: Tr
5858
},
5959
});
6060

61-
// const booking = await createBookingThroughFactory();
6261
return booking;
63-
64-
// To be added in the follow-up PR
65-
// async function createBookingThroughFactory() {
66-
// console.log("Creating booking through factory");
67-
// const regularBookingService = getRegularBookingService();
68-
69-
// const booking = await regularBookingService.createBooking({
70-
// bookingData: req.body,
71-
// bookingMeta: {
72-
// userId: session?.user?.id || -1,
73-
// hostname: req.headers.host || "",
74-
// forcedSlug: req.headers["x-cal-force-slug"] as string | undefined,
75-
// },
76-
// });
77-
// return booking;
78-
// }
7962
}
8063

81-
export default defaultResponder(handler, "/api/book/event");
64+
export default defaultResponder(handler, "/api/book/event");

apps/web/pages/api/book/recurring-event.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type { NextApiRequest } from "next";
2-
32
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
43
import { getRecurringBookingService } from "@calcom/features/bookings/di/RecurringBookingService.container";
54
import type { BookingResponse } from "@calcom/features/bookings/types";
@@ -55,11 +54,12 @@ async function handler(req: NextApiRequest & RequestMeta) {
5554
platformBookingLocation: req.platformBookingLocation,
5655
noEmail: req.noEmail,
5756
},
57+
creationSource: "WEBAPP",
5858
});
5959

6060
return createdBookings;
6161
}
6262

6363
export const handleRecurringEventBooking = handler;
6464

65-
export default defaultResponder(handler);
65+
export default defaultResponder(handler);

apps/web/public/static/locales/en/common.json

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3321,7 +3321,7 @@
33213321
"only_if_field_is_empty": "Only if field is empty",
33223322
"booking_start_date": "Booking start date",
33233323
"booking_created_date": "Booking created date",
3324-
"booking_reassigned_to_host": "Booking reassigned to {{host}}",
3324+
"booking_reassigned_to_host": "Reassigned to {{host}}",
33253325
"no_contact_owner": "No contact owner",
33263326
"routing_forms_created": "Routing forms created",
33273327
"routing_forms_total_responses": "Total Responses",
@@ -3718,13 +3718,15 @@
37183718
"pbac_action_delete": "Delete",
37193719
"pbac_action_manage": "Manage",
37203720
"pbac_action_invite": "Invite",
3721-
"pbac_action_remove": "Remove",
3721+
"pbac_action_remove": "Remove",
37223722
"pbac_action_change_member_role": "Change member role",
37233723
"pbac_action_list_members": "List members",
37243724
"pbac_action_manage_billing": "Manage billing",
37253725
"pbac_action_read_team_bookings": "View team bookings",
37263726
"pbac_action_read_org_bookings": "View organization bookings",
37273727
"pbac_action_read_recordings": "View recordings",
3728+
"pbac_action_read_team_booking_audit_logs": "View team booking audit logs",
3729+
"pbac_action_read_org_booking_audit_logs": "View organization booking audit logs",
37283730
"pbac_action_impersonate": "Impersonate",
37293731
"pbac_action_edit_users": "Edit users",
37303732
"role_created_successfully": "Role created successfully",
@@ -3795,6 +3797,8 @@
37953797
"pbac_desc_view_team_bookings": "View team bookings",
37963798
"pbac_desc_view_organization_bookings": "View organization bookings",
37973799
"pbac_desc_view_booking_recordings": "View booking recordings",
3800+
"pbac_desc_read_team_booking_audit_logs": "View audit logs for team bookings",
3801+
"pbac_desc_read_org_booking_audit_logs": "View audit logs for organization bookings",
37983802
"pbac_desc_update_bookings": "Update bookings",
37993803
"pbac_desc_manage_bookings": "All actions on bookings",
38003804
"pbac_desc_view_team_insights": "View team insights",
@@ -4263,7 +4267,8 @@
42634267
"booking_history": "Booking history",
42644268
"booking_history_description": "View the history of actions performed on this booking",
42654269
"booking_audit_action": {
4266-
"created": "Created",
4270+
"created": "Booked with {{host}}",
4271+
"created_with_seat": "Seat Booked with {{host}}",
42674272
"cancelled": "Cancelled",
42684273
"rescheduled": "Rescheduled {{oldDate}} -> <0>{{newDate}}</0>",
42694274
"rescheduled_from": "Rescheduled <0>{{oldDate}}</0> -> {{newDate}}",
@@ -4278,9 +4283,11 @@
42784283
"location_changed": "Location changed",
42794284
"location_changed_from_to": "Location changed from {{fromLocation}} to {{toLocation}}",
42804285
"attendee_no_show_updated": "Attendee no-show updated",
4281-
"type": "Assignment type",
4282-
"assignmentType_manual": "Manual assignment",
4283-
"assignmentType_roundRobin": "Round robin assignment",
4286+
"seat_booked": "Seat booked",
4287+
"seat_rescheduled": "Seat rescheduled {{oldDate}} -> <0>{{newDate}}</0>",
4288+
"assignment_type": "Assignment",
4289+
"assignment_type_manual": "Manual",
4290+
"assignment_type_round_robin": "Round robin",
42844291
"actor_impersonated_by": "Impersonator",
42854292
"source": "Source"
42864293
},

packages/features/booking-audit/lib/actions/CreatedAuditActionService.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { z } from "zod";
22
import { BookingStatus } from "@calcom/prisma/enums";
3+
import type { UserRepository } from "@calcom/features/users/repositories/UserRepository";
34

45
import { AuditActionServiceHelper } from "./AuditActionServiceHelper";
56
import type { IAuditActionService, TranslationWithParams, GetDisplayTitleParams, GetDisplayJsonParams } from "./IAuditActionService";
@@ -15,8 +16,14 @@ const fieldsSchemaV1 = z.object({
1516
startTime: z.number(),
1617
endTime: z.number(),
1718
status: z.nativeEnum(BookingStatus),
19+
hostUserUuid: z.string().nullable(),
20+
// Allowing it to be optional because most of the time(non-seated booking) it won't be there
21+
seatReferenceUid: z.string().nullish(),
1822
});
1923

24+
type Deps = {
25+
userRepository: UserRepository;
26+
};
2027
export class CreatedAuditActionService implements IAuditActionService {
2128
readonly VERSION = 1;
2229
public static readonly TYPE = "CREATED" as const;
@@ -32,7 +39,7 @@ export class CreatedAuditActionService implements IAuditActionService {
3239
public static readonly storedFieldsSchema = CreatedAuditActionService.fieldsSchemaV1;
3340
private helper: AuditActionServiceHelper<typeof CreatedAuditActionService.latestFieldsSchema, typeof CreatedAuditActionService.storedDataSchema>;
3441

35-
constructor() {
42+
constructor(private readonly deps: Deps) {
3643
this.helper = new AuditActionServiceHelper({
3744
latestVersion: this.VERSION,
3845
latestFieldsSchema: CreatedAuditActionService.latestFieldsSchema,
@@ -58,8 +65,14 @@ export class CreatedAuditActionService implements IAuditActionService {
5865
return { isMigrated: false, latestData: validated };
5966
}
6067

61-
async getDisplayTitle(_: GetDisplayTitleParams): Promise<TranslationWithParams> {
62-
return { key: "booking_audit_action.created" };
68+
async getDisplayTitle({ storedData }: GetDisplayTitleParams): Promise<TranslationWithParams> {
69+
const { fields } = this.parseStored(storedData);
70+
const hostUser = fields.hostUserUuid ? await this.deps.userRepository.findByUuid({ uuid: fields.hostUserUuid }) : null;
71+
const hostName = hostUser?.name || "Unknown";
72+
if (fields.seatReferenceUid) {
73+
return { key: "booking_audit_action.created_with_seat", params: { host: hostName } };
74+
}
75+
return { key: "booking_audit_action.created", params: { host: hostName } };
6376
}
6477

6578
getDisplayJson({
@@ -73,6 +86,7 @@ export class CreatedAuditActionService implements IAuditActionService {
7386
startTime: AuditActionServiceHelper.formatDateTimeInTimeZone(fields.startTime, timeZone),
7487
endTime: AuditActionServiceHelper.formatDateTimeInTimeZone(fields.endTime, timeZone),
7588
status: fields.status,
89+
...(fields.seatReferenceUid ? { seatReferenceUid: fields.seatReferenceUid } : {}),
7690
};
7791
}
7892
}
@@ -83,5 +97,6 @@ export type CreatedAuditDisplayData = {
8397
startTime: string;
8498
endTime: string;
8599
status: BookingStatus;
100+
seatReferenceUid?: string;
86101
};
87102

packages/features/booking-audit/lib/actions/ReassignmentAuditActionService.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,16 @@ export class ReassignmentAuditActionService implements IAuditActionService {
8888
labelKey: string;
8989
valueKey: string;
9090
}> {
91-
const { fields } = storedData;
92-
const typeTranslationKey = `booking_audit_action.assignmentType_${fields.reassignmentType}`;
91+
const { fields } = this.parseStored(storedData);
92+
const map = {
93+
manual: "manual",
94+
roundRobin: "round_robin",
95+
}
96+
const typeTranslationKey = `booking_audit_action.assignment_type_${map[fields.reassignmentType]}`;
9397

9498
return [
9599
{
96-
labelKey: "booking_audit_action.type",
100+
labelKey: "booking_audit_action.assignment_type",
97101
valueKey: typeTranslationKey,
98102
}
99103
];

packages/features/booking-audit/lib/actions/RescheduledAuditActionService.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { z } from "zod";
22

3-
import { StringChangeSchema } from "../common/changeSchemas";
3+
import { NumberChangeSchema, StringChangeSchema } from "../common/changeSchemas";
44
import { AuditActionServiceHelper } from "./AuditActionServiceHelper";
55
import type { IAuditActionService, TranslationWithParams, GetDisplayTitleParams, GetDisplayJsonParams, BaseStoredAuditData } from "./IAuditActionService";
66

@@ -11,8 +11,8 @@ import type { IAuditActionService, TranslationWithParams, GetDisplayTitleParams,
1111

1212
// Module-level because it is passed to IAuditActionService type outside the class scope
1313
const fieldsSchemaV1 = z.object({
14-
startTime: StringChangeSchema,
15-
endTime: StringChangeSchema,
14+
startTime: NumberChangeSchema,
15+
endTime: NumberChangeSchema,
1616
rescheduledToUid: StringChangeSchema,
1717
});
1818

0 commit comments

Comments
 (0)