Skip to content

Commit

Permalink
Merge pull request #2303 from MoveOnOrg/stage-main-13.1.0
Browse files Browse the repository at this point in the history
Spoke 13.1.0 Release
  • Loading branch information
crayolakat authored Oct 27, 2023
2 parents 19c5eea + ffa6386 commit 2915b4d
Show file tree
Hide file tree
Showing 43 changed files with 668 additions and 168 deletions.
12 changes: 11 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,14 @@ TEXTER_SIDEBOXES=celebration-gif,default-dynamicassignment,default-releasecontac
OWNER_CONFIGURABLE=ALL
NGP_VAN_API_KEY=
NGP_VAN_APP_NAME=
NGP_VAN_WEBHOOK_BASE_URL=
NGP_VAN_WEBHOOK_BASE_URL=
NGP_VAN_API_KEY_ENCRYPTED=
NGP_VAN_API_BASE_URL=
NGP_VAN_CACHE_TTL=
NGP_VAN_EXPORT_JOB_TYPE_ID=
NGP_VAN_MAXIMUM_LIST_SIZE=
NGP_VAN_CAUTIOUS_CELL_PHONE_SELECTION=
ACTION_HANDLERS=
MESSAGE_HANDLERS=
CONTACT_LOADERS=
SERVICE_MANAGERS=
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Spoke is an open source text-distribution tool for organizations to mobilize sup

Spoke was created by Saikat Chakrabarti and Sheena Pakanati, and is now maintained by MoveOn.org.

