Skip to content

Commit adb6064

Browse files
Merge pull request #2430 from StateVoicesNational/stage-main-14.0.2
Stage main 14.0.2
2 parents 58c5562 + 1a54275 commit adb6064

File tree

9 files changed

+150
-57
lines changed

9 files changed

+150
-57
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Spoke was created by Saikat Chakrabarti and Sheena Pakanati.
88

99
On November 19th, 2023, the repo Spoke was transfered from MoveOn to StateVoices.
1010

11-
The latest version is [14.0.1](https://github.com/StateVoicesNational/Spoke/tree/v14.0.1) (see [release notes](https://github.com/StateVoicesNational/Spoke/blob/main/docs/RELEASE_NOTES.md#v1401))
11+
The latest version is [14.0.2](https://github.com/StateVoicesNational/Spoke/tree/v14.0.2) (see [release notes](https://github.com/StateVoicesNational/Spoke/blob/main/docs/RELEASE_NOTES.md#v1402))
1212

1313

1414
## Setting up Spoke
@@ -25,7 +25,7 @@ Want to know more?
2525
### Quick Start with Heroku
2626
This version of Spoke suitable for testing and, potentially, for small campaigns. This won't cost any money and will not support production(aka large-scale) usage. It's a great way to practice deploying Spoke or see it in action.
2727

28-
<a href="https://heroku.com/deploy?template=https://github.com/StateVoicesNational/Spoke/tree/v14.0.1">
28+
<a href="https://heroku.com/deploy?template=https://github.com/StateVoicesNational/Spoke/tree/v14.0.2">
2929

3030
<img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy">
3131
</a>

docs/RELEASE_NOTES.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Release Notes
22

3+
## v14.0.2
4+
_August 2024:_ Version 14.0.2
5+
6+
14.0.2 is a patch release.
7+
8+
### Bug Fixes
9+
- [#2410](https://github.com/StateVoicesNational/Spoke/pull/2410) - Data exports bug
10+
- [#2424](https://github.com/StateVoicesNational/Spoke/pull/2424) - Reset Password Bug
11+
- [#2425](https://github.com/StateVoicesNational/Spoke/pull/2425) - Message Review Reassignment Bug
12+
13+
### Appreciations
14+
[Maureen Zitouni](https://github.com/mau11), [Ruby Engelhart](https://github.com/engelhartrueben)
15+
316
## v14.0.1
417
_July 2024:_ Version 14.0.1
518

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "spoke",
3-
"version": "14.0.1",
3+
"version": "14.0.2",
44
"description": "Spoke",
55
"main": "src/server",
66
"engines": {

src/containers/AdminCampaignStats.jsx

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,8 @@ class AdminCampaignStats extends React.Component {
346346
{campaign.exportResults.error && (
347347
<div>Export failed: {campaign.exportResults.error}</div>
348348
)}
349-
{campaign.exportResults.campaignExportUrl &&
350-
campaign.exportResults.campaignExportUrl.startsWith("http") ? (
349+
{campaign.exportResults.campaignExportUrl && (
350+
(campaign.exportResults.campaignExportUrl.startsWith("http")) ? (
351351
<div>
352352
Most recent export:
353353
<a href={campaign.exportResults.campaignExportUrl} download>
@@ -360,15 +360,16 @@ class AdminCampaignStats extends React.Component {
360360
Messages Export CSV
361361
</a>
362362
</div>
363-
) : (
364-
<div>
365-
Local export was successful, saved on the server at:
366-
<br />
367-
{campaign.exportResults.campaignExportUrl}
368-
<br />
369-
{campaign.exportResults.campaignMessagesExportUrl}
370-
</div>
371-
)}
363+
) : (campaign.exportResults.campaignExportUrl.startsWith("file://") && (
364+
<div>
365+
Local export was successful, saved on the server at:
366+
<br />
367+
{campaign.exportResults.campaignExportUrl}
368+
<br />
369+
{campaign.exportResults.campaignMessagesExportUrl}
370+
</div>
371+
)
372+
))}
372373
</div>
373374
)}
374375
{campaign.joinToken && campaign.useDynamicAssignment && (
@@ -424,21 +425,21 @@ class AdminCampaignStats extends React.Component {
424425
message={
425426
<span>
426427
Export started -
427-
{this.props.organizationData &&
428-
this.props.organizationData.emailEnabled &&
429-
" we'll e-mail you when it's done. "}
430-
{campaign.cacheable && (
428+
{(this.props.organizationData &&
429+
this.props.organizationData.organization.emailEnabled) ?
430+
" we'll e-mail you when it's done. " :
431+
(campaign.cacheable && (
431432
<span>
432433
<Link
433434
onClick={() => {
434435
this.props.data.refetch();
435436
}}
436437
>
437-
Reload the page
438+
{" Reload the page"} {/*Hacky way to add a space at the beginning */}
438439
</Link>{" "}
439440
to see a download link when its ready.
440441
</span>
441-
)}
442+
))}
442443
</span>
443444
}
444445
autoHideDuration={campaign.cacheable ? null : 5000}

src/containers/AdminIncomingMessageList.jsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ export class AdminIncomingMessageList extends Component {
2828
props.location.query,
2929
props.organization.organization.tags
3030
);
31+
// Make sure campaignIds is an array of numbers
32+
filters.campaignsFilter = {
33+
campaignIds: filters.campaignsFilter.campaignIds?.map(id => Number(id))
34+
};
35+
3136
this.state = {
3237
page: 0,
3338
pageSize: 10,
@@ -207,10 +212,17 @@ export class AdminIncomingMessageList extends Component {
207212
};
208213

209214
handleReassignRequested = async newTexterUserId => {
215+
const updatedCampaignIdsContactIds = this.state.campaignIdsContactIds.map(
216+
campaign => {
217+
campaign.campaignContactId = Number(campaign.campaignContactId);
218+
campaign.messageIds = campaign.messageIds.map(id => Number(id));
219+
return campaign;
220+
}
221+
);
210222
await this.props.mutations.reassignCampaignContacts(
211223
this.props.params.organizationId,
212-
this.state.campaignIdsContactIds,
213-
newTexterUserId
224+
updatedCampaignIdsContactIds,
225+
newTexterUserId.toString()
214226
);
215227
this.setState({
216228
utc: Date.now().toString(),

src/containers/PeopleList.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,11 +263,11 @@ export class PeopleList extends Component {
263263
};
264264

265265
renderChangePasswordButton = (value, tableMeta) => {
266-
const texterId = tableMeta.rowData[0];
266+
const texterId = Number(tableMeta.rowData[0]);
267267
const { currentUser } = this.props;
268268
return (
269269
<Button
270-
disabled={currentUser.id === texterId}
270+
disabled={currentUser.id == texterId}
271271
onClick={() => {
272272
this.resetPassword(texterId);
273273
}}

src/server/models/cacheable_queries/campaign.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ const campaignCache = {
254254
await r.redis
255255
.MULTI()
256256
.SET(exportCacheKey, JSON.stringify(data))
257-
.EXPIRE(exportCacheKey, 43200)
257+
.EXPIRE(exportCacheKey, 86400)
258258
.exec();
259259
}
260260
},

src/server/models/cacheable_queries/user.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ const dbLoadUserRoles = async userId => {
8282
await r.redis
8383
.MULTI()
8484
.DEL(key)
85-
.HSET(key, ...mappedHighestRoles)
85+
.HSET(key, mappedHighestRoles)
8686
.exec();
8787
} else {
8888
await r.redis.DEL(key);

src/workers/jobs.js

Lines changed: 98 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,14 @@ import { rawIngestMethod } from "../extensions/contact-loaders";
2828

2929
import { Lambda } from "@aws-sdk/client-lambda";
3030
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
31-
import { GetObjectCommand, S3 } from "@aws-sdk/client-s3";
31+
import {
32+
CreateBucketCommand,
33+
HeadBucketCommand,
34+
GetObjectCommand,
35+
waitUntilBucketExists,
36+
S3Client,
37+
PutObjectCommand
38+
} from "@aws-sdk/client-s3";
3239
import { SQS } from "@aws-sdk/client-sqs";
3340
import Papa from "papaparse";
3441
import moment from "moment";
@@ -861,46 +868,106 @@ export async function exportCampaign(job) {
861868
(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY)
862869
) {
863870
try {
864-
const s3bucket = new S3({
865-
// The transformation for params is not implemented.
866-
// Refer to UPGRADING.md on aws-sdk-js-v3 for changes needed.
867-
// Please create/upvote feature request on aws-sdk-js-codemod for params.
868-
params: { Bucket: process.env.AWS_S3_BUCKET_NAME }
871+
const client = new S3Client({
872+
region: process.env.AWS_REGION
869873
});
874+
const bucketName = process.env.AWS_S3_BUCKET_NAME;
875+
876+
try {
877+
// Check if the S3 bucket already exists
878+
const verifyBucketCommand = new HeadBucketCommand({
879+
Bucket: bucketName
880+
});
881+
await client.send(verifyBucketCommand);
882+
883+
console.log(`S3 bucket "${bucketName}" already exists.`);
884+
} catch (error) {
885+
if (error.name === "NotFound") {
886+
console.log(
887+
`S3 bucket "${bucketName}" not found. Creating a new bucket.`
888+
);
889+
890+
try {
891+
// Create the S3 bucket
892+
const createBucketCommand = new CreateBucketCommand({
893+
Bucket: bucketName
894+
});
895+
await client.send(createBucketCommand);
896+
897+
console.log(`S3 bucket "${bucketName}" created successfully.`);
898+
} catch (createError) {
899+
console.error(
900+
`Error creating bucket "${bucketName}":`,
901+
createError
902+
);
903+
}
904+
} else {
905+
console.error("Error checking bucket existence:", error);
906+
}
907+
}
908+
909+
// verifies that the bucket exists before moving forward
910+
// if for some reason this fails, Spoke defensively deletes the job
911+
await waitUntilBucketExists(
912+
{ client, maxWaitTime: 15 },
913+
{ Bucket: bucketName }
914+
);
915+
870916
const campaignTitle = campaign.title
871917
.replace(/ /g, "_")
872918
.replace(/\//g, "_");
873919
const key = `${campaignTitle}-${moment().format(
874920
"YYYY-MM-DD-HH-mm-ss"
875921
)}.csv`;
876922
const messageKey = `${key}-messages.csv`;
877-
let params = { Key: key, Body: campaignCsv };
878-
await s3bucket.putObject(params);
879-
params = { Key: key, Expires: 86400 };
880-
const campaignExportUrl = await await getSignedUrl(s3bucket, new GetObjectCommand(params), {
881-
expiresIn: "/* add value from 'Expires' from v2 call if present, else remove */"
882-
});
883-
params = { Key: messageKey, Body: messageCsv };
884-
await s3bucket.putObject(params);
885-
params = { Key: messageKey, Expires: 86400 };
886-
const campaignMessagesExportUrl = await await getSignedUrl(s3bucket, new GetObjectCommand(params), {
887-
expiresIn: "/* add value from 'Expires' from v2 call if present, else remove */"
888-
});
923+
let params = { Key: key,
924+
Body: campaignCsv,
925+
Bucket: bucketName };
926+
await client.send(new PutObjectCommand(params));
927+
params = { Key: key,
928+
Expires: 86400,
929+
Bucket: bucketName };
930+
const campaignExportUrl = await getSignedUrl(client, new GetObjectCommand(params));
931+
params = { Key: messageKey,
932+
Body: messageCsv,
933+
Bucket: bucketName };
934+
await client.send(new PutObjectCommand(params));
935+
params = { Key: messageKey,
936+
Expires: 86400,
937+
Bucket: bucketName };
938+
const campaignMessagesExportUrl = await getSignedUrl(client, new GetObjectCommand(params));
889939
exportResults.campaignExportUrl = campaignExportUrl;
890940
exportResults.campaignMessagesExportUrl = campaignMessagesExportUrl;
891941

892-
await sendEmail({
893-
to: user.email,
894-
subject: `Export ready for ${campaign.title}`,
895-
text: `Your Spoke exports are ready! These URLs will be valid for 24 hours.
896-
Campaign export: ${campaignExportUrl}
897-
Message export: ${campaignMessagesExportUrl}`
898-
}).catch(err => {
899-
log.error(err);
900-
log.info(`Campaign Export URL - ${campaignExportUrl}`);
901-
log.info(`Campaign Messages Export URL - ${campaignMessagesExportUrl}`);
902-
});
903-
log.info(`Successfully exported ${id}`);
942+
// extreme check on email set-up
943+
if ((
944+
process.env.EMAIL_FROM &&
945+
process.env.EMAIL_HOST &&
946+
process.env.EMAIL_HOST_PASSWORD &&
947+
process.env.EMAIL_HOST_PORT &&
948+
process.env.EMAIL_HOST_USER) ||
949+
(
950+
process.env.MAILGUN_DOMAIN &&
951+
process.env.MAILGUN_SMTP_LOGIN &&
952+
process.env.MAILGUN_SMTP_PASSWORD &&
953+
process.env.MAILGUN_SMTP_PORT &&
954+
process.env.MAILGUN_SMTP_SERVER &&
955+
process.env.MAILGUN_PUBLIC_KEY
956+
)
957+
) {
958+
await sendEmail({
959+
to: user.email,
960+
subject: `Export ready for ${campaign.title}`,
961+
text: `Your Spoke exports are ready! These URLs will be valid for 24 hours.
962+
Campaign export: ${campaignExportUrl}
963+
Message export: ${campaignMessagesExportUrl}`
964+
}).catch(err => {
965+
log.error(err);
966+
log.info(`Campaign Export URL - ${campaignExportUrl}`);
967+
log.info(`Campaign Messages Export URL - ${campaignMessagesExportUrl}`);
968+
});
969+
log.info(`Successfully exported ${id}`);
970+
}
904971
} catch (err) {
905972
log.error(err);
906973
exportResults.error = err.message;
@@ -927,7 +994,7 @@ export async function exportCampaign(job) {
927994
log.debug(campaignCsv);
928995
log.debug(messageCsv);
929996
}
930-
if (exportResults.campaignExportUrl) {
997+
if (exportResults.campaignExportUrl || exportResults.error) {
931998
exportResults.createdAt = String(new Date());
932999
await cacheableData.campaign.saveExportData(campaign.id, exportResults);
9331000
}

0 commit comments

Comments
 (0)