Skip to content
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

feat: support phone based booking api #17635

Merged
merged 16 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/api/v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"@axiomhq/winston": "^1.2.0",
"@calcom/platform-constants": "*",
"@calcom/platform-enums": "*",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.79",
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.81",
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
"@calcom/platform-types": "*",
"@calcom/platform-utils": "*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ describe("Bookings Endpoints 2024-08-13", () => {

let team1EventTypeId: number;
let team2EventTypeId: number;
let phoneOnlyEventTypeId: number;

beforeAll(async () => {
const moduleRef = await withApiAuth(
Expand Down Expand Up @@ -205,6 +206,83 @@ describe("Bookings Endpoints 2024-08-13", () => {

team1EventTypeId = team1EventType.id;

const phoneOnlyEventType = await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "ROUND_ROBIN",
team: {
connect: { id: team1.id },
},
title: "Phone Only Event Type",
slug: "phone-only-event-type",
length: 15,
assignAllTeamMembers: false,
hosts: {
connectOrCreate: [
{
where: {
userId_eventTypeId: {
userId: teamUser.id,
eventTypeId: team1EventTypeId,
},
},
create: {
userId: teamUser.id,
isFixed: true,
},
},
],
},
bookingFields: [
{
name: "name",
type: "name",
label: "your name",
sources: [{ id: "default", type: "default", label: "Default" }],
variant: "fullName",
editable: "system",
required: true,
defaultLabel: "your_name",
variantsConfig: {
variants: {
fullName: {
fields: [{ name: "fullName", type: "text", label: "your name", required: true }],
},
},
},
},
{
name: "email",
type: "email",
label: "your email",
sources: [{ id: "default", type: "default", label: "Default" }],
editable: "system",
required: false,
defaultLabel: "email_address",
},
{
name: "attendeePhoneNumber",
type: "phone",
label: "phone_number",
sources: [{ id: "user", type: "user", label: "User", fieldRequired: true }],
editable: "user",
required: true,
placeholder: "",
},
{
name: "rescheduleReason",
type: "textarea",
views: [{ id: "reschedule", label: "Reschedule View" }],
sources: [{ id: "default", type: "default", label: "Default" }],
editable: "system-but-optional",
required: false,
defaultLabel: "reason_for_reschedule",
defaultPlaceholder: "reschedule_placeholder",
},
],
locations: [],
});

phoneOnlyEventTypeId = phoneOnlyEventType.id;

const team2EventType = await eventTypesRepositoryFixture.createTeamEventType({
schedulingType: "COLLECTIVE",
team: {
Expand Down Expand Up @@ -322,6 +400,60 @@ describe("Bookings Endpoints 2024-08-13", () => {
});
});

it("should create a phone based booking", async () => {
const body: CreateBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2030, 0, 8, 15, 0, 0)).toISOString(),
eventTypeId: phoneOnlyEventTypeId,
attendee: {
name: "alice",
phoneNumber: "+919876543210",
timeZone: "Europe/Madrid",
language: "es",
},
meetingUrl: "https://meet.google.com/abc-def-ghi",
};

return request(app.getHttpServer())
.post("/v2/bookings")
.send(body)
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
.expect(201)
.then(async (response) => {
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseDataIsBooking(responseBody.data)).toBe(true);

if (responseDataIsBooking(responseBody.data)) {
const data: BookingOutput_2024_08_13 = responseBody.data;
expect(data.id).toBeDefined();
expect(data.uid).toBeDefined();
expect(data.hosts.length).toEqual(1);
expect(data.hosts[0].id).toEqual(teamUser.id);
expect(data.status).toEqual("accepted");
expect(data.start).toEqual(body.start);
expect(data.end).toEqual(new Date(Date.UTC(2030, 0, 8, 15, 15, 0)).toISOString());
expect(data.duration).toEqual(15);
expect(data.eventTypeId).toEqual(phoneOnlyEventTypeId);
expect(data.attendees.length).toEqual(1);
expect(data.attendees[0]).toEqual({
name: body.attendee.name,
email: "919876543210@sms.cal.com",
phoneNumber: body.attendee.phoneNumber,
timeZone: body.attendee.timeZone,
language: body.attendee.language,
absent: false,
});
expect(data.meetingUrl).toEqual(body.meetingUrl);
expect(data.absentHost).toEqual(false);
} else {
throw new Error(
"Invalid response data - expected booking but received array of possibily recurring bookings"
);
}
});
});

