cleanup(webhooks): remove instant meeting trigger support (#29022)

* cleanup(webhooks): remove instant meeting trigger support

* remove dead code

* remove instant meeting from prisma enums and related cleanup

* fix(webhooks): make instant meeting enum migration atomic
This commit is contained in:
Romit
2026-05-01 08:11:32 +05:30
committed by GitHub
parent 1591a50bc5
commit 91341cbba4
15 changed files with 35 additions and 163 deletions
@@ -56,7 +56,6 @@ const WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP_V2: Record<string, WebhookTriggerEve
{ value: WebhookTriggerEvents.MEETING_ENDED, label: "meeting_ended" },
{ value: WebhookTriggerEvents.MEETING_STARTED, label: "meeting_started" },
{ value: WebhookTriggerEvents.RECORDING_READY, label: "recording_ready" },
{ value: WebhookTriggerEvents.INSTANT_MEETING, label: "instant_meeting" },
{ value: WebhookTriggerEvents.OOO_CREATED, label: "ooo_created" },
{
value: WebhookTriggerEvents.RECORDING_TRANSCRIPTION_GENERATED,
@@ -248,13 +247,12 @@ const WebhookForm = (props: {
overrideTriggerOptions?: (typeof WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP_V2)["core"];
onSubmit: (event: WebhookFormSubmitData) => void;
onCancel?: () => void;
selectOnlyInstantMeetingOption?: boolean;
headerWrapper?: (
formMethods: ReturnType<typeof useForm<WebhookFormValues>>,
children: React.ReactNode
) => React.ReactNode;
}) => {
const { apps = [], selectOnlyInstantMeetingOption = false, overrideTriggerOptions } = props;
const { apps = [], overrideTriggerOptions } = props;
const { t } = useLocale();
const webhookVariables = getWebhookVariables(t);
@@ -273,11 +271,7 @@ const WebhookForm = (props: {
const getEventTriggers = () => {
if (props.webhook) return props.webhook.eventTriggers;
return (
selectOnlyInstantMeetingOption
? translatedTriggerOptions.filter((option) => option.value === WebhookTriggerEvents.INSTANT_MEETING)
: translatedTriggerOptions.filter((option) => option.value !== WebhookTriggerEvents.INSTANT_MEETING)
).map((option) => option.value);
return translatedTriggerOptions.map((option) => option.value);
};
const formMethods = useForm<WebhookFormValues>({
@@ -54,7 +54,6 @@ export const WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP = {
WebhookTriggerEvents.BOOKING_REQUESTED,
WebhookTriggerEvents.BOOKING_REJECTED,
WebhookTriggerEvents.RECORDING_READY,
WebhookTriggerEvents.INSTANT_MEETING,
WebhookTriggerEvents.RECORDING_TRANSCRIPTION_GENERATED,
WebhookTriggerEvents.BOOKING_NO_SHOW_UPDATED,
WebhookTriggerEvents.OOO_CREATED,
@@ -301,17 +301,6 @@ export interface MeetingEndedDTO extends BaseEventDTO {
};
}
export interface InstantMeetingDTO extends BaseEventDTO {
triggerEvent: typeof WebhookTriggerEvents.INSTANT_MEETING;
title: string;
body: string;
icon?: string;
url?: string;
actions?: { action: string; title: string; type: string; image: string | null }[];
requireInteraction?: boolean;
type: string;
}
export interface AfterHostsNoShowDTO extends BaseEventDTO {
triggerEvent: typeof WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW;
bookingId: number;
@@ -350,7 +339,6 @@ export type WebhookEventDTO =
| TranscriptionGeneratedDTO
| MeetingStartedDTO
| MeetingEndedDTO
| InstantMeetingDTO
| AfterHostsNoShowDTO
| AfterGuestsNoShowDTO;
@@ -1,40 +0,0 @@
import { describe, it, expect } from "vitest";
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
import type { InstantMeetingDTO } from "../../dto/types";
import { InstantMeetingBuilder } from "../versioned/v2021-10-20/InstantMeetingBuilder";
describe("InstantMeetingBuilder (v2021-10-20)", () => {
const builder = new InstantMeetingBuilder();
describe("INSTANT_MEETING", () => {
it("should build payload with all instant meeting fields", () => {
const dto: InstantMeetingDTO = {
triggerEvent: WebhookTriggerEvents.INSTANT_MEETING,
createdAt: "2024-01-15T10:00:00Z",
title: "Instant Meeting Request",
body: "Someone is requesting an instant meeting",
icon: "https://cal.com/icon.png",
url: "https://cal.com/meeting/instant-123",
actions: [
{ action: "accept", title: "Accept" },
{ action: "decline", title: "Decline" },
],
requireInteraction: true,
type: "instant",
};
const payload = builder.build(dto);
expect(payload.triggerEvent).toBe(WebhookTriggerEvents.INSTANT_MEETING);
expect(payload.createdAt).toBe("2024-01-15T10:00:00Z");
expect(payload.payload.title).toBe("Instant Meeting Request");
expect(payload.payload.body).toBe("Someone is requesting an instant meeting");
expect(payload.payload.icon).toBe("https://cal.com/icon.png");
expect(payload.payload.url).toBe("https://cal.com/meeting/instant-123");
expect(payload.payload.actions).toHaveLength(2);
expect(payload.payload.requireInteraction).toBe(true);
expect(payload.payload.type).toBe("instant");
});
});
});
@@ -1,20 +0,0 @@
import type { InstantMeetingDTO } from "../../dto/types";
import type { WebhookPayload } from "../types";
import type { IInstantMeetingBuilder } from "../versioned/PayloadBuilderFactory";
/**
* Abstract base class for instant meeting payload builders.
*
* This class defines the interface that all version-specific instant meeting
* payload builders must implement. It does NOT contain any version-specific payload logic.
*
* Each webhook version should have its own concrete implementation in
* versioned/v{VERSION}/InstantMeetingBuilder.ts
*/
export abstract class BaseInstantMeetingBuilder implements IInstantMeetingBuilder {
/**
* Build the instant meeting webhook payload.
* Each version must implement this method with its specific payload structure.
*/
abstract build(dto: InstantMeetingDTO): WebhookPayload;
}
@@ -15,5 +15,4 @@ export { BaseBookingPayloadBuilder } from "./BaseBookingPayloadBuilder";
export { BaseMeetingPayloadBuilder } from "./BaseMeetingPayloadBuilder";
export { BaseRecordingPayloadBuilder } from "./BaseRecordingPayloadBuilder";
export { BaseOOOPayloadBuilder } from "./BaseOOOPayloadBuilder";
export { BaseInstantMeetingBuilder } from "./BaseInstantMeetingBuilder";
export type { BookingExtraDataMap, BookingPayloadParams } from "./BaseBookingPayloadBuilder";
@@ -63,16 +63,6 @@ export interface MeetingPayload {
}[];
}
export interface InstantMeetingPayload {
title: string;
body: string;
icon?: string;
url?: string;
actions?: { action: string; title: string; type: string; image: string | null }[];
requireInteraction?: boolean;
type: string;
}
export interface NoShowWebhookPayload {
bookingId: number;
webhook: {
@@ -93,6 +83,5 @@ export interface WebhookPayload {
| FormSubmittedPayload
| RecordingPayload
| MeetingPayload
| InstantMeetingPayload
| NoShowWebhookPayload;
}
@@ -16,7 +16,6 @@ describe("PayloadBuilderFactory", () => {
ooo: new V2021_10_20.OOOPayloadBuilder(),
recording: new V2021_10_20.RecordingPayloadBuilder(),
meeting: new V2021_10_20.MeetingPayloadBuilder(),
instantMeeting: new V2021_10_20.InstantMeetingBuilder(),
};
factory = new PayloadBuilderFactory(WebhookVersionEnum.V_2021_10_20, defaultBuilders);
@@ -42,7 +41,6 @@ describe("PayloadBuilderFactory", () => {
ooo: new V2021_10_20.OOOPayloadBuilder(),
recording: new V2021_10_20.RecordingPayloadBuilder(),
meeting: new V2021_10_20.MeetingPayloadBuilder(),
instantMeeting: new V2021_10_20.InstantMeetingBuilder(),
};
factory.registerVersion("2024-12-01", newVersionBuilders);
@@ -57,7 +55,6 @@ describe("PayloadBuilderFactory", () => {
ooo: new V2021_10_20.OOOPayloadBuilder(),
recording: new V2021_10_20.RecordingPayloadBuilder(),
meeting: new V2021_10_20.MeetingPayloadBuilder(),
instantMeeting: new V2021_10_20.InstantMeetingBuilder(),
};
factory.registerVersion(WebhookVersionEnum.V_2021_10_20, newBuilders);
@@ -111,13 +108,6 @@ describe("PayloadBuilderFactory", () => {
});
});
it("should route instant meeting events to instant meeting builder", () => {
const builder = factory.getBuilder(
WebhookVersionEnum.V_2021_10_20,
WebhookTriggerEvents.INSTANT_MEETING
);
expect(builder).toBe(defaultBuilders.instantMeeting);
});
});
describe("Fallback Behavior", () => {
@@ -166,17 +156,12 @@ describe("PayloadBuilderFactory", () => {
WebhookVersionEnum.V_2021_10_20,
WebhookTriggerEvents.MEETING_STARTED
);
const instantBuilder = factory.getBuilder(
WebhookVersionEnum.V_2021_10_20,
WebhookTriggerEvents.INSTANT_MEETING
);
// Runtime verification
expect(bookingBuilder).toBeDefined();
expect(oooBuilder).toBeDefined();
expect(recordingBuilder).toBeDefined();
expect(meetingBuilder).toBeDefined();
expect(instantBuilder).toBeDefined();
});
it("should build valid payload with correctly typed DTO", () => {
@@ -244,7 +229,6 @@ describe("PayloadBuilderFactory", () => {
ooo: new V2021_10_20.OOOPayloadBuilder(),
recording: new V2021_10_20.RecordingPayloadBuilder(),
meeting: new V2021_10_20.MeetingPayloadBuilder(),
instantMeeting: new V2021_10_20.InstantMeetingBuilder(),
};
factory.registerVersion(WebhookVersionEnum.V_2024_12_01, v2Builders);
@@ -4,7 +4,6 @@ import type {
AfterGuestsNoShowDTO,
AfterHostsNoShowDTO,
BookingWebhookEventDTO,
InstantMeetingDTO,
MeetingEndedDTO,
MeetingStartedDTO,
OOOCreatedDTO,
@@ -44,16 +43,11 @@ export interface IMeetingPayloadBuilder
): WebhookPayload;
}
export interface IInstantMeetingBuilder extends IPayloadBuilder<InstantMeetingDTO> {
build(dto: InstantMeetingDTO): WebhookPayload;
}
export interface PayloadBuilderSet {
booking: IBookingPayloadBuilder;
ooo: IOOOPayloadBuilder;
recording: IRecordingPayloadBuilder;
meeting: IMeetingPayloadBuilder;
instantMeeting: IInstantMeetingBuilder;
}
type BuilderCategory = keyof PayloadBuilderSet;
@@ -92,9 +86,6 @@ const TRIGGER_TO_BUILDER_CATEGORY: Record<WebhookTriggerEvents, BuilderCategory>
[WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW]: "meeting",
[WebhookTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW]: "meeting",
// Instant meeting events
[WebhookTriggerEvents.INSTANT_MEETING]: "instantMeeting",
// Delegation events (enterprise feature removed, mapped to booking as fallback)
[WebhookTriggerEvents.DELEGATION_CREDENTIAL_ERROR]: "booking",
@@ -128,8 +119,6 @@ export type MeetingTriggerEvents =
| typeof WebhookTriggerEvents.AFTER_HOSTS_CAL_VIDEO_NO_SHOW
| typeof WebhookTriggerEvents.AFTER_GUESTS_CAL_VIDEO_NO_SHOW;
export type InstantMeetingTriggerEvents = typeof WebhookTriggerEvents.INSTANT_MEETING;
/**
* Factory that routes to version-specific payload builders
*
@@ -192,7 +181,6 @@ export class PayloadBuilderFactory {
getBuilder(version: WebhookVersion, triggerEvent: OOOTriggerEvents): IOOOPayloadBuilder;
getBuilder(version: WebhookVersion, triggerEvent: RecordingTriggerEvents): IRecordingPayloadBuilder;
getBuilder(version: WebhookVersion, triggerEvent: MeetingTriggerEvents): IMeetingPayloadBuilder;
getBuilder(version: WebhookVersion, triggerEvent: InstantMeetingTriggerEvents): IInstantMeetingBuilder;
getBuilder(version: WebhookVersion, triggerEvent: WebhookTriggerEvents): IPayloadBuilder<WebhookEventDTO>;
getBuilder(version: WebhookVersion, triggerEvent: WebhookTriggerEvents): IPayloadBuilder<WebhookEventDTO> {
const builderSet = this.getBuilderSet(version);
@@ -31,16 +31,11 @@ describe("Payload Builder Registry", () => {
DEFAULT_WEBHOOK_VERSION,
WebhookTriggerEvents.MEETING_STARTED
);
const instantBuilder = factory.getBuilder(
DEFAULT_WEBHOOK_VERSION,
WebhookTriggerEvents.INSTANT_MEETING
);
expect(bookingBuilder).toBeDefined();
expect(oooBuilder).toBeDefined();
expect(recordingBuilder).toBeDefined();
expect(meetingBuilder).toBeDefined();
expect(instantBuilder).toBeDefined();
});
it("should register v2021-10-20 builders", () => {
@@ -92,7 +87,6 @@ describe("Payload Builder Registry", () => {
ooo: new V2021_10_20.OOOPayloadBuilder(),
recording: new V2021_10_20.RecordingPayloadBuilder(),
meeting: new V2021_10_20.MeetingPayloadBuilder(),
instantMeeting: new V2021_10_20.InstantMeetingBuilder(),
};
factory.registerVersion(NEW_VERSION, newVersionBuilders);
@@ -111,7 +105,6 @@ describe("Payload Builder Registry", () => {
ooo: new V2021_10_20.OOOPayloadBuilder(),
recording: new V2021_10_20.RecordingPayloadBuilder(),
meeting: new V2021_10_20.MeetingPayloadBuilder(),
instantMeeting: new V2021_10_20.InstantMeetingBuilder(),
};
factory.registerVersion(NEW_VERSION, v2Builders);
@@ -140,7 +133,6 @@ describe("Payload Builder Registry", () => {
ooo: new V2021_10_20.OOOPayloadBuilder(),
recording: new V2021_10_20.RecordingPayloadBuilder(),
meeting: new V2021_10_20.MeetingPayloadBuilder(),
instantMeeting: new V2021_10_20.InstantMeetingBuilder(),
});
expect(factory1.getRegisteredVersions()).not.toEqual(factory2.getRegisteredVersions());
@@ -26,7 +26,6 @@ export { DEFAULT_WEBHOOK_VERSION } from "../../interface/IWebhookRepository";
* ooo: new V2024_12_01.OOOPayloadBuilder(),
* recording: new V2024_12_01.RecordingPayloadBuilder(),
* meeting: new V2024_12_01.MeetingPayloadBuilder(),
* instantMeeting: new V2024_12_01.InstantMeetingBuilder(),
* });
* ```
*/
@@ -36,7 +35,6 @@ export function createPayloadBuilderFactory(): PayloadBuilderFactory {
ooo: new V2021_10_20.OOOPayloadBuilder(),
recording: new V2021_10_20.RecordingPayloadBuilder(),
meeting: new V2021_10_20.MeetingPayloadBuilder(),
instantMeeting: new V2021_10_20.InstantMeetingBuilder(),
};
const factory = new PayloadBuilderFactory(DEFAULT_WEBHOOK_VERSION, defaultBuilders);
@@ -1,30 +0,0 @@
import type { InstantMeetingDTO } from "../../../dto/types";
import type { WebhookPayload } from "../../types";
import { BaseInstantMeetingBuilder } from "../../base/BaseInstantMeetingBuilder";
/**
* Instant meeting payload builder for webhook version v2021-10-20.
*
* This is the initial instant meeting webhook payload format.
* It includes notification-style data (title, body, icon, url, actions).
*/
export class InstantMeetingBuilder extends BaseInstantMeetingBuilder {
/**
* Build the instant meeting webhook payload for v2021-10-20.
*/
build(dto: InstantMeetingDTO): WebhookPayload {
return {
triggerEvent: dto.triggerEvent,
createdAt: dto.createdAt,
payload: {
title: dto.title,
body: dto.body,
icon: dto.icon,
url: dto.url,
actions: dto.actions,
requireInteraction: dto.requireInteraction,
type: dto.type,
},
};
}
}
@@ -10,4 +10,3 @@ export { BookingPayloadBuilder } from "./BookingPayloadBuilder";
export { MeetingPayloadBuilder } from "./MeetingPayloadBuilder";
export { RecordingPayloadBuilder } from "./RecordingPayloadBuilder";
export { OOOPayloadBuilder } from "./OOOPayloadBuilder";
export { InstantMeetingBuilder } from "./InstantMeetingBuilder";
@@ -0,0 +1,33 @@
BEGIN;
UPDATE "public"."Webhook"
SET "eventTriggers" = array_remove("eventTriggers", 'INSTANT_MEETING'::"public"."WebhookTriggerEvents")
WHERE "eventTriggers" @> ARRAY['INSTANT_MEETING']::"public"."WebhookTriggerEvents"[];
CREATE TYPE "public"."WebhookTriggerEvents_new" AS ENUM (
'BOOKING_CREATED',
'BOOKING_PAYMENT_INITIATED',
'BOOKING_PAID',
'BOOKING_RESCHEDULED',
'BOOKING_REQUESTED',
'BOOKING_CANCELLED',
'BOOKING_REJECTED',
'BOOKING_NO_SHOW_UPDATED',
'FORM_SUBMITTED',
'MEETING_ENDED',
'MEETING_STARTED',
'RECORDING_READY',
'RECORDING_TRANSCRIPTION_GENERATED',
'OOO_CREATED',
'AFTER_HOSTS_CAL_VIDEO_NO_SHOW',
'AFTER_GUESTS_CAL_VIDEO_NO_SHOW',
'FORM_SUBMITTED_NO_EVENT',
'DELEGATION_CREDENTIAL_ERROR',
'WRONG_ASSIGNMENT_REPORT'
);
ALTER TABLE "public"."Webhook"
ALTER COLUMN "eventTriggers" TYPE "public"."WebhookTriggerEvents_new"[]
USING ("eventTriggers"::text::"public"."WebhookTriggerEvents_new"[]);
ALTER TYPE "public"."WebhookTriggerEvents" RENAME TO "WebhookTriggerEvents_old";
ALTER TYPE "public"."WebhookTriggerEvents_new" RENAME TO "WebhookTriggerEvents";
DROP TYPE "public"."WebhookTriggerEvents_old";
COMMIT;
-1
View File
@@ -1130,7 +1130,6 @@ enum WebhookTriggerEvents {
MEETING_ENDED
MEETING_STARTED
RECORDING_READY
INSTANT_MEETING
RECORDING_TRANSCRIPTION_GENERATED
OOO_CREATED
AFTER_HOSTS_CAL_VIDEO_NO_SHOW