-
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
4f73fa3
commit acba639
Showing
12 changed files
with
599 additions
and
373 deletions.
There are no files selected for viewing
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,7 @@ | ||
- [ ] findFirst -> findUnique for Form | ||
- [ ] Abstract out response handling fn and reuse in /router and /response | ||
- [ ] Pass on orgId directly to findTeamMembersMatchingAttributeLogic | ||
- [ ] Slowest query seems to be allAssignedOptionsForTheOrg | ||
- Don't query attributes in it. We query it already in another fn | ||
- Changed the querying approach completely doing some calculations ourselves | ||
- [ ] Measure _getMembershipsWithOrgAndTeam perf with 6000 membership entries for orgId and |
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,198 @@ | ||
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 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", | ||
}); | ||
} | ||
|
||
console.log("before findTeamMembersMatchingAttributeLogic"); | ||
const teamMembersMatchingAttributeLogicWithResult = form.teamId | ||
? await findTeamMembersMatchingAttributeLogic( | ||
{ | ||
dynamicFieldValueOperands: { | ||
response, | ||
fields: form.fields || [], | ||
}, | ||
attributesQueryValue: chosenRoute.attributesQueryValue ?? null, | ||
fallbackAttributesQueryValue: chosenRoute.fallbackAttributesQueryValue, | ||
teamId: form.teamId, | ||
orgId: form.team?.parentId ?? null, | ||
}, | ||
{ | ||
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
Oops, something went wrong.