The latest version is [13.0.1](https://github.com/MoveOnOrg/Spoke/tree/13.0.1) (see [release notes](https://github.com/MoveOnOrg/Spoke/blob/main/docs/RELEASE_NOTES.md#v1301))
The latest version is [13.1.0](https://github.com/MoveOnOrg/Spoke/tree/13.1.0) (see [release notes](https://github.com/MoveOnOrg/Spoke/blob/main/docs/RELEASE_NOTES.md#v1310))


## Setting up Spoke
Expand All @@ -24,7 +24,7 @@ Want to know more?
### Quick Start with Heroku
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.

<a href="https://heroku.com/deploy?template=https://github.com/MoveOnOrg/Spoke/tree/13.0.1">
<a href="https://heroku.com/deploy?template=https://github.com/MoveOnOrg/Spoke/tree/13.1.0">

<img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy">
</a>
Expand Down
25 changes: 15 additions & 10 deletions __test__/components/AssignmentSummary.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ function getAssignment({ isDynamic = false, counts = {} }) {
id: "1",
title: "New Campaign",
description: "asdf",
organization: {
allowSendAll: window.ALLOW_SEND_ALL
},
useDynamicAssignment: isDynamic,
hasUnassignedContacts: false,
introHtml: "yoyo",
Expand Down Expand Up @@ -79,7 +82,7 @@ describe("AssignmentSummary text", function t() {
);
});

describe("AssignmentSummary actions inUSA and NOT AllowSendAll", () => {
describe("AssignmentSummary actions when NOT AllowSendAll", () => {
function create(
unmessaged,
unreplied,
Expand All @@ -88,7 +91,6 @@ describe("AssignmentSummary actions inUSA and NOT AllowSendAll", () => {
skipped,
isDynamic
) {
window.NOT_IN_USA = 0;
window.ALLOW_SEND_ALL = false;
return mount(
<ApolloProvider client={ApolloClientSingleton}>
Expand Down Expand Up @@ -185,7 +187,7 @@ describe("AssignmentSummary actions inUSA and NOT AllowSendAll", () => {
});
});

describe("AssignmentSummary NOT inUSA and AllowSendAll", () => {
describe("AssignmentSummary when AllowSendAll", () => {
function create(
unmessaged,
unreplied,
Expand All @@ -194,7 +196,6 @@ describe("AssignmentSummary NOT inUSA and AllowSendAll", () => {
skipped,
isDynamic
) {
window.NOT_IN_USA = 1;
window.ALLOW_SEND_ALL = true;
return mount(
<AssignmentSummary
Expand Down Expand Up @@ -223,14 +224,14 @@ describe("AssignmentSummary NOT inUSA and AllowSendAll", () => {
).toBe("Send messages");
});

it('renders "Send messages" with unreplied', () => {
it('renders "Respond" with unreplied', () => {
const actions = create(0, 1, 0, 0, 0, false);
expect(
actions
.find(Button)
.at(0)
.text()
).toBe("Send messages");
).toBe("Respond");
});
});

Expand Down Expand Up @@ -328,17 +329,21 @@ describe("contacts filters", () => {
})}
/>
);
const sendMessages = mockRender.mock.calls[0][0];
const respondMessages = mockRender.mock.calls[0][0];
expect(respondMessages.title).toBe("Respond");
expect(respondMessages.contactsFilter).toBe("reply");

const sendMessages = mockRender.mock.calls[1][0];
expect(sendMessages.title).toBe("Past Messages");
expect(sendMessages.contactsFilter).toBe("stale");

const skippedMessages = mockRender.mock.calls[1][0];
const skippedMessages = mockRender.mock.calls[2][0];
expect(skippedMessages.title).toBe("Skipped Messages");
expect(skippedMessages.contactsFilter).toBe("skipped");

const sendFirstTexts = mockRender.mock.calls[2][0];
const sendFirstTexts = mockRender.mock.calls[3][0];
expect(sendFirstTexts.title).toBe("Send messages");
expect(sendFirstTexts.contactsFilter).toBe("all");
expect(sendFirstTexts.contactsFilter).toBe("text");
});
});

Expand Down
1 change: 1 addition & 0 deletions __test__/extensions/contact-loaders/ngpvan/ngpvan.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ describe("ngpvan", () => {
["NGP_VAN_API_BASE_URL", organization],
["NGP_VAN_TIMEOUT", organization],
["NGP_VAN_APP_NAME", organization],
["NGP_VAN_API_KEY_ENCRYPTED", organization, {}],
["NGP_VAN_API_KEY", organization],
["NGP_VAN_DATABASE_MODE", organization],
["NGP_VAN_EXPORT_JOB_TYPE_ID", organization],
Expand Down
3 changes: 2 additions & 1 deletion __test__/extensions/contact-loaders/ngpvan/util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,13 @@ describe("ngpvan/util", () => {
}

try {
auth = Van.getAuth(organization);
auth = await Van.getAuth(organization);
} catch (caughtException) {
error = caughtException;
} finally {
expect(config.getConfig.mock.calls).toEqual([
["NGP_VAN_APP_NAME", organization],
["NGP_VAN_API_KEY_ENCRYPTED", organization, {}],
["NGP_VAN_API_KEY", organization],
["NGP_VAN_DATABASE_MODE", organization]
]);
Expand Down
1 change: 0 additions & 1 deletion __test__/extensions/service-vendors/twilio.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,6 @@ describe("config functions", () => {
["TWILIO_ACCOUNT_SID", organization, expectedConfigOpts]
]);
expect(configFunctions.getConfig.mock.calls).toEqual([
["TWILIO_AUTH_TOKEN_ENCRYPTED", organization, expectedConfigOpts],
["TWILIO_AUTH_TOKEN_ENCRYPTED", organization, expectedConfigOpts],
["TWILIO_ACCOUNT_SID", organization, expectedConfigOpts],
["TWILIO_ACCOUNT_SID", organization, expectedConfigOpts],
Expand Down
17 changes: 16 additions & 1 deletion __test__/server/api/campaign/campaign.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,8 @@ describe("Bulk Send", () => {
) => {
process.env.ALLOW_SEND_ALL = params.allowSendAll;
process.env.ALLOW_SEND_ALL_ENABLED = params.allowSendAllEnabled;
process.env.BULK_SEND_CHUNK_SIZE = params.bulkSendChunkSize;
process.env.BULK_SEND_BATCH_SIZE =
params.bulkSendBatchSize || params.bulkSendChunkSize;

testCampaign.use_dynamic_assignment = true;
await createScript(testAdminUser, testCampaign);
Expand Down Expand Up @@ -1014,6 +1015,20 @@ describe("Bulk Send", () => {
};
await testBulkSend(params, 0, expectErrorBulkSending);
});

it("should send initial texts to as many contacts as are in the batch size if batch size is smaller than the chunk size", async () => {
const params = {
allowSendAll: true,
allowSendAllEnabled: true,
bulkSendBatchSize: Math.round(NUMBER_OF_CONTACTS / 4),
bulkSendChunkSize: NUMBER_OF_CONTACTS
};
await testBulkSend(
params,
params.bulkSendBatchSize,
expectSuccessBulkSending(params.bulkSendBatchSize)
);
});
});

describe("campaigns query", () => {
Expand Down
50 changes: 50 additions & 0 deletions __test__/server/api/people.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
/**
* @jest-environment jsdom
*/
/* eslint-disable no-unused-expressions, consistent-return */
import { r } from "../../../src/server/models/";
import { getUsersGql } from "../../../src/containers/PeopleList";
import { GraphQLError } from "graphql/error";
import { resolvers } from "../../../src/server/api/schema";
import { validate as uuidValidate } from 'uuid';

import {
setupTest,
Expand Down Expand Up @@ -479,4 +484,49 @@ describe("people", () => {
]);
});
});

describe("reset password", () => {
/**
* Run the resetUserPassword mutation
* @param {number} organizationId
* @param {number} texterId
* @param {number} userId
* @returns Promise
*/
function resetUserPassword(admin, organizationId, texterId) {
return resolvers.RootMutation.resetUserPassword(null, {
organizationId: organizationId,
userId: texterId
}, {
loaders: {
organization: {
load: async id => {
return (await r.knex("organization").where({ id }))[0];
}
}
},
user: admin
});
}

it("reset local password", () => {
resetUserPassword(testAdminUsers[0], organizationId, testTexterUsers[0].id).then(uuid => {
// Non-Auth0 password reset will return verion 4 UUID
expect(uuidValidate(uuid)).toBeTruthy();
});
});

it("reset Auth0 password", () => {
// Remove PASSPORT_STRATEGY env var. PASSPORT_STRATEGY will default to "auth0" if there's nothing explicitly set
delete window.PASSPORT_STRATEGY;

resetUserPassword(testAdminUsers[0], organizationId, testTexterUsers[0].id).catch(e => {
// Auth0 password reset will attempt to make HTTP request, which will fail in Jest test
const match = e.message.match(/Error: Request id (.*) failed; all 2 retries exhausted/);

expect(match).toHaveLength(2);
expect(uuidValidate(match[1])).toBeTruthy();
});
});
});
});
13 changes: 6 additions & 7 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,18 @@
},

"DEFAULT_SERVICE": {
"description": "specifies using twilio api ",
"required": true,
"value": "twilio"
"description": "type bandwidth or twilio",
"required": true
},

"TWILIO_ACCOUNT_SID": {
"description": "for twilio integration and connected to twilio account",
"required": true
"required": false
},

"TWILIO_AUTH_TOKEN": {
"description": "for twilio integration and connected to twilio account",
"required": true
"required": false
},

"TWILIO_MULTI_ORG": {
Expand Down Expand Up @@ -184,9 +183,9 @@
}
},
"addons": [
"heroku-postgresql:hobby-dev",
"heroku-postgresql:basic",
{
"plan": "heroku-redis:hobby-dev",
"plan": "heroku-redis:mini",
"options": {
"maxmemory-policy": "volatile-lru"
}
Expand Down
2 changes: 1 addition & 1 deletion docs/HOWTO-configure-auth0.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ callback(null, user, context);
<script src="https://cdn.auth0.com/js/base64.js"></script>
<script src="https://cdn.auth0.com/js/es5-shim.min.js"></script>
<![endif]-->
<script src="https://cdn.auth0.com/js/lock/11.28/lock.min.js"></script>
<script src="https://cdn.auth0.com/js/lock/11.34.2/lock.min.js"></script>
<script>
// Decode utf8 characters properly
var config = JSON.parse(decodeURIComponent(escape(window.atob('@@config@@'))));
Expand Down
4 changes: 4 additions & 0 deletions docs/HOWTO-use-texter-sideboxes.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ Mobilize (America) event scheduling. User clicks the button and the MOBILIZE_EVE

Note: as of 3/1/21 you will need to reach out to Mobilize support to have them enable embedding for your dashboard(s), otherwise you'll probably get the error `Refused to display '{MOBILIZE_EVENT_SHIFTER_URL}' in a frame because it set 'X-Frame-Options' to 'sameorigin'.`

### per-campaign-bulk-send

This texter sidebox allows you to enable or disable bulk send per campaign. To use this texter sidebox, bulk send must be enabled in your Spoke instance.

### tag-contact

If you create tags related to 'escalation' or something you want texters to be able to mark
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

If Spoke is configured to do bulk sending, a `Send Bulk` button will appear on the texter's view for the first contact. When the texter clicks that button, the next chunk of contacts will receive the initial message. After Spoke queues up messages for those contacts, if there are any contacts left in the texter's assignment, the next chunk of contacts will receive messages the next time the texter clicks `Send Bulk`.

If you navigate away from the page or your computer falls asleep during a bulk send, the send will be interrupted. You must refresh the page and click the `Send Bulk` button again to continue sending messages. You can check on the progress of a bulk send by opening Developer Tools and looking at the console logs.

Only contacts needing the initial message will receive a message.

## After confirming that you can legally use the feature
Expand All @@ -14,7 +16,7 @@ Refer to [REFERENCE-environment_variables.md](REFERENCE-environment_variables.md

1. Set the environment variable `ALLOW_SEND_ALL` to `true`.
2. Set the environment variable `BULK_SEND_CHUNK_SIZE` to a number greater than 0.
3. Set the environment variable `NOT_IN_USA` to `true`.
3. Set the environment variable `BULK_SEND_BATCH_SIZE` to a number greater than 0 and less than or equal to `BULK_SEND_CHUNK_SIZE`.
4. Restart Spoke.

## Example: `.env` file
Expand All @@ -23,6 +25,6 @@ If you use a `.env` file to configure Spoke, the changes above would appear as f

```
ALLOW_SEND_ALL=true
BULK_SEND_CHUNK_SIZE=30
NOT_IN_USA=true
BULK_SEND_CHUNK_SIZE=50000
BULK_SEND_BATCH_SIZE=400
```
Loading

0 comments on commit 2915b4d

Please sign in to comment.