* perf: routing forms loading time * chore: add TODO * chore * chore: add type * chore: use Promise.all * fix: type err * fix: fetch id * refactor: don't fetch team * chore: add name
124 lines
3.8 KiB
TypeScript
124 lines
3.8 KiB
TypeScript
import type { App_RoutingForms_Form } from "@prisma/client";
|
|
import type { NextApiRequest, NextApiResponse } from "next";
|
|
import { getSession } from "next-auth/react";
|
|
|
|
import { sanitizeValue } from "@calcom/lib/csvUtils";
|
|
import { entityPrismaWhereClause, canEditEntity } from "@calcom/lib/entityPermissionUtils";
|
|
import prisma from "@calcom/prisma";
|
|
|
|
import { getSerializableForm } from "../../lib/getSerializableForm";
|
|
import { ensureStringOrStringArray, getLabelsFromOptionIds } from "../../lib/reportingUtils";
|
|
import type { FormResponse, SerializableForm } from "../../types/types";
|
|
|
|
type Fields = NonNullable<SerializableForm<App_RoutingForms_Form>["fields"]>;
|
|
|
|
function getHumanReadableFieldResponseValue({
|
|
field,
|
|
value,
|
|
}: {
|
|
field: Fields[number];
|
|
value: string | number | string[];
|
|
}) {
|
|
if (field.options) {
|
|
const optionIds = ensureStringOrStringArray(value);
|
|
return getLabelsFromOptionIds({ options: field.options, optionIds });
|
|
} else {
|
|
return (value instanceof Array ? value : [value]).map(String);
|
|
}
|
|
}
|
|
|
|
async function* getResponses(formId: string, fields: Fields) {
|
|
let responses;
|
|
let skip = 0;
|
|
// Keep it small enough to be in Vercel limits of Serverless Function in terms of memory.
|
|
// To avoid limit in terms of execution time there is an RFC https://linear.app/calcom/issue/CAL-204/rfc-routing-form-improved-csv-exports
|
|
const take = 100;
|
|
while (
|
|
(responses = await prisma.app_RoutingForms_FormResponse.findMany({
|
|
where: {
|
|
formId,
|
|
},
|
|
take: take,
|
|
skip: skip,
|
|
})) &&
|
|
responses.length
|
|
) {
|
|
const csv: string[] = [];
|
|
responses.forEach((response) => {
|
|
const fieldResponses = response.response as FormResponse;
|
|
const csvCells: string[] = [];
|
|
fields.forEach((field) => {
|
|
const fieldResponse = fieldResponses[field.id];
|
|
const value = fieldResponse?.value || "";
|
|
const readableValues = getHumanReadableFieldResponseValue({ field, value });
|
|
const serializedValue = readableValues.map((value) => sanitizeValue(value)).join(" | ");
|
|
csvCells.push(serializedValue);
|
|
});
|
|
csvCells.push(response.createdAt.toISOString());
|
|
csv.push(csvCells.join(","));
|
|
});
|
|
skip += take;
|
|
yield csv.join("\n");
|
|
}
|
|
return "";
|
|
}
|
|
|
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
const { args } = req.query;
|
|
|
|
if (!args) {
|
|
throw new Error("args must be set");
|
|
}
|
|
const formId = args[2];
|
|
if (!formId) {
|
|
throw new Error("formId must be provided");
|
|
}
|
|
|
|
const session = await getSession({ req });
|
|
|
|
if (!session) {
|
|
return res.status(401).json({ message: "Unauthorized" });
|
|
}
|
|
|
|
const { user } = session;
|
|
|
|
const form = await prisma.app_RoutingForms_Form.findFirst({
|
|
where: {
|
|
id: formId,
|
|
...entityPrismaWhereClause({ userId: user.id }),
|
|
},
|
|
});
|
|
|
|
if (!form) {
|
|
return res.status(404).json({ message: "Form not found or unauthorized" });
|
|
}
|
|
|
|
if (!(await canEditEntity(form, user.id))) {
|
|
return res.status(404).json({ message: "Form not found or unauthorized" });
|
|
}
|
|
|
|
const serializableForm = await getSerializableForm({ form, withDeletedFields: true });
|
|
res.setHeader("Content-Type", "text/csv; charset=UTF-8");
|
|
res.setHeader(
|
|
"Content-Disposition",
|
|
`attachment; filename="${serializableForm.name}-${serializableForm.id}.csv"`
|
|
);
|
|
res.setHeader("Transfer-Encoding", "chunked");
|
|
const headerFields = serializableForm.fields || [];
|
|
const csvIterator = getResponses(formId, headerFields);
|
|
|
|
// Make Header
|
|
res.write(
|
|
`${headerFields
|
|
.map((field) => `${field.label}${field.deleted ? "(Deleted)" : ""}`)
|
|
.concat(["Submission Time"])
|
|
.join(",")}\n`
|
|
);
|
|
|
|
for await (const partialCsv of csvIterator) {
|
|
res.write(partialCsv);
|
|
res.write("\n");
|
|
}
|
|
res.end();
|
|
}
|