Skip to content

Commit d655aa2

Browse files
Merge branch 'main' into devin/qualified-hosts-service-1754645117
2 parents fdd5709 + 5a350ef commit d655aa2

File tree

99 files changed

+10607
-160
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+10607
-160
lines changed

.yarn/versions/96bd4a2c.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
undecided:
2+
- "@calcom/prisma"

apps/api/v2/src/modules/workflows/inputs/workflow-step.input.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const SMS_ATTENDEE = "sms_attendee";
1010
export const SMS_NUMBER = "sms_number";
1111
export const WHATSAPP_ATTENDEE = "whatsapp_attendee";
1212
export const WHATSAPP_NUMBER = "whatsapp_number";
13+
export const CAL_AI_PHONE_CALL = "cal_ai_phone_call";
1314

1415
export const STEP_ACTIONS = [
1516
EMAIL_HOST,
@@ -19,6 +20,7 @@ export const STEP_ACTIONS = [
1920
SMS_NUMBER,
2021
WHATSAPP_ATTENDEE,
2122
WHATSAPP_NUMBER,
23+
CAL_AI_PHONE_CALL,
2224
] as const;
2325

2426
export const STEP_ACTIONS_TO_ENUM = {
@@ -29,6 +31,7 @@ export const STEP_ACTIONS_TO_ENUM = {
2931
[WHATSAPP_ATTENDEE]: WorkflowActions.WHATSAPP_ATTENDEE,
3032
[WHATSAPP_NUMBER]: WorkflowActions.WHATSAPP_NUMBER,
3133
[SMS_NUMBER]: WorkflowActions.SMS_NUMBER,
34+
[CAL_AI_PHONE_CALL]: WorkflowActions.CAL_AI_PHONE_CALL,
3235
} as const;
3336

3437
export const ENUM_TO_STEP_ACTIONS = {
@@ -39,6 +42,7 @@ export const ENUM_TO_STEP_ACTIONS = {
3942
[WorkflowActions.WHATSAPP_ATTENDEE]: WHATSAPP_ATTENDEE,
4043
[WorkflowActions.WHATSAPP_NUMBER]: WHATSAPP_NUMBER,
4144
[WorkflowActions.SMS_NUMBER]: SMS_NUMBER,
45+
[WorkflowActions.CAL_AI_PHONE_CALL]: CAL_AI_PHONE_CALL,
4246
} as const;
4347

4448
export type StepAction = (typeof STEP_ACTIONS)[number];

apps/web/app/api/auth/saml/callback/route.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,27 @@ import type { SAMLResponsePayload } from "@calcom/features/ee/sso/lib/jackson";
99
import logger from "@calcom/lib/logger";
1010

1111
async function handler(req: NextRequest) {
12-
const log = logger.getSubLogger({ prefix: ["[SAML callback]"] });
12+
const uid = uuid();
13+
const log = logger.getSubLogger({ prefix: ["[SAML callback]", `trace: ${uid}`] });
1314
const { oauthController } = await jackson();
1415

1516
const requestData = (await parseRequestData(req)) as SAMLResponsePayload;
1617

17-
const { redirect_url, error } = await oauthController.samlResponse(requestData);
18-
19-
if (redirect_url) {
20-
return NextResponse.redirect(redirect_url, 302);
21-
}
22-
23-
if (error) {
24-
const uid = uuid();
25-
log.error(
26-
`Error authenticating user with error ${error} for relayState ${requestData?.RelayState} trace:${uid}`
27-
);
28-
return NextResponse.json({ message: `Error authorizing user. trace: ${uid}` }, { status: 400 });
18+
try {
19+
const { redirect_url, error } = await oauthController.samlResponse(requestData);
20+
21+
if (redirect_url) {
22+
return NextResponse.redirect(redirect_url, 302);
23+
}
24+
25+
if (error) {
26+
const uid = uuid();
27+
log.error(`Error authenticating user with error ${error} for relayState ${requestData?.RelayState}`);
28+
return NextResponse.json({ message: `Error authorizing user. trace: ${uid}` }, { status: 400 });
29+
}
30+
} catch (error) {
31+
log.error(`Error processing SAML response`, error);
32+
return NextResponse.json({ message: `Error processing SAML response. trace: ${uid}` }, { status: 500 });
2933
}
3034

3135
return NextResponse.json({ message: "No redirect URL provided" }, { status: 400 });

apps/web/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@calcom/web",
3-
"version": "5.5.13",
3+
"version": "5.5.14",
44
"private": true,
55
"scripts": {
66
"analyze": "ANALYZE=true next build",
@@ -138,6 +138,7 @@
138138
"react-use-intercom": "1.5.1",
139139
"recoil": "^0.7.7",
140140
"remove-markdown": "^0.5.0",
141+
"retell-sdk": "^4.40.0",
141142
"rrule": "^2.7.1",
142143
"sanitize-html": "^2.10.0",
143144
"schema-dts": "^1.1.0",

apps/web/pages/api/get-inbound-dynamic-variables.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import type { NextApiRequest, NextApiResponse } from "next";
33
import { z } from "zod";
44

55
import dayjs from "@calcom/dayjs";
6-
import { ZGetRetellLLMSchema } from "@calcom/features/ee/cal-ai-phone/zod-utils";
7-
import type { TGetRetellLLMSchema } from "@calcom/features/ee/cal-ai-phone/zod-utils";
6+
import { ZGetRetellLLMSchema } from "@calcom/features/calAIPhone/zod-utils";
7+
import type { TGetRetellLLMSchema } from "@calcom/features/calAIPhone/zod-utils";
88
import { getAvailableSlotsService } from "@calcom/lib/di/containers/available-slots";
99
import { fetcher } from "@calcom/lib/retellAIFetcher";
1010
import { defaultHandler } from "@calcom/lib/server/defaultHandler";

packages/app-store/components.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export const AppDependencyComponent = ({
9797
{t("this_app_requires_connected_account", {
9898
appName,
9999
dependencyName: dependency.name,
100+
interpolation: { escapeValue: false },
100101
})}
101102
</span>
102103
</div>
@@ -112,7 +113,11 @@ export const AppDependencyComponent = ({
112113
</div>
113114
<div>
114115
<span className="font-semibold">
115-
{t("this_app_requires_connected_account", { appName, dependencyName: dependency.name })}
116+
{t("this_app_requires_connected_account", {
117+
appName,
118+
dependencyName: dependency.name,
119+
interpolation: { escapeValue: false },
120+
})}
116121
</span>
117122

118123
<div>

packages/app-store/locations.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ export const DailyLocationType = "integrations:daily";
6161

6262
export const MeetLocationType = "integrations:google:meet";
6363

64+
export const MSTeamsLocationType = "integrations:office365_video";
65+
6466
/**
6567
* This isn't an actual location app type. It is a special value that informs to use the Organizer's default conferencing app during booking
6668
*/

packages/app-store/office365calendar/lib/CalendarService.ts

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { Calendar as OfficeCalendar, User, Event } from "@microsoft/microsoft-graph-types-beta";
22
import type { DefaultBodyType } from "msw";
33

4+
import { MSTeamsLocationType } from "@calcom/app-store/locations";
45
import dayjs from "@calcom/dayjs";
5-
import { getLocation } from "@calcom/lib/CalEventParser";
6+
import { getLocation, getRichDescriptionHTML } from "@calcom/lib/CalEventParser";
67
import {
78
CalendarAppDelegationCredentialInvalidGrantError,
89
CalendarAppDelegationCredentialConfigurationError,
@@ -254,7 +255,13 @@ export default class Office365CalendarService implements Calendar {
254255
body: JSON.stringify(this.translateEvent(event)),
255256
});
256257

257-
const responseJson = await handleErrorsJson<NewCalendarEventType & { iCalUId: string }>(response);
258+
const responseJson = await handleErrorsJson<
259+
NewCalendarEventType & { iCalUId: string; onlineMeeting?: { joinUrl?: string } }
260+
>(response);
261+
262+
if (responseJson?.onlineMeeting?.joinUrl) {
263+
responseJson.url = responseJson?.onlineMeeting?.joinUrl;
264+
}
258265

259266
return { ...responseJson, iCalUID: responseJson.iCalUId };
260267
} catch (error) {
@@ -266,12 +273,28 @@ export default class Office365CalendarService implements Calendar {
266273

267274
async updateEvent(uid: string, event: CalendarServiceEvent): Promise<NewCalendarEventType> {
268275
try {
276+
let rescheduledEvent: Event | undefined;
277+
if (event.location === MSTeamsLocationType) {
278+
// Extract the existing body content to preserve the meeting blob, otherwise it breaks and converts it into non-onlineMeeting
279+
const response = await this.fetcher(`${await this.getUserEndpoint()}/calendar/events/${uid}`, {
280+
method: "GET",
281+
});
282+
283+
rescheduledEvent = await handleErrorsJson<Event>(response);
284+
}
285+
269286
const response = await this.fetcher(`${await this.getUserEndpoint()}/calendar/events/${uid}`, {
270287
method: "PATCH",
271-
body: JSON.stringify(this.translateEvent(event)),
288+
body: JSON.stringify(this.translateEvent(event, rescheduledEvent)),
272289
});
273290

274-
const responseJson = await handleErrorsJson<NewCalendarEventType & { iCalUId: string }>(response);
291+
const responseJson = await handleErrorsJson<
292+
NewCalendarEventType & { iCalUId: string; onlineMeeting?: { joinUrl?: string } }
293+
>(response);
294+
295+
if (responseJson?.onlineMeeting?.joinUrl) {
296+
responseJson.url = responseJson?.onlineMeeting?.joinUrl;
297+
}
275298

276299
return { ...responseJson, iCalUID: responseJson.iCalUId };
277300
} catch (error) {
@@ -401,12 +424,30 @@ export default class Office365CalendarService implements Calendar {
401424
});
402425
}
403426

404-
private translateEvent = (event: CalendarServiceEvent) => {
427+
private translateEvent = (event: CalendarServiceEvent, rescheduledEvent?: Event) => {
428+
const isOnlineMeeting = event.location === MSTeamsLocationType;
429+
const isRescheduledOnlineMeeting = rescheduledEvent ? rescheduledEvent.isOnlineMeeting : false;
430+
const existingBody =
431+
rescheduledEvent?.body?.contentType === "html" ? rescheduledEvent.body.content : undefined;
432+
433+
let content = "";
434+
if (isOnlineMeeting) {
435+
if (isRescheduledOnlineMeeting && existingBody) {
436+
content = `
437+
${getRichDescriptionHTML(event)}<hr>
438+
${existingBody}`.trim();
439+
} else {
440+
content = getRichDescriptionHTML(event);
441+
}
442+
} else {
443+
content = event.calendarDescription;
444+
}
445+
405446
const office365Event: Event = {
406447
subject: event.title,
407448
body: {
408-
contentType: "text",
409-
content: event.calendarDescription,
449+
contentType: isOnlineMeeting ? "html" : "text",
450+
content,
410451
},
411452
start: {
412453
dateTime: dayjs(event.startTime).tz(event.organizer.timeZone).format("YYYY-MM-DDTHH:mm:ss"),
@@ -456,6 +497,17 @@ export default class Office365CalendarService implements Calendar {
456497
if (event.hideCalendarEventDetails) {
457498
office365Event.sensitivity = "private";
458499
}
500+
if (isOnlineMeeting) {
501+
office365Event.isOnlineMeeting = true;
502+
office365Event.allowNewTimeProposals = true;
503+
office365Event.onlineMeetingProvider = "teamsForBusiness";
504+
// MSTeams sets location as 'Microsoft Teams Meeting' by default, if location is undefined.
505+
// For backward compatibility, setting explicitly.
506+
office365Event.location =
507+
rescheduledEvent && !isRescheduledOnlineMeeting
508+
? { displayName: "Microsoft Teams Meeting" }
509+
: undefined;
510+
}
459511
return office365Event;
460512
};
461513

packages/app-store/office365video/_metadata.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const metadata = {
2121
publisher: "Cal.com",
2222
slug: "msteams",
2323
dirName: "office365video",
24+
dependencies: ["office365-calendar"],
2425
url: "https://www.microsoft.com/en-ca/microsoft-teams/group-chat-software",
2526
email: "help@cal.com",
2627
isOAuth: true,

packages/app-store/office365video/config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
}
2323
},
2424
"dirName": "office365video",
25+
"dependencies": ["office365-calendar"],
2526
"concurrentMeetings": true,
2627
"isOAuth": true
2728
}

0 commit comments

Comments
 (0)