-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3c2619a
commit 3dff0ca
Showing
22 changed files
with
779 additions
and
580 deletions.
There are no files selected for viewing
12 changes: 12 additions & 0 deletions
12
packages/app-store/routing-forms/lib/getServerTimingHeader.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
export function getServerTimingHeader(timeTaken: Record<string, number | null | undefined>) { | ||
const headerParts = Object.entries(timeTaken) | ||
.map(([key, value]) => { | ||
if (value !== null && value !== undefined) { | ||
return `${key};dur=${value}`; | ||
} | ||
return null; | ||
}) | ||
.filter(Boolean); | ||
|
||
return headerParts.join(", "); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
import { Prisma } from "@prisma/client"; | ||
import { z } from "zod"; | ||
|
||
import { emailSchema } from "@calcom/lib/emailSchema"; | ||
import logger from "@calcom/lib/logger"; | ||
import { findTeamMembersMatchingAttributeLogic } from "@calcom/lib/raqb/findTeamMembersMatchingAttributeLogic"; | ||
import { safeStringify } from "@calcom/lib/safeStringify"; | ||
import { prisma } from "@calcom/prisma"; | ||
import type { App_RoutingForms_Form } from "@calcom/prisma/client"; | ||
import { RoutingFormSettings } from "@calcom/prisma/zod-utils"; | ||
import { TRPCError } from "@calcom/trpc/server"; | ||
|
||
import isRouter from "../lib/isRouter"; | ||
import type { ZResponseInputSchema } from "../trpc/response.schema"; | ||
import { onFormSubmission } from "../trpc/utils"; | ||
import type { FormResponse, SerializableForm } from "../types/types"; | ||
|
||
const moduleLogger = logger.getSubLogger({ prefix: ["routing-forms/lib/handleResponse"] }); | ||
|
||
export const handleResponse = async ({ | ||
response, | ||
form, | ||
// formFillerId, | ||
chosenRouteId, | ||
}: { | ||
response: z.infer<typeof ZResponseInputSchema>["response"]; | ||
form: SerializableForm< | ||
App_RoutingForms_Form & { | ||
user: { | ||
id: number; | ||
email: string; | ||
}; | ||
team: { | ||
parentId: number | null; | ||
} | null; | ||
} | ||
>; | ||
formFillerId: string; | ||
chosenRouteId: string | null; | ||
}) => { | ||
try { | ||
if (!form.fields) { | ||
// There is no point in submitting a form that doesn't have fields defined | ||
throw new TRPCError({ | ||
code: "BAD_REQUEST", | ||
}); | ||
} | ||
|
||
const formTeamId = form.teamId; | ||
const formOrgId = form.team?.parentId ?? null; | ||
const serializableFormWithFields = { | ||
...form, | ||
fields: form.fields, | ||
}; | ||
|
||
const missingFields = serializableFormWithFields.fields | ||
.filter((field) => !(field.required ? response[field.id]?.value : true)) | ||
.map((f) => f.label); | ||
|
||
if (missingFields.length) { | ||
throw new TRPCError({ | ||
code: "BAD_REQUEST", | ||
message: `Missing required fields ${missingFields.join(", ")}`, | ||
}); | ||
} | ||
const invalidFields = serializableFormWithFields.fields | ||
.filter((field) => { | ||
const fieldValue = response[field.id]?.value; | ||
// The field isn't required at this point. Validate only if it's set | ||
if (!fieldValue) { | ||
return false; | ||
} | ||
let schema; | ||
if (field.type === "email") { | ||
schema = emailSchema; | ||
} else if (field.type === "phone") { | ||
schema = z.any(); | ||
} else { | ||
schema = z.any(); | ||
} | ||
return !schema.safeParse(fieldValue).success; | ||
}) | ||
.map((f) => ({ label: f.label, type: f.type, value: response[f.id]?.value })); | ||
|
||
if (invalidFields.length) { | ||
throw new TRPCError({ | ||
code: "BAD_REQUEST", | ||
message: `Invalid value for fields ${invalidFields | ||
.map((f) => `'${f.label}' with value '${f.value}' should be valid ${f.type}`) | ||
.join(", ")}`, | ||
}); | ||
} | ||
|
||
const settings = RoutingFormSettings.parse(form.settings); | ||
let userWithEmails: string[] = []; | ||
if (form.teamId && settings?.sendUpdatesTo?.length) { | ||
const userEmails = await prisma.membership.findMany({ | ||
where: { | ||
teamId: form.teamId, | ||
userId: { | ||
in: settings.sendUpdatesTo, | ||
}, | ||
}, | ||
select: { | ||
user: { | ||
select: { | ||
email: true, | ||
}, | ||
}, | ||
}, | ||
}); | ||
userWithEmails = userEmails.map((userEmail) => userEmail.user.email); | ||
} | ||
|
||
const chosenRoute = serializableFormWithFields.routes?.find((route) => route.id === chosenRouteId); | ||
let teamMemberIdsMatchingAttributeLogic: number[] | null = null; | ||
let timeTaken: Record<string, number | null> = {}; | ||
if (chosenRoute) { | ||
if (isRouter(chosenRoute)) { | ||
throw new TRPCError({ | ||
code: "BAD_REQUEST", | ||
message: "Chosen route is a router", | ||
}); | ||
} | ||
|
||
const teamMembersMatchingAttributeLogicWithResult = | ||
formTeamId && formOrgId | ||
? await findTeamMembersMatchingAttributeLogic( | ||
{ | ||
dynamicFieldValueOperands: { | ||
response, | ||
fields: form.fields || [], | ||
}, | ||
attributesQueryValue: chosenRoute.attributesQueryValue ?? null, | ||
fallbackAttributesQueryValue: chosenRoute.fallbackAttributesQueryValue, | ||
teamId: formTeamId, | ||
orgId: formOrgId, | ||
}, | ||
{ | ||
enablePerf: true, | ||
} | ||
) | ||
: null; | ||
|
||
moduleLogger.debug( | ||
"teamMembersMatchingAttributeLogic", | ||
safeStringify({ teamMembersMatchingAttributeLogicWithResult }) | ||
); | ||
|
||
teamMemberIdsMatchingAttributeLogic = | ||
teamMembersMatchingAttributeLogicWithResult?.teamMembersMatchingAttributeLogic | ||
? teamMembersMatchingAttributeLogicWithResult.teamMembersMatchingAttributeLogic.map( | ||
(member) => member.userId | ||
) | ||
: null; | ||
|
||
timeTaken = teamMembersMatchingAttributeLogicWithResult?.timeTaken ?? {}; | ||
} else { | ||
// 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, | ||
}, | ||
}); | ||
|
||
await onFormSubmission( | ||
{ ...serializableFormWithFields, userWithEmails }, | ||
dbFormResponse.response as FormResponse, | ||
dbFormResponse.id, | ||
chosenRoute ? ("action" in chosenRoute ? chosenRoute.action : undefined) : undefined | ||
); | ||
|
||
return { | ||
isPreview: false, | ||
formResponse: dbFormResponse, | ||
teamMembersMatchingAttributeLogic: teamMemberIdsMatchingAttributeLogic, | ||
attributeRoutingConfig: chosenRoute | ||
? "attributeRoutingConfig" in chosenRoute | ||
? chosenRoute.attributeRoutingConfig | ||
: null | ||
: null, | ||
timeTaken, | ||
}; | ||
} catch (e) { | ||
if (e instanceof Prisma.PrismaClientKnownRequestError) { | ||
if (e.code === "P2002") { | ||
throw new TRPCError({ | ||
code: "CONFLICT", | ||
}); | ||
} | ||
} | ||
throw e; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.