diff --git a/src/server/api/export-multiple-campaigns.ts b/src/server/api/export-multiple-campaigns.ts new file mode 100644 index 000000000..be727d0eb --- /dev/null +++ b/src/server/api/export-multiple-campaigns.ts @@ -0,0 +1,10 @@ +import type { CampaignExportMetaData } from "../lib/templates/export-multiple-campaigns"; +import getEmailContent from "../lib/templates/export-multiple-campaigns"; + +const formatMultipleCampaignExportsEmail = ( + campaignExportMetaData: CampaignExportMetaData +) => { + return getEmailContent(campaignExportMetaData); +}; + +export default formatMultipleCampaignExportsEmail; diff --git a/src/server/lib/templates/export-campaign.tsx b/src/server/lib/templates/export-campaign.tsx index f5b2a9752..7a0e360e5 100644 --- a/src/server/lib/templates/export-campaign.tsx +++ b/src/server/lib/templates/export-campaign.tsx @@ -7,13 +7,12 @@ export type ExportURLs = { campaignOptOutsExportUrl?: string | undefined; campaignFilteredContactsExportUrl?: string | undefined; }; - interface ExportProps { exportUrls: ExportURLs; campaignTitle: string; } -const ExportCampaign: React.FC = ({ +export const ExportCampaign: React.FC = ({ exportUrls, campaignTitle }) => { diff --git a/src/server/lib/templates/export-multiple-campaigns.tsx b/src/server/lib/templates/export-multiple-campaigns.tsx new file mode 100644 index 000000000..ee20dc460 --- /dev/null +++ b/src/server/lib/templates/export-multiple-campaigns.tsx @@ -0,0 +1,79 @@ +import React from "react"; +import ReactDOMServer from "react-dom/server"; + +import type { ExportURLs } from "./export-campaign"; + +export type CampaignExportDetails = { + campaignTitle: string; + exportUrls: ExportURLs | null; +}; + +export type CampaignExportMetaData = { + [id: string]: CampaignExportDetails; +}; + +interface Props { + campaignExportMetaData: CampaignExportMetaData; +} + +const ExportMultipleCampaigns: React.FC = ({ + campaignExportMetaData +}) => { + const campaignIds = Object.keys(campaignExportMetaData); + return ( +
+

+ Your spoke exports are ready! These URLs will be valid for 24 hours. +

+ {campaignIds.map((campaignId) => { + const { exportUrls, campaignTitle } = campaignExportMetaData[ + campaignId + ]; + // this is checked before rendering the component, but satisfying typescript here + if (!exportUrls) { + throw new Error( + "attempted to render email component without export urls" + ); + } + const { + campaignExportUrl, + campaignMessagesExportUrl, + campaignOptOutsExportUrl, + campaignFilteredContactsExportUrl + } = exportUrls; + return ( + <> +

+ {campaignTitle} - ID: {campaignId} +

+ {campaignExportUrl &&

Campaign Export: {campaignExportUrl}

} + {campaignMessagesExportUrl && ( +

Campaign Messages Export: {campaignMessagesExportUrl}

+ )} + {campaignOptOutsExportUrl && ( +

Campaign OptOuts Export: {campaignOptOutsExportUrl}

+ )} + {campaignFilteredContactsExportUrl && ( +

+ Campaign Filtered Contacts Export:{" "} + {campaignFilteredContactsExportUrl} +

+ )} + + ); + })} +
+ -- The Spoke Rewired Team +
+ ); +}; + +const getEmailContent = (campaignExportMetaData: CampaignExportMetaData) => { + const template = ( + + ); + + return ReactDOMServer.renderToStaticMarkup(template); +}; + +export default getEmailContent; diff --git a/src/server/tasks/export-multiple-campaigns.ts b/src/server/tasks/export-multiple-campaigns.ts index d899f3e55..376eb8f79 100644 --- a/src/server/tasks/export-multiple-campaigns.ts +++ b/src/server/tasks/export-multiple-campaigns.ts @@ -1,5 +1,7 @@ import DateTime from "../../lib/datetime"; +import formatMultipleCampaignExportsEmail from "../api/export-multiple-campaigns"; import type { JobRequestRecord } from "../api/types"; +import type { CampaignExportMetaData } from "../lib/templates/export-multiple-campaigns"; import sendEmail from "../mail"; import { r } from "../models"; import type { ExportCampaignPayload } from "./export-campaign"; @@ -56,7 +58,9 @@ export const exportCampaignForBulkOperation: ProgressTask }; const exportUrls = await processExportData(campaignMetaData, spokeOptions); - helpers.logger.info(`Successfully exported ${campaignId}`); + helpers.logger.info( + `Exported data for campaign: ${campaignTitle} - ID ${campaignId}` + ); // store exportUrls in job_request table await helpers.updateResult({ message: JSON.stringify(exportUrls) }); @@ -77,13 +81,14 @@ export const sendEmailForBulkExportOperation: ProgressTask> = {}; + const campaignMetaDataMap: CampaignExportMetaData = {}; for (const id of campaignIds) { const parsed = parseInt(id, 10); const { campaignTitle } = await fetchExportData(parsed, requesterId); campaignMetaDataMap[id] = { campaignTitle, exportUrls: null }; } + const jobRequestIds = jobRequestRecords.map((record) => record.id); // query job_req table for campaign export urls where status is 100 (exports complete) const { rows } = await helpers.query( ` @@ -92,9 +97,12 @@ export const sendEmailForBulkExportOperation: ProgressTask rec.id)] + [jobRequestIds] ); + // wait for all campaign exports to process + const exportsStillProcessing = rows.length !== campaignIds.length; + // map query result to campaign id and parse JSON const campaignExportsMap = rows.map((result) => ({ campaignId: result.campaign_id, @@ -103,34 +111,44 @@ export const sendEmailForBulkExportOperation: ProgressTask