feat: "cal.isBookingDryRun" to be used for Routing form as well (#18832)

This commit is contained in:
Hariom Balhara authored and GitHub committed 2025-01-24 09:27:18 +05:30
1 parent 161aece3a1
commit b3f946dd0b
6 files changed
+57 -25

No files matched your search

@@ -1,7 +1,6 @@
import type { GetServerSidePropsContext } from "next";
import { stringify } from "querystring";
import z from "zod";
import { GetServerSidePropsContext } from "next";
import { enrichFormWithMigrationData } from "@calcom/app-store/routing-forms/enrichFormWithMigrationData";
import { getAbsoluteEventTypeRedirectUrlWithEmbedSupport } from "@calcom/app-store/routing-forms/getEventTypeRedirectUrl";
import getFieldIdentifier from "@calcom/app-store/routing-forms/lib/getFieldIdentifier";
@@ -20,7 +19,6 @@ import { RoutingFormRepository } from "@calcom/lib/server/repository/routingForm
import { TRPCError } from "@calcom/trpc/server";
const log = logger.getSubLogger({ prefix: ["[routing-forms]", "[router]"] });
const querySchema = z
.object({
form: z.string(),
@@ -46,8 +44,16 @@ export const getServerSideProps = async function getServerSideProps(context: Get
};
}
// Known params reserved by Cal.com are form, embed, layout. We should exclude all of them.
const { form: formId, ...fieldsResponses } = queryParsed.data;
// TODO: Known params reserved by Cal.com are form, embed, layout and other cal. prefixed params. We should exclude all of them from fieldsResponses.
// But they must be present in `paramsToBeForwardedAsIs` as they could be needed by Booking Page as well.
const { form: formId, "cal.isBookingDryRun": isBookingDryRunParam, ...fieldsResponses } = queryParsed.data;
const isBookingDryRun = isBookingDryRunParam === "true";
const paramsToBeForwardedAsIs = {
...fieldsResponses,
// Must be forwarded if present to Booking Page. Setting it explicitly here as it is critical to be present in the URL.
...(isBookingDryRunParam ? { "cal.isBookingDryRun": isBookingDryRunParam } : null),
};
const { currentOrgDomain } = orgDomainConfig(context.req);
let timeTaken: Record<string, number | null> = {};
@@ -115,6 +121,7 @@ export const getServerSideProps = async function getServerSideProps(context: Get
formFillerId: uuidv4(),
response: response,
chosenRouteId: matchingRoute.id,
isPreview: isBookingDryRun,
});
teamMembersMatchingAttributeLogic = result.teamMembersMatchingAttributeLogic;
formResponseId = result.formResponse.id;
@@ -162,7 +169,7 @@ export const getServerSideProps = async function getServerSideProps(context: Get
allURLSearchParams: getUrlSearchParamsToForward({
formResponse: response,
fields: serializableForm.fields,
searchParams: new URLSearchParams(stringify(fieldsResponses)),
searchParams: new URLSearchParams(stringify(paramsToBeForwardedAsIs)),
teamMembersMatchingAttributeLogic,
// formResponseId is guaranteed to be set because in catch block of trpc request we return from the function and otherwise it would have been set
formResponseId: formResponseId!,
@@ -35,11 +35,13 @@ export const handleResponse = async ({
// Unused but probably should be used
// formFillerId,
chosenRouteId,
isPreview,
}: {
response: z.infer<typeof ZResponseInputSchema>["response"];
form: Form;
formFillerId: string;
chosenRouteId: string | null;
isPreview: boolean;
}) => {
try {
if (!form.fields) {
@@ -162,25 +164,39 @@ export const handleResponse = async ({
// It currently happens for a Router route. Such a route id isn't present in the form.routes
}
const dbFormResponse = await prisma.app_RoutingForms_FormResponse.create({
data: {
// TODO: Why do we not save formFillerId available in the input?
// formFillerId,
formId: form.id,
response: response,
chosenRouteId,
},
});
let dbFormResponse;
if (!isPreview) {
dbFormResponse = await prisma.app_RoutingForms_FormResponse.create({
data: {
// TODO: Why do we not save formFillerId available in the input?
// formFillerId,
formId: form.id,
response: response,
chosenRouteId,
},
});
await onFormSubmission(
{ ...serializableFormWithFields, userWithEmails },
dbFormResponse.response as FormResponse,
dbFormResponse.id,
chosenRoute ? ("action" in chosenRoute ? chosenRoute.action : undefined) : undefined
);
await onFormSubmission(
{ ...serializableFormWithFields, userWithEmails },
dbFormResponse.response as FormResponse,
dbFormResponse.id,
chosenRoute ? ("action" in chosenRoute ? chosenRoute.action : undefined) : undefined
);
} else {
moduleLogger.debug("Dry run mode - Form response not stored and also webhooks and emails not sent");
// Create a mock response for dry run
dbFormResponse = {
id: 0,
formId: form.id,
response,
chosenRouteId,
createdAt: new Date(),
updatedAt: new Date(),
};
}
return {
isPreview: false,
isPreview: !!isPreview,
formResponse: dbFormResponse,
teamMembersMatchingAttributeLogic: teamMemberIdsMatchingAttributeLogic,
attributeRoutingConfig: chosenRoute
@@ -54,6 +54,8 @@ function RoutingForm({ form, profile, ...restProps }: Props) {
});
const [response, setResponse] = usePrefilledResponse(form);
const pageSearchParams = useCompatSearchParams();
const isBookingDryRun = pageSearchParams?.get("cal.isBookingDryRun") === "true";
// TODO: We might want to prevent spam from a single user by having same formFillerId across pageviews
// But technically, a user can fill form multiple times due to any number of reasons and we currently can't differentiate b/w that.
@@ -79,6 +81,7 @@ function RoutingForm({ form, profile, ...restProps }: Props) {
formFillerId,
response: response,
chosenRouteId: chosenRoute.id,
isPreview: isBookingDryRun,
});
chosenRouteWithFormResponseRef.current = {
@@ -92,6 +92,9 @@ export function getFieldResponse({
};
}
/**
* Not called in preview mode or dry run mode
*/
export async function onFormSubmission(
form: Ensure<
SerializableForm<App_RoutingForms_Form> & { user: Pick<User, "id" | "email">; userWithEmails?: string[] },
@@ -73,7 +73,10 @@ export const BookEventForm = ({
// Cloudflare Turnstile Captcha
const shouldRenderCaptcha =
renderCaptcha && CLOUDFLARE_SITE_ID && CLOUDFLARE_USE_TURNSTILE_IN_BOOKER === "1";
!process.env.NEXT_PUBLIC_IS_E2E &&
renderCaptcha &&
CLOUDFLARE_SITE_ID &&
CLOUDFLARE_USE_TURNSTILE_IN_BOOKER === "1";
const [responseVercelIdHeader] = useState<string | null>(null);
const { t } = useLocale();
@@ -13,7 +13,7 @@ interface ResponseHandlerOptions {
}
export const responseHandler = async ({ ctx, input }: ResponseHandlerOptions) => {
const { prisma } = ctx;
const { formId, response, formFillerId, chosenRouteId = null } = input;
const { formId, response, formFillerId, chosenRouteId = null, isPreview = false } = input;
const form = await prisma.app_RoutingForms_Form.findFirst({
where: {
id: formId,
@@ -43,7 +43,7 @@ export const responseHandler = async ({ ctx, input }: ResponseHandlerOptions) =>
form,
});
return handleResponse({ response, form: serializableForm, formFillerId, chosenRouteId });
return handleResponse({ response, form: serializableForm, formFillerId, chosenRouteId, isPreview });
};
export default responseHandler;