Skip to content

Commit

Permalink
feat: booking no show webhook (#15502)
Browse files Browse the repository at this point in the history
* WIP

Signed-off-by: zomars <[email protected]>

* WIP

Signed-off-by: zomars <[email protected]>

* WIP

Signed-off-by: zomars <[email protected]>

* Type fixes

* Update webhook.e2e.ts

* Update noShow.handler.ts

* Log failed results

* Updates tests

* Show generic error on frontend.

* test: add basic webhook service test

* fix: webhook end to end test

* fix: type error

* fix: test

---------

Signed-off-by: zomars <[email protected]>
Co-authored-by: sean-brydon <[email protected]>
Co-authored-by: Udit Takkar <[email protected]>
Co-authored-by: Udit Takkar <[email protected]>
  • Loading branch information
4 people authored Jun 27, 2024
1 parent efc00d6 commit 6be842f
Show file tree
Hide file tree
Showing 14 changed files with 371 additions and 156 deletions.
31 changes: 13 additions & 18 deletions apps/web/components/booking/BookingListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Link from "next/link";
import { useState } from "react";
import { useForm, Controller, useFieldArray } from "react-hook-form";
import { Controller, useFieldArray, useForm } from "react-hook-form";

import type { EventLocationType, getEventLocationValue } from "@calcom/app-store/locations";
import {
Expand Down Expand Up @@ -31,20 +31,20 @@ import {
DialogClose,
DialogContent,
DialogFooter,
Dropdown,
DropdownItem,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
Icon,
MeetingTimeInTimezones,
showToast,
TableActions,
TextAreaField,
Tooltip,
Dropdown,
DropdownMenuContent,
DropdownMenuTrigger,
DropdownMenuItem,
DropdownItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuCheckboxItem,
} from "@calcom/ui";

import { ChargeCardDialog } from "@components/dialog/ChargeCardDialog";
Expand Down Expand Up @@ -691,13 +691,8 @@ const Attendee = (attendeeProps: AttendeeProps & NoShowProps) => {
const { copyToClipboard, isCopied } = useCopy();

const noShowMutation = trpc.viewer.public.noShow.useMutation({
onSuccess: async () => {
showToast(
t(noShow ? "x_marked_as_no_show" : "x_unmarked_as_no_show", {
x: name || email,
}),
"success"
);
onSuccess: async (data) => {
showToast(t(data.message, { x: name || email }), "success");
},
onError: (err) => {
showToast(err.message, "error");
Expand Down Expand Up @@ -804,8 +799,8 @@ const GroupedAttendees = (groupedAttendeeProps: GroupedAttendeeProps) => {
});
const { t } = useLocale();
const noShowMutation = trpc.viewer.public.noShow.useMutation({
onSuccess: async () => {
showToast(t("no_show_updated"), "success");
onSuccess: async (data) => {
showToast(t(data.message), "success");
},
onError: (err) => {
showToast(err.message, "error");
Expand Down
21 changes: 19 additions & 2 deletions apps/web/playwright/bookings-list.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ test.describe("Bookings", () => {
});
});
test.describe("Past bookings", () => {
test("Mark first guest as no-show", async ({ page, users, bookings }) => {
test("Mark first guest as no-show", async ({ page, users, bookings, webhooks }) => {
const firstUser = await users.create();
const secondUser = await users.create();

Expand All @@ -81,8 +81,8 @@ test.describe("Bookings", () => {
],
});
const bookingWhereFirstUserIsOrganizer = await bookingWhereFirstUserIsOrganizerFixture.self();

await firstUser.apiLogin();
const webhookReceiver = await webhooks.createReceiver();
await page.goto(`/bookings/past`);
const pastBookings = page.locator('[data-testid="past-bookings"]');
const firstPastBooking = pastBookings.locator('[data-testid="booking-item"]').nth(0);
Expand All @@ -95,6 +95,23 @@ test.describe("Bookings", () => {
await firstGuest.click();
await expect(titleAndAttendees.locator('[data-testid="unmark-no-show"]')).toBeVisible();
await expect(titleAndAttendees.locator('[data-testid="mark-no-show"]')).toBeHidden();
await webhookReceiver.waitForRequestCount(1);
const [request] = webhookReceiver.requestList;
const body = request.body;
// remove dynamic properties that differs depending on where you run the tests
const dynamic = "[redacted/dynamic]";
// @ts-expect-error we are modifying the object
body.createdAt = dynamic;
expect(body).toMatchObject({
triggerEvent: "BOOKING_NO_SHOW_UPDATED",
createdAt: "[redacted/dynamic]",
payload: {
message: "[email protected] marked as no-show",
attendees: [{ email: "[email protected]", noShow: true, utcOffset: null }],
bookingUid: bookingWhereFirstUserIsOrganizer?.uid,
},
});
webhookReceiver.close();
});
test("Mark 3rd attendee as no-show", async ({ page, users, bookings }) => {
const firstUser = await users.create();
Expand Down
39 changes: 39 additions & 0 deletions apps/web/playwright/fixtures/webhooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { expect, type Page } from "@playwright/test";

import { createHttpServer } from "../lib/testUtils";

export function createWebhookPageFixture(page: Page) {
return {
createTeamReceiver: async () => {
const webhookReceiver = createHttpServer();
await page.goto(`/settings/developer/webhooks`);
await page.click('[data-testid="new_webhook"]');
await page.click('[data-testid="option-team-1"]');
await page.waitForURL((u) => u.pathname === "/settings/developer/webhooks/new");
const url = page.url();
const teamId = Number(new URL(url).searchParams.get("teamId")) as number;
await page.click('[data-testid="new_webhook"]');
await page.fill('[name="subscriberUrl"]', webhookReceiver.url);
await page.fill('[name="secret"]', "secret");
await Promise.all([
page.click("[type=submit]"),
page.waitForURL((url) => url.pathname.endsWith("/settings/developer/webhooks")),
]);
expect(page.locator(`text='${webhookReceiver.url}'`)).toBeDefined();
return { webhookReceiver, teamId };
},
createReceiver: async () => {
const webhookReceiver = createHttpServer();
await page.goto(`/settings/developer/webhooks`);
await page.click('[data-testid="new_webhook"]');
await page.fill('[name="subscriberUrl"]', webhookReceiver.url);
await page.fill('[name="secret"]', "secret");
await Promise.all([
page.click("[type=submit]"),
page.waitForURL((url) => url.pathname.endsWith("/settings/developer/webhooks")),
]);
expect(page.locator(`text='${webhookReceiver.url}'`)).toBeDefined();
return webhookReceiver;
},
};
}
6 changes: 6 additions & 0 deletions apps/web/playwright/lib/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { createBookingPageFixture } from "../fixtures/regularBookings";
import { createRoutingFormsFixture } from "../fixtures/routingForms";
import { createServersFixture } from "../fixtures/servers";
import { createUsersFixture } from "../fixtures/users";
import { createWebhookPageFixture } from "../fixtures/webhooks";
import { createWorkflowPageFixture } from "../fixtures/workflows";

export interface Fixtures {
Expand All @@ -34,6 +35,7 @@ export interface Fixtures {
features: ReturnType<typeof createFeatureFixture>;
eventTypePage: ReturnType<typeof createEventTypeFixture>;
appsPage: ReturnType<typeof createAppsFixture>;
webhooks: ReturnType<typeof createWebhookPageFixture>;
}

declare global {
Expand Down Expand Up @@ -110,4 +112,8 @@ export const test = base.extend<Fixtures>({
const appsPage = createAppsFixture(page);
await use(appsPage);
},
webhooks: async ({ page }, use) => {
const webhooks = createWebhookPageFixture(page);
await use(webhooks);
},
});
Loading

0 comments on commit 6be842f

Please sign in to comment.