From 1582ac13da6bfb76a27d59de61a56a883b48843d Mon Sep 17 00:00:00 2001 From: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com> Date: Tue, 21 Nov 2023 19:30:31 +0530 Subject: [PATCH] fix: Weekly summary endpoint failing (#1440) Co-authored-by: Johannes --- apps/web/app/api/cron/weekly_summary/route.ts | 202 ++++++++++-------- 1 file changed, 109 insertions(+), 93 deletions(-) diff --git a/apps/web/app/api/cron/weekly_summary/route.ts b/apps/web/app/api/cron/weekly_summary/route.ts index de3f5d90ba..a999b872b2 100644 --- a/apps/web/app/api/cron/weekly_summary/route.ts +++ b/apps/web/app/api/cron/weekly_summary/route.ts @@ -6,122 +6,78 @@ import { NextResponse } from "next/server"; import { sendNoLiveSurveyNotificationEmail, sendWeeklySummaryNotificationEmail } from "./email"; import { EnvironmentData, NotificationResponse, ProductData, Survey, SurveyResponse } from "./types"; +const BATCH_SIZE = 10; + export async function POST(): Promise { - // check authentication with x-api-key header and CRON_SECRET env variable + // Check authentication if (headers().get("x-api-key") !== CRON_SECRET) { return responses.notAuthenticatedResponse(); } - // list of email sending promises to wait for const emailSendingPromises: Promise[] = []; - const products = await getProducts(); + // Fetch all team IDs + const teamIds = await getTeamIds(); - // iterate through the products and send weekly summary email to each team member - for await (const product of products) { - // check if there are team members that have weekly summary notification enabled - const teamMembers = product.team.memberships; - const teamMembersWithNotificationEnabled = teamMembers.filter((member) => { - return ( - member.user.notificationSettings?.weeklySummary && - member.user.notificationSettings.weeklySummary[product.id] - ); - }); - // if there are no team members with weekly summary notification enabled, skip to the next product (do not send email) - if (teamMembersWithNotificationEnabled.length == 0) { - continue; - } - // calculate insights for the product - const notificationResponse = getNotificationResponse(product.environments[0], product.name); + // Paginate through teams + for (let i = 0; i < teamIds.length; i += BATCH_SIZE) { + const batchedTeamIds = teamIds.slice(i, i + BATCH_SIZE); + // Fetch products for batched teams asynchronously + const batchedProductsPromises = batchedTeamIds.map((teamId) => getProductsByTeamId(teamId)); - // if there were no responses in the last 7 days, send a different email - if (notificationResponse.insights.numLiveSurvey == 0) { - for (const teamMember of teamMembersWithNotificationEnabled) { - emailSendingPromises.push( - sendNoLiveSurveyNotificationEmail(teamMember.user.email, notificationResponse) + const batchedProducts = await Promise.all(batchedProductsPromises); + for (const products of batchedProducts) { + for (const product of products) { + const teamMembers = product.team.memberships; + const teamMembersWithNotificationEnabled = teamMembers.filter( + (member) => + member.user.notificationSettings?.weeklySummary && + member.user.notificationSettings.weeklySummary[product.id] ); - } - continue; - } - // send weekly summary email - for (const teamMember of teamMembersWithNotificationEnabled) { - emailSendingPromises.push( - sendWeeklySummaryNotificationEmail(teamMember.user.email, notificationResponse) - ); + if (teamMembersWithNotificationEnabled.length === 0) continue; + + const notificationResponse = getNotificationResponse(product.environments[0], product.name); + + if (notificationResponse.insights.numLiveSurvey === 0) { + for (const teamMember of teamMembersWithNotificationEnabled) { + emailSendingPromises.push( + sendNoLiveSurveyNotificationEmail(teamMember.user.email, notificationResponse) + ); + } + continue; + } + + for (const teamMember of teamMembersWithNotificationEnabled) { + emailSendingPromises.push( + sendWeeklySummaryNotificationEmail(teamMember.user.email, notificationResponse) + ); + } + } } } - // wait for all emails to be sent + await Promise.all(emailSendingPromises); return responses.successResponse({}, true); } -const getNotificationResponse = (environment: EnvironmentData, productName: string): NotificationResponse => { - const insights = { - totalCompletedResponses: 0, - totalDisplays: 0, - totalResponses: 0, - completionRate: 0, - numLiveSurvey: 0, - }; - - const surveys: Survey[] = []; - - // iterate through the surveys and calculate the overall insights - for (const survey of environment.surveys) { - const surveyData: Survey = { - id: survey.id, - name: survey.name, - status: survey.status, - responseCount: survey.responses.length, - responses: [], - }; - // iterate through the responses and calculate the survey insights - for (const response of survey.responses) { - // only take the first 3 responses - if (surveyData.responses.length >= 1) { - break; - } - const surveyResponse: SurveyResponse = {}; - for (const question of survey.questions) { - const headline = question.headline; - const answer = response.data[question.id]?.toString() || null; - if (answer === null || answer === "" || answer?.length === 0) { - continue; - } - surveyResponse[headline] = answer; - } - surveyData.responses.push(surveyResponse); - } - surveys.push(surveyData); - // calculate the overall insights - if (survey.status == "inProgress") { - insights.numLiveSurvey += 1; - } - insights.totalCompletedResponses += survey.responses.filter((r) => r.finished).length; - insights.totalDisplays += survey.displays.length; - insights.totalResponses += survey.responses.length; - insights.completionRate = Math.round((insights.totalCompletedResponses / insights.totalResponses) * 100); - } - // build the notification response needed for the emails - const lastWeekDate = new Date(); - lastWeekDate.setDate(lastWeekDate.getDate() - 7); - return { - environmentId: environment.id, - currentDate: new Date(), - lastWeekDate, - productName: productName, - surveys, - insights, - }; +const getTeamIds = async (): Promise => { + const teams = await prisma.team.findMany({ + select: { + id: true, + }, + }); + return teams.map((team) => team.id); }; -const getProducts = async (): Promise => { - // gets all products together with team members, surveys, responses, and displays for the last 7 days +const getProductsByTeamId = async (teamId: string): Promise => { const sevenDaysAgo = new Date(); sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); return await prisma.product.findMany({ + where: { + teamId: teamId, + }, select: { id: true, name: true, @@ -204,3 +160,63 @@ const getProducts = async (): Promise => { }, }); }; + +const getNotificationResponse = (environment: EnvironmentData, productName: string): NotificationResponse => { + const insights = { + totalCompletedResponses: 0, + totalDisplays: 0, + totalResponses: 0, + completionRate: 0, + numLiveSurvey: 0, + }; + + const surveys: Survey[] = []; + + // iterate through the surveys and calculate the overall insights + for (const survey of environment.surveys) { + const surveyData: Survey = { + id: survey.id, + name: survey.name, + status: survey.status, + responseCount: survey.responses.length, + responses: [], + }; + // iterate through the responses and calculate the survey insights + for (const response of survey.responses) { + // only take the first 3 responses + if (surveyData.responses.length >= 1) { + break; + } + const surveyResponse: SurveyResponse = {}; + for (const question of survey.questions) { + const headline = question.headline; + const answer = response.data[question.id]?.toString() || null; + if (answer === null || answer === "" || answer?.length === 0) { + continue; + } + surveyResponse[headline] = answer; + } + surveyData.responses.push(surveyResponse); + } + surveys.push(surveyData); + // calculate the overall insights + if (survey.status == "inProgress") { + insights.numLiveSurvey += 1; + } + insights.totalCompletedResponses += survey.responses.filter((r) => r.finished).length; + insights.totalDisplays += survey.displays.length; + insights.totalResponses += survey.responses.length; + insights.completionRate = Math.round((insights.totalCompletedResponses / insights.totalResponses) * 100); + } + // build the notification response needed for the emails + const lastWeekDate = new Date(); + lastWeekDate.setDate(lastWeekDate.getDate() - 7); + return { + environmentId: environment.id, + currentDate: new Date(), + lastWeekDate, + productName: productName, + surveys, + insights, + }; +};