From cfefba6d4d8fd5c48bffd3fd2faca60f2fae93a2 Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Date: Thu, 7 Aug 2025 14:55:13 -0400 Subject: [PATCH] Revert "perf: use repository for webhooks list query & caching in /settings/developer/webhooks/... RSC (#21781)" (#22963) --- .../webhooks/(with-loader)/actions.ts | 6 +- .../developer/webhooks/(with-loader)/page.tsx | 20 +---- .../developer/webhooks/[id]/actions.ts | 7 -- .../developer/webhooks/[id]/page.tsx | 15 +--- .../developer/webhooks/new/actions.ts | 7 -- .../developer/webhooks/new/page.tsx | 28 ++----- .../webhooks/components/WebhookListItem.tsx | 13 +-- .../webhooks/pages/webhook-edit-view.tsx | 10 +-- .../webhooks/pages/webhook-new-view.tsx | 6 +- packages/lib/server/repository/webhook.ts | 83 ------------------- .../routers/viewer/webhook/list.handler.ts | 54 +++++++++++- 11 files changed, 79 insertions(+), 170 deletions(-) delete mode 100644 apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/[id]/actions.ts delete mode 100644 apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/new/actions.ts diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions.ts b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions.ts index adab4c4a7e..fd9836607c 100644 --- a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions.ts +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions.ts @@ -1,7 +1,7 @@ "use server"; -import { revalidateTag } from "next/cache"; +import { revalidatePath } from "next/cache"; -export async function revalidateWebhooksListGetByViewer() { - revalidateTag("viewer.webhook.getByViewer"); +export async function revalidateWebhooksList() { + revalidatePath("/settings/developer/webhooks"); } diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/page.tsx b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/page.tsx index aedc702dfb..93174bcd5f 100644 --- a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/page.tsx +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/page.tsx @@ -1,13 +1,13 @@ +import { createRouterCaller } from "app/_trpc/context"; import { _generateMetadata } from "app/_utils"; -import { unstable_cache } from "next/cache"; import { cookies, headers } from "next/headers"; import { redirect } from "next/navigation"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import WebhooksView from "@calcom/features/webhooks/pages/webhooks-view"; import { APP_NAME } from "@calcom/lib/constants"; -import { WebhookRepository } from "@calcom/lib/server/repository/webhook"; import { UserPermissionRole } from "@calcom/prisma/enums"; +import { webhookRouter } from "@calcom/trpc/server/routers/viewer/webhook/_router"; import { buildLegacyRequest } from "@lib/buildLegacyCtx"; @@ -20,17 +20,6 @@ export const generateMetadata = async () => "/settings/developer/webhooks" ); -const getCachedWebhooksList = unstable_cache( - async ({ userId, userRole }: { userId: number; userRole?: UserPermissionRole }) => { - return await WebhookRepository.getAllWebhooksByUserId({ - userId, - userRole, - }); - }, - undefined, - { revalidate: 3600, tags: ["viewer.webhook.getByViewer"] } -); - const WebhooksViewServerWrapper = async () => { const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) }); if (!session?.user?.id) { @@ -38,9 +27,8 @@ const WebhooksViewServerWrapper = async () => { } const isAdmin = session.user.role === UserPermissionRole.ADMIN; - const userRole = session.user.role !== "INACTIVE_ADMIN" ? session.user.role : undefined; - - const data = await getCachedWebhooksList({ userId: session.user.id, userRole }); + const caller = await createRouterCaller(webhookRouter); + const data = await caller.getByViewer(); return ; }; diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/[id]/actions.ts b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/[id]/actions.ts deleted file mode 100644 index 0c0de6176c..0000000000 --- a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/[id]/actions.ts +++ /dev/null @@ -1,7 +0,0 @@ -"use server"; - -import { revalidateTag } from "next/cache"; - -export async function revalidateWebhookById(id: string) { - revalidateTag(`viewer.webhook.get:${id}`); -} diff --git a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/[id]/page.tsx b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/[id]/page.tsx index 37fce01dce..173de93ab2 100644 --- a/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/[id]/page.tsx +++ b/apps/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/[id]/page.tsx @@ -1,6 +1,5 @@ import type { PageProps } from "app/_types"; import { getTranslate, _generateMetadata } from "app/_utils"; -import { unstable_cache } from "next/cache"; import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader"; import { EditWebhookView } from "@calcom/features/webhooks/pages/webhook-edit-view"; @@ -16,24 +15,12 @@ export const generateMetadata = async ({ params }: { params: Promise<{ id: strin `/settings/developer/webhooks/${(await params).id}` ); -const getCachedWebhook = (id?: string) => { - const fn = unstable_cache( - async () => { - return await WebhookRepository.findByWebhookId(id); - }, - undefined, - { revalidate: 3600, tags: [`viewer.webhook.get:${id}`] } - ); - - return fn(); -}; - const Page = async ({ params: _params }: PageProps) => { const t = await getTranslate(); const params = await _params; const id = typeof params?.id === "string" ? params.id : undefined; - const webhook = await getCachedWebhook(id); + const webhook = await WebhookRepository.findByWebhookId(id); return ( await _generateMetadata( @@ -21,25 +15,15 @@ export const generateMetadata = async () => "/settings/developer/webhooks/new" ); -const getCachedWebhooksList = unstable_cache( - async ({ userId }: { userId: number }) => { - return await WebhookRepository.findWebhooksByFilters({ userId }); - }, - undefined, - { revalidate: 3600, tags: ["viewer.webhook.list"] } -); - const Page = async () => { - const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) }); - if (!session?.user?.id) { - redirect("/auth/login"); - } - - const appsCaller = await createRouterCaller(appsRouter); + const [appsCaller, webhookCaller] = await Promise.all([ + createRouterCaller(appsRouter), + createRouterCaller(webhookRouter), + ]); const [installedApps, webhooks] = await Promise.all([ appsCaller.integrations({ variant: "other", onlyInstalled: true }), - getCachedWebhooksList({ userId: session.user.id }), + webhookCaller.list(), ]); return ; diff --git a/packages/features/webhooks/components/WebhookListItem.tsx b/packages/features/webhooks/components/WebhookListItem.tsx index aa9177d853..1d354da071 100644 --- a/packages/features/webhooks/components/WebhookListItem.tsx +++ b/packages/features/webhooks/components/WebhookListItem.tsx @@ -18,8 +18,7 @@ import { Switch } from "@calcom/ui/components/form"; import { showToast } from "@calcom/ui/components/toast"; import { Tooltip } from "@calcom/ui/components/tooltip"; import { revalidateEventTypeEditPage } from "@calcom/web/app/(use-page-wrapper)/event-types/[type]/actions"; -import { revalidateWebhooksListGetByViewer } from "@calcom/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions"; -import { revalidateWebhookList } from "@calcom/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/new/actions"; +import { revalidateWebhooksList } from "@calcom/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions"; type WebhookProps = { id: string; @@ -47,19 +46,21 @@ export default function WebhookListItem(props: { const deleteWebhook = trpc.viewer.webhook.delete.useMutation({ async onSuccess() { if (webhook.eventTypeId) revalidateEventTypeEditPage(webhook.eventTypeId); - revalidateWebhooksListGetByViewer(); - revalidateWebhookList(); + revalidateWebhooksList(); showToast(t("webhook_removed_successfully"), "success"); + await utils.viewer.webhook.getByViewer.invalidate(); + await utils.viewer.webhook.list.invalidate(); await utils.viewer.eventTypes.get.invalidate(); }, }); const toggleWebhook = trpc.viewer.webhook.edit.useMutation({ async onSuccess(data) { if (webhook.eventTypeId) revalidateEventTypeEditPage(webhook.eventTypeId); - revalidateWebhooksListGetByViewer(); - revalidateWebhookList(); + revalidateWebhooksList(); // TODO: Better success message showToast(t(data?.active ? "enabled" : "disabled"), "success"); + await utils.viewer.webhook.getByViewer.invalidate(); + await utils.viewer.webhook.list.invalidate(); await utils.viewer.eventTypes.get.invalidate(); }, }); diff --git a/packages/features/webhooks/pages/webhook-edit-view.tsx b/packages/features/webhooks/pages/webhook-edit-view.tsx index c09eb4cd57..48d87332cb 100644 --- a/packages/features/webhooks/pages/webhook-edit-view.tsx +++ b/packages/features/webhooks/pages/webhook-edit-view.tsx @@ -7,9 +7,7 @@ import type { WebhookTriggerEvents } from "@calcom/prisma/enums"; import { trpc } from "@calcom/trpc/react"; import { SkeletonContainer } from "@calcom/ui/components/skeleton"; import { showToast } from "@calcom/ui/components/toast"; -import { revalidateWebhooksListGetByViewer } from "@calcom/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions"; -import { revalidateWebhookById } from "@calcom/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/[id]/actions"; -import { revalidateWebhookList } from "@calcom/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/new/actions"; +import { revalidateWebhooksList } from "@calcom/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions"; import type { WebhookFormSubmitData } from "../components/WebhookForm"; import WebhookForm from "../components/WebhookForm"; @@ -45,10 +43,10 @@ export function EditWebhookView({ webhook }: { webhook?: WebhookProps }) { }); const editWebhookMutation = trpc.viewer.webhook.edit.useMutation({ async onSuccess() { - revalidateWebhookById(webhook?.id ?? ""); - revalidateWebhookList(); + await utils.viewer.webhook.list.invalidate(); + await utils.viewer.webhook.get.invalidate({ webhookId: webhook?.id }); showToast(t("webhook_updated_successfully"), "success"); - revalidateWebhooksListGetByViewer(); + revalidateWebhooksList(); router.push("/settings/developer/webhooks"); }, onError(error) { diff --git a/packages/features/webhooks/pages/webhook-new-view.tsx b/packages/features/webhooks/pages/webhook-new-view.tsx index 06cc853641..c2f5551197 100644 --- a/packages/features/webhooks/pages/webhook-new-view.tsx +++ b/packages/features/webhooks/pages/webhook-new-view.tsx @@ -10,8 +10,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; import type { RouterOutputs } from "@calcom/trpc/react"; import { showToast } from "@calcom/ui/components/toast"; -import { revalidateWebhooksListGetByViewer } from "@calcom/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions"; -import { revalidateWebhookList } from "@calcom/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/new/actions"; +import { revalidateWebhooksList } from "@calcom/web/app/(use-page-wrapper)/settings/(settings-layout)/developer/webhooks/(with-loader)/actions"; import type { WebhookFormSubmitData } from "../components/WebhookForm"; import WebhookForm from "../components/WebhookForm"; @@ -36,8 +35,7 @@ export const NewWebhookView = ({ webhooks, installedApps }: Props) => { async onSuccess() { showToast(t("webhook_created_successfully"), "success"); await utils.viewer.webhook.list.invalidate(); - revalidateWebhookList(); - revalidateWebhooksListGetByViewer(); + revalidateWebhooksList(); router.push("/settings/developer/webhooks"); }, onError(error) { diff --git a/packages/lib/server/repository/webhook.ts b/packages/lib/server/repository/webhook.ts index 48eaca8293..a17cf3f86a 100644 --- a/packages/lib/server/repository/webhook.ts +++ b/packages/lib/server/repository/webhook.ts @@ -1,7 +1,3 @@ -import type { Prisma } from "@prisma/client"; -import { z } from "zod"; - -import { WEBHOOK_TRIGGER_EVENTS } from "@calcom/features/webhooks/lib/constants"; import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage"; import { compareMembership } from "@calcom/lib/event-types/getEventTypesByViewer"; import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl"; @@ -23,24 +19,6 @@ type WebhookGroup = { webhooks: Webhook[]; }; -const webhookIdAndEventTypeIdSchema = z.object({ - // Webhook ID - id: z.string().optional(), - eventTypeId: z.number().optional(), - teamId: z.number().optional(), -}); - -const ZFindWebhooksByFiltersInputSchema = webhookIdAndEventTypeIdSchema - .extend({ - appId: z.string().optional(), - teamId: z.number().optional(), - eventTypeId: z.number().optional(), - eventTriggers: z.enum(WEBHOOK_TRIGGER_EVENTS).array().optional(), - }) - .optional(); - -export type TFindWebhooksByFiltersInputSchema = z.infer; - const filterWebhooks = (webhook: Webhook) => { const appIds = [ "zapier", @@ -202,65 +180,4 @@ export class WebhookRepository { }, }); } - - static async findWebhooksByFilters({ - userId, - input, - }: { - userId: number; - input?: TFindWebhooksByFiltersInputSchema; - }) { - const where: Prisma.WebhookWhereInput = { - /* Don't mixup zapier webhooks with normal ones */ - AND: [{ appId: !input?.appId ? null : input.appId }], - }; - - const user = await prisma.user.findUnique({ - where: { - id: userId, - }, - select: { - teams: true, - }, - }); - - if (Array.isArray(where.AND)) { - if (input?.eventTypeId) { - const managedParentEvt = await prisma.eventType.findFirst({ - where: { - id: input.eventTypeId, - parentId: { - not: null, - }, - }, - select: { - parentId: true, - }, - }); - - if (managedParentEvt?.parentId) { - where.AND?.push({ - OR: [ - { eventTypeId: input.eventTypeId }, - { eventTypeId: managedParentEvt.parentId, active: true }, - ], - }); - } else { - where.AND?.push({ eventTypeId: input.eventTypeId }); - } - } else { - where.AND?.push({ - OR: [{ userId }, { teamId: { in: user?.teams.map((membership) => membership.teamId) } }], - }); - } - - if (input?.eventTriggers) { - where.AND?.push({ eventTriggers: { hasEvery: input.eventTriggers } }); - } - } - - return await prisma.webhook.findMany({ - where, - }); - } } diff --git a/packages/trpc/server/routers/viewer/webhook/list.handler.ts b/packages/trpc/server/routers/viewer/webhook/list.handler.ts index 11205add04..120223ec81 100644 --- a/packages/trpc/server/routers/viewer/webhook/list.handler.ts +++ b/packages/trpc/server/routers/viewer/webhook/list.handler.ts @@ -1,4 +1,6 @@ -import { WebhookRepository } from "@calcom/lib/server/repository/webhook"; +import type { Prisma } from "@prisma/client"; + +import { prisma } from "@calcom/prisma"; import type { TrpcSessionUser } from "@calcom/trpc/server/types"; import type { TListInputSchema } from "./list.schema"; @@ -11,5 +13,53 @@ type ListOptions = { }; export const listHandler = async ({ ctx, input }: ListOptions) => { - return await WebhookRepository.findWebhooksByFilters({ userId: ctx.user.id, input }); + const where: Prisma.WebhookWhereInput = { + /* Don't mixup zapier webhooks with normal ones */ + AND: [{ appId: !input?.appId ? null : input.appId }], + }; + + const user = await prisma.user.findUnique({ + where: { + id: ctx.user.id, + }, + select: { + teams: true, + }, + }); + + if (Array.isArray(where.AND)) { + if (input?.eventTypeId) { + const managedParentEvt = await prisma.eventType.findFirst({ + where: { + id: input.eventTypeId, + parentId: { + not: null, + }, + }, + select: { + parentId: true, + }, + }); + + if (managedParentEvt?.parentId) { + where.AND?.push({ + OR: [{ eventTypeId: input.eventTypeId }, { eventTypeId: managedParentEvt.parentId, active: true }], + }); + } else { + where.AND?.push({ eventTypeId: input.eventTypeId }); + } + } else { + where.AND?.push({ + OR: [{ userId: ctx.user.id }, { teamId: { in: user?.teams.map((membership) => membership.teamId) } }], + }); + } + + if (input?.eventTriggers) { + where.AND?.push({ eventTriggers: { hasEvery: input.eventTriggers } }); + } + } + + return await prisma.webhook.findMany({ + where, + }); };