From dac4adf8cefc892a278ddee594ced8b9c85d5ff1 Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Tue, 30 Jul 2024 15:23:57 -0400 Subject: [PATCH 01/28] new means of creating a bucket --- src/workers/jobs.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/workers/jobs.js b/src/workers/jobs.js index cf2453fb4..05a94bacd 100644 --- a/src/workers/jobs.js +++ b/src/workers/jobs.js @@ -28,7 +28,7 @@ import { rawIngestMethod } from "../extensions/contact-loaders"; import { Lambda } from "@aws-sdk/client-lambda"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; -import { GetObjectCommand, S3 } from "@aws-sdk/client-s3"; +import { CreateBucketCommand, GetObjectCommand, S3, waitUntilBucketExists, S3Client } from "@aws-sdk/client-s3"; import { SQS } from "@aws-sdk/client-sqs"; import Papa from "papaparse"; import moment from "moment"; @@ -861,12 +861,17 @@ export async function exportCampaign(job) { (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) ) { try { - const s3bucket = new S3({ - // The transformation for params is not implemented. - // Refer to UPGRADING.md on aws-sdk-js-v3 for changes needed. - // Please create/upvote feature request on aws-sdk-js-codemod for params. - params: { Bucket: process.env.AWS_S3_BUCKET_NAME } + const s3bucket = new S3Client({ + // S3 endpoint: US East (Ohio) + region: "us-east-2" }); + + const Bucket = process.env.AWS_S3_BUCKET_NAME; + const command = new CreateBucketCommand({ Bucket }); + + await s3bucket.send(command); + await waitUntilBucketExists({ s3bucket, maxWaitTime: 60 }, { Bucket }); + const campaignTitle = campaign.title .replace(/ /g, "_") .replace(/\//g, "_"); @@ -877,7 +882,7 @@ export async function exportCampaign(job) { let params = { Key: key, Body: campaignCsv }; await s3bucket.putObject(params); params = { Key: key, Expires: 86400 }; - const campaignExportUrl = await await getSignedUrl(s3bucket, new GetObjectCommand(params), { + const campaignExportUrl = await getSignedUrl(s3bucket, new GetObjectCommand(params), { expiresIn: "/* add value from 'Expires' from v2 call if present, else remove */" }); params = { Key: messageKey, Body: messageCsv }; From 98b85637cdeba62a0a5892666ecf80e557d0cdb6 Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Tue, 30 Jul 2024 15:25:54 -0400 Subject: [PATCH 02/28] remove now unused S3 import --- src/workers/jobs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workers/jobs.js b/src/workers/jobs.js index 05a94bacd..61522f8cc 100644 --- a/src/workers/jobs.js +++ b/src/workers/jobs.js @@ -28,7 +28,7 @@ import { rawIngestMethod } from "../extensions/contact-loaders"; import { Lambda } from "@aws-sdk/client-lambda"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; -import { CreateBucketCommand, GetObjectCommand, S3, waitUntilBucketExists, S3Client } from "@aws-sdk/client-s3"; +import { CreateBucketCommand, GetObjectCommand, waitUntilBucketExists, S3Client } from "@aws-sdk/client-s3"; import { SQS } from "@aws-sdk/client-sqs"; import Papa from "papaparse"; import moment from "moment"; From 8338ac1a65616803e2cf53ba1d1c08e6e8c93645 Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Tue, 30 Jul 2024 15:26:09 -0400 Subject: [PATCH 03/28] remove double await that was doing nothing --- src/workers/jobs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workers/jobs.js b/src/workers/jobs.js index 61522f8cc..0dd72bd63 100644 --- a/src/workers/jobs.js +++ b/src/workers/jobs.js @@ -888,7 +888,7 @@ export async function exportCampaign(job) { params = { Key: messageKey, Body: messageCsv }; await s3bucket.putObject(params); params = { Key: messageKey, Expires: 86400 }; - const campaignMessagesExportUrl = await await getSignedUrl(s3bucket, new GetObjectCommand(params), { + const campaignMessagesExportUrl = await getSignedUrl(s3bucket, new GetObjectCommand(params), { expiresIn: "/* add value from 'Expires' from v2 call if present, else remove */" }); exportResults.campaignExportUrl = campaignExportUrl; From 8cc52b8649f339d4c25237d22b73c380deb7bd17 Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Tue, 30 Jul 2024 15:29:19 -0400 Subject: [PATCH 04/28] remove expires in parameter in getSignedURL that is taken care of in the variable params --- src/workers/jobs.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/workers/jobs.js b/src/workers/jobs.js index 0dd72bd63..40504b623 100644 --- a/src/workers/jobs.js +++ b/src/workers/jobs.js @@ -882,15 +882,11 @@ export async function exportCampaign(job) { let params = { Key: key, Body: campaignCsv }; await s3bucket.putObject(params); params = { Key: key, Expires: 86400 }; - const campaignExportUrl = await getSignedUrl(s3bucket, new GetObjectCommand(params), { - expiresIn: "/* add value from 'Expires' from v2 call if present, else remove */" - }); + const campaignExportUrl = await getSignedUrl(s3bucket, new GetObjectCommand(params)); params = { Key: messageKey, Body: messageCsv }; await s3bucket.putObject(params); params = { Key: messageKey, Expires: 86400 }; - const campaignMessagesExportUrl = await getSignedUrl(s3bucket, new GetObjectCommand(params), { - expiresIn: "/* add value from 'Expires' from v2 call if present, else remove */" - }); + const campaignMessagesExportUrl = await getSignedUrl(s3bucket, new GetObjectCommand(params)); exportResults.campaignExportUrl = campaignExportUrl; exportResults.campaignMessagesExportUrl = campaignMessagesExportUrl; From 43fd02db058fce558eacb5b5ece19b1cc6ea2113 Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Tue, 30 Jul 2024 15:31:19 -0400 Subject: [PATCH 05/28] change var name s3bucket to client + comment --- src/workers/jobs.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/workers/jobs.js b/src/workers/jobs.js index 40504b623..feb72cb27 100644 --- a/src/workers/jobs.js +++ b/src/workers/jobs.js @@ -861,7 +861,7 @@ export async function exportCampaign(job) { (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) ) { try { - const s3bucket = new S3Client({ + const client = new S3Client({ // S3 endpoint: US East (Ohio) region: "us-east-2" }); @@ -869,8 +869,9 @@ export async function exportCampaign(job) { const Bucket = process.env.AWS_S3_BUCKET_NAME; const command = new CreateBucketCommand({ Bucket }); - await s3bucket.send(command); - await waitUntilBucketExists({ s3bucket, maxWaitTime: 60 }, { Bucket }); + await client.send(command); + // verifies that the bucket exists before moving forward + await waitUntilBucketExists({ client, maxWaitTime: 60 }, { Bucket }); const campaignTitle = campaign.title .replace(/ /g, "_") @@ -880,13 +881,13 @@ export async function exportCampaign(job) { )}.csv`; const messageKey = `${key}-messages.csv`; let params = { Key: key, Body: campaignCsv }; - await s3bucket.putObject(params); + await client.putObject(params); params = { Key: key, Expires: 86400 }; - const campaignExportUrl = await getSignedUrl(s3bucket, new GetObjectCommand(params)); + const campaignExportUrl = await getSignedUrl(client, new GetObjectCommand(params)); params = { Key: messageKey, Body: messageCsv }; - await s3bucket.putObject(params); + await client.putObject(params); params = { Key: messageKey, Expires: 86400 }; - const campaignMessagesExportUrl = await getSignedUrl(s3bucket, new GetObjectCommand(params)); + const campaignMessagesExportUrl = await getSignedUrl(client, new GetObjectCommand(params)); exportResults.campaignExportUrl = campaignExportUrl; exportResults.campaignMessagesExportUrl = campaignMessagesExportUrl; From 3fcab793ff43b29044f6daa4ccdcde76ec80b3e7 Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Tue, 30 Jul 2024 17:05:28 -0400 Subject: [PATCH 06/28] call region from env --- src/workers/jobs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workers/jobs.js b/src/workers/jobs.js index feb72cb27..a1a8c6a45 100644 --- a/src/workers/jobs.js +++ b/src/workers/jobs.js @@ -863,7 +863,7 @@ export async function exportCampaign(job) { try { const client = new S3Client({ // S3 endpoint: US East (Ohio) - region: "us-east-2" + region: process.env.AWS_REGION }); const Bucket = process.env.AWS_S3_BUCKET_NAME; From af2bddd59499b6d789e9e689e751883153148438 Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Tue, 30 Jul 2024 17:08:12 -0400 Subject: [PATCH 07/28] move bucket name to func call. add location var found in example docs --- src/workers/jobs.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/workers/jobs.js b/src/workers/jobs.js index a1a8c6a45..cc46458a7 100644 --- a/src/workers/jobs.js +++ b/src/workers/jobs.js @@ -866,10 +866,11 @@ export async function exportCampaign(job) { region: process.env.AWS_REGION }); - const Bucket = process.env.AWS_S3_BUCKET_NAME; - const command = new CreateBucketCommand({ Bucket }); + const command = new CreateBucketCommand({ Bucket : process.env.AWS_S3_BUCKET_NAME }); + + const Location = await client.send(command); + console.log(`Bucket created with location ${location}`); - await client.send(command); // verifies that the bucket exists before moving forward await waitUntilBucketExists({ client, maxWaitTime: 60 }, { Bucket }); From 6818d2ae9deddcbce659ed035ceeb14367cc0131 Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Tue, 30 Jul 2024 17:45:07 -0400 Subject: [PATCH 08/28] implement PutObjectCommand w/ supporting parameters. --- src/workers/jobs.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/workers/jobs.js b/src/workers/jobs.js index cc46458a7..e3f600355 100644 --- a/src/workers/jobs.js +++ b/src/workers/jobs.js @@ -28,7 +28,7 @@ import { rawIngestMethod } from "../extensions/contact-loaders"; import { Lambda } from "@aws-sdk/client-lambda"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; -import { CreateBucketCommand, GetObjectCommand, waitUntilBucketExists, S3Client } from "@aws-sdk/client-s3"; +import { CreateBucketCommand, GetObjectCommand, waitUntilBucketExists, S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; import { SQS } from "@aws-sdk/client-sqs"; import Papa from "papaparse"; import moment from "moment"; @@ -862,17 +862,17 @@ export async function exportCampaign(job) { ) { try { const client = new S3Client({ - // S3 endpoint: US East (Ohio) region: process.env.AWS_REGION }); + const bucketName = process.env.AWS_S3_BUCKET_NAME; + const command = new CreateBucketCommand({ Bucket : bucketName }); - const command = new CreateBucketCommand({ Bucket : process.env.AWS_S3_BUCKET_NAME }); - - const Location = await client.send(command); - console.log(`Bucket created with location ${location}`); + // this can return Location and $metadata, we just don't need that info + await client.send(command); // verifies that the bucket exists before moving forward - await waitUntilBucketExists({ client, maxWaitTime: 60 }, { Bucket }); + // if for some reason this fails, Spoke defensively deletes the job + await waitUntilBucketExists({ client, maxWaitTime: 60 }, { Bucket : bucketName }); const campaignTitle = campaign.title .replace(/ /g, "_") @@ -881,17 +881,22 @@ export async function exportCampaign(job) { "YYYY-MM-DD-HH-mm-ss" )}.csv`; const messageKey = `${key}-messages.csv`; - let params = { Key: key, Body: campaignCsv }; - await client.putObject(params); - params = { Key: key, Expires: 86400 }; + let params = { Key: key, Body: campaignCsv, Bucket: bucketName }; + await client.send(new PutObjectCommand(params)); + params = { Key: key, Expires: 86400, Bucket: bucketName }; const campaignExportUrl = await getSignedUrl(client, new GetObjectCommand(params)); - params = { Key: messageKey, Body: messageCsv }; - await client.putObject(params); - params = { Key: messageKey, Expires: 86400 }; + params = { Key: messageKey, Body: messageCsv, Bucket: bucketName }; + await client.send(new PutObjectCommand(params)); + params = { Key: messageKey, Expires: 86400,Bucket: bucketName }; const campaignMessagesExportUrl = await getSignedUrl(client, new GetObjectCommand(params)); exportResults.campaignExportUrl = campaignExportUrl; exportResults.campaignMessagesExportUrl = campaignMessagesExportUrl; + console.log(` + campaignExportUrl: ${campaignExportUrl}\n + campaignMessageExportUrl: ${campaignMessagesExportUrl} + `) + await sendEmail({ to: user.email, subject: `Export ready for ${campaign.title}`, From 0a52da79ded7911a7dd24a815ec95b56b587e992 Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Tue, 30 Jul 2024 17:50:42 -0400 Subject: [PATCH 09/28] make pretty --- src/workers/jobs.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/workers/jobs.js b/src/workers/jobs.js index e3f600355..14ec1a11a 100644 --- a/src/workers/jobs.js +++ b/src/workers/jobs.js @@ -881,13 +881,21 @@ export async function exportCampaign(job) { "YYYY-MM-DD-HH-mm-ss" )}.csv`; const messageKey = `${key}-messages.csv`; - let params = { Key: key, Body: campaignCsv, Bucket: bucketName }; + let params = { Key: key, + Body: campaignCsv, + Bucket: bucketName }; await client.send(new PutObjectCommand(params)); - params = { Key: key, Expires: 86400, Bucket: bucketName }; + params = { Key: key, + Expires: 86400, + Bucket: bucketName }; const campaignExportUrl = await getSignedUrl(client, new GetObjectCommand(params)); - params = { Key: messageKey, Body: messageCsv, Bucket: bucketName }; + params = { Key: messageKey, + Body: messageCsv, + Bucket: bucketName }; await client.send(new PutObjectCommand(params)); - params = { Key: messageKey, Expires: 86400,Bucket: bucketName }; + params = { Key: messageKey, + Expires: 86400, + Bucket: bucketName }; const campaignMessagesExportUrl = await getSignedUrl(client, new GetObjectCommand(params)); exportResults.campaignExportUrl = campaignExportUrl; exportResults.campaignMessagesExportUrl = campaignMessagesExportUrl; From 78c2aaa2f8e87b02080fb1056b3be578796107c7 Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Tue, 30 Jul 2024 18:00:55 -0400 Subject: [PATCH 10/28] remove logging --- src/workers/jobs.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/workers/jobs.js b/src/workers/jobs.js index 14ec1a11a..b8c084e8c 100644 --- a/src/workers/jobs.js +++ b/src/workers/jobs.js @@ -900,11 +900,6 @@ export async function exportCampaign(job) { exportResults.campaignExportUrl = campaignExportUrl; exportResults.campaignMessagesExportUrl = campaignMessagesExportUrl; - console.log(` - campaignExportUrl: ${campaignExportUrl}\n - campaignMessageExportUrl: ${campaignMessagesExportUrl} - `) - await sendEmail({ to: user.email, subject: `Export ready for ${campaign.title}`, From 2c101dce73c2dde626afac5faf5961f5364d940f Mon Sep 17 00:00:00 2001 From: Rueben Engelhart Date: Wed, 31 Jul 2024 17:29:11 -0400 Subject: [PATCH 11/28] add front end language for when export will end up in local Spoke directory --- src/containers/AdminCampaignStats.jsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/containers/AdminCampaignStats.jsx b/src/containers/AdminCampaignStats.jsx index 7db3bc25a..be8b6601f 100644 --- a/src/containers/AdminCampaignStats.jsx +++ b/src/containers/AdminCampaignStats.jsx @@ -424,9 +424,10 @@ class AdminCampaignStats extends React.Component { message={ Export started - - {this.props.organizationData && - this.props.organizationData.emailEnabled && - " we'll e-mail you when it's done. "} + {(this.props.organizationData && + this.props.organizationData.emailEnabled) ? + " we'll e-mail you when it's done. " : + " check your Spoke directory. "} {campaign.cacheable && ( Date: Fri, 2 Aug 2024 11:28:55 -0400 Subject: [PATCH 12/28] add hard check on email set-up. Next step is to (re-add) fix front end links regardless of email set-up. Then, if AWS is not set up, maybe create a direct email stream? --- src/workers/jobs.js | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/workers/jobs.js b/src/workers/jobs.js index b8c084e8c..cc7ade9b6 100644 --- a/src/workers/jobs.js +++ b/src/workers/jobs.js @@ -900,18 +900,35 @@ export async function exportCampaign(job) { exportResults.campaignExportUrl = campaignExportUrl; exportResults.campaignMessagesExportUrl = campaignMessagesExportUrl; - await sendEmail({ - to: user.email, - subject: `Export ready for ${campaign.title}`, - text: `Your Spoke exports are ready! These URLs will be valid for 24 hours. - Campaign export: ${campaignExportUrl} - Message export: ${campaignMessagesExportUrl}` - }).catch(err => { - log.error(err); - log.info(`Campaign Export URL - ${campaignExportUrl}`); - log.info(`Campaign Messages Export URL - ${campaignMessagesExportUrl}`); - }); - log.info(`Successfully exported ${id}`); + // extreme check on email set-up + if (( + process.env.EMAIL_FROM && + process.env.EMAIL_HOST && + process.env.EMAIL_HOST_PASSOWRD && + process.env.EMAIL_HOST_PORT && + process.env.EMAIL_HOST_USER) || + ( + process.env.MAILGUN_DOMIAN && + process.env.MAILGUN_SMTP_LOGN && + process.env.MAILGUN_SMTP_PASSWORD && + process.env.MAILGUN_SMTP_PORT && + process.env.MAILGUN_SMTP_SERVER && + process.env.MAILGUN_PUBLIC_KEY + ) + ) { + await sendEmail({ + to: user.email, + subject: `Export ready for ${campaign.title}`, + text: `Your Spoke exports are ready! These URLs will be valid for 24 hours. + Campaign export: ${campaignExportUrl} + Message export: ${campaignMessagesExportUrl}` + }).catch(err => { + log.error(err); + log.info(`Campaign Export URL - ${campaignExportUrl}`); + log.info(`Campaign Messages Export URL - ${campaignMessagesExportUrl}`); + }); + log.info(`Successfully exported ${id}`); + } } catch (err) { log.error(err); exportResults.error = err.message; From 87538cfe1365f462dda0648f795eed592091fd1c Mon Sep 17 00:00:00 2001 From: "Maureen E. Zitouni" Date: Wed, 14 Aug 2024 04:28:10 -0400 Subject: [PATCH 13/28] Add error handling when exporting to an S3 bucket --- src/workers/jobs.js | 49 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/src/workers/jobs.js b/src/workers/jobs.js index cc7ade9b6..aa3d279eb 100644 --- a/src/workers/jobs.js +++ b/src/workers/jobs.js @@ -28,7 +28,14 @@ import { rawIngestMethod } from "../extensions/contact-loaders"; import { Lambda } from "@aws-sdk/client-lambda"; import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; -import { CreateBucketCommand, GetObjectCommand, waitUntilBucketExists, S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; +import { + CreateBucketCommand, + HeadBucketCommand, + GetObjectCommand, + waitUntilBucketExists, + S3Client, + PutObjectCommand +} from "@aws-sdk/client-s3"; import { SQS } from "@aws-sdk/client-sqs"; import Papa from "papaparse"; import moment from "moment"; @@ -865,14 +872,46 @@ export async function exportCampaign(job) { region: process.env.AWS_REGION }); const bucketName = process.env.AWS_S3_BUCKET_NAME; - const command = new CreateBucketCommand({ Bucket : bucketName }); - // this can return Location and $metadata, we just don't need that info - await client.send(command); + try { + // Check if the S3 bucket already exists + const verifyBucketCommand = new HeadBucketCommand({ + Bucket: bucketName + }); + await client.send(verifyBucketCommand); + + console.log(`S3 bucket "${bucketName}" already exists.`); + } catch (error) { + if (error.name === "NotFound") { + console.log( + `S3 bucket "${bucketName}" not found. Creating a new bucket.` + ); + + try { + // Create the S3 bucket + const createBucketCommand = new CreateBucketCommand({ + Bucket: bucketName + }); + await client.send(createBucketCommand); + + console.log(`S3 bucket "${bucketName}" created successfully.`); + } catch (createError) { + console.error( + `Error creating bucket "${bucketName}":`, + createError + ); + } + } else { + console.error("Error checking bucket existence:", error); + } + } // verifies that the bucket exists before moving forward // if for some reason this fails, Spoke defensively deletes the job - await waitUntilBucketExists({ client, maxWaitTime: 60 }, { Bucket : bucketName }); + await waitUntilBucketExists( + { client, maxWaitTime: 60 }, + { Bucket: bucketName } + ); const campaignTitle = campaign.title .replace(/ /g, "_") From fb0f56d39ab089560c647f12b90dc921c5326e91 Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Wed, 14 Aug 2024 10:56:42 -0400 Subject: [PATCH 14/28] misspointed check on emailEnabled --- src/containers/AdminCampaignStats.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containers/AdminCampaignStats.jsx b/src/containers/AdminCampaignStats.jsx index be8b6601f..4723b01e4 100644 --- a/src/containers/AdminCampaignStats.jsx +++ b/src/containers/AdminCampaignStats.jsx @@ -425,7 +425,7 @@ class AdminCampaignStats extends React.Component { Export started - {(this.props.organizationData && - this.props.organizationData.emailEnabled) ? + this.props.organizationData.organization.emailEnabled) ? " we'll e-mail you when it's done. " : " check your Spoke directory. "} {campaign.cacheable && ( From 48f095e4458ca711417543e2892892d5303e0aed Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Wed, 14 Aug 2024 10:59:15 -0400 Subject: [PATCH 15/28] typos --- src/workers/jobs.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/workers/jobs.js b/src/workers/jobs.js index aa3d279eb..787e263f5 100644 --- a/src/workers/jobs.js +++ b/src/workers/jobs.js @@ -943,12 +943,12 @@ export async function exportCampaign(job) { if (( process.env.EMAIL_FROM && process.env.EMAIL_HOST && - process.env.EMAIL_HOST_PASSOWRD && + process.env.EMAIL_HOST_PASSWORD && process.env.EMAIL_HOST_PORT && process.env.EMAIL_HOST_USER) || ( - process.env.MAILGUN_DOMIAN && - process.env.MAILGUN_SMTP_LOGN && + process.env.MAILGUN_DOMAIN && + process.env.MAILGUN_SMTP_LOGIN && process.env.MAILGUN_SMTP_PASSWORD && process.env.MAILGUN_SMTP_PORT && process.env.MAILGUN_SMTP_SERVER && From 243b610c680a910893c56922718acb9e4f37e4d9 Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Thu, 15 Aug 2024 11:33:03 -0400 Subject: [PATCH 16/28] change Snackbar logic to better fit outcome --- src/containers/AdminCampaignStats.jsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/containers/AdminCampaignStats.jsx b/src/containers/AdminCampaignStats.jsx index 4723b01e4..31fd91068 100644 --- a/src/containers/AdminCampaignStats.jsx +++ b/src/containers/AdminCampaignStats.jsx @@ -425,21 +425,20 @@ class AdminCampaignStats extends React.Component { Export started - {(this.props.organizationData && - this.props.organizationData.organization.emailEnabled) ? + process.env.EMAIL_HOST) ? " we'll e-mail you when it's done. " : - " check your Spoke directory. "} - {campaign.cacheable && ( + (campaign.cacheable && ( { this.props.data.refetch(); }} > - Reload the page + {" Reload the page"} {/*Hacky way to add a space at the beginning */} {" "} to see a download link when its ready. - )} + ))} } autoHideDuration={campaign.cacheable ? null : 5000} From 052685b8340fa5464a28b9b59c8555ee6048890c Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Thu, 15 Aug 2024 11:37:46 -0400 Subject: [PATCH 17/28] change exportCacheKey expiration to match AWS expiration of 1 day --- src/server/models/cacheable_queries/campaign.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/models/cacheable_queries/campaign.js b/src/server/models/cacheable_queries/campaign.js index f7bab4e09..48d04c2bf 100644 --- a/src/server/models/cacheable_queries/campaign.js +++ b/src/server/models/cacheable_queries/campaign.js @@ -254,7 +254,7 @@ const campaignCache = { await r.redis .MULTI() .SET(exportCacheKey, JSON.stringify(data)) - .EXPIRE(exportCacheKey, 43200) + .EXPIRE(exportCacheKey, 86400) .exec(); } }, From 7417b630fedf1ea90eaea64c0188cb582f0abd7c Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Thu, 15 Aug 2024 12:01:04 -0400 Subject: [PATCH 18/28] cache error --- src/workers/jobs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workers/jobs.js b/src/workers/jobs.js index 787e263f5..d8cde3ae2 100644 --- a/src/workers/jobs.js +++ b/src/workers/jobs.js @@ -994,7 +994,7 @@ export async function exportCampaign(job) { log.debug(campaignCsv); log.debug(messageCsv); } - if (exportResults.campaignExportUrl) { + if (exportResults.campaignExportUrl || exportResults.error) { exportResults.createdAt = String(new Date()); await cacheableData.campaign.saveExportData(campaign.id, exportResults); } From 0d1055ac9a1a489318af1d09f657ab0c81d20cbb Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Thu, 15 Aug 2024 12:02:22 -0400 Subject: [PATCH 19/28] reduce timout to 15 sec --- src/workers/jobs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workers/jobs.js b/src/workers/jobs.js index d8cde3ae2..9dcb55ab3 100644 --- a/src/workers/jobs.js +++ b/src/workers/jobs.js @@ -909,7 +909,7 @@ export async function exportCampaign(job) { // verifies that the bucket exists before moving forward // if for some reason this fails, Spoke defensively deletes the job await waitUntilBucketExists( - { client, maxWaitTime: 60 }, + { client, maxWaitTime: 15 }, { Bucket: bucketName } ); From ee6a29ca25e0c98786005be37134d5a6651a0f5b Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Thu, 15 Aug 2024 13:08:25 -0400 Subject: [PATCH 20/28] adjust export UI logic to only show each respective methods output, avoiding confusion. i.e. saying the export was stored locally when it was placed in an AWS bucket --- src/containers/AdminCampaignStats.jsx | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/containers/AdminCampaignStats.jsx b/src/containers/AdminCampaignStats.jsx index 31fd91068..91bb32ea9 100644 --- a/src/containers/AdminCampaignStats.jsx +++ b/src/containers/AdminCampaignStats.jsx @@ -346,8 +346,8 @@ class AdminCampaignStats extends React.Component { {campaign.exportResults.error && (
Export failed: {campaign.exportResults.error}
)} - {campaign.exportResults.campaignExportUrl && - campaign.exportResults.campaignExportUrl.startsWith("http") ? ( + {campaign.exportResults.campaignExportUrl && ( + (campaign.exportResults.campaignExportUrl.startsWith("http")) ? ( - ) : ( -
- Local export was successful, saved on the server at: -
- {campaign.exportResults.campaignExportUrl} -
- {campaign.exportResults.campaignMessagesExportUrl} -
- )} + ) : (campaign.exportResults.campaignExportUrl.startsWith("file://") && ( +
+ Local export was successful, saved on the server at: +
+ {campaign.exportResults.campaignExportUrl} +
+ {campaign.exportResults.campaignMessagesExportUrl} +
+ ) + ))} )} {campaign.joinToken && campaign.useDynamicAssignment && ( From be0bd30327c594462ccacd4881eea21af697249f Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Thu, 15 Aug 2024 13:12:22 -0400 Subject: [PATCH 21/28] revert back to emailEnabled check --- src/containers/AdminCampaignStats.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containers/AdminCampaignStats.jsx b/src/containers/AdminCampaignStats.jsx index 91bb32ea9..3d206b58a 100644 --- a/src/containers/AdminCampaignStats.jsx +++ b/src/containers/AdminCampaignStats.jsx @@ -426,7 +426,7 @@ class AdminCampaignStats extends React.Component { Export started - {(this.props.organizationData && - process.env.EMAIL_HOST) ? + this.props.organizationData.organization.emailEnabled) ? " we'll e-mail you when it's done. " : (campaign.cacheable && ( From a2805107a1d3a0c9f1556070bf54e51ecb6b07ba Mon Sep 17 00:00:00 2001 From: "Maureen E. Zitouni" Date: Sun, 18 Aug 2024 02:18:25 -0400 Subject: [PATCH 22/28] Make sure ids are the expected type --- src/containers/AdminIncomingMessageList.jsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/containers/AdminIncomingMessageList.jsx b/src/containers/AdminIncomingMessageList.jsx index 46f892c36..6556f415d 100644 --- a/src/containers/AdminIncomingMessageList.jsx +++ b/src/containers/AdminIncomingMessageList.jsx @@ -28,6 +28,11 @@ export class AdminIncomingMessageList extends Component { props.location.query, props.organization.organization.tags ); + // Make sure campaignIds is an array of numbers + filters.campaignsFilter = { + campaignIds: filters.campaignsFilter.campaignIds?.map(id => Number(id)) + }; + this.state = { page: 0, pageSize: 10, @@ -207,10 +212,17 @@ export class AdminIncomingMessageList extends Component { }; handleReassignRequested = async newTexterUserId => { + const updatedCampaignIdsContactIds = this.state.campaignIdsContactIds.map( + campaign => { + campaign.campaignContactId = Number(campaign.campaignContactId); + campaign.messageIds = campaign.messageIds.map(x => Number(x)); + return campaign; + } + ); await this.props.mutations.reassignCampaignContacts( this.props.params.organizationId, - this.state.campaignIdsContactIds, - newTexterUserId + updatedCampaignIdsContactIds, + newTexterUserId.toString() ); this.setState({ utc: Date.now().toString(), From 1b4982461fc8185dcc75934c3357554eae9c30ed Mon Sep 17 00:00:00 2001 From: "Maureen E. Zitouni" Date: Sun, 18 Aug 2024 02:21:51 -0400 Subject: [PATCH 23/28] Update parameter name --- src/containers/AdminIncomingMessageList.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containers/AdminIncomingMessageList.jsx b/src/containers/AdminIncomingMessageList.jsx index 6556f415d..0732c5eef 100644 --- a/src/containers/AdminIncomingMessageList.jsx +++ b/src/containers/AdminIncomingMessageList.jsx @@ -215,7 +215,7 @@ export class AdminIncomingMessageList extends Component { const updatedCampaignIdsContactIds = this.state.campaignIdsContactIds.map( campaign => { campaign.campaignContactId = Number(campaign.campaignContactId); - campaign.messageIds = campaign.messageIds.map(x => Number(x)); + campaign.messageIds = campaign.messageIds.map(id => Number(id)); return campaign; } ); From ee636299372fd5ef73747b475d83d64d299a3c95 Mon Sep 17 00:00:00 2001 From: engelhartrueben Date: Thu, 15 Aug 2024 17:22:43 -0400 Subject: [PATCH 24/28] string to int --- src/containers/PeopleList.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containers/PeopleList.jsx b/src/containers/PeopleList.jsx index bd7da0c7b..db7cfa971 100644 --- a/src/containers/PeopleList.jsx +++ b/src/containers/PeopleList.jsx @@ -263,7 +263,7 @@ export class PeopleList extends Component { }; renderChangePasswordButton = (value, tableMeta) => { - const texterId = tableMeta.rowData[0]; + const texterId = Number(tableMeta.rowData[0]); const { currentUser } = this.props; return (