it("should create a team 2 booking", async () => {
const body: CreateBookingInput_2024_08_13 = {
start: new Date(Date.UTC(2030, 0, 8, 10, 0, 0)).toISOString(),
Expand Down Expand Up @@ -398,7 +530,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
| RecurringBookingOutput_2024_08_13
| GetSeatedBookingOutput_2024_08_13
)[] = responseBody.data;
expect(data.length).toEqual(1);
expect(data.length).toEqual(2);
expect(data[0].eventTypeId).toEqual(team1EventTypeId);
});
});
Expand Down Expand Up @@ -436,7 +568,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
| RecurringBookingOutput_2024_08_13
| GetSeatedBookingOutput_2024_08_13
)[] = responseBody.data;
expect(data.length).toEqual(2);
expect(data.length).toEqual(3);
expect(data.find((booking) => booking.eventTypeId === team1EventTypeId)).toBeDefined();
expect(data.find((booking) => booking.eventTypeId === team2EventTypeId)).toBeDefined();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { Request } from "express";
import { z } from "zod";

import {
handleNewBooking,
handleNewRecurringBooking,
getAllUserBookings,
handleInstantMeeting,
Expand All @@ -23,6 +22,7 @@ import {
handleMarkNoShow,
confirmBookingHandler,
} from "@calcom/platform-libraries";
import { handleNewBooking } from "@calcom/platform-libraries";
import {
CreateBookingInput_2024_08_13,
CreateBookingInput,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,14 @@ export class InputBookingsService_2024_08_13 {
? {
...inputBooking.bookingFieldsResponses,
name: inputBooking.attendee.name,
email: inputBooking.attendee.email,
email: inputBooking.attendee.email ?? "",
attendeePhoneNumber: inputBooking.attendee.phoneNumber,
}
: { name: inputBooking.attendee.name, email: inputBooking.attendee.email },
: {
name: inputBooking.attendee.name,
email: inputBooking.attendee.email ?? "",
attendeePhoneNumber: inputBooking.attendee.phoneNumber,
},
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ type DatabaseBooking = Booking & {
email: string;
timeZone: string;
locale: string | null;
phoneNumber?: string | null;
noShow: boolean | null;
bookingSeat?: BookingSeat | null;
}[];
Expand Down Expand Up @@ -121,6 +122,7 @@ export class OutputBookingsService_2024_08_13 {
timeZone: attendee.timeZone,
language: attendee.locale,
absent: !!attendee.noShow,
phoneNumber: attendee.phoneNumber ?? undefined,
})),
guests: bookingResponses.guests,
location,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export class OutputEventTypesService_2024_06_14 {
const bookingFields = databaseEventType.bookingFields
? this.transformBookingFields(databaseEventType.bookingFields)
: this.getDefaultBookingFields(isOrgTeamEvent);

const recurrence = this.transformRecurringEvent(databaseEventType.recurringEvent);
const metadata = this.transformMetadata(databaseEventType.metadata) || {};
const users = this.transformUsers(databaseEventType.users || []);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,98 @@ describe("Organizations Event Types Endpoints", () => {
});
});

it("should be able to configure phone-only event type", async () => {
const body: UpdateTeamEventTypeInput_2024_06_14 = {
bookingFields: [
{
type: "email",
Copy link
Contributor

Choose a reason for hiding this comment

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

Like before - shouldn't we also omit email booking field here?

required: false,
label: "Email",
},
{
type: "phone",
slug: "attendeePhoneNumber",
required: true,
label: "Phone number",
},
],
};

return request(app.getHttpServer())
.patch(`/v2/organizations/${org.id}/teams/${team.id}/event-types/${collectiveEventType.id}`)
.send(body)
.expect(200)
.then(async (response) => {
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
const data = responseBody.data;
expect(data.bookingFields).toEqual([
{
isDefault: true,
type: "name",
slug: "name",
required: true,
disableOnPrefill: false,
},
{
isDefault: true,
type: "email",
slug: "email",
required: false,
label: "Email",
disableOnPrefill: false,
},
{
isDefault: true,
type: "radioInput",
slug: "location",
required: false,
disableOnPrefill: false,
hidden: false,
},
{
isDefault: true,
type: "phone",
slug: "attendeePhoneNumber",
required: true,
hidden: true,
},
{
isDefault: true,
type: "text",
slug: "title",
required: true,
disableOnPrefill: false,
hidden: true,
},
{
isDefault: true,
type: "multiemail",
slug: "guests",
required: false,
disableOnPrefill: false,
hidden: false,
},
{
isDefault: true,
type: "textarea",
slug: "rescheduleReason",
required: false,
disableOnPrefill: false,
hidden: false,
},
{
isDefault: true,
type: "textarea",
slug: "notes",
required: false,
disableOnPrefill: false,
hidden: false,
},
]);
});
});

it("should assign all members to managed event-type", async () => {
const body: UpdateTeamEventTypeInput_2024_06_14 = {
assignAllTeamMembers: true,
Expand Down
Loading
Loading