diff --git a/__mocks__/fileMock.js b/__mocks__/fileMock.js
index 84c1da6fd..0a445d060 100644
--- a/__mocks__/fileMock.js
+++ b/__mocks__/fileMock.js
@@ -1 +1 @@
-module.exports = 'test-file-stub';
\ No newline at end of file
+module.exports = "test-file-stub";
diff --git a/__mocks__/styleMock.js b/__mocks__/styleMock.js
index a09954537..f053ebf79 100644
--- a/__mocks__/styleMock.js
+++ b/__mocks__/styleMock.js
@@ -1 +1 @@
-module.exports = {};
\ No newline at end of file
+module.exports = {};
diff --git a/__test__/AssignmentSummary.test.js b/__test__/AssignmentSummary.test.js
index eff06773c..8197f9423 100644
--- a/__test__/AssignmentSummary.test.js
+++ b/__test__/AssignmentSummary.test.js
@@ -1,34 +1,34 @@
/**
* @jest-environment jsdom
*/
-import React from 'react'
-import { mount } from 'enzyme'
-import { StyleSheetTestUtils } from 'aphrodite'
-import injectTapEventPlugin from 'react-tap-event-plugin'
-import each from 'jest-each'
-import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
-import { CardActions, CardTitle } from 'material-ui/Card'
-import { AssignmentSummary } from '../src/components/AssignmentSummary'
-import Badge from 'material-ui/Badge/Badge'
-import RaisedButton from 'material-ui/RaisedButton/RaisedButton'
+import React from "react";
+import { mount } from "enzyme";
+import { StyleSheetTestUtils } from "aphrodite";
+import injectTapEventPlugin from "react-tap-event-plugin";
+import each from "jest-each";
+import MuiThemeProvider from "material-ui/styles/MuiThemeProvider";
+import { CardActions, CardTitle } from "material-ui/Card";
+import { AssignmentSummary } from "../src/components/AssignmentSummary";
+import Badge from "material-ui/Badge/Badge";
+import RaisedButton from "material-ui/RaisedButton/RaisedButton";
function getAssignment(isDynamic = false) {
return {
- id: '1',
+ id: "1",
campaign: {
- id: '1',
- title: 'New Campaign',
- description: 'asdf',
+ id: "1",
+ title: "New Campaign",
+ description: "asdf",
useDynamicAssignment: isDynamic,
hasUnassignedContacts: false,
- introHtml: 'yoyo',
- primaryColor: '#2052d8',
- logoImageUrl: ''
+ introHtml: "yoyo",
+ primaryColor: "#2052d8",
+ logoImageUrl: ""
}
- }
+ };
}
-describe('AssignmentSummary text', function t() {
+describe("AssignmentSummary text", function t() {
beforeEach(() => {
this.summary = mount(
@@ -41,33 +41,40 @@ describe('AssignmentSummary text', function t() {
skippedMessagesCount={0}
/>
- )
- })
+ );
+ });
each([[0, false], [1, false], [0, true], [1, true]]).test(
- 'renders title and html for notInUSA=%s and allowSendAll=%s',
+ "renders title and html for notInUSA=%s and allowSendAll=%s",
(notInUSA, allowSendAll) => {
- window.NOT_IN_USA = notInUSA
- window.ALLOW_SEND_ALL = allowSendAll
- const title = this.summary.find(CardTitle)
- expect(title.prop('title')).toBe('New Campaign')
+ window.NOT_IN_USA = notInUSA;
+ window.ALLOW_SEND_ALL = allowSendAll;
+ const title = this.summary.find(CardTitle);
+ expect(title.prop("title")).toBe("New Campaign");
// expect(title.find(CardTitle).prop('subtitle')).toBe('asdf - Jan 31 2018')
const htmlWrapper = this.summary.findWhere(
- d => d.length && d.type() === 'div' && d.prop('dangerouslySetInnerHTML')
- )
- expect(htmlWrapper.prop('dangerouslySetInnerHTML')).toEqual({
- __html: 'yoyo'
- })
+ d => d.length && d.type() === "div" && d.prop("dangerouslySetInnerHTML")
+ );
+ expect(htmlWrapper.prop("dangerouslySetInnerHTML")).toEqual({
+ __html: "yoyo"
+ });
}
- )
-})
-
-describe('AssignmentSummary actions inUSA and NOT AllowSendAll', () => {
- injectTapEventPlugin() // prevents warning
- function create(unmessaged, unreplied, badTimezone, past, skipped, isDynamic) {
- window.NOT_IN_USA = 0
- window.ALLOW_SEND_ALL = false
+ );
+});
+
+describe("AssignmentSummary actions inUSA and NOT AllowSendAll", () => {
+ injectTapEventPlugin(); // prevents warning
+ function create(
+ unmessaged,
+ unreplied,
+ badTimezone,
+ past,
+ skipped,
+ isDynamic
+ ) {
+ window.NOT_IN_USA = 0;
+ window.ALLOW_SEND_ALL = false;
return mount(
{
skippedMessagesCount={skipped}
/>
- ).find(CardActions)
+ ).find(CardActions);
}
it('renders "send first texts (1)" with unmessaged (dynamic assignment)', () => {
- const actions = create(5, 0, 0, 0, 0, true)
- expect(actions.find(Badge).at(0).prop('badgeContent')).toBe(5)
- expect(actions.find(RaisedButton).at(0).prop('label')).toBe('Send first texts')
- })
+ const actions = create(5, 0, 0, 0, 0, true);
+ expect(
+ actions
+ .find(Badge)
+ .at(0)
+ .prop("badgeContent")
+ ).toBe(5);
+ expect(
+ actions
+ .find(RaisedButton)
+ .at(0)
+ .prop("label")
+ ).toBe("Send first texts");
+ });
it('renders "send first texts (1)" with unmessaged (non-dynamic)', () => {
- const actions = create(1, 0, 0, 0, 0, false)
- expect(actions.find(Badge).at(0).prop('badgeContent')).toBe(1)
- expect(actions.find(RaisedButton).at(0).prop('label')).toBe('Send first texts')
- })
+ const actions = create(1, 0, 0, 0, 0, false);
+ expect(
+ actions
+ .find(Badge)
+ .at(0)
+ .prop("badgeContent")
+ ).toBe(1);
+ expect(
+ actions
+ .find(RaisedButton)
+ .at(0)
+ .prop("label")
+ ).toBe("Send first texts");
+ });
it('renders "send first texts" with no unmessaged (dynamic assignment)', () => {
- const actions = create(0, 0, 0, 0, 0, true)
- expect(actions.find(RaisedButton).at(0).prop('label')).toBe('Send first texts')
- })
+ const actions = create(0, 0, 0, 0, 0, true);
+ expect(
+ actions
+ .find(RaisedButton)
+ .at(0)
+ .prop("label")
+ ).toBe("Send first texts");
+ });
it('renders a "past messages" badge after messaged contacts', () => {
- const actions = create(0, 0, 0, 1, 0, false)
- expect(actions.find(RaisedButton).length).toBe(1)
- })
+ const actions = create(0, 0, 0, 1, 0, false);
+ expect(actions.find(RaisedButton).length).toBe(1);
+ });
- it('renders two buttons with unmessaged and unreplied', () => {
- const actions = create(3, 9, 0, 0, 0, false)
- expect(actions.find(RaisedButton).length).toBe(2)
- })
+ it("renders two buttons with unmessaged and unreplied", () => {
+ const actions = create(3, 9, 0, 0, 0, false);
+ expect(actions.find(RaisedButton).length).toBe(2);
+ });
it('renders "past messages (n)" with messaged', () => {
- const actions = create(0, 0, 0, 9, 0, false)
- expect(actions.find(Badge).at(0).prop('badgeContent')).toBe(9)
- expect(actions.find(RaisedButton).at(0).prop('label')).toBe('Past Messages')
- })
-})
-
-describe('AssignmentSummary NOT inUSA and AllowSendAll', () => {
- function create(unmessaged, unreplied, badTimezone, past, skipped, isDynamic) {
- window.NOT_IN_USA = 1
- window.ALLOW_SEND_ALL = true
+ const actions = create(0, 0, 0, 9, 0, false);
+ expect(
+ actions
+ .find(Badge)
+ .at(0)
+ .prop("badgeContent")
+ ).toBe(9);
+ expect(
+ actions
+ .find(RaisedButton)
+ .at(0)
+ .prop("label")
+ ).toBe("Past Messages");
+ });
+});
+
+describe("AssignmentSummary NOT inUSA and AllowSendAll", () => {
+ function create(
+ unmessaged,
+ unreplied,
+ badTimezone,
+ past,
+ skipped,
+ isDynamic
+ ) {
+ window.NOT_IN_USA = 1;
+ window.ALLOW_SEND_ALL = true;
return mount(
{
skippedMessagesCount={skipped}
/>
- ).find(CardActions)
+ ).find(CardActions);
}
it('renders "Send message" with unmessaged', () => {
- const actions = create(1, 0, 0, 0, 0, false)
- expect(actions.find(RaisedButton).at(0).prop('label')).toBe('Send messages')
- })
+ const actions = create(1, 0, 0, 0, 0, false);
+ expect(
+ actions
+ .find(RaisedButton)
+ .at(0)
+ .prop("label")
+ ).toBe("Send messages");
+ });
it('renders "Send messages" with unreplied', () => {
- const actions = create(0, 1, 0, 0, 0, false)
- expect(actions.find(RaisedButton).at(0).prop('label')).toBe('Send messages')
- })
-})
+ const actions = create(0, 1, 0, 0, 0, false);
+ expect(
+ actions
+ .find(RaisedButton)
+ .at(0)
+ .prop("label")
+ ).toBe("Send messages");
+ });
+});
it('renders "Send later" when there is a badTimezoneCount', () => {
const actions = mount(
@@ -156,23 +215,38 @@ it('renders "Send later" when there is a badTimezoneCount', () => {
skippedMessagesCount={0}
/>
- ).find(CardActions)
- expect(actions.find(Badge).at(1).prop('badgeContent')).toBe(4)
- expect(actions.find(RaisedButton).at(0).prop('label')).toBe('Past Messages')
- expect(actions.find(RaisedButton).at(1).prop('label')).toBe('Send messages')
-})
+ ).find(CardActions);
+ expect(
+ actions
+ .find(Badge)
+ .at(1)
+ .prop("badgeContent")
+ ).toBe(4);
+ expect(
+ actions
+ .find(RaisedButton)
+ .at(0)
+ .prop("label")
+ ).toBe("Past Messages");
+ expect(
+ actions
+ .find(RaisedButton)
+ .at(1)
+ .prop("label")
+ ).toBe("Send messages");
+});
-describe('contacts filters', () => {
+describe("contacts filters", () => {
// These are an attempt to confirm that the buttons will work.
// It would be better to simulate clicking them, but I can't
// get it to work right now because of 'react-tap-event-plugin'
// some hints are here https://github.com/mui-org/material-ui/issues/4200#issuecomment-217738345
- it('filters correctly in USA', () => {
- window.NOT_IN_USA = 0
- window.ALLOW_SEND_ALL = false
- const mockRender = jest.fn()
- AssignmentSummary.prototype.renderBadgedButton = mockRender
+ it("filters correctly in USA", () => {
+ window.NOT_IN_USA = 0;
+ window.ALLOW_SEND_ALL = false;
+ const mockRender = jest.fn();
+ AssignmentSummary.prototype.renderBadgedButton = mockRender;
mount(
{
skippedMessagesCount={0}
/>
- )
- const sendFirstTexts = mockRender.mock.calls[0][0]
- expect(sendFirstTexts.title).toBe('Send first texts')
- expect(sendFirstTexts.contactsFilter).toBe('text')
-
- const sendReplies = mockRender.mock.calls[1][0]
- expect(sendReplies.title).toBe('Send replies')
- expect(sendReplies.contactsFilter).toBe('reply')
-
- const sendLater = mockRender.mock.calls[2][0]
- expect(sendLater.title).toBe('Past Messages')
- expect(sendLater.contactsFilter).toBe('stale')
-
- const skippedMessages = mockRender.mock.calls[3][0]
- expect(skippedMessages.title).toBe('Skipped Messages')
- expect(skippedMessages.contactsFilter).toBe('skipped')
- })
- it('filters correctly out of USA', () => {
- window.NOT_IN_USA = 1
- window.ALLOW_SEND_ALL = true
- const mockRender = jest.fn()
- AssignmentSummary.prototype.renderBadgedButton = mockRender
+ );
+ const sendFirstTexts = mockRender.mock.calls[0][0];
+ expect(sendFirstTexts.title).toBe("Send first texts");
+ expect(sendFirstTexts.contactsFilter).toBe("text");
+
+ const sendReplies = mockRender.mock.calls[1][0];
+ expect(sendReplies.title).toBe("Send replies");
+ expect(sendReplies.contactsFilter).toBe("reply");
+
+ const sendLater = mockRender.mock.calls[2][0];
+ expect(sendLater.title).toBe("Past Messages");
+ expect(sendLater.contactsFilter).toBe("stale");
+
+ const skippedMessages = mockRender.mock.calls[3][0];
+ expect(skippedMessages.title).toBe("Skipped Messages");
+ expect(skippedMessages.contactsFilter).toBe("skipped");
+ });
+ it("filters correctly out of USA", () => {
+ window.NOT_IN_USA = 1;
+ window.ALLOW_SEND_ALL = true;
+ const mockRender = jest.fn();
+ AssignmentSummary.prototype.renderBadgedButton = mockRender;
mount(
{
skippedMessagesCount={0}
/>
- )
- const sendMessages = mockRender.mock.calls[0][0]
- expect(sendMessages.title).toBe('Past Messages')
- expect(sendMessages.contactsFilter).toBe('stale')
+ );
+ const sendMessages = mockRender.mock.calls[0][0];
+ expect(sendMessages.title).toBe("Past Messages");
+ expect(sendMessages.contactsFilter).toBe("stale");
- const skippedMessages = mockRender.mock.calls[1][0]
- expect(skippedMessages.title).toBe('Skipped Messages')
- expect(skippedMessages.contactsFilter).toBe('skipped')
+ const skippedMessages = mockRender.mock.calls[1][0];
+ expect(skippedMessages.title).toBe("Skipped Messages");
+ expect(skippedMessages.contactsFilter).toBe("skipped");
- const sendFirstTexts = mockRender.mock.calls[2][0]
- expect(sendFirstTexts.title).toBe('Send messages')
- expect(sendFirstTexts.contactsFilter).toBe('all')
- })
-})
+ const sendFirstTexts = mockRender.mock.calls[2][0];
+ expect(sendFirstTexts.title).toBe("Send messages");
+ expect(sendFirstTexts.contactsFilter).toBe("all");
+ });
+});
// https://github.com/Khan/aphrodite/issues/62#issuecomment-267026726
beforeEach(() => {
- StyleSheetTestUtils.suppressStyleInjection()
-})
+ StyleSheetTestUtils.suppressStyleInjection();
+});
afterEach(() => {
- StyleSheetTestUtils.clearBufferAndResumeStyleInjection()
-})
+ StyleSheetTestUtils.clearBufferAndResumeStyleInjection();
+});
diff --git a/__test__/TexterStats.test.js b/__test__/TexterStats.test.js
index bc69f0fec..8f35a9b53 100644
--- a/__test__/TexterStats.test.js
+++ b/__test__/TexterStats.test.js
@@ -1,88 +1,86 @@
-import React from 'react'
-import { shallow } from 'enzyme'
-import TexterStats from '../src/components/TexterStats'
+import React from "react";
+import { shallow } from "enzyme";
+import TexterStats from "../src/components/TexterStats";
const campaign = {
useDynamicAssignment: false,
assignments: [
{
- id: '1',
+ id: "1",
texter: {
- id: '1',
- firstName: 'Test',
- lastName: 'Tester'
+ id: "1",
+ firstName: "Test",
+ lastName: "Tester"
},
unmessagedCount: 193,
contactsCount: 238
},
{
- id: '1',
+ id: "1",
texter: {
- id: '1',
- firstName: 'Someone',
- lastName: 'Else',
+ id: "1",
+ firstName: "Someone",
+ lastName: "Else"
},
unmessagedCount: 4,
contactsCount: 545
}
]
-}
+};
const campaignDynamic = {
useDynamicAssignment: true,
assignments: [
{
- id: '1',
+ id: "1",
texter: {
- id: '1',
- firstName: 'Test',
- lastName: 'Tester'
+ id: "1",
+ firstName: "Test",
+ lastName: "Tester"
},
unmessagedCount: 193,
contactsCount: 238
},
{
- id: '1',
+ id: "1",
texter: {
- id: '1',
- firstName: 'Someone',
- lastName: 'Else',
+ id: "1",
+ firstName: "Someone",
+ lastName: "Else"
},
unmessagedCount: 4,
contactsCount: 545
}
]
-}
+};
-
-describe('TexterStats (Non-dynamic campaign)', () => {
- it('contains the right text', () => {
- const stats = shallow()
+describe("TexterStats (Non-dynamic campaign)", () => {
+ it("contains the right text", () => {
+ const stats = shallow();
expect(stats.text()).toEqual(
- 'Test Tester19%Someone Else99%'
- )
- })
+ "Test Tester19%Someone Else99%"
+ );
+ });
- it('creates linear progress correctly', () => {
+ it("creates linear progress correctly", () => {
const linearProgress = shallow().find(
- 'LinearProgress'
- )
- expect(linearProgress.length).toBe(2)
+ "LinearProgress"
+ );
+ expect(linearProgress.length).toBe(2);
expect(linearProgress.first().props()).toEqual({
max: 100,
min: 0,
- mode: 'determinate',
+ mode: "determinate",
value: 19
- })
- })
-})
-
+ });
+ });
+});
-describe('TexterStats (Dynamic campaign)', () => {
- it('contains the right text', () => {
- const stats = shallow()
+describe("TexterStats (Dynamic campaign)", () => {
+ it("contains the right text", () => {
+ const stats = shallow();
expect(stats.text()).toEqual(
- 'Test45 initial messages sentSomeone541 initial messages sent'
- )
- })
-})
+ "Test45 initial messages sentSomeone541 initial messages sent"
+ );
+ });
+});
diff --git a/__test__/TopNav.test.js b/__test__/TopNav.test.js
index f3051d2bf..3e5f60de7 100644
--- a/__test__/TopNav.test.js
+++ b/__test__/TopNav.test.js
@@ -1,36 +1,36 @@
-import React from 'react'
-import { shallow } from 'enzyme'
-import { StyleSheetTestUtils } from 'aphrodite'
-import TopNav from '../src/components/TopNav'
+import React from "react";
+import { shallow } from "enzyme";
+import { StyleSheetTestUtils } from "aphrodite";
+import TopNav from "../src/components/TopNav";
-describe('TopNav', () => {
- it('can render only title', () => {
- const nav = shallow()
+describe("TopNav", () => {
+ it("can render only title", () => {
+ const nav = shallow();
expect(nav.text()).toEqual(
- 'Welcome to my website'
- )
- expect(nav.find('Link').length).toBe(0)
- })
+ "Welcome to my website"
+ );
+ expect(nav.find("Link").length).toBe(0);
+ });
- it('can render Link to go back', () => {
+ it("can render Link to go back", () => {
const link = shallow(
- ).find('Link')
- expect(link.length).toBe(1)
- expect(link.prop('to')).toBe('/admin/1/campaigns')
- expect(link.find('IconButton').length).toBe(1)
- })
+ ).find("Link");
+ expect(link.length).toBe(1);
+ expect(link.prop("to")).toBe("/admin/1/campaigns");
+ expect(link.find("IconButton").length).toBe(1);
+ });
- it('renders UserMenu', () => {
- const nav = shallow()
- expect(nav.find('Connect(Apollo(withRouter(UserMenu)))').length).toBe(1)
- })
-})
+ it("renders UserMenu", () => {
+ const nav = shallow();
+ expect(nav.find("Connect(Apollo(withRouter(UserMenu)))").length).toBe(1);
+ });
+});
// https://github.com/Khan/aphrodite/issues/62#issuecomment-267026726
beforeEach(() => {
- StyleSheetTestUtils.suppressStyleInjection()
-})
+ StyleSheetTestUtils.suppressStyleInjection();
+});
afterEach(() => {
- StyleSheetTestUtils.clearBufferAndResumeStyleInjection()
-})
+ StyleSheetTestUtils.clearBufferAndResumeStyleInjection();
+});
diff --git a/__test__/backend.test.js b/__test__/backend.test.js
index 212fd8873..fefa518a8 100644
--- a/__test__/backend.test.js
+++ b/__test__/backend.test.js
@@ -1,53 +1,60 @@
-import { resolvers } from '../src/server/api/schema'
-import { schema } from '../src/api/schema'
+import { resolvers } from "../src/server/api/schema";
+import { schema } from "../src/api/schema";
import {
accessRequired,
assignmentRequired,
authRequired,
superAdminRequired
-} from '../src/server/api/errors'
-import { graphql } from 'graphql'
-import { User, Organization, Campaign, CampaignContact, Assignment, r } from '../src/server/models/'
-import { resolvers as campaignResolvers } from '../src/server/api/campaign'
-import { getContext,
- setupTest,
- cleanupTest } from './test_helpers'
-import { makeExecutableSchema } from 'graphql-tools'
+} from "../src/server/api/errors";
+import { graphql } from "graphql";
+import {
+ User,
+ Organization,
+ Campaign,
+ CampaignContact,
+ Assignment,
+ r
+} from "../src/server/models/";
+import { resolvers as campaignResolvers } from "../src/server/api/campaign";
+import { getContext, setupTest, cleanupTest } from "./test_helpers";
+import { makeExecutableSchema } from "graphql-tools";
const mySchema = makeExecutableSchema({
typeDefs: schema,
resolvers: resolvers,
- allowUndefinedInResolve: true,
-})
+ allowUndefinedInResolve: true
+});
-const rootValue = {}
+const rootValue = {};
// data items used across tests
-let testAdminUser
-let testInvite
-let testOrganization
-let testCampaign
-let testTexterUser
+let testAdminUser;
+let testInvite;
+let testOrganization;
+let testCampaign;
+let testTexterUser;
// data creation functions
-async function createUser(userInfo = {
- auth0_id: 'test123',
- first_name: 'TestUserFirst',
- last_name: 'TestUserLast',
- cell: '555-555-5555',
- email: 'testuser@example.com',
-}) {
- const user = new User(userInfo)
+async function createUser(
+ userInfo = {
+ auth0_id: "test123",
+ first_name: "TestUserFirst",
+ last_name: "TestUserLast",
+ cell: "555-555-5555",
+ email: "testuser@example.com"
+ }
+) {
+ const user = new User(userInfo);
try {
- await user.save()
- console.log("created user")
- console.log(user)
- return user
- } catch(err) {
- console.error('Error saving user')
- return false
+ await user.save();
+ console.log("created user");
+ console.log(user);
+ return user;
+ } catch (err) {
+ console.error("Error saving user");
+ return false;
}
}
@@ -58,15 +65,15 @@ async function createContact(campaignId) {
cell: "5555555555",
zip: "12345",
campaign_id: campaignId
- })
+ });
try {
- await contact.save()
- console.log("created contact")
- console.log(contact)
- return contact
- } catch(err) {
- console.error('Error saving contact: ', err)
- return false
+ await contact.save();
+ console.log("created contact");
+ console.log(contact);
+ return contact;
+ } catch (err) {
+ console.error("Error saving contact: ", err);
+ return false;
}
}
@@ -75,19 +82,19 @@ async function createInvite() {
createInvite(invite: {is_valid: true}) {
id
}
- }`
- const context = getContext()
+ }`;
+ const context = getContext();
try {
- const invite = await graphql(mySchema, inviteQuery, rootValue, context)
- return invite
- } catch(err) {
- console.error('Error creating invite')
- return false
+ const invite = await graphql(mySchema, inviteQuery, rootValue, context);
+ return invite;
+ } catch (err) {
+ console.error("Error creating invite");
+ return false;
}
}
async function createOrganization(user, name, userId, inviteId) {
- const context = getContext({ user })
+ const context = getContext({ user });
const orgQuery = `mutation createOrganization($name: String!, $userId: String!, $inviteId: String!) {
createOrganization(name: $name, userId: $userId, inviteId: $inviteId) {
@@ -99,25 +106,37 @@ async function createOrganization(user, name, userId, inviteId) {
textingHoursStart
textingHoursEnd
}
- }`
+ }`;
const variables = {
- "userId": userId,
- "name": name,
- "inviteId": inviteId
- }
+ userId: userId,
+ name: name,
+ inviteId: inviteId
+ };
try {
- const org = await graphql(mySchema, orgQuery, rootValue, context, variables)
- return org
- } catch(err) {
- console.error('Error creating organization')
- return false
+ const org = await graphql(
+ mySchema,
+ orgQuery,
+ rootValue,
+ context,
+ variables
+ );
+ return org;
+ } catch (err) {
+ console.error("Error creating organization");
+ return false;
}
}
-async function createCampaign(user, title, description, organizationId, contacts = []) {
- const context = getContext({user})
+async function createCampaign(
+ user,
+ title,
+ description,
+ organizationId,
+ contacts = []
+) {
+ const context = getContext({ user });
const campaignQuery = `mutation createCampaign($input: CampaignInput!) {
createCampaign(campaign: $input) {
@@ -128,116 +147,146 @@ async function createCampaign(user, title, description, organizationId, contacts
lastName
}
}
- }`
+ }`;
const variables = {
- "input": {
- "title": title,
- "description": description,
- "organizationId": organizationId,
- "contacts": contacts
+ input: {
+ title: title,
+ description: description,
+ organizationId: organizationId,
+ contacts: contacts
}
- }
+ };
try {
- const campaign = await graphql(mySchema, campaignQuery, rootValue, context, variables)
- return campaign
- } catch(err) {
- console.error('Error creating campaign')
- return false
+ const campaign = await graphql(
+ mySchema,
+ campaignQuery,
+ rootValue,
+ context,
+ variables
+ );
+ return campaign;
+ } catch (err) {
+ console.error("Error creating campaign");
+ return false;
}
}
// graphQL tests
-beforeAll(async () => await setupTest(), global.DATABASE_SETUP_TEARDOWN_TIMEOUT)
-afterAll(async () => await cleanupTest(), global.DATABASE_SETUP_TEARDOWN_TIMEOUT)
+beforeAll(
+ async () => await setupTest(),
+ global.DATABASE_SETUP_TEARDOWN_TIMEOUT
+);
+afterAll(
+ async () => await cleanupTest(),
+ global.DATABASE_SETUP_TEARDOWN_TIMEOUT
+);
-it('should be undefined when user not logged in', async () => {
+it("should be undefined when user not logged in", async () => {
const query = `{
currentUser {
id
}
- }`
- const context = getContext()
- const result = await graphql(mySchema, query, rootValue, context)
- const data = result
+ }`;
+ const context = getContext();
+ const result = await graphql(mySchema, query, rootValue, context);
+ const data = result;
- expect(typeof data.currentUser).toEqual('undefined')
-})
+ expect(typeof data.currentUser).toEqual("undefined");
+});
-it('should return the current user when user is logged in', async () => {
- testAdminUser = await createUser()
+it("should return the current user when user is logged in", async () => {
+ testAdminUser = await createUser();
const query = `{
currentUser {
email
}
- }`
- const context = getContext({ user: testAdminUser })
- const result = await graphql(mySchema, query, rootValue, context)
- const { data } = result
+ }`;
+ const context = getContext({ user: testAdminUser });
+ const result = await graphql(mySchema, query, rootValue, context);
+ const { data } = result;
- expect(data.currentUser.email).toBe('testuser@example.com')
-})
+ expect(data.currentUser.email).toBe("testuser@example.com");
+});
// TESTING CAMPAIGN CREATION FROM END TO END
-it('should create an invite', async () => {
- testInvite = await createInvite()
+it("should create an invite", async () => {
+ testInvite = await createInvite();
- expect(testInvite.data.createInvite.id).toBeTruthy()
-})
-
-it('should convert an invitation and user into a valid organization instance', async () => {
+ expect(testInvite.data.createInvite.id).toBeTruthy();
+});
+it("should convert an invitation and user into a valid organization instance", async () => {
if (testInvite && testAdminUser) {
- console.log("user and invite for org")
- console.log([testAdminUser,testInvite.data])
-
- testOrganization = await createOrganization(testAdminUser, "Testy test organization", testInvite.data.createInvite.id, testInvite.data.createInvite.id)
-
- expect(testOrganization.data.createOrganization.name).toBe('Testy test organization')
+ console.log("user and invite for org");
+ console.log([testAdminUser, testInvite.data]);
+
+ testOrganization = await createOrganization(
+ testAdminUser,
+ "Testy test organization",
+ testInvite.data.createInvite.id,
+ testInvite.data.createInvite.id
+ );
+
+ expect(testOrganization.data.createOrganization.name).toBe(
+ "Testy test organization"
+ );
} else {
- console.log("Failed to create invite and/or user for organization test")
- return false
+ console.log("Failed to create invite and/or user for organization test");
+ return false;
}
-})
-
-
-it('should create a test campaign', async () => {
- const campaignTitle = "test campaign"
- testCampaign = await createCampaign(testAdminUser, campaignTitle, "test description", testOrganization.data.createOrganization.id)
-
- expect(testCampaign.data.createCampaign.title).toBe(campaignTitle)
-})
-
-it('should create campaign contacts', async () => {
- const contact = await createContact(testCampaign.data.createCampaign.id)
- expect(contact.campaign_id).toBe(parseInt(testCampaign.data.createCampaign.id))
-})
-
-it('should add texters to a organization', async () => {
+});
+
+it("should create a test campaign", async () => {
+ const campaignTitle = "test campaign";
+ testCampaign = await createCampaign(
+ testAdminUser,
+ campaignTitle,
+ "test description",
+ testOrganization.data.createOrganization.id
+ );
+
+ expect(testCampaign.data.createCampaign.title).toBe(campaignTitle);
+});
+
+it("should create campaign contacts", async () => {
+ const contact = await createContact(testCampaign.data.createCampaign.id);
+ expect(contact.campaign_id).toBe(
+ parseInt(testCampaign.data.createCampaign.id)
+ );
+});
+
+it("should add texters to a organization", async () => {
testTexterUser = await createUser({
- auth0_id: 'test456',
- first_name: 'TestTexterFirst',
- last_name: 'TestTexterLast',
- cell: '555-555-6666',
- email: 'testtexter@example.com',
- })
+ auth0_id: "test456",
+ first_name: "TestTexterFirst",
+ last_name: "TestTexterLast",
+ cell: "555-555-6666",
+ email: "testtexter@example.com"
+ });
const joinQuery = `
mutation joinOrganization($organizationUuid: String!) {
joinOrganization(organizationUuid: $organizationUuid) {
id
}
- }`
+ }`;
const variables = {
organizationUuid: testOrganization.data.createOrganization.uuid
- }
- const context = getContext({user: testTexterUser})
- const result = await graphql(mySchema, joinQuery, rootValue, context, variables)
- expect(result.data.joinOrganization.id).toBeTruthy()
-})
-
-it('should assign texters to campaign contacts', async () => {
+ };
+ const context = getContext({ user: testTexterUser });
+ const result = await graphql(
+ mySchema,
+ joinQuery,
+ rootValue,
+ context,
+ variables
+ );
+ expect(result.data.joinOrganization.id).toBeTruthy();
+});
+
+it("should assign texters to campaign contacts", async () => {
const campaignEditQuery = `
mutation editCampaign($campaignId: String!, $campaign: CampaignInput!) {
editCampaign(id: $campaignId, campaign: $campaign) {
@@ -273,23 +322,31 @@ it('should assign texters to campaign contacts', async () => {
text
}
}
- }`
- const context = getContext({user: testAdminUser})
- const updateCampaign = Object.assign({}, testCampaign.data.createCampaign)
- const campaignId = updateCampaign.id
- updateCampaign.texters = [{
- id: testTexterUser.id
- }]
- delete(updateCampaign.id)
- delete(updateCampaign.contacts)
+ }`;
+ const context = getContext({ user: testAdminUser });
+ const updateCampaign = Object.assign({}, testCampaign.data.createCampaign);
+ const campaignId = updateCampaign.id;
+ updateCampaign.texters = [
+ {
+ id: testTexterUser.id
+ }
+ ];
+ delete updateCampaign.id;
+ delete updateCampaign.contacts;
const variables = {
campaignId: campaignId,
campaign: updateCampaign
- }
- const result = await graphql(mySchema, campaignEditQuery, rootValue, context, variables)
- expect(result.data.editCampaign.texters.length).toBe(1)
- expect(result.data.editCampaign.texters[0].assignment.contactsCount).toBe(1)
-})
+ };
+ const result = await graphql(
+ mySchema,
+ campaignEditQuery,
+ rootValue,
+ context,
+ variables
+ );
+ expect(result.data.editCampaign.texters.length).toBe(1);
+ expect(result.data.editCampaign.texters[0].assignment.contactsCount).toBe(1);
+});
// it('should save a campaign script composed of interaction steps', async() => {})
@@ -301,147 +358,193 @@ it('should assign texters to campaign contacts', async () => {
// it('should send an inital message to test contacts', async() => {})
-describe('Campaign', () => {
- let organization
- const adminUser = { is_superadmin: true, id: 1 }
+describe("Campaign", () => {
+ let organization;
+ const adminUser = { is_superadmin: true, id: 1 };
beforeEach(async () => {
- organization = await (new Organization({
- name: 'organization',
+ organization = await new Organization({
+ name: "organization",
texting_hours_start: 0,
texting_hours_end: 0
- })).save()
- })
+ }).save();
+ });
- describe('contacts', async () => {
- let campaigns
- let contacts
+ describe("contacts", async () => {
+ let campaigns;
+ let contacts;
beforeEach(async () => {
- campaigns = await Promise.all([
- new Campaign({
- organization_id: organization.id,
- is_started: false,
- is_archived: false,
- due_by: new Date()
- }),
- new Campaign({
- organization_id: organization.id,
- is_started: false,
- is_archived: false,
- due_by: new Date()
- })
- ].map(async (each) => (
- each.save()
- )))
-
- contacts = await Promise.all([
- new CampaignContact({campaign_id: campaigns[0].id, cell: '', message_status: 'closed'}),
- new CampaignContact({campaign_id: campaigns[1].id, cell: '', message_status: 'closed'})
- ].map(async (each) => (
- each.save()
- )))
- })
-
- test('resolves contacts', async () => {
- const results = await campaignResolvers.Campaign.contacts(campaigns[0], null, { user: adminUser })
- expect(results).toHaveLength(1)
- expect(results[0].campaign_id).toEqual(campaigns[0].id)
- })
-
- test('resolves contacts count', async () => {
- const results = await campaignResolvers.Campaign.contactsCount(campaigns[0], null, { user: adminUser })
- expect(results).toEqual(1)
- })
-
- test('resolves contacts count when empty', async () => {
- const campaign = await (new Campaign({
+ campaigns = await Promise.all(
+ [
+ new Campaign({
+ organization_id: organization.id,
+ is_started: false,
+ is_archived: false,
+ due_by: new Date()
+ }),
+ new Campaign({
+ organization_id: organization.id,
+ is_started: false,
+ is_archived: false,
+ due_by: new Date()
+ })
+ ].map(async each => each.save())
+ );
+
+ contacts = await Promise.all(
+ [
+ new CampaignContact({
+ campaign_id: campaigns[0].id,
+ cell: "",
+ message_status: "closed"
+ }),
+ new CampaignContact({
+ campaign_id: campaigns[1].id,
+ cell: "",
+ message_status: "closed"
+ })
+ ].map(async each => each.save())
+ );
+ });
+
+ test("resolves contacts", async () => {
+ const results = await campaignResolvers.Campaign.contacts(
+ campaigns[0],
+ null,
+ { user: adminUser }
+ );
+ expect(results).toHaveLength(1);
+ expect(results[0].campaign_id).toEqual(campaigns[0].id);
+ });
+
+ test("resolves contacts count", async () => {
+ const results = await campaignResolvers.Campaign.contactsCount(
+ campaigns[0],
+ null,
+ { user: adminUser }
+ );
+ expect(results).toEqual(1);
+ });
+
+ test("resolves contacts count when empty", async () => {
+ const campaign = await new Campaign({
organization_id: organization.id,
is_started: false,
is_archived: false,
due_by: new Date()
- })).save()
- const results = await campaignResolvers.Campaign.contactsCount(campaign, null, { user: adminUser })
- expect(results).toEqual(0)
- })
- })
-
- describe('unassigned contacts', () => {
- let campaign
+ }).save();
+ const results = await campaignResolvers.Campaign.contactsCount(
+ campaign,
+ null,
+ { user: adminUser }
+ );
+ expect(results).toEqual(0);
+ });
+ });
+
+ describe("unassigned contacts", () => {
+ let campaign;
beforeEach(async () => {
- campaign = await (new Campaign({
+ campaign = await new Campaign({
organization_id: organization.id,
is_started: false,
is_archived: false,
use_dynamic_assignment: true,
due_by: new Date()
- })).save()
- })
+ }).save();
+ });
- test('resolves unassigned contacts when true', async () => {
- const contact = await (new CampaignContact({
+ test("resolves unassigned contacts when true", async () => {
+ const contact = await new CampaignContact({
campaign_id: campaign.id,
- message_status: 'needsMessage',
- cell: '',
- })).save()
-
- const results = await campaignResolvers.Campaign.hasUnassignedContacts(campaign, null, { user: adminUser })
- expect(results).toEqual(true)
- const resultsForTexter = await campaignResolvers.Campaign.hasUnassignedContactsForTexter(campaign, null, { user: adminUser })
- expect(resultsForTexter).toEqual(true)
- })
-
- test('resolves unassigned contacts when false with assigned contacts', async () => {
- const user = await (new User({
- auth0_id: 'test123',
- first_name: 'TestUserFirst',
- last_name: 'TestUserLast',
- cell: '555-555-5555',
- email: 'testuser@example.com',
- })).save()
-
- const assignment = await (new Assignment({
+ message_status: "needsMessage",
+ cell: ""
+ }).save();
+
+ const results = await campaignResolvers.Campaign.hasUnassignedContacts(
+ campaign,
+ null,
+ { user: adminUser }
+ );
+ expect(results).toEqual(true);
+ const resultsForTexter = await campaignResolvers.Campaign.hasUnassignedContactsForTexter(
+ campaign,
+ null,
+ { user: adminUser }
+ );
+ expect(resultsForTexter).toEqual(true);
+ });
+
+ test("resolves unassigned contacts when false with assigned contacts", async () => {
+ const user = await new User({
+ auth0_id: "test123",
+ first_name: "TestUserFirst",
+ last_name: "TestUserLast",
+ cell: "555-555-5555",
+ email: "testuser@example.com"
+ }).save();
+
+ const assignment = await new Assignment({
user_id: user.id,
- campaign_id: campaign.id,
- })).save()
+ campaign_id: campaign.id
+ }).save();
- const contact = await (new CampaignContact({
+ const contact = await new CampaignContact({
campaign_id: campaign.id,
assignment_id: assignment.id,
- message_status: 'closed',
- cell: '',
- })).save()
-
- const results = await campaignResolvers.Campaign.hasUnassignedContacts(campaign, null, { user: adminUser })
- expect(results).toEqual(false)
- const resultsForTexter = await campaignResolvers.Campaign.hasUnassignedContactsForTexter(campaign, null, { user: adminUser })
- expect(resultsForTexter).toEqual(false)
- })
-
- test('resolves unassigned contacts when false with no contacts', async () => {
- const results = await campaignResolvers.Campaign.hasUnassignedContacts(campaign, null, { user: adminUser })
- expect(results).toEqual(false)
- })
-
- test('test assignmentRequired access control', async () => {
- const user = await createUser()
-
- const assignment = await (new Assignment({
+ message_status: "closed",
+ cell: ""
+ }).save();
+
+ const results = await campaignResolvers.Campaign.hasUnassignedContacts(
+ campaign,
+ null,
+ { user: adminUser }
+ );
+ expect(results).toEqual(false);
+ const resultsForTexter = await campaignResolvers.Campaign.hasUnassignedContactsForTexter(
+ campaign,
+ null,
+ { user: adminUser }
+ );
+ expect(resultsForTexter).toEqual(false);
+ });
+
+ test("resolves unassigned contacts when false with no contacts", async () => {
+ const results = await campaignResolvers.Campaign.hasUnassignedContacts(
+ campaign,
+ null,
+ { user: adminUser }
+ );
+ expect(results).toEqual(false);
+ });
+
+ test("test assignmentRequired access control", async () => {
+ const user = await createUser();
+
+ const assignment = await new Assignment({
user_id: user.id,
- campaign_id: campaign.id,
- })).save()
-
- const allowUser = await assignmentRequired(user, assignment.id, assignment)
- expect(allowUser).toEqual(true)
- const allowUserAssignmentId = await assignmentRequired(user, assignment.id)
- expect(allowUserAssignmentId).toEqual(true)
+ campaign_id: campaign.id
+ }).save();
+
+ const allowUser = await assignmentRequired(
+ user,
+ assignment.id,
+ assignment
+ );
+ expect(allowUser).toEqual(true);
+ const allowUserAssignmentId = await assignmentRequired(
+ user,
+ assignment.id
+ );
+ expect(allowUserAssignmentId).toEqual(true);
try {
- const notAllowed = await assignmentRequired(user, -1)
- throw new Exception('should throw BEFORE this exception')
- } catch(err) {
- expect(/not authorized/.test(String(err))).toEqual(true)
+ const notAllowed = await assignmentRequired(user, -1);
+ throw new Exception("should throw BEFORE this exception");
+ } catch (err) {
+ expect(/not authorized/.test(String(err))).toEqual(true);
}
- })
-
- })
-})
+ });
+ });
+});
diff --git a/__test__/components/AssignmentTexter.test.js b/__test__/components/AssignmentTexter.test.js
index 5688a93da..168656e56 100644
--- a/__test__/components/AssignmentTexter.test.js
+++ b/__test__/components/AssignmentTexter.test.js
@@ -1,9 +1,9 @@
-import React from 'react'
-import { shallow } from 'enzyme'
-import { StyleSheetTestUtils } from 'aphrodite'
+import React from "react";
+import { shallow } from "enzyme";
+import { StyleSheetTestUtils } from "aphrodite";
-import { genAssignment, contactGenerator } from '../test_client_helpers'
-import { AssignmentTexter } from '../../src/components/AssignmentTexter'
+import { genAssignment, contactGenerator } from "../test_client_helpers";
+import { AssignmentTexter } from "../../src/components/AssignmentTexter";
/*
These tests try to ensure the 'texting loop' -- i.e. the loop between
@@ -35,13 +35,12 @@ import { AssignmentTexter } from '../../src/components/AssignmentTexter'
* clearcontactidolddata(contactid)
*/
-
function sleep(ms) {
- return new Promise(resolve => setTimeout(resolve, ms))
+ return new Promise(resolve => setTimeout(resolve, ms));
}
function genComponent(assignment, propertyOverrides = {}) {
- StyleSheetTestUtils.suppressStyleInjection()
+ StyleSheetTestUtils.suppressStyleInjection();
const wrapper = shallow(
{} }}
refreshData={() => {}}
- loadContacts={(getIds) => {}}
+ loadContacts={getIds => {}}
getNewContacts={() => {}}
- assignContactsIfNeeded={
- (checkServer, currentContactIndex) => Promise.resolve()
- }
- organizationId={'123'}
+ assignContactsIfNeeded={(checkServer, currentContactIndex) =>
+ Promise.resolve()
+ }
+ organizationId={"123"}
{...propertyOverrides}
/>
- )
- return wrapper
+ );
+ return wrapper;
}
-describe('AssignmentTexter process flows', async () => {
- it('Normal nondynamic assignment queue', async () => {
- const assignment = genAssignment(false, true, /* contacts=*/ 6, 'needsMessage')
- const createContact = contactGenerator(assignment.id, 'needsMessage')
- let calledAssignmentIfNeeded = false
- let component
+describe("AssignmentTexter process flows", async () => {
+ it("Normal nondynamic assignment queue", async () => {
+ const assignment = genAssignment(
+ false,
+ true,
+ /* contacts=*/ 6,
+ "needsMessage"
+ );
+ const createContact = contactGenerator(assignment.id, "needsMessage");
+ let calledAssignmentIfNeeded = false;
+ let component;
const wrapper = genComponent(assignment, {
- loadContacts: (getIds) => {
- return { data: { getAssignmentContacts: getIds.map(createContact) } }
+ loadContacts: getIds => {
+ return { data: { getAssignmentContacts: getIds.map(createContact) } };
},
assignContactsIfNeeded: (checkServer, curContactIndex) => {
- calledAssignmentIfNeeded = true
- return Promise.resolve()
+ calledAssignmentIfNeeded = true;
+ return Promise.resolve();
}
- })
- component = wrapper.instance()
- let contactsContacted = 0
+ });
+ component = wrapper.instance();
+ let contactsContacted = 0;
while (component.hasNext()) {
- component.handleFinishContact(wrapper.state('currentContactIndex'))
- contactsContacted += 1
- await sleep(1) // triggers updates
+ component.handleFinishContact(wrapper.state("currentContactIndex"));
+ contactsContacted += 1;
+ await sleep(1); // triggers updates
}
// last contact
- component.handleFinishContact(wrapper.state('currentContactIndex'))
- contactsContacted += 1
- await sleep(1)
- expect(calledAssignmentIfNeeded).toBe(true)
- expect(contactsContacted).toBe(6)
- })
-})
+ component.handleFinishContact(wrapper.state("currentContactIndex"));
+ contactsContacted += 1;
+ await sleep(1);
+ expect(calledAssignmentIfNeeded).toBe(true);
+ expect(contactsContacted).toBe(6);
+ });
+});
/*
TESTS:
@@ -120,4 +124,3 @@ TESTS:
}
*
*/
-
diff --git a/__test__/components/TexterFAQs.test.js b/__test__/components/TexterFAQs.test.js
index ebb095413..527b2da71 100644
--- a/__test__/components/TexterFAQs.test.js
+++ b/__test__/components/TexterFAQs.test.js
@@ -1,27 +1,25 @@
-import React from 'react'
+import React from "react";
-import { shallow } from 'enzyme';
-import TexterFaqs from '../../src/components/TexterFaqs'
+import { shallow } from "enzyme";
+import TexterFaqs from "../../src/components/TexterFaqs";
-describe('FAQs component', () => {
+describe("FAQs component", () => {
// given
const faq = [
{
- question: 'q1',
- answer: 'a2'
+ question: "q1",
+ answer: "a2"
}
- ]
- const wrapper = shallow(
-
- )
+ ];
+ const wrapper = shallow();
// when
- test('Renders question and answer', () => {
- const question = wrapper.find('CardTitle')
- const answer = wrapper.find('CardText p')
+ test("Renders question and answer", () => {
+ const question = wrapper.find("CardTitle");
+ const answer = wrapper.find("CardText p");
// then
- expect(question.prop('title')).toBe('1. q1')
- expect(answer.text()).toBe('a2')
- })
-})
+ expect(question.prop("title")).toBe("1. q1");
+ expect(answer.text()).toBe("a2");
+ });
+});
diff --git a/__test__/containers/AssignmentTexterContact.test.js b/__test__/containers/AssignmentTexterContact.test.js
index 0f3be4b22..f0a667e56 100644
--- a/__test__/containers/AssignmentTexterContact.test.js
+++ b/__test__/containers/AssignmentTexterContact.test.js
@@ -1,20 +1,20 @@
/**
* @jest-environment jsdom
*/
-import React from 'react'
-import moment from 'moment-timezone'
-import {mount} from "enzyme";
-import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
-import {StyleSheetTestUtils} from 'aphrodite'
-import {AssignmentTexterContact} from "../../src/containers/AssignmentTexterContact";
+import React from "react";
+import moment from "moment-timezone";
+import { mount } from "enzyme";
+import MuiThemeProvider from "material-ui/styles/MuiThemeProvider";
+import { StyleSheetTestUtils } from "aphrodite";
+import { AssignmentTexterContact } from "../../src/containers/AssignmentTexterContact";
-var MockDate = require('mockdate');
+var MockDate = require("mockdate");
-jest.mock('../../src/lib/timezones')
-jest.unmock('../../src/lib/tz-helpers')
-jest.useFakeTimers()
+jest.mock("../../src/lib/timezones");
+jest.unmock("../../src/lib/tz-helpers");
+jest.useFakeTimers();
-var timezones = require('../../src/lib/timezones')
+var timezones = require("../../src/lib/timezones");
const campaign = {
id: 9,
@@ -36,8 +36,8 @@ const campaign = {
answerOptions: []
}
}
- ]
-}
+ ]
+};
const propsWithEnforcedTextingHoursCampaign = {
texter: {
@@ -66,41 +66,45 @@ const propsWithEnforcedTextingHoursCampaign = {
id: 20
}
],
- allContactsCount: 2,
+ allContactsCount: 2
},
refreshData: jest.fn(),
contact: {
- id: 19,
- assignmentId: 9,
- firstName: "larry",
- lastName: "person",
- cell: "+19734779697",
- zip: "10025",
- customFields: "{}",
- optOut: null,
- currentInteractionStepScript: "{firstName}",
- questionResponseValues: [],
- location: {
- city: "New York",
- state: "NY",
- timezone: {
- offset: -5,
- hasDST: true
- }
- },
- messageStatus: "needsMessage",
- messages: []
+ id: 19,
+ assignmentId: 9,
+ firstName: "larry",
+ lastName: "person",
+ cell: "+19734779697",
+ zip: "10025",
+ customFields: "{}",
+ optOut: null,
+ currentInteractionStepScript: "{firstName}",
+ questionResponseValues: [],
+ location: {
+ city: "New York",
+ state: "NY",
+ timezone: {
+ offset: -5,
+ hasDST: true
+ }
+ },
+ messageStatus: "needsMessage",
+ messages: []
}
-}
+};
-describe('when contact is not within texting hours...', () => {
+describe("when contact is not within texting hours...", () => {
afterEach(() => {
- propsWithEnforcedTextingHoursCampaign.refreshData.mockReset()
- })
-
- it('it refreshes data in componentDidMount', () => {
- timezones.isBetweenTextingHours.mockReturnValue(false)
- timezones.getLocalTime.mockReturnValue(moment().utc().utcOffset(-5))
+ propsWithEnforcedTextingHoursCampaign.refreshData.mockReset();
+ });
+
+ it("it refreshes data in componentDidMount", () => {
+ timezones.isBetweenTextingHours.mockReturnValue(false);
+ timezones.getLocalTime.mockReturnValue(
+ moment()
+ .utc()
+ .utcOffset(-5)
+ );
StyleSheetTestUtils.suppressStyleInjection();
let component = mount(
@@ -112,18 +116,23 @@ describe('when contact is not within texting hours...', () => {
contact={propsWithEnforcedTextingHoursCampaign.contact}
/>
- )
- jest.runOnlyPendingTimers()
- expect(propsWithEnforcedTextingHoursCampaign.refreshData.mock.calls).toHaveLength(1)
- })
-})
-
-
-describe('when contact is within texting hours...', () => {
- var component
+ );
+ jest.runOnlyPendingTimers();
+ expect(
+ propsWithEnforcedTextingHoursCampaign.refreshData.mock.calls
+ ).toHaveLength(1);
+ });
+});
+
+describe("when contact is within texting hours...", () => {
+ var component;
beforeEach(() => {
- timezones.isBetweenTextingHours.mockReturnValue(true)
- timezones.getLocalTime.mockReturnValue(moment().utc().utcOffset(-5))
+ timezones.isBetweenTextingHours.mockReturnValue(true);
+ timezones.getLocalTime.mockReturnValue(
+ moment()
+ .utc()
+ .utcOffset(-5)
+ );
StyleSheetTestUtils.suppressStyleInjection();
component = mount(
@@ -135,108 +144,131 @@ describe('when contact is within texting hours...', () => {
contact={propsWithEnforcedTextingHoursCampaign.contact}
/>
- )
- })
+ );
+ });
afterEach(() => {
- propsWithEnforcedTextingHoursCampaign.refreshData.mockReset()
- })
- it('it does NOT refresh data in componentDidMount', () => {
- jest.runOnlyPendingTimers()
- expect(propsWithEnforcedTextingHoursCampaign.refreshData.mock.calls).toHaveLength(0)
- })
-})
-
-describe('AssignmentTextContact has the proper enabled/disabled state when created', () => {
-
- it('is enabled if the contact is inside texting hours', () => {
- timezones.isBetweenTextingHours.mockReturnValueOnce(true)
- var assignmentTexterContact = new AssignmentTexterContact(propsWithEnforcedTextingHoursCampaign)
- expect(assignmentTexterContact.state.disabled).toBeFalsy()
- expect(assignmentTexterContact.state.disabledText).toEqual('Sending...')
- })
-
- it('is disabled if the contact is inside texting hours', () => {
- timezones.isBetweenTextingHours.mockReturnValueOnce(false)
- var assignmentTexterContact = new AssignmentTexterContact(propsWithEnforcedTextingHoursCampaign)
- expect(assignmentTexterContact.state.disabled).toBeTruthy()
- expect(assignmentTexterContact.state.disabledText).toEqual('Refreshing ...')
- })
-})
-
-describe('test isContactBetweenTextingHours', () => {
- var assignmentTexterContact
-
- beforeAll(() => {
- assignmentTexterContact = new AssignmentTexterContact(propsWithEnforcedTextingHoursCampaign)
- timezones.isBetweenTextingHours.mockImplementation((o, c) => false)
- MockDate.set('2018-02-01T15:00:00.000Z')
- timezones.getLocalTime.mockReturnValue(moment().utc().utcOffset(-5))
- })
-
- afterAll(() => {
- MockDate.reset()
- })
-
- beforeEach(() => {
- jest.resetAllMocks()
- })
-
- it('works when the contact has location data with empty timezone', () => {
-
- let contact = {
- location: {
- city: "New York",
- state: "NY",
- timezone: {
- offset: null,
- hasDST: null
- }
- }
- }
-
- expect(assignmentTexterContact.isContactBetweenTextingHours(contact)).toBeFalsy()
- expect(timezones.isBetweenTextingHours.mock.calls).toHaveLength(1)
-
- let theCall = timezones.isBetweenTextingHours.mock.calls[0]
- expect(theCall[0]).toBeFalsy()
- expect(theCall[1]).toEqual({textingHoursStart: 8, textingHoursEnd: 21, textingHoursEnforced: true})
- })
+ propsWithEnforcedTextingHoursCampaign.refreshData.mockReset();
+ });
+ it("it does NOT refresh data in componentDidMount", () => {
+ jest.runOnlyPendingTimers();
+ expect(
+ propsWithEnforcedTextingHoursCampaign.refreshData.mock.calls
+ ).toHaveLength(0);
+ });
+});
+
+describe("AssignmentTextContact has the proper enabled/disabled state when created", () => {
+ it("is enabled if the contact is inside texting hours", () => {
+ timezones.isBetweenTextingHours.mockReturnValueOnce(true);
+ var assignmentTexterContact = new AssignmentTexterContact(
+ propsWithEnforcedTextingHoursCampaign
+ );
+ expect(assignmentTexterContact.state.disabled).toBeFalsy();
+ expect(assignmentTexterContact.state.disabledText).toEqual("Sending...");
+ });
+
+ it("is disabled if the contact is inside texting hours", () => {
+ timezones.isBetweenTextingHours.mockReturnValueOnce(false);
+ var assignmentTexterContact = new AssignmentTexterContact(
+ propsWithEnforcedTextingHoursCampaign
+ );
+ expect(assignmentTexterContact.state.disabled).toBeTruthy();
+ expect(assignmentTexterContact.state.disabledText).toEqual(
+ "Refreshing ..."
+ );
+ });
+});
+
+describe("test isContactBetweenTextingHours", () => {
+ var assignmentTexterContact;
+
+ beforeAll(() => {
+ assignmentTexterContact = new AssignmentTexterContact(
+ propsWithEnforcedTextingHoursCampaign
+ );
+ timezones.isBetweenTextingHours.mockImplementation((o, c) => false);
+ MockDate.set("2018-02-01T15:00:00.000Z");
+ timezones.getLocalTime.mockReturnValue(
+ moment()
+ .utc()
+ .utcOffset(-5)
+ );
+ });
+
+ afterAll(() => {
+ MockDate.reset();
+ });
- it('works when the contact has location data', () => {
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
- let contact = {
- location: {
- city: "New York",
- state: "NY",
- timezone: {
- offset: -5,
- hasDST: true
- }
+ it("works when the contact has location data with empty timezone", () => {
+ let contact = {
+ location: {
+ city: "New York",
+ state: "NY",
+ timezone: {
+ offset: null,
+ hasDST: null
}
}
-
- expect(assignmentTexterContact.isContactBetweenTextingHours(contact)).toBeFalsy()
- expect(timezones.isBetweenTextingHours.mock.calls).toHaveLength(1)
-
- let theCall = timezones.isBetweenTextingHours.mock.calls[0]
- expect(theCall[0]).toEqual({hasDST: true, offset: -5})
- expect(theCall[1]).toEqual({textingHoursStart: 8, textingHoursEnd: 21, textingHoursEnforced: true})
-
-
- })
-
- it('works when the contact does not have location data', () => {
-
- let contact = {}
-
- expect(assignmentTexterContact.isContactBetweenTextingHours(contact)).toBeFalsy()
- expect(timezones.isBetweenTextingHours.mock.calls).toHaveLength(1)
-
- let theCall = timezones.isBetweenTextingHours.mock.calls[0]
- expect(theCall[0]).toBeNull()
- expect(theCall[1]).toEqual({textingHoursStart: 8, textingHoursEnd: 21, textingHoursEnforced: true})
-
-
- })
- }
-)
+ };
+
+ expect(
+ assignmentTexterContact.isContactBetweenTextingHours(contact)
+ ).toBeFalsy();
+ expect(timezones.isBetweenTextingHours.mock.calls).toHaveLength(1);
+
+ let theCall = timezones.isBetweenTextingHours.mock.calls[0];
+ expect(theCall[0]).toBeFalsy();
+ expect(theCall[1]).toEqual({
+ textingHoursStart: 8,
+ textingHoursEnd: 21,
+ textingHoursEnforced: true
+ });
+ });
+
+ it("works when the contact has location data", () => {
+ let contact = {
+ location: {
+ city: "New York",
+ state: "NY",
+ timezone: {
+ offset: -5,
+ hasDST: true
+ }
+ }
+ };
+
+ expect(
+ assignmentTexterContact.isContactBetweenTextingHours(contact)
+ ).toBeFalsy();
+ expect(timezones.isBetweenTextingHours.mock.calls).toHaveLength(1);
+
+ let theCall = timezones.isBetweenTextingHours.mock.calls[0];
+ expect(theCall[0]).toEqual({ hasDST: true, offset: -5 });
+ expect(theCall[1]).toEqual({
+ textingHoursStart: 8,
+ textingHoursEnd: 21,
+ textingHoursEnforced: true
+ });
+ });
+
+ it("works when the contact does not have location data", () => {
+ let contact = {};
+
+ expect(
+ assignmentTexterContact.isContactBetweenTextingHours(contact)
+ ).toBeFalsy();
+ expect(timezones.isBetweenTextingHours.mock.calls).toHaveLength(1);
+
+ let theCall = timezones.isBetweenTextingHours.mock.calls[0];
+ expect(theCall[0]).toBeNull();
+ expect(theCall[1]).toEqual({
+ textingHoursStart: 8,
+ textingHoursEnd: 21,
+ textingHoursEnforced: true
+ });
+ });
+});
diff --git a/__test__/containers/CampaignList.test.js b/__test__/containers/CampaignList.test.js
index 575cba663..aa6668133 100644
--- a/__test__/containers/CampaignList.test.js
+++ b/__test__/containers/CampaignList.test.js
@@ -1,63 +1,66 @@
/**
* @jest-environment jsdom
*/
-import React from 'react'
-import { mount } from 'enzyme'
-import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
-import { CampaignList } from '../../src/containers/CampaignList'
+import React from "react";
+import { mount } from "enzyme";
+import MuiThemeProvider from "material-ui/styles/MuiThemeProvider";
+import { CampaignList } from "../../src/containers/CampaignList";
-describe('Campaign list for campaign with null creator', () => {
+describe("Campaign list for campaign with null creator", () => {
// given
const campaignWithoutCreator = {
id: 1,
- title: 'Yes on A',
- creator: null,
- }
+ title: "Yes on A",
+ creator: null
+ };
const data = {
organization: {
campaigns: {
- campaigns: [ campaignWithoutCreator ],
- },
- },
- }
+ campaigns: [campaignWithoutCreator]
+ }
+ }
+ };
// when
- test('Renders for campaign with null creator, doesn\'t include created by', () => {
+ test("Renders for campaign with null creator, doesn't include created by", () => {
const wrapper = mount(
- )
- expect(wrapper.text().includes('Created by')).toBeFalsy()
- })
-})
+ );
+ expect(wrapper.text().includes("Created by")).toBeFalsy();
+ });
+});
-describe('Campaign list for campaign with creator', () => {
+describe("Campaign list for campaign with creator", () => {
// given
const campaignWithCreator = {
id: 1,
creator: {
- displayName: 'Lorem Ipsum'
- },
- }
+ displayName: "Lorem Ipsum"
+ }
+ };
const data = {
organization: {
campaigns: {
- campaigns: [ campaignWithCreator ],
- },
- },
- }
+ campaigns: [campaignWithCreator]
+ }
+ }
+ };
// when
- test('Renders for campaign with creator, includes created by', () => {
+ test("Renders for campaign with creator, includes created by", () => {
const wrapper = mount(
- )
- expect(wrapper.containsMatchingElement( — Created by Lorem Ipsum)).toBeTruthy()
- })
-})
-
+ );
+ expect(
+ wrapper.containsMatchingElement(
+ — Created by Lorem Ipsum
+ )
+ ).toBeTruthy();
+ });
+});
diff --git a/__test__/containers/TexterTodo.test.js b/__test__/containers/TexterTodo.test.js
index a1784fee8..8cf339a29 100644
--- a/__test__/containers/TexterTodo.test.js
+++ b/__test__/containers/TexterTodo.test.js
@@ -1,83 +1,104 @@
/**
* @jest-environment jsdom
*/
-import React from 'react'
-import {mount} from "enzyme";
-import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
-import {StyleSheetTestUtils} from 'aphrodite'
+import React from "react";
+import { mount } from "enzyme";
+import MuiThemeProvider from "material-ui/styles/MuiThemeProvider";
+import { StyleSheetTestUtils } from "aphrodite";
-import {genAssignment, contactGenerator} from '../test_client_helpers';
-import {TexterTodo} from "../../src/containers/TexterTodo";
+import { genAssignment, contactGenerator } from "../test_client_helpers";
+import { TexterTodo } from "../../src/containers/TexterTodo";
-function genComponent(isArchived, hasContacts, routerPushes, statusMessage, assignmentNull) {
- const assignmentId = 8
- const contactMapper = contactGenerator(assignmentId, statusMessage)
- let assignment = genAssignment(assignmentId, isArchived, hasContacts)
+function genComponent(
+ isArchived,
+ hasContacts,
+ routerPushes,
+ statusMessage,
+ assignmentNull
+) {
+ const assignmentId = 8;
+ const contactMapper = contactGenerator(assignmentId, statusMessage);
+ let assignment = genAssignment(assignmentId, isArchived, hasContacts);
if (assignmentNull) {
- assignment = null
+ assignment = null;
}
StyleSheetTestUtils.suppressStyleInjection();
return mount(
-
-
-
- )
-
+
+
+
+ );
}
-describe('TexterTodo tests...', () => {
+describe("TexterTodo tests...", () => {
//afterEach(() => {
// propsWithEnforcedTextingHoursCampaign.refreshData.mockReset()
//})
- it('redirect if the assignment is archived', () => {
- const routerPushes = []
- const isArchived = true
- const hasContacts = true
- const component = genComponent(isArchived, hasContacts, routerPushes, 'needsMessage')
- expect(routerPushes[0]).toBe('/app/123/todos')
- })
+ it("redirect if the assignment is archived", () => {
+ const routerPushes = [];
+ const isArchived = true;
+ const hasContacts = true;
+ const component = genComponent(
+ isArchived,
+ hasContacts,
+ routerPushes,
+ "needsMessage"
+ );
+ expect(routerPushes[0]).toBe("/app/123/todos");
+ });
- it('redirect if the assignment is null', () => {
- const routerPushes = []
- const isArchived = false
- const hasContacts = true
- const assignmentNull = true
- const component = genComponent(isArchived, hasContacts, routerPushes, 'needsMessage', assignmentNull)
- expect(routerPushes[0]).toBe('/app/123/todos')
- })
+ it("redirect if the assignment is null", () => {
+ const routerPushes = [];
+ const isArchived = false;
+ const hasContacts = true;
+ const assignmentNull = true;
+ const component = genComponent(
+ isArchived,
+ hasContacts,
+ routerPushes,
+ "needsMessage",
+ assignmentNull
+ );
+ expect(routerPushes[0]).toBe("/app/123/todos");
+ });
- it('redirect if the assignment is normal no redirects', () => {
- const routerPushes = []
- const isArchived = false
- const hasContacts = true
- const assignmentNull = true
- const component = genComponent(isArchived, hasContacts, routerPushes, 'needsMessage')
- expect(routerPushes).toEqual([])
- })
+ it("redirect if the assignment is normal no redirects", () => {
+ const routerPushes = [];
+ const isArchived = false;
+ const hasContacts = true;
+ const assignmentNull = true;
+ const component = genComponent(
+ isArchived,
+ hasContacts,
+ routerPushes,
+ "needsMessage"
+ );
+ expect(routerPushes).toEqual([]);
+ });
// 1. test assignContactsIfNeeded()
// 2. test getNewContacts()
// 3. test loadContacts()
// 4. component render
-
-})
+});
diff --git a/__test__/e2e/basic_text_manager.test.js b/__test__/e2e/basic_text_manager.test.js
index b26ea79e2..625903d13 100644
--- a/__test__/e2e/basic_text_manager.test.js
+++ b/__test__/e2e/basic_text_manager.test.js
@@ -1,17 +1,21 @@
-import { selenium } from './util/helpers'
-import STRINGS from './data/strings'
-import { campaigns, login, main, people, texter } from './page-functions/index'
+import { selenium } from "./util/helpers";
+import STRINGS from "./data/strings";
+import { campaigns, login, main, people, texter } from "./page-functions/index";
-jasmine.getEnv().addReporter(selenium.reporter)
+jasmine.getEnv().addReporter(selenium.reporter);
-describe('Basic Text Manager Workflow', () => {
+describe("Basic Text Manager Workflow", () => {
// Instantiate browser(s)
- const driverAdmin = selenium.buildDriver({ name: 'Spoke E2E Tests - Chrome - Basic Text Manager Workflow - Admin' })
- const driverTexter = selenium.buildDriver({ name: 'Spoke E2E Tests - Chrome - Basic Text Manager Workflow - Texter' })
+ const driverAdmin = selenium.buildDriver({
+ name: "Spoke E2E Tests - Chrome - Basic Text Manager Workflow - Admin"
+ });
+ const driverTexter = selenium.buildDriver({
+ name: "Spoke E2E Tests - Chrome - Basic Text Manager Workflow - Texter"
+ });
beforeAll(() => {
- global.e2e = {}
- })
+ global.e2e = {};
+ });
/**
* Test Suite Sequence:
@@ -23,121 +27,121 @@ describe('Basic Text Manager Workflow', () => {
*/
afterAll(async () => {
- await selenium.quitDriver(driverAdmin)
- await selenium.quitDriver(driverTexter)
- })
-
- describe('Setup Admin User', () => {
- describe('(As Admin) Open Landing Page', () => {
- login.landing(driverAdmin)
- })
-
- describe('(As Admin) Log In an admin to Spoke', () => {
- login.tryLoginThenSignUp(driverAdmin, STRINGS.users.admin0)
- })
-
- describe('(As Admin) Create a New Organization / Team', () => {
- main.createOrg(driverAdmin, STRINGS.org)
- })
- })
-
- describe('Create Campaign (No Existing Texter)', () => {
- const CAMPAIGN = STRINGS.campaigns.noExistingTexter
-
- describe('(As Admin) Create a New Campaign', () => {
- campaigns.startCampaign(driverAdmin, CAMPAIGN)
- })
-
- describe('(As Texter) Follow the Invite URL', () => {
- texter.viewInvite(driverTexter)
- login.tryLoginThenSignUp(driverTexter, CAMPAIGN.texter)
- })
-
- describe('(As Texter) Verify Todos', () => {
- texter.viewSendFirstTexts(driverTexter)
- })
-
- describe('(As Texter) Log Out', () => {
- main.logOutUser(driverTexter)
- })
- })
-
- describe('Create Campaign (Existing Texter)', () => {
- const CAMPAIGN = STRINGS.campaigns.existingTexter
-
- describe('(As Admin) Invite a new Texter', () => {
- people.invite(driverAdmin)
- })
-
- describe('(As Texter) Follow the Invite URL', () => {
- texter.viewInvite(driverTexter)
- login.tryLoginThenSignUp(driverTexter, CAMPAIGN.texter)
- })
-
- describe('(As Admin) Create a New Campaign', () => {
- campaigns.startCampaign(driverAdmin, CAMPAIGN)
- })
-
- describe('(As Texter) Send Texts', () => {
- texter.sendTexts(driverTexter, CAMPAIGN)
- })
-
- describe('(As Admin) Send Replies', () => {
- campaigns.sendReplies(driverAdmin, CAMPAIGN)
- })
-
- describe('(As Texter) View Replies', () => {
- texter.viewReplies(driverTexter, CAMPAIGN)
- })
-
- describe('(As Texter) Opt Out Contact', () => {
- texter.optOutContact(driverTexter)
- })
-
- describe('(As Texter) Log Out', () => {
- main.logOutUser(driverTexter)
- })
- })
-
- describe('Create Campaign (No Existing Texter with Opt-Out)', () => {
- const CAMPAIGN = STRINGS.campaigns.noExistingTexterOptOut
-
- describe('(As Admin) Create a New Campaign', () => {
- campaigns.startCampaign(driverAdmin, CAMPAIGN)
- })
-
- describe('(As Texter) Follow the Invite URL', () => {
- texter.viewInvite(driverTexter)
- login.tryLoginThenSignUp(driverTexter, CAMPAIGN.texter)
- })
-
- describe('(As Texter) Verify Todos', () => {
- texter.viewSendFirstTexts(driverTexter)
- })
-
- describe('(As Texter) Log Out', () => {
- main.logOutUser(driverTexter)
- })
- })
-
- describe('Create Campaign (Existing Texters with Opt-Out)', () => {
- const CAMPAIGN = STRINGS.campaigns.existingTexterOptOut
-
- describe('(As Admin) Invite a new Texter', () => {
- people.invite(driverAdmin)
- })
-
- describe('(As Texter) Follow the Invite URL', () => {
- texter.viewInvite(driverTexter)
- login.tryLoginThenSignUp(driverTexter, CAMPAIGN.texter)
- })
-
- describe('(As Admin) Create a New Campaign', () => {
- campaigns.startCampaign(driverAdmin, CAMPAIGN)
- })
-
- describe('(As Texter) Verify Todos', () => {
- texter.viewSendFirstTexts(driverTexter)
- })
- })
-})
+ await selenium.quitDriver(driverAdmin);
+ await selenium.quitDriver(driverTexter);
+ });
+
+ describe("Setup Admin User", () => {
+ describe("(As Admin) Open Landing Page", () => {
+ login.landing(driverAdmin);
+ });
+
+ describe("(As Admin) Log In an admin to Spoke", () => {
+ login.tryLoginThenSignUp(driverAdmin, STRINGS.users.admin0);
+ });
+
+ describe("(As Admin) Create a New Organization / Team", () => {
+ main.createOrg(driverAdmin, STRINGS.org);
+ });
+ });
+
+ describe("Create Campaign (No Existing Texter)", () => {
+ const CAMPAIGN = STRINGS.campaigns.noExistingTexter;
+
+ describe("(As Admin) Create a New Campaign", () => {
+ campaigns.startCampaign(driverAdmin, CAMPAIGN);
+ });
+
+ describe("(As Texter) Follow the Invite URL", () => {
+ texter.viewInvite(driverTexter);
+ login.tryLoginThenSignUp(driverTexter, CAMPAIGN.texter);
+ });
+
+ describe("(As Texter) Verify Todos", () => {
+ texter.viewSendFirstTexts(driverTexter);
+ });
+
+ describe("(As Texter) Log Out", () => {
+ main.logOutUser(driverTexter);
+ });
+ });
+
+ describe("Create Campaign (Existing Texter)", () => {
+ const CAMPAIGN = STRINGS.campaigns.existingTexter;
+
+ describe("(As Admin) Invite a new Texter", () => {
+ people.invite(driverAdmin);
+ });
+
+ describe("(As Texter) Follow the Invite URL", () => {
+ texter.viewInvite(driverTexter);
+ login.tryLoginThenSignUp(driverTexter, CAMPAIGN.texter);
+ });
+
+ describe("(As Admin) Create a New Campaign", () => {
+ campaigns.startCampaign(driverAdmin, CAMPAIGN);
+ });
+
+ describe("(As Texter) Send Texts", () => {
+ texter.sendTexts(driverTexter, CAMPAIGN);
+ });
+
+ describe("(As Admin) Send Replies", () => {
+ campaigns.sendReplies(driverAdmin, CAMPAIGN);
+ });
+
+ describe("(As Texter) View Replies", () => {
+ texter.viewReplies(driverTexter, CAMPAIGN);
+ });
+
+ describe("(As Texter) Opt Out Contact", () => {
+ texter.optOutContact(driverTexter);
+ });
+
+ describe("(As Texter) Log Out", () => {
+ main.logOutUser(driverTexter);
+ });
+ });
+
+ describe("Create Campaign (No Existing Texter with Opt-Out)", () => {
+ const CAMPAIGN = STRINGS.campaigns.noExistingTexterOptOut;
+
+ describe("(As Admin) Create a New Campaign", () => {
+ campaigns.startCampaign(driverAdmin, CAMPAIGN);
+ });
+
+ describe("(As Texter) Follow the Invite URL", () => {
+ texter.viewInvite(driverTexter);
+ login.tryLoginThenSignUp(driverTexter, CAMPAIGN.texter);
+ });
+
+ describe("(As Texter) Verify Todos", () => {
+ texter.viewSendFirstTexts(driverTexter);
+ });
+
+ describe("(As Texter) Log Out", () => {
+ main.logOutUser(driverTexter);
+ });
+ });
+
+ describe("Create Campaign (Existing Texters with Opt-Out)", () => {
+ const CAMPAIGN = STRINGS.campaigns.existingTexterOptOut;
+
+ describe("(As Admin) Invite a new Texter", () => {
+ people.invite(driverAdmin);
+ });
+
+ describe("(As Texter) Follow the Invite URL", () => {
+ texter.viewInvite(driverTexter);
+ login.tryLoginThenSignUp(driverTexter, CAMPAIGN.texter);
+ });
+
+ describe("(As Admin) Create a New Campaign", () => {
+ campaigns.startCampaign(driverAdmin, CAMPAIGN);
+ });
+
+ describe("(As Texter) Verify Todos", () => {
+ texter.viewSendFirstTexts(driverTexter);
+ });
+ });
+});
diff --git a/__test__/e2e/create_copy_campaign.test.js b/__test__/e2e/create_copy_campaign.test.js
index d5185e7ce..d2ae33ad3 100644
--- a/__test__/e2e/create_copy_campaign.test.js
+++ b/__test__/e2e/create_copy_campaign.test.js
@@ -1,50 +1,54 @@
-import { selenium } from './util/helpers'
-import STRINGS from './data/strings'
-import { campaigns, login, main, people, texter } from './page-functions/index'
+import { selenium } from "./util/helpers";
+import STRINGS from "./data/strings";
+import { campaigns, login, main, people, texter } from "./page-functions/index";
-jasmine.getEnv().addReporter(selenium.reporter)
+jasmine.getEnv().addReporter(selenium.reporter);
-describe('Create and Copy Campaign', () => {
+describe("Create and Copy Campaign", () => {
// Instantiate browser(s)
- const driverAdmin = selenium.buildDriver({ name: 'Spoke E2E Tests - Chrome - Create and Copy Campaign - Admin' })
- const driverTexter = selenium.buildDriver({ name: 'Spoke E2E Tests - Chrome - Create and Copy Campaign - Texter' })
- const CAMPAIGN = STRINGS.campaigns.copyCampaign
+ const driverAdmin = selenium.buildDriver({
+ name: "Spoke E2E Tests - Chrome - Create and Copy Campaign - Admin"
+ });
+ const driverTexter = selenium.buildDriver({
+ name: "Spoke E2E Tests - Chrome - Create and Copy Campaign - Texter"
+ });
+ const CAMPAIGN = STRINGS.campaigns.copyCampaign;
beforeAll(() => {
- global.e2e = {}
- })
+ global.e2e = {};
+ });
afterAll(async () => {
- await selenium.quitDriver(driverAdmin)
- await selenium.quitDriver(driverTexter)
- })
-
- describe('(As Admin) Open Landing Page', () => {
- login.landing(driverAdmin)
- })
-
- describe('(As Admin) Log In an admin to Spoke', () => {
- login.tryLoginThenSignUp(driverAdmin, CAMPAIGN.admin)
- })
-
- describe('(As Admin) Create a New Organization / Team', () => {
- main.createOrg(driverAdmin, STRINGS.org)
- })
-
- describe('(As Admin) Invite a new User', () => {
- people.invite(driverAdmin)
- })
-
- describe('(As Texter) Follow the Invite URL', () => {
- texter.viewInvite(driverTexter)
- login.tryLoginThenSignUp(driverTexter, CAMPAIGN.texter)
- })
-
- describe('(As Admin) Create a New Campaign', () => {
- campaigns.startCampaign(driverAdmin, CAMPAIGN)
- })
-
- describe('(As Admin) Copy Campaign', () => {
- campaigns.copyCampaign(driverAdmin, CAMPAIGN)
- })
-})
+ await selenium.quitDriver(driverAdmin);
+ await selenium.quitDriver(driverTexter);
+ });
+
+ describe("(As Admin) Open Landing Page", () => {
+ login.landing(driverAdmin);
+ });
+
+ describe("(As Admin) Log In an admin to Spoke", () => {
+ login.tryLoginThenSignUp(driverAdmin, CAMPAIGN.admin);
+ });
+
+ describe("(As Admin) Create a New Organization / Team", () => {
+ main.createOrg(driverAdmin, STRINGS.org);
+ });
+
+ describe("(As Admin) Invite a new User", () => {
+ people.invite(driverAdmin);
+ });
+
+ describe("(As Texter) Follow the Invite URL", () => {
+ texter.viewInvite(driverTexter);
+ login.tryLoginThenSignUp(driverTexter, CAMPAIGN.texter);
+ });
+
+ describe("(As Admin) Create a New Campaign", () => {
+ campaigns.startCampaign(driverAdmin, CAMPAIGN);
+ });
+
+ describe("(As Admin) Copy Campaign", () => {
+ campaigns.copyCampaign(driverAdmin, CAMPAIGN);
+ });
+});
diff --git a/__test__/e2e/create_edit_campaign.test.js b/__test__/e2e/create_edit_campaign.test.js
index 508e799bc..24a712c34 100644
--- a/__test__/e2e/create_edit_campaign.test.js
+++ b/__test__/e2e/create_edit_campaign.test.js
@@ -1,39 +1,41 @@
-import { selenium } from './util/helpers'
-import STRINGS from './data/strings'
-import { campaigns, login, main } from './page-functions/index'
+import { selenium } from "./util/helpers";
+import STRINGS from "./data/strings";
+import { campaigns, login, main } from "./page-functions/index";
-jasmine.getEnv().addReporter(selenium.reporter)
+jasmine.getEnv().addReporter(selenium.reporter);
-describe('Create and Edit Campaign', () => {
+describe("Create and Edit Campaign", () => {
// Instantiate browser(s)
- const driver = selenium.buildDriver({ name: 'Spoke E2E Tests - Chrome - Create and Edit Campaign - Admin' })
- const CAMPAIGN = STRINGS.campaigns.editCampaign
+ const driver = selenium.buildDriver({
+ name: "Spoke E2E Tests - Chrome - Create and Edit Campaign - Admin"
+ });
+ const CAMPAIGN = STRINGS.campaigns.editCampaign;
beforeAll(() => {
- global.e2e = {}
- })
+ global.e2e = {};
+ });
afterAll(async () => {
- await selenium.quitDriver(driver)
- })
+ await selenium.quitDriver(driver);
+ });
- describe('(As Admin) Open Landing Page', () => {
- login.landing(driver)
- })
+ describe("(As Admin) Open Landing Page", () => {
+ login.landing(driver);
+ });
- describe('(As Admin) Log In an admin to Spoke', () => {
- login.tryLoginThenSignUp(driver, CAMPAIGN.admin)
- })
+ describe("(As Admin) Log In an admin to Spoke", () => {
+ login.tryLoginThenSignUp(driver, CAMPAIGN.admin);
+ });
- describe('(As Admin) Create a New Organization / Team', () => {
- main.createOrg(driver, STRINGS.org)
- })
+ describe("(As Admin) Create a New Organization / Team", () => {
+ main.createOrg(driver, STRINGS.org);
+ });
- describe('(As Admin) Create a New Campaign', () => {
- campaigns.startCampaign(driver, CAMPAIGN)
- })
+ describe("(As Admin) Create a New Campaign", () => {
+ campaigns.startCampaign(driver, CAMPAIGN);
+ });
- describe('(As Admin) Edit Campaign', () => {
- campaigns.editCampaign(driver, CAMPAIGN)
- })
-})
+ describe("(As Admin) Edit Campaign", () => {
+ campaigns.editCampaign(driver, CAMPAIGN);
+ });
+});
diff --git a/__test__/e2e/data/strings.js b/__test__/e2e/data/strings.js
index 51d29accf..2452e66d3 100644
--- a/__test__/e2e/data/strings.js
+++ b/__test__/e2e/data/strings.js
@@ -1,116 +1,116 @@
-import path from 'path'
-import _ from 'lodash'
+import path from "path";
+import _ from "lodash";
// Common to all campaigns
const contacts = {
- csv: path.resolve(__dirname, './people.csv')
-}
+ csv: path.resolve(__dirname, "./people.csv")
+};
const texters = {
contactLength: 2,
contactLengthAfterOptOut: 1
-}
+};
const interaction = {
- script: 'Test First {firstName} Last {lastName}!',
- question: 'Test Question?',
+ script: "Test First {firstName} Last {lastName}!",
+ question: "Test Question?",
answers: [
{
- answerOption: 'Test Answer 0',
- script: 'Test Answer 0 {firstName}.',
- questionText: 'Test Child Question 0?'
+ answerOption: "Test Answer 0",
+ script: "Test Answer 0 {firstName}.",
+ questionText: "Test Child Question 0?"
},
{
- answerOption: 'Test Answer 1',
- script: 'Test Answer 1 {lastName}.',
- questionText: 'Test Child Question 1?'
+ answerOption: "Test Answer 1",
+ script: "Test Answer 1 {lastName}.",
+ questionText: "Test Child Question 1?"
}
]
-}
+};
const cannedResponses = [
{
- title: 'Test CR0',
- script: 'Test CR First {firstName} Last {lastName}.'
+ title: "Test CR0",
+ script: "Test CR First {firstName} Last {lastName}."
}
-]
+];
-const standardReply = 'Test Reply'
+const standardReply = "Test Reply";
-const org = 'SpokeTestOrg'
+const org = "SpokeTestOrg";
const users = {
/**
* Note: Changing passwords for existing Auth0 users requires the user be removed from Auth0
*/
admin0: {
- name: 'admin0',
- email: 'spokeadmin0@moveon.org',
- password: 'SpokeAdmin0!',
- given_name: 'Adminzerofirst',
- family_name: 'Adminzerolast',
- cell: '4145550000'
+ name: "admin0",
+ email: "spokeadmin0@moveon.org",
+ password: "SpokeAdmin0!",
+ given_name: "Adminzerofirst",
+ family_name: "Adminzerolast",
+ cell: "4145550000"
},
admin1: {
- name: 'admin1',
- email: 'spokeadmin1@moveon.org',
- email_changed: 'spokeadmin1b@moveon.org',
- password: 'SpokeAdmin1!',
- given_name: 'Adminonefirst',
- given_name_changed: 'Adminonefirstb',
- family_name: 'Adminonelast',
- family_name_changed: 'Adminonelastb',
- cell: '4145550001',
- cell_changed: '6085550001'
+ name: "admin1",
+ email: "spokeadmin1@moveon.org",
+ email_changed: "spokeadmin1b@moveon.org",
+ password: "SpokeAdmin1!",
+ given_name: "Adminonefirst",
+ given_name_changed: "Adminonefirstb",
+ family_name: "Adminonelast",
+ family_name_changed: "Adminonelastb",
+ cell: "4145550001",
+ cell_changed: "6085550001"
},
texter0: {
- name: 'texter0',
- email: 'spoketexter0@moveon.org',
- password: 'SpokeTexter0!',
- given_name: 'Texterzerofirst',
- family_name: 'Texterzerolast',
- cell: '4146660000'
+ name: "texter0",
+ email: "spoketexter0@moveon.org",
+ password: "SpokeTexter0!",
+ given_name: "Texterzerofirst",
+ family_name: "Texterzerolast",
+ cell: "4146660000"
},
texter1: {
- name: 'texter1',
- email: 'spoketexter1@moveon.org',
- email_changed: 'spoketexter1b@moveon.org',
- password: 'SpokeTexter1!',
- given_name: 'Texteronefirst',
- given_name_changed: 'Texteronefirstb',
- family_name: 'Texteronelast',
- family_name_changed: 'Texteronelastb',
- cell: '4146660001',
- cell_changed: '6086660001'
+ name: "texter1",
+ email: "spoketexter1@moveon.org",
+ email_changed: "spoketexter1b@moveon.org",
+ password: "SpokeTexter1!",
+ given_name: "Texteronefirst",
+ given_name_changed: "Texteronefirstb",
+ family_name: "Texteronelast",
+ family_name_changed: "Texteronelastb",
+ cell: "4146660001",
+ cell_changed: "6086660001"
},
texter2: {
- name: 'texter2',
- email: 'spoketexter2@moveon.org',
- password: 'SpokeTexter2!',
- given_name: 'Textertwofirst',
- family_name: 'Textertwolast',
- cell: '4146660002'
+ name: "texter2",
+ email: "spoketexter2@moveon.org",
+ password: "SpokeTexter2!",
+ given_name: "Textertwofirst",
+ family_name: "Textertwolast",
+ cell: "4146660002"
},
texter3: {
- name: 'texter3',
- email: 'spoketexter3@moveon.org',
- password: 'SpokeTexter3!',
- given_name: 'Texterthreefirst',
- family_name: 'Texterthreelast',
- cell: '4146660003'
+ name: "texter3",
+ email: "spoketexter3@moveon.org",
+ password: "SpokeTexter3!",
+ given_name: "Texterthreefirst",
+ family_name: "Texterthreelast",
+ cell: "4146660003"
}
-}
+};
const campaigns = {
noExistingTexter: {
- name: 'noExistingTexter',
+ name: "noExistingTexter",
optOut: false,
admin: users.admin0,
texter: users.texter0,
existingTexter: false,
basics: {
- title: 'Test NET Campaign Title',
- description: 'Test NET Campaign Description'
+ title: "Test NET Campaign Title",
+ description: "Test NET Campaign Description"
},
contacts,
texters,
@@ -119,14 +119,14 @@ const campaigns = {
standardReply
},
existingTexter: {
- name: 'existingTexter',
+ name: "existingTexter",
optOut: false,
admin: users.admin0,
texter: users.texter1,
existingTexter: true,
basics: {
- title: 'Test ET Campaign Title',
- description: 'Test ET Campaign Description'
+ title: "Test ET Campaign Title",
+ description: "Test ET Campaign Description"
},
contacts,
texters,
@@ -135,47 +135,51 @@ const campaigns = {
standardReply
},
noExistingTexterOptOut: {
- name: 'noExistingTexterOptOut',
+ name: "noExistingTexterOptOut",
optOut: true,
admin: users.admin0,
texter: users.texter2,
existingTexter: false,
basics: {
- title: 'Test NETOO Campaign Title',
- description: 'Test NETOO Campaign Description'
+ title: "Test NETOO Campaign Title",
+ description: "Test NETOO Campaign Description"
},
contacts,
- texters: _.assign({}, texters, { contactLength: texters.contactLengthAfterOptOut }),
+ texters: _.assign({}, texters, {
+ contactLength: texters.contactLengthAfterOptOut
+ }),
interaction,
cannedResponses,
standardReply
},
existingTexterOptOut: {
- name: 'existingTexterOptOut',
+ name: "existingTexterOptOut",
optOut: true,
admin: users.admin0,
texter: users.texter3,
existingTexter: true,
basics: {
- title: 'Test ETOO Campaign Title',
- description: 'Test ETOO Campaign Description'
+ title: "Test ETOO Campaign Title",
+ description: "Test ETOO Campaign Description"
},
contacts,
- texters: _.assign({}, texters, { contactLength: texters.contactLengthAfterOptOut }),
+ texters: _.assign({}, texters, {
+ contactLength: texters.contactLengthAfterOptOut
+ }),
interaction,
cannedResponses,
standardReply
},
copyCampaign: {
- name: 'copyCampaign',
+ name: "copyCampaign",
admin: users.admin0,
texter: users.texter0,
existingTexter: true,
dynamicAssignment: false,
basics: {
- title: 'Test C Campaign Title',
- title_copied: 'COPY - Test C Campaign Title',
- description: 'Test C Campaign Description'
+ title: "Test C Campaign Title",
+ title_copied: "COPY - Test C Campaign Title",
+ description: "Test C Campaign Description"
},
contacts,
texters,
@@ -183,14 +187,14 @@ const campaigns = {
cannedResponses
},
editCampaign: {
- name: 'editCampaign',
+ name: "editCampaign",
admin: users.admin1,
existingTexter: false,
dynamicAssignment: true,
basics: {
- title: 'Test E Campaign Title',
- title_changed: 'Test E Campaign Title Changed',
- description: 'Test E Campaign Description'
+ title: "Test E Campaign Title",
+ title_changed: "Test E Campaign Title Changed",
+ description: "Test E Campaign Description"
},
contacts,
texters,
@@ -198,14 +202,14 @@ const campaigns = {
cannedResponses
},
userManagement: {
- name: 'userManagement',
+ name: "userManagement",
admin: users.admin1,
texter: users.texter1
}
-}
+};
export default {
campaigns,
org,
users
-}
+};
diff --git a/__test__/e2e/invite_texter.test.js b/__test__/e2e/invite_texter.test.js
index 3e53a9c8a..9504503bd 100644
--- a/__test__/e2e/invite_texter.test.js
+++ b/__test__/e2e/invite_texter.test.js
@@ -1,50 +1,54 @@
-import { selenium } from './util/helpers'
-import STRINGS from './data/strings'
-import { login, main, people, texter } from './page-functions/index'
+import { selenium } from "./util/helpers";
+import STRINGS from "./data/strings";
+import { login, main, people, texter } from "./page-functions/index";
-jasmine.getEnv().addReporter(selenium.reporter)
+jasmine.getEnv().addReporter(selenium.reporter);
-describe('Invite Texter workflow', () => {
+describe("Invite Texter workflow", () => {
// Instantiate browser(s)
- const driverAdmin = selenium.buildDriver({ name: 'Spoke E2E Tests - Chrome - Invite Texter workflow - Admin' })
- const driverTexter = selenium.buildDriver({ name: 'Spoke E2E Tests - Chrome - Invite Texter workflow - Texter' })
- const CAMPAIGN = STRINGS.campaigns.userManagement
+ const driverAdmin = selenium.buildDriver({
+ name: "Spoke E2E Tests - Chrome - Invite Texter workflow - Admin"
+ });
+ const driverTexter = selenium.buildDriver({
+ name: "Spoke E2E Tests - Chrome - Invite Texter workflow - Texter"
+ });
+ const CAMPAIGN = STRINGS.campaigns.userManagement;
beforeAll(() => {
- global.e2e = {}
- })
+ global.e2e = {};
+ });
afterAll(async () => {
- await selenium.quitDriver(driverAdmin)
- await selenium.quitDriver(driverTexter)
- })
-
- describe('(As Admin) Open Landing Page', () => {
- login.landing(driverAdmin)
- })
-
- describe('(As Admin) Log In an admin to Spoke', () => {
- login.tryLoginThenSignUp(driverAdmin, CAMPAIGN.admin)
- })
-
- describe('(As Admin) Create a New Organization / Team', () => {
- main.createOrg(driverAdmin, STRINGS.org)
- })
-
- describe('(As Admin) Invite a new User', () => {
- people.invite(driverAdmin)
- })
-
- describe('(As Texter) Follow the Invite URL', () => {
- texter.viewInvite(driverTexter)
- login.tryLoginThenSignUp(driverTexter, CAMPAIGN.texter)
- })
-
- describe('(As Admin) Edit User', () => {
- people.editUser(driverAdmin, CAMPAIGN.admin)
- })
-
- describe('(As Texter) Edit User', () => {
- main.editUser(driverTexter, CAMPAIGN.texter)
- })
-})
+ await selenium.quitDriver(driverAdmin);
+ await selenium.quitDriver(driverTexter);
+ });
+
+ describe("(As Admin) Open Landing Page", () => {
+ login.landing(driverAdmin);
+ });
+
+ describe("(As Admin) Log In an admin to Spoke", () => {
+ login.tryLoginThenSignUp(driverAdmin, CAMPAIGN.admin);
+ });
+
+ describe("(As Admin) Create a New Organization / Team", () => {
+ main.createOrg(driverAdmin, STRINGS.org);
+ });
+
+ describe("(As Admin) Invite a new User", () => {
+ people.invite(driverAdmin);
+ });
+
+ describe("(As Texter) Follow the Invite URL", () => {
+ texter.viewInvite(driverTexter);
+ login.tryLoginThenSignUp(driverTexter, CAMPAIGN.texter);
+ });
+
+ describe("(As Admin) Edit User", () => {
+ people.editUser(driverAdmin, CAMPAIGN.admin);
+ });
+
+ describe("(As Texter) Edit User", () => {
+ main.editUser(driverTexter, CAMPAIGN.texter);
+ });
+});
diff --git a/__test__/e2e/page-functions/campaigns.js b/__test__/e2e/page-functions/campaigns.js
index 3d89249c1..15700841e 100644
--- a/__test__/e2e/page-functions/campaigns.js
+++ b/__test__/e2e/page-functions/campaigns.js
@@ -5,254 +5,443 @@
* Similarly, a sleep is added because it's difficult to know when the picker dialog is gone.
*/
-import _ from 'lodash'
-import { wait, urlBuilder } from '../util/helpers'
-import pom from '../page-objects/index'
+import _ from "lodash";
+import { wait, urlBuilder } from "../util/helpers";
+import pom from "../page-objects/index";
// For legibility
-const form = pom.campaigns.form
+const form = pom.campaigns.form;
export const campaigns = {
startCampaign(driver, campaign) {
- it('opens the Campaigns tab', async () => {
- await driver.get(urlBuilder.admin.root())
- await wait.andClick(driver, pom.navigation.sections.campaigns)
- })
+ it("opens the Campaigns tab", async () => {
+ await driver.get(urlBuilder.admin.root());
+ await wait.andClick(driver, pom.navigation.sections.campaigns);
+ });
- it('clicks the + button to add a new campaign', async () => {
- await wait.andClick(driver, pom.campaigns.add, { goesStale: true })
- })
+ it("clicks the + button to add a new campaign", async () => {
+ await wait.andClick(driver, pom.campaigns.add, { goesStale: true });
+ });
- it('completes the Basics section', async () => {
+ it("completes the Basics section", async () => {
// Title
- await wait.andType(driver, form.basics.title, campaign.basics.title)
+ await wait.andType(driver, form.basics.title, campaign.basics.title);
// Description
- await wait.andType(driver, form.basics.description, campaign.basics.description)
+ await wait.andType(
+ driver,
+ form.basics.description,
+ campaign.basics.description
+ );
// Select a Due Date using the Date Picker
- await wait.andClick(driver, form.basics.dueBy)
- await wait.andClick(driver, form.datePickerDialog.nextMonth, { waitAfterVisible: 2000 })
- await wait.andClick(driver, form.datePickerDialog.enabledDate, { waitAfterVisible: 2000, goesStale: true })
+ await wait.andClick(driver, form.basics.dueBy);
+ await wait.andClick(driver, form.datePickerDialog.nextMonth, {
+ waitAfterVisible: 2000
+ });
+ await wait.andClick(driver, form.datePickerDialog.enabledDate, {
+ waitAfterVisible: 2000,
+ goesStale: true
+ });
// Save
- await wait.andClick(driver, form.save, { waitAfterVisible: 2000 })
+ await wait.andClick(driver, form.save, { waitAfterVisible: 2000 });
// This should switch to the Contacts section
- expect(await wait.andGetEl(driver, form.contacts.uploadButton)).toBeDefined()
- expect(await wait.andGetEl(driver, form.contacts.input, { elementIsVisible: false })).toBeDefined()
- })
+ expect(
+ await wait.andGetEl(driver, form.contacts.uploadButton)
+ ).toBeDefined();
+ expect(
+ await wait.andGetEl(driver, form.contacts.input, {
+ elementIsVisible: false
+ })
+ ).toBeDefined();
+ });
- it('completes the Contacts section', async () => {
- await wait.andType(driver, form.contacts.input, campaign.contacts.csv, { clear: false, click: false, elementIsVisible: false })
- expect(await wait.andGetEl(driver, form.contacts.uploadedContacts)).toBeDefined()
+ it("completes the Contacts section", async () => {
+ await wait.andType(driver, form.contacts.input, campaign.contacts.csv, {
+ clear: false,
+ click: false,
+ elementIsVisible: false
+ });
+ expect(
+ await wait.andGetEl(driver, form.contacts.uploadedContacts)
+ ).toBeDefined();
// Save
- await wait.andClick(driver, form.save, { waitAfterVisible: 2000 })
+ await wait.andClick(driver, form.save, { waitAfterVisible: 2000 });
// Reload the Contacts section to validate Contacts
- await wait.andClick(driver, form.contacts.section, { waitAfterVisible: 2000 })
- expect(await wait.andGetEl(driver, form.contacts.uploadedContacts)).toBeDefined()
- expect(await wait.andGetEl(driver, form.contacts.uploadedContactsByQty(campaign.texters.contactLength))).toBeDefined()
- await wait.andClick(driver, form.texters.section, { waitAfterVisible: 2000 })
+ await wait.andClick(driver, form.contacts.section, {
+ waitAfterVisible: 2000
+ });
+ expect(
+ await wait.andGetEl(driver, form.contacts.uploadedContacts)
+ ).toBeDefined();
+ expect(
+ await wait.andGetEl(
+ driver,
+ form.contacts.uploadedContactsByQty(campaign.texters.contactLength)
+ )
+ ).toBeDefined();
+ await wait.andClick(driver, form.texters.section, {
+ waitAfterVisible: 2000
+ });
// This should switch to the Texters section
- expect(await wait.andGetEl(driver, form.texters.addAll)).toBeDefined()
- })
+ expect(await wait.andGetEl(driver, form.texters.addAll)).toBeDefined();
+ });
- it('completes the Texters section', async () => {
+ it("completes the Texters section", async () => {
if (campaign.existingTexter) {
// Add All
- await wait.andClick(driver, form.texters.addAll)
+ await wait.andClick(driver, form.texters.addAll);
// Assign (Split)
- await wait.andClick(driver, form.texters.autoSplit, { elementIsVisible: false })
+ await wait.andClick(driver, form.texters.autoSplit, {
+ elementIsVisible: false
+ });
// Validate Assignment
- const assignedToFirstTexter = await wait.andGetValue(driver, form.texters.texterAssignmentByIndex(0))
- expect(Number(assignedToFirstTexter)).toBeGreaterThan(0)
+ const assignedToFirstTexter = await wait.andGetValue(
+ driver,
+ form.texters.texterAssignmentByIndex(0)
+ );
+ expect(Number(assignedToFirstTexter)).toBeGreaterThan(0);
// Assign (All to Texter)
- await wait.andClick(driver, form.texters.autoSplit, { elementIsVisible: false })
- await wait.andType(driver, form.texters.texterAssignmentByText(campaign.admin.given_name), '0')
- await driver.sleep(1000)
- await wait.andType(driver, form.texters.texterAssignmentByText(campaign.texter.given_name), campaign.texters.contactLength)
+ await wait.andClick(driver, form.texters.autoSplit, {
+ elementIsVisible: false
+ });
+ await wait.andType(
+ driver,
+ form.texters.texterAssignmentByText(campaign.admin.given_name),
+ "0"
+ );
+ await driver.sleep(1000);
+ await wait.andType(
+ driver,
+ form.texters.texterAssignmentByText(campaign.texter.given_name),
+ campaign.texters.contactLength
+ );
// Validate Assignment
- expect(await wait.andGetValue(driver, form.texters.texterAssignmentByText(campaign.admin.given_name))).toBe('0')
+ expect(
+ await wait.andGetValue(
+ driver,
+ form.texters.texterAssignmentByText(campaign.admin.given_name)
+ )
+ ).toBe("0");
} else {
// Dynamically Assign
- await wait.andClick(driver, form.texters.useDynamicAssignment, { elementIsVisible: false, waitAfterVisible: 2000 })
+ await wait.andClick(driver, form.texters.useDynamicAssignment, {
+ elementIsVisible: false,
+ waitAfterVisible: 2000
+ });
// Store the invite (join) URL into a global for future use.
- global.e2e.joinUrl = await wait.andGetValue(driver, form.texters.joinUrl)
+ global.e2e.joinUrl = await wait.andGetValue(
+ driver,
+ form.texters.joinUrl
+ );
}
// Save
- await wait.andClick(driver, form.save)
+ await wait.andClick(driver, form.save);
// This should switch to the Interactions section
- expect(await wait.andGetEl(driver, form.interactions.editorLaunch)).toBeDefined()
- })
+ expect(
+ await wait.andGetEl(driver, form.interactions.editorLaunch)
+ ).toBeDefined();
+ });
- describe('completes the Interactions section', () => {
- it('adds an initial question', async () => {
+ describe("completes the Interactions section", () => {
+ it("adds an initial question", async () => {
// Script
- await wait.andClick(driver, form.interactions.editorLaunch)
- await wait.andType(driver, pom.scriptEditor.editor, campaign.interaction.script, { clear: false, click: false, waitAfterVisible: 2000 })
- await wait.andClick(driver, pom.scriptEditor.done, { goesStale: true })
+ await wait.andClick(driver, form.interactions.editorLaunch);
+ await wait.andType(
+ driver,
+ pom.scriptEditor.editor,
+ campaign.interaction.script,
+ { clear: false, click: false, waitAfterVisible: 2000 }
+ );
+ await wait.andClick(driver, pom.scriptEditor.done, { goesStale: true });
// Question
- await wait.andType(driver, form.interactions.questionText, campaign.interaction.question, { waitAfterVisible: 2000 })
+ await wait.andType(
+ driver,
+ form.interactions.questionText,
+ campaign.interaction.question,
+ { waitAfterVisible: 2000 }
+ );
// Save with No Answers Defined
- await wait.andClick(driver, form.interactions.submit)
- await wait.andClick(driver, form.interactions.section, { waitAfterVisible: 2000 })
- let allChildInteractions = await driver.findElements(form.interactions.childInteraction)
- expect(allChildInteractions.length).toBe(0)
+ await wait.andClick(driver, form.interactions.submit);
+ await wait.andClick(driver, form.interactions.section, {
+ waitAfterVisible: 2000
+ });
+ let allChildInteractions = await driver.findElements(
+ form.interactions.childInteraction
+ );
+ expect(allChildInteractions.length).toBe(0);
// Save with Empty Answer
- await wait.andClick(driver, form.interactions.addResponse)
- await wait.andClick(driver, form.interactions.submit)
- await wait.andClick(driver, form.interactions.section, { waitAfterVisible: 2000 })
- allChildInteractions = await driver.findElements(form.interactions.childInteraction)
- expect(allChildInteractions.length).toBe(1)
- })
+ await wait.andClick(driver, form.interactions.addResponse);
+ await wait.andClick(driver, form.interactions.submit);
+ await wait.andClick(driver, form.interactions.section, {
+ waitAfterVisible: 2000
+ });
+ allChildInteractions = await driver.findElements(
+ form.interactions.childInteraction
+ );
+ expect(allChildInteractions.length).toBe(1);
+ });
- describe('Add all Responses', () => {
+ describe("Add all Responses", () => {
_.each(campaign.interaction.answers, (answer, index) => {
it(`Adds Answer ${index}`, async () => {
- if (index > 0) await wait.andClick(driver, form.interactions.addResponse) // The first (0th) response reuses the empty Answer created above
+ if (index > 0)
+ await wait.andClick(driver, form.interactions.addResponse); // The first (0th) response reuses the empty Answer created above
// Answer
- await wait.andType(driver, form.interactions.answerOptionChildByIndex(index), answer.answerOption, { clear: false, waitAfterVisible: 2000 })
+ await wait.andType(
+ driver,
+ form.interactions.answerOptionChildByIndex(index),
+ answer.answerOption,
+ { clear: false, waitAfterVisible: 2000 }
+ );
// Answer Script
- await wait.andClick(driver, form.interactions.editorLaunchChildByIndex(index))
- await wait.andType(driver, pom.scriptEditor.editor, answer.script, { clear: false, click: false, waitAfterVisible: 2000 })
- await wait.andClick(driver, pom.scriptEditor.done, { goesStale: true })
+ await wait.andClick(
+ driver,
+ form.interactions.editorLaunchChildByIndex(index)
+ );
+ await wait.andType(driver, pom.scriptEditor.editor, answer.script, {
+ clear: false,
+ click: false,
+ waitAfterVisible: 2000
+ });
+ await wait.andClick(driver, pom.scriptEditor.done, {
+ goesStale: true
+ });
// Answer - Next Question
- await wait.andType(driver, form.interactions.questionTextChildByIndex(index), answer.questionText, { clear: false, waitAfterVisible: 2000 })
- })
- })
- it('validates that all responses were added', async () => {
- const allChildInteractions = await driver.findElements(form.interactions.childInteraction)
- expect(allChildInteractions.length).toBe(campaign.interaction.answers.length)
- })
- })
+ await wait.andType(
+ driver,
+ form.interactions.questionTextChildByIndex(index),
+ answer.questionText,
+ { clear: false, waitAfterVisible: 2000 }
+ );
+ });
+ });
+ it("validates that all responses were added", async () => {
+ const allChildInteractions = await driver.findElements(
+ form.interactions.childInteraction
+ );
+ expect(allChildInteractions.length).toBe(
+ campaign.interaction.answers.length
+ );
+ });
+ });
- it('saves for the last time', async () => {
+ it("saves for the last time", async () => {
// Save
- await wait.andClick(driver, form.interactions.submit)
+ await wait.andClick(driver, form.interactions.submit);
// This should switch to the Canned Responses section
- expect(await wait.andGetEl(driver, form.cannedResponse.addNew)).toBeDefined()
- })
- })
+ expect(
+ await wait.andGetEl(driver, form.cannedResponse.addNew)
+ ).toBeDefined();
+ });
+ });
- it('completes the Canned Responses section', async () => {
+ it("completes the Canned Responses section", async () => {
// Add New
- await wait.andClick(driver, form.cannedResponse.addNew)
+ await wait.andClick(driver, form.cannedResponse.addNew);
// Title
- await wait.andType(driver, form.cannedResponse.title, campaign.cannedResponses[0].title)
+ await wait.andType(
+ driver,
+ form.cannedResponse.title,
+ campaign.cannedResponses[0].title
+ );
// Script
- await wait.andClick(driver, form.cannedResponse.editorLaunch)
- await wait.andType(driver, pom.scriptEditor.editor, campaign.cannedResponses[0].script, { clear: false, click: false, waitAfterVisible: 2000 })
- await wait.andClick(driver, pom.scriptEditor.done, { goesStale: true })
+ await wait.andClick(driver, form.cannedResponse.editorLaunch);
+ await wait.andType(
+ driver,
+ pom.scriptEditor.editor,
+ campaign.cannedResponses[0].script,
+ { clear: false, click: false, waitAfterVisible: 2000 }
+ );
+ await wait.andClick(driver, pom.scriptEditor.done, { goesStale: true });
// Script - Relaunch and cancel (bug?)
- await wait.andClick(driver, form.cannedResponse.editorLaunch, { waitAfterVisible: 2000 })
- await wait.andClick(driver, pom.scriptEditor.cancel, { waitAfterVisible: 2000, goesStale: true })
+ await wait.andClick(driver, form.cannedResponse.editorLaunch, {
+ waitAfterVisible: 2000
+ });
+ await wait.andClick(driver, pom.scriptEditor.cancel, {
+ waitAfterVisible: 2000,
+ goesStale: true
+ });
// Submit Response
- await wait.andClick(driver, form.cannedResponse.submit, { waitAfterVisible: 2000, goesStale: true })
+ await wait.andClick(driver, form.cannedResponse.submit, {
+ waitAfterVisible: 2000,
+ goesStale: true
+ });
// Save
- await wait.andClick(driver, form.save, { waitAfterVisible: 2000, goesStale: true })
+ await wait.andClick(driver, form.save, {
+ waitAfterVisible: 2000,
+ goesStale: true
+ });
// Should be able to start campaign
- expect(await wait.andIsEnabled(driver, pom.campaigns.start)).toBeTruthy()
- })
+ expect(await wait.andIsEnabled(driver, pom.campaigns.start)).toBeTruthy();
+ });
- it('clicks Start Campaign', async () => {
+ it("clicks Start Campaign", async () => {
// Store the new campaign URL into a global for future use.
- global.e2e.newCampaignUrl = await driver.getCurrentUrl()
- await wait.andClick(driver, pom.campaigns.start, { waitAfterVisible: 2000, goesStale: true })
+ global.e2e.newCampaignUrl = await driver.getCurrentUrl();
+ await wait.andClick(driver, pom.campaigns.start, {
+ waitAfterVisible: 2000,
+ goesStale: true
+ });
// Validate Started
- expect(await wait.andGetEl(driver, pom.campaigns.isStarted)).toBeTruthy()
- })
+ expect(await wait.andGetEl(driver, pom.campaigns.isStarted)).toBeTruthy();
+ });
},
copyCampaign(driver, campaign) {
- it('opens the Campaigns tab', async () => {
- await driver.get(urlBuilder.admin.root())
- await wait.andClick(driver, pom.navigation.sections.campaigns)
- })
+ it("opens the Campaigns tab", async () => {
+ await driver.get(urlBuilder.admin.root());
+ await wait.andClick(driver, pom.navigation.sections.campaigns);
+ });
- it('clicks on an existing campaign', async () => {
- await wait.andClick(driver, pom.campaigns.campaignRowByText(campaign.basics.title), { goesStale: true })
- })
+ it("clicks on an existing campaign", async () => {
+ await wait.andClick(
+ driver,
+ pom.campaigns.campaignRowByText(campaign.basics.title),
+ { goesStale: true }
+ );
+ });
- it('clicks Copy in Stats', async () => {
- await wait.andClick(driver, pom.campaigns.stats.copy, { waitAfterVisible: 2000 })
- })
+ it("clicks Copy in Stats", async () => {
+ await wait.andClick(driver, pom.campaigns.stats.copy, {
+ waitAfterVisible: 2000
+ });
+ });
- it('verifies copy in Campaigns list', async () => {
- await wait.andClick(driver, pom.navigation.sections.campaigns)
- expect(await wait.andGetEl(driver, pom.campaigns.campaignRowByText('COPY'))).toBeDefined()
+ it("verifies copy in Campaigns list", async () => {
+ await wait.andClick(driver, pom.navigation.sections.campaigns);
+ expect(
+ await wait.andGetEl(driver, pom.campaigns.campaignRowByText("COPY"))
+ ).toBeDefined();
// expect(await wait.andGetEl(driver, pom.campaigns.warningIcon)).toBeDefined()
- await wait.andClick(driver, pom.campaigns.campaignRowByText('COPY'))
- })
+ await wait.andClick(driver, pom.campaigns.campaignRowByText("COPY"));
+ });
- describe('verifies Campaign sections', () => {
- it('verifies Basics section', async () => {
- await wait.andClick(driver, form.basics.section)
- expect(await wait.andGetValue(driver, form.basics.title)).toBe(campaign.basics.title_copied)
- expect(await wait.andGetValue(driver, form.basics.description)).toBe(campaign.basics.description)
- expect(await wait.andGetValue(driver, form.basics.dueBy)).toBe('')
- })
- it('verifies Contacts section', async () => {
- await wait.andClick(driver, form.contacts.section)
- const uploadedContacts = await driver.findElements(form.contacts.uploadedContacts)
- expect(uploadedContacts.length > 0).toBeFalsy()
- })
- it('verifies Texters section', async () => {
- await wait.andClick(driver, form.texters.section)
- const assignedContacts = await driver.findElements(form.texters.texterAssignmentByText(campaign.texter.given_name))
- expect(assignedContacts.length > 0).toBeFalsy()
- })
- it('verifies Interactions section', async () => {
- await wait.andClick(driver, form.interactions.section)
- expect(await wait.andGetValue(driver, form.interactions.editorLaunch)).toBe(campaign.interaction.script)
- expect(await wait.andGetValue(driver, form.interactions.questionText)).toBe(campaign.interaction.question)
+ describe("verifies Campaign sections", () => {
+ it("verifies Basics section", async () => {
+ await wait.andClick(driver, form.basics.section);
+ expect(await wait.andGetValue(driver, form.basics.title)).toBe(
+ campaign.basics.title_copied
+ );
+ expect(await wait.andGetValue(driver, form.basics.description)).toBe(
+ campaign.basics.description
+ );
+ expect(await wait.andGetValue(driver, form.basics.dueBy)).toBe("");
+ });
+ it("verifies Contacts section", async () => {
+ await wait.andClick(driver, form.contacts.section);
+ const uploadedContacts = await driver.findElements(
+ form.contacts.uploadedContacts
+ );
+ expect(uploadedContacts.length > 0).toBeFalsy();
+ });
+ it("verifies Texters section", async () => {
+ await wait.andClick(driver, form.texters.section);
+ const assignedContacts = await driver.findElements(
+ form.texters.texterAssignmentByText(campaign.texter.given_name)
+ );
+ expect(assignedContacts.length > 0).toBeFalsy();
+ });
+ it("verifies Interactions section", async () => {
+ await wait.andClick(driver, form.interactions.section);
+ expect(
+ await wait.andGetValue(driver, form.interactions.editorLaunch)
+ ).toBe(campaign.interaction.script);
+ expect(
+ await wait.andGetValue(driver, form.interactions.questionText)
+ ).toBe(campaign.interaction.question);
// Verify Answers
- const allChildInteractions = await driver.findElements(form.interactions.childInteraction)
- expect(allChildInteractions.length).toBe(campaign.interaction.answers.length)
- })
- it('verifies Canned Responses section', async () => {
- await wait.andClick(driver, form.cannedResponse.section)
- expect(await wait.andGetEl(driver, form.cannedResponse.createdResponseByText(campaign.cannedResponses[0].title))).toBeDefined()
- expect(await wait.andGetEl(driver, form.cannedResponse.createdResponseByText(campaign.cannedResponses[0].script))).toBeDefined()
- })
- })
+ const allChildInteractions = await driver.findElements(
+ form.interactions.childInteraction
+ );
+ expect(allChildInteractions.length).toBe(
+ campaign.interaction.answers.length
+ );
+ });
+ it("verifies Canned Responses section", async () => {
+ await wait.andClick(driver, form.cannedResponse.section);
+ expect(
+ await wait.andGetEl(
+ driver,
+ form.cannedResponse.createdResponseByText(
+ campaign.cannedResponses[0].title
+ )
+ )
+ ).toBeDefined();
+ expect(
+ await wait.andGetEl(
+ driver,
+ form.cannedResponse.createdResponseByText(
+ campaign.cannedResponses[0].script
+ )
+ )
+ ).toBeDefined();
+ });
+ });
},
editCampaign(driver, campaign) {
- it('opens the Campaigns tab', async () => {
- await driver.get(urlBuilder.admin.root())
- await wait.andClick(driver, pom.navigation.sections.campaigns)
- })
+ it("opens the Campaigns tab", async () => {
+ await driver.get(urlBuilder.admin.root());
+ await wait.andClick(driver, pom.navigation.sections.campaigns);
+ });
- it('clicks on an existing campaign', async () => {
- await wait.andClick(driver, pom.campaigns.campaignRowByText(campaign.basics.title), { goesStale: true })
- })
+ it("clicks on an existing campaign", async () => {
+ await wait.andClick(
+ driver,
+ pom.campaigns.campaignRowByText(campaign.basics.title),
+ { goesStale: true }
+ );
+ });
- it('clicks edit in Stats', async () => {
- await wait.andClick(driver, pom.campaigns.stats.edit, { waitAfterVisible: 2000, goesStale: true })
- })
+ it("clicks edit in Stats", async () => {
+ await wait.andClick(driver, pom.campaigns.stats.edit, {
+ waitAfterVisible: 2000,
+ goesStale: true
+ });
+ });
- it('changes the title in the Basics section', async () => {
+ it("changes the title in the Basics section", async () => {
// Expand Basics section
- await wait.andClick(driver, form.basics.section)
+ await wait.andClick(driver, form.basics.section);
// Change Title
- await wait.andType(driver, form.basics.title, campaign.basics.title_changed, { clear: false })
+ await wait.andType(
+ driver,
+ form.basics.title,
+ campaign.basics.title_changed,
+ { clear: false }
+ );
// Save
- await wait.andClick(driver, form.save)
- })
+ await wait.andClick(driver, form.save);
+ });
- it('reopens the Basics section to verify title', async () => {
+ it("reopens the Basics section to verify title", async () => {
// Expand Basics section
- await wait.andClick(driver, form.basics.section, { waitAfterVisible: 2000 })
+ await wait.andClick(driver, form.basics.section, {
+ waitAfterVisible: 2000
+ });
// Verify Title
- expect(await wait.andGetValue(driver, form.basics.title)).toBe(campaign.basics.title_changed)
- })
+ expect(await wait.andGetValue(driver, form.basics.title)).toBe(
+ campaign.basics.title_changed
+ );
+ });
},
sendReplies(driver, campaign) {
- it('sends Replies', async () => {
- const sendRepliesUrl = global.e2e.newCampaignUrl.substring(0, global.e2e.newCampaignUrl.indexOf('edit?new=true')) + 'send-replies'
- await driver.get(sendRepliesUrl)
- })
- describe('simulates the assigned contacts sending replies', () => {
+ it("sends Replies", async () => {
+ const sendRepliesUrl =
+ global.e2e.newCampaignUrl.substring(
+ 0,
+ global.e2e.newCampaignUrl.indexOf("edit?new=true")
+ ) + "send-replies";
+ await driver.get(sendRepliesUrl);
+ });
+ describe("simulates the assigned contacts sending replies", () => {
_.times(campaign.texters.contactLength, n => {
it(`sends reply ${n}`, async () => {
- await wait.andType(driver, pom.campaigns.replyByIndex(n), campaign.standardReply)
- await wait.andClick(driver, pom.campaigns.sendByIndex(n))
- })
- })
- })
+ await wait.andType(
+ driver,
+ pom.campaigns.replyByIndex(n),
+ campaign.standardReply
+ );
+ await wait.andClick(driver, pom.campaigns.sendByIndex(n));
+ });
+ });
+ });
}
-}
+};
diff --git a/__test__/e2e/page-functions/index.js b/__test__/e2e/page-functions/index.js
index 907884d59..37511f521 100644
--- a/__test__/e2e/page-functions/index.js
+++ b/__test__/e2e/page-functions/index.js
@@ -1,5 +1,5 @@
-export * from './campaigns'
-export * from './login'
-export * from './main'
-export * from './people'
-export * from './texter'
+export * from "./campaigns";
+export * from "./login";
+export * from "./main";
+export * from "./people";
+export * from "./texter";
diff --git a/__test__/e2e/page-functions/login.js b/__test__/e2e/page-functions/login.js
index 4875b759c..4880cfa7f 100644
--- a/__test__/e2e/page-functions/login.js
+++ b/__test__/e2e/page-functions/login.js
@@ -1,90 +1,95 @@
-import { until } from 'selenium-webdriver'
-import config from '../util/config'
-import { wait, urlBuilder } from '../util/helpers'
-import pom from '../page-objects/index'
+import { until } from "selenium-webdriver";
+import config from "../util/config";
+import { wait, urlBuilder } from "../util/helpers";
+import pom from "../page-objects/index";
// For legibility
-const auth0 = pom.login.auth0
+const auth0 = pom.login.auth0;
export const login = {
landing(driver) {
- it('gets the landing page', async () => {
- await driver.get(config.baseUrl)
- })
+ it("gets the landing page", async () => {
+ await driver.get(config.baseUrl);
+ });
- it('clicks the login link', async () => {
+ it("clicks the login link", async () => {
// Click on the login button
- wait.andClick(driver, pom.login.loginGetStarted, { msWait: 50000, waitAfterVisible: 2000 })
+ wait.andClick(driver, pom.login.loginGetStarted, {
+ msWait: 50000,
+ waitAfterVisible: 2000
+ });
// Wait until the Auth0 login page loads
- await driver.wait(until.urlContains(urlBuilder.login))
- })
+ await driver.wait(until.urlContains(urlBuilder.login));
+ });
},
signUpTab(driver, user) {
- let skip = false // Assume that these tests will proceed
- it('opens the Sign Up tab', async () => {
- skip = !!global.e2e[user.name].loginSucceeded // Skip tests if the login succeeded
+ let skip = false; // Assume that these tests will proceed
+ it("opens the Sign Up tab", async () => {
+ skip = !!global.e2e[user.name].loginSucceeded; // Skip tests if the login succeeded
if (!skip) {
- wait.andClick(driver, auth0.tabs.signIn, { msWait: 20000 })
+ wait.andClick(driver, auth0.tabs.signIn, { msWait: 20000 });
}
- })
+ });
- it('fills in the new user details', async () => {
+ it("fills in the new user details", async () => {
if (!skip) {
- await driver.sleep(3000) // Allow time for the client to populate the email
- await wait.andType(driver, auth0.form.email, user.email)
- await wait.andType(driver, auth0.form.password, user.password)
- await wait.andType(driver, auth0.form.given_name, user.given_name)
- await wait.andType(driver, auth0.form.family_name, user.family_name)
- await wait.andType(driver, auth0.form.cell, user.cell)
+ await driver.sleep(3000); // Allow time for the client to populate the email
+ await wait.andType(driver, auth0.form.email, user.email);
+ await wait.andType(driver, auth0.form.password, user.password);
+ await wait.andType(driver, auth0.form.given_name, user.given_name);
+ await wait.andType(driver, auth0.form.family_name, user.family_name);
+ await wait.andType(driver, auth0.form.cell, user.cell);
}
- })
+ });
- it('accepts the user agreement', async () => {
- if (!skip) await wait.andClick(driver, auth0.form.agreement)
- })
+ it("accepts the user agreement", async () => {
+ if (!skip) await wait.andClick(driver, auth0.form.agreement);
+ });
- it('clicks the submit button', async () => {
- if (!skip) await wait.andClick(driver, auth0.form.submit)
- })
+ it("clicks the submit button", async () => {
+ if (!skip) await wait.andClick(driver, auth0.form.submit);
+ });
- it('authorizes Auth0 to access tenant', async () => {
- if (!skip) await wait.andClick(driver, auth0.authorize.allow)
- })
+ it("authorizes Auth0 to access tenant", async () => {
+ if (!skip) await wait.andClick(driver, auth0.authorize.allow);
+ });
},
signUp(driver, user) {
- this.landing(driver, user)
- this.signUpTab(driver, user)
+ this.landing(driver, user);
+ this.signUpTab(driver, user);
},
logIn(driver, user) {
- it('opens the Log In tab', async () => {
- await wait.andClick(driver, auth0.tabs.logIn, { msWait: 50000 })
- })
+ it("opens the Log In tab", async () => {
+ await wait.andClick(driver, auth0.tabs.logIn, { msWait: 50000 });
+ });
- it('fills in the existing user details', async () => {
- await wait.andType(driver, auth0.form.email, user.email)
- await wait.andType(driver, auth0.form.password, user.password)
- })
+ it("fills in the existing user details", async () => {
+ await wait.andType(driver, auth0.form.email, user.email);
+ await wait.andType(driver, auth0.form.password, user.password);
+ });
- it('clicks the submit button', async () => {
- await wait.andClick(driver, auth0.form.submit, { waitAfterVisible: 1000 })
- })
+ it("clicks the submit button", async () => {
+ await wait.andClick(driver, auth0.form.submit, {
+ waitAfterVisible: 1000
+ });
+ });
},
tryLoginThenSignUp(driver, user) {
- this.logIn(driver, user)
- it('looks for an error', async () => {
- global.e2e[user.name] = {} // Set a global object for the user
- await driver.sleep(5000) // Wait for login attempt to return. Takes about 1 sec
- const errors = await driver.findElements(auth0.form.error)
- global.e2e[user.name].loginSucceeded = errors.length === 0
- })
- describe('Sign Up if Login Fails', () => {
+ this.logIn(driver, user);
+ it("looks for an error", async () => {
+ global.e2e[user.name] = {}; // Set a global object for the user
+ await driver.sleep(5000); // Wait for login attempt to return. Takes about 1 sec
+ const errors = await driver.findElements(auth0.form.error);
+ global.e2e[user.name].loginSucceeded = errors.length === 0;
+ });
+ describe("Sign Up if Login Fails", () => {
/**
* Note
* This always runs, as the test suite is defined before any tests run
* However, all tests will skip if global.e2e[user.name].loginSucceeded
*/
- this.signUpTab(driver, user)
- })
+ this.signUpTab(driver, user);
+ });
}
-}
+};
diff --git a/__test__/e2e/page-functions/main.js b/__test__/e2e/page-functions/main.js
index e0fdae627..1409e07bf 100644
--- a/__test__/e2e/page-functions/main.js
+++ b/__test__/e2e/page-functions/main.js
@@ -1,70 +1,104 @@
-import { until } from 'selenium-webdriver'
-import { wait } from '../util/helpers'
-import config from '../util/config'
-import pom from '../page-objects/index'
+import { until } from "selenium-webdriver";
+import { wait } from "../util/helpers";
+import config from "../util/config";
+import pom from "../page-objects/index";
export const main = {
createOrg(driver, name) {
- it('fills in the organization name', async () => {
- await wait.andType(driver, pom.main.organization.name, name)
- })
+ it("fills in the organization name", async () => {
+ await wait.andType(driver, pom.main.organization.name, name);
+ });
- it('clicks the submit button', async () => {
- await wait.andClick(driver, pom.main.organization.submit)
- await driver.wait(until.urlContains('admin'))
- const url = await driver.getCurrentUrl()
- const re = /\/admin\/(\d+)\//g
- global.e2e.organization = await re.exec(url)[1]
- })
+ it("clicks the submit button", async () => {
+ await wait.andClick(driver, pom.main.organization.submit);
+ await driver.wait(until.urlContains("admin"));
+ const url = await driver.getCurrentUrl();
+ const re = /\/admin\/(\d+)\//g;
+ global.e2e.organization = await re.exec(url)[1];
+ });
},
editUser(driver, user) {
- it('opens the User menu', async () => {
- await wait.andClick(driver, pom.main.userMenuButton)
- })
+ it("opens the User menu", async () => {
+ await wait.andClick(driver, pom.main.userMenuButton);
+ });
- it('click on the user name', async () => {
- await wait.andClick(driver, pom.main.userMenuDisplayName)
- })
+ it("click on the user name", async () => {
+ await wait.andClick(driver, pom.main.userMenuDisplayName);
+ });
- it('changes user details', async () => {
- await wait.andType(driver, pom.people.edit.firstName, user.given_name_changed, { clear: false })
- await wait.andType(driver, pom.people.edit.lastName, user.family_name_changed, { clear: false })
- await wait.andType(driver, pom.people.edit.email, user.email_changed, { clear: false })
- await wait.andType(driver, pom.people.edit.cell, user.cell_changed, { clear: false })
+ it("changes user details", async () => {
+ await wait.andType(
+ driver,
+ pom.people.edit.firstName,
+ user.given_name_changed,
+ { clear: false }
+ );
+ await wait.andType(
+ driver,
+ pom.people.edit.lastName,
+ user.family_name_changed,
+ { clear: false }
+ );
+ await wait.andType(driver, pom.people.edit.email, user.email_changed, {
+ clear: false
+ });
+ await wait.andType(driver, pom.people.edit.cell, user.cell_changed, {
+ clear: false
+ });
// Save
- await wait.andClick(driver, pom.people.edit.save)
+ await wait.andClick(driver, pom.people.edit.save);
// Verify edits
- expect(await wait.andGetValue(driver, pom.people.edit.firstName)).toBe(user.given_name_changed)
- expect(await wait.andGetValue(driver, pom.people.edit.lastName)).toBe(user.family_name_changed)
- expect(await wait.andGetValue(driver, pom.people.edit.email)).toBe(user.email_changed)
- })
+ expect(await wait.andGetValue(driver, pom.people.edit.firstName)).toBe(
+ user.given_name_changed
+ );
+ expect(await wait.andGetValue(driver, pom.people.edit.lastName)).toBe(
+ user.family_name_changed
+ );
+ expect(await wait.andGetValue(driver, pom.people.edit.email)).toBe(
+ user.email_changed
+ );
+ });
- it('reverts user details back to original settings', async () => {
- await wait.andType(driver, pom.people.edit.firstName, user.given_name, { clear: false })
- await wait.andType(driver, pom.people.edit.lastName, user.family_name, { clear: false })
- await wait.andType(driver, pom.people.edit.email, user.email, { clear: false })
- await wait.andType(driver, pom.people.edit.cell, user.cell, { clear: false })
+ it("reverts user details back to original settings", async () => {
+ await wait.andType(driver, pom.people.edit.firstName, user.given_name, {
+ clear: false
+ });
+ await wait.andType(driver, pom.people.edit.lastName, user.family_name, {
+ clear: false
+ });
+ await wait.andType(driver, pom.people.edit.email, user.email, {
+ clear: false
+ });
+ await wait.andType(driver, pom.people.edit.cell, user.cell, {
+ clear: false
+ });
// Save
- await wait.andClick(driver, pom.people.edit.save)
+ await wait.andClick(driver, pom.people.edit.save);
// Verify edits
- expect(await wait.andGetValue(driver, pom.people.edit.firstName)).toBe(user.given_name)
- expect(await wait.andGetValue(driver, pom.people.edit.lastName)).toBe(user.family_name)
- expect(await wait.andGetValue(driver, pom.people.edit.email)).toBe(user.email)
- })
+ expect(await wait.andGetValue(driver, pom.people.edit.firstName)).toBe(
+ user.given_name
+ );
+ expect(await wait.andGetValue(driver, pom.people.edit.lastName)).toBe(
+ user.family_name
+ );
+ expect(await wait.andGetValue(driver, pom.people.edit.email)).toBe(
+ user.email
+ );
+ });
},
logOutUser(driver) {
- it('gets the landing page', async () => {
- await driver.get(config.baseUrl)
- })
+ it("gets the landing page", async () => {
+ await driver.get(config.baseUrl);
+ });
- it('opens the User menu', async () => {
- await wait.andClick(driver, pom.main.userMenuButton)
- })
+ it("opens the User menu", async () => {
+ await wait.andClick(driver, pom.main.userMenuButton);
+ });
- it('clicks on log out', async () => {
- await wait.andClick(driver, pom.main.logOut, { waitAfterVisible: 3000 })
- const re = /http[s]*:\/\/[^\/]+[\/]*$/g
- await driver.wait(until.urlMatches(re))
- })
+ it("clicks on log out", async () => {
+ await wait.andClick(driver, pom.main.logOut, { waitAfterVisible: 3000 });
+ const re = /http[s]*:\/\/[^\/]+[\/]*$/g;
+ await driver.wait(until.urlMatches(re));
+ });
}
-}
+};
diff --git a/__test__/e2e/page-functions/people.js b/__test__/e2e/page-functions/people.js
index f58b93c1e..2deda6d59 100644
--- a/__test__/e2e/page-functions/people.js
+++ b/__test__/e2e/page-functions/people.js
@@ -1,58 +1,99 @@
-import { wait, urlBuilder } from '../util/helpers'
-import pom from '../page-objects/index'
+import { wait, urlBuilder } from "../util/helpers";
+import pom from "../page-objects/index";
export const people = {
invite(driver) {
- it('opens the People tab', async () => {
- await driver.get(urlBuilder.admin.root())
- await wait.andClick(driver, pom.navigation.sections.people)
- })
+ it("opens the People tab", async () => {
+ await driver.get(urlBuilder.admin.root());
+ await wait.andClick(driver, pom.navigation.sections.people);
+ });
- it('clicks on the + button to Invite a User', async () => {
- await wait.andClick(driver, pom.people.add)
- })
+ it("clicks on the + button to Invite a User", async () => {
+ await wait.andClick(driver, pom.people.add);
+ });
- it('views the invitation link', async () => {
+ it("views the invitation link", async () => {
// Store Invite
- global.e2e.joinUrl = await wait.andGetValue(driver, pom.people.invite.joinUrl)
+ global.e2e.joinUrl = await wait.andGetValue(
+ driver,
+ pom.people.invite.joinUrl
+ );
// OK
- await wait.andClick(driver, pom.people.invite.ok)
- })
+ await wait.andClick(driver, pom.people.invite.ok);
+ });
},
editUser(driver, user) {
- it('opens the People tab', async () => {
- await driver.get(urlBuilder.admin.root())
- await wait.andClick(driver, pom.navigation.sections.people)
- })
+ it("opens the People tab", async () => {
+ await driver.get(urlBuilder.admin.root());
+ await wait.andClick(driver, pom.navigation.sections.people);
+ });
- it('clicks on the Edit button next to name', async () => {
- await wait.andClick(driver, pom.people.editButtonByName(user.given_name), { waitAfterVisible: 2000 })
- })
+ it("clicks on the Edit button next to name", async () => {
+ await wait.andClick(
+ driver,
+ pom.people.editButtonByName(user.given_name),
+ { waitAfterVisible: 2000 }
+ );
+ });
- it('changes user details', async () => {
- await wait.andType(driver, pom.people.edit.firstName, user.given_name_changed, { clear: false, waitAfterVisible: 2000 })
- await wait.andType(driver, pom.people.edit.lastName, user.family_name_changed, { clear: false })
- await wait.andType(driver, pom.people.edit.email, user.email_changed, { clear: false })
- await wait.andType(driver, pom.people.edit.cell, user.cell_changed, { clear: false })
+ it("changes user details", async () => {
+ await wait.andType(
+ driver,
+ pom.people.edit.firstName,
+ user.given_name_changed,
+ { clear: false, waitAfterVisible: 2000 }
+ );
+ await wait.andType(
+ driver,
+ pom.people.edit.lastName,
+ user.family_name_changed,
+ { clear: false }
+ );
+ await wait.andType(driver, pom.people.edit.email, user.email_changed, {
+ clear: false
+ });
+ await wait.andType(driver, pom.people.edit.cell, user.cell_changed, {
+ clear: false
+ });
// Save
- await wait.andClick(driver, pom.people.edit.save)
+ await wait.andClick(driver, pom.people.edit.save);
// Verify edits
- expect(await wait.andGetEl(driver, pom.people.getRowByName(user.given_name_changed))).toBeDefined()
- })
+ expect(
+ await wait.andGetEl(
+ driver,
+ pom.people.getRowByName(user.given_name_changed)
+ )
+ ).toBeDefined();
+ });
- it('clicks on the Edit button next to name', async () => {
- await wait.andClick(driver, pom.people.editButtonByName(user.given_name), { waitAfterVisible: 2000 })
- })
+ it("clicks on the Edit button next to name", async () => {
+ await wait.andClick(
+ driver,
+ pom.people.editButtonByName(user.given_name),
+ { waitAfterVisible: 2000 }
+ );
+ });
- it('reverts user details back to original settings', async () => {
- await wait.andType(driver, pom.people.edit.firstName, user.given_name, { clear: false, waitAfterVisible: 2000 })
- await wait.andType(driver, pom.people.edit.lastName, user.family_name, { clear: false })
- await wait.andType(driver, pom.people.edit.email, user.email, { clear: false })
- await wait.andType(driver, pom.people.edit.cell, user.cell, { clear: false })
+ it("reverts user details back to original settings", async () => {
+ await wait.andType(driver, pom.people.edit.firstName, user.given_name, {
+ clear: false,
+ waitAfterVisible: 2000
+ });
+ await wait.andType(driver, pom.people.edit.lastName, user.family_name, {
+ clear: false
+ });
+ await wait.andType(driver, pom.people.edit.email, user.email, {
+ clear: false
+ });
+ await wait.andType(driver, pom.people.edit.cell, user.cell, {
+ clear: false
+ });
// Save
- await wait.andClick(driver, pom.people.edit.save)
+ await wait.andClick(driver, pom.people.edit.save);
// Verify edits
- expect(await wait.andGetEl(driver, pom.people.getRowByName(user.given_name))).toBeDefined()
- })
+ expect(
+ await wait.andGetEl(driver, pom.people.getRowByName(user.given_name))
+ ).toBeDefined();
+ });
}
-}
+};
diff --git a/__test__/e2e/page-functions/texter.js b/__test__/e2e/page-functions/texter.js
index dd60c9f9e..bee441b62 100644
--- a/__test__/e2e/page-functions/texter.js
+++ b/__test__/e2e/page-functions/texter.js
@@ -1,52 +1,59 @@
-import _ from 'lodash'
-import { wait, urlBuilder } from '../util/helpers'
-import pom from '../page-objects/index'
+import _ from "lodash";
+import { wait, urlBuilder } from "../util/helpers";
+import pom from "../page-objects/index";
export const texter = {
sendTexts(driver, campaign) {
- it('refreshes Dashboard', async () => {
- await driver.get(urlBuilder.app.todos())
- await wait.andClick(driver, pom.texter.sendFirstTexts)
- })
- describe('works though the list of assigned contacts', () => {
+ it("refreshes Dashboard", async () => {
+ await driver.get(urlBuilder.app.todos());
+ await wait.andClick(driver, pom.texter.sendFirstTexts);
+ });
+ describe("works though the list of assigned contacts", () => {
_.times(campaign.texters.contactLength, n => {
it(`sends text ${n}`, async () => {
- await wait.andClick(driver, pom.texter.send)
- })
- })
- it('should have an empty todo list', async () => {
- await driver.get(urlBuilder.app.todos())
- expect(await wait.andGetEl(driver, pom.texter.emptyTodo)).toBeDefined()
- })
- })
+ await wait.andClick(driver, pom.texter.send);
+ });
+ });
+ it("should have an empty todo list", async () => {
+ await driver.get(urlBuilder.app.todos());
+ expect(await wait.andGetEl(driver, pom.texter.emptyTodo)).toBeDefined();
+ });
+ });
},
optOutContact(driver) {
- it('clicks the Opt Out button', async () => {
- await wait.andClick(driver, pom.texter.optOut.button)
- })
- it('clicks Send', async () => {
- await wait.andClick(driver, pom.texter.optOut.send)
- await driver.sleep(3000)
- })
+ it("clicks the Opt Out button", async () => {
+ await wait.andClick(driver, pom.texter.optOut.button);
+ });
+ it("clicks Send", async () => {
+ await wait.andClick(driver, pom.texter.optOut.send);
+ await driver.sleep(3000);
+ });
},
viewInvite(driver) {
- it('follows the link to the invite', async () => {
- await driver.get(global.e2e.joinUrl)
- })
+ it("follows the link to the invite", async () => {
+ await driver.get(global.e2e.joinUrl);
+ });
},
viewReplies(driver, campaign) {
- it('refreshes Dashboard', async () => {
- await driver.get(urlBuilder.app.todos())
- await wait.andClick(driver, pom.texter.sendReplies)
- })
- it('verifies reply', async () => {
- expect(await wait.andGetEl(driver, pom.texter.replyByText(campaign.standardReply))).toBeDefined()
- })
+ it("refreshes Dashboard", async () => {
+ await driver.get(urlBuilder.app.todos());
+ await wait.andClick(driver, pom.texter.sendReplies);
+ });
+ it("verifies reply", async () => {
+ expect(
+ await wait.andGetEl(
+ driver,
+ pom.texter.replyByText(campaign.standardReply)
+ )
+ ).toBeDefined();
+ });
},
viewSendFirstTexts(driver) {
- it('verifies that Send First Texts button is present', async () => {
- await driver.get(urlBuilder.app.todos())
- expect(await wait.andGetEl(driver, pom.texter.sendFirstTexts)).toBeDefined()
- })
+ it("verifies that Send First Texts button is present", async () => {
+ await driver.get(urlBuilder.app.todos());
+ expect(
+ await wait.andGetEl(driver, pom.texter.sendFirstTexts)
+ ).toBeDefined();
+ });
}
-}
+};
diff --git a/__test__/e2e/page-objects/campaigns.js b/__test__/e2e/page-objects/campaigns.js
index d06264500..2292a8d6b 100644
--- a/__test__/e2e/page-objects/campaigns.js
+++ b/__test__/e2e/page-objects/campaigns.js
@@ -1,64 +1,108 @@
-import { By } from 'selenium-webdriver'
+import { By } from "selenium-webdriver";
export const campaigns = {
- add: By.css('[data-test=addCampaign]'),
- start: By.css('[data-test=startCampaign]:not([disabled])'),
- campaignRowByText(text) { return By.xpath(`//*[contains(text(),'${text}')]/ancestor::*[@data-test="campaignRow"]`) },
- warningIcon: By.css('[data-test=warningIcon]'),
- replyByIndex(index) { return By.xpath(`(//input[@data-test='reply'])[${index + 1}]`) },
- sendByIndex(index) { return By.xpath(`(//button[@data-test='send'])[${index + 1}]`) },
+ add: By.css("[data-test=addCampaign]"),
+ start: By.css("[data-test=startCampaign]:not([disabled])"),
+ campaignRowByText(text) {
+ return By.xpath(
+ `//*[contains(text(),'${text}')]/ancestor::*[@data-test="campaignRow"]`
+ );
+ },
+ warningIcon: By.css("[data-test=warningIcon]"),
+ replyByIndex(index) {
+ return By.xpath(`(//input[@data-test='reply'])[${index + 1}]`);
+ },
+ sendByIndex(index) {
+ return By.xpath(`(//button[@data-test='send'])[${index + 1}]`);
+ },
form: {
basics: {
- section: By.css('[data-test=basics]'),
- title: By.css('[data-test=title]'),
- description: By.css('[data-test=description]'),
- dueBy: By.css('[data-test=dueBy]')
+ section: By.css("[data-test=basics]"),
+ title: By.css("[data-test=title]"),
+ description: By.css("[data-test=description]"),
+ dueBy: By.css("[data-test=dueBy]")
},
datePickerDialog: {
// This selector is fragile and alternate means of finding an enabled date should be investigated.
- nextMonth: By.css('body > div:nth-child(5) > div > div:nth-child(1) > div > div > div > div > div:nth-child(2) > div:nth-child(1) > div:nth-child(1) > button:nth-child(3)'),
- enabledDate: By.css('body > div:nth-child(5) > div > div:nth-child(1) > div > div > div > div > div:nth-child(2) > div:nth-child(1) > div:nth-child(3) > div > div button[tabindex="0"]')
+ nextMonth: By.css(
+ "body > div:nth-child(5) > div > div:nth-child(1) > div > div > div > div > div:nth-child(2) > div:nth-child(1) > div:nth-child(1) > button:nth-child(3)"
+ ),
+ enabledDate: By.css(
+ 'body > div:nth-child(5) > div > div:nth-child(1) > div > div > div > div > div:nth-child(2) > div:nth-child(1) > div:nth-child(3) > div > div button[tabindex="0"]'
+ )
},
contacts: {
- section: By.css('[data-test=contacts]'),
- uploadButton: By.css('[data-test=uploadButton]'),
- input: By.css('#contact-upload'),
- uploadedContacts: By.css('[data-test=uploadedContacts]'),
- uploadedContactsByQty(n) { return By.xpath(`//*[@data-test='uploadedContacts']/descendant::*[contains(text(),'${n} contact')]`) }
+ section: By.css("[data-test=contacts]"),
+ uploadButton: By.css("[data-test=uploadButton]"),
+ input: By.css("#contact-upload"),
+ uploadedContacts: By.css("[data-test=uploadedContacts]"),
+ uploadedContactsByQty(n) {
+ return By.xpath(
+ `//*[@data-test='uploadedContacts']/descendant::*[contains(text(),'${n} contact')]`
+ );
+ }
},
texters: {
- section: By.css('[data-test=texters]'),
- useDynamicAssignment: By.css('[data-test=useDynamicAssignment]'),
- joinUrl: By.css('[data-test=joinUrl]'),
- addAll: By.css('[data-test=addAll]'),
- autoSplit: By.css('[data-test=autoSplit]'),
- texterAssignmentByText(text) { return By.xpath(`//*[@data-test='texterName' and contains(text(),'${text}')]/ancestor::*[@data-test='texterRow']/descendant::input[@data-test='texterAssignment']`) },
- texterAssignmentByIndex(index) { return By.xpath(`(//*[@data-test='texterRow'])[${index + 1}]/descendant::input[@data-test='texterAssignment']`) }
+ section: By.css("[data-test=texters]"),
+ useDynamicAssignment: By.css("[data-test=useDynamicAssignment]"),
+ joinUrl: By.css("[data-test=joinUrl]"),
+ addAll: By.css("[data-test=addAll]"),
+ autoSplit: By.css("[data-test=autoSplit]"),
+ texterAssignmentByText(text) {
+ return By.xpath(
+ `//*[@data-test='texterName' and contains(text(),'${text}')]/ancestor::*[@data-test='texterRow']/descendant::input[@data-test='texterAssignment']`
+ );
+ },
+ texterAssignmentByIndex(index) {
+ return By.xpath(
+ `(//*[@data-test='texterRow'])[${index +
+ 1}]/descendant::input[@data-test='texterAssignment']`
+ );
+ }
},
interactions: {
- section: By.css('[data-test=interactions]'),
- questionText: By.css('[data-test=questionText]'),
- addResponse: By.css('[data-test=addResponse]:nth-child(1)'),
- childInteraction: By.css('[data-test=childInteraction]'),
- questionTextChildByIndex(index) { return By.xpath(`(//*[@data-test='childInteraction']/descendant::*[@data-test='questionText'])[${index + 1}]`) },
- editorLaunch: By.css('[data-test=editorInteraction]'),
- editorLaunchChildByIndex(index) { return By.xpath(`(//*[@data-test='childInteraction']/descendant::*[@data-test='editorInteraction'])[${index + 1}]`) },
- answerOptionChildByIndex(index) { return By.xpath(`(//*[@data-test='childInteraction']/descendant::*[@data-test='answerOption'])[${index + 1}]`) },
- submit: By.css('[data-test=interactionSubmit]')
+ section: By.css("[data-test=interactions]"),
+ questionText: By.css("[data-test=questionText]"),
+ addResponse: By.css("[data-test=addResponse]:nth-child(1)"),
+ childInteraction: By.css("[data-test=childInteraction]"),
+ questionTextChildByIndex(index) {
+ return By.xpath(
+ `(//*[@data-test='childInteraction']/descendant::*[@data-test='questionText'])[${index +
+ 1}]`
+ );
+ },
+ editorLaunch: By.css("[data-test=editorInteraction]"),
+ editorLaunchChildByIndex(index) {
+ return By.xpath(
+ `(//*[@data-test='childInteraction']/descendant::*[@data-test='editorInteraction'])[${index +
+ 1}]`
+ );
+ },
+ answerOptionChildByIndex(index) {
+ return By.xpath(
+ `(//*[@data-test='childInteraction']/descendant::*[@data-test='answerOption'])[${index +
+ 1}]`
+ );
+ },
+ submit: By.css("[data-test=interactionSubmit]")
},
cannedResponse: {
- section: By.css('[data-test=cannedResponses]'),
- addNew: By.css('[data-test=newCannedResponse]'),
- title: By.css('[data-test=title]'),
- editorLaunch: By.css('[data-test=editorResponse]'),
- createdResponseByText(text) { return By.xpath(`//span[@data-test='cannedResponse']/descendant::*[contains(text(),'${text}')]`) },
- submit: By.css('[data-test=addResponse]')
+ section: By.css("[data-test=cannedResponses]"),
+ addNew: By.css("[data-test=newCannedResponse]"),
+ title: By.css("[data-test=title]"),
+ editorLaunch: By.css("[data-test=editorResponse]"),
+ createdResponseByText(text) {
+ return By.xpath(
+ `//span[@data-test='cannedResponse']/descendant::*[contains(text(),'${text}')]`
+ );
+ },
+ submit: By.css("[data-test=addResponse]")
},
- save: By.css('[type=submit]:not([disabled])')
+ save: By.css("[type=submit]:not([disabled])")
},
stats: {
- copy: By.css('[data-test=copyCampaign]'),
- edit: By.css('[data-test=editCampaign]')
+ copy: By.css("[data-test=copyCampaign]"),
+ edit: By.css("[data-test=editCampaign]")
},
- isStarted: By.css('[data-test=campaignIsStarted]')
-}
+ isStarted: By.css("[data-test=campaignIsStarted]")
+};
diff --git a/__test__/e2e/page-objects/index.js b/__test__/e2e/page-objects/index.js
index 5e1ea4b35..d920ce90f 100644
--- a/__test__/e2e/page-objects/index.js
+++ b/__test__/e2e/page-objects/index.js
@@ -1,10 +1,10 @@
-import { campaigns } from './campaigns'
-import { main } from './main'
-import { login } from './login'
-import { navigation } from './navigation'
-import { people } from './people'
-import { scriptEditor } from './scriptEditor'
-import { texter } from './texter'
+import { campaigns } from "./campaigns";
+import { main } from "./main";
+import { login } from "./login";
+import { navigation } from "./navigation";
+import { people } from "./people";
+import { scriptEditor } from "./scriptEditor";
+import { texter } from "./texter";
export default {
campaigns,
@@ -14,4 +14,4 @@ export default {
people,
scriptEditor,
texter
-}
+};
diff --git a/__test__/e2e/page-objects/login.js b/__test__/e2e/page-objects/login.js
index d9daa94fc..c46e92913 100644
--- a/__test__/e2e/page-objects/login.js
+++ b/__test__/e2e/page-objects/login.js
@@ -1,24 +1,26 @@
-import { By } from 'selenium-webdriver'
+import { By } from "selenium-webdriver";
export const login = {
auth0: {
tabs: {
- logIn: By.css('.auth0-lock-tabs>li:nth-child(1)'),
- signIn: By.css('.auth0-lock-tabs>li:nth-child(2)')
+ logIn: By.css(".auth0-lock-tabs>li:nth-child(1)"),
+ signIn: By.css(".auth0-lock-tabs>li:nth-child(2)")
},
form: {
- email: By.css('div.auth0-lock-input-email > div > input'),
- password: By.css('div.auth0-lock-input-password > div > input'),
- given_name: By.css('div.auth0-lock-input-given_name > div > input'),
- family_name: By.css('div.auth0-lock-input-family_name > div > input'),
- cell: By.css('div.auth0-lock-input-cell > div > input'),
- agreement: By.css('span.auth0-lock-sign-up-terms-agreement > label > input'), // Checkbox
- submit: By.css('button.auth0-lock-submit'),
- error: By.css('div.auth0-global-message-error')
+ email: By.css("div.auth0-lock-input-email > div > input"),
+ password: By.css("div.auth0-lock-input-password > div > input"),
+ given_name: By.css("div.auth0-lock-input-given_name > div > input"),
+ family_name: By.css("div.auth0-lock-input-family_name > div > input"),
+ cell: By.css("div.auth0-lock-input-cell > div > input"),
+ agreement: By.css(
+ "span.auth0-lock-sign-up-terms-agreement > label > input"
+ ), // Checkbox
+ submit: By.css("button.auth0-lock-submit"),
+ error: By.css("div.auth0-global-message-error")
},
authorize: {
- allow: By.css('#allow')
+ allow: By.css("#allow")
}
},
- loginGetStarted: By.css('#login')
-}
+ loginGetStarted: By.css("#login")
+};
diff --git a/__test__/e2e/page-objects/main.js b/__test__/e2e/page-objects/main.js
index a6072aaa8..3ce7e8763 100644
--- a/__test__/e2e/page-objects/main.js
+++ b/__test__/e2e/page-objects/main.js
@@ -1,20 +1,20 @@
-import { By } from 'selenium-webdriver'
+import { By } from "selenium-webdriver";
export const main = {
organization: {
- name: By.css('[data-test=organization]'),
+ name: By.css("[data-test=organization]"),
submit: By.css('button[name="submit"]')
},
- userMenuButton: By.css('[data-test=userMenuButton]'),
- userMenuDisplayName: By.css('[data-test=userMenuDisplayName]'),
+ userMenuButton: By.css("[data-test=userMenuButton]"),
+ userMenuDisplayName: By.css("[data-test=userMenuDisplayName]"),
edit: {
- editButton: By.css('[data-test=editPerson]'),
- firstName: By.css('[data-test=firstName]'),
- lastName: By.css('[data-test=lastName]'),
- email: By.css('[data-test=email]'),
- cell: By.css('[data-test=cell]'),
- save: By.css('[type=submit]')
+ editButton: By.css("[data-test=editPerson]"),
+ firstName: By.css("[data-test=firstName]"),
+ lastName: By.css("[data-test=lastName]"),
+ email: By.css("[data-test=email]"),
+ cell: By.css("[data-test=cell]"),
+ save: By.css("[type=submit]")
},
- home: By.css('[data-test=home]'),
- logOut: By.css('[data-test=userMenuLogOut]')
-}
+ home: By.css("[data-test=home]"),
+ logOut: By.css("[data-test=userMenuLogOut]")
+};
diff --git a/__test__/e2e/page-objects/navigation.js b/__test__/e2e/page-objects/navigation.js
index a543c9c95..20f109139 100644
--- a/__test__/e2e/page-objects/navigation.js
+++ b/__test__/e2e/page-objects/navigation.js
@@ -1,12 +1,12 @@
-import { By } from 'selenium-webdriver'
+import { By } from "selenium-webdriver";
export const navigation = {
sections: {
- campaigns: By.css('[data-test=navCampaigns]'),
- people: By.css('[data-test=navPeople]'),
- optouts: By.css('[data-test=navOptouts]'),
- messageReview: By.css('[data-test=navIncoming]'),
- settings: By.css('[data-test=navSettings]'),
- switchToTexter: By.css('[data-test=navSwitchToTexter]')
+ campaigns: By.css("[data-test=navCampaigns]"),
+ people: By.css("[data-test=navPeople]"),
+ optouts: By.css("[data-test=navOptouts]"),
+ messageReview: By.css("[data-test=navIncoming]"),
+ settings: By.css("[data-test=navSettings]"),
+ switchToTexter: By.css("[data-test=navSwitchToTexter]")
}
-}
+};
diff --git a/__test__/e2e/page-objects/people.js b/__test__/e2e/page-objects/people.js
index 818f3f74a..c3f7d4478 100644
--- a/__test__/e2e/page-objects/people.js
+++ b/__test__/e2e/page-objects/people.js
@@ -1,19 +1,25 @@
-import { By } from 'selenium-webdriver'
+import { By } from "selenium-webdriver";
export const people = {
- add: By.css('[data-test=addPerson]'),
+ add: By.css("[data-test=addPerson]"),
invite: {
- joinUrl: By.css('[data-test=joinUrl]'),
- ok: By.css('[data-test=inviteOk]')
+ joinUrl: By.css("[data-test=joinUrl]"),
+ ok: By.css("[data-test=inviteOk]")
+ },
+ getRowByName(name) {
+ return By.xpath(`//td[contains(text(),'${name}')]/ancestor::tr`);
+ },
+ editButtonByName(name) {
+ return By.xpath(
+ `//td[contains(text(),'${name}')]/ancestor::tr/descendant::button[@data-test='editPerson']`
+ );
},
- getRowByName(name) { return By.xpath(`//td[contains(text(),'${name}')]/ancestor::tr`) },
- editButtonByName(name) { return By.xpath(`//td[contains(text(),'${name}')]/ancestor::tr/descendant::button[@data-test='editPerson']`) },
edit: {
- editButton: By.css('[data-test=editPerson]'),
- firstName: By.css('[data-test=firstName]'),
- lastName: By.css('[data-test=lastName]'),
- email: By.css('[data-test=email]'),
- cell: By.css('[data-test=cell]'),
- save: By.css('[type=submit]')
+ editButton: By.css("[data-test=editPerson]"),
+ firstName: By.css("[data-test=firstName]"),
+ lastName: By.css("[data-test=lastName]"),
+ email: By.css("[data-test=email]"),
+ cell: By.css("[data-test=cell]"),
+ save: By.css("[type=submit]")
}
-}
+};
diff --git a/__test__/e2e/page-objects/scriptEditor.js b/__test__/e2e/page-objects/scriptEditor.js
index 46268c378..52cecde48 100644
--- a/__test__/e2e/page-objects/scriptEditor.js
+++ b/__test__/e2e/page-objects/scriptEditor.js
@@ -1,7 +1,7 @@
-import { By } from 'selenium-webdriver'
+import { By } from "selenium-webdriver";
export const scriptEditor = {
- editor: By.css('.public-DraftEditor-content'),
- done: By.css('[data-test=scriptDone]'),
- cancel: By.css('[data-test=scriptCancel]')
-}
+ editor: By.css(".public-DraftEditor-content"),
+ done: By.css("[data-test=scriptDone]"),
+ cancel: By.css("[data-test=scriptCancel]")
+};
diff --git a/__test__/e2e/page-objects/texter.js b/__test__/e2e/page-objects/texter.js
index af56059b6..9ebbcfc35 100644
--- a/__test__/e2e/page-objects/texter.js
+++ b/__test__/e2e/page-objects/texter.js
@@ -1,13 +1,17 @@
-import { By } from 'selenium-webdriver'
+import { By } from "selenium-webdriver";
export const texter = {
- sendFirstTexts: By.css('[data-test=sendFirstTexts]'),
- sendReplies: By.css('[data-test=sendReplies]'),
- send: By.css('[data-test=send]:not([disabled])'),
- replyByText(text) { return By.xpath(`//*[@data-test='messageList']/descendant::*[contains(text(),'${text}')]`) },
- emptyTodo: By.css('[data-test=empty]'),
+ sendFirstTexts: By.css("[data-test=sendFirstTexts]"),
+ sendReplies: By.css("[data-test=sendReplies]"),
+ send: By.css("[data-test=send]:not([disabled])"),
+ replyByText(text) {
+ return By.xpath(
+ `//*[@data-test='messageList']/descendant::*[contains(text(),'${text}')]`
+ );
+ },
+ emptyTodo: By.css("[data-test=empty]"),
optOut: {
- button: By.css('[data-test=optOut]'),
- send: By.css('[type=submit]')
+ button: By.css("[data-test=optOut]"),
+ send: By.css("[type=submit]")
}
-}
+};
diff --git a/__test__/e2e/util/config.js b/__test__/e2e/util/config.js
index 851978602..9b2fc124e 100644
--- a/__test__/e2e/util/config.js
+++ b/__test__/e2e/util/config.js
@@ -4,18 +4,18 @@ const config = {
username: process.env.SAUCE_USERNAME,
accessKey: process.env.SAUCE_ACCESS_KEY,
capabilities: {
- name: 'Spoke - Chrome E2E Tests',
- browserName: 'chrome',
+ name: "Spoke - Chrome E2E Tests",
+ browserName: "chrome",
idleTimeout: 240, // 4 minute idle
- 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
+ "tunnel-identifier": process.env.TRAVIS_JOB_NUMBER,
username: process.env.SAUCE_USERNAME,
accessKey: process.env.SAUCE_ACCESS_KEY,
build: process.env.TRAVIS_BUILD_NUMBER
},
server: `http://${process.env.SAUCE_USERNAME}:${process.env.SAUCE_ACCESS_KEY}@ondemand.saucelabs.com:80/wd/hub`,
- host: 'localhost',
+ host: "localhost",
port: 4445
}
-}
+};
-export default config
+export default config;
diff --git a/__test__/e2e/util/helpers.js b/__test__/e2e/util/helpers.js
index c84a3bf96..c915a419a 100644
--- a/__test__/e2e/util/helpers.js
+++ b/__test__/e2e/util/helpers.js
@@ -1,88 +1,109 @@
-import { Builder, until } from 'selenium-webdriver'
-import remote from 'selenium-webdriver/remote'
-import config from './config'
-import _ from 'lodash'
+import { Builder, until } from "selenium-webdriver";
+import remote from "selenium-webdriver/remote";
+import config from "./config";
+import _ from "lodash";
-import SauceLabs from 'saucelabs'
+import SauceLabs from "saucelabs";
const saucelabs = new SauceLabs({
username: process.env.SAUCE_USERNAME,
password: process.env.SAUCE_ACCESS_KEY
-})
+});
-const defaultWait = 10000
+const defaultWait = 10000;
export const selenium = {
buildDriver(options) {
- const capabilities = _.assign({}, config.sauceLabs.capabilities, options)
- const driver = process.env.npm_config_saucelabs ?
- new Builder()
- .withCapabilities(capabilities)
- .usingServer(config.sauceLabs.server)
- .build() :
- new Builder().forBrowser('chrome').build()
- driver.setFileDetector(new remote.FileDetector())
- return driver
+ const capabilities = _.assign({}, config.sauceLabs.capabilities, options);
+ const driver = process.env.npm_config_saucelabs
+ ? new Builder()
+ .withCapabilities(capabilities)
+ .usingServer(config.sauceLabs.server)
+ .build()
+ : new Builder().forBrowser("chrome").build();
+ driver.setFileDetector(new remote.FileDetector());
+ return driver;
},
async quitDriver(driver) {
- await driver.getSession()
- .then(async session => {
- if (process.env.npm_config_saucelabs) {
- const sessionId = session.getId()
- process.env.SELENIUM_ID = sessionId
- await saucelabs.updateJob(sessionId, { passed: global.e2e.failureCount === 0 })
- console.log(`SauceOnDemandSessionID=${sessionId} job-name=${process.env.TRAVIS_JOB_NUMBER || ''}`)
- }
- })
- await driver.quit()
+ await driver.getSession().then(async session => {
+ if (process.env.npm_config_saucelabs) {
+ const sessionId = session.getId();
+ process.env.SELENIUM_ID = sessionId;
+ await saucelabs.updateJob(sessionId, {
+ passed: global.e2e.failureCount === 0
+ });
+ console.log(
+ `SauceOnDemandSessionID=${sessionId} job-name=${process.env
+ .TRAVIS_JOB_NUMBER || ""}`
+ );
+ }
+ });
+ await driver.quit();
},
reporter: {
- specDone: async (result) => { global.e2e.failureCount = global.e2e.failureCount + result.failedExpectations.length || 0 },
- suiteDone: async (result) => { global.e2e.failureCount = global.e2e.failureCount + result.failedExpectations.length || 0 }
+ specDone: async result => {
+ global.e2e.failureCount =
+ global.e2e.failureCount + result.failedExpectations.length || 0;
+ },
+ suiteDone: async result => {
+ global.e2e.failureCount =
+ global.e2e.failureCount + result.failedExpectations.length || 0;
+ }
}
-}
+};
export const urlBuilder = {
login: `${config.baseUrl}/login`,
admin: {
- root() { return `${config.baseUrl}/admin/${global.e2e.organization}` }
+ root() {
+ return `${config.baseUrl}/admin/${global.e2e.organization}`;
+ }
},
app: {
- todos() { return `${config.baseUrl}/app/${global.e2e.organization}/todos` }
+ todos() {
+ return `${config.baseUrl}/app/${global.e2e.organization}/todos`;
+ }
}
-}
+};
const waitAnd = async (driver, locator, options) => {
- const el = await driver.wait(until.elementLocated(locator, options.msWait || defaultWait))
- if (options.elementIsVisible !== false) await driver.wait(until.elementIsVisible(el))
- if (options.waitAfterVisible) await driver.sleep(options.waitAfterVisible)
- if (options.click) await el.click()
- if (options.keys) await driver.sleep(500)
- if (options.clear) await el.clear()
- if (options.keys) await el.sendKeys(options.keys)
- if (options.goesStale) await driver.wait(until.stalenessOf(el))
- return el
-}
+ const el = await driver.wait(
+ until.elementLocated(locator, options.msWait || defaultWait)
+ );
+ if (options.elementIsVisible !== false)
+ await driver.wait(until.elementIsVisible(el));
+ if (options.waitAfterVisible) await driver.sleep(options.waitAfterVisible);
+ if (options.click) await el.click();
+ if (options.keys) await driver.sleep(500);
+ if (options.clear) await el.clear();
+ if (options.keys) await el.sendKeys(options.keys);
+ if (options.goesStale) await driver.wait(until.stalenessOf(el));
+ return el;
+};
export const wait = {
async untilLocated(driver, locator, options) {
- return await waitAnd(driver, locator, _.assign({}, options))
+ return await waitAnd(driver, locator, _.assign({}, options));
},
async andGetEl(driver, locator, options) {
- return await waitAnd(driver, locator, _.assign({}, options))
+ return await waitAnd(driver, locator, _.assign({}, options));
},
async andClick(driver, locator, options) {
- return await waitAnd(driver, locator, _.assign({ click: true }, options))
+ return await waitAnd(driver, locator, _.assign({ click: true }, options));
},
async andType(driver, locator, keys, options) {
- return await waitAnd(driver, locator, _.assign({ keys, clear: true, click: true }, options))
+ return await waitAnd(
+ driver,
+ locator,
+ _.assign({ keys, clear: true, click: true }, options)
+ );
},
async andGetValue(driver, locator, options) {
- const el = await waitAnd(driver, locator, _.assign({}, options))
- return await el.getAttribute('value')
+ const el = await waitAnd(driver, locator, _.assign({}, options));
+ return await el.getAttribute("value");
},
async andIsEnabled(driver, locator, options) {
- const el = await waitAnd(driver, locator, _.assign({}, options))
- return await el.isEnabled()
+ const el = await waitAnd(driver, locator, _.assign({}, options));
+ return await el.isEnabled();
}
-}
+};
diff --git a/__test__/e2e/util/setup.js b/__test__/e2e/util/setup.js
index cd8ad403e..dbfaac3a8 100644
--- a/__test__/e2e/util/setup.js
+++ b/__test__/e2e/util/setup.js
@@ -1,3 +1,3 @@
// This script will execute before the entire end to end run
-jest.setTimeout(1 * 60 * 1000) // Set the test callback timeout to 1 minute
-global.e2e = {} // Pass global information around using the global object as Jasmine context isn't available.
+jest.setTimeout(1 * 60 * 1000); // Set the test callback timeout to 1 minute
+global.e2e = {}; // Pass global information around using the global object as Jasmine context isn't available.
diff --git a/__test__/lambda.test.js b/__test__/lambda.test.js
index f1945f341..0231f1a62 100644
--- a/__test__/lambda.test.js
+++ b/__test__/lambda.test.js
@@ -1,59 +1,72 @@
-import { handler } from '../lambda.js'
-import { setupTest, cleanupTest } from './test_helpers'
+import { handler } from "../lambda.js";
+import { setupTest, cleanupTest } from "./test_helpers";
-beforeAll(async () => await setupTest(), global.DATABASE_SETUP_TEARDOWN_TIMEOUT)
-afterAll(async () => await cleanupTest(), global.DATABASE_SETUP_TEARDOWN_TIMEOUT)
+beforeAll(
+ async () => await setupTest(),
+ global.DATABASE_SETUP_TEARDOWN_TIMEOUT
+);
+afterAll(
+ async () => await cleanupTest(),
+ global.DATABASE_SETUP_TEARDOWN_TIMEOUT
+);
-describe('AWS Lambda', async () => {
- test('completes request to lambda', () => {
+describe("AWS Lambda", async () => {
+ test("completes request to lambda", () => {
const fakeEvent = {
- resource: '/{proxy+}',
- path: '/',
- httpMethod: 'GET',
- headers:
- { Accept: '*/*',
- 'CloudFront-Forwarded-Proto': 'https',
- Host: 'spoke.example.com',
- origin: 'https://spoke.example.com',
- 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
- 'X-Twilio-Body': '\u0019!@#!@K#J!@K#J!@#',
- 'X-Forwarded-Port': '443',
- 'X-Forwarded-Proto': 'https' },
- multiValueHeaders:
- { Accept: ['*/*'],
- 'CloudFront-Forwarded-Proto': ['https'],
- Host: ['spoke.example.com'],
- 'X-Twilio-Body': ['\u0019!@#!@K#J!@K#J!@#'],
- 'X-Forwarded-Port': ['443'],
- 'X-Forwarded-Proto': ['https'] },
+ resource: "/{proxy+}",
+ path: "/",
+ httpMethod: "GET",
+ headers: {
+ Accept: "*/*",
+ "CloudFront-Forwarded-Proto": "https",
+ Host: "spoke.example.com",
+ origin: "https://spoke.example.com",
+ "User-Agent":
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36",
+ "X-Twilio-Body": "\u0019!@#!@K#J!@K#J!@#",
+ "X-Forwarded-Port": "443",
+ "X-Forwarded-Proto": "https"
+ },
+ multiValueHeaders: {
+ Accept: ["*/*"],
+ "CloudFront-Forwarded-Proto": ["https"],
+ Host: ["spoke.example.com"],
+ "X-Twilio-Body": ["\u0019!@#!@K#J!@K#J!@#"],
+ "X-Forwarded-Port": ["443"],
+ "X-Forwarded-Proto": ["https"]
+ },
queryStringParameters: null,
multiValueQueryStringParameters: null,
- pathParameters: { proxy: '' },
- stageVariables: { lambdaVersion: 'latest' },
- requestContext:
- { resourcePath: '/{proxy+}',
- httpMethod: 'POST',
- requestTime: '25/Oct/2018:00:08:03 +0000',
- path: '/',
- protocol: 'HTTP/1.1',
- stage: 'latest',
- domainPrefix: 'spoke',
+ pathParameters: { proxy: "" },
+ stageVariables: { lambdaVersion: "latest" },
+ requestContext: {
+ resourcePath: "/{proxy+}",
+ httpMethod: "POST",
+ requestTime: "25/Oct/2018:00:08:03 +0000",
+ path: "/",
+ protocol: "HTTP/1.1",
+ stage: "latest",
+ domainPrefix: "spoke",
requestTimeEpoch: 1540426083986,
- domainName: 'spoke.example.com'
+ domainName: "spoke.example.com"
},
isBase64Encoded: false
- }
+ };
handler(
fakeEvent,
- { succeed: (response) => {
- expect(response.statusCode).toBe(200)
- expect(response.headers['content-type']).toBe('text/html; charset=utf-8')
+ {
+ succeed: response => {
+ expect(response.statusCode).toBe(200);
+ expect(response.headers["content-type"]).toBe(
+ "text/html; charset=utf-8"
+ );
// console.log('context.succeed response', response)
- }
+ }
},
(err, res) => {
- console.log('result returned through callback', err, res)
- })
+ console.log("result returned through callback", err, res);
+ }
+ );
// console.log('lambda server', result)
- })
-})
+ });
+});
diff --git a/__test__/lib.test.js b/__test__/lib.test.js
index 4405b8167..07c553dd7 100644
--- a/__test__/lib.test.js
+++ b/__test__/lib.test.js
@@ -1,23 +1,37 @@
-import { resolvers } from '../src/server/api/schema'
-import { schema } from '../src/api/schema'
-import twilio from '../src/server/api/lib/twilio'
-import { makeExecutableSchema } from 'graphql-tools'
+import { resolvers } from "../src/server/api/schema";
+import { schema } from "../src/api/schema";
+import twilio from "../src/server/api/lib/twilio";
+import { makeExecutableSchema } from "graphql-tools";
const mySchema = makeExecutableSchema({
typeDefs: schema,
resolvers: resolvers,
- allowUndefinedInResolve: true,
-})
+ allowUndefinedInResolve: true
+});
-it('should parse a message with a media url', () => {
- expect(twilio.parseMessageText({ text: 'foo bar' }).body).toBe('foo bar')
- expect(twilio.parseMessageText({ text: 'foo bar [http://example.com/foo.jpg]' }).body).toBe('foo bar ')
- expect(twilio.parseMessageText({ text: 'foo bar [http://example.com/foo.jpg]' }).mediaUrl).toBe('http://example.com/foo.jpg')
- expect(twilio.parseMessageText({ text: 'foo bar [ https://example.com/foo.jpg ]' }).mediaUrl).toBe('https://example.com/foo.jpg')
+it("should parse a message with a media url", () => {
+ expect(twilio.parseMessageText({ text: "foo bar" }).body).toBe("foo bar");
+ expect(
+ twilio.parseMessageText({ text: "foo bar [http://example.com/foo.jpg]" })
+ .body
+ ).toBe("foo bar ");
+ expect(
+ twilio.parseMessageText({ text: "foo bar [http://example.com/foo.jpg]" })
+ .mediaUrl
+ ).toBe("http://example.com/foo.jpg");
+ expect(
+ twilio.parseMessageText({ text: "foo bar [ https://example.com/foo.jpg ]" })
+ .mediaUrl
+ ).toBe("https://example.com/foo.jpg");
- const doubleShouldOnlyUseFirst = 'foo bar [ https://example.com/foo.jpg ] and this other image! [ https://example.com/bar.jpg ]'
- expect(twilio.parseMessageText({ text: doubleShouldOnlyUseFirst }).mediaUrl).toBe('https://example.com/foo.jpg')
- expect(twilio.parseMessageText({ text: doubleShouldOnlyUseFirst }).body).toBe('foo bar and this other image! [ https://example.com/bar.jpg ]')
+ const doubleShouldOnlyUseFirst =
+ "foo bar [ https://example.com/foo.jpg ] and this other image! [ https://example.com/bar.jpg ]";
+ expect(
+ twilio.parseMessageText({ text: doubleShouldOnlyUseFirst }).mediaUrl
+ ).toBe("https://example.com/foo.jpg");
+ expect(twilio.parseMessageText({ text: doubleShouldOnlyUseFirst }).body).toBe(
+ "foo bar and this other image! [ https://example.com/bar.jpg ]"
+ );
- expect(twilio.parseMessageText({ text: undefined }).body).toBe('')
-})
+ expect(twilio.parseMessageText({ text: undefined }).body).toBe("");
+});
diff --git a/__test__/lib/dst-helper.test.js b/__test__/lib/dst-helper.test.js
index 7443b1259..f505d5b63 100644
--- a/__test__/lib/dst-helper.test.js
+++ b/__test__/lib/dst-helper.test.js
@@ -1,85 +1,101 @@
-import {DstHelper} from '../../src/lib/dst-helper'
-import {DateTime, zone, DateFunctions} from 'timezonecomplete'
+import { DstHelper } from "../../src/lib/dst-helper";
+import { DateTime, zone, DateFunctions } from "timezonecomplete";
-var MockDate = require('mockdate');
+var MockDate = require("mockdate");
-describe('test DstHelper', () => {
+describe("test DstHelper", () => {
afterEach(() => {
- MockDate.reset()
- })
+ MockDate.reset();
+ });
- it('helps us figure out if we\'re in DST in February in New York', () => {
- MockDate.set('2018-02-01T15:00:00Z')
- let d = new DateTime(new Date(), DateFunctions.Get, zone('America/New_York'))
- expect(DstHelper.isOffsetDst(d.offset(), 'America/New_York')).toBeFalsy()
- expect(DstHelper.isDateTimeDst(d, 'America/New_York')).toBeFalsy()
- expect(DstHelper.isDateDst(new Date(), 'America/New_York')).toBeFalsy()
- })
+ it("helps us figure out if we're in DST in February in New York", () => {
+ MockDate.set("2018-02-01T15:00:00Z");
+ let d = new DateTime(
+ new Date(),
+ DateFunctions.Get,
+ zone("America/New_York")
+ );
+ expect(DstHelper.isOffsetDst(d.offset(), "America/New_York")).toBeFalsy();
+ expect(DstHelper.isDateTimeDst(d, "America/New_York")).toBeFalsy();
+ expect(DstHelper.isDateDst(new Date(), "America/New_York")).toBeFalsy();
+ });
- it('helps us figure out if we\'re in DST in July in New York', () => {
- MockDate.set('2018-07-21T15:00:00Z')
- let d = new DateTime(new Date(), DateFunctions.Get, zone('America/New_York'))
- expect(DstHelper.isOffsetDst(d.offset(), 'America/New_York')).toBeTruthy()
- expect(DstHelper.isDateTimeDst(d, 'America/New_York')).toBeTruthy()
- expect(DstHelper.isDateDst(new Date(), 'America/New_York')).toBeTruthy()
- })
+ it("helps us figure out if we're in DST in July in New York", () => {
+ MockDate.set("2018-07-21T15:00:00Z");
+ let d = new DateTime(
+ new Date(),
+ DateFunctions.Get,
+ zone("America/New_York")
+ );
+ expect(DstHelper.isOffsetDst(d.offset(), "America/New_York")).toBeTruthy();
+ expect(DstHelper.isDateTimeDst(d, "America/New_York")).toBeTruthy();
+ expect(DstHelper.isDateDst(new Date(), "America/New_York")).toBeTruthy();
+ });
- it('helps us figure out if we\'re in DST in February in Sydney', () => {
- MockDate.set('2018-02-01T15:00:00Z')
- let d = new DateTime(new Date(), DateFunctions.Get, zone('Australia/Sydney'))
- expect(DstHelper.isOffsetDst(d.offset(), 'Australia/Sydney')).toBeTruthy()
- expect(DstHelper.isDateTimeDst(d, 'Australia/Sydney')).toBeTruthy()
- expect(DstHelper.isDateDst(new Date(), 'Australia/Sydney')).toBeTruthy()
- })
+ it("helps us figure out if we're in DST in February in Sydney", () => {
+ MockDate.set("2018-02-01T15:00:00Z");
+ let d = new DateTime(
+ new Date(),
+ DateFunctions.Get,
+ zone("Australia/Sydney")
+ );
+ expect(DstHelper.isOffsetDst(d.offset(), "Australia/Sydney")).toBeTruthy();
+ expect(DstHelper.isDateTimeDst(d, "Australia/Sydney")).toBeTruthy();
+ expect(DstHelper.isDateDst(new Date(), "Australia/Sydney")).toBeTruthy();
+ });
- it('helps us figure out if we\'re in DST in July in Sydney', () => {
- MockDate.set('2018-07-01T15:00:00Z')
- let d = new DateTime(new Date(), DateFunctions.Get, zone('Australia/Sydney'))
- expect(DstHelper.isOffsetDst(d.offset(), 'Australia/Sydney')).toBeFalsy()
- expect(DstHelper.isDateTimeDst(d, 'Australia/Sydney')).toBeFalsy()
- expect(DstHelper.isDateDst(new Date(), 'Australia/Sydney')).toBeFalsy()
- })
+ it("helps us figure out if we're in DST in July in Sydney", () => {
+ MockDate.set("2018-07-01T15:00:00Z");
+ let d = new DateTime(
+ new Date(),
+ DateFunctions.Get,
+ zone("Australia/Sydney")
+ );
+ expect(DstHelper.isOffsetDst(d.offset(), "Australia/Sydney")).toBeFalsy();
+ expect(DstHelper.isDateTimeDst(d, "Australia/Sydney")).toBeFalsy();
+ expect(DstHelper.isDateDst(new Date(), "Australia/Sydney")).toBeFalsy();
+ });
- it('helps us figure out if we\'re in DST in February in Kathmandu, which has no DST', () => {
- MockDate.set('2018-02-01T15:00:00Z')
- let d = new DateTime(new Date(), DateFunctions.Get, zone('Asia/Kathmandu'))
- expect(DstHelper.isOffsetDst(d.offset(), 'Asia/Kathmandu')).toBeFalsy()
- expect(DstHelper.isDateTimeDst(d, 'Asia/Kathmandu')).toBeFalsy()
- expect(DstHelper.isDateDst(new Date(), 'Asia/Kathmandu')).toBeFalsy()
- })
+ it("helps us figure out if we're in DST in February in Kathmandu, which has no DST", () => {
+ MockDate.set("2018-02-01T15:00:00Z");
+ let d = new DateTime(new Date(), DateFunctions.Get, zone("Asia/Kathmandu"));
+ expect(DstHelper.isOffsetDst(d.offset(), "Asia/Kathmandu")).toBeFalsy();
+ expect(DstHelper.isDateTimeDst(d, "Asia/Kathmandu")).toBeFalsy();
+ expect(DstHelper.isDateDst(new Date(), "Asia/Kathmandu")).toBeFalsy();
+ });
- it('helps us figure out if we\'re in DST in July in Kathmandu, which has no DST', () => {
- MockDate.set('2018-07-01T15:00:00Z')
- let d = new DateTime(new Date(), DateFunctions.Get, zone('Asia/Kathmandu'))
- expect(DstHelper.isOffsetDst(d.offset(), 'Asia/Kathmandu')).toBeFalsy()
- expect(DstHelper.isDateTimeDst(d, 'Asia/Kathmandu')).toBeFalsy()
- expect(DstHelper.isDateDst(new Date(), 'Asia/Kathmandu')).toBeFalsy()
- })
+ it("helps us figure out if we're in DST in July in Kathmandu, which has no DST", () => {
+ MockDate.set("2018-07-01T15:00:00Z");
+ let d = new DateTime(new Date(), DateFunctions.Get, zone("Asia/Kathmandu"));
+ expect(DstHelper.isOffsetDst(d.offset(), "Asia/Kathmandu")).toBeFalsy();
+ expect(DstHelper.isDateTimeDst(d, "Asia/Kathmandu")).toBeFalsy();
+ expect(DstHelper.isDateDst(new Date(), "Asia/Kathmandu")).toBeFalsy();
+ });
- it('helps us figure out if we\'re in DST in February in Arizona, which has no DST', () => {
- MockDate.set('2018-02-01T15:00:00Z')
- let d = new DateTime(new Date(), DateFunctions.Get, zone('US/Arizona'))
- expect(DstHelper.isOffsetDst(d.offset(), 'US/Arizona')).toBeFalsy()
- expect(DstHelper.isDateTimeDst(d, 'US/Arizona')).toBeFalsy()
- expect(DstHelper.isDateDst(new Date(), 'US/Arizona')).toBeFalsy()
- })
+ it("helps us figure out if we're in DST in February in Arizona, which has no DST", () => {
+ MockDate.set("2018-02-01T15:00:00Z");
+ let d = new DateTime(new Date(), DateFunctions.Get, zone("US/Arizona"));
+ expect(DstHelper.isOffsetDst(d.offset(), "US/Arizona")).toBeFalsy();
+ expect(DstHelper.isDateTimeDst(d, "US/Arizona")).toBeFalsy();
+ expect(DstHelper.isDateDst(new Date(), "US/Arizona")).toBeFalsy();
+ });
- it('helps us figure out if we\'re in DST in July in Arizona, which has no DST', () => {
- MockDate.set('2018-07-01T15:00:00Z')
- let d = new DateTime(new Date(), DateFunctions.Get, zone('US/Arizona'))
- expect(DstHelper.isOffsetDst(d.offset(), 'US/Arizona')).toBeFalsy()
- expect(DstHelper.isDateTimeDst(d, 'US/Arizona')).toBeFalsy()
- expect(DstHelper.isDateDst(new Date(), 'US/Arizona')).toBeFalsy()
- })
+ it("helps us figure out if we're in DST in July in Arizona, which has no DST", () => {
+ MockDate.set("2018-07-01T15:00:00Z");
+ let d = new DateTime(new Date(), DateFunctions.Get, zone("US/Arizona"));
+ expect(DstHelper.isOffsetDst(d.offset(), "US/Arizona")).toBeFalsy();
+ expect(DstHelper.isDateTimeDst(d, "US/Arizona")).toBeFalsy();
+ expect(DstHelper.isDateDst(new Date(), "US/Arizona")).toBeFalsy();
+ });
- it('correctly reports a timezone\'s offset and whether it has DST', () => {
- expect(DstHelper.getTimezoneOffsetHours('America/New_York')).toEqual(-5)
- expect(DstHelper.timezoneHasDst('America/New_York')).toBeTruthy()
- expect(DstHelper.getTimezoneOffsetHours('US/Arizona')).toEqual(-7)
- expect(DstHelper.timezoneHasDst('US/Arizona')).toBeFalsy()
- expect(DstHelper.getTimezoneOffsetHours('Europe/Paris')).toEqual(1)
- expect(DstHelper.timezoneHasDst('Europe/Paris')).toBeTruthy()
- expect(DstHelper.getTimezoneOffsetHours('Europe/London')).toEqual(0)
- expect(DstHelper.timezoneHasDst('Europe/London')).toBeTruthy()
- })
-})
\ No newline at end of file
+ it("correctly reports a timezone's offset and whether it has DST", () => {
+ expect(DstHelper.getTimezoneOffsetHours("America/New_York")).toEqual(-5);
+ expect(DstHelper.timezoneHasDst("America/New_York")).toBeTruthy();
+ expect(DstHelper.getTimezoneOffsetHours("US/Arizona")).toEqual(-7);
+ expect(DstHelper.timezoneHasDst("US/Arizona")).toBeFalsy();
+ expect(DstHelper.getTimezoneOffsetHours("Europe/Paris")).toEqual(1);
+ expect(DstHelper.timezoneHasDst("Europe/Paris")).toBeTruthy();
+ expect(DstHelper.getTimezoneOffsetHours("Europe/London")).toEqual(0);
+ expect(DstHelper.timezoneHasDst("Europe/London")).toBeTruthy();
+ });
+});
diff --git a/__test__/lib/parse-csv.test.js b/__test__/lib/parse-csv.test.js
index 56567be64..a8ad92604 100644
--- a/__test__/lib/parse-csv.test.js
+++ b/__test__/lib/parse-csv.test.js
@@ -1,16 +1,20 @@
-import {parseCSV} from '../../src/lib'
+import { parseCSV } from "../../src/lib";
-describe('parseCSV', () => {
- describe('with PHONE_NUMBER_COUNTRY set', () => {
- beforeEach(() => process.env.PHONE_NUMBER_COUNTRY = 'AU')
- afterEach(() => delete process.env.PHONE_NUMBER_COUNTRY)
+describe("parseCSV", () => {
+ describe("with PHONE_NUMBER_COUNTRY set", () => {
+ beforeEach(() => (process.env.PHONE_NUMBER_COUNTRY = "AU"));
+ afterEach(() => delete process.env.PHONE_NUMBER_COUNTRY);
- it('should consider phone numbers from that country as valid', () => {
- const csv = "firstName,lastName,cell\ntest,test,61468511000"
- parseCSV(csv, [], ({ contacts, customFields, validationStats, error }) => {
- expect(error).toBeFalsy()
- expect(contacts.length).toEqual(1)
- })
- })
- })
-})
+ it("should consider phone numbers from that country as valid", () => {
+ const csv = "firstName,lastName,cell\ntest,test,61468511000";
+ parseCSV(
+ csv,
+ [],
+ ({ contacts, customFields, validationStats, error }) => {
+ expect(error).toBeFalsy();
+ expect(contacts.length).toEqual(1);
+ }
+ );
+ });
+ });
+});
diff --git a/__test__/lib/timezones.test.js b/__test__/lib/timezones.test.js
index 126ffc92f..895a2bab5 100644
--- a/__test__/lib/timezones.test.js
+++ b/__test__/lib/timezones.test.js
@@ -1,6 +1,6 @@
-import moment from 'moment-timezone'
+import moment from "moment-timezone";
-const MockDate = require('mockdate');
+const MockDate = require("mockdate");
import {
convertOffsetsToStrings,
@@ -12,9 +12,9 @@ import {
getUtcFromOffsetAndHour,
getUtcFromTimezoneAndHour,
getSendBeforeTimeUtc
-} from '../../src/lib/index'
+} from "../../src/lib/index";
-import { getProcessEnvDstReferenceTimezone } from '../../src/lib/tz-helpers'
+import { getProcessEnvDstReferenceTimezone } from "../../src/lib/tz-helpers";
const makeCampignTextingHoursConfig = (
textingHoursEnforced,
@@ -27,8 +27,8 @@ const makeCampignTextingHoursConfig = (
textingHoursStart,
textingHoursEnd,
timezone
- }
-}
+ };
+};
const makeCampaignOnlyWithTextingHoursConfigFields = (
overrideOrganizationTextingHours,
@@ -42,10 +42,10 @@ const makeCampaignOnlyWithTextingHoursConfigFields = (
textingHoursStart,
textingHoursEnd,
timezone
- )
- textingHoursConfigFields.overrideOrganizationTextingHours = overrideOrganizationTextingHours
- return textingHoursConfigFields
-}
+ );
+ textingHoursConfigFields.overrideOrganizationTextingHours = overrideOrganizationTextingHours;
+ return textingHoursConfigFields;
+};
const makeConfig = (
textingHoursStart,
@@ -58,461 +58,605 @@ const makeConfig = (
textingHoursEnd,
textingHoursEnforced,
campaignTextingHours
- }
-}
+ };
+};
const makeLocationWithOnlyTimezoneData = (offset, hasDst) => {
- return { timezone: { offset, hasDst } }
-}
+ return { timezone: { offset, hasDst } };
+};
-const buildIsBetweenTextingHoursExpectForSpecifiedTimezone = (offsetData, start, end, timezone) => {
- return expect(isBetweenTextingHours(offsetData, makeConfig(0, 0, false, makeCampignTextingHoursConfig(true, start, end, timezone ))))
-}
+const buildIsBetweenTextingHoursExpectForSpecifiedTimezone = (
+ offsetData,
+ start,
+ end,
+ timezone
+) => {
+ return expect(
+ isBetweenTextingHours(
+ offsetData,
+ makeConfig(
+ 0,
+ 0,
+ false,
+ makeCampignTextingHoursConfig(true, start, end, timezone)
+ )
+ )
+ );
+};
const buildIsBetweenTextingHoursExpect = (offsetData, start, end) => {
- return buildIsBetweenTextingHoursExpectForSpecifiedTimezone(offsetData, start, end, 'America/Los_Angeles')
-}
+ return buildIsBetweenTextingHoursExpectForSpecifiedTimezone(
+ offsetData,
+ start,
+ end,
+ "America/Los_Angeles"
+ );
+};
const buildIsBetweenTextingHoursExpectWithNoOffset = (start, end) => {
- return expect(isBetweenTextingHours(null, makeConfig(0, 0, false, makeCampignTextingHoursConfig(true, start, end, 'America/New_York' ))))
-}
+ return expect(
+ isBetweenTextingHours(
+ null,
+ makeConfig(
+ 0,
+ 0,
+ false,
+ makeCampignTextingHoursConfig(true, start, end, "America/New_York")
+ )
+ )
+ );
+};
-jest.unmock('../../src/lib/timezones')
-jest.mock('../../src/lib/tz-helpers')
+jest.unmock("../../src/lib/timezones");
+jest.mock("../../src/lib/tz-helpers");
-describe('test getLocalTime winter (standard time)', () => {
+describe("test getLocalTime winter (standard time)", () => {
beforeAll(() => {
- MockDate.set('2018-02-01T15:00:00Z')
- })
+ MockDate.set("2018-02-01T15:00:00Z");
+ });
afterAll(() => {
- MockDate.reset()
- })
-
- it('returns correct local time UTC-5 standard time', () => {
- let localTime = getLocalTime(-5, true, getProcessEnvDstReferenceTimezone())
- expect(localTime.hours()).toEqual(10)
- expect(new Date(localTime)).toEqual(new Date('2018-02-01T10:00:00.000-05:00'))
- })
-})
-
-describe('test getLocalTime summer (DST)', () => {
+ MockDate.reset();
+ });
+
+ it("returns correct local time UTC-5 standard time", () => {
+ let localTime = getLocalTime(-5, true, getProcessEnvDstReferenceTimezone());
+ expect(localTime.hours()).toEqual(10);
+ expect(new Date(localTime)).toEqual(
+ new Date("2018-02-01T10:00:00.000-05:00")
+ );
+ });
+});
+
+describe("test getLocalTime summer (DST)", () => {
beforeEach(() => {
- MockDate.set('2018-07-21T15:00:00Z')
- })
+ MockDate.set("2018-07-21T15:00:00Z");
+ });
afterEach(() => {
- MockDate.reset()
- })
-
- it('returns correct local time UTC-5 DST', () => {
- let localTime = getLocalTime(-5, true, getProcessEnvDstReferenceTimezone())
- expect(localTime.hours()).toEqual(11)
- expect(new Date(localTime)).toEqual(new Date('2018-07-21T10:00:00.000-05:00'))
- })
-})
-
-describe('testing isBetweenTextingHours with env.TZ set', () => {
- var tzHelpers = require('../../src/lib/tz-helpers')
+ MockDate.reset();
+ });
+
+ it("returns correct local time UTC-5 DST", () => {
+ let localTime = getLocalTime(-5, true, getProcessEnvDstReferenceTimezone());
+ expect(localTime.hours()).toEqual(11);
+ expect(new Date(localTime)).toEqual(
+ new Date("2018-07-21T10:00:00.000-05:00")
+ );
+ });
+});
+
+describe("testing isBetweenTextingHours with env.TZ set", () => {
+ var tzHelpers = require("../../src/lib/tz-helpers");
beforeAll(() => {
- tzHelpers.getProcessEnvTz.mockImplementation(() => 'America/Los_Angeles')
- MockDate.set('2018-02-01T15:00:00.000-05:00')
- })
+ tzHelpers.getProcessEnvTz.mockImplementation(() => "America/Los_Angeles");
+ MockDate.set("2018-02-01T15:00:00.000-05:00");
+ });
afterAll(() => {
jest.restoreAllMocks();
- MockDate.reset()
- })
+ MockDate.reset();
+ });
- it('returns true if texting hours are not enforced', () => {
- expect(isBetweenTextingHours(null, makeConfig(1, 1, false))).toBeTruthy()
- })
+ it("returns true if texting hours are not enforced", () => {
+ expect(isBetweenTextingHours(null, makeConfig(1, 1, false))).toBeTruthy();
+ });
- it('returns true if texting hours are not enforced and there are campaign texting hours with !textingHoursEnforced', () => {
- expect(isBetweenTextingHours(null, makeConfig(1, 1, false, makeCampignTextingHoursConfig(false, 0, 0, 'not_used')))).toBeTruthy()
- })
+ it("returns true if texting hours are not enforced and there are campaign texting hours with !textingHoursEnforced", () => {
+ expect(
+ isBetweenTextingHours(
+ null,
+ makeConfig(
+ 1,
+ 1,
+ false,
+ makeCampignTextingHoursConfig(false, 0, 0, "not_used")
+ )
+ )
+ ).toBeTruthy();
+ });
- it('returns false if texting hours are 05-07 and time is 12:00', () => {
- expect(isBetweenTextingHours(null, makeConfig(5, 7, true))).toBeFalsy()
- }
- )
+ it("returns false if texting hours are 05-07 and time is 12:00", () => {
+ expect(isBetweenTextingHours(null, makeConfig(5, 7, true))).toBeFalsy();
+ });
- it('returns false if texting hours are 14-21 and time is 12:00', () => {
- expect(isBetweenTextingHours(null, makeConfig(14, 21, true))).toBeFalsy()
- }
- )
+ it("returns false if texting hours are 14-21 and time is 12:00", () => {
+ expect(isBetweenTextingHours(null, makeConfig(14, 21, true))).toBeFalsy();
+ });
- it('returns true if texting hours are 10-21 and time is 12:00', () => {
- expect(isBetweenTextingHours(null, makeConfig(10, 21, true))).toBeTruthy()
- })
+ it("returns true if texting hours are 10-21 and time is 12:00", () => {
+ expect(isBetweenTextingHours(null, makeConfig(10, 21, true))).toBeTruthy();
+ });
- it('returns true if texting hours are 12-21 and time is 12:00', () => {
- expect(isBetweenTextingHours(null, makeConfig(12, 21, true))).toBeTruthy()
- })
+ it("returns true if texting hours are 12-21 and time is 12:00", () => {
+ expect(isBetweenTextingHours(null, makeConfig(12, 21, true))).toBeTruthy();
+ });
- it('returns true if texting hours are 10-12 and time is 12:00', () => {
- expect(isBetweenTextingHours(null, makeConfig(10, 12, true))).toBeTruthy()
- })
-})
+ it("returns true if texting hours are 10-12 and time is 12:00", () => {
+ expect(isBetweenTextingHours(null, makeConfig(10, 12, true))).toBeTruthy();
+ });
+});
-describe('isBetweenTextingHours with campaign overrides works with DST', () => {
+describe("isBetweenTextingHours with campaign overrides works with DST", () => {
afterEach(() => {
- MockDate.reset()
- })
-
- const easternOffsetData = {offset: -5, hasDST: true}
- const arizonaOffsetData = {offset: -7, hasDST: true}
-
- it('works for NYC in January', () => {
- MockDate.set('2018-01-01T15:00:00.000-05:00')
- buildIsBetweenTextingHoursExpectForSpecifiedTimezone(easternOffsetData, 14, 18, 'US/Eastern').toBeTruthy()
- buildIsBetweenTextingHoursExpectForSpecifiedTimezone(easternOffsetData, 15, 18, 'US/Eastern').toBeTruthy()
- buildIsBetweenTextingHoursExpectForSpecifiedTimezone(easternOffsetData, 16, 18, 'US/Eastern').toBeFalsy()
- buildIsBetweenTextingHoursExpectForSpecifiedTimezone(easternOffsetData, 17, 18, 'US/Eastern').toBeFalsy()
- })
-
- it('works for NYC in July', () => {
- MockDate.set('2018-06-01T15:00:00.000-05:00')
- buildIsBetweenTextingHoursExpectForSpecifiedTimezone(easternOffsetData, 14, 18, 'US/Eastern').toBeTruthy()
- buildIsBetweenTextingHoursExpectForSpecifiedTimezone(easternOffsetData, 15, 18, 'US/Eastern').toBeTruthy()
- buildIsBetweenTextingHoursExpectForSpecifiedTimezone(easternOffsetData, 16, 18, 'US/Eastern').toBeTruthy()
- buildIsBetweenTextingHoursExpectForSpecifiedTimezone(easternOffsetData, 17, 18, 'US/Eastern').toBeFalsy()
- })
-
- it('works for Arizona in January', () => {
- MockDate.set('2018-01-01T15:00:00.000-07:00')
- buildIsBetweenTextingHoursExpectForSpecifiedTimezone(arizonaOffsetData, 14, 18, 'US/Arizona').toBeTruthy()
- buildIsBetweenTextingHoursExpectForSpecifiedTimezone(arizonaOffsetData, 15, 18, 'US/Arizona').toBeTruthy()
- buildIsBetweenTextingHoursExpectForSpecifiedTimezone(arizonaOffsetData, 16, 18, 'US/Arizona').toBeFalsy()
- buildIsBetweenTextingHoursExpectForSpecifiedTimezone(arizonaOffsetData, 17, 18, 'US/Arizona').toBeFalsy()
- })
-
- it('works for Arizona in July', () => {
- MockDate.set('2018-06-01T15:00:00.000-07:00')
- buildIsBetweenTextingHoursExpectForSpecifiedTimezone(arizonaOffsetData, 14, 18, 'US/Arizona').toBeTruthy()
- buildIsBetweenTextingHoursExpectForSpecifiedTimezone(arizonaOffsetData, 15, 18, 'US/Arizona').toBeTruthy()
- buildIsBetweenTextingHoursExpectForSpecifiedTimezone(arizonaOffsetData, 16, 18, 'US/Arizona').toBeFalsy()
- buildIsBetweenTextingHoursExpectForSpecifiedTimezone(arizonaOffsetData, 17, 18, 'US/Arizona').toBeFalsy()
- })
-})
-
-describe('test isBetweenTextingHours with campaign overrides', () => {
+ MockDate.reset();
+ });
+
+ const easternOffsetData = { offset: -5, hasDST: true };
+ const arizonaOffsetData = { offset: -7, hasDST: true };
+
+ it("works for NYC in January", () => {
+ MockDate.set("2018-01-01T15:00:00.000-05:00");
+ buildIsBetweenTextingHoursExpectForSpecifiedTimezone(
+ easternOffsetData,
+ 14,
+ 18,
+ "US/Eastern"
+ ).toBeTruthy();
+ buildIsBetweenTextingHoursExpectForSpecifiedTimezone(
+ easternOffsetData,
+ 15,
+ 18,
+ "US/Eastern"
+ ).toBeTruthy();
+ buildIsBetweenTextingHoursExpectForSpecifiedTimezone(
+ easternOffsetData,
+ 16,
+ 18,
+ "US/Eastern"
+ ).toBeFalsy();
+ buildIsBetweenTextingHoursExpectForSpecifiedTimezone(
+ easternOffsetData,
+ 17,
+ 18,
+ "US/Eastern"
+ ).toBeFalsy();
+ });
+
+ it("works for NYC in July", () => {
+ MockDate.set("2018-06-01T15:00:00.000-05:00");
+ buildIsBetweenTextingHoursExpectForSpecifiedTimezone(
+ easternOffsetData,
+ 14,
+ 18,
+ "US/Eastern"
+ ).toBeTruthy();
+ buildIsBetweenTextingHoursExpectForSpecifiedTimezone(
+ easternOffsetData,
+ 15,
+ 18,
+ "US/Eastern"
+ ).toBeTruthy();
+ buildIsBetweenTextingHoursExpectForSpecifiedTimezone(
+ easternOffsetData,
+ 16,
+ 18,
+ "US/Eastern"
+ ).toBeTruthy();
+ buildIsBetweenTextingHoursExpectForSpecifiedTimezone(
+ easternOffsetData,
+ 17,
+ 18,
+ "US/Eastern"
+ ).toBeFalsy();
+ });
+
+ it("works for Arizona in January", () => {
+ MockDate.set("2018-01-01T15:00:00.000-07:00");
+ buildIsBetweenTextingHoursExpectForSpecifiedTimezone(
+ arizonaOffsetData,
+ 14,
+ 18,
+ "US/Arizona"
+ ).toBeTruthy();
+ buildIsBetweenTextingHoursExpectForSpecifiedTimezone(
+ arizonaOffsetData,
+ 15,
+ 18,
+ "US/Arizona"
+ ).toBeTruthy();
+ buildIsBetweenTextingHoursExpectForSpecifiedTimezone(
+ arizonaOffsetData,
+ 16,
+ 18,
+ "US/Arizona"
+ ).toBeFalsy();
+ buildIsBetweenTextingHoursExpectForSpecifiedTimezone(
+ arizonaOffsetData,
+ 17,
+ 18,
+ "US/Arizona"
+ ).toBeFalsy();
+ });
+
+ it("works for Arizona in July", () => {
+ MockDate.set("2018-06-01T15:00:00.000-07:00");
+ buildIsBetweenTextingHoursExpectForSpecifiedTimezone(
+ arizonaOffsetData,
+ 14,
+ 18,
+ "US/Arizona"
+ ).toBeTruthy();
+ buildIsBetweenTextingHoursExpectForSpecifiedTimezone(
+ arizonaOffsetData,
+ 15,
+ 18,
+ "US/Arizona"
+ ).toBeTruthy();
+ buildIsBetweenTextingHoursExpectForSpecifiedTimezone(
+ arizonaOffsetData,
+ 16,
+ 18,
+ "US/Arizona"
+ ).toBeFalsy();
+ buildIsBetweenTextingHoursExpectForSpecifiedTimezone(
+ arizonaOffsetData,
+ 17,
+ 18,
+ "US/Arizona"
+ ).toBeFalsy();
+ });
+});
+
+describe("test isBetweenTextingHours with campaign overrides", () => {
beforeAll(() => {
- MockDate.set('2018-02-01T15:00:00.000-05:00')
- })
+ MockDate.set("2018-02-01T15:00:00.000-05:00");
+ });
afterAll(() => {
- MockDate.reset()
- })
-
- const offsetData = {offset: -8, hasDST: true}
-
- it('returns false if texting hours are 05-07 and time is 12:00', () => {
- buildIsBetweenTextingHoursExpect(offsetData, 5, 7).toBeFalsy()
- }
- )
-
- it('returns false if texting hours are 14-21 and time is 12:00', () => {
- buildIsBetweenTextingHoursExpect(offsetData, 14, 21).toBeFalsy()
- }
- )
-
- it('returns true if texting hours are 10-21 and time is 12:00', () => {
- buildIsBetweenTextingHoursExpect(offsetData,10, 21).toBeTruthy()
- })
-
- it('returns true if texting hours are 12-21 and time is 12:00', () => {
- buildIsBetweenTextingHoursExpect(offsetData,12, 21).toBeTruthy()
- })
-
- it('returns false if texting hours are 10-12 and time is 12:00', () => {
- buildIsBetweenTextingHoursExpect(offsetData,10, 12).toBeFalsy()
- })
-
- it('returns false if texting hours are 16-21 and time is 3pm NY and offset data is not provided', () => {
- buildIsBetweenTextingHoursExpectWithNoOffset(16, 21).toBeFalsy()
- })
-
- it('returns true if texting hours are 09-21 and time is 3pm NY and offset data is not provided', () => {
- buildIsBetweenTextingHoursExpectWithNoOffset(9, 21).toBeTruthy()
- })
-})
-
-describe('test isBetweenTextingHours with offset data supplied', () => {
- var offsetData = {offset: -8, hasDST: true}
- var tzHelpers = require('../../src/lib/tz-helpers')
- beforeAll(() => {
- jest.doMock('../../src/lib/tz-helpers')
- tzHelpers.getProcessEnvTz.mockImplementation(() => null)
- MockDate.set('2018-02-01T12:00:00.000-08:00')
- })
-
- afterAll(() => {
- jest.restoreAllMocks();
- MockDate.reset()
- })
-
- it('returns true if texting hours are not enforced', () => {
- expect(isBetweenTextingHours(offsetData, makeConfig(null, null, false))).toBeTruthy()
- })
-
- it('returns false if texting hours are 05-07 and time is 12:00', () => {
- expect(isBetweenTextingHours(offsetData, makeConfig(5, 7, true))).toBeFalsy()
- }
- )
+ MockDate.reset();
+ });
- it('returns false if texting hours are 14-21 and time is 12:00', () => {
- expect(isBetweenTextingHours(offsetData, makeConfig(14, 21, true))).toBeFalsy()
- }
- )
+ const offsetData = { offset: -8, hasDST: true };
- it('returns true if texting hours are 10-21 and time is 12:00', () => {
- expect(isBetweenTextingHours(offsetData, makeConfig(10, 21, true))).toBeTruthy()
- })
-
- it('returns true if texting hours are 12-21 and time is 12:00', () => {
- expect(isBetweenTextingHours(offsetData, makeConfig(12, 21, true))).toBeTruthy()
- })
-
- it('returns true if texting hours are 10-12 and time is 12:00', () => {
- expect(isBetweenTextingHours(offsetData, makeConfig(10, 12, true))).toBeFalsy()
- })
-
- it('returns true if texting hours are 10-11 and time is 12:00', () => {
- expect(isBetweenTextingHours(offsetData, makeConfig(10, 13, true))).toBeTruthy()
- })
- }
-)
-
-describe('test isBetweenTextingHours with offset data empty', () => {
- var offsetData = {offset: null, hasDST: null}
- var tzHelpers = require('../../src/lib/tz-helpers')
- beforeAll(() => {
- tzHelpers.getProcessEnvTz.mockImplementation(() => null)
- })
-
- afterEach(() => {
- MockDate.reset()
- })
-
- afterAll(() => {
- jest.restoreAllMocks();
- })
-
- it('returns true if texting hours are not enforced', () => {
- expect(isBetweenTextingHours(offsetData, makeConfig(null, null, false))).toBeTruthy()
- })
-
- it('returns false if texting hours are for MISSING TIME ZONE and time is 12:00 EST', () => {
- MockDate.set('2018-02-01T12:00:00.000-05:00')
- expect(isBetweenTextingHours(offsetData, makeConfig(null, null, true))).toBeTruthy()
- }
- )
+ it("returns false if texting hours are 05-07 and time is 12:00", () => {
+ buildIsBetweenTextingHoursExpect(offsetData, 5, 7).toBeFalsy();
+ });
- it('returns false if texting hours are for MISSING TIME ZONE and time is 11:00 EST', () => {
- MockDate.set('2018-02-01T11:00:00.000-05:00')
- expect(isBetweenTextingHours(offsetData, makeConfig(null, null, true))).toBeFalsy()
- }
- )
+ it("returns false if texting hours are 14-21 and time is 12:00", () => {
+ buildIsBetweenTextingHoursExpect(offsetData, 14, 21).toBeFalsy();
+ });
- it('returns false if texting hours are for MISSING TIME ZONE and time is 20:00 EST', () => {
- MockDate.set('2018-02-01T20:00:00.000-05:00')
- expect(isBetweenTextingHours(offsetData, makeConfig(null, null, true))).toBeTruthy()
- }
- )
+ it("returns true if texting hours are 10-21 and time is 12:00", () => {
+ buildIsBetweenTextingHoursExpect(offsetData, 10, 21).toBeTruthy();
+ });
- it('returns false if texting hours are for MISSING TIME ZONE and time is 21:00 EST', () => {
- MockDate.set('2018-02-01T21:00:00.000-05:00')
- expect(isBetweenTextingHours(offsetData, makeConfig(null, null, true))).toBeFalsy()
- }
- )
- }
-)
-
-describe('test isBetweenTextingHours with offset data NOT supplied', () => {
- var tzHelpers = require('../../src/lib/tz-helpers')
- beforeAll(() => {
- tzHelpers.getProcessEnvTz.mockImplementation(() => null)
- })
-
- afterEach(() => {
- MockDate.reset()
- })
-
- afterAll(() => {
- jest.restoreAllMocks();
- })
-
- it('returns true if texting hours are not enforced', () => {
- expect(isBetweenTextingHours(null, makeConfig(null, null, false))).toBeTruthy()
- })
-
- it('returns false if texting hours are for MISSING TIME ZONE and time is 12:00 EST', () => {
- MockDate.set('2018-02-01T12:00:00.000-05:00')
- expect(isBetweenTextingHours(null, makeConfig(null, null, true))).toBeTruthy()
- }
- )
+ it("returns true if texting hours are 12-21 and time is 12:00", () => {
+ buildIsBetweenTextingHoursExpect(offsetData, 12, 21).toBeTruthy();
+ });
- it('returns false if texting hours are for MISSING TIME ZONE and time is 11:00 EST', () => {
- MockDate.set('2018-02-01T11:00:00.000-05:00')
- expect(isBetweenTextingHours(null, makeConfig(null, null, true))).toBeFalsy()
- }
- )
+ it("returns false if texting hours are 10-12 and time is 12:00", () => {
+ buildIsBetweenTextingHoursExpect(offsetData, 10, 12).toBeFalsy();
+ });
- it('returns false if texting hours are for MISSING TIME ZONE and time is 20:00 EST', () => {
- MockDate.set('2018-02-01T20:00:00.000-05:00')
- expect(isBetweenTextingHours(null, makeConfig(null, null, true))).toBeTruthy()
- }
- )
+ it("returns false if texting hours are 16-21 and time is 3pm NY and offset data is not provided", () => {
+ buildIsBetweenTextingHoursExpectWithNoOffset(16, 21).toBeFalsy();
+ });
- it('returns false if texting hours are for MISSING TIME ZONE and time is 21:00 EST', () => {
- MockDate.set('2018-02-01T21:00:00.000-05:00')
- expect(isBetweenTextingHours(null, makeConfig(null, null, true))).toBeFalsy()
- }
- )
- }
-)
+ it("returns true if texting hours are 09-21 and time is 3pm NY and offset data is not provided", () => {
+ buildIsBetweenTextingHoursExpectWithNoOffset(9, 21).toBeTruthy();
+ });
+});
+describe("test isBetweenTextingHours with offset data supplied", () => {
+ var offsetData = { offset: -8, hasDST: true };
+ var tzHelpers = require("../../src/lib/tz-helpers");
+ beforeAll(() => {
+ jest.doMock("../../src/lib/tz-helpers");
+ tzHelpers.getProcessEnvTz.mockImplementation(() => null);
+ MockDate.set("2018-02-01T12:00:00.000-08:00");
+ });
-describe('test defaultTimezoneIsBetweenTextingHours', () => {
- var tzHelpers = require('../../src/lib/tz-helpers')
- beforeAll(() => {
- tzHelpers.getProcessEnvTz.mockImplementation(() => null)
- jest.doMock('../../src/lib/tz-helpers')
- })
+ afterAll(() => {
+ jest.restoreAllMocks();
+ MockDate.reset();
+ });
- afterEach(() => {
- MockDate.reset()
- })
+ it("returns true if texting hours are not enforced", () => {
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(null, null, false))
+ ).toBeTruthy();
+ });
- afterAll(() => {
- jest.restoreAllMocks();
- })
+ it("returns false if texting hours are 05-07 and time is 12:00", () => {
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(5, 7, true))
+ ).toBeFalsy();
+ });
- it('returns true if texting hours are not enforced', () => {
- expect(defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, false))).toBeTruthy()
- })
+ it("returns false if texting hours are 14-21 and time is 12:00", () => {
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(14, 21, true))
+ ).toBeFalsy();
+ });
- it('returns false if time is 12:00 EST', () => {
- MockDate.set('2018-02-01T12:00:00.000-05:00')
- expect(defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, true))).toBeTruthy()
- }
- )
+ it("returns true if texting hours are 10-21 and time is 12:00", () => {
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(10, 21, true))
+ ).toBeTruthy();
+ });
- it('returns false if time is 11:00 EST', () => {
- MockDate.set('2018-02-01T11:00:00.000-05:00')
- expect(defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, true))).toBeFalsy()
- }
- )
+ it("returns true if texting hours are 12-21 and time is 12:00", () => {
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(12, 21, true))
+ ).toBeTruthy();
+ });
- it('returns false if time is 20:00 EST', () => {
- MockDate.set('2018-02-01T20:00:00.000-05:00')
- expect(defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, true))).toBeTruthy()
- }
- )
+ it("returns true if texting hours are 10-12 and time is 12:00", () => {
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(10, 12, true))
+ ).toBeFalsy();
+ });
+
+ it("returns true if texting hours are 10-11 and time is 12:00", () => {
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(10, 13, true))
+ ).toBeTruthy();
+ });
+});
+
+describe("test isBetweenTextingHours with offset data empty", () => {
+ var offsetData = { offset: null, hasDST: null };
+ var tzHelpers = require("../../src/lib/tz-helpers");
+ beforeAll(() => {
+ tzHelpers.getProcessEnvTz.mockImplementation(() => null);
+ });
- it('returns false if time is 21:00 EST', () => {
- MockDate.set('2018-02-01T21:00:00.000-05:00')
- expect(defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, true))).toBeFalsy()
- }
- )
- }
-)
-
-describe('test convertOffsetsToStrings', () => {
- it('works', () => {
- let test_offsets = [[1, true], [2, false], [-1, true]]
- let strings_returned = convertOffsetsToStrings(test_offsets)
- expect(strings_returned).toHaveLength(3)
- expect(strings_returned[0]).toBe('1_1')
- expect(strings_returned[1]).toBe('2_0')
- expect(strings_returned[2]).toBe('-1_1')
- }
- )
-})
-
-describe('test getOffsets', () => {
afterEach(() => {
- MockDate.reset()
- })
-
- it('works during daylight-savings time', () => {
- MockDate.set('2018-07-21T17:00:00.000Z')
- let offsets_returned = getOffsets(makeConfig(10, 12, true))
- expect(offsets_returned).toHaveLength(2)
-
- let valid_offsets_returned = offsets_returned[0]
- expect(valid_offsets_returned).toHaveLength(4)
- expect(valid_offsets_returned[0]).toBe('-7_1')
- expect(valid_offsets_returned[1]).toBe('-8_1')
- expect(valid_offsets_returned[2]).toBe('-6_0')
- expect(valid_offsets_returned[3]).toBe('-7_0')
-
- let invalid_offsets_returned = offsets_returned[1]
- expect(invalid_offsets_returned).toHaveLength(14)
- expect(invalid_offsets_returned[0]).toBe('-4_1')
- expect(invalid_offsets_returned[1]).toBe('-5_1')
- expect(invalid_offsets_returned[2]).toBe('-6_1')
- expect(invalid_offsets_returned[3]).toBe('-9_1')
- expect(invalid_offsets_returned[4]).toBe('-10_1')
- expect(invalid_offsets_returned[5]).toBe('-11_1')
- expect(invalid_offsets_returned[6]).toBe('10_1')
- expect(invalid_offsets_returned[7]).toBe('-4_0')
- expect(invalid_offsets_returned[8]).toBe('-5_0')
- expect(invalid_offsets_returned[9]).toBe('-8_0')
- expect(invalid_offsets_returned[10]).toBe('-9_0')
- expect(invalid_offsets_returned[11]).toBe('-10_0')
- expect(invalid_offsets_returned[12]).toBe('-11_0')
- expect(invalid_offsets_returned[13]).toBe('10_0')
- })
-
- it('works during standard time', () => {
- MockDate.set('2018-02-01T17:00:00.000Z')
- let offsets_returned = getOffsets(makeConfig(10, 12, true))
- expect(offsets_returned).toHaveLength(2)
-
- let valid_offsets_returned = offsets_returned[0]
- expect(valid_offsets_returned).toHaveLength(4)
- expect(valid_offsets_returned[0]).toBe('-6_1')
- expect(valid_offsets_returned[1]).toBe('-7_1')
- expect(valid_offsets_returned[2]).toBe('-6_0')
- expect(valid_offsets_returned[3]).toBe('-7_0')
-
- let invalid_offsets_returned = offsets_returned[1]
- expect(invalid_offsets_returned).toHaveLength(14)
- expect(invalid_offsets_returned[0]).toBe('-4_1')
- expect(invalid_offsets_returned[1]).toBe('-5_1')
- expect(invalid_offsets_returned[2]).toBe('-8_1')
- expect(invalid_offsets_returned[3]).toBe('-9_1')
- expect(invalid_offsets_returned[4]).toBe('-10_1')
- expect(invalid_offsets_returned[5]).toBe('-11_1')
- expect(invalid_offsets_returned[6]).toBe('10_1')
- expect(invalid_offsets_returned[7]).toBe('-4_0')
- expect(invalid_offsets_returned[8]).toBe('-5_0')
- expect(invalid_offsets_returned[9]).toBe('-8_0')
- expect(invalid_offsets_returned[10]).toBe('-9_0')
- expect(invalid_offsets_returned[11]).toBe('-10_0')
- expect(invalid_offsets_returned[12]).toBe('-11_0')
- expect(invalid_offsets_returned[13]).toBe('10_0')
- })
-})
-
-describe('test getContactTimezone', () => {
- var tzHelpers = require('../../src/lib/tz-helpers')
+ MockDate.reset();
+ });
+
+ afterAll(() => {
+ jest.restoreAllMocks();
+ });
+
+ it("returns true if texting hours are not enforced", () => {
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(null, null, false))
+ ).toBeTruthy();
+ });
+
+ it("returns false if texting hours are for MISSING TIME ZONE and time is 12:00 EST", () => {
+ MockDate.set("2018-02-01T12:00:00.000-05:00");
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(null, null, true))
+ ).toBeTruthy();
+ });
+
+ it("returns false if texting hours are for MISSING TIME ZONE and time is 11:00 EST", () => {
+ MockDate.set("2018-02-01T11:00:00.000-05:00");
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(null, null, true))
+ ).toBeFalsy();
+ });
+
+ it("returns false if texting hours are for MISSING TIME ZONE and time is 20:00 EST", () => {
+ MockDate.set("2018-02-01T20:00:00.000-05:00");
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(null, null, true))
+ ).toBeTruthy();
+ });
+
+ it("returns false if texting hours are for MISSING TIME ZONE and time is 21:00 EST", () => {
+ MockDate.set("2018-02-01T21:00:00.000-05:00");
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(null, null, true))
+ ).toBeFalsy();
+ });
+});
+
+describe("test isBetweenTextingHours with offset data NOT supplied", () => {
+ var tzHelpers = require("../../src/lib/tz-helpers");
+ beforeAll(() => {
+ tzHelpers.getProcessEnvTz.mockImplementation(() => null);
+ });
+
+ afterEach(() => {
+ MockDate.reset();
+ });
+
+ afterAll(() => {
+ jest.restoreAllMocks();
+ });
+
+ it("returns true if texting hours are not enforced", () => {
+ expect(
+ isBetweenTextingHours(null, makeConfig(null, null, false))
+ ).toBeTruthy();
+ });
+
+ it("returns false if texting hours are for MISSING TIME ZONE and time is 12:00 EST", () => {
+ MockDate.set("2018-02-01T12:00:00.000-05:00");
+ expect(
+ isBetweenTextingHours(null, makeConfig(null, null, true))
+ ).toBeTruthy();
+ });
+
+ it("returns false if texting hours are for MISSING TIME ZONE and time is 11:00 EST", () => {
+ MockDate.set("2018-02-01T11:00:00.000-05:00");
+ expect(
+ isBetweenTextingHours(null, makeConfig(null, null, true))
+ ).toBeFalsy();
+ });
+
+ it("returns false if texting hours are for MISSING TIME ZONE and time is 20:00 EST", () => {
+ MockDate.set("2018-02-01T20:00:00.000-05:00");
+ expect(
+ isBetweenTextingHours(null, makeConfig(null, null, true))
+ ).toBeTruthy();
+ });
+
+ it("returns false if texting hours are for MISSING TIME ZONE and time is 21:00 EST", () => {
+ MockDate.set("2018-02-01T21:00:00.000-05:00");
+ expect(
+ isBetweenTextingHours(null, makeConfig(null, null, true))
+ ).toBeFalsy();
+ });
+});
+
+describe("test defaultTimezoneIsBetweenTextingHours", () => {
+ var tzHelpers = require("../../src/lib/tz-helpers");
+ beforeAll(() => {
+ tzHelpers.getProcessEnvTz.mockImplementation(() => null);
+ jest.doMock("../../src/lib/tz-helpers");
+ });
+
+ afterEach(() => {
+ MockDate.reset();
+ });
+
+ afterAll(() => {
+ jest.restoreAllMocks();
+ });
+
+ it("returns true if texting hours are not enforced", () => {
+ expect(
+ defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, false))
+ ).toBeTruthy();
+ });
+
+ it("returns false if time is 12:00 EST", () => {
+ MockDate.set("2018-02-01T12:00:00.000-05:00");
+ expect(
+ defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, true))
+ ).toBeTruthy();
+ });
+
+ it("returns false if time is 11:00 EST", () => {
+ MockDate.set("2018-02-01T11:00:00.000-05:00");
+ expect(
+ defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, true))
+ ).toBeFalsy();
+ });
+
+ it("returns false if time is 20:00 EST", () => {
+ MockDate.set("2018-02-01T20:00:00.000-05:00");
+ expect(
+ defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, true))
+ ).toBeTruthy();
+ });
+
+ it("returns false if time is 21:00 EST", () => {
+ MockDate.set("2018-02-01T21:00:00.000-05:00");
+ expect(
+ defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, true))
+ ).toBeFalsy();
+ });
+});
+
+describe("test convertOffsetsToStrings", () => {
+ it("works", () => {
+ let test_offsets = [[1, true], [2, false], [-1, true]];
+ let strings_returned = convertOffsetsToStrings(test_offsets);
+ expect(strings_returned).toHaveLength(3);
+ expect(strings_returned[0]).toBe("1_1");
+ expect(strings_returned[1]).toBe("2_0");
+ expect(strings_returned[2]).toBe("-1_1");
+ });
+});
+
+describe("test getOffsets", () => {
+ afterEach(() => {
+ MockDate.reset();
+ });
+
+ it("works during daylight-savings time", () => {
+ MockDate.set("2018-07-21T17:00:00.000Z");
+ let offsets_returned = getOffsets(makeConfig(10, 12, true));
+ expect(offsets_returned).toHaveLength(2);
+
+ let valid_offsets_returned = offsets_returned[0];
+ expect(valid_offsets_returned).toHaveLength(4);
+ expect(valid_offsets_returned[0]).toBe("-7_1");
+ expect(valid_offsets_returned[1]).toBe("-8_1");
+ expect(valid_offsets_returned[2]).toBe("-6_0");
+ expect(valid_offsets_returned[3]).toBe("-7_0");
+
+ let invalid_offsets_returned = offsets_returned[1];
+ expect(invalid_offsets_returned).toHaveLength(14);
+ expect(invalid_offsets_returned[0]).toBe("-4_1");
+ expect(invalid_offsets_returned[1]).toBe("-5_1");
+ expect(invalid_offsets_returned[2]).toBe("-6_1");
+ expect(invalid_offsets_returned[3]).toBe("-9_1");
+ expect(invalid_offsets_returned[4]).toBe("-10_1");
+ expect(invalid_offsets_returned[5]).toBe("-11_1");
+ expect(invalid_offsets_returned[6]).toBe("10_1");
+ expect(invalid_offsets_returned[7]).toBe("-4_0");
+ expect(invalid_offsets_returned[8]).toBe("-5_0");
+ expect(invalid_offsets_returned[9]).toBe("-8_0");
+ expect(invalid_offsets_returned[10]).toBe("-9_0");
+ expect(invalid_offsets_returned[11]).toBe("-10_0");
+ expect(invalid_offsets_returned[12]).toBe("-11_0");
+ expect(invalid_offsets_returned[13]).toBe("10_0");
+ });
+
+ it("works during standard time", () => {
+ MockDate.set("2018-02-01T17:00:00.000Z");
+ let offsets_returned = getOffsets(makeConfig(10, 12, true));
+ expect(offsets_returned).toHaveLength(2);
+
+ let valid_offsets_returned = offsets_returned[0];
+ expect(valid_offsets_returned).toHaveLength(4);
+ expect(valid_offsets_returned[0]).toBe("-6_1");
+ expect(valid_offsets_returned[1]).toBe("-7_1");
+ expect(valid_offsets_returned[2]).toBe("-6_0");
+ expect(valid_offsets_returned[3]).toBe("-7_0");
+
+ let invalid_offsets_returned = offsets_returned[1];
+ expect(invalid_offsets_returned).toHaveLength(14);
+ expect(invalid_offsets_returned[0]).toBe("-4_1");
+ expect(invalid_offsets_returned[1]).toBe("-5_1");
+ expect(invalid_offsets_returned[2]).toBe("-8_1");
+ expect(invalid_offsets_returned[3]).toBe("-9_1");
+ expect(invalid_offsets_returned[4]).toBe("-10_1");
+ expect(invalid_offsets_returned[5]).toBe("-11_1");
+ expect(invalid_offsets_returned[6]).toBe("10_1");
+ expect(invalid_offsets_returned[7]).toBe("-4_0");
+ expect(invalid_offsets_returned[8]).toBe("-5_0");
+ expect(invalid_offsets_returned[9]).toBe("-8_0");
+ expect(invalid_offsets_returned[10]).toBe("-9_0");
+ expect(invalid_offsets_returned[11]).toBe("-10_0");
+ expect(invalid_offsets_returned[12]).toBe("-11_0");
+ expect(invalid_offsets_returned[13]).toBe("10_0");
+ });
+});
+
+describe("test getContactTimezone", () => {
+ var tzHelpers = require("../../src/lib/tz-helpers");
afterEach(() => {
- jest.resetAllMocks()
- })
+ jest.resetAllMocks();
+ });
- it('returns the location if one is supplied', () => {
- let location = makeLocationWithOnlyTimezoneData(7, true)
- expect(getContactTimezone({}, location)).toEqual(location)
+ it("returns the location if one is supplied", () => {
+ let location = makeLocationWithOnlyTimezoneData(7, true);
+ expect(getContactTimezone({}, location)).toEqual(location);
- location = makeLocationWithOnlyTimezoneData(9, false)
- expect(getContactTimezone({}, location)).toEqual(location)
- })
+ location = makeLocationWithOnlyTimezoneData(9, false);
+ expect(getContactTimezone({}, location)).toEqual(location);
+ });
- it('uses campaign.timezone if no location is supplied and the campaign overrides', () => {
+ it("uses campaign.timezone if no location is supplied and the campaign overrides", () => {
expect(
getContactTimezone(
makeCampaignOnlyWithTextingHoursConfigFields(
@@ -520,7 +664,7 @@ describe('test getContactTimezone', () => {
true,
14,
16,
- 'America/New_York'
+ "America/New_York"
),
{}
)
@@ -529,286 +673,309 @@ describe('test getContactTimezone', () => {
offset: -5,
hasDST: true
}
- })
- })
-
-})
-
-describe('test isBetweenTextingHours with offset data supplied', () => {
- var offsetData = {offset: -8, hasDST: true}
- var tzHelpers = require('../../src/lib/tz-helpers')
- beforeAll(() => {
- jest.doMock('../../src/lib/tz-helpers')
- tzHelpers.getProcessEnvTz.mockImplementation(() => null)
- MockDate.set('2018-02-01T12:00:00.000-08:00')
- })
-
- afterAll(() => {
- jest.restoreAllMocks();
- MockDate.reset()
- })
-
- it('returns true if texting hours are not enforced', () => {
- expect(isBetweenTextingHours(offsetData, makeConfig(null, null, false))).toBeTruthy()
- })
-
- it('returns false if texting hours are 05-07 and time is 12:00', () => {
- expect(isBetweenTextingHours(offsetData, makeConfig(5, 7, true))).toBeFalsy()
- }
- )
+ });
+ });
+});
- it('returns false if texting hours are 14-21 and time is 12:00', () => {
- expect(isBetweenTextingHours(offsetData, makeConfig(14, 21, true))).toBeFalsy()
- }
- )
+describe("test isBetweenTextingHours with offset data supplied", () => {
+ var offsetData = { offset: -8, hasDST: true };
+ var tzHelpers = require("../../src/lib/tz-helpers");
+ beforeAll(() => {
+ jest.doMock("../../src/lib/tz-helpers");
+ tzHelpers.getProcessEnvTz.mockImplementation(() => null);
+ MockDate.set("2018-02-01T12:00:00.000-08:00");
+ });
- it('returns true if texting hours are 10-21 and time is 12:00', () => {
- expect(isBetweenTextingHours(offsetData, makeConfig(10, 21, true))).toBeTruthy()
- })
-
- it('returns true if texting hours are 12-21 and time is 12:00', () => {
- expect(isBetweenTextingHours(offsetData, makeConfig(12, 21, true))).toBeTruthy()
- })
-
- it('returns true if texting hours are 10-12 and time is 12:00', () => {
- expect(isBetweenTextingHours(offsetData, makeConfig(10, 12, true))).toBeFalsy()
- })
-
- it('returns true if texting hours are 10-11 and time is 12:00', () => {
- expect(isBetweenTextingHours(offsetData, makeConfig(10, 13, true))).toBeTruthy()
- })
- }
-)
-
-describe('test isBetweenTextingHours with offset data empty', () => {
- var offsetData = {offset: null, hasDST: null}
- var tzHelpers = require('../../src/lib/tz-helpers')
- beforeAll(() => {
- tzHelpers.getProcessEnvTz.mockImplementation(() => null)
- })
-
- afterEach(() => {
- MockDate.reset()
- })
-
- afterAll(() => {
- jest.restoreAllMocks();
- })
-
- it('returns true if texting hours are not enforced', () => {
- expect(isBetweenTextingHours(offsetData, makeConfig(null, null, false))).toBeTruthy()
- })
-
- it('returns false if texting hours are for MISSING TIME ZONE and time is 12:00 EST', () => {
- MockDate.set('2018-02-01T12:00:00.000-05:00')
- expect(isBetweenTextingHours(offsetData, makeConfig(null, null, true))).toBeTruthy()
- }
- )
+ afterAll(() => {
+ jest.restoreAllMocks();
+ MockDate.reset();
+ });
- it('returns false if texting hours are for MISSING TIME ZONE and time is 11:00 EST', () => {
- MockDate.set('2018-02-01T11:00:00.000-05:00')
- expect(isBetweenTextingHours(offsetData, makeConfig(null, null, true))).toBeFalsy()
- }
- )
+ it("returns true if texting hours are not enforced", () => {
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(null, null, false))
+ ).toBeTruthy();
+ });
- it('returns false if texting hours are for MISSING TIME ZONE and time is 20:00 EST', () => {
- MockDate.set('2018-02-01T20:00:00.000-05:00')
- expect(isBetweenTextingHours(offsetData, makeConfig(null, null, true))).toBeTruthy()
- }
- )
+ it("returns false if texting hours are 05-07 and time is 12:00", () => {
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(5, 7, true))
+ ).toBeFalsy();
+ });
- it('returns false if texting hours are for MISSING TIME ZONE and time is 21:00 EST', () => {
- MockDate.set('2018-02-01T21:00:00.000-05:00')
- expect(isBetweenTextingHours(offsetData, makeConfig(null, null, true))).toBeFalsy()
- }
- )
- }
-)
-
-describe('test isBetweenTextingHours with offset data NOT supplied', () => {
- var tzHelpers = require('../../src/lib/tz-helpers')
- beforeAll(() => {
- tzHelpers.getProcessEnvTz.mockImplementation(() => null)
- })
-
- afterEach(() => {
- MockDate.reset()
- })
-
- afterAll(() => {
- jest.restoreAllMocks();
- })
-
- it('returns true if texting hours are not enforced', () => {
- expect(isBetweenTextingHours(null, makeConfig(null, null, false))).toBeTruthy()
- })
-
- it('returns false if texting hours are for MISSING TIME ZONE and time is 12:00 EST', () => {
- MockDate.set('2018-02-01T12:00:00.000-05:00')
- expect(isBetweenTextingHours(null, makeConfig(null, null, true))).toBeTruthy()
- }
- )
+ it("returns false if texting hours are 14-21 and time is 12:00", () => {
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(14, 21, true))
+ ).toBeFalsy();
+ });
- it('returns false if texting hours are for MISSING TIME ZONE and time is 11:00 EST', () => {
- MockDate.set('2018-02-01T11:00:00.000-05:00')
- expect(isBetweenTextingHours(null, makeConfig(null, null, true))).toBeFalsy()
- }
- )
+ it("returns true if texting hours are 10-21 and time is 12:00", () => {
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(10, 21, true))
+ ).toBeTruthy();
+ });
- it('returns false if texting hours are for MISSING TIME ZONE and time is 20:00 EST', () => {
- MockDate.set('2018-02-01T20:00:00.000-05:00')
- expect(isBetweenTextingHours(null, makeConfig(null, null, true))).toBeTruthy()
- }
- )
+ it("returns true if texting hours are 12-21 and time is 12:00", () => {
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(12, 21, true))
+ ).toBeTruthy();
+ });
- it('returns false if texting hours are for MISSING TIME ZONE and time is 21:00 EST', () => {
- MockDate.set('2018-02-01T21:00:00.000-05:00')
- expect(isBetweenTextingHours(null, makeConfig(null, null, true))).toBeFalsy()
- }
- )
- }
-)
+ it("returns true if texting hours are 10-12 and time is 12:00", () => {
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(10, 12, true))
+ ).toBeFalsy();
+ });
+ it("returns true if texting hours are 10-11 and time is 12:00", () => {
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(10, 13, true))
+ ).toBeTruthy();
+ });
+});
+
+describe("test isBetweenTextingHours with offset data empty", () => {
+ var offsetData = { offset: null, hasDST: null };
+ var tzHelpers = require("../../src/lib/tz-helpers");
+ beforeAll(() => {
+ tzHelpers.getProcessEnvTz.mockImplementation(() => null);
+ });
-describe('test defaultTimezoneIsBetweenTextingHours', () => {
- var tzHelpers = require('../../src/lib/tz-helpers')
- beforeAll(() => {
- tzHelpers.getProcessEnvTz.mockImplementation(() => null)
- jest.doMock('../../src/lib/tz-helpers')
- })
+ afterEach(() => {
+ MockDate.reset();
+ });
- afterEach(() => {
- MockDate.reset()
- })
+ afterAll(() => {
+ jest.restoreAllMocks();
+ });
- afterAll(() => {
- jest.restoreAllMocks();
- })
+ it("returns true if texting hours are not enforced", () => {
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(null, null, false))
+ ).toBeTruthy();
+ });
- it('returns true if texting hours are not enforced', () => {
- expect(defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, false))).toBeTruthy()
- })
+ it("returns false if texting hours are for MISSING TIME ZONE and time is 12:00 EST", () => {
+ MockDate.set("2018-02-01T12:00:00.000-05:00");
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(null, null, true))
+ ).toBeTruthy();
+ });
- it('returns false if time is 12:00 EST', () => {
- MockDate.set('2018-02-01T12:00:00.000-05:00')
- expect(defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, true))).toBeTruthy()
- }
- )
+ it("returns false if texting hours are for MISSING TIME ZONE and time is 11:00 EST", () => {
+ MockDate.set("2018-02-01T11:00:00.000-05:00");
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(null, null, true))
+ ).toBeFalsy();
+ });
- it('returns false if time is 11:00 EST', () => {
- MockDate.set('2018-02-01T11:00:00.000-05:00')
- expect(defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, true))).toBeFalsy()
- }
- )
+ it("returns false if texting hours are for MISSING TIME ZONE and time is 20:00 EST", () => {
+ MockDate.set("2018-02-01T20:00:00.000-05:00");
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(null, null, true))
+ ).toBeTruthy();
+ });
- it('returns false if time is 20:00 EST', () => {
- MockDate.set('2018-02-01T20:00:00.000-05:00')
- expect(defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, true))).toBeTruthy()
- }
- )
+ it("returns false if texting hours are for MISSING TIME ZONE and time is 21:00 EST", () => {
+ MockDate.set("2018-02-01T21:00:00.000-05:00");
+ expect(
+ isBetweenTextingHours(offsetData, makeConfig(null, null, true))
+ ).toBeFalsy();
+ });
+});
+
+describe("test isBetweenTextingHours with offset data NOT supplied", () => {
+ var tzHelpers = require("../../src/lib/tz-helpers");
+ beforeAll(() => {
+ tzHelpers.getProcessEnvTz.mockImplementation(() => null);
+ });
- it('returns false if time is 21:00 EST', () => {
- MockDate.set('2018-02-01T21:00:00.000-05:00')
- expect(defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, true))).toBeFalsy()
- }
- )
- }
-)
-
-describe('test convertOffsetsToStrings', () => {
- it('works', () => {
- let test_offsets = [[1, true], [2, false], [-1, true]]
- let strings_returned = convertOffsetsToStrings(test_offsets)
- expect(strings_returned).toHaveLength(3)
- expect(strings_returned[0]).toBe('1_1')
- expect(strings_returned[1]).toBe('2_0')
- expect(strings_returned[2]).toBe('-1_1')
- }
- )
-})
-
-describe('test getOffsets', () => {
afterEach(() => {
- MockDate.reset()
- })
-
- it('works during daylight-savings time', () => {
- MockDate.set('2018-07-21T17:00:00.000Z')
- let offsets_returned = getOffsets(makeConfig(10, 12, true))
- expect(offsets_returned).toHaveLength(2)
-
- let valid_offsets_returned = offsets_returned[0]
- expect(valid_offsets_returned).toHaveLength(4)
- expect(valid_offsets_returned[0]).toBe('-7_1')
- expect(valid_offsets_returned[1]).toBe('-8_1')
- expect(valid_offsets_returned[2]).toBe('-6_0')
- expect(valid_offsets_returned[3]).toBe('-7_0')
-
- let invalid_offsets_returned = offsets_returned[1]
- expect(invalid_offsets_returned).toHaveLength(14)
- expect(invalid_offsets_returned[0]).toBe('-4_1')
- expect(invalid_offsets_returned[1]).toBe('-5_1')
- expect(invalid_offsets_returned[2]).toBe('-6_1')
- expect(invalid_offsets_returned[3]).toBe('-9_1')
- expect(invalid_offsets_returned[4]).toBe('-10_1')
- expect(invalid_offsets_returned[5]).toBe('-11_1')
- expect(invalid_offsets_returned[6]).toBe('10_1')
- expect(invalid_offsets_returned[7]).toBe('-4_0')
- expect(invalid_offsets_returned[8]).toBe('-5_0')
- expect(invalid_offsets_returned[9]).toBe('-8_0')
- expect(invalid_offsets_returned[10]).toBe('-9_0')
- expect(invalid_offsets_returned[11]).toBe('-10_0')
- expect(invalid_offsets_returned[12]).toBe('-11_0')
- expect(invalid_offsets_returned[13]).toBe('10_0')
- })
-
- it('works during standard time', () => {
- MockDate.set('2018-02-01T17:00:00.000Z')
- let offsets_returned = getOffsets(makeConfig(10, 12, true))
- expect(offsets_returned).toHaveLength(2)
-
- let valid_offsets_returned = offsets_returned[0]
- expect(valid_offsets_returned).toHaveLength(4)
- expect(valid_offsets_returned[0]).toBe('-6_1')
- expect(valid_offsets_returned[1]).toBe('-7_1')
- expect(valid_offsets_returned[2]).toBe('-6_0')
- expect(valid_offsets_returned[3]).toBe('-7_0')
-
- let invalid_offsets_returned = offsets_returned[1]
- expect(invalid_offsets_returned).toHaveLength(14)
- expect(invalid_offsets_returned[0]).toBe('-4_1')
- expect(invalid_offsets_returned[1]).toBe('-5_1')
- expect(invalid_offsets_returned[2]).toBe('-8_1')
- expect(invalid_offsets_returned[3]).toBe('-9_1')
- expect(invalid_offsets_returned[4]).toBe('-10_1')
- expect(invalid_offsets_returned[5]).toBe('-11_1')
- expect(invalid_offsets_returned[6]).toBe('10_1')
- expect(invalid_offsets_returned[7]).toBe('-4_0')
- expect(invalid_offsets_returned[8]).toBe('-5_0')
- expect(invalid_offsets_returned[9]).toBe('-8_0')
- expect(invalid_offsets_returned[10]).toBe('-9_0')
- expect(invalid_offsets_returned[11]).toBe('-10_0')
- expect(invalid_offsets_returned[12]).toBe('-11_0')
- expect(invalid_offsets_returned[13]).toBe('10_0')
- })
-})
-
-describe('test getContactTimezone', () => {
- var tzHelpers = require('../../src/lib/tz-helpers')
+ MockDate.reset();
+ });
+ afterAll(() => {
+ jest.restoreAllMocks();
+ });
+
+ it("returns true if texting hours are not enforced", () => {
+ expect(
+ isBetweenTextingHours(null, makeConfig(null, null, false))
+ ).toBeTruthy();
+ });
+
+ it("returns false if texting hours are for MISSING TIME ZONE and time is 12:00 EST", () => {
+ MockDate.set("2018-02-01T12:00:00.000-05:00");
+ expect(
+ isBetweenTextingHours(null, makeConfig(null, null, true))
+ ).toBeTruthy();
+ });
+
+ it("returns false if texting hours are for MISSING TIME ZONE and time is 11:00 EST", () => {
+ MockDate.set("2018-02-01T11:00:00.000-05:00");
+ expect(
+ isBetweenTextingHours(null, makeConfig(null, null, true))
+ ).toBeFalsy();
+ });
+
+ it("returns false if texting hours are for MISSING TIME ZONE and time is 20:00 EST", () => {
+ MockDate.set("2018-02-01T20:00:00.000-05:00");
+ expect(
+ isBetweenTextingHours(null, makeConfig(null, null, true))
+ ).toBeTruthy();
+ });
+
+ it("returns false if texting hours are for MISSING TIME ZONE and time is 21:00 EST", () => {
+ MockDate.set("2018-02-01T21:00:00.000-05:00");
+ expect(
+ isBetweenTextingHours(null, makeConfig(null, null, true))
+ ).toBeFalsy();
+ });
+});
+
+describe("test defaultTimezoneIsBetweenTextingHours", () => {
+ var tzHelpers = require("../../src/lib/tz-helpers");
+ beforeAll(() => {
+ tzHelpers.getProcessEnvTz.mockImplementation(() => null);
+ jest.doMock("../../src/lib/tz-helpers");
+ });
+
+ afterEach(() => {
+ MockDate.reset();
+ });
+
+ afterAll(() => {
+ jest.restoreAllMocks();
+ });
+
+ it("returns true if texting hours are not enforced", () => {
+ expect(
+ defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, false))
+ ).toBeTruthy();
+ });
+
+ it("returns false if time is 12:00 EST", () => {
+ MockDate.set("2018-02-01T12:00:00.000-05:00");
+ expect(
+ defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, true))
+ ).toBeTruthy();
+ });
+
+ it("returns false if time is 11:00 EST", () => {
+ MockDate.set("2018-02-01T11:00:00.000-05:00");
+ expect(
+ defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, true))
+ ).toBeFalsy();
+ });
+
+ it("returns false if time is 20:00 EST", () => {
+ MockDate.set("2018-02-01T20:00:00.000-05:00");
+ expect(
+ defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, true))
+ ).toBeTruthy();
+ });
+
+ it("returns false if time is 21:00 EST", () => {
+ MockDate.set("2018-02-01T21:00:00.000-05:00");
+ expect(
+ defaultTimezoneIsBetweenTextingHours(makeConfig(null, null, true))
+ ).toBeFalsy();
+ });
+});
+
+describe("test convertOffsetsToStrings", () => {
+ it("works", () => {
+ let test_offsets = [[1, true], [2, false], [-1, true]];
+ let strings_returned = convertOffsetsToStrings(test_offsets);
+ expect(strings_returned).toHaveLength(3);
+ expect(strings_returned[0]).toBe("1_1");
+ expect(strings_returned[1]).toBe("2_0");
+ expect(strings_returned[2]).toBe("-1_1");
+ });
+});
+
+describe("test getOffsets", () => {
afterEach(() => {
- jest.resetAllMocks()
- })
+ MockDate.reset();
+ });
+
+ it("works during daylight-savings time", () => {
+ MockDate.set("2018-07-21T17:00:00.000Z");
+ let offsets_returned = getOffsets(makeConfig(10, 12, true));
+ expect(offsets_returned).toHaveLength(2);
+
+ let valid_offsets_returned = offsets_returned[0];
+ expect(valid_offsets_returned).toHaveLength(4);
+ expect(valid_offsets_returned[0]).toBe("-7_1");
+ expect(valid_offsets_returned[1]).toBe("-8_1");
+ expect(valid_offsets_returned[2]).toBe("-6_0");
+ expect(valid_offsets_returned[3]).toBe("-7_0");
+
+ let invalid_offsets_returned = offsets_returned[1];
+ expect(invalid_offsets_returned).toHaveLength(14);
+ expect(invalid_offsets_returned[0]).toBe("-4_1");
+ expect(invalid_offsets_returned[1]).toBe("-5_1");
+ expect(invalid_offsets_returned[2]).toBe("-6_1");
+ expect(invalid_offsets_returned[3]).toBe("-9_1");
+ expect(invalid_offsets_returned[4]).toBe("-10_1");
+ expect(invalid_offsets_returned[5]).toBe("-11_1");
+ expect(invalid_offsets_returned[6]).toBe("10_1");
+ expect(invalid_offsets_returned[7]).toBe("-4_0");
+ expect(invalid_offsets_returned[8]).toBe("-5_0");
+ expect(invalid_offsets_returned[9]).toBe("-8_0");
+ expect(invalid_offsets_returned[10]).toBe("-9_0");
+ expect(invalid_offsets_returned[11]).toBe("-10_0");
+ expect(invalid_offsets_returned[12]).toBe("-11_0");
+ expect(invalid_offsets_returned[13]).toBe("10_0");
+ });
+
+ it("works during standard time", () => {
+ MockDate.set("2018-02-01T17:00:00.000Z");
+ let offsets_returned = getOffsets(makeConfig(10, 12, true));
+ expect(offsets_returned).toHaveLength(2);
+
+ let valid_offsets_returned = offsets_returned[0];
+ expect(valid_offsets_returned).toHaveLength(4);
+ expect(valid_offsets_returned[0]).toBe("-6_1");
+ expect(valid_offsets_returned[1]).toBe("-7_1");
+ expect(valid_offsets_returned[2]).toBe("-6_0");
+ expect(valid_offsets_returned[3]).toBe("-7_0");
+
+ let invalid_offsets_returned = offsets_returned[1];
+ expect(invalid_offsets_returned).toHaveLength(14);
+ expect(invalid_offsets_returned[0]).toBe("-4_1");
+ expect(invalid_offsets_returned[1]).toBe("-5_1");
+ expect(invalid_offsets_returned[2]).toBe("-8_1");
+ expect(invalid_offsets_returned[3]).toBe("-9_1");
+ expect(invalid_offsets_returned[4]).toBe("-10_1");
+ expect(invalid_offsets_returned[5]).toBe("-11_1");
+ expect(invalid_offsets_returned[6]).toBe("10_1");
+ expect(invalid_offsets_returned[7]).toBe("-4_0");
+ expect(invalid_offsets_returned[8]).toBe("-5_0");
+ expect(invalid_offsets_returned[9]).toBe("-8_0");
+ expect(invalid_offsets_returned[10]).toBe("-9_0");
+ expect(invalid_offsets_returned[11]).toBe("-10_0");
+ expect(invalid_offsets_returned[12]).toBe("-11_0");
+ expect(invalid_offsets_returned[13]).toBe("10_0");
+ });
+});
+
+describe("test getContactTimezone", () => {
+ var tzHelpers = require("../../src/lib/tz-helpers");
- it('returns the location if one is supplied', () => {
- let location = makeLocationWithOnlyTimezoneData(7, true)
- expect(getContactTimezone({}, location)).toEqual(location)
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
- location = makeLocationWithOnlyTimezoneData(9, false)
- expect(getContactTimezone({}, location)).toEqual(location)
- })
+ it("returns the location if one is supplied", () => {
+ let location = makeLocationWithOnlyTimezoneData(7, true);
+ expect(getContactTimezone({}, location)).toEqual(location);
- it('uses campaign.timezone if no location is supplied and the campaign overrides', () => {
+ location = makeLocationWithOnlyTimezoneData(9, false);
+ expect(getContactTimezone({}, location)).toEqual(location);
+ });
+
+ it("uses campaign.timezone if no location is supplied and the campaign overrides", () => {
expect(
getContactTimezone(
makeCampaignOnlyWithTextingHoursConfigFields(
@@ -816,7 +983,7 @@ describe('test getContactTimezone', () => {
true,
14,
16,
- 'America/New_York'
+ "America/New_York"
),
{}
)
@@ -825,11 +992,11 @@ describe('test getContactTimezone', () => {
offset: -5,
hasDST: true
}
- })
- })
+ });
+ });
it("uses TZ if no location is supplied, and the campaign doesn't override, and TZ exists in the environment", () => {
- tzHelpers.getProcessEnvTz.mockImplementation(() => 'America/Boise')
+ tzHelpers.getProcessEnvTz.mockImplementation(() => "America/Boise");
expect(
getContactTimezone(
makeCampaignOnlyWithTextingHoursConfigFields(
@@ -837,7 +1004,7 @@ describe('test getContactTimezone', () => {
true,
14,
16,
- 'America/New_York'
+ "America/New_York"
),
{}
)
@@ -846,8 +1013,8 @@ describe('test getContactTimezone', () => {
offset: -7,
hasDST: true
}
- })
- })
+ });
+ });
it("uses TIMEZONE_CONFIG.missingTimeZone if no location is supplied, and the campaign doesn't override, and TZ is not in the environment", () => {
expect(
@@ -857,7 +1024,7 @@ describe('test getContactTimezone', () => {
true,
14,
16,
- 'America/New_York'
+ "America/New_York"
),
{}
)
@@ -866,152 +1033,223 @@ describe('test getContactTimezone', () => {
offset: -5,
hasDST: true
}
- })
- })
-})
-
+ });
+ });
+});
-describe('test getUtcFromOffsetAndHour', () => {
+describe("test getUtcFromOffsetAndHour", () => {
afterEach(() => {
- MockDate.reset()
- })
-
- it('returns the correct UTC during northern hemisphere summer', () => {
- MockDate.set('2018-07-01T11:00:00.000-05:00')
- expect(getUtcFromOffsetAndHour(-5, true, 12, 'America/New_York').unix()).toEqual(moment('2018-07-01T16:00:00.000Z').unix())
- })
+ MockDate.reset();
+ });
- it('returns the correct UTC during northern hemisphere summer with result being next day', () => {
- MockDate.set('2018-07-01T11:00:00.000-05:00')
- expect(getUtcFromOffsetAndHour(-5, true, 23, 'America/New_York').unix()).toEqual(moment('2018-07-02T03:00:00.000Z').unix())
- })
+ it("returns the correct UTC during northern hemisphere summer", () => {
+ MockDate.set("2018-07-01T11:00:00.000-05:00");
+ expect(
+ getUtcFromOffsetAndHour(-5, true, 12, "America/New_York").unix()
+ ).toEqual(moment("2018-07-01T16:00:00.000Z").unix());
+ });
- it('returns the correct UTC during northern hemisphere winter', () => {
- MockDate.set('2018-02-01T11:00:00.000-05:00')
- expect(getUtcFromOffsetAndHour(-5, true, 12, 'America/New_York').unix()).toEqual(moment('2018-02-01T17:00:00.000Z').unix())
+ it("returns the correct UTC during northern hemisphere summer with result being next day", () => {
+ MockDate.set("2018-07-01T11:00:00.000-05:00");
+ expect(
+ getUtcFromOffsetAndHour(-5, true, 23, "America/New_York").unix()
+ ).toEqual(moment("2018-07-02T03:00:00.000Z").unix());
+ });
- })
+ it("returns the correct UTC during northern hemisphere winter", () => {
+ MockDate.set("2018-02-01T11:00:00.000-05:00");
+ expect(
+ getUtcFromOffsetAndHour(-5, true, 12, "America/New_York").unix()
+ ).toEqual(moment("2018-02-01T17:00:00.000Z").unix());
+ });
- it('returns the correct UTC during northern hemisphere summer if offset doesn\'t have DST', () => {
- MockDate.set('2018-07-01T11:00:00.000-05:00')
- expect(getUtcFromOffsetAndHour(-5, false, 12, 'America/New_York').unix()).toEqual(moment('2018-07-01T17:00:00.000Z').unix())
- })
+ it("returns the correct UTC during northern hemisphere summer if offset doesn't have DST", () => {
+ MockDate.set("2018-07-01T11:00:00.000-05:00");
+ expect(
+ getUtcFromOffsetAndHour(-5, false, 12, "America/New_York").unix()
+ ).toEqual(moment("2018-07-01T17:00:00.000Z").unix());
+ });
- it('returns the correct UTC during northern hemisphere winter if offset doesn\'t have DST', () => {
- MockDate.set('2018-02-01T11:00:00.000-05:00')
- expect(getUtcFromOffsetAndHour(-5, false, 12, 'America/New_York').unix()).toEqual(moment('2018-02-01T17:00:00.000Z').unix())
- })
-})
+ it("returns the correct UTC during northern hemisphere winter if offset doesn't have DST", () => {
+ MockDate.set("2018-02-01T11:00:00.000-05:00");
+ expect(
+ getUtcFromOffsetAndHour(-5, false, 12, "America/New_York").unix()
+ ).toEqual(moment("2018-02-01T17:00:00.000Z").unix());
+ });
+});
-describe('test getUtcFromTimezoneAndHour', () => {
+describe("test getUtcFromTimezoneAndHour", () => {
afterEach(() => {
- MockDate.reset()
- })
+ MockDate.reset();
+ });
+
+ it("returns the correct UTC during northern hemisphere summer", () => {
+ MockDate.set("2018-07-01T11:00:00.000-05:00");
+ expect(getUtcFromTimezoneAndHour("America/New_York", 12).unix()).toEqual(
+ moment("2018-07-01T16:00:00.000Z").unix()
+ );
+ });
+
+ it("returns the correct UTC during northern hemisphere summer with result being next day", () => {
+ MockDate.set("2018-07-01T11:00:00.000-05:00");
+ expect(getUtcFromTimezoneAndHour("America/New_York", 23).unix()).toEqual(
+ moment("2018-07-02T03:00:00.000Z").unix()
+ );
+ });
+
+ it("returns the correct UTC during northern hemisphere winter", () => {
+ MockDate.set("2018-02-01T11:00:00.000-05:00");
+ expect(getUtcFromTimezoneAndHour("America/New_York", 12).unix()).toEqual(
+ moment("2018-02-01T17:00:00.000Z").unix()
+ );
+ });
+
+ it("returns the correct UTC during northern hemisphere summer if timezone doesn't have DST", () => {
+ MockDate.set("2018-07-01T11:00:00.000-05:00");
+ expect(getUtcFromTimezoneAndHour("US/Arizona", 12).unix()).toEqual(
+ moment("2018-07-01T19:00:00.000Z").unix()
+ );
+ });
+
+ it("returns the correct UTC during northern hemisphere winter if timezone doesn't have DST", () => {
+ MockDate.set("2018-02-01T11:00:00.000-05:00");
+ expect(getUtcFromTimezoneAndHour("US/Arizona", 12).unix()).toEqual(
+ moment("2018-02-01T19:00:00.000Z").unix()
+ );
+ });
+});
+
+describe("test getSendBeforeTimewUtc", () => {
+ const tzHelpers = require("../../src/lib/tz-helpers");
- it('returns the correct UTC during northern hemisphere summer', () => {
- MockDate.set('2018-07-01T11:00:00.000-05:00')
- expect(getUtcFromTimezoneAndHour('America/New_York', 12).unix()).toEqual(moment('2018-07-01T16:00:00.000Z').unix())
- })
-
- it('returns the correct UTC during northern hemisphere summer with result being next day', () => {
- MockDate.set('2018-07-01T11:00:00.000-05:00')
- expect(getUtcFromTimezoneAndHour('America/New_York', 23).unix()).toEqual(moment('2018-07-02T03:00:00.000Z').unix())
- })
+ beforeAll(() => {
+ MockDate.set("2018-09-03T11:00:00.000-05:00");
+ });
- it('returns the correct UTC during northern hemisphere winter', () => {
- MockDate.set('2018-02-01T11:00:00.000-05:00')
- expect(getUtcFromTimezoneAndHour('America/New_York', 12).unix()).toEqual(moment('2018-02-01T17:00:00.000Z').unix())
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
- })
+ afterAll(() => {
+ MockDate.reset();
+ });
- it('returns the correct UTC during northern hemisphere summer if timezone doesn\'t have DST', () => {
- MockDate.set('2018-07-01T11:00:00.000-05:00')
- expect(getUtcFromTimezoneAndHour('US/Arizona', 12).unix()).toEqual(moment('2018-07-01T19:00:00.000Z').unix())
- })
+ it("returns undefined if campaign overrides and texting hours are not enforced", () => {
+ expect(
+ getSendBeforeTimeUtc(
+ {},
+ {},
+ { overrideOrganizationTextingHours: true, textingHoursEnforced: false }
+ )
+ ).toBeNull();
+ });
- it('returns the correct UTC during northern hemisphere winter if timezone doesn\'t have DST', () => {
- MockDate.set('2018-02-01T11:00:00.000-05:00')
- expect(getUtcFromTimezoneAndHour('US/Arizona', 12).unix()).toEqual(moment('2018-02-01T19:00:00.000Z').unix())
- })
-})
+ it("returns undefined if campaign does not override and texting hours are not enforced", () => {
+ expect(
+ getSendBeforeTimeUtc(
+ {},
+ {
+ textingHoursStart: 9,
+ textingHoursEnd: 21,
+ textingHoursEnforced: false
+ },
+ {}
+ )
+ ).toBeNull();
+ });
-describe('test getSendBeforeTimewUtc', () => {
- const tzHelpers = require('../../src/lib/tz-helpers')
+ it("returns correct time if campaign overrides and contact offset is supplied", () => {
+ expect(
+ getSendBeforeTimeUtc(
+ { offset: -5, hasDST: 1 },
+ {
+ textingHoursStart: 9,
+ textingHoursEnd: 21,
+ textingHoursEnforced: true
+ },
+ {
+ overrideOrganizationTextingHours: true,
+ textingHoursEnforced: true,
+ textingHoursEnd: 21,
+ timezone: "America/New_York"
+ }
+ ).unix()
+ ).toEqual(moment("2018-09-04T01:00:00.000Z").unix());
+ });
+
+ it("returns correct time if campaign overrides and contact offset is not supplied", () => {
+ expect(
+ getSendBeforeTimeUtc(
+ {},
+ {
+ textingHoursStart: 9,
+ textingHoursEnd: 21,
+ textingHoursEnforced: true
+ },
+ {
+ overrideOrganizationTextingHours: true,
+ textingHoursEnforced: true,
+ textingHoursEnd: 21,
+ timezone: "America/New_York"
+ }
+ ).unix()
+ ).toEqual(moment("2018-09-04T01:00:00.000Z").unix());
+ });
+
+ it("returns correct time if campaign does not override and TZ is set", () => {
+ tzHelpers.getProcessEnvTz.mockImplementation(() => "America/New_York");
+ expect(
+ getSendBeforeTimeUtc(
+ {},
+ {
+ textingHoursStart: 9,
+ textingHoursEnd: 21,
+ textingHoursEnforced: true
+ },
+ {}
+ ).unix()
+ ).toEqual(moment("2018-09-04T01:00:00.000Z").unix());
+ });
- beforeAll(() => {
- MockDate.set('2018-09-03T11:00:00.000-05:00')
- })
+ it("returns correct time if campaign does not override and TZ is not set and contact offset is supplied", () => {
+ expect(
+ getSendBeforeTimeUtc(
+ { offset: -5, hasDST: 1 },
+ {
+ textingHoursStart: 9,
+ textingHoursEnd: 21,
+ textingHoursEnforced: true
+ },
+ {}
+ ).unix()
+ ).toEqual(moment("2018-09-04T01:00:00.000Z").unix());
+ });
- afterEach(() => {
- jest.restoreAllMocks();
- })
+ it("returns correct time if campaign does not override and TZ is not set and contact offset is not supplied", () => {
+ expect(
+ getSendBeforeTimeUtc(
+ {},
+ {
+ textingHoursStart: 9,
+ textingHoursEnd: 21,
+ textingHoursEnforced: true
+ },
+ {}
+ ).unix()
+ ).toEqual(moment("2018-09-04T01:00:00.000Z").unix());
+ });
- afterAll(() => {
- MockDate.reset()
- })
-
- it('returns undefined if campaign overrides and texting hours are not enforced', () => {
- expect(getSendBeforeTimeUtc(
- {},
- {},
- { overrideOrganizationTextingHours: true, textingHoursEnforced: false}
- )).toBeNull()
- })
-
- it('returns undefined if campaign does not override and texting hours are not enforced', () => {
- expect(getSendBeforeTimeUtc(
- {},
- { textingHoursStart: 9, textingHoursEnd: 21, textingHoursEnforced: false},
- {}
- )).toBeNull()
- })
-
- it('returns correct time if campaign overrides and contact offset is supplied', () => {
- expect(getSendBeforeTimeUtc(
- { offset: -5, hasDST: 1 },
- { textingHoursStart: 9, textingHoursEnd: 21, textingHoursEnforced: true },
- { overrideOrganizationTextingHours: true, textingHoursEnforced: true, textingHoursEnd: 21, timezone: 'America/New_York'}
- ).unix()).toEqual(moment('2018-09-04T01:00:00.000Z').unix())
- })
-
- it('returns correct time if campaign overrides and contact offset is not supplied', () => {
- expect(getSendBeforeTimeUtc(
- {},
- { textingHoursStart: 9, textingHoursEnd: 21, textingHoursEnforced: true },
- { overrideOrganizationTextingHours: true, textingHoursEnforced: true, textingHoursEnd: 21, timezone: 'America/New_York'}
- ).unix()).toEqual(moment('2018-09-04T01:00:00.000Z').unix())
- })
-
- it('returns correct time if campaign does not override and TZ is set', () => {
- tzHelpers.getProcessEnvTz.mockImplementation(() => 'America/New_York')
- expect(getSendBeforeTimeUtc(
- {},
- { textingHoursStart: 9, textingHoursEnd: 21, textingHoursEnforced: true },
- {}
- ).unix()).toEqual(moment('2018-09-04T01:00:00.000Z').unix())
- })
-
- it('returns correct time if campaign does not override and TZ is not set and contact offset is supplied', () => {
- expect(getSendBeforeTimeUtc(
- { offset: -5, hasDST: 1 },
- { textingHoursStart: 9, textingHoursEnd: 21, textingHoursEnforced: true },
- {}
- ).unix()).toEqual(moment('2018-09-04T01:00:00.000Z').unix())
- })
-
- it('returns correct time if campaign does not override and TZ is not set and contact offset is not supplied', () => {
- expect(getSendBeforeTimeUtc(
- {},
- { textingHoursStart: 9, textingHoursEnd: 21, textingHoursEnforced: true },
- {}
- ).unix()).toEqual(moment('2018-09-04T01:00:00.000Z').unix())
- })
-
- it('converts to Date as expected', () => {
- expect(getSendBeforeTimeUtc(
- {},
- { textingHoursStart: 9, textingHoursEnd: 21, textingHoursEnforced: true },
- {}
- ).toDate()).toEqual(Date('2018-09-04T01:00:00.000Z'))
- })
-})
+ it("converts to Date as expected", () => {
+ expect(
+ getSendBeforeTimeUtc(
+ {},
+ {
+ textingHoursStart: 9,
+ textingHoursEnd: 21,
+ textingHoursEnforced: true
+ },
+ {}
+ ).toDate()
+ ).toEqual(Date("2018-09-04T01:00:00.000Z"));
+ });
+});
diff --git a/__test__/lib/tz-helpers.test.js b/__test__/lib/tz-helpers.test.js
index 8f2c858e5..89b60a474 100644
--- a/__test__/lib/tz-helpers.test.js
+++ b/__test__/lib/tz-helpers.test.js
@@ -1,10 +1,9 @@
-import {getProcessEnvDstReferenceTimezone} from "../../src/lib/tz-helpers";
+import { getProcessEnvDstReferenceTimezone } from "../../src/lib/tz-helpers";
-jest.unmock('../../src/lib/tz-helpers')
-
-describe('test getProcessEnvDstReferenceTimezone', () => {
- it('works', () => {
- expect(getProcessEnvDstReferenceTimezone()).toEqual('America/New_York')
- })
-})
+jest.unmock("../../src/lib/tz-helpers");
+describe("test getProcessEnvDstReferenceTimezone", () => {
+ it("works", () => {
+ expect(getProcessEnvDstReferenceTimezone()).toEqual("America/New_York");
+ });
+});
diff --git a/__test__/lib/zip-format.test.js b/__test__/lib/zip-format.test.js
index 45556dd17..6d0f5b638 100644
--- a/__test__/lib/zip-format.test.js
+++ b/__test__/lib/zip-format.test.js
@@ -1,64 +1,64 @@
-import {getFormattedZip, zipToTimeZone} from "../../src/lib";
+import { getFormattedZip, zipToTimeZone } from "../../src/lib";
-describe('test getFormattedZip', () => {
- it('handles zip correctly', () => {
- expect(getFormattedZip('12345')).toEqual('12345');
- })
+describe("test getFormattedZip", () => {
+ it("handles zip correctly", () => {
+ expect(getFormattedZip("12345")).toEqual("12345");
+ });
- it('handles zip + 4 correctly', () => {
- expect(getFormattedZip('12345-3456')).toEqual('12345');
- })
+ it("handles zip + 4 correctly", () => {
+ expect(getFormattedZip("12345-3456")).toEqual("12345");
+ });
- it('handles malformed zip correctly 1', () => {
- expect(getFormattedZip('12345-abcd')).toEqual('12345');
- })
+ it("handles malformed zip correctly 1", () => {
+ expect(getFormattedZip("12345-abcd")).toEqual("12345");
+ });
- it('handles malformed zip correctly 2', () => {
- expect(getFormattedZip('a2345-abcd')).toBeFalsy();
- })
+ it("handles malformed zip correctly 2", () => {
+ expect(getFormattedZip("a2345-abcd")).toBeFalsy();
+ });
- it('handles malformed zip correctly 3', () => {
- expect(getFormattedZip('2345-abcd')).toBeFalsy();
- })
+ it("handles malformed zip correctly 3", () => {
+ expect(getFormattedZip("2345-abcd")).toBeFalsy();
+ });
function wrapper() {
- getFormattedZip('11790', 'OZ');
+ getFormattedZip("11790", "OZ");
}
- it('handles not the USA correctly', () => {
+ it("handles not the USA correctly", () => {
expect(wrapper).toThrow(/OZ/);
- })
-})
+ });
+});
-describe('test zipToTimeZone', () => {
- it('handles string with 2 leading zeroes', () => {
- var result = zipToTimeZone('00100')
- expect(result[0]).toBe(-1)
- expect(result[1]).toBe(210)
- expect(result[2]).toBe(-4)
- expect(result[3]).toBe(1)
- })
- it('handles 3-digit integer', () => {
- var result = zipToTimeZone(100)
- expect(result[0]).toBe(-1)
- expect(result[1]).toBe(210)
- expect(result[2]).toBe(-4)
- expect(result[3]).toBe(1)
- })
- it('handles highest zip in the list', () => {
- expect(zipToTimeZone('99501')).toBeFalsy()
- })
- it('handles a zip at the lower boundary of a range', () => {
- var result = zipToTimeZone('59000')
- expect(result[2]).toBe(-7)
- expect(result[3]).toBe(1)
- })
- it('handles a zip one lower than the upper limit of a range', () => {
- var result = zipToTimeZone('69020')
- expect(result[2]).toBe(-6)
- expect(result[3]).toBe(1)
- })
- it('handles a zip at the upper limit of a range', () => {
- expect(zipToTimeZone('69021')).toBeFalsy()
- })
-})
+describe("test zipToTimeZone", () => {
+ it("handles string with 2 leading zeroes", () => {
+ var result = zipToTimeZone("00100");
+ expect(result[0]).toBe(-1);
+ expect(result[1]).toBe(210);
+ expect(result[2]).toBe(-4);
+ expect(result[3]).toBe(1);
+ });
+ it("handles 3-digit integer", () => {
+ var result = zipToTimeZone(100);
+ expect(result[0]).toBe(-1);
+ expect(result[1]).toBe(210);
+ expect(result[2]).toBe(-4);
+ expect(result[3]).toBe(1);
+ });
+ it("handles highest zip in the list", () => {
+ expect(zipToTimeZone("99501")).toBeFalsy();
+ });
+ it("handles a zip at the lower boundary of a range", () => {
+ var result = zipToTimeZone("59000");
+ expect(result[2]).toBe(-7);
+ expect(result[3]).toBe(1);
+ });
+ it("handles a zip one lower than the upper limit of a range", () => {
+ var result = zipToTimeZone("69020");
+ expect(result[2]).toBe(-6);
+ expect(result[3]).toBe(1);
+ });
+ it("handles a zip at the upper limit of a range", () => {
+ expect(zipToTimeZone("69021")).toBeFalsy();
+ });
+});
diff --git a/__test__/server/api/assignment.test.js b/__test__/server/api/assignment.test.js
index bbba659f7..d7ed902f4 100644
--- a/__test__/server/api/assignment.test.js
+++ b/__test__/server/api/assignment.test.js
@@ -1,187 +1,222 @@
-import { getContacts } from '../../../src/server/api/assignment'
-import { Organization, Assignment, Campaign } from '../../../src/server/models'
+import { getContacts } from "../../../src/server/api/assignment";
+import { Organization, Assignment, Campaign } from "../../../src/server/models";
-jest.mock('../../../src/lib/timezones.js')
-var timezones = require('../../../src/lib/timezones.js')
+jest.mock("../../../src/lib/timezones.js");
+var timezones = require("../../../src/lib/timezones.js");
-describe('test getContacts builds queries correctly', () => {
+describe("test getContacts builds queries correctly", () => {
var organization = new Organization({
texting_hours_enforced: false,
texting_hours_start: 9,
texting_hours_end: 14
- })
+ });
var campaign = new Campaign({
due_by: new Date()
- })
+ });
const past_due_campaign = new Campaign({
due_by: new Date().setFullYear(new Date().getFullYear() - 1)
- })
+ });
var assignment = new Assignment({
id: 1
- })
+ });
beforeEach(() => {
- timezones.getOffsets.mockReturnValueOnce([['-5_1'], ['-4_1']])
- })
+ timezones.getOffsets.mockReturnValueOnce([["-5_1"], ["-4_1"]]);
+ });
afterAll(() => {
- jest.restoreAllMocks()
- })
+ jest.restoreAllMocks();
+ });
- it('works with: no contacts filter', () => {
- const query = getContacts(assignment, undefined, organization, campaign)
+ it("works with: no contacts filter", () => {
+ const query = getContacts(assignment, undefined, organization, campaign);
expect(query.toString()).toBe(
- "select * from \"campaign_contact\" where \"assignment_id\" = 1 order by message_status DESC, updated_at"
- )
- }) // it
+ 'select * from "campaign_contact" where "assignment_id" = 1 order by message_status DESC, updated_at'
+ );
+ }); // it
- it('works with: contacts filter, include past due, message status', () => {
- const query = getContacts(assignment, { includePastDue: true }, organization, campaign)
+ it("works with: contacts filter, include past due, message status", () => {
+ const query = getContacts(
+ assignment,
+ { includePastDue: true },
+ organization,
+ campaign
+ );
expect(query.toString()).toBe(
- "select * from \"campaign_contact\" where \"assignment_id\" = 1 and \"message_status\" in ('needsResponse', 'needsMessage') order by message_status DESC, updated_at"
- )
- }) // it
+ 'select * from "campaign_contact" where "assignment_id" = 1 and "message_status" in (\'needsResponse\', \'needsMessage\') order by message_status DESC, updated_at'
+ );
+ }); // it
- it('works with: contacts filter, exclude past due, message status needsMessageOrResponse', () => {
+ it("works with: contacts filter, exclude past due, message status needsMessageOrResponse", () => {
const query = getContacts(
assignment,
- { messageStatus: 'needsMessageOrResponse' },
+ { messageStatus: "needsMessageOrResponse" },
organization,
campaign
- )
+ );
expect(query.toString()).toBe(
- "select * from \"campaign_contact\" where \"assignment_id\" = 1 and \"message_status\" in ('needsResponse', 'needsMessage') order by message_status DESC, updated_at"
- )
- }) // it
+ 'select * from "campaign_contact" where "assignment_id" = 1 and "message_status" in (\'needsResponse\', \'needsMessage\') order by message_status DESC, updated_at'
+ );
+ }); // it
- it('works with: contacts filter, exclude past due, campaign is past due, message status needsMessage', () => {
+ it("works with: contacts filter, exclude past due, campaign is past due, message status needsMessage", () => {
const query = getContacts(
assignment,
- { messageStatus: 'needsMessage' },
+ { messageStatus: "needsMessage" },
organization,
past_due_campaign
- )
+ );
// this should be empty because the query is empty and thus we return []
- expect(query.toString()).toBe('')
- }) // it
+ expect(query.toString()).toBe("");
+ }); // it
- it('works with: contacts filter, exclude past due, message status one other', () => {
- const query = getContacts(assignment, { messageStatus: 'convo' }, organization, campaign)
+ it("works with: contacts filter, exclude past due, message status one other", () => {
+ const query = getContacts(
+ assignment,
+ { messageStatus: "convo" },
+ organization,
+ campaign
+ );
expect(query.toString()).toBe(
- "select * from \"campaign_contact\" where \"assignment_id\" = 1 and \"message_status\" in ('convo') order by message_status DESC, updated_at DESC"
- )
- }) // it
+ 'select * from "campaign_contact" where "assignment_id" = 1 and "message_status" in (\'convo\') order by message_status DESC, updated_at DESC'
+ );
+ }); // it
- it('works with: contacts filter, exclude past due, message status multiple other', () => {
+ it("works with: contacts filter, exclude past due, message status multiple other", () => {
const query = getContacts(
assignment,
- { messageStatus: 'convo,messageReceived' },
+ { messageStatus: "convo,messageReceived" },
organization,
campaign
- )
+ );
expect(query.toString()).toBe(
- "select * from \"campaign_contact\" where \"assignment_id\" = 1 and \"message_status\" in ('convo', 'messageReceived') order by message_status DESC, updated_at"
- )
- }) // it
+ 'select * from "campaign_contact" where "assignment_id" = 1 and "message_status" in (\'convo\', \'messageReceived\') order by message_status DESC, updated_at'
+ );
+ }); // it
- it('works with: contacts filter, exclude past due, no message status, campaign is past due', () => {
- const query = getContacts(assignment, {}, organization, past_due_campaign)
+ it("works with: contacts filter, exclude past due, no message status, campaign is past due", () => {
+ const query = getContacts(assignment, {}, organization, past_due_campaign);
expect(query.toString()).toBe(
'select * from "campaign_contact" where "assignment_id" = 1 and "message_status" in (\'needsResponse\') order by message_status DESC, updated_at'
- )
- }) // it
+ );
+ }); // it
- it('works with: contacts filter, exclude past due, no message status, campaign not past due', () => {
- const query = getContacts(assignment, {}, organization, campaign)
+ it("works with: contacts filter, exclude past due, no message status, campaign not past due", () => {
+ const query = getContacts(assignment, {}, organization, campaign);
expect(query.toString()).toBe(
'select * from "campaign_contact" where "assignment_id" = 1 and "message_status" in (\'needsResponse\', \'needsMessage\') order by message_status DESC, updated_at'
- )
- }) // it
+ );
+ }); // it
- it('works with: forCount, contacts filter, exclude past due, no message status, campaign not past due', () => {
- const query = getContacts(assignment, {}, organization, campaign, true)
+ it("works with: forCount, contacts filter, exclude past due, no message status, campaign not past due", () => {
+ const query = getContacts(assignment, {}, organization, campaign, true);
expect(query.toString()).toBe(
'select * from "campaign_contact" where "assignment_id" = 1 and "message_status" in (\'needsResponse\', \'needsMessage\')'
- )
- }) // it
-}) // describe
+ );
+ }); // it
+}); // describe
-describe('test getContacts timezone stuff only', () => {
+describe("test getContacts timezone stuff only", () => {
var organization = new Organization({
texting_hours_enforced: true,
texting_hours_start: 9,
texting_hours_end: 14
- })
+ });
var campaign = new Campaign({
due_by: new Date()
- })
+ });
var assignment = new Assignment({
id: 1
- })
+ });
beforeEach(() => {
- timezones.getOffsets.mockReturnValueOnce([['-5_1'], ['-4_1']])
- })
+ timezones.getOffsets.mockReturnValueOnce([["-5_1"], ["-4_1"]]);
+ });
afterAll(() => {
- jest.restoreAllMocks()
- })
+ jest.restoreAllMocks();
+ });
- it('returns the correct query -- in default texting hours, with valid_timezone == true', () => {
- timezones.defaultTimezoneIsBetweenTextingHours.mockReturnValueOnce(true)
- var query = getContacts(assignment, { validTimezone: true }, organization, campaign)
+ it("returns the correct query -- in default texting hours, with valid_timezone == true", () => {
+ timezones.defaultTimezoneIsBetweenTextingHours.mockReturnValueOnce(true);
+ var query = getContacts(
+ assignment,
+ { validTimezone: true },
+ organization,
+ campaign
+ );
expect(query.toString()).toMatch(
- "select * from \"campaign_contact\" where \"assignment_id\" = 1 and \"timezone_offset\" in ('-5_1', '') and \"message_status\" in ('needsResponse', 'needsMessage') order by message_status DESC, updated_at"
- )
- }) // it
+ "select * from \"campaign_contact\" where \"assignment_id\" = 1 and \"timezone_offset\" in ('-5_1', '') and \"message_status\" in ('needsResponse', 'needsMessage') order by message_status DESC, updated_at"
+ );
+ }); // it
- it('returns the correct query -- in default texting hours, with valid_timezone == false', () => {
- timezones.defaultTimezoneIsBetweenTextingHours.mockReturnValueOnce(true)
- var query = getContacts(assignment, { validTimezone: false }, organization, campaign)
+ it("returns the correct query -- in default texting hours, with valid_timezone == false", () => {
+ timezones.defaultTimezoneIsBetweenTextingHours.mockReturnValueOnce(true);
+ var query = getContacts(
+ assignment,
+ { validTimezone: false },
+ organization,
+ campaign
+ );
expect(query.toString()).toMatch(
- "select * from \"campaign_contact\" where \"assignment_id\" = 1 and \"timezone_offset\" in ('-4_1') and \"message_status\" in ('needsResponse', 'needsMessage') order by message_status DESC, updated_at"
- )
- }) // it
+ 'select * from "campaign_contact" where "assignment_id" = 1 and "timezone_offset" in (\'-4_1\') and "message_status" in (\'needsResponse\', \'needsMessage\') order by message_status DESC, updated_at'
+ );
+ }); // it
- it('returns the correct query -- NOT in default texting hours, with valid_timezone == true', () => {
- timezones.defaultTimezoneIsBetweenTextingHours.mockReturnValueOnce(false)
- var query = getContacts(assignment, { validTimezone: true }, organization, campaign)
+ it("returns the correct query -- NOT in default texting hours, with valid_timezone == true", () => {
+ timezones.defaultTimezoneIsBetweenTextingHours.mockReturnValueOnce(false);
+ var query = getContacts(
+ assignment,
+ { validTimezone: true },
+ organization,
+ campaign
+ );
expect(query.toString()).toMatch(
- "select * from \"campaign_contact\" where \"assignment_id\" = 1 and \"timezone_offset\" in ('-5_1') and \"message_status\" in ('needsResponse', 'needsMessage') order by message_status DESC, updated_at"
- )
- }) // it
+ 'select * from "campaign_contact" where "assignment_id" = 1 and "timezone_offset" in (\'-5_1\') and "message_status" in (\'needsResponse\', \'needsMessage\') order by message_status DESC, updated_at'
+ );
+ }); // it
- it('returns the correct query -- NOT in default texting hours, with valid_timezone == false', () => {
- timezones.defaultTimezoneIsBetweenTextingHours.mockReturnValueOnce(false)
- var query = getContacts(assignment, { validTimezone: false }, organization, campaign)
+ it("returns the correct query -- NOT in default texting hours, with valid_timezone == false", () => {
+ timezones.defaultTimezoneIsBetweenTextingHours.mockReturnValueOnce(false);
+ var query = getContacts(
+ assignment,
+ { validTimezone: false },
+ organization,
+ campaign
+ );
expect(query.toString()).toMatch(
"select * from \"campaign_contact\" where \"assignment_id\" = 1 and \"timezone_offset\" in ('-4_1', '') and \"message_status\" in ('needsResponse', 'needsMessage') order by message_status DESC, updated_at"
- )
- }) // it
+ );
+ }); // it
- it('returns the correct query -- no contacts filter', () => {
- var query = getContacts(assignment, null, organization, campaign)
+ it("returns the correct query -- no contacts filter", () => {
+ var query = getContacts(assignment, null, organization, campaign);
expect(query.toString()).toMatch(
/^select \* from \"campaign_contact\" where \"assignment_id\" = 1.*/
- )
- }) // it
+ );
+ }); // it
- it('returns the correct query -- no validTimezone property in contacts filter', () => {
- var query = getContacts(assignment, {}, organization, campaign)
+ it("returns the correct query -- no validTimezone property in contacts filter", () => {
+ var query = getContacts(assignment, {}, organization, campaign);
expect(query.toString()).toMatch(
/^select \* from \"campaign_contact\" where \"assignment_id\" = 1.*/
- )
- }) // it
+ );
+ }); // it
- it('returns the correct query -- validTimezone property is null', () => {
- var query = getContacts(assignment, { validTimezone: null }, organization, campaign)
+ it("returns the correct query -- validTimezone property is null", () => {
+ var query = getContacts(
+ assignment,
+ { validTimezone: null },
+ organization,
+ campaign
+ );
expect(query.toString()).toMatch(
/^select \* from \"campaign_contact\" where \"assignment_id\" = 1.*/
- )
- }) // it
-}) // describe
+ );
+ }); // it
+}); // describe
diff --git a/__test__/server/api/campaign.test.js b/__test__/server/api/campaign.test.js
index 185cfe0c9..cd4c92df8 100644
--- a/__test__/server/api/campaign.test.js
+++ b/__test__/server/api/campaign.test.js
@@ -1,15 +1,15 @@
/* eslint-disable no-unused-expressions, consistent-return */
-import { r } from '../../../src/server/models/'
-import { dataQuery as TexterTodoListQuery } from '../../../src/containers/TexterTodoList'
-import { dataQuery as TexterTodoQuery } from '../../../src/containers/TexterTodo'
-import { campaignDataQuery as AdminCampaignEditQuery } from '../../../src/containers/AdminCampaignEdit'
+import { r } from "../../../src/server/models/";
+import { dataQuery as TexterTodoListQuery } from "../../../src/containers/TexterTodoList";
+import { dataQuery as TexterTodoQuery } from "../../../src/containers/TexterTodo";
+import { campaignDataQuery as AdminCampaignEditQuery } from "../../../src/containers/AdminCampaignEdit";
import {
bulkReassignCampaignContactsMutation,
reassignCampaignContactsMutation
-} from '../../../src/containers/AdminIncomingMessageList'
+} from "../../../src/containers/AdminIncomingMessageList";
-import { makeTree } from '../../../src/lib'
+import { makeTree } from "../../../src/lib";
import {
setupTest,
@@ -29,503 +29,723 @@ import {
startCampaign,
getCampaignContact,
sendMessage
-} from '../../test_helpers'
-
-let testAdminUser
-let testInvite
-let testOrganization
-let testCampaign
-let testTexterUser
-let testTexterUser2
-let testContacts
-let organizationId
-let assignmentId
+} from "../../test_helpers";
+
+let testAdminUser;
+let testInvite;
+let testOrganization;
+let testCampaign;
+let testTexterUser;
+let testTexterUser2;
+let testContacts;
+let organizationId;
+let assignmentId;
beforeEach(async () => {
// Set up an entire working campaign
- await setupTest()
- testAdminUser = await createUser()
- testInvite = await createInvite()
- testOrganization = await createOrganization(testAdminUser, testInvite)
- organizationId = testOrganization.data.createOrganization.id
- testCampaign = await createCampaign(testAdminUser, testOrganization)
- testContacts = await createContacts(testCampaign, 100)
- testTexterUser = await createTexter(testOrganization)
- testTexterUser2 = await createTexter(testOrganization)
- await assignTexter(testAdminUser, testTexterUser, testCampaign)
- const dbCampaignContact = await getCampaignContact(testContacts[0].id)
- assignmentId = dbCampaignContact.assignment_id
+ await setupTest();
+ testAdminUser = await createUser();
+ testInvite = await createInvite();
+ testOrganization = await createOrganization(testAdminUser, testInvite);
+ organizationId = testOrganization.data.createOrganization.id;
+ testCampaign = await createCampaign(testAdminUser, testOrganization);
+ testContacts = await createContacts(testCampaign, 100);
+ testTexterUser = await createTexter(testOrganization);
+ testTexterUser2 = await createTexter(testOrganization);
+ await assignTexter(testAdminUser, testTexterUser, testCampaign);
+ const dbCampaignContact = await getCampaignContact(testContacts[0].id);
+ assignmentId = dbCampaignContact.assignment_id;
// await createScript(testAdminUser, testCampaign)
// await startCampaign(testAdminUser, testCampaign)
-}, global.DATABASE_SETUP_TEARDOWN_TIMEOUT)
+}, global.DATABASE_SETUP_TEARDOWN_TIMEOUT);
afterEach(async () => {
- await cleanupTest()
- if (r.redis) r.redis.flushdb()
-}, global.DATABASE_SETUP_TEARDOWN_TIMEOUT)
+ await cleanupTest();
+ if (r.redis) r.redis.flushdb();
+}, global.DATABASE_SETUP_TEARDOWN_TIMEOUT);
+it("save campaign data, edit it, make sure the last value", async () => {
+ let campaignDataResults = await runComponentGql(
+ AdminCampaignEditQuery,
+ { campaignId: testCampaign.id },
+ testAdminUser
+ );
-it('save campaign data, edit it, make sure the last value', async () => {
- let campaignDataResults = await runComponentGql(AdminCampaignEditQuery,
- { campaignId: testCampaign.id },
- testAdminUser)
-
- expect(campaignDataResults.data.campaign.title).toEqual('test campaign')
- expect(campaignDataResults.data.campaign.description).toEqual('test description')
+ expect(campaignDataResults.data.campaign.title).toEqual("test campaign");
+ expect(campaignDataResults.data.campaign.description).toEqual(
+ "test description"
+ );
- let texterCampaignDataResults = await runComponentGql(TexterTodoListQuery,
- { organizationId },
- testTexterUser)
+ let texterCampaignDataResults = await runComponentGql(
+ TexterTodoListQuery,
+ { organizationId },
+ testTexterUser
+ );
// empty before we start the campaign
- expect(texterCampaignDataResults.data.currentUser.todos).toEqual([])
+ expect(texterCampaignDataResults.data.currentUser.todos).toEqual([]);
// now we start and confirm that we can access it
- await startCampaign(testAdminUser, testCampaign)
- texterCampaignDataResults = await runComponentGql(TexterTodoListQuery,
- { organizationId },
- testTexterUser)
- expect(texterCampaignDataResults.data.currentUser.todos[0].campaign.title).toEqual('test campaign')
- expect(texterCampaignDataResults.data.currentUser.todos[0].campaign.description).toEqual('test description')
+ await startCampaign(testAdminUser, testCampaign);
+ texterCampaignDataResults = await runComponentGql(
+ TexterTodoListQuery,
+ { organizationId },
+ testTexterUser
+ );
+ expect(
+ texterCampaignDataResults.data.currentUser.todos[0].campaign.title
+ ).toEqual("test campaign");
+ expect(
+ texterCampaignDataResults.data.currentUser.todos[0].campaign.description
+ ).toEqual("test description");
// now we modify it, and confirm that it changes
- const savedCampaign = await saveCampaign(testAdminUser,
- { id: testCampaign.id,
- organizationId },
- 'test campaign new title')
- expect(savedCampaign.title).toEqual('test campaign new title')
+ const savedCampaign = await saveCampaign(
+ testAdminUser,
+ { id: testCampaign.id, organizationId },
+ "test campaign new title"
+ );
+ expect(savedCampaign.title).toEqual("test campaign new title");
- campaignDataResults = await runComponentGql(AdminCampaignEditQuery,
- { campaignId: testCampaign.id },
- testAdminUser)
-
- texterCampaignDataResults = await runComponentGql(TexterTodoListQuery,
- { organizationId },
- testTexterUser)
+ campaignDataResults = await runComponentGql(
+ AdminCampaignEditQuery,
+ { campaignId: testCampaign.id },
+ testAdminUser
+ );
- expect(texterCampaignDataResults.data.currentUser.todos[0].campaign.title).toEqual('test campaign new title')
-})
+ texterCampaignDataResults = await runComponentGql(
+ TexterTodoListQuery,
+ { organizationId },
+ testTexterUser
+ );
+ expect(
+ texterCampaignDataResults.data.currentUser.todos[0].campaign.title
+ ).toEqual("test campaign new title");
+});
-it('save campaign interaction steps, edit it, make sure the last value is set', async () => {
- await createScript(testAdminUser, testCampaign)
- let campaignDataResults = await runComponentGql(AdminCampaignEditQuery,
- { campaignId: testCampaign.id },
- testAdminUser)
- expect(campaignDataResults.data.campaign.interactionSteps.length).toEqual(2)
- expect(campaignDataResults.data.campaign.interactionSteps[0].questionText).toEqual('hmm0')
- expect(campaignDataResults.data.campaign.interactionSteps[1].questionText).toEqual('hmm1')
- expect(campaignDataResults.data.campaign.interactionSteps[0].script).toEqual('autorespond {zip}')
- expect(campaignDataResults.data.campaign.interactionSteps[1].script).toEqual('{lastName}')
+it("save campaign interaction steps, edit it, make sure the last value is set", async () => {
+ await createScript(testAdminUser, testCampaign);
+ let campaignDataResults = await runComponentGql(
+ AdminCampaignEditQuery,
+ { campaignId: testCampaign.id },
+ testAdminUser
+ );
+ expect(campaignDataResults.data.campaign.interactionSteps.length).toEqual(2);
+ expect(
+ campaignDataResults.data.campaign.interactionSteps[0].questionText
+ ).toEqual("hmm0");
+ expect(
+ campaignDataResults.data.campaign.interactionSteps[1].questionText
+ ).toEqual("hmm1");
+ expect(campaignDataResults.data.campaign.interactionSteps[0].script).toEqual(
+ "autorespond {zip}"
+ );
+ expect(campaignDataResults.data.campaign.interactionSteps[1].script).toEqual(
+ "{lastName}"
+ );
// save an update with a new questionText script
- const interactionStepsClone1 = makeTree(campaignDataResults.data.campaign.interactionSteps)
- interactionStepsClone1.interactionSteps[0].script = 'second save before campaign start'
- await createScript(testAdminUser, testCampaign, interactionStepsClone1)
-
- campaignDataResults = await runComponentGql(AdminCampaignEditQuery,
- { campaignId: testCampaign.id },
- testAdminUser)
- expect(campaignDataResults.data.campaign.interactionSteps[1].script).toEqual('second save before campaign start')
+ const interactionStepsClone1 = makeTree(
+ campaignDataResults.data.campaign.interactionSteps
+ );
+ interactionStepsClone1.interactionSteps[0].script =
+ "second save before campaign start";
+ await createScript(testAdminUser, testCampaign, interactionStepsClone1);
+
+ campaignDataResults = await runComponentGql(
+ AdminCampaignEditQuery,
+ { campaignId: testCampaign.id },
+ testAdminUser
+ );
+ expect(campaignDataResults.data.campaign.interactionSteps[1].script).toEqual(
+ "second save before campaign start"
+ );
// save an update with a change to first text
- const interactionStepsClone2 = makeTree(campaignDataResults.data.campaign.interactionSteps)
- interactionStepsClone2.script = 'Hi {firstName}, please autorespond'
- await createScript(testAdminUser, testCampaign, interactionStepsClone2)
+ const interactionStepsClone2 = makeTree(
+ campaignDataResults.data.campaign.interactionSteps
+ );
+ interactionStepsClone2.script = "Hi {firstName}, please autorespond";
+ await createScript(testAdminUser, testCampaign, interactionStepsClone2);
- campaignDataResults = await runComponentGql(AdminCampaignEditQuery,
- { campaignId: testCampaign.id },
- testAdminUser)
- expect(campaignDataResults.data.campaign.interactionSteps[0].script).toEqual('Hi {firstName}, please autorespond')
+ campaignDataResults = await runComponentGql(
+ AdminCampaignEditQuery,
+ { campaignId: testCampaign.id },
+ testAdminUser
+ );
+ expect(campaignDataResults.data.campaign.interactionSteps[0].script).toEqual(
+ "Hi {firstName}, please autorespond"
+ );
// CAMPAIGN START
- await startCampaign(testAdminUser, testCampaign)
+ await startCampaign(testAdminUser, testCampaign);
// now we start and confirm that we can access the script as a texter
let texterCampaignDataResults = await runComponentGql(
TexterTodoQuery,
{
contactsFilter: {
- messageStatus: 'needsMessage',
+ messageStatus: "needsMessage",
isOptedOut: false,
validTimezone: true
},
assignmentId
},
- testTexterUser)
+ testTexterUser
+ );
expect(
- texterCampaignDataResults.data.assignment.campaign.interactionSteps[0].script)
- .toEqual('Hi {firstName}, please autorespond')
+ texterCampaignDataResults.data.assignment.campaign.interactionSteps[0]
+ .script
+ ).toEqual("Hi {firstName}, please autorespond");
expect(
- texterCampaignDataResults.data.assignment.campaign.interactionSteps[1].script)
- .toEqual('second save before campaign start')
+ texterCampaignDataResults.data.assignment.campaign.interactionSteps[1]
+ .script
+ ).toEqual("second save before campaign start");
// after campaign start: update script of first and second text and question text
// verify both admin and texter queries
- const interactionStepsClone3 = makeTree(campaignDataResults.data.campaign.interactionSteps)
- interactionStepsClone3.script = 'Hi {firstName}, please autorespond -- after campaign start'
- interactionStepsClone3.interactionSteps[0].script = 'third save after campaign start'
- interactionStepsClone3.interactionSteps[0].questionText = 'hmm1 after campaign start'
- await createScript(testAdminUser, testCampaign, interactionStepsClone3)
-
- campaignDataResults = await runComponentGql(AdminCampaignEditQuery,
- { campaignId: testCampaign.id },
- testAdminUser)
- expect(
- campaignDataResults.data.campaign.interactionSteps[0].script)
- .toEqual('Hi {firstName}, please autorespond -- after campaign start')
- expect(
- campaignDataResults.data.campaign.interactionSteps[1].script)
- .toEqual('third save after campaign start')
+ const interactionStepsClone3 = makeTree(
+ campaignDataResults.data.campaign.interactionSteps
+ );
+ interactionStepsClone3.script =
+ "Hi {firstName}, please autorespond -- after campaign start";
+ interactionStepsClone3.interactionSteps[0].script =
+ "third save after campaign start";
+ interactionStepsClone3.interactionSteps[0].questionText =
+ "hmm1 after campaign start";
+ await createScript(testAdminUser, testCampaign, interactionStepsClone3);
+
+ campaignDataResults = await runComponentGql(
+ AdminCampaignEditQuery,
+ { campaignId: testCampaign.id },
+ testAdminUser
+ );
+ expect(campaignDataResults.data.campaign.interactionSteps[0].script).toEqual(
+ "Hi {firstName}, please autorespond -- after campaign start"
+ );
+ expect(campaignDataResults.data.campaign.interactionSteps[1].script).toEqual(
+ "third save after campaign start"
+ );
expect(
- campaignDataResults.data.campaign.interactionSteps[1].questionText)
- .toEqual('hmm1 after campaign start')
- texterCampaignDataResults = await runComponentGql(TexterTodoQuery,
+ campaignDataResults.data.campaign.interactionSteps[1].questionText
+ ).toEqual("hmm1 after campaign start");
+ texterCampaignDataResults = await runComponentGql(
+ TexterTodoQuery,
{
contactsFilter: {
- messageStatus: 'needsMessage',
+ messageStatus: "needsMessage",
isOptedOut: false,
validTimezone: true
},
assignmentId
},
- testTexterUser)
+ testTexterUser
+ );
expect(
- texterCampaignDataResults.data.assignment.campaign.interactionSteps[0].script)
- .toEqual('Hi {firstName}, please autorespond -- after campaign start')
+ texterCampaignDataResults.data.assignment.campaign.interactionSteps[0]
+ .script
+ ).toEqual("Hi {firstName}, please autorespond -- after campaign start");
expect(
- texterCampaignDataResults.data.assignment.campaign.interactionSteps[1].script)
- .toEqual('third save after campaign start')
+ texterCampaignDataResults.data.assignment.campaign.interactionSteps[1]
+ .script
+ ).toEqual("third save after campaign start");
expect(
- texterCampaignDataResults.data.assignment.campaign.interactionSteps[1].question.text)
- .toEqual('hmm1 after campaign start')
+ texterCampaignDataResults.data.assignment.campaign.interactionSteps[1]
+ .question.text
+ ).toEqual("hmm1 after campaign start");
// COPIED CAMPAIGN
- const copiedCampaign1 = await copyCampaign(testCampaign.id, testAdminUser)
+ const copiedCampaign1 = await copyCampaign(testCampaign.id, testAdminUser);
// 2nd campaign to test against https://github.com/MoveOnOrg/Spoke/issues/854
- const copiedCampaign2 = await copyCampaign(testCampaign.id, testAdminUser)
- expect(copiedCampaign1.data.copyCampaign.id).not.toEqual(testCampaign.id)
+ const copiedCampaign2 = await copyCampaign(testCampaign.id, testAdminUser);
+ expect(copiedCampaign1.data.copyCampaign.id).not.toEqual(testCampaign.id);
- const prevCampaignIsteps = campaignDataResults.data.campaign.interactionSteps
+ const prevCampaignIsteps = campaignDataResults.data.campaign.interactionSteps;
const compareToLater = async (campaignId, prevCampaignIsteps) => {
const campaignDataResults = await runComponentGql(
- AdminCampaignEditQuery, { campaignId: campaignId }, testAdminUser)
+ AdminCampaignEditQuery,
+ { campaignId: campaignId },
+ testAdminUser
+ );
expect(
- campaignDataResults.data.campaign.interactionSteps[0].script)
- .toEqual('Hi {firstName}, please autorespond -- after campaign start')
+ campaignDataResults.data.campaign.interactionSteps[0].script
+ ).toEqual("Hi {firstName}, please autorespond -- after campaign start");
expect(
- campaignDataResults.data.campaign.interactionSteps[1].script)
- .toEqual('third save after campaign start')
+ campaignDataResults.data.campaign.interactionSteps[1].script
+ ).toEqual("third save after campaign start");
expect(
- campaignDataResults.data.campaign.interactionSteps[1].questionText)
- .toEqual('hmm1 after campaign start')
+ campaignDataResults.data.campaign.interactionSteps[1].questionText
+ ).toEqual("hmm1 after campaign start");
// make sure the copied steps are new ones
expect(
- Number(campaignDataResults.data.campaign.interactionSteps[0].id))
- .toBeGreaterThan(Number(prevCampaignIsteps[1].id))
+ Number(campaignDataResults.data.campaign.interactionSteps[0].id)
+ ).toBeGreaterThan(Number(prevCampaignIsteps[1].id));
expect(
- Number(campaignDataResults.data.campaign.interactionSteps[1].id))
- .toBeGreaterThan(Number(prevCampaignIsteps[1].id))
- return campaignDataResults
- }
- const campaign1Results = await compareToLater(copiedCampaign1.data.copyCampaign.id, prevCampaignIsteps)
- await compareToLater(copiedCampaign2.data.copyCampaign.id, prevCampaignIsteps)
- await compareToLater(copiedCampaign2.data.copyCampaign.id, campaign1Results.data.campaign.interactionSteps)
-
-
-})
-
-it('should save campaign canned responses across copies and match saved data', async () => {
- await createScript(testAdminUser, testCampaign)
- await createCannedResponses(testAdminUser, testCampaign,
- [{title: "canned 1", text: "can1 {firstName}"},
- {title: "canned 2", text: "can2 {firstName}"},
- {title: "canned 3", text: "can3 {firstName}"},
- {title: "canned 4", text: "can4 {firstName}"},
- {title: "canned 5", text: "can5 {firstName}"},
- {title: "canned 6", text: "can6 {firstName}"},
- ])
+ Number(campaignDataResults.data.campaign.interactionSteps[1].id)
+ ).toBeGreaterThan(Number(prevCampaignIsteps[1].id));
+ return campaignDataResults;
+ };
+ const campaign1Results = await compareToLater(
+ copiedCampaign1.data.copyCampaign.id,
+ prevCampaignIsteps
+ );
+ await compareToLater(
+ copiedCampaign2.data.copyCampaign.id,
+ prevCampaignIsteps
+ );
+ await compareToLater(
+ copiedCampaign2.data.copyCampaign.id,
+ campaign1Results.data.campaign.interactionSteps
+ );
+});
+
+it("should save campaign canned responses across copies and match saved data", async () => {
+ await createScript(testAdminUser, testCampaign);
+ await createCannedResponses(testAdminUser, testCampaign, [
+ { title: "canned 1", text: "can1 {firstName}" },
+ { title: "canned 2", text: "can2 {firstName}" },
+ { title: "canned 3", text: "can3 {firstName}" },
+ { title: "canned 4", text: "can4 {firstName}" },
+ { title: "canned 5", text: "can5 {firstName}" },
+ { title: "canned 6", text: "can6 {firstName}" }
+ ]);
let campaignDataResults = await runComponentGql(
- AdminCampaignEditQuery, { campaignId: testCampaign.id }, testAdminUser)
-
- expect(campaignDataResults.data.campaign.cannedResponses.length).toEqual(6)
- for (let i=0; i<6; i++) {
- expect(campaignDataResults.data.campaign.cannedResponses[i].title).toEqual(`canned ${i+1}`)
- expect(campaignDataResults.data.campaign.cannedResponses[i].text).toEqual(`can${i+1} {firstName}`)
+ AdminCampaignEditQuery,
+ { campaignId: testCampaign.id },
+ testAdminUser
+ );
+
+ expect(campaignDataResults.data.campaign.cannedResponses.length).toEqual(6);
+ for (let i = 0; i < 6; i++) {
+ expect(campaignDataResults.data.campaign.cannedResponses[i].title).toEqual(
+ `canned ${i + 1}`
+ );
+ expect(campaignDataResults.data.campaign.cannedResponses[i].text).toEqual(
+ `can${i + 1} {firstName}`
+ );
}
// COPY CAMPAIGN
- const copiedCampaign1 = await copyCampaign(testCampaign.id, testAdminUser)
- const copiedCampaign2 = await copyCampaign(testCampaign.id, testAdminUser)
-
- campaignDataResults = await runComponentGql(
- AdminCampaignEditQuery, { campaignId: copiedCampaign2.data.copyCampaign.id }, testAdminUser)
- expect(campaignDataResults.data.campaign.cannedResponses.length).toEqual(6)
- for (let i=0; i<6; i++) {
- expect(campaignDataResults.data.campaign.cannedResponses[i].title).toEqual(`canned ${i+1}`)
- expect(campaignDataResults.data.campaign.cannedResponses[i].text).toEqual(`can${i+1} {firstName}`)
+ const copiedCampaign1 = await copyCampaign(testCampaign.id, testAdminUser);
+ const copiedCampaign2 = await copyCampaign(testCampaign.id, testAdminUser);
+
+ campaignDataResults = await runComponentGql(
+ AdminCampaignEditQuery,
+ { campaignId: copiedCampaign2.data.copyCampaign.id },
+ testAdminUser
+ );
+ expect(campaignDataResults.data.campaign.cannedResponses.length).toEqual(6);
+ for (let i = 0; i < 6; i++) {
+ expect(campaignDataResults.data.campaign.cannedResponses[i].title).toEqual(
+ `canned ${i + 1}`
+ );
+ expect(campaignDataResults.data.campaign.cannedResponses[i].text).toEqual(
+ `can${i + 1} {firstName}`
+ );
}
campaignDataResults = await runComponentGql(
- AdminCampaignEditQuery, { campaignId: copiedCampaign1.data.copyCampaign.id }, testAdminUser)
-
- expect(campaignDataResults.data.campaign.cannedResponses.length).toEqual(6)
- for (let i=0; i<6; i++) {
- expect(campaignDataResults.data.campaign.cannedResponses[i].title).toEqual(`canned ${i+1}`)
- expect(campaignDataResults.data.campaign.cannedResponses[i].text).toEqual(`can${i+1} {firstName}`)
+ AdminCampaignEditQuery,
+ { campaignId: copiedCampaign1.data.copyCampaign.id },
+ testAdminUser
+ );
+
+ expect(campaignDataResults.data.campaign.cannedResponses.length).toEqual(6);
+ for (let i = 0; i < 6; i++) {
+ expect(campaignDataResults.data.campaign.cannedResponses[i].title).toEqual(
+ `canned ${i + 1}`
+ );
+ expect(campaignDataResults.data.campaign.cannedResponses[i].text).toEqual(
+ `can${i + 1} {firstName}`
+ );
}
+});
-
-})
-
-describe('Reassignments', async () => {
- it('should allow reassignments before campaign start', async() => {
+describe("Reassignments", async () => {
+ it("should allow reassignments before campaign start", async () => {
// - user gets assignment todos
// - assignments are changed in different ways (with different mutations)
// - and the current assignments are verified
// - assign three texters 10 contacts each
// - reassign 5 from one to another
// - verify admin query texter counts are correct
- expect(true).toEqual(true)
- })
-
+ expect(true).toEqual(true);
+ });
- it('should allow reassignments after campaign start', async () => {
- await createScript(testAdminUser, testCampaign)
- await startCampaign(testAdminUser, testCampaign)
+ it("should allow reassignments after campaign start", async () => {
+ await createScript(testAdminUser, testCampaign);
+ await startCampaign(testAdminUser, testCampaign);
let texterCampaignDataResults = await runComponentGql(
TexterTodoQuery,
- { contactsFilter: { messageStatus: 'needsMessage',
- isOptedOut: false,
- validTimezone: true },
+ {
+ contactsFilter: {
+ messageStatus: "needsMessage",
+ isOptedOut: false,
+ validTimezone: true
+ },
assignmentId
},
- testTexterUser)
+ testTexterUser
+ );
// TEXTER 1 (100 needsMessage)
- expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(100)
- expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(100)
+ expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(
+ 100
+ );
+ expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(
+ 100
+ );
// send some texts
- for (let i=0; i<5; i++) {
- const messageResult = await sendMessage(testContacts[i].id, testTexterUser,
- { userId: testTexterUser.id,
- contactNumber: testContacts[i].cell,
- text: 'test text',
- assignmentId })
+ for (let i = 0; i < 5; i++) {
+ const messageResult = await sendMessage(
+ testContacts[i].id,
+ testTexterUser,
+ {
+ userId: testTexterUser.id,
+ contactNumber: testContacts[i].cell,
+ text: "test text",
+ assignmentId
+ }
+ );
}
// TEXTER 1 (95 needsMessage, 5 needsResponse)
texterCampaignDataResults = await runComponentGql(
TexterTodoQuery,
- { contactsFilter: { messageStatus: 'needsMessage',
- isOptedOut: false,
- validTimezone: true },
+ {
+ contactsFilter: {
+ messageStatus: "needsMessage",
+ isOptedOut: false,
+ validTimezone: true
+ },
assignmentId
},
- testTexterUser)
-
- expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(95)
- expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(100)
+ testTexterUser
+ );
+
+ expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(
+ 95
+ );
+ expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(
+ 100
+ );
// - reassign 5 from one to another
// using editCampaign
- await assignTexter(testAdminUser, testTexterUser, testCampaign,
- [{id: testTexterUser.id, needsMessageCount: 70, contactsCount: 100},
- {id: testTexterUser2.id, needsMessageCount: 20}])
+ await assignTexter(testAdminUser, testTexterUser, testCampaign, [
+ { id: testTexterUser.id, needsMessageCount: 70, contactsCount: 100 },
+ { id: testTexterUser2.id, needsMessageCount: 20 }
+ ]);
// TEXTER 1 (70 needsMessage, 5 messaged)
// TEXTER 2 (20 needsMessage)
texterCampaignDataResults = await runComponentGql(
TexterTodoQuery,
- { contactsFilter: { messageStatus: 'needsMessage',
- isOptedOut: false,
- validTimezone: true },
+ {
+ contactsFilter: {
+ messageStatus: "needsMessage",
+ isOptedOut: false,
+ validTimezone: true
+ },
assignmentId
},
- testTexterUser)
- let texterCampaignDataResults2 = await runComponentGql(TexterTodoListQuery,
- { organizationId },
- testTexterUser2)
- expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(70)
- expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(75)
-
- const assignmentId2 = texterCampaignDataResults2.data.currentUser.todos[0].id
+ testTexterUser
+ );
+ let texterCampaignDataResults2 = await runComponentGql(
+ TexterTodoListQuery,
+ { organizationId },
+ testTexterUser2
+ );
+ expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(
+ 70
+ );
+ expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(
+ 75
+ );
+
+ const assignmentId2 =
+ texterCampaignDataResults2.data.currentUser.todos[0].id;
texterCampaignDataResults = await runComponentGql(
TexterTodoQuery,
- { contactsFilter: { messageStatus: 'needsMessage',
- isOptedOut: false,
- validTimezone: true },
+ {
+ contactsFilter: {
+ messageStatus: "needsMessage",
+ isOptedOut: false,
+ validTimezone: true
+ },
assignmentId: assignmentId2
},
- testTexterUser2)
- expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(20)
- expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(20)
- let assignmentContacts2 = texterCampaignDataResults.data.assignment.contacts
- for (let i=0; i<5; i++) {
- const contact = testContacts.filter(c => assignmentContacts2[i].id == c.id)[0]
- const messageResult = await sendMessage(contact.id, testTexterUser2,
- { userId: testTexterUser2.id,
- contactNumber: contact.cell,
- text: 'test text autorespond',
- assignmentId: assignmentId2 })
+ testTexterUser2
+ );
+ expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(
+ 20
+ );
+ expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(
+ 20
+ );
+ let assignmentContacts2 =
+ texterCampaignDataResults.data.assignment.contacts;
+ for (let i = 0; i < 5; i++) {
+ const contact = testContacts.filter(
+ c => assignmentContacts2[i].id == c.id
+ )[0];
+ const messageResult = await sendMessage(contact.id, testTexterUser2, {
+ userId: testTexterUser2.id,
+ contactNumber: contact.cell,
+ text: "test text autorespond",
+ assignmentId: assignmentId2
+ });
}
// TEXTER 1 (70 needsMessage, 5 messaged)
// TEXTER 2 (15 needsMessage, 5 needsResponse)
texterCampaignDataResults = await runComponentGql(
TexterTodoQuery,
- { contactsFilter: { messageStatus: 'needsMessage',
- isOptedOut: false,
- validTimezone: true },
+ {
+ contactsFilter: {
+ messageStatus: "needsMessage",
+ isOptedOut: false,
+ validTimezone: true
+ },
assignmentId: assignmentId2
},
- testTexterUser2)
-
- expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(15)
- expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(20)
+ testTexterUser2
+ );
+
+ expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(
+ 15
+ );
+ expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(
+ 20
+ );
texterCampaignDataResults = await runComponentGql(
TexterTodoQuery,
- { contactsFilter: { messageStatus: 'needsResponse',
- isOptedOut: false,
- validTimezone: true },
+ {
+ contactsFilter: {
+ messageStatus: "needsResponse",
+ isOptedOut: false,
+ validTimezone: true
+ },
assignmentId: assignmentId2
},
- testTexterUser2)
- expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(5)
- expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(20)
- for (let i=0; i<3; i++) {
- const contact = testContacts.filter(c => texterCampaignDataResults.data.assignment.contacts[i].id == c.id)[0]
- const messageResult = await sendMessage(contact.id, testTexterUser2,
- { userId: testTexterUser2.id,
- contactNumber: contact.cell,
- text: 'keep talking',
- assignmentId: assignmentId2 })
+ testTexterUser2
+ );
+ expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(
+ 5
+ );
+ expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(
+ 20
+ );
+ for (let i = 0; i < 3; i++) {
+ const contact = testContacts.filter(
+ c => texterCampaignDataResults.data.assignment.contacts[i].id == c.id
+ )[0];
+ const messageResult = await sendMessage(contact.id, testTexterUser2, {
+ userId: testTexterUser2.id,
+ contactNumber: contact.cell,
+ text: "keep talking",
+ assignmentId: assignmentId2
+ });
}
// TEXTER 1 (70 needsMessage, 5 messaged)
// TEXTER 2 (15 needsMessage, 2 needsResponse, 3 convo)
texterCampaignDataResults = await runComponentGql(
TexterTodoQuery,
- { contactsFilter: { messageStatus: 'needsResponse',
- isOptedOut: false,
- validTimezone: true },
+ {
+ contactsFilter: {
+ messageStatus: "needsResponse",
+ isOptedOut: false,
+ validTimezone: true
+ },
assignmentId: assignmentId2
},
- testTexterUser2)
- expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(2)
- expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(20)
+ testTexterUser2
+ );
+ expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(
+ 2
+ );
+ expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(
+ 20
+ );
texterCampaignDataResults = await runComponentGql(
TexterTodoQuery,
- { contactsFilter: { messageStatus: 'convo',
- isOptedOut: false,
- validTimezone: true },
+ {
+ contactsFilter: {
+ messageStatus: "convo",
+ isOptedOut: false,
+ validTimezone: true
+ },
assignmentId: assignmentId2
},
- testTexterUser2)
- expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(3)
- expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(20)
-
- await assignTexter(testAdminUser, testTexterUser, testCampaign,
- [{id: testTexterUser.id, needsMessageCount: 60, contactsCount: 75},
- // contactsCount: 30 = 25 (desired needsMessage) + 5 (messaged)
- {id: testTexterUser2.id, needsMessageCount: 25, contactsCount: 30}])
+ testTexterUser2
+ );
+ expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(
+ 3
+ );
+ expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(
+ 20
+ );
+
+ await assignTexter(testAdminUser, testTexterUser, testCampaign, [
+ { id: testTexterUser.id, needsMessageCount: 60, contactsCount: 75 },
+ // contactsCount: 30 = 25 (desired needsMessage) + 5 (messaged)
+ { id: testTexterUser2.id, needsMessageCount: 25, contactsCount: 30 }
+ ]);
// TEXTER 1 (60 needsMessage, 5 messaged)
// TEXTER 2 (25 needsMessage, 2 needsResponse, 3 convo)
texterCampaignDataResults = await runComponentGql(
TexterTodoQuery,
- { contactsFilter: { messageStatus: 'needsMessage',
- isOptedOut: false,
- validTimezone: true },
+ {
+ contactsFilter: {
+ messageStatus: "needsMessage",
+ isOptedOut: false,
+ validTimezone: true
+ },
assignmentId
},
- testTexterUser)
+ testTexterUser
+ );
texterCampaignDataResults2 = await runComponentGql(
TexterTodoQuery,
- { contactsFilter: { messageStatus: 'needsMessage',
- isOptedOut: false,
- validTimezone: true },
+ {
+ contactsFilter: {
+ messageStatus: "needsMessage",
+ isOptedOut: false,
+ validTimezone: true
+ },
assignmentId: assignmentId2
},
- testTexterUser2)
-
-
- expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(60)
- expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(65)
- expect(texterCampaignDataResults2.data.assignment.contacts.length).toEqual(25)
- expect(texterCampaignDataResults2.data.assignment.allContactsCount).toEqual(30)
+ testTexterUser2
+ );
+
+ expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(
+ 60
+ );
+ expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(
+ 65
+ );
+ expect(texterCampaignDataResults2.data.assignment.contacts.length).toEqual(
+ 25
+ );
+ expect(texterCampaignDataResults2.data.assignment.allContactsCount).toEqual(
+ 30
+ );
// maybe test no intersections of texted people and non-texted, and/or needsReply
// reassignCampaignContactsMutation
await runComponentGql(
- reassignCampaignContactsMutation, { organizationId,
- newTexterUserId: testTexterUser2.id,
- campaignIdsContactIds: [
- { campaignId: testCampaign.id,
- // depending on testContacts[0] being
- // first message sent at top of text
- campaignContactId: testContacts[0].id,
- messageIds: [1]
- }
- ]
- }, testAdminUser)
+ reassignCampaignContactsMutation,
+ {
+ organizationId,
+ newTexterUserId: testTexterUser2.id,
+ campaignIdsContactIds: [
+ {
+ campaignId: testCampaign.id,
+ // depending on testContacts[0] being
+ // first message sent at top of text
+ campaignContactId: testContacts[0].id,
+ messageIds: [1]
+ }
+ ]
+ },
+ testAdminUser
+ );
// TEXTER 1 (60 needsMessage, 4 messaged)
// TEXTER 2 (25 needsMessage, 2 needsResponse, 3 convo, 1 messaged)
texterCampaignDataResults = await runComponentGql(
TexterTodoQuery,
- { contactsFilter: { messageStatus: 'messaged',
- isOptedOut: false,
- validTimezone: true },
+ {
+ contactsFilter: {
+ messageStatus: "messaged",
+ isOptedOut: false,
+ validTimezone: true
+ },
assignmentId
},
- testTexterUser)
+ testTexterUser
+ );
texterCampaignDataResults2 = await runComponentGql(
TexterTodoQuery,
- { contactsFilter: { messageStatus: 'messaged',
- isOptedOut: false,
- validTimezone: true },
+ {
+ contactsFilter: {
+ messageStatus: "messaged",
+ isOptedOut: false,
+ validTimezone: true
+ },
assignmentId: assignmentId2
},
- testTexterUser2)
-
-
- expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(4)
- expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(64)
- expect(texterCampaignDataResults2.data.assignment.contacts.length).toEqual(1)
- expect(texterCampaignDataResults2.data.assignment.allContactsCount).toEqual(31)
+ testTexterUser2
+ );
+
+ expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(
+ 4
+ );
+ expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(
+ 64
+ );
+ expect(texterCampaignDataResults2.data.assignment.contacts.length).toEqual(
+ 1
+ );
+ expect(texterCampaignDataResults2.data.assignment.allContactsCount).toEqual(
+ 31
+ );
// bulkReassignCampaignContactsMutation
await runComponentGql(
- bulkReassignCampaignContactsMutation, {
+ bulkReassignCampaignContactsMutation,
+ {
organizationId,
- contactsFilter: { messageStatus: 'needsResponse',
- isOptedOut: false,
- validTimezone: true },
+ contactsFilter: {
+ messageStatus: "needsResponse",
+ isOptedOut: false,
+ validTimezone: true
+ },
campaignsFilter: { campaignId: testCampaign.id },
assignmentsFilter: { texterId: testTexterUser2.id },
newTexterUserId: testTexterUser.id
- }, testAdminUser)
+ },
+ testAdminUser
+ );
// TEXTER 1 (60 needsMessage, 2 needsResponse, 4 messaged)
// TEXTER 2 (25 needsMessage, 3 convo, 1 messaged)
texterCampaignDataResults = await runComponentGql(
TexterTodoQuery,
- { contactsFilter: { messageStatus: 'needsResponse',
- isOptedOut: false,
- validTimezone: true },
+ {
+ contactsFilter: {
+ messageStatus: "needsResponse",
+ isOptedOut: false,
+ validTimezone: true
+ },
assignmentId
},
- testTexterUser)
+ testTexterUser
+ );
texterCampaignDataResults2 = await runComponentGql(
TexterTodoQuery,
- { contactsFilter: { messageStatus: 'needsResponse',
- isOptedOut: false,
- validTimezone: true },
+ {
+ contactsFilter: {
+ messageStatus: "needsResponse",
+ isOptedOut: false,
+ validTimezone: true
+ },
assignmentId: assignmentId2
},
- testTexterUser2)
-
- expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(2)
- expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(66)
- expect(texterCampaignDataResults2.data.assignment.contacts.length).toEqual(0)
- expect(texterCampaignDataResults2.data.assignment.allContactsCount).toEqual(29)
- })
-})
+ testTexterUser2
+ );
+
+ expect(texterCampaignDataResults.data.assignment.contacts.length).toEqual(
+ 2
+ );
+ expect(texterCampaignDataResults.data.assignment.allContactsCount).toEqual(
+ 66
+ );
+ expect(texterCampaignDataResults2.data.assignment.contacts.length).toEqual(
+ 0
+ );
+ expect(texterCampaignDataResults2.data.assignment.allContactsCount).toEqual(
+ 29
+ );
+ });
+});
diff --git a/__test__/server/db/export.js b/__test__/server/db/export.js
index 7b1f7ae2c..4063310da 100644
--- a/__test__/server/db/export.js
+++ b/__test__/server/db/export.js
@@ -1,32 +1,40 @@
-import {r} from '../../../src/server/models'
-import { tables, indexQuery } from './utils'
-import fs from 'fs'
+import { r } from "../../../src/server/models";
+import { tables, indexQuery } from "./utils";
+import fs from "fs";
function getSchema(s) {
- return r.k(s).columnInfo().then(schema => {
- console.log('exported schema for', s)
- fs.writeFileSync(`init_schemas/${s}.json`, JSON.stringify(schema, null, 2))
- })
+ return r
+ .k(s)
+ .columnInfo()
+ .then(schema => {
+ console.log("exported schema for", s);
+ fs.writeFileSync(
+ `init_schemas/${s}.json`,
+ JSON.stringify(schema, null, 2)
+ );
+ });
}
function getIndexes() {
- return r.k.raw(indexQuery)
- .then(indexes => {
- fs.writeFileSync('init_schemas/indexes.json', JSON.stringify(indexes, null, 2))
- console.log('exported indices')
- })
+ return r.k.raw(indexQuery).then(indexes => {
+ fs.writeFileSync(
+ "init_schemas/indexes.json",
+ JSON.stringify(indexes, null, 2)
+ );
+ console.log("exported indices");
+ });
}
-const tablePromises = tables.map(getSchema)
-const indexesPromises = getIndexes()
+const tablePromises = tables.map(getSchema);
+const indexesPromises = getIndexes();
Promise.all(tablePromises.concat([indexesPromises]))
.then(() => {
- console.log('completed')
- process.exit(0)
+ console.log("completed");
+ process.exit(0);
})
.catch(error => {
- console.error(error)
- process.exit(1)
- })
+ console.error(error);
+ process.exit(1);
+ });
// Run this file _from this directory_ (e.g. with npx babel-node export.js) to get nice JSON representations of each table's schema, for testing.
diff --git a/__test__/server/db/utils.js b/__test__/server/db/utils.js
index 8b03a145f..4f4aac431 100644
--- a/__test__/server/db/utils.js
+++ b/__test__/server/db/utils.js
@@ -1,22 +1,22 @@
export const tables = [
- 'log',
- 'message',
- 'user_cell',
- 'job_request',
- 'pending_message_part',
- 'zip_code',
- 'invite',
- 'user',
- 'user_organization',
- 'campaign',
- 'interaction_step',
- 'assignment',
- 'organization',
- 'canned_response',
- 'opt_out',
- 'question_response',
- 'campaign_contact'
-]
+ "log",
+ "message",
+ "user_cell",
+ "job_request",
+ "pending_message_part",
+ "zip_code",
+ "invite",
+ "user",
+ "user_organization",
+ "campaign",
+ "interaction_step",
+ "assignment",
+ "organization",
+ "canned_response",
+ "opt_out",
+ "question_response",
+ "campaign_contact"
+];
// Adapted from https://dba.stackexchange.com/a/37068
export const indexQuery = `SELECT conrelid::regclass AS table_from
@@ -29,4 +29,4 @@ AND conrelid::regclass::text <> 'migrations'
AND conrelid::regclass::text <> 'knex_migrations'
AND conrelid::regclass::text <> 'knex_migrations_lock'
AND n.nspname = 'public'
-ORDER BY conrelid::regclass::text, contype DESC;`
+ORDER BY conrelid::regclass::text, contype DESC;`;
diff --git a/__test__/server/render-index.test.js b/__test__/server/render-index.test.js
index c2c44f649..272a27d87 100644
--- a/__test__/server/render-index.test.js
+++ b/__test__/server/render-index.test.js
@@ -1,23 +1,25 @@
-import renderIndex from '../../src/server/middleware/render-index'
+import renderIndex from "../../src/server/middleware/render-index";
const fakeArguments = {
- html: '',
+ html: "",
css: {
- content: '',
- renderedClassNames: ''
+ content: "",
+ renderedClassNames: ""
},
assetMap: {
- 'bundle.js': ''
+ "bundle.js": ""
},
store: {
- getState: () => ''
+ getState: () => ""
}
-}
+};
-describe('renderIndex', () => {
- it('returns html markup that contains a tag link for the favicon', () => {
- const { html, css, assetMap, store } = fakeArguments
- const htmlMarkup = renderIndex(html, css, assetMap, store)
- expect(htmlMarkup).toContain('')
- })
-})
+describe("renderIndex", () => {
+ it("returns html markup that contains a tag link for the favicon", () => {
+ const { html, css, assetMap, store } = fakeArguments;
+ const htmlMarkup = renderIndex(html, css, assetMap, store);
+ expect(htmlMarkup).toContain(
+ ''
+ );
+ });
+});
diff --git a/__test__/server/texter.test.js b/__test__/server/texter.test.js
index e5fa47c54..ffd46973a 100644
--- a/__test__/server/texter.test.js
+++ b/__test__/server/texter.test.js
@@ -1,5 +1,5 @@
/* eslint-disable no-unused-expressions, consistent-return */
-import { r } from '../../src/server/models/'
+import { r } from "../../src/server/models/";
import {
runGql,
setupTest,
@@ -15,188 +15,211 @@ import {
createScript,
startCampaign,
getCampaignContact
-} from '../test_helpers'
-import waitForExpect from 'wait-for-expect'
-
-let testAdminUser
-let testInvite
-let testOrganization
-let testCampaign
-let testTexterUser
-let testTexterUser2
-let testContacts
-let testContact
-let assignmentId
+} from "../test_helpers";
+import waitForExpect from "wait-for-expect";
+
+let testAdminUser;
+let testInvite;
+let testOrganization;
+let testCampaign;
+let testTexterUser;
+let testTexterUser2;
+let testContacts;
+let testContact;
+let assignmentId;
beforeEach(async () => {
// Set up an entire working campaign
- await setupTest()
- testAdminUser = await createUser()
- testInvite = await createInvite()
- testOrganization = await createOrganization(testAdminUser, testInvite)
- testCampaign = await createCampaign(testAdminUser, testOrganization)
- testContacts = await createContacts(testCampaign, 100)
- testContact = testContacts[0]
- testTexterUser = await createTexter(testOrganization)
- testTexterUser2 = await createTexter(testOrganization)
-
- await assignTexter(testAdminUser, testTexterUser, testCampaign)
-
- const dbCampaignContact = await getCampaignContact(testContact.id)
- assignmentId = dbCampaignContact.assignment_id
- await createScript(testAdminUser, testCampaign)
- await startCampaign(testAdminUser, testCampaign)
-}, global.DATABASE_SETUP_TEARDOWN_TIMEOUT)
+ await setupTest();
+ testAdminUser = await createUser();
+ testInvite = await createInvite();
+ testOrganization = await createOrganization(testAdminUser, testInvite);
+ testCampaign = await createCampaign(testAdminUser, testOrganization);
+ testContacts = await createContacts(testCampaign, 100);
+ testContact = testContacts[0];
+ testTexterUser = await createTexter(testOrganization);
+ testTexterUser2 = await createTexter(testOrganization);
+
+ await assignTexter(testAdminUser, testTexterUser, testCampaign);
+
+ const dbCampaignContact = await getCampaignContact(testContact.id);
+ assignmentId = dbCampaignContact.assignment_id;
+ await createScript(testAdminUser, testCampaign);
+ await startCampaign(testAdminUser, testCampaign);
+}, global.DATABASE_SETUP_TEARDOWN_TIMEOUT);
afterEach(async () => {
- await cleanupTest()
- if (r.redis) r.redis.flushdb()
-}, global.DATABASE_SETUP_TEARDOWN_TIMEOUT)
+ await cleanupTest();
+ if (r.redis) r.redis.flushdb();
+}, global.DATABASE_SETUP_TEARDOWN_TIMEOUT);
-it('should send an inital message to test contacts', async () => {
+it("should send an inital message to test contacts", async () => {
const {
query: [getContacts, getContactsVars],
mutations
- } = getGql('../src/containers/TexterTodo', {
- messageStatus: 'needsMessage',
+ } = getGql("../src/containers/TexterTodo", {
+ messageStatus: "needsMessage",
params: { assignmentId }
- })
+ });
- const contactsResult = await runGql(getContacts, getContactsVars, testTexterUser)
+ const contactsResult = await runGql(
+ getContacts,
+ getContactsVars,
+ testTexterUser
+ );
const [getAssignmentContacts, assignVars] = mutations.getAssignmentContacts(
contactsResult.data.assignment.contacts.map(e => e.id),
false
- )
+ );
- const ret2 = await runGql(getAssignmentContacts, assignVars, testTexterUser)
- const contact = ret2.data.getAssignmentContacts[0]
+ const ret2 = await runGql(getAssignmentContacts, assignVars, testTexterUser);
+ const contact = ret2.data.getAssignmentContacts[0];
const message = {
contactNumber: contact.cell,
userId: testTexterUser.id,
- text: 'test text',
+ text: "test text",
assignmentId
- }
+ };
- const [messageMutation, messageVars] = mutations.sendMessage(message, contact.id)
+ const [messageMutation, messageVars] = mutations.sendMessage(
+ message,
+ contact.id
+ );
- const messageResult = await runGql(messageMutation, messageVars, testTexterUser)
- const campaignContact = messageResult.data.sendMessage
+ const messageResult = await runGql(
+ messageMutation,
+ messageVars,
+ testTexterUser
+ );
+ const campaignContact = messageResult.data.sendMessage;
// These things are expected to be returned from the sendMessage mutation
- expect(campaignContact.messageStatus).toBe('messaged')
- expect(campaignContact.messages.length).toBe(1)
- expect(campaignContact.messages[0].text).toBe(message.text)
+ expect(campaignContact.messageStatus).toBe("messaged");
+ expect(campaignContact.messages.length).toBe(1);
+ expect(campaignContact.messages[0].text).toBe(message.text);
const expectedDbMessage = {
// user_id: testTexterUser.id, //FUTURE
contact_number: testContact.cell,
text: message.text,
- assignment_id: assignmentId,
+ assignment_id: assignmentId
// campaign_contact_id: testContact.id //FUTURE
- }
+ };
// wait for fakeservice to mark the message as sent
await waitForExpect(async () => {
- const dbMessage = await r.knex('message')
- expect(dbMessage.length).toEqual(1)
+ const dbMessage = await r.knex("message");
+ expect(dbMessage.length).toEqual(1);
expect(dbMessage[0]).toEqual(
expect.objectContaining({
- send_status: 'SENT',
+ send_status: "SENT",
...expectedDbMessage
})
- )
- const dbCampaignContact = await getCampaignContact(testContact.id)
- expect(dbCampaignContact.message_status).toBe('messaged')
- })
+ );
+ const dbCampaignContact = await getCampaignContact(testContact.id);
+ expect(dbCampaignContact.message_status).toBe("messaged");
+ });
// Refetch the contacts via gql to check the caching
- const ret3 = await runGql(getAssignmentContacts, assignVars, testTexterUser)
- expect(ret3.data.getAssignmentContacts[0].messageStatus).toEqual('messaged')
-})
+ const ret3 = await runGql(getAssignmentContacts, assignVars, testTexterUser);
+ expect(ret3.data.getAssignmentContacts[0].messageStatus).toEqual("messaged");
+});
-it('should be able to receive a response and reply (using fakeService)', async () => {
+it("should be able to receive a response and reply (using fakeService)", async () => {
const {
query: [getContacts, getContactsVars],
mutations
- } = getGql('../src/containers/TexterTodo', {
- messageStatus: 'needsMessage',
+ } = getGql("../src/containers/TexterTodo", {
+ messageStatus: "needsMessage",
params: { assignmentId }
- })
+ });
- const contactsResult = await runGql(getContacts, getContactsVars, testTexterUser)
+ const contactsResult = await runGql(
+ getContacts,
+ getContactsVars,
+ testTexterUser
+ );
const [getAssignmentContacts, assignVars] = mutations.getAssignmentContacts(
contactsResult.data.assignment.contacts.map(e => e.id),
false
- )
+ );
- const ret2 = await runGql(getAssignmentContacts, assignVars, testTexterUser)
- const contact = ret2.data.getAssignmentContacts[0]
+ const ret2 = await runGql(getAssignmentContacts, assignVars, testTexterUser);
+ const contact = ret2.data.getAssignmentContacts[0];
const message = {
contactNumber: contact.cell,
userId: testTexterUser.id,
- text: 'test text autorespond',
+ text: "test text autorespond",
assignmentId
- }
+ };
- const [messageMutation, messageVars] = mutations.sendMessage(message, contact.id)
+ const [messageMutation, messageVars] = mutations.sendMessage(
+ message,
+ contact.id
+ );
- await runGql(messageMutation, messageVars, testTexterUser)
+ await runGql(messageMutation, messageVars, testTexterUser);
// wait for fakeservice to autorespond
await waitForExpect(async () => {
- const dbMessage = await r.knex('message')
- expect(dbMessage.length).toEqual(2)
+ const dbMessage = await r.knex("message");
+ expect(dbMessage.length).toEqual(2);
expect(dbMessage[1]).toEqual(
expect.objectContaining({
- send_status: 'DELIVERED',
+ send_status: "DELIVERED",
text: `responding to ${message.text}`,
// user_id: testTexterUser.id, //FUTURE
contact_number: testContact.cell,
- assignment_id: assignmentId,
+ assignment_id: assignmentId
// campaign_contact_id: testContact.id //FUTURE
})
- )
- })
+ );
+ });
await waitForExpect(async () => {
- const dbCampaignContact = await getCampaignContact(testContact.id)
- expect(dbCampaignContact.message_status).toBe('needsResponse')
- })
+ const dbCampaignContact = await getCampaignContact(testContact.id);
+ expect(dbCampaignContact.message_status).toBe("needsResponse");
+ });
// Refetch the contacts via gql to check the caching
- const ret3 = await runGql(getAssignmentContacts, assignVars, testTexterUser)
- expect(ret3.data.getAssignmentContacts[0].messageStatus).toEqual('needsResponse')
+ const ret3 = await runGql(getAssignmentContacts, assignVars, testTexterUser);
+ expect(ret3.data.getAssignmentContacts[0].messageStatus).toEqual(
+ "needsResponse"
+ );
// Then we reply
const message2 = {
contactNumber: contact.cell,
userId: testTexterUser.id,
- text: 'reply',
+ text: "reply",
assignmentId
- }
+ };
- const [replyMutation, replyVars] = mutations.sendMessage(message2, contact.id)
+ const [replyMutation, replyVars] = mutations.sendMessage(
+ message2,
+ contact.id
+ );
- await runGql(replyMutation, replyVars, testTexterUser)
+ await runGql(replyMutation, replyVars, testTexterUser);
// wait for fakeservice to mark the message as sent
await waitForExpect(async () => {
- const dbMessage = await r.knex('message')
- expect(dbMessage.length).toEqual(3)
+ const dbMessage = await r.knex("message");
+ expect(dbMessage.length).toEqual(3);
expect(dbMessage[2]).toEqual(
expect.objectContaining({
- send_status: 'SENT'
+ send_status: "SENT"
})
- )
- const dbCampaignContact = await getCampaignContact(testContact.id)
- expect(dbCampaignContact.message_status).toBe('convo')
- })
+ );
+ const dbCampaignContact = await getCampaignContact(testContact.id);
+ expect(dbCampaignContact.message_status).toBe("convo");
+ });
// Refetch the contacts via gql to check the caching
- const ret4 = await runGql(getAssignmentContacts, assignVars, testTexterUser)
- expect(ret4.data.getAssignmentContacts[0].messageStatus).toEqual('convo')
-})
+ const ret4 = await runGql(getAssignmentContacts, assignVars, testTexterUser);
+ expect(ret4.data.getAssignmentContacts[0].messageStatus).toEqual("convo");
+});
diff --git a/__test__/setup.js b/__test__/setup.js
index 84aa76ff9..814fd5021 100644
--- a/__test__/setup.js
+++ b/__test__/setup.js
@@ -1,6 +1,4 @@
-import { configure } from 'enzyme'
-import Adapter from 'enzyme-adapter-react-15'
-
-
-configure({ adapter: new Adapter() })
+import { configure } from "enzyme";
+import Adapter from "enzyme-adapter-react-15";
+configure({ adapter: new Adapter() });
diff --git a/__test__/sum.js b/__test__/sum.js
index b73a9313d..e335e1652 100644
--- a/__test__/sum.js
+++ b/__test__/sum.js
@@ -1,4 +1,4 @@
-function sum(a,b) {
- return a+b;
+function sum(a, b) {
+ return a + b;
}
-module.exports = sum;
\ No newline at end of file
+module.exports = sum;
diff --git a/__test__/sum.test.js b/__test__/sum.test.js
index f3c6a6037..2b6c378b0 100644
--- a/__test__/sum.test.js
+++ b/__test__/sum.test.js
@@ -1,5 +1,5 @@
-const sum = require('./sum');
+const sum = require("./sum");
-test('adds 1+2 to equal 3', () => {
- expect(sum(1,2)).toBe(3);
-});
\ No newline at end of file
+test("adds 1+2 to equal 3", () => {
+ expect(sum(1, 2)).toBe(3);
+});
diff --git a/__test__/test_client_helpers.js b/__test__/test_client_helpers.js
index d5914a2d6..6e0dadea0 100644
--- a/__test__/test_client_helpers.js
+++ b/__test__/test_client_helpers.js
@@ -1,83 +1,100 @@
-import moment from 'moment-timezone'
-import {mount} from "enzyme";
-import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
-import {StyleSheetTestUtils} from 'aphrodite'
-
+import moment from "moment-timezone";
+import { mount } from "enzyme";
+import MuiThemeProvider from "material-ui/styles/MuiThemeProvider";
+import { StyleSheetTestUtils } from "aphrodite";
export function genAssignment(assignmentId, isArchived, hasContacts) {
- const contacts = []
+ const contacts = [];
if (hasContacts) {
- if (typeof hasContacts !== 'number') {
- contacts.push.apply(contacts, (new Array(hasContacts)).map((x,i) => ({ id: i })))
+ if (typeof hasContacts !== "number") {
+ contacts.push.apply(
+ contacts,
+ new Array(hasContacts).map((x, i) => ({ id: i }))
+ );
} else {
- contacts.push({ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 6 })
+ contacts.push(
+ { id: 1 },
+ { id: 2 },
+ { id: 3 },
+ { id: 4 },
+ { id: 5 },
+ { id: 6 }
+ );
}
}
const assignmentTest = {
- id: assignmentId,
- userCannedResponses: [{
- id: 78,
- title: 'user canned response',
- text: 'This is a canned response, {firstName}',
- isUserCreated: true
- }],
- campaignCannedResponses: [{
- id: 89,
- title: 'campaign canned response',
- text: 'This is a campaign canned response, {firstName}',
- isUserCreated: true
- }],
- texter: {
- id: 67,
- firstName: 'Tixer',
- lastName: 'Texterness'
- },
- campaign: {
- id: 56,
- isArchived: isArchived,
- useDynamicAssignment: false,
- organization: {
- id: 123,
- textingHoursEnforced: false,
- textingHoursStart: 9,
- textingHoursEnd: 15,
- threeClickEnabled: false,
- optOutMessage: 'We will remove you from any further communication.'
- },
- customFields: ['customField'],
- interactionSteps: [{
- id: 34,
- script: 'Will you remember to vote today? {firstName} at {customField}'
- /*question: {
+ id: assignmentId,
+ userCannedResponses: [
+ {
+ id: 78,
+ title: "user canned response",
+ text: "This is a canned response, {firstName}",
+ isUserCreated: true
+ }
+ ],
+ campaignCannedResponses: [
+ {
+ id: 89,
+ title: "campaign canned response",
+ text: "This is a campaign canned response, {firstName}",
+ isUserCreated: true
+ }
+ ],
+ texter: {
+ id: 67,
+ firstName: "Tixer",
+ lastName: "Texterness"
+ },
+ campaign: {
+ id: 56,
+ isArchived: isArchived,
+ useDynamicAssignment: false,
+ organization: {
+ id: 123,
+ textingHoursEnforced: false,
+ textingHoursStart: 9,
+ textingHoursEnd: 15,
+ threeClickEnabled: false,
+ optOutMessage: "We will remove you from any further communication."
+ },
+ customFields: ["customField"],
+ interactionSteps: [
+ {
+ id: 34,
+ script:
+ "Will you remember to vote today? {firstName} at {customField}"
+ /*question: {
text
answerOptions: [{
value
}]assignmentTest
}*/
- }]
- },
- contacts: contacts,
- allContactsCount: 5
- }
- return assignmentTest
+ }
+ ]
+ },
+ contacts: contacts,
+ allContactsCount: 5
+ };
+ return assignmentTest;
}
export function contactGenerator(assignmentId, messageStatus) {
- const messages = []
- if (messageStatus !== 'needsMessage') {
+ const messages = [];
+ if (messageStatus !== "needsMessage") {
messages.push(
{
id: 90,
- createdAt: '2019-04-27T01:11:07.836Z',
- text: 'Will you remember to vote today? Same at fakecustomvalue',
+ createdAt: "2019-04-27T01:11:07.836Z",
+ text: "Will you remember to vote today? Same at fakecustomvalue",
isFromContact: false
},
{
id: 91,
- createdAt: '2019-04-27T02:22:07.836Z',
- text: 'Yes, I will vote for reals',
+ createdAt: "2019-04-27T02:22:07.836Z",
+ text: "Yes, I will vote for reals",
isFromContact: true
- })
+ }
+ );
//if (messageStatus !== 'convo') {}
}
return function createContact(id) {
@@ -85,15 +102,15 @@ export function contactGenerator(assignmentId, messageStatus) {
id: id,
assignmentId: assignmentId,
firstName: `first${id}Name`,
- lastName: 'Lastname',
- cell: '+155555550990',
+ lastName: "Lastname",
+ cell: "+155555550990",
zip: `0909${id}`,
- customFields: { customField: 'customfieldvalue' },
+ customFields: { customField: "customfieldvalue" },
optOut: null,
questionResponseValues: [],
location: {
- city: 'City',
- state: 'CA',
+ city: "City",
+ state: "CA",
timezone: {
offset: -9,
hasDST: 1
@@ -101,6 +118,6 @@ export function contactGenerator(assignmentId, messageStatus) {
},
messageStatus: messageStatus,
messages: messages
- }
- }
+ };
+ };
}
diff --git a/__test__/test_helpers.js b/__test__/test_helpers.js
index 1e408b3d8..200246975 100644
--- a/__test__/test_helpers.js
+++ b/__test__/test_helpers.js
@@ -1,13 +1,20 @@
-import _ from 'lodash'
-import { createLoaders, createTables, dropTables, User, CampaignContact, r } from '../src/server/models/'
-import { graphql } from 'graphql'
+import _ from "lodash";
+import {
+ createLoaders,
+ createTables,
+ dropTables,
+ User,
+ CampaignContact,
+ r
+} from "../src/server/models/";
+import { graphql } from "graphql";
export async function setupTest() {
- await createTables()
+ await createTables();
}
export async function cleanupTest() {
- await dropTables()
+ await dropTables();
}
export function getContext(context) {
@@ -15,10 +22,10 @@ export function getContext(context) {
...context,
req: {},
loaders: createLoaders()
- }
+ };
}
-import loadData from '../src/containers/hoc/load-data'
-jest.mock('../src/containers/hoc/load-data')
+import loadData from "../src/containers/hoc/load-data";
+jest.mock("../src/containers/hoc/load-data");
/* Used to get graphql queries from components.
* Because of some limitations with the jest require cache that
* I can't find a way of getting around, it should only be called once
@@ -27,154 +34,162 @@ jest.mock('../src/containers/hoc/load-data')
* The query it returns will be that of the requested component, but
* the mutations will be merged from the component and its children.
*/
-export function getGql(componentPath, props, dataKey='data') {
- require(componentPath) // eslint-disable-line
- const { mapQueriesToProps } = _.last(loadData.mock.calls)[1]
+export function getGql(componentPath, props, dataKey = "data") {
+ require(componentPath); // eslint-disable-line
+ const { mapQueriesToProps } = _.last(loadData.mock.calls)[1];
const mutations = loadData.mock.calls.reduce((acc, mapping) => {
- if (!mapping[1].mapMutationsToProps) return acc
+ if (!mapping[1].mapMutationsToProps) return acc;
return {
...acc,
..._.mapValues(
mapping[1].mapMutationsToProps({ ownProps: props }),
mutation => (...params) => {
- const m = mutation(...params)
- return [m.mutation.loc.source.body, m.variables]
+ const m = mutation(...params);
+ return [m.mutation.loc.source.body, m.variables];
}
)
- }
- }, {})
+ };
+ }, {});
- let query
+ let query;
if (mapQueriesToProps) {
- const data = mapQueriesToProps({ ownProps: props })
- query = [data[dataKey].query.loc.source.body, data[dataKey].variables]
+ const data = mapQueriesToProps({ ownProps: props });
+ query = [data[dataKey].query.loc.source.body, data[dataKey].variables];
}
- return { query, mutations }
+ return { query, mutations };
}
export async function createUser(
userInfo = {
- auth0_id: 'test123',
- first_name: 'TestUserFirst',
- last_name: 'TestUserLast',
- cell: '555-555-5555',
- email: 'testuser@example.com'
+ auth0_id: "test123",
+ first_name: "TestUserFirst",
+ last_name: "TestUserLast",
+ cell: "555-555-5555",
+ email: "testuser@example.com"
}
) {
- const user = new User(userInfo)
- await user.save()
- return user
+ const user = new User(userInfo);
+ await user.save();
+ return user;
}
-export async function createContacts(campaign, count=1) {
- const campaignId = campaign.id
- const contacts = []
- for (let i=0; i`
- const rootValue = {}
+ const rootValue = {};
const campaignEditQuery = `
mutation editCampaign($campaignId: String!, $campaign: CampaignInput!) {
editCampaign(id: $campaignId, campaign: $campaign) {
id
}
- }`
- const context = getContext({ user: admin })
- const updateCampaign = Object.assign({}, campaign)
- const campaignId = updateCampaign.id
+ }`;
+ const context = getContext({ user: admin });
+ const updateCampaign = Object.assign({}, campaign);
+ const campaignId = updateCampaign.id;
updateCampaign.texters = assignments || [
{
id: user.id
}
- ]
- delete updateCampaign.id
- delete updateCampaign.contacts
+ ];
+ delete updateCampaign.id;
+ delete updateCampaign.contacts;
const variables = {
campaignId,
campaign: updateCampaign
- }
- return await graphql(mySchema, campaignEditQuery, rootValue, context, variables)
+ };
+ return await graphql(
+ mySchema,
+ campaignEditQuery,
+ rootValue,
+ context,
+ variables
+ );
}
export async function sendMessage(campaignContactId, user, message) {
- const rootValue = {}
+ const rootValue = {};
const query = `
mutation sendMessage($message: MessageInput!, $campaignContactId: String!) {
sendMessage(message: $message, campaignContactId: $campaignContactId) {
@@ -267,99 +293,126 @@ export async function sendMessage(campaignContactId, user, message) {
isFromContact
}
}
- }`
- const context = getContext({ user: user })
+ }`;
+ const context = getContext({ user: user });
const variables = {
message,
campaignContactId
- }
- return await graphql(mySchema, query, rootValue, context, variables)
+ };
+ return await graphql(mySchema, query, rootValue, context, variables);
}
-export function buildScript(steps=2) {
+export function buildScript(steps = 2) {
const createSteps = (step, max) => {
if (max <= step) {
- return []
+ return [];
}
- return [{
- id: 'new'+step,
- questionText: 'hmm' + step,
- script: (step === 1 ? '{lastName}' : (
- step === 0 ? 'autorespond {zip}' : ('Step Script ' + step))),
- answerOption: 'hmm' + step,
- answerActions: '',
- parentInteractionId: (step > 0 ? ('new' + (step - 1)) : null),
- isDeleted: false,
- interactionSteps: createSteps(step+1, max)
- }]
- }
- return createSteps(0, steps)
+ return [
+ {
+ id: "new" + step,
+ questionText: "hmm" + step,
+ script:
+ step === 1
+ ? "{lastName}"
+ : step === 0
+ ? "autorespond {zip}"
+ : "Step Script " + step,
+ answerOption: "hmm" + step,
+ answerActions: "",
+ parentInteractionId: step > 0 ? "new" + (step - 1) : null,
+ isDeleted: false,
+ interactionSteps: createSteps(step + 1, max)
+ }
+ ];
+ };
+ return createSteps(0, steps);
}
-export async function createScript(admin, campaign, interactionSteps, steps=2) {
- const rootValue = {}
+export async function createScript(
+ admin,
+ campaign,
+ interactionSteps,
+ steps = 2
+) {
+ const rootValue = {};
const campaignEditQuery = `
mutation editCampaign($campaignId: String!, $campaign: CampaignInput!) {
editCampaign(id: $campaignId, campaign: $campaign) {
id
}
- }`
+ }`;
// function to create a recursive set of steps of arbitrary depth
- let builtInteractionSteps
+ let builtInteractionSteps;
if (!interactionSteps) {
- builtInteractionSteps = buildScript(steps)
+ builtInteractionSteps = buildScript(steps);
}
- const context = getContext({ user: admin })
- const campaignId = campaign.id
+ const context = getContext({ user: admin });
+ const campaignId = campaign.id;
const variables = {
campaignId,
campaign: {
- interactionSteps: (interactionSteps || builtInteractionSteps[0])
+ interactionSteps: interactionSteps || builtInteractionSteps[0]
}
- }
- return await graphql(mySchema, campaignEditQuery, rootValue, context, variables)
+ };
+ return await graphql(
+ mySchema,
+ campaignEditQuery,
+ rootValue,
+ context,
+ variables
+ );
}
export async function createCannedResponses(admin, campaign, cannedResponses) {
// cannedResponses: {title, text}
- const rootValue = {}
+ const rootValue = {};
const campaignEditQuery = `
mutation editCampaign($campaignId: String!, $campaign: CampaignInput!) {
editCampaign(id: $campaignId, campaign: $campaign) {
id
}
- }`
- const context = getContext({ user: admin })
- const campaignId = campaign.id
+ }`;
+ const context = getContext({ user: admin });
+ const campaignId = campaign.id;
const variables = {
campaignId,
campaign: {
cannedResponses
}
- }
- return await graphql(mySchema, campaignEditQuery, rootValue, context, variables)
-
+ };
+ return await graphql(
+ mySchema,
+ campaignEditQuery,
+ rootValue,
+ context,
+ variables
+ );
}
-
-jest.mock('../src/server/mail')
+jest.mock("../src/server/mail");
export async function startCampaign(admin, campaign) {
- const rootValue = {}
+ const rootValue = {};
const startCampaignQuery = `mutation startCampaign($campaignId: String!) {
startCampaign(id: $campaignId) {
id
}
- }`
- const context = getContext({ user: admin })
- const variables = { campaignId: campaign.id }
- return await graphql(mySchema, startCampaignQuery, rootValue, context, variables)
+ }`;
+ const context = getContext({ user: admin });
+ const variables = { campaignId: campaign.id };
+ return await graphql(
+ mySchema,
+ startCampaignQuery,
+ rootValue,
+ context,
+ variables
+ );
}
export async function getCampaignContact(id) {
return await r
- .knex('campaign_contact')
- .where({ id })
- .first()
+ .knex("campaign_contact")
+ .where({ id })
+ .first();
}
diff --git a/__test__/workers/assign-texters.test.js b/__test__/workers/assign-texters.test.js
index dc80e5b6c..003a118d7 100644
--- a/__test__/workers/assign-texters.test.js
+++ b/__test__/workers/assign-texters.test.js
@@ -1,54 +1,73 @@
-import {assignTexters} from '../../src/workers/jobs'
-import {r, Campaign, CampaignContact, JobRequest, Organization, User, ZipCode} from '../../src/server/models'
-import {setupTest, cleanupTest} from "../test_helpers";
+import { assignTexters } from "../../src/workers/jobs";
+import {
+ r,
+ Campaign,
+ CampaignContact,
+ JobRequest,
+ Organization,
+ User,
+ ZipCode
+} from "../../src/server/models";
+import { setupTest, cleanupTest } from "../test_helpers";
-describe('test texter assignment in dynamic mode', () => {
-
- beforeAll(async () => await setupTest(), global.DATABASE_SETUP_TEARDOWN_TIMEOUT)
- afterAll(async () => await cleanupTest(), global.DATABASE_SETUP_TEARDOWN_TIMEOUT)
+describe("test texter assignment in dynamic mode", () => {
+ beforeAll(
+ async () => await setupTest(),
+ global.DATABASE_SETUP_TEARDOWN_TIMEOUT
+ );
+ afterAll(
+ async () => await cleanupTest(),
+ global.DATABASE_SETUP_TEARDOWN_TIMEOUT
+ );
const testOrg = new Organization({
- id: '7777777',
+ id: "7777777",
texting_hours_enforced: false,
texting_hours_start: 9,
- texting_hours_end: 14,
- name: 'Test Organization'
- })
+ texting_hours_end: 14,
+ name: "Test Organization"
+ });
const testCampaign = new Campaign({
organization_id: testOrg.id,
- id: '7777777',
+ id: "7777777",
use_dynamic_assignment: true
- })
+ });
const texterInfo = [
- {
- id: '1',
- auth0_id: 'aaa',
- first_name: 'Ruth',
- last_name: 'Bader',
- cell: '9999999999',
- email: 'rbg@example.com',
- },
- {
- id: '2',
- auth0_id: 'bbb',
- first_name: 'Elena',
- last_name: 'Kagan',
- cell: '8888888888',
- email: 'ek@example.com'
- }
- ]
+ {
+ id: "1",
+ auth0_id: "aaa",
+ first_name: "Ruth",
+ last_name: "Bader",
+ cell: "9999999999",
+ email: "rbg@example.com"
+ },
+ {
+ id: "2",
+ auth0_id: "bbb",
+ first_name: "Elena",
+ last_name: "Kagan",
+ cell: "8888888888",
+ email: "ek@example.com"
+ }
+ ];
- const contactInfo = ['1111111111','2222222222','3333333333','4444444444','5555555555']
+ const contactInfo = [
+ "1111111111",
+ "2222222222",
+ "3333333333",
+ "4444444444",
+ "5555555555"
+ ];
- it('assigns no contacts to texters in dynamic assignment mode', async() => {
- const organization = await Organization.save(testOrg)
- const campaign = await Campaign.save(testCampaign)
- contactInfo.map((contact) => {
- CampaignContact.save({cell: contact, campaign_id: campaign.id})
- })
- texterInfo.map(async(texter) => {
+ it("assigns no contacts to texters in dynamic assignment mode", async () => {
+ const organization = await Organization.save(testOrg);
+ const campaign = await Campaign.save(testCampaign);
+ contactInfo.map(contact => {
+ CampaignContact.save({ cell: contact, campaign_id: campaign.id });
+ });
+ texterInfo.map(async texter => {
await User.save({
id: texter.id,
auth0_id: texter.auth0_id,
@@ -56,59 +75,64 @@ describe('test texter assignment in dynamic mode', () => {
last_name: texter.last_name,
cell: texter.cell,
email: texter.email
- })
- })
- const payload = '{"id": "3","texters":[{"id":"1","needsMessageCount":5,"maxContacts":"","contactsCount":0},{"id":"2","needsMessageCount":5,"maxContacts":"0","contactsCount":0}]}'
+ });
+ });
+ const payload =
+ '{"id": "3","texters":[{"id":"1","needsMessageCount":5,"maxContacts":"","contactsCount":0},{"id":"2","needsMessageCount":5,"maxContacts":"0","contactsCount":0}]}';
const job = new JobRequest({
campaign_id: testCampaign.id,
payload: payload,
queue_name: "3:edit_campaign",
- job_type: 'assign_texters',
- })
- await assignTexters(job)
- const result = await r.knex('campaign_contact')
- .where({campaign_id: campaign.id})
- .whereNotNull('assignment_id')
- .count()
- const assignedTextersCount = result[0]["count"]
- expect(assignedTextersCount).toEqual("0")
- })
+ job_type: "assign_texters"
+ });
+ await assignTexters(job);
+ const result = await r
+ .knex("campaign_contact")
+ .where({ campaign_id: campaign.id })
+ .whereNotNull("assignment_id")
+ .count();
+ const assignedTextersCount = result[0]["count"];
+ expect(assignedTextersCount).toEqual("0");
+ });
- it('supports saving null or zero maxContacts', async() => {
- const zero = await r.knex('assignment')
- .where({campaign_id: testCampaign.id, user_id: "2"})
- .select('max_contacts')
- const blank = await r.knex('assignment')
- .where({campaign_id: testCampaign.id, user_id: "1"})
- .select('max_contacts')
- const maxContactsZero = zero[0]["max_contacts"]
- const maxContactsBlank = blank[0]["max_contacts"]
- expect(maxContactsZero).toEqual(0)
- expect(maxContactsBlank).toEqual(null)
+ it("supports saving null or zero maxContacts", async () => {
+ const zero = await r
+ .knex("assignment")
+ .where({ campaign_id: testCampaign.id, user_id: "2" })
+ .select("max_contacts");
+ const blank = await r
+ .knex("assignment")
+ .where({ campaign_id: testCampaign.id, user_id: "1" })
+ .select("max_contacts");
+ const maxContactsZero = zero[0]["max_contacts"];
+ const maxContactsBlank = blank[0]["max_contacts"];
+ expect(maxContactsZero).toEqual(0);
+ expect(maxContactsBlank).toEqual(null);
+ });
- })
-
- it('updates max contacts when nothing else changes', async() => {
- const payload = '{"id": "3","texters":[{"id":"1","needsMessageCount":0,"maxContacts":"10","contactsCount":0},{"id":"2","needsMessageCount":5,"maxContacts":"15","contactsCount":0}]}'
+ it("updates max contacts when nothing else changes", async () => {
+ const payload =
+ '{"id": "3","texters":[{"id":"1","needsMessageCount":0,"maxContacts":"10","contactsCount":0},{"id":"2","needsMessageCount":5,"maxContacts":"15","contactsCount":0}]}';
const job = new JobRequest({
campaign_id: testCampaign.id,
payload: payload,
queue_name: "4:edit_campaign",
- job_type: 'assign_texters',
- })
- await assignTexters(job)
- const ten = await r.knex('assignment')
- .where({campaign_id: testCampaign.id, user_id: "1"})
- .select('max_contacts')
- const fifteen = await r.knex('assignment')
- .where({campaign_id: testCampaign.id, user_id: "2"})
- .select('max_contacts')
- const maxContactsTen = ten[0]["max_contacts"]
- const maxContactsFifteen = fifteen[0]["max_contacts"]
- expect(maxContactsTen).toEqual(10)
- expect(maxContactsFifteen).toEqual(15)
- })
-
-})
+ job_type: "assign_texters"
+ });
+ await assignTexters(job);
+ const ten = await r
+ .knex("assignment")
+ .where({ campaign_id: testCampaign.id, user_id: "1" })
+ .select("max_contacts");
+ const fifteen = await r
+ .knex("assignment")
+ .where({ campaign_id: testCampaign.id, user_id: "2" })
+ .select("max_contacts");
+ const maxContactsTen = ten[0]["max_contacts"];
+ const maxContactsFifteen = fifteen[0]["max_contacts"];
+ expect(maxContactsTen).toEqual(10);
+ expect(maxContactsFifteen).toEqual(15);
+ });
+});
-// TODO: test in standard assignment mode
\ No newline at end of file
+// TODO: test in standard assignment mode
diff --git a/__test__/workers/jobs.test.js b/__test__/workers/jobs.test.js
index 3f9d73efc..8a8ab0c0b 100644
--- a/__test__/workers/jobs.test.js
+++ b/__test__/workers/jobs.test.js
@@ -1,71 +1,79 @@
-import {getTimezoneByZip} from '../../src/workers/jobs'
-import {r, ZipCode} from '../../src/server/models'
-import {setupTest, cleanupTest} from "../test_helpers";
+import { getTimezoneByZip } from "../../src/workers/jobs";
+import { r, ZipCode } from "../../src/server/models";
+import { setupTest, cleanupTest } from "../test_helpers";
-jest.mock('../../src/lib/zip-format')
-var zipFormat = require('../../src/lib/zip-format')
+jest.mock("../../src/lib/zip-format");
+var zipFormat = require("../../src/lib/zip-format");
-describe('test getTimezoneByZip', () => {
+describe("test getTimezoneByZip", () => {
+ beforeAll(
+ async () => await setupTest(),
+ global.DATABASE_SETUP_TEARDOWN_TIMEOUT
+ );
+ afterAll(
+ async () => await cleanupTest(),
+ global.DATABASE_SETUP_TEARDOWN_TIMEOUT
+ );
- beforeAll(async () => await setupTest(), global.DATABASE_SETUP_TEARDOWN_TIMEOUT)
- afterAll(async () => await cleanupTest(), global.DATABASE_SETUP_TEARDOWN_TIMEOUT)
+ it("returns timezone data from the common zipcode/timezone mappings", async () => {
+ zipFormat.zipToTimeZone.mockReturnValueOnce([0, 0, 3, 1]);
- it('returns timezone data from the common zipcode/timezone mappings', async () => {
- zipFormat.zipToTimeZone.mockReturnValueOnce([0, 0, 3, 1])
+ var good_things_come_to_those_who_wait = await getTimezoneByZip("11790");
+ expect(good_things_come_to_those_who_wait).toEqual("3_1");
+ });
- var good_things_come_to_those_who_wait = await getTimezoneByZip('11790')
- expect(good_things_come_to_those_who_wait).toEqual('3_1')
- })
+ it("does not memoize common zipcode/timezone mappings", async () => {
+ zipFormat.zipToTimeZone.mockReturnValueOnce([0, 0, 4, 1]);
- it('does not memoize common zipcode/timezone mappings', async () => {
- zipFormat.zipToTimeZone.mockReturnValueOnce([0, 0, 4, 1])
+ var future = await getTimezoneByZip("11790");
+ expect(future).toEqual("4_1");
+ });
- var future = await getTimezoneByZip('11790')
- expect(future).toEqual('4_1')
- })
+ it("does not find a zipcode in the database!", async () => {
+ zipFormat.zipToTimeZone.mockReturnValueOnce(undefined);
- it('does not find a zipcode in the database!', async () => {
- zipFormat.zipToTimeZone.mockReturnValueOnce(undefined)
+ var future = await getTimezoneByZip("11790");
+ expect(future).toEqual("");
+ });
- var future = await getTimezoneByZip('11790')
- expect(future).toEqual('')
- })
-
- it('finds a zipcode in the database and memoizes it', async () => {
- zipFormat.zipToTimeZone.mockReturnValueOnce(undefined)
+ it("finds a zipcode in the database and memoizes it", async () => {
+ zipFormat.zipToTimeZone.mockReturnValueOnce(undefined);
try {
var zipCode = new ZipCode({
- zip: '11790',
- city: 'Stony Brook',
- state: 'NY',
+ zip: "11790",
+ city: "Stony Brook",
+ state: "NY",
timezone_offset: 7,
has_dst: true,
latitude: 0,
longitude: 0
- })
- var future = await ZipCode.save(zipCode)
- expect(future).resolves
-
- future = await getTimezoneByZip('11790')
- expect(future).toEqual('7_1')
-
- future = await r.table('zip_code').getAll().delete()
- expect(future).resolves
-
- future = await r.table('zip_code').get('11790')
- expect(future).toEqual([])
-
- future = await getTimezoneByZip('11790')
- expect(future).toEqual('7_1')
- }
- finally {
- return await r.table('zip_code').getAll().delete()
+ });
+ var future = await ZipCode.save(zipCode);
+ expect(future).resolves;
+
+ future = await getTimezoneByZip("11790");
+ expect(future).toEqual("7_1");
+
+ future = await r
+ .table("zip_code")
+ .getAll()
+ .delete();
+ expect(future).resolves;
+
+ future = await r.table("zip_code").get("11790");
+ expect(future).toEqual([]);
+
+ future = await getTimezoneByZip("11790");
+ expect(future).toEqual("7_1");
+ } finally {
+ return await r
+ .table("zip_code")
+ .getAll()
+ .delete();
}
-
-
- })
-})
+ });
+});
// TODO
// 1. loadContacts with upload
diff --git a/dev-tools/babel-run-with-env.js b/dev-tools/babel-run-with-env.js
index fab9eb5f3..2afb295ac 100755
--- a/dev-tools/babel-run-with-env.js
+++ b/dev-tools/babel-run-with-env.js
@@ -1,5 +1,5 @@
#!/usr/bin/env node
-require('dotenv').config()
-require('babel-register')
-require('babel-polyfill')
-require('../' + process.argv[2])
+require("dotenv").config();
+require("babel-register");
+require("babel-polyfill");
+require("../" + process.argv[2]);
diff --git a/dev-tools/db-startup.js b/dev-tools/db-startup.js
index cdcebd012..e1a92a67a 100644
--- a/dev-tools/db-startup.js
+++ b/dev-tools/db-startup.js
@@ -1,3 +1 @@
-import '../src/server/models' // This forces Thinky to autocreate the database, tables, and indexes if they don't exist
-
-
+import "../src/server/models"; // This forces Thinky to autocreate the database, tables, and indexes if they don't exist
diff --git a/dev-tools/export-broken-interaction-steps.js b/dev-tools/export-broken-interaction-steps.js
index bd8ab88c9..832311a74 100644
--- a/dev-tools/export-broken-interaction-steps.js
+++ b/dev-tools/export-broken-interaction-steps.js
@@ -1,44 +1,52 @@
-import { r } from '../src/server/models'
-import Papa from 'papaparse'
+import { r } from "../src/server/models";
+import Papa from "papaparse";
-(async function () {
+(async function() {
try {
- const res = await r.table('question_response')
- .merge((row) => ({
- campaign_contact: r.table('campaign_contact')
- .get(row('campaign_contact_id'))
+ const res = await r
+ .table("question_response")
+ .merge(row => ({
+ campaign_contact: r
+ .table("campaign_contact")
+ .get(row("campaign_contact_id"))
}))
- .merge((row) => ({
- interaction_step: r.table('interaction_step')
- .get(row('interaction_step_id'))
+ .merge(row => ({
+ interaction_step: r
+ .table("interaction_step")
+ .get(row("interaction_step_id"))
}))
- .merge((row) => ({
- campaign: r.table('campaign')
- .get(row('campaign_contact')('campaign_id'))
+ .merge(row => ({
+ campaign: r
+ .table("campaign")
+ .get(row("campaign_contact")("campaign_id"))
}))
- .merge((row) => ({
- organization: r.table('organization')
- .get(row('campaign')('organization_id'))
+ .merge(row => ({
+ organization: r
+ .table("organization")
+ .get(row("campaign")("organization_id"))
}))
.filter({ interaction_step: null })
- .group(r.row('campaign')('id'))
- const finalResults = res.map((doc) => (
- doc.reduction.map((row) => ({
- organization: row.organization.name,
- 'campaign[title]': row.campaign.title,
- 'contact[cell]': row.campaign_contact.cell,
- 'contact[first_name]': row.campaign_contact.first_name,
- 'contact[last_name]': row.campaign_contact.last_name,
- interaction_step_id: row.interaction_step_id,
- value: row.value
- }))
- .reduce((left, right) => left.concat(right), [])
- )).reduce((left, right) => left.concat(right), [])
+ .group(r.row("campaign")("id"));
+ const finalResults = res
+ .map(doc =>
+ doc.reduction
+ .map(row => ({
+ organization: row.organization.name,
+ "campaign[title]": row.campaign.title,
+ "contact[cell]": row.campaign_contact.cell,
+ "contact[first_name]": row.campaign_contact.first_name,
+ "contact[last_name]": row.campaign_contact.last_name,
+ interaction_step_id: row.interaction_step_id,
+ value: row.value
+ }))
+ .reduce((left, right) => left.concat(right), [])
+ )
+ .reduce((left, right) => left.concat(right), []);
- console.log(finalResults[0])
- const csvResults = Papa.unparse(finalResults)
- console.log(csvResults)
+ console.log(finalResults[0]);
+ const csvResults = Papa.unparse(finalResults);
+ console.log(csvResults);
} catch (ex) {
- console.log(ex)
+ console.log(ex);
}
-})()
+})();
diff --git a/dev-tools/export-query.js b/dev-tools/export-query.js
index 52b5e3e19..ac1d929ab 100644
--- a/dev-tools/export-query.js
+++ b/dev-tools/export-query.js
@@ -1,26 +1,25 @@
-import { r } from '../src/server/models'
-import Papa from 'papaparse'
+import { r } from "../src/server/models";
+import Papa from "papaparse";
-(async function () {
+(async function() {
try {
- const res = await r.table('message')
- .eqJoin('assignment_id', r.table('assignment'))
+ const res = await r
+ .table("message")
+ .eqJoin("assignment_id", r.table("assignment"))
.zip()
- .filter({ campaign_id: process.env.CAMPAIGN_ID })
- const finalResults = res.map((row) => (
- {
- assignment_id: row.assignment_id,
- campaign_id: row.campaign_id,
- contact_number: row.contact_number,
- user_number: row.user_number,
- is_from_contact: row.is_from_contact,
- send_status: row.send_status,
- text: row.text
- }
- ))
- const csvResults = Papa.unparse(finalResults)
- console.log(csvResults)
+ .filter({ campaign_id: process.env.CAMPAIGN_ID });
+ const finalResults = res.map(row => ({
+ assignment_id: row.assignment_id,
+ campaign_id: row.campaign_id,
+ contact_number: row.contact_number,
+ user_number: row.user_number,
+ is_from_contact: row.is_from_contact,
+ send_status: row.send_status,
+ text: row.text
+ }));
+ const csvResults = Papa.unparse(finalResults);
+ console.log(csvResults);
} catch (ex) {
- console.log(ex)
+ console.log(ex);
}
-})()
+})();
diff --git a/dev-tools/generate-contacts.js b/dev-tools/generate-contacts.js
index 3ea915108..41ab0169d 100644
--- a/dev-tools/generate-contacts.js
+++ b/dev-tools/generate-contacts.js
@@ -1,10 +1,10 @@
-import faker from 'faker'
-import json2csv from 'json2csv'
+import faker from "faker";
+import json2csv from "json2csv";
-const fields = ['firstName', 'lastName', 'cell', 'companyName', 'city', 'zip']
-const numContacts = 10000
+const fields = ["firstName", "lastName", "cell", "companyName", "city", "zip"];
+const numContacts = 10000;
-const data = []
+const data = [];
for (let index = 0; index < numContacts; index++) {
data.push({
firstName: faker.name.firstName(),
@@ -13,8 +13,8 @@ for (let index = 0; index < numContacts; index++) {
companyName: faker.company.companyName(),
city: faker.address.city(),
zip: faker.address.zipCode()
- })
+ });
}
-const csvFile = json2csv({ data, fields })
-console.log(csvFile)
+const csvFile = json2csv({ data, fields });
+console.log(csvFile);
diff --git a/dev-tools/jest.transform.js b/dev-tools/jest.transform.js
index e390c671f..b9a324bf2 100644
--- a/dev-tools/jest.transform.js
+++ b/dev-tools/jest.transform.js
@@ -1,6 +1,6 @@
#!/usr/bin/env node
-var config = require('dotenv').config()
-require('babel-register')
-require('babel-polyfill')
-require('../' + process.argv[2])
-model.exports = require('babel-jest').createTransformer(config)
+var config = require("dotenv").config();
+require("babel-register");
+require("babel-polyfill");
+require("../" + process.argv[2]);
+model.exports = require("babel-jest").createTransformer(config);
diff --git a/jest.config.e2e.js b/jest.config.e2e.js
index b7cf22f1b..edaae8e03 100644
--- a/jest.config.e2e.js
+++ b/jest.config.e2e.js
@@ -1,22 +1,25 @@
-const _ = require('lodash')
-const config = require('./jest.config')
+const _ = require("lodash");
+const config = require("./jest.config");
const overrides = {
- setupTestFrameworkScriptFile: '/__test__/e2e/util/setup.js',
- testMatch: ['**/__test__/e2e/**/*.test.js'],
- testPathIgnorePatterns: ['/node_modules/', '/__test__/e2e/util/', '/__test__/e2e/pom/'],
+ setupTestFrameworkScriptFile: "/__test__/e2e/util/setup.js",
+ testMatch: ["**/__test__/e2e/**/*.test.js"],
+ testPathIgnorePatterns: [
+ "/node_modules/",
+ "/__test__/e2e/util/",
+ "/__test__/e2e/pom/"
+ ],
bail: true // To learn about errors sooner
-}
+};
const merges = {
// Merge in changes to deeper objects
globals: {
// This sets the BASE_URL for the target of the e2e tests (what the tests are testing)
- BASE_URL: 'localhost:3000'
+ BASE_URL: "localhost:3000"
}
-}
+};
-module.exports = _
- .chain(config)
+module.exports = _.chain(config)
.assign(overrides)
.merge(merges)
- .value()
+ .value();
diff --git a/jest.config.js b/jest.config.js
index 89937ad18..08b4e3d03 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -8,37 +8,39 @@ module.exports = {
client: "pg",
connection: {
host: "127.0.0.1",
- "port": "5432",
- "database": "spoke_test",
- "password": "spoke_test",
- "user": "spoke_test"
- },
+ port: "5432",
+ database: "spoke_test",
+ password: "spoke_test",
+ user: "spoke_test"
+ }
}),
JOBS_SYNC: "1",
JOBS_SAME_PROCESS: "1",
RETHINK_KNEX_NOREFS: "1", // avoids db race conditions
- DEFAULT_SERVICE: 'fakeservice',
- DST_REFERENCE_TIMEZONE: 'America/New_York',
+ DEFAULT_SERVICE: "fakeservice",
+ DST_REFERENCE_TIMEZONE: "America/New_York",
DATABASE_SETUP_TEARDOWN_TIMEOUT: 60000,
- PASSPORT_STRATEGY: 'local',
- SESSION_SECRET: 'it is JUST a test! -- it better be!',
- TEST_ENVIRONMENT: '1'
+ PASSPORT_STRATEGY: "local",
+ SESSION_SECRET: "it is JUST a test! -- it better be!",
+ TEST_ENVIRONMENT: "1"
},
- moduleFileExtensions: [
- "js",
- "jsx"
- ],
+ moduleFileExtensions: ["js", "jsx"],
transform: {
".*.js": "/node_modules/babel-jest"
},
- moduleDirectories: [
- "node_modules"
- ],
+ moduleDirectories: ["node_modules"],
moduleNameMapper: {
- "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/fileMock.js",
+ "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
+ "/__mocks__/fileMock.js",
"\\.(css|less)$": "/__mocks__/styleMock.js"
},
- collectCoverageFrom: ["**/*.{js,jsx}", "!**/node_modules/**", "!**/__test__/**", "!**/deploy/**", "!**/coverage/**"],
+ collectCoverageFrom: [
+ "**/*.{js,jsx}",
+ "!**/node_modules/**",
+ "!**/__test__/**",
+ "!**/deploy/**",
+ "!**/coverage/**"
+ ],
setupTestFrameworkScriptFile: "/__test__/setup.js",
testPathIgnorePatterns: ["/node_modules/", "/__test__/e2e/"]
};
diff --git a/jest.config.sqlite.js b/jest.config.sqlite.js
index 872856528..d80fd209f 100644
--- a/jest.config.sqlite.js
+++ b/jest.config.sqlite.js
@@ -1,6 +1,6 @@
-module.exports = require('./jest.config')
+module.exports = require("./jest.config");
module.exports.globals.DB_JSON = JSON.stringify({
client: "sqlite3",
- connection: {filename:"./test.sqlite"},
+ connection: { filename: "./test.sqlite" },
defaultsUnsupported: true
-})
+});
diff --git a/knexfile.env.js b/knexfile.env.js
index a7588e9e0..8d69eb435 100644
--- a/knexfile.env.js
+++ b/knexfile.env.js
@@ -1,7 +1,7 @@
-require('dotenv').config()
+require("dotenv").config();
// environment variables will be populated from above, and influence the knex-connect import
-var config = require('./src/server/knex-connect')
+var config = require("./src/server/knex-connect");
module.exports = {
development: config,
diff --git a/lambda.js b/lambda.js
index 74d5ab1ec..8b7cafdbc 100644
--- a/lambda.js
+++ b/lambda.js
@@ -1,18 +1,18 @@
-'use strict'
-const AWS = require('aws-sdk')
-const awsServerlessExpress = require('aws-serverless-express')
-let app, server, jobs
+"use strict";
+const AWS = require("aws-sdk");
+const awsServerlessExpress = require("aws-serverless-express");
+let app, server, jobs;
try {
- app = require('./build/server/server/index')
- server = awsServerlessExpress.createServer(app.default)
- jobs = require('./build/server/workers/job-processes')
-} catch(err) {
+ app = require("./build/server/server/index");
+ server = awsServerlessExpress.createServer(app.default);
+ jobs = require("./build/server/workers/job-processes");
+} catch (err) {
if (!global.TEST_ENVIRONMENT) {
- console.error(`Unable to load built server: ${err}`)
+ console.error(`Unable to load built server: ${err}`);
}
- app = require('./src/server/index')
- server = awsServerlessExpress.createServer(app.default)
- jobs = require('./src/workers/job-processes')
+ app = require("./src/server/index");
+ server = awsServerlessExpress.createServer(app.default);
+ jobs = require("./src/workers/job-processes");
}
// NOTE: the downside of loading above is environment variables are initially loaded immediately,
@@ -21,20 +21,20 @@ try {
// See: http://docs.aws.amazon.com/lambda/latest/dg/best-practices.html#function-code
// "Separate the Lambda handler (entry point) from your core logic"
-let invocationContext = {}
-let invocationEvent = {}
-app.default.set('awsContextGetter', function(req, res) {
- return [invocationEvent, invocationContext]
-})
+let invocationContext = {};
+let invocationEvent = {};
+app.default.set("awsContextGetter", function(req, res) {
+ return [invocationEvent, invocationContext];
+});
function cleanHeaders(event) {
// X-Twilio-Body can contain unicode and disallowed chars by aws-serverless-express like "'"
// We don't need it anyway
if (event.headers) {
- delete event.headers['X-Twilio-Body']
+ delete event.headers["X-Twilio-Body"];
}
if (event.multiValueHeaders) {
- delete event.multiValueHeaders['X-Twilio-Body']
+ delete event.multiValueHeaders["X-Twilio-Body"];
}
}
@@ -43,55 +43,65 @@ exports.handler = (event, context, handleCallback) => {
// or Lambda will re-run/re-try the invocation twice:
// https://docs.aws.amazon.com/lambda/latest/dg/retries-on-errors.html
if (process.env.LAMBDA_DEBUG_LOG) {
- console.log('LAMBDA EVENT', event)
+ console.log("LAMBDA EVENT", event);
}
if (!event.command) {
// default web server stuff
- const startTime = (context.getRemainingTimeInMillis ? context.getRemainingTimeInMillis() : 0)
- invocationEvent = event
- invocationContext = context
- cleanHeaders(event)
- const webResponse = awsServerlessExpress.proxy(server, event, context)
+ const startTime = context.getRemainingTimeInMillis
+ ? context.getRemainingTimeInMillis()
+ : 0;
+ invocationEvent = event;
+ invocationContext = context;
+ cleanHeaders(event);
+ const webResponse = awsServerlessExpress.proxy(server, event, context);
if (process.env.DEBUG_SCALING) {
- const endTime = (context.getRemainingTimeInMillis ? context.getRemainingTimeInMillis() : 0)
- if ((endTime - startTime) > 3000) { //3 seconds
- console.log('SLOW_RESPONSE milliseconds:', endTime-startTime, event)
+ const endTime = context.getRemainingTimeInMillis
+ ? context.getRemainingTimeInMillis()
+ : 0;
+ if (endTime - startTime > 3000) {
+ //3 seconds
+ console.log("SLOW_RESPONSE milliseconds:", endTime - startTime, event);
}
}
- return webResponse
+ return webResponse;
} else {
// handle a custom command sent as an event
- const functionName = context.functionName
+ const functionName = context.functionName;
if (event.env) {
for (var a in event.env) {
- process.env[a] = event.env[a]
+ process.env[a] = event.env[a];
}
}
- console.log('Running ' + event.command)
+ console.log("Running " + event.command);
if (event.command in jobs) {
- const job = jobs[event.command]
+ const job = jobs[event.command];
// behavior and arguments documented here:
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Lambda.html#invoke-property
- job(event,
- function dispatcher(dataToSend, callback) {
- const lambda = new AWS.Lambda()
- return lambda.invoke({
+ job(
+ event,
+ function dispatcher(dataToSend, callback) {
+ const lambda = new AWS.Lambda();
+ return lambda.invoke(
+ {
FunctionName: functionName,
InvocationType: "Event", //asynchronous
Payload: JSON.stringify(dataToSend)
- }, function(err, dataReceived) {
+ },
+ function(err, dataReceived) {
if (err) {
- console.error('Failed to invoke Lambda job: ', err)
+ console.error("Failed to invoke Lambda job: ", err);
}
if (callback) {
- callback(err, dataReceived)
+ callback(err, dataReceived);
}
- })
- },
- handleCallback)
+ }
+ );
+ },
+ handleCallback
+ );
} else {
- console.error('Unfound command sent as a Lambda event: ' + event.command)
+ console.error("Unfound command sent as a Lambda event: " + event.command);
}
}
-}
+};
diff --git a/migrations/20190207220000_init_db.js b/migrations/20190207220000_init_db.js
index 485d840da..2e5d7e25d 100644
--- a/migrations/20190207220000_init_db.js
+++ b/migrations/20190207220000_init_db.js
@@ -2,331 +2,431 @@ const initialize = async (knex, Promise) => {
// This object's keys are table names and each key's value is a function that defines that table's schema.
const buildTableSchema = [
{
- tableName: 'user',
+ tableName: "user",
create: t => {
- t.increments('id').primary()
- t.text('auth0_id').notNullable()
- t.text('first_name').notNullable()
- t.text('last_name').notNullable()
- t.text('cell').notNullable()
- t.text('email').notNullable()
- t.timestamp('created_at').notNullable().defaultTo(knex.fn.now())
- t.text('assigned_cell')
- t.boolean('is_superadmin')
- t.boolean('terms').defaultTo(false)
+ t.increments("id").primary();
+ t.text("auth0_id").notNullable();
+ t.text("first_name").notNullable();
+ t.text("last_name").notNullable();
+ t.text("cell").notNullable();
+ t.text("email").notNullable();
+ t.timestamp("created_at")
+ .notNullable()
+ .defaultTo(knex.fn.now());
+ t.text("assigned_cell");
+ t.boolean("is_superadmin");
+ t.boolean("terms").defaultTo(false);
}
},
{
- tableName: 'pending_message_part',
+ tableName: "pending_message_part",
create: t => {
- t.increments('id').primary()
- t.text('service').notNullable()
- t.text('service_id').notNullable()
- t.text('parent_id').defaultTo('')
- t.text('service_message').notNullable()
- t.text('user_number').notNullable().defaultTo('')
- t.text('contact_number').notNullable()
- t.timestamp('created_at').defaultTo(knex.fn.now()).notNullable()
+ t.increments("id").primary();
+ t.text("service").notNullable();
+ t.text("service_id").notNullable();
+ t.text("parent_id").defaultTo("");
+ t.text("service_message").notNullable();
+ t.text("user_number")
+ .notNullable()
+ .defaultTo("");
+ t.text("contact_number").notNullable();
+ t.timestamp("created_at")
+ .defaultTo(knex.fn.now())
+ .notNullable();
- t.index('parent_id')
- t.index('service')
+ t.index("parent_id");
+ t.index("service");
}
},
{
- tableName: 'organization',
+ tableName: "organization",
create: t => {
- t.increments('id')
- t.text('uuid')
- t.text('name').notNullable()
- t.timestamp('created_at').defaultTo(knex.fn.now()).notNullable()
- t.text('features').defaultTo('')
- t.boolean('texting_hours_enforced').defaultTo(false)
- t.integer('texting_hours_start').defaultTo(9)
- t.integer('texting_hours_end').defaultTo(21)
+ t.increments("id");
+ t.text("uuid");
+ t.text("name").notNullable();
+ t.timestamp("created_at")
+ .defaultTo(knex.fn.now())
+ .notNullable();
+ t.text("features").defaultTo("");
+ t.boolean("texting_hours_enforced").defaultTo(false);
+ t.integer("texting_hours_start").defaultTo(9);
+ t.integer("texting_hours_end").defaultTo(21);
}
},
{
- tableName: 'campaign',
+ tableName: "campaign",
create: t => {
- t.increments('id')
- t.integer('organization_id').notNullable()
- t.text('title').notNullable().defaultTo('')
- t.text('description').notNullable().defaultTo('')
- t.boolean('is_started')
- t.timestamp('due_by').defaultTo(null)
- t.timestamp('created_at').notNullable().defaultTo(knex.fn.now())
- t.boolean('is_archived')
- t.boolean('use_dynamic_assignment')
- t.text('logo_image_url')
- t.text('intro_html')
- t.text('primary_color')
- t.boolean('override_organization_texting_hours').defaultTo(false)
- t.boolean('texting_hours_enforced').defaultTo(true)
- t.integer('texting_hours_start').defaultTo(9)
- t.integer('texting_hours_end').defaultTo(21)
- t.text('timezone').defaultTo('US/Eastern')
+ t.increments("id");
+ t.integer("organization_id").notNullable();
+ t.text("title")
+ .notNullable()
+ .defaultTo("");
+ t.text("description")
+ .notNullable()
+ .defaultTo("");
+ t.boolean("is_started");
+ t.timestamp("due_by").defaultTo(null);
+ t.timestamp("created_at")
+ .notNullable()
+ .defaultTo(knex.fn.now());
+ t.boolean("is_archived");
+ t.boolean("use_dynamic_assignment");
+ t.text("logo_image_url");
+ t.text("intro_html");
+ t.text("primary_color");
+ t.boolean("override_organization_texting_hours").defaultTo(false);
+ t.boolean("texting_hours_enforced").defaultTo(true);
+ t.integer("texting_hours_start").defaultTo(9);
+ t.integer("texting_hours_end").defaultTo(21);
+ t.text("timezone").defaultTo("US/Eastern");
- t.index('organization_id')
- t.foreign('organization_id').references('organization.id')
+ t.index("organization_id");
+ t.foreign("organization_id").references("organization.id");
t.integer("creator_id")
.unsigned()
.nullable()
.default(null)
.index()
- .references("user.id")
+ .references("user.id");
}
},
{
- tableName: 'assignment',
+ tableName: "assignment",
create: t => {
- t.increments('id')
- t.integer('user_id').notNullable()
- t.integer('campaign_id').notNullable()
- t.timestamp('created_at').defaultTo(knex.fn.now()).notNullable()
- t.integer('max_contacts')
+ t.increments("id");
+ t.integer("user_id").notNullable();
+ t.integer("campaign_id").notNullable();
+ t.timestamp("created_at")
+ .defaultTo(knex.fn.now())
+ .notNullable();
+ t.integer("max_contacts");
- t.index('user_id')
- t.foreign('user_id').references('user.id')
- t.index('campaign_id')
- t.foreign('campaign_id').references('campaign.id')
+ t.index("user_id");
+ t.foreign("user_id").references("user.id");
+ t.index("campaign_id");
+ t.foreign("campaign_id").references("campaign.id");
}
},
{
- tableName: 'campaign_contact',
+ tableName: "campaign_contact",
create: t => {
- t.increments('id')
- t.integer('campaign_id').notNullable()
- t.integer('assignment_id')
- t.text('external_id').notNullable().defaultTo('')
- t.text('first_name').notNullable().defaultTo('')
- t.text('last_name').notNullable().defaultTo('')
- t.text('cell').notNullable()
- t.text('zip').defaultTo('').notNullable()
- t.text('custom_fields').notNullable().defaultTo('{}')
- t.timestamp('created_at').defaultTo(knex.fn.now()).notNullable()
- t.timestamp('updated_at').defaultTo(knex.fn.now()).notNullable()
- t.enu('message_status', [
- 'needsMessage',
- 'needsResponse',
- 'convo',
- 'messaged',
- 'closed',
- 'UPDATING'
+ t.increments("id");
+ t.integer("campaign_id").notNullable();
+ t.integer("assignment_id");
+ t.text("external_id")
+ .notNullable()
+ .defaultTo("");
+ t.text("first_name")
+ .notNullable()
+ .defaultTo("");
+ t.text("last_name")
+ .notNullable()
+ .defaultTo("");
+ t.text("cell").notNullable();
+ t.text("zip")
+ .defaultTo("")
+ .notNullable();
+ t.text("custom_fields")
+ .notNullable()
+ .defaultTo("{}");
+ t.timestamp("created_at")
+ .defaultTo(knex.fn.now())
+ .notNullable();
+ t.timestamp("updated_at")
+ .defaultTo(knex.fn.now())
+ .notNullable();
+ t.enu("message_status", [
+ "needsMessage",
+ "needsResponse",
+ "convo",
+ "messaged",
+ "closed",
+ "UPDATING"
])
- .defaultTo('needsMessage')
- .notNullable()
- t.boolean('is_opted_out').defaultTo(false)
- t.text('timezone_offset').defaultTo('')
+ .defaultTo("needsMessage")
+ .notNullable();
+ t.boolean("is_opted_out").defaultTo(false);
+ t.text("timezone_offset").defaultTo("");
- t.index('assignment_id')
- t.foreign('assignment_id').references('assignment.id')
- t.index('campaign_id')
- t.foreign('campaign_id').references('campaign.id')
- t.index('cell')
- t.index(['campaign_id', 'assignment_id'], 'campaign_contact_campaign_id_assignment_id_index')
- t.index(['assignment_id', 'timezone_offset'], 'campaign_contact_assignment_id_timezone_offset_index') // See footnote ¹ for clarification on naming.
+ t.index("assignment_id");
+ t.foreign("assignment_id").references("assignment.id");
+ t.index("campaign_id");
+ t.foreign("campaign_id").references("campaign.id");
+ t.index("cell");
+ t.index(
+ ["campaign_id", "assignment_id"],
+ "campaign_contact_campaign_id_assignment_id_index"
+ );
+ t.index(
+ ["assignment_id", "timezone_offset"],
+ "campaign_contact_assignment_id_timezone_offset_index"
+ ); // See footnote ¹ for clarification on naming.
}
},
{
- tableName: 'interaction_step',
+ tableName: "interaction_step",
create: t => {
- t.increments('id')
- t.integer('campaign_id').notNullable()
- t.text('question').notNullable().defaultTo('')
- t.text('script').notNullable().defaultTo('')
- t.timestamp('created_at').notNullable().defaultTo(knex.fn.now())
+ t.increments("id");
+ t.integer("campaign_id").notNullable();
+ t.text("question")
+ .notNullable()
+ .defaultTo("");
+ t.text("script")
+ .notNullable()
+ .defaultTo("");
+ t.timestamp("created_at")
+ .notNullable()
+ .defaultTo(knex.fn.now());
// FIELDS FOR SUB-INTERACTIONS (only):
- t.integer('parent_interaction_id')
- t.text('answer_option').notNullable().defaultTo('')
- t.text('answer_actions').notNullable().defaultTo('')
- t.boolean('is_deleted').defaultTo(false).notNullable()
+ t.integer("parent_interaction_id");
+ t.text("answer_option")
+ .notNullable()
+ .defaultTo("");
+ t.text("answer_actions")
+ .notNullable()
+ .defaultTo("");
+ t.boolean("is_deleted")
+ .defaultTo(false)
+ .notNullable();
- t.index('parent_interaction_id')
- t.foreign('parent_interaction_id').references('interaction_step.id')
- t.index('campaign_id')
- t.foreign('campaign_id').references('campaign.id')
+ t.index("parent_interaction_id");
+ t.foreign("parent_interaction_id").references("interaction_step.id");
+ t.index("campaign_id");
+ t.foreign("campaign_id").references("campaign.id");
}
},
{
- tableName: 'question_response',
+ tableName: "question_response",
create: t => {
- t.increments('id')
- t.integer('campaign_contact_id').notNullable()
- t.integer('interaction_step_id').notNullable()
- t.text('value').notNullable()
- t.timestamp('created_at').notNullable().defaultTo(knex.fn.now())
+ t.increments("id");
+ t.integer("campaign_contact_id").notNullable();
+ t.integer("interaction_step_id").notNullable();
+ t.text("value").notNullable();
+ t.timestamp("created_at")
+ .notNullable()
+ .defaultTo(knex.fn.now());
- t.index('campaign_contact_id')
- t.foreign('campaign_contact_id').references('campaign_contact.id')
- t.index('interaction_step_id')
- t.foreign('interaction_step_id').references('interaction_step.id')
+ t.index("campaign_contact_id");
+ t.foreign("campaign_contact_id").references("campaign_contact.id");
+ t.index("interaction_step_id");
+ t.foreign("interaction_step_id").references("interaction_step.id");
}
},
{
- tableName: 'opt_out',
+ tableName: "opt_out",
create: t => {
- t.increments('id')
- t.text('cell').notNullable()
- t.integer('assignment_id').notNullable()
- t.integer('organization_id').notNullable()
- t.text('reason_code').notNullable().defaultTo('')
- t.timestamp('created_at').notNullable().defaultTo(knex.fn.now())
+ t.increments("id");
+ t.text("cell").notNullable();
+ t.integer("assignment_id").notNullable();
+ t.integer("organization_id").notNullable();
+ t.text("reason_code")
+ .notNullable()
+ .defaultTo("");
+ t.timestamp("created_at")
+ .notNullable()
+ .defaultTo(knex.fn.now());
- t.index('cell')
- t.index('assignment_id')
- t.foreign('assignment_id').references('assignment.id')
- t.index('organization_id')
- t.foreign('organization_id').references('organization.id')
+ t.index("cell");
+ t.index("assignment_id");
+ t.foreign("assignment_id").references("assignment.id");
+ t.index("organization_id");
+ t.foreign("organization_id").references("organization.id");
}
},
// The migrations table appears at this position in the list, but Knex manages that table itself, so it's ommitted from the schema builder
{
- tableName: 'job_request',
+ tableName: "job_request",
create: t => {
- t.increments('id')
- t.integer('campaign_id').notNullable()
- t.text('payload').notNullable()
- t.text('queue_name').notNullable()
- t.text('job_type').notNullable()
- t.text('result_message').defaultTo('')
- t.boolean('locks_queue').defaultTo(false)
- t.boolean('assigned').defaultTo(false)
- t.integer('status').defaultTo(0)
- t.timestamp('updated_at').notNullable().defaultTo(knex.fn.now())
- t.timestamp('created_at').notNullable().defaultTo(knex.fn.now())
+ t.increments("id");
+ t.integer("campaign_id").notNullable();
+ t.text("payload").notNullable();
+ t.text("queue_name").notNullable();
+ t.text("job_type").notNullable();
+ t.text("result_message").defaultTo("");
+ t.boolean("locks_queue").defaultTo(false);
+ t.boolean("assigned").defaultTo(false);
+ t.integer("status").defaultTo(0);
+ t.timestamp("updated_at")
+ .notNullable()
+ .defaultTo(knex.fn.now());
+ t.timestamp("created_at")
+ .notNullable()
+ .defaultTo(knex.fn.now());
- t.index('queue_name')
- t.foreign('campaign_id').references('campaign.id')
+ t.index("queue_name");
+ t.foreign("campaign_id").references("campaign.id");
}
},
{
- tableName: 'invite',
+ tableName: "invite",
create: t => {
- t.increments('id')
- t.boolean('is_valid').notNullable()
- t.text('hash')
- t.timestamp('created_at').notNullable().defaultTo(knex.fn.now())
+ t.increments("id");
+ t.boolean("is_valid").notNullable();
+ t.text("hash");
+ t.timestamp("created_at")
+ .notNullable()
+ .defaultTo(knex.fn.now());
- t.index('is_valid')
+ t.index("is_valid");
}
},
{
- tableName: 'canned_response',
+ tableName: "canned_response",
create: t => {
- t.increments('id')
- t.integer('campaign_id').notNullable()
- t.text('text').notNullable()
- t.text('title').notNullable()
- t.integer('user_id')
- t.timestamp('created_at').notNullable().defaultTo(knex.fn.now())
+ t.increments("id");
+ t.integer("campaign_id").notNullable();
+ t.text("text").notNullable();
+ t.text("title").notNullable();
+ t.integer("user_id");
+ t.timestamp("created_at")
+ .notNullable()
+ .defaultTo(knex.fn.now());
- t.index('campaign_id')
- t.foreign('campaign_id').references('campaign.id')
- t.index('user_id')
- t.foreign('user_id').references('user.id')
+ t.index("campaign_id");
+ t.foreign("campaign_id").references("campaign.id");
+ t.index("user_id");
+ t.foreign("user_id").references("user.id");
}
},
{
- tableName: 'user_organization',
+ tableName: "user_organization",
create: t => {
- t.increments('id')
- t.integer('user_id').notNullable()
- t.integer('organization_id').notNullable()
- t.enu('role', ['OWNER', 'ADMIN', 'SUPERVOLUNTEER', 'TEXTER']).notNullable()
+ t.increments("id");
+ t.integer("user_id").notNullable();
+ t.integer("organization_id").notNullable();
+ t.enu("role", [
+ "OWNER",
+ "ADMIN",
+ "SUPERVOLUNTEER",
+ "TEXTER"
+ ]).notNullable();
- t.index('user_id')
- t.foreign('user_id').references('user.id')
- t.index('organization_id')
- t.foreign('organization_id').references('organization.id')
- t.index(['organization_id', 'user_id'], 'user_organization_organization_id_user_id_index')
+ t.index("user_id");
+ t.foreign("user_id").references("user.id");
+ t.index("organization_id");
+ t.foreign("organization_id").references("organization.id");
+ t.index(
+ ["organization_id", "user_id"],
+ "user_organization_organization_id_user_id_index"
+ );
// rethink-knex-adapter doesn't properly preserve index names when making multicolumn indexes, so the name here ('user_organization_organization_id_user_id_index') is different from the corresponding Thinky model ('organization_user'). However, the underlying PG index that is created has the same name, so the tests pass.¹
}
},
{
- tableName: 'user_cell',
+ tableName: "user_cell",
create: t => {
- t.increments('id').primary()
- t.text('cell').notNullable()
- t.integer('user_id').notNullable()
- t.enu('service', ['nexmo', 'twilio'])
- t.boolean('is_primary')
+ t.increments("id").primary();
+ t.text("cell").notNullable();
+ t.integer("user_id").notNullable();
+ t.enu("service", ["nexmo", "twilio"]);
+ t.boolean("is_primary");
- t.foreign('user_id').references('user.id')
+ t.foreign("user_id").references("user.id");
}
},
{
- tableName: 'message',
+ tableName: "message",
create: t => {
- t.increments('id').primary()
- t.text('user_number').notNullable().defaultTo('')
- t.integer('user_id')
- t.text('contact_number').notNullable()
- t.boolean('is_from_contact').notNullable()
- t.text('text').notNullable().defaultTo('')
- t.text('service_response').notNullable().defaultTo('')
- t.integer('assignment_id').notNullable()
- t.text('service').notNullable().defaultTo('')
- t.text('service_id').notNullable().defaultTo('')
- t.enu('send_status', ['QUEUED', 'SENDING', 'SENT', 'DELIVERED', 'ERROR', 'PAUSED', 'NOT_ATTEMPTED']).notNullable()
- t.timestamp('created_at').defaultTo(knex.fn.now()).notNullable()
- t.timestamp('queued_at').defaultTo(knex.fn.now()).notNullable()
- t.timestamp('sent_at').defaultTo(knex.fn.now()).notNullable()
- t.timestamp('service_response_at').defaultTo(knex.fn.now()).notNullable()
- t.timestamp('send_before')
+ t.increments("id").primary();
+ t.text("user_number")
+ .notNullable()
+ .defaultTo("");
+ t.integer("user_id");
+ t.text("contact_number").notNullable();
+ t.boolean("is_from_contact").notNullable();
+ t.text("text")
+ .notNullable()
+ .defaultTo("");
+ t.text("service_response")
+ .notNullable()
+ .defaultTo("");
+ t.integer("assignment_id").notNullable();
+ t.text("service")
+ .notNullable()
+ .defaultTo("");
+ t.text("service_id")
+ .notNullable()
+ .defaultTo("");
+ t.enu("send_status", [
+ "QUEUED",
+ "SENDING",
+ "SENT",
+ "DELIVERED",
+ "ERROR",
+ "PAUSED",
+ "NOT_ATTEMPTED"
+ ]).notNullable();
+ t.timestamp("created_at")
+ .defaultTo(knex.fn.now())
+ .notNullable();
+ t.timestamp("queued_at")
+ .defaultTo(knex.fn.now())
+ .notNullable();
+ t.timestamp("sent_at")
+ .defaultTo(knex.fn.now())
+ .notNullable();
+ t.timestamp("service_response_at")
+ .defaultTo(knex.fn.now())
+ .notNullable();
+ t.timestamp("send_before");
- t.index('assignment_id')
- t.foreign('assignment_id').references('assignment.id')
- t.foreign('user_id').references('user.id')
- t.index('send_status')
- t.index('user_number')
- t.index('contact_number')
- t.index('service_id')
+ t.index("assignment_id");
+ t.foreign("assignment_id").references("assignment.id");
+ t.foreign("user_id").references("user.id");
+ t.index("send_status");
+ t.index("user_number");
+ t.index("contact_number");
+ t.index("service_id");
}
},
{
- tableName: 'zip_code',
+ tableName: "zip_code",
create: t => {
- t.text('zip').notNullable().primary()
- t.text('city').notNullable()
- t.text('state').notNullable()
- t.float('latitude').notNullable()
- t.float('longitude').notNullable()
- t.float('timezone_offset').notNullable()
- t.boolean('has_dst').notNullable()
+ t.text("zip")
+ .notNullable()
+ .primary();
+ t.text("city").notNullable();
+ t.text("state").notNullable();
+ t.float("latitude").notNullable();
+ t.float("longitude").notNullable();
+ t.float("timezone_offset").notNullable();
+ t.boolean("has_dst").notNullable();
}
},
{
- tableName: 'log',
+ tableName: "log",
create: t => {
- t.increments('id').primary()
- t.text('message_sid').notNullable()
- t.text('body')
- t.timestamp('created_at').notNullable().defaultTo(knex.fn.now())
+ t.increments("id").primary();
+ t.text("message_sid").notNullable();
+ t.text("body");
+ t.timestamp("created_at")
+ .notNullable()
+ .defaultTo(knex.fn.now());
}
}
- ]
+ ];
// For each table in the schema array, check if it exists and create it if necessary. Do these in order, to avoid race conditions surrounding foreign keys.
- const tablePromises = []
+ const tablePromises = [];
for (let i = 0; i < buildTableSchema.length; i++) {
- const { tableName, create } = buildTableSchema[i]
- if (!await knex.schema.hasTable(tableName)) {
+ const { tableName, create } = buildTableSchema[i];
+ if (!(await knex.schema.hasTable(tableName))) {
// create is the function that defines the table's schema. knex.schema.createTable calls it with one argument, the table instance (t).
- const result = await knex.schema.createTable(tableName, create)
- tablePromises.push(result)
+ const result = await knex.schema.createTable(tableName, create);
+ tablePromises.push(result);
}
}
- return Promise.all(tablePromises)
-}
+ return Promise.all(tablePromises);
+};
module.exports = {
up: initialize,
down: (knex, Promise) => {
// consider a rollback here that would simply drop all the tables
- Promise.resolve()
+ Promise.resolve();
}
-}
+};
/*
This table ordering is taken from __test__/test_helpers.js. Go from the bottom up.
- log
diff --git a/src/api/assignment.js b/src/api/assignment.js
index 1f16a90f2..f99880e1d 100644
--- a/src/api/assignment.js
+++ b/src/api/assignment.js
@@ -12,4 +12,4 @@ export const schema = `
campaignCannedResponses: [CannedResponse]
maxContacts: Int
}
-`
+`;
diff --git a/src/api/campaign-contact.js b/src/api/campaign-contact.js
index 1a964cb4c..e32f38e4d 100644
--- a/src/api/campaign-contact.js
+++ b/src/api/campaign-contact.js
@@ -35,5 +35,4 @@ export const schema = `
messageStatus: String
assignmentId: String
}
-`
-
+`;
diff --git a/src/api/campaign.js b/src/api/campaign.js
index 22ce9ee2b..8e83ceb9f 100644
--- a/src/api/campaign.js
+++ b/src/api/campaign.js
@@ -65,4 +65,4 @@ export const schema = `
campaigns: [Campaign]
pageInfo: PageInfo
}
-`
+`;
diff --git a/src/api/canned-response.js b/src/api/canned-response.js
index 32d834d23..dcffe5599 100644
--- a/src/api/canned-response.js
+++ b/src/api/canned-response.js
@@ -13,5 +13,4 @@ export const schema = `
text: String
isUserCreated: Boolean
}
-`
-
+`;
diff --git a/src/api/conversations.js b/src/api/conversations.js
index dc381041a..ab6016dc9 100644
--- a/src/api/conversations.js
+++ b/src/api/conversations.js
@@ -15,4 +15,4 @@ export const schema = `
conversations: [Conversation]!
pageInfo: PageInfo
}
-`
+`;
diff --git a/src/api/interaction-step.js b/src/api/interaction-step.js
index 44995091b..f385fda80 100644
--- a/src/api/interaction-step.js
+++ b/src/api/interaction-step.js
@@ -10,5 +10,4 @@ export const schema = `
answerActions: String
questionResponse(campaignContactId: String): QuestionResponse
}
-`
-
+`;
diff --git a/src/api/invite.js b/src/api/invite.js
index 2030c9ab3..085e6f4a9 100644
--- a/src/api/invite.js
+++ b/src/api/invite.js
@@ -4,5 +4,4 @@ export const schema = `
isValid: Boolean
hash: String
}
-`
-
+`;
diff --git a/src/api/message.js b/src/api/message.js
index 8640b8442..ded70b92f 100644
--- a/src/api/message.js
+++ b/src/api/message.js
@@ -9,5 +9,4 @@ export const schema = `
assignment: Assignment
campaignId: String
}
-`
-
+`;
diff --git a/src/api/opt-out.js b/src/api/opt-out.js
index 3796c6faf..539d89419 100644
--- a/src/api/opt-out.js
+++ b/src/api/opt-out.js
@@ -5,5 +5,4 @@ export const schema = `
assignment: Assignment
createdAt: Date
}
-`
-
+`;
diff --git a/src/api/organization.js b/src/api/organization.js
index ed3de0909..fb41c0783 100644
--- a/src/api/organization.js
+++ b/src/api/organization.js
@@ -12,4 +12,4 @@ export const schema = `
textingHoursStart: Int
textingHoursEnd: Int
}
-`
\ No newline at end of file
+`;
diff --git a/src/api/question-response.js b/src/api/question-response.js
index 7c72bfe18..9d43d3183 100644
--- a/src/api/question-response.js
+++ b/src/api/question-response.js
@@ -4,5 +4,4 @@ export const schema = `
value: String
question: Question
}
-`
-
+`;
diff --git a/src/api/question.js b/src/api/question.js
index bcaafff40..7366a11ea 100644
--- a/src/api/question.js
+++ b/src/api/question.js
@@ -14,5 +14,4 @@ export const schema = `
responderCount: Int
question: Question
}
-`
-
+`;
diff --git a/src/api/schema.js b/src/api/schema.js
index f5eca34ca..a70e967fd 100644
--- a/src/api/schema.js
+++ b/src/api/schema.js
@@ -1,38 +1,56 @@
-import gql from 'graphql-tag'
+import gql from "graphql-tag";
-import { schema as userSchema, resolvers as userResolvers, buildUserOrganizationQuery } from './user'
+import {
+ schema as userSchema,
+ resolvers as userResolvers,
+ buildUserOrganizationQuery
+} from "./user";
import {
schema as conversationSchema,
getConversations,
resolvers as conversationsResolver
-} from './conversations'
-import { schema as organizationSchema, resolvers as organizationResolvers } from './organization'
-import { schema as campaignSchema, resolvers as campaignResolvers } from './campaign'
+} from "./conversations";
+import {
+ schema as organizationSchema,
+ resolvers as organizationResolvers
+} from "./organization";
+import {
+ schema as campaignSchema,
+ resolvers as campaignResolvers
+} from "./campaign";
import {
schema as assignmentSchema,
resolvers as assignmentResolvers
-} from './assignment'
+} from "./assignment";
import {
schema as interactionStepSchema,
resolvers as interactionStepResolvers
-} from './interaction-step'
-import { schema as questionSchema, resolvers as questionResolvers } from './question'
+} from "./interaction-step";
+import {
+ schema as questionSchema,
+ resolvers as questionResolvers
+} from "./question";
import {
schema as questionResponseSchema,
resolvers as questionResponseResolvers
-} from './question-response'
-import { schema as optOutSchema, resolvers as optOutResolvers } from './opt-out'
-import { schema as messageSchema, resolvers as messageResolvers } from './message'
+} from "./question-response";
+import {
+ schema as optOutSchema,
+ resolvers as optOutResolvers
+} from "./opt-out";
+import {
+ schema as messageSchema,
+ resolvers as messageResolvers
+} from "./message";
import {
schema as campaignContactSchema,
resolvers as campaignContactResolvers
-} from './campaign-contact'
+} from "./campaign-contact";
import {
schema as cannedResponseSchema,
resolvers as cannedResponseResolvers
-} from './canned-response'
-import { schema as inviteSchema, resolvers as inviteResolvers } from './invite'
-
+} from "./canned-response";
+import { schema as inviteSchema, resolvers as inviteResolvers } from "./invite";
const rootSchema = gql`
input CampaignContactInput {
@@ -180,71 +198,142 @@ const rootSchema = gql`
LAST_NAME
NEWEST
OLDEST
- }
+ }
type RootQuery {
currentUser: User
- organization(id:String!, utc:String): Organization
- campaign(id:String!): Campaign
- inviteByHash(hash:String!): [Invite]
- assignment(id:String!): Assignment
+ organization(id: String!, utc: String): Organization
+ campaign(id: String!): Campaign
+ inviteByHash(hash: String!): [Invite]
+ assignment(id: String!): Assignment
organizations: [Organization]
- availableActions(organizationId:String!): [Action]
- conversations(cursor:OffsetLimitCursor!, organizationId:String!, campaignsFilter:CampaignsFilter, assignmentsFilter:AssignmentsFilter, contactsFilter:ContactsFilter, utc:String): PaginatedConversations
- campaigns(organizationId:String!, cursor:OffsetLimitCursor, campaignsFilter: CampaignsFilter): CampaignsReturn
- people(organizationId:String!, cursor:OffsetLimitCursor, campaignsFilter:CampaignsFilter, role: String, sortBy: SortPeopleBy): UsersReturn
+ availableActions(organizationId: String!): [Action]
+ conversations(
+ cursor: OffsetLimitCursor!
+ organizationId: String!
+ campaignsFilter: CampaignsFilter
+ assignmentsFilter: AssignmentsFilter
+ contactsFilter: ContactsFilter
+ utc: String
+ ): PaginatedConversations
+ campaigns(
+ organizationId: String!
+ cursor: OffsetLimitCursor
+ campaignsFilter: CampaignsFilter
+ ): CampaignsReturn
+ people(
+ organizationId: String!
+ cursor: OffsetLimitCursor
+ campaignsFilter: CampaignsFilter
+ role: String
+ sortBy: SortPeopleBy
+ ): UsersReturn
}
type RootMutation {
- createInvite(invite:InviteInput!): Invite
- createCampaign(campaign:CampaignInput!): Campaign
- editCampaign(id:String!, campaign:CampaignInput!): Campaign
- deleteJob(campaignId:String!, id:String!): JobRequest
+ createInvite(invite: InviteInput!): Invite
+ createCampaign(campaign: CampaignInput!): Campaign
+ editCampaign(id: String!, campaign: CampaignInput!): Campaign
+ deleteJob(campaignId: String!, id: String!): JobRequest
copyCampaign(id: String!): Campaign
- exportCampaign(id:String!): JobRequest
- createCannedResponse(cannedResponse:CannedResponseInput!): CannedResponse
- createOrganization(name: String!, userId: String!, inviteId: String!): Organization
+ exportCampaign(id: String!): JobRequest
+ createCannedResponse(cannedResponse: CannedResponseInput!): CannedResponse
+ createOrganization(
+ name: String!
+ userId: String!
+ inviteId: String!
+ ): Organization
joinOrganization(organizationUuid: String!): Organization
- editOrganizationRoles(organizationId: String!, userId: String!, campaignId: String, roles: [String]): Organization
- editUser(organizationId: String!, userId: Int!, userData:UserInput): User
+ editOrganizationRoles(
+ organizationId: String!
+ userId: String!
+ campaignId: String
+ roles: [String]
+ ): Organization
+ editUser(organizationId: String!, userId: Int!, userData: UserInput): User
resetUserPassword(organizationId: String!, userId: Int!): String!
changeUserPassword(userId: Int!, formData: UserPasswordChange): User
- updateTextingHours( organizationId: String!, textingHoursStart: Int!, textingHoursEnd: Int!): Organization
- updateTextingHoursEnforcement( organizationId: String!, textingHoursEnforced: Boolean!): Organization
- updateOptOutMessage( organizationId: String!, optOutMessage: String!): Organization
+ updateTextingHours(
+ organizationId: String!
+ textingHoursStart: Int!
+ textingHoursEnd: Int!
+ ): Organization
+ updateTextingHoursEnforcement(
+ organizationId: String!
+ textingHoursEnforced: Boolean!
+ ): Organization
+ updateOptOutMessage(
+ organizationId: String!
+ optOutMessage: String!
+ ): Organization
bulkSendMessages(assignmentId: Int!): [CampaignContact]
- sendMessage(message:MessageInput!, campaignContactId:String!): CampaignContact,
- createOptOut(optOut:OptOutInput!, campaignContactId:String!):CampaignContact,
- editCampaignContactMessageStatus(messageStatus: String!, campaignContactId:String!): CampaignContact,
- deleteQuestionResponses(interactionStepIds:[String], campaignContactId:String!): CampaignContact,
- updateQuestionResponses(questionResponses:[QuestionResponseInput], campaignContactId:String!): CampaignContact,
- startCampaign(id:String!): Campaign,
- archiveCampaign(id:String!): Campaign,
- archiveCampaigns(ids: [String!]): [Campaign],
- unarchiveCampaign(id:String!): Campaign,
+ sendMessage(
+ message: MessageInput!
+ campaignContactId: String!
+ ): CampaignContact
+ createOptOut(
+ optOut: OptOutInput!
+ campaignContactId: String!
+ ): CampaignContact
+ editCampaignContactMessageStatus(
+ messageStatus: String!
+ campaignContactId: String!
+ ): CampaignContact
+ deleteQuestionResponses(
+ interactionStepIds: [String]
+ campaignContactId: String!
+ ): CampaignContact
+ updateQuestionResponses(
+ questionResponses: [QuestionResponseInput]
+ campaignContactId: String!
+ ): CampaignContact
+ startCampaign(id: String!): Campaign
+ archiveCampaign(id: String!): Campaign
+ archiveCampaigns(ids: [String!]): [Campaign]
+ unarchiveCampaign(id: String!): Campaign
sendReply(id: String!, message: String!): CampaignContact
- getAssignmentContacts(assignmentId: String!, contactIds: [String], findNew: Boolean): [CampaignContact],
- findNewCampaignContact(assignmentId: String!, numberContacts: Int!): FoundContact,
- assignUserToCampaign(organizationUuid: String!, campaignId: String!): Campaign
+ getAssignmentContacts(
+ assignmentId: String!
+ contactIds: [String]
+ findNew: Boolean
+ ): [CampaignContact]
+ findNewCampaignContact(
+ assignmentId: String!
+ numberContacts: Int!
+ ): FoundContact
+ assignUserToCampaign(
+ organizationUuid: String!
+ campaignId: String!
+ ): Campaign
userAgreeTerms(userId: String!): User
- reassignCampaignContacts(organizationId:String!, campaignIdsContactIds:[CampaignIdContactId]!, newTexterUserId:String!):[CampaignIdAssignmentId],
- bulkReassignCampaignContacts(organizationId:String!, campaignsFilter:CampaignsFilter, assignmentsFilter:AssignmentsFilter, contactsFilter:ContactsFilter, newTexterUserId:String!):[CampaignIdAssignmentId],
- importCampaignScript(campaignId:String!, url:String!): Int
+ reassignCampaignContacts(
+ organizationId: String!
+ campaignIdsContactIds: [CampaignIdContactId]!
+ newTexterUserId: String!
+ ): [CampaignIdAssignmentId]
+ bulkReassignCampaignContacts(
+ organizationId: String!
+ campaignsFilter: CampaignsFilter
+ assignmentsFilter: AssignmentsFilter
+ contactsFilter: ContactsFilter
+ newTexterUserId: String!
+ ): [CampaignIdAssignmentId]
+ importCampaignScript(campaignId: String!, url: String!): Int
}
schema {
query: RootQuery
mutation: RootMutation
}
-`
+`;
export const schema = [
rootSchema,
userSchema,
organizationSchema,
- 'scalar Date',
- 'scalar JSON',
- 'scalar Phone',
+ "scalar Date",
+ "scalar JSON",
+ "scalar Phone",
campaignSchema,
assignmentSchema,
interactionStepSchema,
@@ -256,4 +345,4 @@ export const schema = [
questionSchema,
inviteSchema,
conversationSchema
-]
+];
diff --git a/src/api/user.js b/src/api/user.js
index 139849995..3439edfcc 100644
--- a/src/api/user.js
+++ b/src/api/user.js
@@ -25,4 +25,4 @@ type PaginatedUsers {
}
union UsersReturn = PaginatedUsers | UsersList
-`
+`;
diff --git a/src/client/auth-service.js b/src/client/auth-service.js
index 15281fc81..2e05c74bc 100644
--- a/src/client/auth-service.js
+++ b/src/client/auth-service.js
@@ -1,17 +1,18 @@
-import auth0 from 'auth0-js'
+import auth0 from "auth0-js";
-const baseURL = window.BASE_URL || `${window.location.protocol}//${window.location.host}`
+const baseURL =
+ window.BASE_URL || `${window.location.protocol}//${window.location.host}`;
export function logout() {
const webAuth = new auth0.WebAuth({
domain: window.AUTH0_DOMAIN,
clientID: window.AUTH0_CLIENT_ID
- })
+ });
webAuth.logout({
returnTo: `${baseURL}/logout-callback`,
client_id: window.AUTH0_CLIENT_ID
- })
+ });
}
export function login(nextUrl) {
@@ -19,10 +20,10 @@ export function login(nextUrl) {
domain: window.AUTH0_DOMAIN,
clientID: window.AUTH0_CLIENT_ID,
redirectUri: `${baseURL}/login-callback`,
- responseType: 'code',
- state: nextUrl || '/',
- scope: 'openid profile email'
- })
+ responseType: "code",
+ state: nextUrl || "/",
+ scope: "openid profile email"
+ });
- webAuth.authorize()
+ webAuth.authorize();
}
diff --git a/src/client/error-catcher.js b/src/client/error-catcher.js
index 617df0448..febc59621 100644
--- a/src/client/error-catcher.js
+++ b/src/client/error-catcher.js
@@ -1,10 +1,10 @@
-import { log } from '../lib'
+import { log } from "../lib";
-export default (error) => {
+export default error => {
if (!error) {
- log.error('Uncaught exception with null error object')
- return
+ log.error("Uncaught exception with null error object");
+ return;
}
- log.error(error)
-}
+ log.error(error);
+};
diff --git a/src/heroku/print-base-url.js b/src/heroku/print-base-url.js
index 5b1f9329d..f61e8cce4 100644
--- a/src/heroku/print-base-url.js
+++ b/src/heroku/print-base-url.js
@@ -1,7 +1,9 @@
if (process.env.BASE_URL) {
- console.log(process.env.BASE_URL)
+ console.log(process.env.BASE_URL);
} else if (process.env.HEROKU_APP_NAME) {
- console.log(`https://${process.env.HEROKU_APP_NAME}.herokuapp.com`)
+ console.log(`https://${process.env.HEROKU_APP_NAME}.herokuapp.com`);
} else {
- throw new Error('Neither BASE_URL nor HEROKU_APP_NAME environment variables are present.')
+ throw new Error(
+ "Neither BASE_URL nor HEROKU_APP_NAME environment variables are present."
+ );
}
diff --git a/src/lib/__mocks__/timezones.js b/src/lib/__mocks__/timezones.js
index 5478eb69d..d3963efca 100644
--- a/src/lib/__mocks__/timezones.js
+++ b/src/lib/__mocks__/timezones.js
@@ -1,2 +1,2 @@
-const timezones = jest.genMockFromModule('../timezones')
-module.exports = timezones
+const timezones = jest.genMockFromModule("../timezones");
+module.exports = timezones;
diff --git a/src/lib/__mocks__/tz-helpers.js b/src/lib/__mocks__/tz-helpers.js
index 86330283d..e3f934cbe 100644
--- a/src/lib/__mocks__/tz-helpers.js
+++ b/src/lib/__mocks__/tz-helpers.js
@@ -1,6 +1,5 @@
-const tzHelpers = jest.genMockFromModule('../tz-helpers')
+const tzHelpers = jest.genMockFromModule("../tz-helpers");
+tzHelpers.getProcessEnvDstReferenceTimezone = () => "America/New_York";
-tzHelpers.getProcessEnvDstReferenceTimezone = () => 'America/New_York'
-
-module.exports = tzHelpers
+module.exports = tzHelpers;
diff --git a/src/lib/__mocks__/zip-format.js b/src/lib/__mocks__/zip-format.js
index 17d4c5c73..d8e5a3fca 100644
--- a/src/lib/__mocks__/zip-format.js
+++ b/src/lib/__mocks__/zip-format.js
@@ -1,2 +1,2 @@
-const zipFormat = jest.genMockFromModule('../zip-format')
-module.exports = zipFormat
+const zipFormat = jest.genMockFromModule("../zip-format");
+module.exports = zipFormat;
diff --git a/src/lib/attributes.js b/src/lib/attributes.js
index 614f0930a..ce5c5845a 100644
--- a/src/lib/attributes.js
+++ b/src/lib/attributes.js
@@ -1,11 +1,14 @@
// Used to generate data-test attributes on non-production environments and used by end-to-end tests
export const dataTest = (value, disable) => {
- const attribute = (window.NODE_ENV !== 'production' && !disable) ? { 'data-test': value } : {}
- return attribute
-}
+ const attribute =
+ window.NODE_ENV !== "production" && !disable ? { "data-test": value } : {};
+ return attribute;
+};
export const camelCase = str => {
- return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => {
- return index == 0 ? letter.toLowerCase() : letter.toUpperCase()
- }).replace(/\s+/g, '')
-}
+ return str
+ .replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => {
+ return index == 0 ? letter.toLowerCase() : letter.toUpperCase();
+ })
+ .replace(/\s+/g, "");
+};
diff --git a/src/lib/dst-helper.js b/src/lib/dst-helper.js
index 6aec76741..08fc785bf 100644
--- a/src/lib/dst-helper.js
+++ b/src/lib/dst-helper.js
@@ -1,61 +1,78 @@
-import { DateTime, zone, DateFunctions } from 'timezonecomplete'
-
+import { DateTime, zone, DateFunctions } from "timezonecomplete";
class TimezoneOffsetAndDst {
constructor(tzOffsetMinutes: number, hasDst: boolean) {
- this.tzOffsetMinutes = tzOffsetMinutes
- this.hasDst = hasDst
+ this.tzOffsetMinutes = tzOffsetMinutes;
+ this.hasDst = hasDst;
}
}
-const _timezoneOffsetAndDst = {}
-
+const _timezoneOffsetAndDst = {};
// a class to help us know if a date is DST in a given timezone
export class DstHelper {
-
static ensureTimezoneDstCalculated(timezone) {
if (!(timezone in _timezoneOffsetAndDst)) {
// If a location has DST, the offset from GMT at January 1 and June 1 will certainly
// be different. The greater of the two is the DST offset. For our check, we
// don't care when DST is (March-October in the northern hemisphere, October-March
// in the southern hemisphere). We only care about the offset during DST.
- const januaryDate = new DateTime(new Date().getFullYear(), 1, 1, 0, 0, 0, 0, zone(timezone))
- const julyDate = new DateTime(new Date().getFullYear(), 6, 1, 0, 0, 0, 0, zone(timezone))
+ const januaryDate = new DateTime(
+ new Date().getFullYear(),
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ zone(timezone)
+ );
+ const julyDate = new DateTime(
+ new Date().getFullYear(),
+ 6,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ zone(timezone)
+ );
_timezoneOffsetAndDst[timezone] = new TimezoneOffsetAndDst(
Math.min(januaryDate.offset(), julyDate.offset()),
januaryDate.offset() !== julyDate.offset()
- )
+ );
}
}
- static getTimezoneOffsetHours(timezone:string): number {
- DstHelper.ensureTimezoneDstCalculated(timezone)
- return _timezoneOffsetAndDst[timezone].tzOffsetMinutes / 60
+ static getTimezoneOffsetHours(timezone: string): number {
+ DstHelper.ensureTimezoneDstCalculated(timezone);
+ return _timezoneOffsetAndDst[timezone].tzOffsetMinutes / 60;
}
static timezoneHasDst(timezone: string): boolean {
- DstHelper.ensureTimezoneDstCalculated(timezone)
- return _timezoneOffsetAndDst[timezone].hasDst
+ DstHelper.ensureTimezoneDstCalculated(timezone);
+ return _timezoneOffsetAndDst[timezone].hasDst;
}
static isOffsetDst(offset: number, timezone: string): boolean {
- DstHelper.ensureTimezoneDstCalculated(timezone)
+ DstHelper.ensureTimezoneDstCalculated(timezone);
// if this timezone has DST (meaning, january and july offsets were different)
// and the offset from GMT passed into this function is the same as the timezone's
// offset from GMT during DST, we return true.
- const timezoneOffsetAndDst = _timezoneOffsetAndDst[timezone]
- return timezoneOffsetAndDst.hasDst && (timezoneOffsetAndDst.tzOffsetMinutes + 60) === offset
+ const timezoneOffsetAndDst = _timezoneOffsetAndDst[timezone];
+ return (
+ timezoneOffsetAndDst.hasDst &&
+ timezoneOffsetAndDst.tzOffsetMinutes + 60 === offset
+ );
}
static isDateDst(date: Date, timezone: string): boolean {
- let d = new DateTime(date, DateFunctions.Get, zone(timezone))
- return DstHelper.isOffsetDst(d.offset(), timezone)
+ let d = new DateTime(date, DateFunctions.Get, zone(timezone));
+ return DstHelper.isOffsetDst(d.offset(), timezone);
}
static isDateTimeDst(date: DateTime, timezone: string): boolean {
- return DstHelper.isOffsetDst(date.offset(), timezone)
+ return DstHelper.isOffsetDst(date.offset(), timezone);
}
}
-
diff --git a/src/lib/faqs.js b/src/lib/faqs.js
index a7527c2a9..ed1b17765 100644
--- a/src/lib/faqs.js
+++ b/src/lib/faqs.js
@@ -1,23 +1,27 @@
const FAQs = [
{
- question: 'Can I edit my name and email?',
- answer: 'Yes - you can edit your name by clicking on the letter in the right hand corner. This will pop up the ' +
- 'user menu in the corner. You can click on your name and edit your information.'
+ question: "Can I edit my name and email?",
+ answer:
+ "Yes - you can edit your name by clicking on the letter in the right hand corner. This will pop up the " +
+ "user menu in the corner. You can click on your name and edit your information."
},
{
- question: 'How do I reset my password?',
- answer: 'Please contact your account administrator or Text Team Manager to reset your password.'
+ question: "How do I reset my password?",
+ answer:
+ "Please contact your account administrator or Text Team Manager to reset your password."
},
{
- question: 'Does Spoke use my personal phone number to text people?',
- answer: 'No - We purchase phone numbers and connect them to the application using a service called Twilio. The ' +
- 'texts you send use those purchased phone numbers.'
+ question: "Does Spoke use my personal phone number to text people?",
+ answer:
+ "No - We purchase phone numbers and connect them to the application using a service called Twilio. The " +
+ "texts you send use those purchased phone numbers."
},
{
- question: 'Is Spoke available as an Android/iPhone app?',
- answer: 'Spoke is a web-based program you can access from any web browser on your computer, tablet or mobile ' +
- 'device. No app needed!'
+ question: "Is Spoke available as an Android/iPhone app?",
+ answer:
+ "Spoke is a web-based program you can access from any web browser on your computer, tablet or mobile " +
+ "device. No app needed!"
}
-]
+];
-export default FAQs
+export default FAQs;
diff --git a/src/lib/index.js b/src/lib/index.js
index 7f1c4d059..dd009edcf 100644
--- a/src/lib/index.js
+++ b/src/lib/index.js
@@ -1,6 +1,11 @@
-import zlib from 'zlib'
-export { getFormattedPhoneNumber, getDisplayPhoneNumber } from './phone-format'
-export { getFormattedZip, zipToTimeZone, findZipRanges, getCommonZipRanges } from './zip-format'
+import zlib from "zlib";
+export { getFormattedPhoneNumber, getDisplayPhoneNumber } from "./phone-format";
+export {
+ getFormattedZip,
+ zipToTimeZone,
+ findZipRanges,
+ getCommonZipRanges
+} from "./zip-format";
export {
convertOffsetsToStrings,
getLocalTime,
@@ -11,21 +16,15 @@ export {
getUtcFromTimezoneAndHour,
getUtcFromOffsetAndHour,
getSendBeforeTimeUtc
-} from './timezones'
-export {
- getProcessEnvTz
-} from './tz-helpers'
-export {
- DstHelper
-} from './dst-helper'
-export {
- isClient
-} from './is-client'
-import { log } from './log'
-export { log }
-import Papa from 'papaparse'
-import _ from 'lodash'
-import { getFormattedPhoneNumber, getFormattedZip } from '../lib'
+} from "./timezones";
+export { getProcessEnvTz } from "./tz-helpers";
+export { DstHelper } from "./dst-helper";
+export { isClient } from "./is-client";
+import { log } from "./log";
+export { log };
+import Papa from "papaparse";
+import _ from "lodash";
+import { getFormattedPhoneNumber, getFormattedZip } from "../lib";
export {
findParent,
getInteractionPath,
@@ -35,39 +34,61 @@ export {
getTopMostParent,
getChildren,
makeTree
-} from './interaction-step-helpers'
-const requiredUploadFields = ['firstName', 'lastName', 'cell']
-const topLevelUploadFields = ['firstName', 'lastName', 'cell', 'zip', 'external_id']
+} from "./interaction-step-helpers";
+const requiredUploadFields = ["firstName", "lastName", "cell"];
+const topLevelUploadFields = [
+ "firstName",
+ "lastName",
+ "cell",
+ "zip",
+ "external_id"
+];
-export { ROLE_HIERARCHY, getHighestRole, hasRole, isRoleGreater } from './permissions'
+export {
+ ROLE_HIERARCHY,
+ getHighestRole,
+ hasRole,
+ isRoleGreater
+} from "./permissions";
const getValidatedData = (data, optOuts) => {
- const optOutCells = optOuts.map((optOut) => optOut.cell)
- let validatedData
- let result
+ const optOutCells = optOuts.map(optOut => optOut.cell);
+ let validatedData;
+ let result;
// For some reason destructuring is not working here
- result = _.partition(data, (row) => !!row.cell)
- validatedData = result[0]
- const missingCellRows = result[1]
-
- validatedData = _.map(validatedData, (row) => _.extend(row, {
- cell: getFormattedPhoneNumber(row.cell, process.env.PHONE_NUMBER_COUNTRY || 'US') }))
- result = _.partition(validatedData, (row) => !!row.cell)
- validatedData = result[0]
- const invalidCellRows = result[1]
-
- const count = validatedData.length
- validatedData = _.uniqBy(validatedData, (row) => row.cell)
- const dupeCount = (count - validatedData.length)
+ result = _.partition(data, row => !!row.cell);
+ validatedData = result[0];
+ const missingCellRows = result[1];
+
+ validatedData = _.map(validatedData, row =>
+ _.extend(row, {
+ cell: getFormattedPhoneNumber(
+ row.cell,
+ process.env.PHONE_NUMBER_COUNTRY || "US"
+ )
+ })
+ );
+ result = _.partition(validatedData, row => !!row.cell);
+ validatedData = result[0];
+ const invalidCellRows = result[1];
- result = _.partition(validatedData, (row) => optOutCells.indexOf(row.cell) === -1)
- validatedData = result[0]
- const optOutRows = result[1]
+ const count = validatedData.length;
+ validatedData = _.uniqBy(validatedData, row => row.cell);
+ const dupeCount = count - validatedData.length;
- validatedData = _.map(validatedData, (row) => _.extend(row, {
- zip: row.zip ? getFormattedZip(row.zip) : null
- }))
- const zipCount = validatedData.filter((row) => !!row.zip).length
+ result = _.partition(
+ validatedData,
+ row => optOutCells.indexOf(row.cell) === -1
+ );
+ validatedData = result[0];
+ const optOutRows = result[1];
+
+ validatedData = _.map(validatedData, row =>
+ _.extend(row, {
+ zip: row.zip ? getFormattedZip(row.zip) : null
+ })
+ );
+ const zipCount = validatedData.filter(row => !!row.zip).length;
return {
validatedData,
@@ -78,75 +99,78 @@ const getValidatedData = (data, optOuts) => {
missingCellCount: missingCellRows.length,
zipCount
}
- }
-}
+ };
+};
-export const gzip = (str) => (
+export const gzip = str =>
new Promise((resolve, reject) => {
zlib.gzip(str, (err, res) => {
if (err) {
- reject(err)
+ reject(err);
} else {
- resolve(res)
+ resolve(res);
}
- })
- })
-)
+ });
+ });
-export const gunzip = (buf) => (
+export const gunzip = buf =>
new Promise((resolve, reject) => {
zlib.gunzip(buf, (err, res) => {
if (err) {
- reject(err)
+ reject(err);
} else {
- resolve(res)
+ resolve(res);
}
- })
- })
-)
+ });
+ });
export const parseCSV = (file, optOuts, callback) => {
Papa.parse(file, {
header: true,
// eslint-disable-next-line no-shadow, no-unused-vars
complete: ({ data, meta, errors }, file) => {
- const fields = meta.fields
+ const fields = meta.fields;
- const missingFields = []
+ const missingFields = [];
for (const field of requiredUploadFields) {
if (fields.indexOf(field) === -1) {
- missingFields.push(field)
+ missingFields.push(field);
}
}
if (missingFields.length > 0) {
- const error = `Missing fields: ${missingFields.join(', ')}`
- callback({ error })
+ const error = `Missing fields: ${missingFields.join(", ")}`;
+ callback({ error });
} else {
- const { validationStats, validatedData } = getValidatedData(data, optOuts)
+ const { validationStats, validatedData } = getValidatedData(
+ data,
+ optOuts
+ );
- const customFields = fields.filter((field) => topLevelUploadFields.indexOf(field) === -1)
+ const customFields = fields.filter(
+ field => topLevelUploadFields.indexOf(field) === -1
+ );
callback({
customFields,
validationStats,
contacts: validatedData
- })
+ });
}
}
- })
-}
+ });
+};
-export const convertRowToContact = (row) => {
- const customFields = row
- const contact = {}
+export const convertRowToContact = row => {
+ const customFields = row;
+ const contact = {};
for (const field of topLevelUploadFields) {
if (_.has(row, field)) {
- contact[field] = row[field]
+ contact[field] = row[field];
}
}
- contact.customFields = customFields
- return contact
-}
+ contact.customFields = customFields;
+ return contact;
+};
diff --git a/src/lib/interaction-step-helpers.js b/src/lib/interaction-step-helpers.js
index 369e0b418..b5ea69eb1 100644
--- a/src/lib/interaction-step-helpers.js
+++ b/src/lib/interaction-step-helpers.js
@@ -1,105 +1,122 @@
export function findParent(interactionStep, allInteractionSteps, isModel) {
- let parent = null
- allInteractionSteps.forEach((step) => {
+ let parent = null;
+ allInteractionSteps.forEach(step => {
if (isModel) {
if (step.id == interactionStep.parent_interaction_id) {
parent = {
...step,
answerLink: interactionStep.answer_option
- }
+ };
}
} else {
- if (isModel || step.question && step.question.answerOptions) {
- step.question.answerOptions.forEach((answer) => {
- if (answer.nextInteractionStep && answer.nextInteractionStep.id === interactionStep.id) {
+ if (isModel || (step.question && step.question.answerOptions)) {
+ step.question.answerOptions.forEach(answer => {
+ if (
+ answer.nextInteractionStep &&
+ answer.nextInteractionStep.id === interactionStep.id
+ ) {
parent = {
...step,
answerLink: answer.value
- }
+ };
}
- })
+ });
}
}
- })
- return parent
+ });
+ return parent;
}
-export function getInteractionPath(interactionStep, allInteractionSteps, isModel) {
- const path = []
- let parent = findParent(interactionStep, allInteractionSteps, isModel)
+export function getInteractionPath(
+ interactionStep,
+ allInteractionSteps,
+ isModel
+) {
+ const path = [];
+ let parent = findParent(interactionStep, allInteractionSteps, isModel);
while (parent !== null) {
- path.unshift(parent)
- parent = findParent(parent, allInteractionSteps, isModel)
+ path.unshift(parent);
+ parent = findParent(parent, allInteractionSteps, isModel);
}
- return path
+ return path;
}
export function interactionStepForId(id, interactionSteps) {
- let interactionStep = null
- interactionSteps.forEach((step) => {
+ let interactionStep = null;
+ interactionSteps.forEach(step => {
if (step.id === id) {
- interactionStep = step
+ interactionStep = step;
}
- })
- return interactionStep
+ });
+ return interactionStep;
}
export function getChildren(interactionStep, allInteractionSteps, isModel) {
- const children = []
- allInteractionSteps.forEach((step) => {
- const path = getInteractionPath(step, allInteractionSteps, isModel)
- path.forEach((pathElement) => {
+ const children = [];
+ allInteractionSteps.forEach(step => {
+ const path = getInteractionPath(step, allInteractionSteps, isModel);
+ path.forEach(pathElement => {
if (pathElement.id === interactionStep.id) {
- children.push(step)
+ children.push(step);
}
- })
- })
- return children
+ });
+ });
+ return children;
}
export function getInteractionTree(allInteractionSteps, isModel) {
- const pathLengthHash = {}
- allInteractionSteps.forEach((step) => {
- const path = getInteractionPath(step, allInteractionSteps, isModel)
- pathLengthHash[path.length] = pathLengthHash[path.length] || []
- pathLengthHash[path.length].push({ interactionStep: step, path })
- })
- return pathLengthHash
+ const pathLengthHash = {};
+ allInteractionSteps.forEach(step => {
+ const path = getInteractionPath(step, allInteractionSteps, isModel);
+ pathLengthHash[path.length] = pathLengthHash[path.length] || [];
+ pathLengthHash[path.length].push({ interactionStep: step, path });
+ });
+ return pathLengthHash;
}
export function sortInteractionSteps(interactionSteps) {
- const pathTree = getInteractionTree(interactionSteps)
- const orderedSteps = []
- Object.keys(pathTree).forEach((key) => {
- const orderedBranch = pathTree[key].sort((a, b) => JSON.stringify(a.interactionStep) < JSON.stringify(b.interactionStep))
- orderedBranch.forEach((ele) => orderedSteps.push(ele.interactionStep))
- })
- return orderedSteps
+ const pathTree = getInteractionTree(interactionSteps);
+ const orderedSteps = [];
+ Object.keys(pathTree).forEach(key => {
+ const orderedBranch = pathTree[key].sort(
+ (a, b) =>
+ JSON.stringify(a.interactionStep) < JSON.stringify(b.interactionStep)
+ );
+ orderedBranch.forEach(ele => orderedSteps.push(ele.interactionStep));
+ });
+ return orderedSteps;
}
export function getTopMostParent(interactionSteps, isModel) {
- return getInteractionTree(interactionSteps, isModel)[0][0].interactionStep
+ return getInteractionTree(interactionSteps, isModel)[0][0].interactionStep;
}
export function makeTree(interactionSteps, id = null) {
- const root = interactionSteps.filter((is) => id ? is.id === id : is.parentInteractionId === null)[0]
- const children = interactionSteps.filter((is) => is.parentInteractionId === root.id)
+ const root = interactionSteps.filter(is =>
+ id ? is.id === id : is.parentInteractionId === null
+ )[0];
+ const children = interactionSteps.filter(
+ is => is.parentInteractionId === root.id
+ );
return {
...root,
- interactionSteps: children.map((c) => {
- return makeTree(interactionSteps, c.id)
+ interactionSteps: children.map(c => {
+ return makeTree(interactionSteps, c.id);
})
- }
+ };
}
export function assembleAnswerOptions(allInteractionSteps) {
// creates recursive array required for the graphQL query with 'answerOptions' key
- const interactionStepsCopy = allInteractionSteps.map(
- is => ({...is, answerOptions: []}))
+ const interactionStepsCopy = allInteractionSteps.map(is => ({
+ ...is,
+ answerOptions: []
+ }));
allInteractionSteps.forEach(interactionStep => {
if (interactionStep.parent_interaction_id) {
const [parentStep] = interactionStepsCopy.filter(
- parent => (parent.id === interactionStep.parent_interaction_id))
+ parent => parent.id === interactionStep.parent_interaction_id
+ );
if (parentStep) {
parentStep.answerOptions.push({
nextInteractionStep: interactionStep,
@@ -107,9 +124,9 @@ export function assembleAnswerOptions(allInteractionSteps) {
action: interactionStep.answer_actions,
interaction_step_id: interactionStep.id,
parent_interaction_step: interactionStep.parent_interaction_id
- })
+ });
}
}
- })
- return interactionStepsCopy
+ });
+ return interactionStepsCopy;
}
diff --git a/src/lib/is-client.js b/src/lib/is-client.js
index be6647c79..88bee7cd7 100644
--- a/src/lib/is-client.js
+++ b/src/lib/is-client.js
@@ -1,3 +1,3 @@
export function isClient() {
- return typeof window !== 'undefined'
+ return typeof window !== "undefined";
}
diff --git a/src/lib/log.js b/src/lib/log.js
index f5734921c..baf85be44 100644
--- a/src/lib/log.js
+++ b/src/lib/log.js
@@ -1,49 +1,56 @@
-import minilog from 'minilog'
-import { isClient } from './is-client'
-const rollbar = require('rollbar')
-let logInstance = null
+import minilog from "minilog";
+import { isClient } from "./is-client";
+const rollbar = require("rollbar");
+let logInstance = null;
if (isClient()) {
- minilog.enable()
- logInstance = minilog('client')
- const existingErrorLogger = logInstance.error
+ minilog.enable();
+ logInstance = minilog("client");
+ const existingErrorLogger = logInstance.error;
logInstance.error = (...err) => {
- const errObj = err
+ const errObj = err;
if (window.Rollbar) {
- window.Rollbar.error(...errObj)
+ window.Rollbar.error(...errObj);
}
- existingErrorLogger.call(...errObj)
- }
+ existingErrorLogger.call(...errObj);
+ };
} else {
- let enableRollbar = false
- if (process.env.NODE_ENV === 'production' && process.env.ROLLBAR_ACCESS_TOKEN) {
- enableRollbar = true
- rollbar.init(process.env.ROLLBAR_ACCESS_TOKEN)
+ let enableRollbar = false;
+ if (
+ process.env.NODE_ENV === "production" &&
+ process.env.ROLLBAR_ACCESS_TOKEN
+ ) {
+ enableRollbar = true;
+ rollbar.init(process.env.ROLLBAR_ACCESS_TOKEN);
}
- minilog.suggest.deny(/.*/, process.env.NODE_ENV === 'development' ? 'debug' : 'debug')
+ minilog.suggest.deny(
+ /.*/,
+ process.env.NODE_ENV === "development" ? "debug" : "debug"
+ );
- minilog.enable()
+ minilog
+ .enable()
.pipe(minilog.backends.console.formatWithStack)
- .pipe(minilog.backends.console)
+ .pipe(minilog.backends.console);
- logInstance = minilog('backend')
- const existingErrorLogger = logInstance.error
- logInstance.error = (err) => {
+ logInstance = minilog("backend");
+ const existingErrorLogger = logInstance.error;
+ logInstance.error = err => {
if (enableRollbar) {
- if (typeof err === 'object') {
- rollbar.handleError(err)
- } else if (typeof err === 'string') {
- rollbar.reportMessage(err)
+ if (typeof err === "object") {
+ rollbar.handleError(err);
+ } else if (typeof err === "string") {
+ rollbar.reportMessage(err);
} else {
- rollbar.reportMessage('Got backend error with no error message')
+ rollbar.reportMessage("Got backend error with no error message");
}
}
- existingErrorLogger(err && err.stack ? err.stack : err)
- }
+ existingErrorLogger(err && err.stack ? err.stack : err);
+ };
}
-const log = (process.env.LAMBDA_DEBUG_LOG ? console : logInstance)
+const log = process.env.LAMBDA_DEBUG_LOG ? console : logInstance;
-export { log }
+export { log };
diff --git a/src/lib/pendingJobsUtils.js b/src/lib/pendingJobsUtils.js
index 2d5fe544f..88058fb32 100644
--- a/src/lib/pendingJobsUtils.js
+++ b/src/lib/pendingJobsUtils.js
@@ -1,7 +1,8 @@
-import gql from 'graphql-tag'
+import gql from "graphql-tag";
-export const pendingJobsGql = (campaignId) => ({
- query: gql `query getCampaignJobs($campaignId: String!) {
+export const pendingJobsGql = campaignId => ({
+ query: gql`
+ query getCampaignJobs($campaignId: String!) {
campaign(id: $campaignId) {
id
pendingJobs {
@@ -12,10 +13,10 @@ export const pendingJobsGql = (campaignId) => ({
resultMessage
}
}
- }`,
+ }
+ `,
variables: {
campaignId
},
pollInterval: 60000
-})
-
+});
diff --git a/src/lib/permissions.js b/src/lib/permissions.js
index 236eca108..975201bf0 100644
--- a/src/lib/permissions.js
+++ b/src/lib/permissions.js
@@ -1,9 +1,13 @@
-export const ROLE_HIERARCHY = ['TEXTER', 'SUPERVOLUNTEER', 'ADMIN', 'OWNER']
+export const ROLE_HIERARCHY = ["TEXTER", "SUPERVOLUNTEER", "ADMIN", "OWNER"];
-export const isRoleGreater = (role1, role2) => (ROLE_HIERARCHY.indexOf(role1) > ROLE_HIERARCHY.indexOf(role2))
+export const isRoleGreater = (role1, role2) =>
+ ROLE_HIERARCHY.indexOf(role1) > ROLE_HIERARCHY.indexOf(role2);
-export const hasRoleAtLeast = (hasRole, wantsRole) => (ROLE_HIERARCHY.indexOf(hasRole) >= ROLE_HIERARCHY.indexOf(wantsRole))
+export const hasRoleAtLeast = (hasRole, wantsRole) =>
+ ROLE_HIERARCHY.indexOf(hasRole) >= ROLE_HIERARCHY.indexOf(wantsRole);
-export const getHighestRole = (roles) => roles.sort(isRoleGreater)[roles.length - 1]
+export const getHighestRole = roles =>
+ roles.sort(isRoleGreater)[roles.length - 1];
-export const hasRole = (role, roles) => hasRoleAtLeast(getHighestRole(roles), role)
+export const hasRole = (role, roles) =>
+ hasRoleAtLeast(getHighestRole(roles), role);
diff --git a/src/lib/phone-format.js b/src/lib/phone-format.js
index f5abe2ae6..b54606de7 100644
--- a/src/lib/phone-format.js
+++ b/src/lib/phone-format.js
@@ -1,26 +1,26 @@
-import { PhoneNumberUtil, PhoneNumberFormat } from 'google-libphonenumber'
-import { log } from './log'
+import { PhoneNumberUtil, PhoneNumberFormat } from "google-libphonenumber";
+import { log } from "./log";
-export const getFormattedPhoneNumber = (cell, country = 'US') => {
- const phoneUtil = PhoneNumberUtil.getInstance()
+export const getFormattedPhoneNumber = (cell, country = "US") => {
+ const phoneUtil = PhoneNumberUtil.getInstance();
// we return an empty string vs null when the phone number is inValid
// because when the cell is null, batch inserts into campaign contacts fail
// then when contacts have cell.length < 12 (+1), it's deleted before assignments are created
try {
- const inputNumber = phoneUtil.parse(cell, country)
- const isValid = phoneUtil.isValidNumber(inputNumber)
+ const inputNumber = phoneUtil.parse(cell, country);
+ const isValid = phoneUtil.isValidNumber(inputNumber);
if (isValid) {
- return phoneUtil.format(inputNumber, PhoneNumberFormat.E164)
+ return phoneUtil.format(inputNumber, PhoneNumberFormat.E164);
}
- return ''
+ return "";
} catch (e) {
- log.error(e)
- return ''
+ log.error(e);
+ return "";
}
-}
+};
-export const getDisplayPhoneNumber = (e164Number, country = 'US') => {
- const phoneUtil = PhoneNumberUtil.getInstance()
- const parsed = phoneUtil.parse(e164Number, country)
- return phoneUtil.format(parsed, PhoneNumberFormat.NATIONAL)
-}
+export const getDisplayPhoneNumber = (e164Number, country = "US") => {
+ const phoneUtil = PhoneNumberUtil.getInstance();
+ const parsed = phoneUtil.parse(e164Number, country);
+ return phoneUtil.format(parsed, PhoneNumberFormat.NATIONAL);
+};
diff --git a/src/lib/timezones.js b/src/lib/timezones.js
index 77e8a2781..2d0287c46 100644
--- a/src/lib/timezones.js
+++ b/src/lib/timezones.js
@@ -1,7 +1,10 @@
-import moment from 'moment-timezone'
+import moment from "moment-timezone";
-import { getProcessEnvTz, getProcessEnvDstReferenceTimezone } from '../lib/tz-helpers'
-import { DstHelper } from './dst-helper'
+import {
+ getProcessEnvTz,
+ getProcessEnvDstReferenceTimezone
+} from "../lib/tz-helpers";
+import { DstHelper } from "./dst-helper";
const TIMEZONE_CONFIG = {
missingTimeZone: {
@@ -10,44 +13,63 @@ const TIMEZONE_CONFIG = {
allowedStart: 12, // 12pm EST/9am PST
allowedEnd: 21 // 9pm EST/6pm PST
}
-}
+};
export const getContactTimezone = (campaign, location) => {
- const returnLocation = Object.assign({}, location)
+ const returnLocation = Object.assign({}, location);
if (location.timezone == null || location.timezone.offset == null) {
- let timezoneData = null
+ let timezoneData = null;
if (campaign.overrideOrganizationTextingHours) {
- const offset = DstHelper.getTimezoneOffsetHours(campaign.timezone)
- const hasDST = DstHelper.timezoneHasDst(campaign.timezone)
- timezoneData = { offset, hasDST }
+ const offset = DstHelper.getTimezoneOffsetHours(campaign.timezone);
+ const hasDST = DstHelper.timezoneHasDst(campaign.timezone);
+ timezoneData = { offset, hasDST };
} else if (getProcessEnvTz()) {
- const offset = DstHelper.getTimezoneOffsetHours(getProcessEnvTz())
- const hasDST = DstHelper.timezoneHasDst(getProcessEnvTz())
- timezoneData = { offset, hasDST }
+ const offset = DstHelper.getTimezoneOffsetHours(getProcessEnvTz());
+ const hasDST = DstHelper.timezoneHasDst(getProcessEnvTz());
+ timezoneData = { offset, hasDST };
} else {
- const offset = TIMEZONE_CONFIG.missingTimeZone.offset
- const hasDST = TIMEZONE_CONFIG.missingTimeZone.hasDST
- timezoneData = { offset, hasDST }
+ const offset = TIMEZONE_CONFIG.missingTimeZone.offset;
+ const hasDST = TIMEZONE_CONFIG.missingTimeZone.hasDST;
+ timezoneData = { offset, hasDST };
}
- returnLocation.timezone = timezoneData
+ returnLocation.timezone = timezoneData;
}
- return returnLocation
-}
-
-export const getUtcFromOffsetAndHour = (offset, hasDst, hour, dstReferenceTimezone) => {
- const isDst = moment().tz(dstReferenceTimezone).isDST()
- return moment().utcOffset(offset + ((hasDst && isDst) ? 1 : 0)).hour(hour).startOf('hour').utc()
-}
+ return returnLocation;
+};
+
+export const getUtcFromOffsetAndHour = (
+ offset,
+ hasDst,
+ hour,
+ dstReferenceTimezone
+) => {
+ const isDst = moment()
+ .tz(dstReferenceTimezone)
+ .isDST();
+ return moment()
+ .utcOffset(offset + (hasDst && isDst ? 1 : 0))
+ .hour(hour)
+ .startOf("hour")
+ .utc();
+};
export const getUtcFromTimezoneAndHour = (timezone, hour) => {
- return moment().tz(timezone).hour(hour).startOf('hour').utc()
-}
-
-export const getSendBeforeTimeUtc = (contactTimezone, organization, campaign) => {
+ return moment()
+ .tz(timezone)
+ .hour(hour)
+ .startOf("hour")
+ .utc();
+};
+
+export const getSendBeforeTimeUtc = (
+ contactTimezone,
+ organization,
+ campaign
+) => {
if (campaign.overrideOrganizationTextingHours) {
if (!campaign.textingHoursEnforced) {
- return null
+ return null;
}
if (contactTimezone && contactTimezone.offset) {
@@ -56,24 +78,24 @@ export const getSendBeforeTimeUtc = (contactTimezone, organization, campaign) =>
contactTimezone.hasDST,
campaign.textingHoursEnd,
campaign.timezone
- )
+ );
} else {
return getUtcFromTimezoneAndHour(
campaign.timezone,
campaign.textingHoursEnd
- )
+ );
}
}
if (!organization.textingHoursEnforced) {
- return null
+ return null;
}
if (getProcessEnvTz()) {
return getUtcFromTimezoneAndHour(
getProcessEnvTz(),
organization.textingHoursEnd
- )
+ );
}
if (contactTimezone && contactTimezone.offset) {
@@ -82,73 +104,90 @@ export const getSendBeforeTimeUtc = (contactTimezone, organization, campaign) =>
contactTimezone.hasDST,
organization.textingHoursEnd,
getProcessEnvDstReferenceTimezone()
- )
+ );
} else {
return getUtcFromOffsetAndHour(
TIMEZONE_CONFIG.missingTimeZone.offset,
TIMEZONE_CONFIG.missingTimeZone.hasDST,
organization.textingHoursEnd,
getProcessEnvDstReferenceTimezone()
- )
+ );
}
-}
+};
export const getLocalTime = (offset, hasDST, dstReferenceTimezone) => {
- return moment().utc().utcOffset(DstHelper.isDateDst(new Date(), dstReferenceTimezone) && hasDST ? offset + 1 : offset)
-}
-
-const isOffsetBetweenTextingHours = (offsetData, textingHoursStart, textingHoursEnd, missingTimezoneConfig, dstReferenceTimezone) => {
- let offset
- let hasDST
- let allowedStart
- let allowedEnd
+ return moment()
+ .utc()
+ .utcOffset(
+ DstHelper.isDateDst(new Date(), dstReferenceTimezone) && hasDST
+ ? offset + 1
+ : offset
+ );
+};
+
+const isOffsetBetweenTextingHours = (
+ offsetData,
+ textingHoursStart,
+ textingHoursEnd,
+ missingTimezoneConfig,
+ dstReferenceTimezone
+) => {
+ let offset;
+ let hasDST;
+ let allowedStart;
+ let allowedEnd;
if (offsetData && offsetData.offset) {
- allowedStart = textingHoursStart
- allowedEnd = textingHoursEnd
- offset = offsetData.offset
- hasDST = offsetData.hasDST
+ allowedStart = textingHoursStart;
+ allowedEnd = textingHoursEnd;
+ offset = offsetData.offset;
+ hasDST = offsetData.hasDST;
} else {
- allowedStart = missingTimezoneConfig.allowedStart
- allowedEnd = missingTimezoneConfig.allowedEnd
- offset = missingTimezoneConfig.offset
- hasDST = missingTimezoneConfig.hasDST
+ allowedStart = missingTimezoneConfig.allowedStart;
+ allowedEnd = missingTimezoneConfig.allowedEnd;
+ offset = missingTimezoneConfig.offset;
+ hasDST = missingTimezoneConfig.hasDST;
}
- const localTime = getLocalTime(offset, hasDST, dstReferenceTimezone)
- return (localTime.hours() >= allowedStart && localTime.hours() < allowedEnd)
-}
+ const localTime = getLocalTime(offset, hasDST, dstReferenceTimezone);
+ return localTime.hours() >= allowedStart && localTime.hours() < allowedEnd;
+};
export const isBetweenTextingHours = (offsetData, config) => {
if (config.campaignTextingHours) {
if (!config.campaignTextingHours.textingHoursEnforced) {
- return true
+ return true;
}
} else if (!config.textingHoursEnforced) {
- return true
+ return true;
}
if (config.campaignTextingHours) {
- const { campaignTextingHours } = config
+ const { campaignTextingHours } = config;
const missingTimezoneConfig = {
allowedStart: campaignTextingHours.textingHoursStart,
allowedEnd: campaignTextingHours.textingHoursEnd,
offset: DstHelper.getTimezoneOffsetHours(campaignTextingHours.timezone),
hasDST: DstHelper.timezoneHasDst(campaignTextingHours.timezone)
- }
+ };
return isOffsetBetweenTextingHours(
offsetData,
campaignTextingHours.textingHoursStart,
campaignTextingHours.textingHoursEnd,
missingTimezoneConfig,
- campaignTextingHours.timezone)
+ campaignTextingHours.timezone
+ );
}
if (getProcessEnvTz()) {
- const today = moment.tz(getProcessEnvTz()).format('YYYY-MM-DD')
- const start = moment.tz(`${today}`, getProcessEnvTz()).add(config.textingHoursStart, 'hours')
- const stop = moment.tz(`${today}`, getProcessEnvTz()).add(config.textingHoursEnd, 'hours')
- return moment.tz(getProcessEnvTz()).isBetween(start, stop, null, '[]')
+ const today = moment.tz(getProcessEnvTz()).format("YYYY-MM-DD");
+ const start = moment
+ .tz(`${today}`, getProcessEnvTz())
+ .add(config.textingHoursStart, "hours");
+ const stop = moment
+ .tz(`${today}`, getProcessEnvTz())
+ .add(config.textingHoursEnd, "hours");
+ return moment.tz(getProcessEnvTz()).isBetween(start, stop, null, "[]");
}
return isOffsetBetweenTextingHours(
@@ -156,42 +195,42 @@ export const isBetweenTextingHours = (offsetData, config) => {
config.textingHoursStart,
config.textingHoursEnd,
TIMEZONE_CONFIG.missingTimeZone,
- getProcessEnvDstReferenceTimezone())
-}
-
+ getProcessEnvDstReferenceTimezone()
+ );
+};
// Currently USA (-4 through -11) and Australia (10)
-const ALL_OFFSETS = [-4, -5, -6, -7, -8, -9, -10, -11, 10]
+const ALL_OFFSETS = [-4, -5, -6, -7, -8, -9, -10, -11, 10];
-export const defaultTimezoneIsBetweenTextingHours = (config) => isBetweenTextingHours(null, config)
+export const defaultTimezoneIsBetweenTextingHours = config =>
+ isBetweenTextingHours(null, config);
export function convertOffsetsToStrings(offsetArray) {
- const result = []
- offsetArray.forEach((offset) => {
- result.push((offset[0].toString() + '_' + (offset[1] === true ? '1' : '0')))
- })
- return result
+ const result = [];
+ offsetArray.forEach(offset => {
+ result.push(offset[0].toString() + "_" + (offset[1] === true ? "1" : "0"));
+ });
+ return result;
}
-export const getOffsets = (config) => {
- const offsets = ALL_OFFSETS.slice(0)
+export const getOffsets = config => {
+ const offsets = ALL_OFFSETS.slice(0);
- const valid = []
- const invalid = []
+ const valid = [];
+ const invalid = [];
- const dst = [true, false]
- dst.forEach((hasDST) => (
- offsets.forEach((offset) => {
+ const dst = [true, false];
+ dst.forEach(hasDST =>
+ offsets.forEach(offset => {
if (isBetweenTextingHours({ offset, hasDST }, config)) {
- valid.push([offset, hasDST])
+ valid.push([offset, hasDST]);
} else {
- invalid.push([offset, hasDST])
+ invalid.push([offset, hasDST]);
}
})
+ );
- ))
-
- const convertedValid = convertOffsetsToStrings(valid)
- const convertedInvalid = convertOffsetsToStrings(invalid)
- return [convertedValid, convertedInvalid]
-}
+ const convertedValid = convertOffsetsToStrings(valid);
+ const convertedInvalid = convertOffsetsToStrings(invalid);
+ return [convertedValid, convertedInvalid];
+};
diff --git a/src/lib/tz-helpers.js b/src/lib/tz-helpers.js
index 322907c3a..3974b3aaf 100644
--- a/src/lib/tz-helpers.js
+++ b/src/lib/tz-helpers.js
@@ -1,5 +1,11 @@
-export function getProcessEnvTz() { return process.env.TZ }
+export function getProcessEnvTz() {
+ return process.env.TZ;
+}
export function getProcessEnvDstReferenceTimezone() {
- return process.env.DST_REFERENCE_TIMEZONE || global.DST_REFERENCE_TIMEZONE || 'America/New_York' }
-
+ return (
+ process.env.DST_REFERENCE_TIMEZONE ||
+ global.DST_REFERENCE_TIMEZONE ||
+ "America/New_York"
+ );
+}
diff --git a/src/lib/zip-format.js b/src/lib/zip-format.js
index 816c84a8f..41c21f340 100644
--- a/src/lib/zip-format.js
+++ b/src/lib/zip-format.js
@@ -1,13 +1,13 @@
-export const getFormattedZip = (zip, country = 'US') => {
- if (country === 'US') {
- const regex = /(\d{5})([ \-]\d{4})?/
- const [, first5] = zip.match(regex) || []
+export const getFormattedZip = (zip, country = "US") => {
+ if (country === "US") {
+ const regex = /(\d{5})([ \-]\d{4})?/;
+ const [, first5] = zip.match(regex) || [];
- return first5
+ return first5;
} else {
- throw new Error(`Do not know how to format zip for country: ${country}`)
+ throw new Error(`Do not know how to format zip for country: ${country}`);
}
-}
+};
var commonZipRanges = [
// list of zip ranges. [, , , , ]
@@ -53,43 +53,52 @@ var commonZipRanges = [
[42501, 42602, -5, 1, 101],
[37401, 37501, -5, 1, 100],
[37501, 37601, -6, 1, 100]
-]
+];
-commonZipRanges.sort((a, b) => (a[0] - b[0]))
+commonZipRanges.sort((a, b) => a[0] - b[0]);
export function getCommonZipRanges() {
- return commonZipRanges
+ return commonZipRanges;
}
-export const zipToTimeZone = function (zip) {
+export const zipToTimeZone = function(zip) {
// will search common zip ranges -- won't necessarily find something
// so fallback on looking it up in db
- if (typeof zip == 'number' || zip.length >= 5) {
- zip = parseInt(zip)
- return getCommonZipRanges().find((g) => (zip >= g[0] && zip < g[1]))
+ if (typeof zip == "number" || zip.length >= 5) {
+ zip = parseInt(zip);
+ return getCommonZipRanges().find(g => zip >= g[0] && zip < g[1]);
}
-}
+};
// lperson 2018.02.10 this is dead code
-export const findZipRanges = function (r) {
- var zipchanges = []
- return r.knex('zip_code').select('zip', 'timezone_offset', 'has_dst')
- .orderBy('zip').then(function (zips) {
- var front = -1
- var curTz = -4
- var curHasDst = -1
- zips.forEach((zipRec) => {
+export const findZipRanges = function(r) {
+ var zipchanges = [];
+ return r
+ .knex("zip_code")
+ .select("zip", "timezone_offset", "has_dst")
+ .orderBy("zip")
+ .then(function(zips) {
+ var front = -1;
+ var curTz = -4;
+ var curHasDst = -1;
+ zips.forEach(zipRec => {
if (zipRec.timezone_offset != curTz || zipRec.has_dst != curHasDst) {
- zipchanges.push([front, parseInt(zipRec.zip), curTz, curHasDst, parseInt(zipRec.zip) - front])
- curTz = zipRec.timezone_offset
- curHasDst = zipRec.has_dst
- front = parseInt(zipRec.zip)
+ zipchanges.push([
+ front,
+ parseInt(zipRec.zip),
+ curTz,
+ curHasDst,
+ parseInt(zipRec.zip) - front
+ ]);
+ curTz = zipRec.timezone_offset;
+ curHasDst = zipRec.has_dst;
+ front = parseInt(zipRec.zip);
}
- })
- zipchanges.sort(function (a, b) {
- return b[4] - a[4]
- })
- console.log(zipchanges)
- })
- return zipchanges
-}
+ });
+ zipchanges.sort(function(a, b) {
+ return b[4] - a[4];
+ });
+ console.log(zipchanges);
+ });
+ return zipchanges;
+};
diff --git a/src/network/apollo-client-singleton.js b/src/network/apollo-client-singleton.js
index c2e0ee3e6..f94fcce60 100644
--- a/src/network/apollo-client-singleton.js
+++ b/src/network/apollo-client-singleton.js
@@ -1,37 +1,42 @@
-import ApolloClient, { addQueryMerging } from 'apollo-client'
-import ResponseMiddlewareNetworkInterface from './response-middleware-network-interface'
-import { log } from '../lib'
-import fetch from 'isomorphic-fetch'
-import { graphQLErrorParser } from './errors'
+import ApolloClient, { addQueryMerging } from "apollo-client";
+import ResponseMiddlewareNetworkInterface from "./response-middleware-network-interface";
+import { log } from "../lib";
+import fetch from "isomorphic-fetch";
+import { graphQLErrorParser } from "./errors";
const responseMiddlewareNetworkInterface = new ResponseMiddlewareNetworkInterface(
- process.env.GRAPHQL_URL || '/graphql', { credentials: 'same-origin' }
-)
+ process.env.GRAPHQL_URL || "/graphql",
+ { credentials: "same-origin" }
+);
responseMiddlewareNetworkInterface.use({
applyResponseMiddleware: (response, next) => {
- const parsedError = graphQLErrorParser(response)
+ const parsedError = graphQLErrorParser(response);
if (parsedError) {
- log.debug(parsedError)
+ log.debug(parsedError);
if (parsedError.status === 401) {
- window.location = `/login?nextUrl=${window.location.pathname}`
+ window.location = `/login?nextUrl=${window.location.pathname}`;
} else if (parsedError.status === 403) {
- window.location = '/'
+ window.location = "/";
} else if (parsedError.status === 404) {
- window.location = '/404'
+ window.location = "/404";
} else {
- log.error(`GraphQL request resulted in error:\nRequest:${JSON.stringify(response.data)}\nError:${JSON.stringify(response.errors)}`)
+ log.error(
+ `GraphQL request resulted in error:\nRequest:${JSON.stringify(
+ response.data
+ )}\nError:${JSON.stringify(response.errors)}`
+ );
}
}
- next()
+ next();
}
-})
+});
-const networkInterface = addQueryMerging(responseMiddlewareNetworkInterface)
+const networkInterface = addQueryMerging(responseMiddlewareNetworkInterface);
const ApolloClientSingleton = new ApolloClient({
networkInterface,
shouldBatch: true,
- dataIdFromObject: (result) => result.id
-})
-export default ApolloClientSingleton
+ dataIdFromObject: result => result.id
+});
+export default ApolloClientSingleton;
diff --git a/src/network/errors.js b/src/network/errors.js
index b4ee6d902..e09ae6032 100644
--- a/src/network/errors.js
+++ b/src/network/errors.js
@@ -1,33 +1,34 @@
export function GraphQLRequestError(err) {
- this.name = this.constructor.name
- this.message = err.message
- this.status = err.status
- this.stack = (new Error()).stack
+ this.name = this.constructor.name;
+ this.message = err.message;
+ this.status = err.status;
+ this.stack = new Error().stack;
}
-GraphQLRequestError.prototype = Object.create(Error.prototype)
-GraphQLRequestError.prototype.constructor = GraphQLRequestError
+GraphQLRequestError.prototype = Object.create(Error.prototype);
+GraphQLRequestError.prototype.constructor = GraphQLRequestError;
export function graphQLErrorParser(response) {
if (response.errors && response.errors.length > 0) {
- const error = response.errors[0]
- let parsedError = null
+ const error = response.errors[0];
+ let parsedError = null;
try {
- parsedError = JSON.parse(error.message)
+ parsedError = JSON.parse(error.message);
} catch (ex) {
// Even if we can't parse an error messge into JSON, still render it as a string
// so that we still display some error message instead of no error message at all.
- parsedError = { status: 500, message: error.message }
+ parsedError = { status: 500, message: error.message };
}
if (parsedError) {
return {
status: parsedError.status,
message: parsedError.message
- }
+ };
}
return {
status: 500,
- message: 'There was an error with your request. Try again in a little bit!'
- }
+ message:
+ "There was an error with your request. Try again in a little bit!"
+ };
}
- return null
+ return null;
}
diff --git a/src/network/response-middleware-network-interface.js b/src/network/response-middleware-network-interface.js
index f1aa4553d..6778f6bc1 100644
--- a/src/network/response-middleware-network-interface.js
+++ b/src/network/response-middleware-network-interface.js
@@ -1,52 +1,54 @@
-import { createNetworkInterface } from 'apollo-client'
-import fetch from 'isomorphic-fetch'
+import { createNetworkInterface } from "apollo-client";
+import fetch from "isomorphic-fetch";
class ResponseMiddlewareNetworkInterface {
- constructor(endpoint = '/graphql', options = {}) {
- this.defaultNetworkInterface = createNetworkInterface(endpoint, options)
- this.responseMiddlewares = []
+ constructor(endpoint = "/graphql", options = {}) {
+ this.defaultNetworkInterface = createNetworkInterface(endpoint, options);
+ this.responseMiddlewares = [];
}
use(responseMiddleware) {
- let responseMiddlewares = responseMiddleware
+ let responseMiddlewares = responseMiddleware;
if (!Array.isArray(responseMiddlewares)) {
- responseMiddlewares = [responseMiddlewares]
+ responseMiddlewares = [responseMiddlewares];
}
- responseMiddlewares.forEach((middleware) => {
- if (typeof middleware.applyMiddleware === 'function') {
- this.defaultNetworkInterface.use([middleware])
- } else if (typeof middleware.applyResponseMiddleware === 'function') {
- this.responseMiddlewares.push(middleware)
+ responseMiddlewares.forEach(middleware => {
+ if (typeof middleware.applyMiddleware === "function") {
+ this.defaultNetworkInterface.use([middleware]);
+ } else if (typeof middleware.applyResponseMiddleware === "function") {
+ this.responseMiddlewares.push(middleware);
} else {
- throw new Error('Middleware must implement the applyMiddleware or applyResponseMiddleware functions')
+ throw new Error(
+ "Middleware must implement the applyMiddleware or applyResponseMiddleware functions"
+ );
}
- })
+ });
}
async applyResponseMiddlewares(response) {
// eslint-disable-next-line no-unused-vars
return new Promise((resolve, reject) => {
- const queue = async (funcs) => {
+ const queue = async funcs => {
const next = async () => {
if (funcs.length > 0) {
- const f = funcs.shift()
- f.applyResponseMiddleware(response, next)
+ const f = funcs.shift();
+ f.applyResponseMiddleware(response, next);
} else {
- resolve(response)
+ resolve(response);
}
- }
- next()
- }
+ };
+ next();
+ };
- queue([...this.responseMiddlewares])
- })
+ queue([...this.responseMiddlewares]);
+ });
}
async query(request) {
- let response = await this.defaultNetworkInterface.query(request)
- response = await this.applyResponseMiddlewares(response)
- return response
+ let response = await this.defaultNetworkInterface.query(request);
+ response = await this.applyResponseMiddlewares(response);
+ return response;
}
}
-export default ResponseMiddlewareNetworkInterface
+export default ResponseMiddlewareNetworkInterface;
diff --git a/src/server/action_handlers/actionkit-rsvp.js b/src/server/action_handlers/actionkit-rsvp.js
index 4814c39cd..94c08bb41 100644
--- a/src/server/action_handlers/actionkit-rsvp.js
+++ b/src/server/action_handlers/actionkit-rsvp.js
@@ -1,105 +1,142 @@
-import request from 'request'
-import { r } from '../models'
-import crypto from 'crypto'
+import request from "request";
+import { r } from "../models";
+import crypto from "crypto";
-export const displayName = () => 'ActionKit Event RSVP'
+export const displayName = () => "ActionKit Event RSVP";
-export const instructions = () => (
+export const instructions = () =>
`
Campaign contacts MUST be uploaded with "event_id" and "event_page" fields
along with external_id=.
Optional fields include "event_source" (defaults to 'spoke') and "event_field_*" fields and "event_action_*"
which will be added as post data where '*' can be any word which will map to an action/event field.
- `)
+ `;
export async function available(organizationId) {
if (process.env.AK_BASEURL && process.env.AK_SECRET) {
- return true
+ return true;
}
- const org = await r.knex('organization').where('id', organizationId).select('features')
- const features = JSON.parse(org.features || '{}')
- let needed = []
+ const org = await r
+ .knex("organization")
+ .where("id", organizationId)
+ .select("features");
+ const features = JSON.parse(org.features || "{}");
+ let needed = [];
if (!process.env.AK_BASEURL && !features.AK_BASEURL) {
- needed.push('AK_BASEURL')
+ needed.push("AK_BASEURL");
}
if (!process.env.AK_SECRET && !features.AK_SECRET) {
- needed.push('AK_SECRET')
+ needed.push("AK_SECRET");
}
if (needed.length) {
- console.error('actionkit-rsvp unavailable because '
- + needed.join(', ')
- + ' must be set (either in environment variables or json value for organization)')
+ console.error(
+ "actionkit-rsvp unavailable because " +
+ needed.join(", ") +
+ " must be set (either in environment variables or json value for organization)"
+ );
}
- return !!(needed.length)
+ return !!needed.length;
}
-export const akidGenerate = function (ak_secret, cleartext) {
- const shaHash = crypto.createHash('sha256')
- shaHash.write(`${ak_secret}.${cleartext}`)
- const shortHash = shaHash.digest('base64').slice(0, 6)
- return `${cleartext}.${shortHash}`
-}
+export const akidGenerate = function(ak_secret, cleartext) {
+ const shaHash = crypto.createHash("sha256");
+ shaHash.write(`${ak_secret}.${cleartext}`);
+ const shortHash = shaHash.digest("base64").slice(0, 6);
+ return `${cleartext}.${shortHash}`;
+};
-export async function processAction(questionResponse, interactionStep, campaignContactId) {
- const contactRes = await r.knex('campaign_contact')
- .where('campaign_contact.id', campaignContactId)
- .leftJoin('campaign', 'campaign_contact.campaign_id', 'campaign.id')
- .leftJoin('organization', 'campaign.organization_id', 'organization.id')
- .select('campaign_contact.custom_fields as custom_fields',
- 'campaign_contact.external_id as external_id',
- 'organization.features as features',
- 'organization.id as organization_id')
- const contact = (contactRes.length ? contactRes[0] : {})
+export async function processAction(
+ questionResponse,
+ interactionStep,
+ campaignContactId
+) {
+ const contactRes = await r
+ .knex("campaign_contact")
+ .where("campaign_contact.id", campaignContactId)
+ .leftJoin("campaign", "campaign_contact.campaign_id", "campaign.id")
+ .leftJoin("organization", "campaign.organization_id", "organization.id")
+ .select(
+ "campaign_contact.custom_fields as custom_fields",
+ "campaign_contact.external_id as external_id",
+ "organization.features as features",
+ "organization.id as organization_id"
+ );
+ const contact = contactRes.length ? contactRes[0] : {};
- if (contact.external_id && contact.custom_fields != '{}') {
+ if (contact.external_id && contact.custom_fields != "{}") {
try {
- const customFields = JSON.parse(contact.custom_fields || '{}')
- const features = JSON.parse(contact.features || '{}')
- const actionkitBaseUrl = process.env.AK_BASEURL || features.AK_BASEURL
- const akSecret = process.env.AK_SECRET || features.AK_SECRET
+ const customFields = JSON.parse(contact.custom_fields || "{}");
+ const features = JSON.parse(contact.features || "{}");
+ const actionkitBaseUrl = process.env.AK_BASEURL || features.AK_BASEURL;
+ const akSecret = process.env.AK_SECRET || features.AK_SECRET;
- if (actionkitBaseUrl && customFields.event_id && customFields.event_page) {
+ if (
+ actionkitBaseUrl &&
+ customFields.event_id &&
+ customFields.event_page
+ ) {
const userData = {
event_id: customFields.event_id,
page: customFields.event_page,
- role: 'attendee',
- status: 'active',
- akid: akidGenerate(akSecret, '.' + contact.external_id),
- event_signup_ground_rules: '1',
- source: customFields.event_source || 'spoke',
- suppress_subscribe: customFields.suppress_subscribe || '1'
- }
+ role: "attendee",
+ status: "active",
+ akid: akidGenerate(akSecret, "." + contact.external_id),
+ event_signup_ground_rules: "1",
+ source: customFields.event_source || "spoke",
+ suppress_subscribe: customFields.suppress_subscribe || "1"
+ };
for (let field in customFields) {
- if (field.startsWith('event_field_')) {
- userData['event_' + field.slice('event_field_'.length)] = customFields[field]
- } else if (field.startsWith('event_action_')) {
- userData[field.slice('event_'.length)] = customFields[field]
+ if (field.startsWith("event_field_")) {
+ userData["event_" + field.slice("event_field_".length)] =
+ customFields[field];
+ } else if (field.startsWith("event_action_")) {
+ userData[field.slice("event_".length)] = customFields[field];
}
}
- request.post({
- 'url': `${actionkitBaseUrl}/act/`,
- 'form': userData
- }, async function (err, httpResponse, body) {
- // TODO: should we save the action id somewhere?
- if (err || (body && body.error)) {
- console.error('error: actionkit event sign up failed', err, userData, body)
- } else {
- if (httpResponse.headers && httpResponse.headers.location) {
- const actionId = httpResponse.headers.location.match(/action_id=([^&]+)/)
- if (actionId) {
- // save the action id of the rsvp back to the contact record
- customFields['processed_event_action'] = actionId[1]
- await r.knex('campaign_contact')
- .where('campaign_contact.id', campaignContactId)
- .update('custom_fields', JSON.stringify(customFields))
+ request.post(
+ {
+ url: `${actionkitBaseUrl}/act/`,
+ form: userData
+ },
+ async function(err, httpResponse, body) {
+ // TODO: should we save the action id somewhere?
+ if (err || (body && body.error)) {
+ console.error(
+ "error: actionkit event sign up failed",
+ err,
+ userData,
+ body
+ );
+ } else {
+ if (httpResponse.headers && httpResponse.headers.location) {
+ const actionId = httpResponse.headers.location.match(
+ /action_id=([^&]+)/
+ );
+ if (actionId) {
+ // save the action id of the rsvp back to the contact record
+ customFields["processed_event_action"] = actionId[1];
+ await r
+ .knex("campaign_contact")
+ .where("campaign_contact.id", campaignContactId)
+ .update("custom_fields", JSON.stringify(customFields));
+ }
}
+ console.info(
+ "actionkit event sign up SUCCESS!",
+ userData,
+ httpResponse,
+ body
+ );
}
- console.info('actionkit event sign up SUCCESS!', userData, httpResponse, body)
}
- })
+ );
}
} catch (err) {
- console.error('Processing Actionkit RSVP action failed on custom field parsing', campaignContactId, err)
+ console.error(
+ "Processing Actionkit RSVP action failed on custom field parsing",
+ campaignContactId,
+ err
+ );
}
}
}
diff --git a/src/server/action_handlers/helper-ak-sync.js b/src/server/action_handlers/helper-ak-sync.js
index 3c2ac590c..453d08f36 100644
--- a/src/server/action_handlers/helper-ak-sync.js
+++ b/src/server/action_handlers/helper-ak-sync.js
@@ -1,60 +1,66 @@
-import request from 'request'
+import request from "request";
-const akAddUserUrl = process.env.AK_ADD_USER_URL
-const akAddPhoneUrl = process.env.AK_ADD_PHONE_URL
+const akAddUserUrl = process.env.AK_ADD_USER_URL;
+const akAddPhoneUrl = process.env.AK_ADD_PHONE_URL;
-export const actionKitSignup = (contact) => {
- const cell = contact.cell.substring(1)
- // We add the user to ActionKit to make sure we keep have a record of their phone number & attach it to a fake email.
+export const actionKitSignup = contact => {
+ const cell = contact.cell.substring(1);
+ // We add the user to ActionKit to make sure we keep have a record of their phone number & attach it to a fake email.
if (akAddUserUrl && akAddPhoneUrl) {
const userData = {
- email: cell + '-smssubscriber@example.com',
+ email: cell + "-smssubscriber@example.com",
first_name: contact.first_name,
last_name: contact.last_name,
- user_sms_subscribed: 'sms_subscribed',
- user_sms_termsandconditions: 'sms_termsandconditions',
- user_robodial_termsandconditions: 'yes',
+ user_sms_subscribed: "sms_subscribed",
+ user_sms_termsandconditions: "sms_termsandconditions",
+ user_robodial_termsandconditions: "yes",
suppress_subscribe: true,
phone: [cell],
- phone_type: 'mobile',
- source: 'spoke-signup'
- }
+ phone_type: "mobile",
+ source: "spoke-signup"
+ };
- request.post({
- url: akAddUserUrl,
- headers: {
- accept: 'application/json',
- 'content-type': 'application/json'
+ request.post(
+ {
+ url: akAddUserUrl,
+ headers: {
+ accept: "application/json",
+ "content-type": "application/json"
+ },
+ form: userData
},
- form: userData
- }, (errorResponse, httpResponse) => {
- if (errorResponse) throw new Error(errorResponse)
- if (httpResponse.statusCode === 201) {
- request.post({
- url: akAddPhoneUrl,
- headers: {
- accept: 'application/json',
- 'content-type': 'application/json'
- },
- form: {
- user: httpResponse.headers.location,
- phone: cell,
- type: 'mobile',
- user_sms_subscribed: 'sms_subscribed',
- action_mobilesubscribe: '1',
- action_sms_termsandconditions: 'sms_termsandconditions',
- user_sms_termsandconditions: 'sms_termsandconditions',
- user_robodial_termsandconditions: 'yes'
- }
- }, (phoneError, phoneResponse) => {
- if (phoneError) throw new Error(phoneError)
- if (phoneResponse.statusCode === 201) {
- return
- }
- })
+ (errorResponse, httpResponse) => {
+ if (errorResponse) throw new Error(errorResponse);
+ if (httpResponse.statusCode === 201) {
+ request.post(
+ {
+ url: akAddPhoneUrl,
+ headers: {
+ accept: "application/json",
+ "content-type": "application/json"
+ },
+ form: {
+ user: httpResponse.headers.location,
+ phone: cell,
+ type: "mobile",
+ user_sms_subscribed: "sms_subscribed",
+ action_mobilesubscribe: "1",
+ action_sms_termsandconditions: "sms_termsandconditions",
+ user_sms_termsandconditions: "sms_termsandconditions",
+ user_robodial_termsandconditions: "yes"
+ }
+ },
+ (phoneError, phoneResponse) => {
+ if (phoneError) throw new Error(phoneError);
+ if (phoneResponse.statusCode === 201) {
+ return;
+ }
+ }
+ );
+ }
}
- })
+ );
} else {
- console.log('No AK Post URLs Configured')
+ console.log("No AK Post URLs Configured");
}
-}
+};
diff --git a/src/server/action_handlers/mobilecommons-signup.js b/src/server/action_handlers/mobilecommons-signup.js
index d52e46f5d..0d939541e 100644
--- a/src/server/action_handlers/mobilecommons-signup.js
+++ b/src/server/action_handlers/mobilecommons-signup.js
@@ -1,62 +1,76 @@
-import request from 'request'
-import aws from 'aws-sdk'
-import { r } from '../models'
-import { actionKitSignup } from './helper-ak-sync.js'
+import request from "request";
+import aws from "aws-sdk";
+import { r } from "../models";
+import { actionKitSignup } from "./helper-ak-sync.js";
// What the user sees as the option
-export const displayName = () => 'Mobile Commons Signup'
+export const displayName = () => "Mobile Commons Signup";
-const akAddUserUrl = process.env.AK_ADD_USER_URL
-const akAddPhoneUrl = process.env.AK_ADD_PHONE_URL
-const createProfileUrl = process.env.UMC_PROFILE_URL
-const defaultProfileOptInId = process.env.UMC_OPT_IN_PATH
-const umcAuth = 'Basic ' + Buffer.from(process.env.UMC_USER + ':' + process.env.UMC_PW).toString('base64')
-const umcConfigured = (defaultProfileOptInId && createProfileUrl)
+const akAddUserUrl = process.env.AK_ADD_USER_URL;
+const akAddPhoneUrl = process.env.AK_ADD_PHONE_URL;
+const createProfileUrl = process.env.UMC_PROFILE_URL;
+const defaultProfileOptInId = process.env.UMC_OPT_IN_PATH;
+const umcAuth =
+ "Basic " +
+ Buffer.from(process.env.UMC_USER + ":" + process.env.UMC_PW).toString(
+ "base64"
+ );
+const umcConfigured = defaultProfileOptInId && createProfileUrl;
// The Help text for the user after selecting the action
-export const instructions = () => (
- 'This option triggers a new user request to Upland Mobile Commons when selected.'
-)
+export const instructions = () =>
+ "This option triggers a new user request to Upland Mobile Commons when selected.";
export async function available(organizationId) {
- if (organizationId && umcConfigured) {
- return true
+ if (organizationId && umcConfigured) {
+ return true;
}
- return false
+ return false;
}
-export async function processAction(questionResponse, interactionStep, campaignContactId) {
- const contactRes = await r.knex('campaign_contact')
- .where('campaign_contact.id', campaignContactId)
- .leftJoin('campaign', 'campaign_contact.campaign_id', 'campaign.id')
- .leftJoin('organization', 'campaign.organization_id', 'organization.id')
- .select('campaign_contact.cell', 'campaign_contact.first_name', 'campaign_contact.last_name', 'campaign_contact.custom_fields')
+export async function processAction(
+ questionResponse,
+ interactionStep,
+ campaignContactId
+) {
+ const contactRes = await r
+ .knex("campaign_contact")
+ .where("campaign_contact.id", campaignContactId)
+ .leftJoin("campaign", "campaign_contact.campaign_id", "campaign.id")
+ .leftJoin("organization", "campaign.organization_id", "organization.id")
+ .select(
+ "campaign_contact.cell",
+ "campaign_contact.first_name",
+ "campaign_contact.last_name",
+ "campaign_contact.custom_fields"
+ );
- const contact = (contactRes.length ? contactRes[0] : {})
- const customFields = JSON.parse(contact.custom_fields)
- const optInPathId = (customFields.umc_opt_in_path ? customFields.umc_opt_in_path : defaultProfileOptInId)
- const cell = contact.cell.substring(1)
+ const contact = contactRes.length ? contactRes[0] : {};
+ const customFields = JSON.parse(contact.custom_fields);
+ const optInPathId = customFields.umc_opt_in_path
+ ? customFields.umc_opt_in_path
+ : defaultProfileOptInId;
+ const cell = contact.cell.substring(1);
- actionKitSignup(contact)
+ actionKitSignup(contact);
const options = {
- method: 'POST',
+ method: "POST",
url: createProfileUrl,
headers: {
- accept: 'application/json',
- 'content-type': 'application/json',
+ accept: "application/json",
+ "content-type": "application/json",
Authorization: umcAuth
},
body: {
phone_number: cell,
- first_name: contact.first_name || '',
- last_name: contact.last_name || '',
+ first_name: contact.first_name || "",
+ last_name: contact.last_name || "",
opt_in_path_id: optInPathId
},
json: true
- }
+ };
return request(options, (error, response) => {
- if (error) throw new Error(error)
- })
-
+ if (error) throw new Error(error);
+ });
}
diff --git a/src/server/action_handlers/revere-signup.js b/src/server/action_handlers/revere-signup.js
index 324969928..905273fce 100644
--- a/src/server/action_handlers/revere-signup.js
+++ b/src/server/action_handlers/revere-signup.js
@@ -1,71 +1,82 @@
-import request from 'request'
-import aws from 'aws-sdk'
-import { r } from '../models'
-import { actionKitSignup } from './helper-ak-sync.js'
+import request from "request";
+import aws from "aws-sdk";
+import { r } from "../models";
+import { actionKitSignup } from "./helper-ak-sync.js";
-const sqs = new aws.SQS()
+const sqs = new aws.SQS();
// What the user sees as the option
-export const displayName = () => 'Revere Signup'
+export const displayName = () => "Revere Signup";
-const listId = process.env.REVERE_LIST_ID
-const defaultMobileFlowId = process.env.REVERE_NEW_SUBSCRIBER_MOBILE_FLOW
-const mobileApiKey = process.env.REVERE_MOBILE_API_KEY
-const sendContentUrl = process.env.REVERE_API_URL
-const akAddUserUrl = process.env.AK_ADD_USER_URL
-const akAddPhoneUrl = process.env.AK_ADD_PHONE_URL
-const sqsUrl = process.env.REVERE_SQS_URL
+const listId = process.env.REVERE_LIST_ID;
+const defaultMobileFlowId = process.env.REVERE_NEW_SUBSCRIBER_MOBILE_FLOW;
+const mobileApiKey = process.env.REVERE_MOBILE_API_KEY;
+const sendContentUrl = process.env.REVERE_API_URL;
+const akAddUserUrl = process.env.AK_ADD_USER_URL;
+const akAddPhoneUrl = process.env.AK_ADD_PHONE_URL;
+const sqsUrl = process.env.REVERE_SQS_URL;
// The Help text for the user after selecting the action
-export const instructions = () => (
- 'This option triggers a new user request to Revere when selected.'
-)
+export const instructions = () =>
+ "This option triggers a new user request to Revere when selected.";
export async function available(organizationId) {
- if ((organizationId && listId) && mobileApiKey) {
- return true
+ if (organizationId && listId && mobileApiKey) {
+ return true;
}
- return false
+ return false;
}
-export async function processAction(questionResponse, interactionStep, campaignContactId) {
- const contactRes = await r.knex('campaign_contact')
- .where('campaign_contact.id', campaignContactId)
- .leftJoin('campaign', 'campaign_contact.campaign_id', 'campaign.id')
- .leftJoin('organization', 'campaign.organization_id', 'organization.id')
- .select('campaign_contact.cell', 'campaign_contact.first_name', 'campaign_contact.last_name', 'campaign_contact.custom_fields')
+export async function processAction(
+ questionResponse,
+ interactionStep,
+ campaignContactId
+) {
+ const contactRes = await r
+ .knex("campaign_contact")
+ .where("campaign_contact.id", campaignContactId)
+ .leftJoin("campaign", "campaign_contact.campaign_id", "campaign.id")
+ .leftJoin("organization", "campaign.organization_id", "organization.id")
+ .select(
+ "campaign_contact.cell",
+ "campaign_contact.first_name",
+ "campaign_contact.last_name",
+ "campaign_contact.custom_fields"
+ );
- const contact = (contactRes.length ? contactRes[0] : {})
- const customFields = JSON.parse(contact.custom_fields)
- const mobileFlowId = (customFields.revere_signup_flow ? customFields.revere_signup_flow : defaultMobileFlowId)
- const contactCell = contact.cell.substring(1)
+ const contact = contactRes.length ? contactRes[0] : {};
+ const customFields = JSON.parse(contact.custom_fields);
+ const mobileFlowId = customFields.revere_signup_flow
+ ? customFields.revere_signup_flow
+ : defaultMobileFlowId;
+ const contactCell = contact.cell.substring(1);
if (sqsUrl) {
const msg = {
payload: {
cell: `${contactCell}`,
mobile_flow_id: `${mobileFlowId}`,
- source: 'spoke'
+ source: "spoke"
}
- }
+ };
const sqsParams = {
MessageBody: JSON.stringify(msg),
QueueUrl: sqsUrl
- }
+ };
sqs.sendMessage(sqsParams, (err, data) => {
if (err) {
- console.log('Error sending message to queue', err)
+ console.log("Error sending message to queue", err);
}
- console.log('Sent message to queue with data:', data)
- })
+ console.log("Sent message to queue with data:", data);
+ });
} else {
const options = {
- method: 'POST',
+ method: "POST",
url: sendContentUrl,
headers: {
- accept: 'application/json',
- 'content-type': 'application/json',
+ accept: "application/json",
+ "content-type": "application/json",
Authorization: mobileApiKey
},
body: {
@@ -73,12 +84,12 @@ export async function processAction(questionResponse, interactionStep, campaignC
mobileFlow: `${mobileFlowId}`
},
json: true
- }
+ };
return request(options, (error, response) => {
- if (error) throw new Error(error)
- })
+ if (error) throw new Error(error);
+ });
}
- if (akAddUserUrl && akAddPhoneUrl) actionKitSignup(contact)
+ if (akAddUserUrl && akAddPhoneUrl) actionKitSignup(contact);
}
diff --git a/src/server/action_handlers/test-action.js b/src/server/action_handlers/test-action.js
index 748d53c76..4ed9e6aca 100644
--- a/src/server/action_handlers/test-action.js
+++ b/src/server/action_handlers/test-action.js
@@ -1,15 +1,14 @@
-import request from 'request'
-import { r } from '../models'
+import request from "request";
+import { r } from "../models";
// What the user sees as the option
-export const displayName = () => 'Test Action'
+export const displayName = () => "Test Action";
// The Help text for the user after selecting the action
-export const instructions = () => (
+export const instructions = () =>
`
This action is for testing and as a code-template for new actions.
- `
-)
+ `;
// return true, if the action is usable and available for the organizationId
// Sometimes this means certain variables/credentials must be setup
@@ -17,23 +16,29 @@ export const instructions = () => (
// Besides this returning true, "test-action" will also need to be added to
// process.env.ACTION_HANDLERS
export async function available(organizationId) {
- return true
+ return true;
}
// What happens when a texter saves the answer that triggers the action
// This is presumably the meat of the action
-export async function processAction(questionResponse, interactionStep, campaignContactId) {
+export async function processAction(
+ questionResponse,
+ interactionStep,
+ campaignContactId
+) {
// This is a meta action that updates a variable in the contact record itself.
// Generally, you want to send action data to the outside world, so you
// might want the request library loaded above
- const contact = await r.knex('campaign_contact')
- .where('campaign_contact_id', campaignContactId)
- const customFields = JSON.parse(contact.custom_fields || '{}')
+ const contact = await r
+ .knex("campaign_contact")
+ .where("campaign_contact_id", campaignContactId);
+ const customFields = JSON.parse(contact.custom_fields || "{}");
if (customFields) {
- customFields['processed_test_action'] = 'completed'
+ customFields["processed_test_action"] = "completed";
}
- await r.knex('campaign_contact')
- .where('campaign_contact.id', campaignContactId)
- .update('custom_fields', JSON.stringify(customFields))
+ await r
+ .knex("campaign_contact")
+ .where("campaign_contact.id", campaignContactId)
+ .update("custom_fields", JSON.stringify(customFields));
}
diff --git a/src/server/api/assignment.js b/src/server/api/assignment.js
index 2a5d4ef0a..248849ec7 100644
--- a/src/server/api/assignment.js
+++ b/src/server/api/assignment.js
@@ -1,123 +1,146 @@
-import { mapFieldsToModel } from './lib/utils'
-import { Assignment, r, cacheableData } from '../models'
-import { getOffsets, defaultTimezoneIsBetweenTextingHours } from '../../lib'
+import { mapFieldsToModel } from "./lib/utils";
+import { Assignment, r, cacheableData } from "../models";
+import { getOffsets, defaultTimezoneIsBetweenTextingHours } from "../../lib";
export function addWhereClauseForContactsFilterMessageStatusIrrespectiveOfPastDue(
queryParameter,
messageStatusFilter
) {
if (!messageStatusFilter) {
- return queryParameter
+ return queryParameter;
}
- let query = queryParameter
- if (messageStatusFilter === 'needsMessageOrResponse') {
- query.whereIn('message_status', ['needsResponse', 'needsMessage'])
+ let query = queryParameter;
+ if (messageStatusFilter === "needsMessageOrResponse") {
+ query.whereIn("message_status", ["needsResponse", "needsMessage"]);
} else {
- query = query.whereIn('message_status', messageStatusFilter.split(','))
+ query = query.whereIn("message_status", messageStatusFilter.split(","));
}
- return query
+ return query;
}
-export function getContacts(assignment, contactsFilter, organization, campaign, forCount = false) {
+export function getContacts(
+ assignment,
+ contactsFilter,
+ organization,
+ campaign,
+ forCount = false
+) {
// / returns list of contacts eligible for contacting _now_ by a particular user
- const textingHoursEnforced = organization.texting_hours_enforced
- const textingHoursStart = organization.texting_hours_start
- const textingHoursEnd = organization.texting_hours_end
+ const textingHoursEnforced = organization.texting_hours_enforced;
+ const textingHoursStart = organization.texting_hours_start;
+ const textingHoursEnd = organization.texting_hours_end;
// 24-hours past due - why is this 24 hours offset?
- const includePastDue = (contactsFilter && contactsFilter.includePastDue)
- const pastDue = (campaign.due_by
- && Number(campaign.due_by) + 24 * 60 * 60 * 1000 < Number(new Date()))
- const config = { textingHoursStart, textingHoursEnd, textingHoursEnforced }
+ const includePastDue = contactsFilter && contactsFilter.includePastDue;
+ const pastDue =
+ campaign.due_by &&
+ Number(campaign.due_by) + 24 * 60 * 60 * 1000 < Number(new Date());
+ const config = { textingHoursStart, textingHoursEnd, textingHoursEnforced };
if (campaign.override_organization_texting_hours) {
- const textingHoursStart = campaign.texting_hours_start
- const textingHoursEnd = campaign.texting_hours_end
- const textingHoursEnforced = campaign.texting_hours_enforced
- const timezone = campaign.timezone
-
- config.campaignTextingHours = { textingHoursStart, textingHoursEnd, textingHoursEnforced, timezone }
+ const textingHoursStart = campaign.texting_hours_start;
+ const textingHoursEnd = campaign.texting_hours_end;
+ const textingHoursEnforced = campaign.texting_hours_enforced;
+ const timezone = campaign.timezone;
+
+ config.campaignTextingHours = {
+ textingHoursStart,
+ textingHoursEnd,
+ textingHoursEnforced,
+ timezone
+ };
}
- const [validOffsets, invalidOffsets] = getOffsets(config)
- if (!includePastDue && pastDue && contactsFilter && contactsFilter.messageStatus === 'needsMessage') {
- return []
+ const [validOffsets, invalidOffsets] = getOffsets(config);
+ if (
+ !includePastDue &&
+ pastDue &&
+ contactsFilter &&
+ contactsFilter.messageStatus === "needsMessage"
+ ) {
+ return [];
}
- let query = r.knex('campaign_contact').where({
+ let query = r.knex("campaign_contact").where({
assignment_id: assignment.id
- })
+ });
if (contactsFilter) {
- const validTimezone = contactsFilter.validTimezone
+ const validTimezone = contactsFilter.validTimezone;
if (validTimezone !== null) {
if (validTimezone === true) {
if (defaultTimezoneIsBetweenTextingHours(config)) {
// missing timezone ok
- validOffsets.push('')
+ validOffsets.push("");
}
- query = query.whereIn('timezone_offset', validOffsets)
+ query = query.whereIn("timezone_offset", validOffsets);
} else if (validTimezone === false) {
if (!defaultTimezoneIsBetweenTextingHours(config)) {
// missing timezones are not ok to text
- invalidOffsets.push('')
+ invalidOffsets.push("");
}
- query = query.whereIn('timezone_offset', invalidOffsets)
+ query = query.whereIn("timezone_offset", invalidOffsets);
}
}
query = addWhereClauseForContactsFilterMessageStatusIrrespectiveOfPastDue(
query,
- ((contactsFilter && contactsFilter.messageStatus) ||
- (pastDue
- // by default if asking for 'send later' contacts we include only those that need replies
- ? 'needsResponse'
- // we do not want to return closed/messaged
- : 'needsMessageOrResponse'))
- )
-
- if (Object.prototype.hasOwnProperty.call(contactsFilter, 'isOptedOut')) {
- query = query.where('is_opted_out', contactsFilter.isOptedOut)
+ (contactsFilter && contactsFilter.messageStatus) ||
+ (pastDue
+ ? // by default if asking for 'send later' contacts we include only those that need replies
+ "needsResponse"
+ : // we do not want to return closed/messaged
+ "needsMessageOrResponse")
+ );
+
+ if (Object.prototype.hasOwnProperty.call(contactsFilter, "isOptedOut")) {
+ query = query.where("is_opted_out", contactsFilter.isOptedOut);
}
}
if (!forCount) {
- if (contactsFilter && contactsFilter.messageStatus === 'convo') {
- query = query.orderByRaw('message_status DESC, updated_at DESC')
+ if (contactsFilter && contactsFilter.messageStatus === "convo") {
+ query = query.orderByRaw("message_status DESC, updated_at DESC");
} else {
- query = query.orderByRaw('message_status DESC, updated_at')
+ query = query.orderByRaw("message_status DESC, updated_at");
}
}
- return query
+ return query;
}
export const resolvers = {
Assignment: {
- ...mapFieldsToModel(['id', 'maxContacts'], Assignment),
- texter: async (assignment, _, { loaders }) => (
+ ...mapFieldsToModel(["id", "maxContacts"], Assignment),
+ texter: async (assignment, _, { loaders }) =>
assignment.texter
- ? assignment.texter
- : loaders.user.load(assignment.user_id)
- ),
- campaign: async (assignment, _, { loaders }) => loaders.campaign.load(assignment.campaign_id),
+ ? assignment.texter
+ : loaders.user.load(assignment.user_id),
+ campaign: async (assignment, _, { loaders }) =>
+ loaders.campaign.load(assignment.campaign_id),
contactsCount: async (assignment, { contactsFilter }) => {
- const campaign = await r.table('campaign').get(assignment.campaign_id)
+ const campaign = await r.table("campaign").get(assignment.campaign_id);
- const organization = await r.table('organization').get(campaign.organization_id)
+ const organization = await r
+ .table("organization")
+ .get(campaign.organization_id);
- return await r.getCount(getContacts(assignment, contactsFilter, organization, campaign, true))
+ return await r.getCount(
+ getContacts(assignment, contactsFilter, organization, campaign, true)
+ );
},
contacts: async (assignment, { contactsFilter }) => {
- const campaign = await r.table('campaign').get(assignment.campaign_id)
+ const campaign = await r.table("campaign").get(assignment.campaign_id);
- const organization = await r.table('organization').get(campaign.organization_id)
- return getContacts(assignment, contactsFilter, organization, campaign)
+ const organization = await r
+ .table("organization")
+ .get(campaign.organization_id);
+ return getContacts(assignment, contactsFilter, organization, campaign);
},
campaignCannedResponses: async assignment =>
await cacheableData.cannedResponse.query({
- userId: '',
+ userId: "",
campaignId: assignment.campaign_id
}),
userCannedResponses: async assignment =>
@@ -126,4 +149,4 @@ export const resolvers = {
campaignId: assignment.campaign_id
})
}
-}
+};
diff --git a/src/server/api/campaign-contact.js b/src/server/api/campaign-contact.js
index c523f6f7f..1ffcca56b 100644
--- a/src/server/api/campaign-contact.js
+++ b/src/server/api/campaign-contact.js
@@ -1,178 +1,196 @@
-import { CampaignContact, r, cacheableData } from '../models'
-import { mapFieldsToModel } from './lib/utils'
-import { log, getTopMostParent, zipToTimeZone } from '../../lib'
+import { CampaignContact, r, cacheableData } from "../models";
+import { mapFieldsToModel } from "./lib/utils";
+import { log, getTopMostParent, zipToTimeZone } from "../../lib";
export const resolvers = {
Location: {
- timezone: (zipCode) => zipCode || {},
- city: (zipCode) => zipCode.city || '',
- state: (zipCode) => zipCode.state || ''
+ timezone: zipCode => zipCode || {},
+ city: zipCode => zipCode.city || "",
+ state: zipCode => zipCode.state || ""
},
Timezone: {
- offset: (zipCode) => zipCode.timezone_offset || null,
- hasDST: (zipCode) => zipCode.has_dst || null
+ offset: zipCode => zipCode.timezone_offset || null,
+ hasDST: zipCode => zipCode.has_dst || null
},
CampaignContact: {
- ...mapFieldsToModel([
- 'id',
- 'firstName',
- 'lastName',
- 'cell',
- 'zip',
- 'customFields',
- 'messageStatus',
- 'assignmentId',
- 'external_id'
- ], CampaignContact),
+ ...mapFieldsToModel(
+ [
+ "id",
+ "firstName",
+ "lastName",
+ "cell",
+ "zip",
+ "customFields",
+ "messageStatus",
+ "assignmentId",
+ "external_id"
+ ],
+ CampaignContact
+ ),
messageStatus: async (campaignContact, _, { loaders }) => {
if (campaignContact.message_status) {
- return campaignContact.message_status
+ return campaignContact.message_status;
}
// TODO: look it up via cacheing
},
- campaign: async (campaignContact, _, { loaders }) => (
- loaders.campaign.load(campaignContact.campaign_id)
- ),
+ campaign: async (campaignContact, _, { loaders }) =>
+ loaders.campaign.load(campaignContact.campaign_id),
// To get that result to look like what the original code returned
// without using the outgoing answer_options array field, try this:
//
questionResponseValues: async (campaignContact, _, { loaders }) => {
- if (campaignContact.message_status === 'needsMessage') {
- return [] // it's the beginning, so there won't be any
+ if (campaignContact.message_status === "needsMessage") {
+ return []; // it's the beginning, so there won't be any
}
- return await r.knex('question_response')
- .where('question_response.campaign_contact_id', campaignContact.id)
- .select('value', 'interaction_step_id')
+ return await r
+ .knex("question_response")
+ .where("question_response.campaign_contact_id", campaignContact.id)
+ .select("value", "interaction_step_id");
},
questionResponses: async (campaignContact, _, { loaders }) => {
- const results = await r.knex('question_response as qres')
- .where('qres.campaign_contact_id', campaignContact.id)
- .join('interaction_step', 'qres.interaction_step_id', 'interaction_step.id')
- .join('interaction_step as child',
- 'qres.interaction_step_id',
- 'child.parent_interaction_id')
- .select('child.answer_option',
- 'child.id',
- 'child.parent_interaction_id',
- 'child.created_at',
- 'interaction_step.interaction_step_id',
- 'interaction_step.campaign_id',
- 'interaction_step.question',
- 'interaction_step.script',
- 'qres.id',
- 'qres.value',
- 'qres.created_at',
- 'qres.interaction_step_id')
- .catch(log.error)
+ const results = await r
+ .knex("question_response as qres")
+ .where("qres.campaign_contact_id", campaignContact.id)
+ .join(
+ "interaction_step",
+ "qres.interaction_step_id",
+ "interaction_step.id"
+ )
+ .join(
+ "interaction_step as child",
+ "qres.interaction_step_id",
+ "child.parent_interaction_id"
+ )
+ .select(
+ "child.answer_option",
+ "child.id",
+ "child.parent_interaction_id",
+ "child.created_at",
+ "interaction_step.interaction_step_id",
+ "interaction_step.campaign_id",
+ "interaction_step.question",
+ "interaction_step.script",
+ "qres.id",
+ "qres.value",
+ "qres.created_at",
+ "qres.interaction_step_id"
+ )
+ .catch(log.error);
- let formatted = {}
+ let formatted = {};
for (let i = 0; i < results.length; i++) {
- const res = results[i]
+ const res = results[i];
- const responseId = res['qres.id']
- const responseValue = res['qres.value']
- const answerValue = res['child.answer_option']
- const interactionStepId = res['child.id']
+ const responseId = res["qres.id"];
+ const responseValue = res["qres.value"];
+ const answerValue = res["child.answer_option"];
+ const interactionStepId = res["child.id"];
if (responseId in formatted) {
- formatted[responseId]['parent_interaction_step']['answer_options'].push({
- 'value': answerValue,
- 'interaction_step_id': interactionStepId
- })
+ formatted[responseId]["parent_interaction_step"][
+ "answer_options"
+ ].push({
+ value: answerValue,
+ interaction_step_id: interactionStepId
+ });
if (responseValue === answerValue) {
- formatted[responseId]['interaction_step_id'] = interactionStepId
+ formatted[responseId]["interaction_step_id"] = interactionStepId;
}
} else {
formatted[responseId] = {
- 'contact_response_value': responseValue,
- 'interaction_step_id': interactionStepId,
- 'parent_interaction_step': {
- 'answer_option': '',
- 'answer_options': [{ 'value': answerValue,
- 'interaction_step_id': interactionStepId
- }],
- 'campaign_id': res['interaction_step.campaign_id'],
- 'created_at': res['child.created_at'],
- 'id': responseId,
- 'parent_interaction_id': res['interaction_step.parent_interaction_id'],
- 'question': res['interaction_step.question'],
- 'script': res['interaction_step.script']
+ contact_response_value: responseValue,
+ interaction_step_id: interactionStepId,
+ parent_interaction_step: {
+ answer_option: "",
+ answer_options: [
+ { value: answerValue, interaction_step_id: interactionStepId }
+ ],
+ campaign_id: res["interaction_step.campaign_id"],
+ created_at: res["child.created_at"],
+ id: responseId,
+ parent_interaction_id:
+ res["interaction_step.parent_interaction_id"],
+ question: res["interaction_step.question"],
+ script: res["interaction_step.script"]
},
- 'value': responseValue
- }
+ value: responseValue
+ };
}
}
- return Object.values(formatted)
+ return Object.values(formatted);
},
location: async (campaignContact, _, { loaders }) => {
if (campaignContact.timezone_offset) {
// couldn't look up the timezone by zip record, so we load it
// from the campaign_contact directly if it's there
- const [offset, hasDst] = campaignContact.timezone_offset.split('_')
+ const [offset, hasDst] = campaignContact.timezone_offset.split("_");
const loc = {
timezone_offset: parseInt(offset, 10),
- has_dst: (hasDst === '1')
- }
+ has_dst: hasDst === "1"
+ };
// From cache
if (campaignContact.city) {
- loc.city = campaignContact.city
- loc.state = campaignContact.state || undefined
+ loc.city = campaignContact.city;
+ loc.state = campaignContact.state || undefined;
}
- return loc
+ return loc;
}
- const mainZip = campaignContact.zip.split('-')[0]
- const calculated = zipToTimeZone(mainZip)
+ const mainZip = campaignContact.zip.split("-")[0];
+ const calculated = zipToTimeZone(mainZip);
if (calculated) {
return {
timezone_offset: calculated[2],
- has_dst: (calculated[3] === 1)
- }
+ has_dst: calculated[3] === 1
+ };
}
- return await loaders.zipCode.load(mainZip)
+ return await loaders.zipCode.load(mainZip);
},
- messages: async (campaignContact) => {
- if (campaignContact.message_status === 'needsMessage') {
- return [] // it's the beginning, so there won't be any
+ messages: async campaignContact => {
+ if (campaignContact.message_status === "needsMessage") {
+ return []; // it's the beginning, so there won't be any
}
- if ('messages' in campaignContact) {
- return campaignContact.messages
+ if ("messages" in campaignContact) {
+ return campaignContact.messages;
}
- const messages = await r.table('message')
- .getAll(campaignContact.assignment_id, { index: 'assignment_id' })
+ const messages = await r
+ .table("message")
+ .getAll(campaignContact.assignment_id, { index: "assignment_id" })
.filter({
contact_number: campaignContact.cell
})
- .orderBy('created_at')
+ .orderBy("created_at");
- return messages
+ return messages;
},
optOut: async (campaignContact, _, { loaders }) => {
- if ('opt_out_cell' in campaignContact) {
+ if ("opt_out_cell" in campaignContact) {
return {
cell: campaignContact.opt_out_cell
- }
+ };
} else {
- let isOptedOut = null
- if (typeof campaignContact.is_opted_out !== 'undefined') {
- isOptedOut = campaignContact.is_opted_out
+ let isOptedOut = null;
+ if (typeof campaignContact.is_opted_out !== "undefined") {
+ isOptedOut = campaignContact.is_opted_out;
} else {
- let organizationId = campaignContact.organization_id
+ let organizationId = campaignContact.organization_id;
if (!organizationId) {
- const campaign = await loaders.campaign.load(campaignContact.campaign_id)
- organizationId = campaign.organization_id
+ const campaign = await loaders.campaign.load(
+ campaignContact.campaign_id
+ );
+ organizationId = campaign.organization_id;
}
const isOptedOut = await cacheableData.optOut.query({
cell: campaignContact.cell,
organizationId
- })
+ });
}
// fake ID so we don't need to look up existance
- return (isOptedOut ? { id: 'optout' } : null)
+ return isOptedOut ? { id: "optout" } : null;
}
}
}
-}
+};
diff --git a/src/server/api/campaign.js b/src/server/api/campaign.js
index 5e5519f3d..4bf8aa72d 100644
--- a/src/server/api/campaign.js
+++ b/src/server/api/campaign.js
@@ -1,267 +1,334 @@
-import { accessRequired } from './errors'
-import { mapFieldsToModel } from './lib/utils'
-import { Campaign, JobRequest, r, cacheableData } from '../models'
-import { currentEditors } from '../models/cacheable_queries'
-import { getUsers } from './user';
-
+import { accessRequired } from "./errors";
+import { mapFieldsToModel } from "./lib/utils";
+import { Campaign, JobRequest, r, cacheableData } from "../models";
+import { currentEditors } from "../models/cacheable_queries";
+import { getUsers } from "./user";
export function addCampaignsFilterToQuery(queryParam, campaignsFilter) {
- let query = queryParam
+ let query = queryParam;
if (campaignsFilter) {
- const resultSize = (campaignsFilter.listSize ? campaignsFilter.listSize : 0)
- const pageSize = (campaignsFilter.pageSize ? campaignsFilter.pageSize : 0)
+ const resultSize = campaignsFilter.listSize ? campaignsFilter.listSize : 0;
+ const pageSize = campaignsFilter.pageSize ? campaignsFilter.pageSize : 0;
- if ('isArchived' in campaignsFilter) {
- query = query.where('campaign.is_archived', campaignsFilter.isArchived )
+ if ("isArchived" in campaignsFilter) {
+ query = query.where("campaign.is_archived", campaignsFilter.isArchived);
}
- if ('campaignId' in campaignsFilter) {
- query = query.where('campaign.id', parseInt(campaignsFilter.campaignId, 10))
+ if ("campaignId" in campaignsFilter) {
+ query = query.where(
+ "campaign.id",
+ parseInt(campaignsFilter.campaignId, 10)
+ );
}
if (resultSize && !pageSize) {
- query = query.limit(resultSize)
+ query = query.limit(resultSize);
}
if (resultSize && pageSize) {
- query = query.limit(resultSize).offSet(pageSize)
+ query = query.limit(resultSize).offSet(pageSize);
}
}
- return query
+ return query;
}
-export function buildCampaignQuery(queryParam, organizationId, campaignsFilter, addFromClause = true) {
- let query = queryParam
+export function buildCampaignQuery(
+ queryParam,
+ organizationId,
+ campaignsFilter,
+ addFromClause = true
+) {
+ let query = queryParam;
if (addFromClause) {
- query = query.from('campaign')
+ query = query.from("campaign");
}
- query = query.where('campaign.organization_id', organizationId)
- query = addCampaignsFilterToQuery(query, campaignsFilter)
+ query = query.where("campaign.organization_id", organizationId);
+ query = addCampaignsFilterToQuery(query, campaignsFilter);
- return query
+ return query;
}
export async function getCampaigns(organizationId, cursor, campaignsFilter) {
-
let campaignsQuery = buildCampaignQuery(
- r.knex.select('*'),
+ r.knex.select("*"),
organizationId,
campaignsFilter
- )
- campaignsQuery = campaignsQuery.orderBy('due_by', 'desc').orderBy('id')
+ );
+ campaignsQuery = campaignsQuery.orderBy("due_by", "desc").orderBy("id");
if (cursor) {
- campaignsQuery = campaignsQuery.limit(cursor.limit).offset(cursor.offset)
- const campaigns = await campaignsQuery
+ campaignsQuery = campaignsQuery.limit(cursor.limit).offset(cursor.offset);
+ const campaigns = await campaignsQuery;
const campaignsCountQuery = buildCampaignQuery(
- r.knex.count('*'),
+ r.knex.count("*"),
organizationId,
- campaignsFilter)
+ campaignsFilter
+ );
- const campaignsCountArray = await campaignsCountQuery
+ const campaignsCountArray = await campaignsCountQuery;
const pageInfo = {
limit: cursor.limit,
offset: cursor.offset,
total: campaignsCountArray[0].count
- }
+ };
return {
campaigns,
pageInfo
- }
+ };
} else {
- return campaignsQuery
+ return campaignsQuery;
}
}
export const resolvers = {
JobRequest: {
- ...mapFieldsToModel([
- 'id',
- 'assigned',
- 'status',
- 'jobType',
- 'resultMessage'
- ], JobRequest)
+ ...mapFieldsToModel(
+ ["id", "assigned", "status", "jobType", "resultMessage"],
+ JobRequest
+ )
},
CampaignStats: {
sentMessagesCount: async (campaign, _, { user }) => {
- await accessRequired(user, campaign.organization_id, 'SUPERVOLUNTEER', true)
- return r.table('assignment')
- .getAll(campaign.id, { index: 'campaign_id' })
- .eqJoin('id', r.table('message'), { index: 'assignment_id' })
+ await accessRequired(
+ user,
+ campaign.organization_id,
+ "SUPERVOLUNTEER",
+ true
+ );
+ return r
+ .table("assignment")
+ .getAll(campaign.id, { index: "campaign_id" })
+ .eqJoin("id", r.table("message"), { index: "assignment_id" })
.filter({ is_from_contact: false })
- .count()
+ .count();
},
receivedMessagesCount: async (campaign, _, { user }) => {
- await accessRequired(user, campaign.organization_id, 'SUPERVOLUNTEER', true)
- return r.table('assignment')
- .getAll(campaign.id, { index: 'campaign_id' })
- // TODO: NEEDSTESTING -- see above setMessagesCount()
- .eqJoin('id', r.table('message'), { index: 'assignment_id' })
- .filter({ is_from_contact: true })
- .count()
+ await accessRequired(
+ user,
+ campaign.organization_id,
+ "SUPERVOLUNTEER",
+ true
+ );
+ return (
+ r
+ .table("assignment")
+ .getAll(campaign.id, { index: "campaign_id" })
+ // TODO: NEEDSTESTING -- see above setMessagesCount()
+ .eqJoin("id", r.table("message"), { index: "assignment_id" })
+ .filter({ is_from_contact: true })
+ .count()
+ );
},
optOutsCount: async (campaign, _, { user }) => {
- await accessRequired(user, campaign.organization_id, 'SUPERVOLUNTEER', true)
+ await accessRequired(
+ user,
+ campaign.organization_id,
+ "SUPERVOLUNTEER",
+ true
+ );
return await r.getCount(
- r.knex('campaign_contact')
+ r
+ .knex("campaign_contact")
.where({ is_opted_out: true, campaign_id: campaign.id })
- )
+ );
}
},
CampaignsReturn: {
__resolveType(obj, context, _) {
if (Array.isArray(obj)) {
- return 'CampaignsList'
- } else if ('campaigns' in obj && 'pageInfo' in obj) {
- return 'PaginatedCampaigns'
+ return "CampaignsList";
+ } else if ("campaigns" in obj && "pageInfo" in obj) {
+ return "PaginatedCampaigns";
}
- return null
+ return null;
}
},
CampaignsList: {
campaigns: campaigns => {
- return campaigns
+ return campaigns;
}
},
PaginatedCampaigns: {
campaigns: queryResult => {
- return queryResult.campaigns
+ return queryResult.campaigns;
},
pageInfo: queryResult => {
- if ('pageInfo' in queryResult) {
- return queryResult.pageInfo
+ if ("pageInfo" in queryResult) {
+ return queryResult.pageInfo;
}
- return null
+ return null;
}
},
Campaign: {
- ...mapFieldsToModel([
- 'id',
- 'title',
- 'description',
- 'isStarted',
- 'isArchived',
- 'useDynamicAssignment',
- 'introHtml',
- 'primaryColor',
- 'logoImageUrl',
- 'overrideOrganizationTextingHours',
- 'textingHoursEnforced',
- 'textingHoursStart',
- 'textingHoursEnd',
- 'timezone'
- ], Campaign),
- dueBy: (campaign) => (
- (campaign.due_by instanceof Date || !campaign.due_by)
- ? campaign.due_by || null
- : new Date(campaign.due_by)
- ),
- organization: async (campaign, _, { loaders }) => (
- campaign.organization
- || loaders.organization.load(campaign.organization_id)
- ),
- datawarehouseAvailable: (campaign, _, { user }) => (
- user.is_superadmin && !!process.env.WAREHOUSE_DB_HOST
+ ...mapFieldsToModel(
+ [
+ "id",
+ "title",
+ "description",
+ "isStarted",
+ "isArchived",
+ "useDynamicAssignment",
+ "introHtml",
+ "primaryColor",
+ "logoImageUrl",
+ "overrideOrganizationTextingHours",
+ "textingHoursEnforced",
+ "textingHoursStart",
+ "textingHoursEnd",
+ "timezone"
+ ],
+ Campaign
),
+ dueBy: campaign =>
+ campaign.due_by instanceof Date || !campaign.due_by
+ ? campaign.due_by || null
+ : new Date(campaign.due_by),
+ organization: async (campaign, _, { loaders }) =>
+ campaign.organization ||
+ loaders.organization.load(campaign.organization_id),
+ datawarehouseAvailable: (campaign, _, { user }) =>
+ user.is_superadmin && !!process.env.WAREHOUSE_DB_HOST,
pendingJobs: async (campaign, _, { user }) => {
- await accessRequired(user, campaign.organization_id, 'SUPERVOLUNTEER', true)
- return r.table('job_request')
- .filter({ campaign_id: campaign.id }).orderBy('updated_at', 'desc')
+ await accessRequired(
+ user,
+ campaign.organization_id,
+ "SUPERVOLUNTEER",
+ true
+ );
+ return r
+ .table("job_request")
+ .filter({ campaign_id: campaign.id })
+ .orderBy("updated_at", "desc");
},
texters: async (campaign, _, { user }) => {
- await accessRequired(user, campaign.organization_id, 'SUPERVOLUNTEER', true)
- return getUsers(campaign.organization_id, null, {campaignId: campaign.id })
+ await accessRequired(
+ user,
+ campaign.organization_id,
+ "SUPERVOLUNTEER",
+ true
+ );
+ return getUsers(campaign.organization_id, null, {
+ campaignId: campaign.id
+ });
},
assignments: async (campaign, { assignmentsFilter }, { user }) => {
- await accessRequired(user, campaign.organization_id, 'SUPERVOLUNTEER', true)
- let query = r.table('assignment')
- .getAll(campaign.id, { index: 'campaign_id' })
+ await accessRequired(
+ user,
+ campaign.organization_id,
+ "SUPERVOLUNTEER",
+ true
+ );
+ let query = r
+ .table("assignment")
+ .getAll(campaign.id, { index: "campaign_id" });
- if (assignmentsFilter && assignmentsFilter.hasOwnProperty('texterId') && assignmentsFilter.textId !== null) {
- query = query.filter({ user_id: assignmentsFilter.texterId })
+ if (
+ assignmentsFilter &&
+ assignmentsFilter.hasOwnProperty("texterId") &&
+ assignmentsFilter.textId !== null
+ ) {
+ query = query.filter({ user_id: assignmentsFilter.texterId });
}
- return query
+ return query;
},
interactionSteps: async (campaign, _, { user }) => {
- await accessRequired(user, campaign.organization_id, 'TEXTER', true)
- return campaign.interactionSteps
- || cacheableData.campaign.dbInteractionSteps(campaign.id)
+ await accessRequired(user, campaign.organization_id, "TEXTER", true);
+ return (
+ campaign.interactionSteps ||
+ cacheableData.campaign.dbInteractionSteps(campaign.id)
+ );
},
cannedResponses: async (campaign, { userId }, { user }) => {
- await accessRequired(user, campaign.organization_id, 'TEXTER', true)
+ await accessRequired(user, campaign.organization_id, "TEXTER", true);
return await cacheableData.cannedResponse.query({
- userId: userId || '',
+ userId: userId || "",
campaignId: campaign.id
- })
+ });
},
contacts: async (campaign, _, { user }) => {
- await accessRequired(user, campaign.organization_id, 'ADMIN', true)
+ await accessRequired(user, campaign.organization_id, "ADMIN", true);
// TODO: should we include a limit() since this is only for send-replies
- return r.knex('campaign_contact')
- .where({ campaign_id: campaign.id })
+ return r.knex("campaign_contact").where({ campaign_id: campaign.id });
},
contactsCount: async (campaign, _, { user }) => {
- await accessRequired(user, campaign.organization_id, 'SUPERVOLUNTEER', true)
+ await accessRequired(
+ user,
+ campaign.organization_id,
+ "SUPERVOLUNTEER",
+ true
+ );
return await r.getCount(
- r.knex('campaign_contact')
- .where({ campaign_id: campaign.id })
- )
+ r.knex("campaign_contact").where({ campaign_id: campaign.id })
+ );
},
hasUnassignedContactsForTexter: async (campaign, _, { user }) => {
// This is the same as hasUnassignedContacts, but the access control
// is different because for TEXTERs it's just for dynamic campaigns
// but hasUnassignedContacts for admins is for the campaigns list
- await accessRequired(user, campaign.organization_id, 'TEXTER', true)
+ await accessRequired(user, campaign.organization_id, "TEXTER", true);
if (!campaign.use_dynamic_assignment || campaign.is_archived) {
- return false
+ return false;
}
- const contacts = await r.knex('campaign_contact')
- .select('id')
+ const contacts = await r
+ .knex("campaign_contact")
+ .select("id")
.where({ campaign_id: campaign.id, assignment_id: null })
- .limit(1)
- return contacts.length > 0
+ .limit(1);
+ return contacts.length > 0;
},
hasUnassignedContacts: async (campaign, _, { user }) => {
- await accessRequired(user, campaign.organization_id, 'SUPERVOLUNTEER', true)
- const contacts = await r.knex('campaign_contact')
- .select('id')
+ await accessRequired(
+ user,
+ campaign.organization_id,
+ "SUPERVOLUNTEER",
+ true
+ );
+ const contacts = await r
+ .knex("campaign_contact")
+ .select("id")
.where({ campaign_id: campaign.id, assignment_id: null })
- .limit(1)
- return contacts.length > 0
+ .limit(1);
+ return contacts.length > 0;
},
hasUnsentInitialMessages: async (campaign, _, { user }) => {
- await accessRequired(user, campaign.organization_id, 'SUPERVOLUNTEER', true)
- const contacts = await r.knex('campaign_contact')
- .select('id')
+ await accessRequired(
+ user,
+ campaign.organization_id,
+ "SUPERVOLUNTEER",
+ true
+ );
+ const contacts = await r
+ .knex("campaign_contact")
+ .select("id")
.where({
campaign_id: campaign.id,
- message_status: 'needsMessage',
+ message_status: "needsMessage",
is_opted_out: false
})
- .limit(1)
- return contacts.length > 0
+ .limit(1);
+ return contacts.length > 0;
},
- customFields: async (campaign) => (
- campaign.customFields
- || cacheableData.campaign.dbCustomFields(campaign.id)
- ),
- stats: async (campaign) => campaign,
+ customFields: async campaign =>
+ campaign.customFields ||
+ cacheableData.campaign.dbCustomFields(campaign.id),
+ stats: async campaign => campaign,
cacheable: (campaign, _, { user }) => Boolean(r.redis),
editors: async (campaign, _, { user }) => {
- await accessRequired(user, campaign.organization_id, 'SUPERVOLUNTEER', true)
+ await accessRequired(
+ user,
+ campaign.organization_id,
+ "SUPERVOLUNTEER",
+ true
+ );
if (r.redis) {
- return cacheableData.campaign.currentEditors(campaign, user)
+ return cacheableData.campaign.currentEditors(campaign, user);
}
- return ''
+ return "";
},
- creator: async (campaign, _, { loaders }) => (
- campaign.creator_id
- ? loaders.user.load(campaign.creator_id)
- : null
- )
+ creator: async (campaign, _, { loaders }) =>
+ campaign.creator_id ? loaders.user.load(campaign.creator_id) : null
}
-}
+};
diff --git a/src/server/api/canned-response.js b/src/server/api/canned-response.js
index ac5c70434..55e659a30 100644
--- a/src/server/api/canned-response.js
+++ b/src/server/api/canned-response.js
@@ -1,15 +1,11 @@
-import { mapFieldsToModel } from './lib/utils'
-import { CannedResponse } from '../models'
+import { mapFieldsToModel } from "./lib/utils";
+import { CannedResponse } from "../models";
export const resolvers = {
CannedResponse: {
- ...mapFieldsToModel([
- 'id',
- 'title',
- 'text'
- ], CannedResponse),
- isUserCreated: (cannedResponse) => cannedResponse.user_id !== ''
+ ...mapFieldsToModel(["id", "title", "text"], CannedResponse),
+ isUserCreated: cannedResponse => cannedResponse.user_id !== ""
}
-}
+};
-CannedResponse.ensureIndex('campaign_id')
+CannedResponse.ensureIndex("campaign_id");
diff --git a/src/server/api/conversations.js b/src/server/api/conversations.js
index 12cfdb8b2..bb13d0deb 100644
--- a/src/server/api/conversations.js
+++ b/src/server/api/conversations.js
@@ -1,8 +1,8 @@
-import _ from 'lodash'
-import { Assignment, r } from '../models'
-import { addWhereClauseForContactsFilterMessageStatusIrrespectiveOfPastDue } from './assignment'
-import { buildCampaignQuery } from './campaign'
-import { log } from '../../lib'
+import _ from "lodash";
+import { Assignment, r } from "../models";
+import { addWhereClauseForContactsFilterMessageStatusIrrespectiveOfPastDue } from "./assignment";
+import { buildCampaignQuery } from "./campaign";
+import { log } from "../../lib";
function getConversationsJoinsAndWhereClause(
queryParam,
@@ -12,34 +12,36 @@ function getConversationsJoinsAndWhereClause(
contactsFilter
) {
let query = queryParam
- .leftJoin('campaign_contact', 'campaign.id', 'campaign_contact.campaign_id')
- .leftJoin('assignment', 'campaign_contact.assignment_id', 'assignment.id')
- .leftJoin('user', 'assignment.user_id', 'user.id')
- .where({ 'campaign.organization_id': organizationId })
+ .leftJoin("campaign_contact", "campaign.id", "campaign_contact.campaign_id")
+ .leftJoin("assignment", "campaign_contact.assignment_id", "assignment.id")
+ .leftJoin("user", "assignment.user_id", "user.id")
+ .where({ "campaign.organization_id": organizationId });
- query = buildCampaignQuery(query, organizationId, campaignsFilter)
+ query = buildCampaignQuery(query, organizationId, campaignsFilter);
if (assignmentsFilter) {
- if ('texterId' in assignmentsFilter && assignmentsFilter.texterId !== null)
- query = query.where({ 'assignment.user_id': assignmentsFilter.texterId })
+ if ("texterId" in assignmentsFilter && assignmentsFilter.texterId !== null)
+ query = query.where({ "assignment.user_id": assignmentsFilter.texterId });
}
query = addWhereClauseForContactsFilterMessageStatusIrrespectiveOfPastDue(
query,
- contactsFilter && contactsFilter.messageStatus)
-
- if (contactsFilter && 'isOptedOut' in contactsFilter) {
- const subQuery = (r.knex.select('cell')
- .from('opt_out')
- .whereRaw('opt_out.cell=campaign_contact.cell'))
+ contactsFilter && contactsFilter.messageStatus
+ );
+
+ if (contactsFilter && "isOptedOut" in contactsFilter) {
+ const subQuery = r.knex
+ .select("cell")
+ .from("opt_out")
+ .whereRaw("opt_out.cell=campaign_contact.cell");
if (contactsFilter.isOptedOut) {
- query = query.whereExists(subQuery)
+ query = query.whereExists(subQuery);
} else {
- query = query.whereNotExists(subQuery)
+ query = query.whereNotExists(subQuery);
}
}
- return query
+ return query;
}
/*
@@ -51,12 +53,12 @@ results can be consumed by downstream resolvers.
*/
function mapQueryFieldsToResolverFields(queryResult, fieldsMap) {
return _.mapKeys(queryResult, (value, key) => {
- const newKey = fieldsMap[key]
+ const newKey = fieldsMap[key];
if (newKey) {
- return newKey
+ return newKey;
}
- return key
- })
+ return key;
+ });
}
export async function getConversations(
@@ -68,8 +70,8 @@ export async function getConversations(
utc
) {
/* Query #1 == get campaign_contact.id for all the conversations matching
- * the criteria with offset and limit. */
- let offsetLimitQuery = r.knex.select('campaign_contact.id as cc_id')
+ * the criteria with offset and limit. */
+ let offsetLimitQuery = r.knex.select("campaign_contact.id as cc_id");
offsetLimitQuery = getConversationsJoinsAndWhereClause(
offsetLimitQuery,
@@ -77,44 +79,44 @@ export async function getConversations(
campaignsFilter,
assignmentsFilter,
contactsFilter
- )
+ );
offsetLimitQuery = offsetLimitQuery
- .orderBy('campaign_contact.updated_at')
- .orderBy('cc_id')
- offsetLimitQuery = offsetLimitQuery.limit(cursor.limit).offset(cursor.offset)
+ .orderBy("campaign_contact.updated_at")
+ .orderBy("cc_id");
+ offsetLimitQuery = offsetLimitQuery.limit(cursor.limit).offset(cursor.offset);
- const ccIdRows = await offsetLimitQuery
- const ccIds = ccIdRows.map((ccIdRow) => {
- return ccIdRow.cc_id
- })
+ const ccIdRows = await offsetLimitQuery;
+ const ccIds = ccIdRows.map(ccIdRow => {
+ return ccIdRow.cc_id;
+ });
/* Query #2 -- get all the columns we need, including messages, using the
- * cc_ids from Query #1 to scope the results to limit, offset */
+ * cc_ids from Query #1 to scope the results to limit, offset */
let query = r.knex.select(
- 'campaign_contact.id as cc_id',
- 'campaign_contact.first_name as cc_first_name',
- 'campaign_contact.last_name as cc_last_name',
- 'campaign_contact.cell',
- 'campaign_contact.message_status',
- 'campaign_contact.is_opted_out',
- 'campaign_contact.updated_at',
- 'campaign_contact.assignment_id',
- 'opt_out.cell as opt_out_cell',
- 'user.id as u_id',
- 'user.first_name as u_first_name',
- 'user.last_name as u_last_name',
- 'campaign.id as cmp_id',
- 'campaign.title',
- 'campaign.due_by',
- 'assignment.id as ass_id',
- 'message.id as mess_id',
- 'message.text',
- 'message.user_number',
- 'message.contact_number',
- 'message.created_at',
- 'message.is_from_contact'
- )
+ "campaign_contact.id as cc_id",
+ "campaign_contact.first_name as cc_first_name",
+ "campaign_contact.last_name as cc_last_name",
+ "campaign_contact.cell",
+ "campaign_contact.message_status",
+ "campaign_contact.is_opted_out",
+ "campaign_contact.updated_at",
+ "campaign_contact.assignment_id",
+ "opt_out.cell as opt_out_cell",
+ "user.id as u_id",
+ "user.first_name as u_first_name",
+ "user.last_name as u_last_name",
+ "campaign.id as cmp_id",
+ "campaign.title",
+ "campaign.due_by",
+ "assignment.id as ass_id",
+ "message.id as mess_id",
+ "message.text",
+ "message.user_number",
+ "message.contact_number",
+ "message.created_at",
+ "message.is_from_contact"
+ );
query = getConversationsJoinsAndWhereClause(
query,
@@ -122,88 +124,90 @@ export async function getConversations(
campaignsFilter,
assignmentsFilter,
contactsFilter
- )
+ );
- query = query.whereIn('campaign_contact.id', ccIds)
+ query = query.whereIn("campaign_contact.id", ccIds);
- query = query.leftJoin('message', table => {
+ query = query.leftJoin("message", table => {
table
- .on('message.assignment_id', '=', 'assignment.id')
- .andOn('message.contact_number', '=', 'campaign_contact.cell')
- })
+ .on("message.assignment_id", "=", "assignment.id")
+ .andOn("message.contact_number", "=", "campaign_contact.cell");
+ });
query = query
- .leftJoin('opt_out', table => {
+ .leftJoin("opt_out", table => {
table
- .on('opt_out.organization_id', '=', 'campaign.organization_id')
- .andOn('campaign_contact.cell', 'opt_out.cell')
+ .on("opt_out.organization_id", "=", "campaign.organization_id")
+ .andOn("campaign_contact.cell", "opt_out.cell");
})
- .orderBy('campaign_contact.updated_at')
- .orderBy('cc_id')
- .orderBy('message.created_at')
+ .orderBy("campaign_contact.updated_at")
+ .orderBy("cc_id")
+ .orderBy("message.created_at");
- const conversationRows = await query
+ const conversationRows = await query;
/* collapse the rows to produce an array of objects, with each object
- * containing the fields for one conversation, each having an array of
- * message objects */
+ * containing the fields for one conversation, each having an array of
+ * message objects */
const messageFields = [
- 'mess_id',
- 'text',
- 'user_number',
- 'contact_number',
- 'created_at',
- 'is_from_contact'
- ]
-
- let ccId = undefined
- let conversation = undefined
- const conversations = []
+ "mess_id",
+ "text",
+ "user_number",
+ "contact_number",
+ "created_at",
+ "is_from_contact"
+ ];
+
+ let ccId = undefined;
+ let conversation = undefined;
+ const conversations = [];
for (const conversationRow of conversationRows) {
if (ccId !== conversationRow.cc_id) {
- ccId = conversationRow.cc_id
- conversation = _.omit(conversationRow, messageFields)
- conversation.messages = []
- conversations.push(conversation)
+ ccId = conversationRow.cc_id;
+ conversation = _.omit(conversationRow, messageFields);
+ conversation.messages = [];
+ conversations.push(conversation);
}
conversation.messages.push(
- mapQueryFieldsToResolverFields(_.pick(conversationRow, messageFields), { mess_id: 'id' })
- )
+ mapQueryFieldsToResolverFields(_.pick(conversationRow, messageFields), {
+ mess_id: "id"
+ })
+ );
}
/* Query #3 -- get the count of all conversations matching the criteria.
- * We need this to show total number of conversations to support paging */
- const countQuery = r.knex.count('*')
+ * We need this to show total number of conversations to support paging */
+ const countQuery = r.knex.count("*");
const conversationsCountArray = await getConversationsJoinsAndWhereClause(
countQuery,
organizationId,
campaignsFilter,
assignmentsFilter,
contactsFilter
- )
+ );
const pageInfo = {
limit: cursor.limit,
offset: cursor.offset,
total: conversationsCountArray[0].count
- }
+ };
return {
conversations,
pageInfo
- }
+ };
}
export async function getCampaignIdMessageIdsAndCampaignIdContactIdsMaps(
organizationId,
campaignsFilter,
assignmentsFilter,
- contactsFilter,
+ contactsFilter
) {
let query = r.knex.select(
- 'campaign_contact.id as cc_id',
- 'campaign.id as cmp_id',
- 'message.id as mess_id',
- )
+ "campaign_contact.id as cc_id",
+ "campaign.id as cmp_id",
+ "message.id as mess_id"
+ );
query = getConversationsJoinsAndWhereClause(
query,
@@ -211,145 +215,149 @@ export async function getCampaignIdMessageIdsAndCampaignIdContactIdsMaps(
campaignsFilter,
assignmentsFilter,
contactsFilter
- )
+ );
- query = query.leftJoin('message', table => {
+ query = query.leftJoin("message", table => {
table
- .on('message.assignment_id', '=', 'assignment.id')
- .andOn('message.contact_number', '=', 'campaign_contact.cell')
- })
+ .on("message.assignment_id", "=", "assignment.id")
+ .andOn("message.contact_number", "=", "campaign_contact.cell");
+ });
- query = query
- .orderBy('cc_id')
+ query = query.orderBy("cc_id");
- const conversationRows = await query
+ const conversationRows = await query;
- const campaignIdContactIdsMap = new Map()
- const campaignIdMessagesIdsMap = new Map()
+ const campaignIdContactIdsMap = new Map();
+ const campaignIdMessagesIdsMap = new Map();
- let ccId = undefined
+ let ccId = undefined;
for (const conversationRow of conversationRows) {
if (ccId !== conversationRow.cc_id) {
- const ccId = conversationRow.cc_id
- campaignIdContactIdsMap[conversationRow.cmp_id] = ccId
+ const ccId = conversationRow.cc_id;
+ campaignIdContactIdsMap[conversationRow.cmp_id] = ccId;
if (!campaignIdContactIdsMap.has(conversationRow.cmp_id)) {
- campaignIdContactIdsMap.set(conversationRow.cmp_id, [])
+ campaignIdContactIdsMap.set(conversationRow.cmp_id, []);
}
- campaignIdContactIdsMap.get(conversationRow.cmp_id).push(ccId)
+ campaignIdContactIdsMap.get(conversationRow.cmp_id).push(ccId);
if (!campaignIdMessagesIdsMap.has(conversationRow.cmp_id)) {
- campaignIdMessagesIdsMap.set(conversationRow.cmp_id, [])
+ campaignIdMessagesIdsMap.set(conversationRow.cmp_id, []);
}
}
if (conversationRow.mess_id) {
- campaignIdMessagesIdsMap.get(conversationRow.cmp_id).push(conversationRow.mess_id)
-
+ campaignIdMessagesIdsMap
+ .get(conversationRow.cmp_id)
+ .push(conversationRow.mess_id);
}
}
return {
campaignIdContactIdsMap,
campaignIdMessagesIdsMap
- }
+ };
}
-export async function reassignConversations(campaignIdContactIdsMap, campaignIdMessagesIdsMap, newTexterUserId) {
+export async function reassignConversations(
+ campaignIdContactIdsMap,
+ campaignIdMessagesIdsMap,
+ newTexterUserId
+) {
// ensure existence of assignments
- const campaignIdAssignmentIdMap = new Map()
+ const campaignIdAssignmentIdMap = new Map();
for (const [campaignId, _] of campaignIdContactIdsMap) {
let assignment = await r
- .table('assignment')
- .getAll(newTexterUserId, { index: 'user_id' })
+ .table("assignment")
+ .getAll(newTexterUserId, { index: "user_id" })
.filter({ campaign_id: campaignId })
.limit(1)(0)
- .default(null)
+ .default(null);
if (!assignment) {
assignment = await Assignment.save({
user_id: newTexterUserId,
campaign_id: campaignId,
max_contacts: parseInt(process.env.MAX_CONTACTS_PER_TEXTER || 0, 10)
- })
+ });
}
- campaignIdAssignmentIdMap.set(campaignId, assignment.id)
+ campaignIdAssignmentIdMap.set(campaignId, assignment.id);
}
// do the reassignment
- const returnCampaignIdAssignmentIds = []
+ const returnCampaignIdAssignmentIds = [];
// TODO(larry) do this in a transaction!
try {
for (const [campaignId, campaignContactIds] of campaignIdContactIdsMap) {
- const assignmentId = campaignIdAssignmentIdMap.get(campaignId)
+ const assignmentId = campaignIdAssignmentIdMap.get(campaignId);
await r
- .knex('campaign_contact')
- .where('campaign_id', campaignId)
- .whereIn('id', campaignContactIds)
+ .knex("campaign_contact")
+ .where("campaign_id", campaignId)
+ .whereIn("id", campaignContactIds)
.update({
assignment_id: assignmentId
- })
+ });
returnCampaignIdAssignmentIds.push({
campaignId,
assignmentId: assignmentId.toString()
- })
+ });
}
for (const [campaignId, messageIds] of campaignIdMessagesIdsMap) {
- const assignmentId = campaignIdAssignmentIdMap.get(campaignId)
+ const assignmentId = campaignIdAssignmentIdMap.get(campaignId);
await r
- .knex('message')
+ .knex("message")
.whereIn(
- 'id',
+ "id",
messageIds.map(messageId => {
- return messageId
+ return messageId;
})
)
.update({
assignment_id: assignmentId
- })
+ });
}
} catch (error) {
- log.error(error)
+ log.error(error);
}
- return returnCampaignIdAssignmentIds
+ return returnCampaignIdAssignmentIds;
}
export const resolvers = {
PaginatedConversations: {
conversations: queryResult => {
- return queryResult.conversations
+ return queryResult.conversations;
},
pageInfo: queryResult => {
- if ('pageInfo' in queryResult) {
- return queryResult.pageInfo
+ if ("pageInfo" in queryResult) {
+ return queryResult.pageInfo;
} else {
- return null
+ return null;
}
}
},
Conversation: {
texter: queryResult => {
return mapQueryFieldsToResolverFields(queryResult, {
- u_id: 'id',
- u_first_name: 'first_name',
- u_last_name: 'last_name'
- })
+ u_id: "id",
+ u_first_name: "first_name",
+ u_last_name: "last_name"
+ });
},
contact: queryResult => {
return mapQueryFieldsToResolverFields(queryResult, {
- cc_id: 'id',
- cc_first_name: 'first_name',
- cc_last_name: 'last_name',
- opt_out_cell: 'opt_out_cell'
- })
+ cc_id: "id",
+ cc_first_name: "first_name",
+ cc_last_name: "last_name",
+ opt_out_cell: "opt_out_cell"
+ });
},
campaign: queryResult => {
- return mapQueryFieldsToResolverFields(queryResult, { cmp_id: 'id' })
+ return mapQueryFieldsToResolverFields(queryResult, { cmp_id: "id" });
}
}
-}
+};
diff --git a/src/server/api/errors.js b/src/server/api/errors.js
index e01728525..56affbd81 100644
--- a/src/server/api/errors.js
+++ b/src/server/api/errors.js
@@ -1,57 +1,65 @@
-import { GraphQLError } from 'graphql/error'
-import { r, cacheableData } from '../models'
+import { GraphQLError } from "graphql/error";
+import { r, cacheableData } from "../models";
export function authRequired(user) {
if (!user) {
throw new GraphQLError({
status: 401,
- message: 'You must login to access that resource.'
- })
+ message: "You must login to access that resource."
+ });
}
}
-export async function accessRequired(user, orgId, role, allowSuperadmin = false) {
- authRequired(user)
+export async function accessRequired(
+ user,
+ orgId,
+ role,
+ allowSuperadmin = false
+) {
+ authRequired(user);
if (!orgId) {
- throw new Error('orgId not passed correctly to accessRequired')
+ throw new Error("orgId not passed correctly to accessRequired");
}
if (allowSuperadmin && user.is_superadmin) {
- return
+ return;
}
// require a permission at-or-higher than the permission requested
- const hasRole = await cacheableData.user.userHasRole(user, orgId, role)
+ const hasRole = await cacheableData.user.userHasRole(user, orgId, role);
if (!hasRole) {
- throw new GraphQLError('You are not authorized to access that resource.')
+ throw new GraphQLError("You are not authorized to access that resource.");
}
}
export async function assignmentRequired(user, assignmentId, assignment) {
- authRequired(user)
+ authRequired(user);
if (user.is_superadmin) {
- return true
+ return true;
}
if (assignment && assignment.user_id === user.id) {
// if we are passed the full assignment object, we can test directly
- return true
+ return true;
}
- const [userHasAssignment] = await r.knex('assignment')
- .where({
- user_id: user.id,
- id: assignmentId
- }).limit(1)
+ const [userHasAssignment] = await r
+ .knex("assignment")
+ .where({
+ user_id: user.id,
+ id: assignmentId
+ })
+ .limit(1);
- if (!userHasAssignment) { // undefined or null
- throw new GraphQLError('You are not authorized to access that resource.')
+ if (!userHasAssignment) {
+ // undefined or null
+ throw new GraphQLError("You are not authorized to access that resource.");
}
- return true
+ return true;
}
export function superAdminRequired(user) {
- authRequired(user)
+ authRequired(user);
if (!user.is_superadmin) {
- throw new GraphQLError('You are not authorized to access that resource.')
+ throw new GraphQLError("You are not authorized to access that resource.");
}
}
diff --git a/src/server/api/interaction-step.js b/src/server/api/interaction-step.js
index 3f5dc473c..d3d73409f 100644
--- a/src/server/api/interaction-step.js
+++ b/src/server/api/interaction-step.js
@@ -1,28 +1,31 @@
-import { mapFieldsToModel } from './lib/utils'
-import { InteractionStep, r } from '../models'
+import { mapFieldsToModel } from "./lib/utils";
+import { InteractionStep, r } from "../models";
export const resolvers = {
InteractionStep: {
- ...mapFieldsToModel([
- 'id',
- 'script',
- 'answerOption',
- 'answerActions',
- 'parentInteractionId',
- 'isDeleted'
- ], InteractionStep),
- questionText: async(interactionStep) => {
- return interactionStep.question
+ ...mapFieldsToModel(
+ [
+ "id",
+ "script",
+ "answerOption",
+ "answerActions",
+ "parentInteractionId",
+ "isDeleted"
+ ],
+ InteractionStep
+ ),
+ questionText: async interactionStep => {
+ return interactionStep.question;
},
- question: async (interactionStep) => interactionStep,
- questionResponse: async (interactionStep, { campaignContactId }) => (
- r.table('question_response')
- .getAll(campaignContactId, { index: 'campaign_contact_id' })
+ question: async interactionStep => interactionStep,
+ questionResponse: async (interactionStep, { campaignContactId }) =>
+ r
+ .table("question_response")
+ .getAll(campaignContactId, { index: "campaign_contact_id" })
.filter({
interaction_step_id: interactionStep.id
})
.limit(1)(0)
.default(null)
- )
}
-}
+};
diff --git a/src/server/api/invite.js b/src/server/api/invite.js
index bc53461d4..4965266d5 100644
--- a/src/server/api/invite.js
+++ b/src/server/api/invite.js
@@ -1,12 +1,8 @@
-import { mapFieldsToModel } from './lib/utils'
-import { Invite } from '../models'
+import { mapFieldsToModel } from "./lib/utils";
+import { Invite } from "../models";
export const resolvers = {
Invite: {
- ...mapFieldsToModel([
- 'id',
- 'isValid',
- 'hash'
- ], Invite)
+ ...mapFieldsToModel(["id", "isValid", "hash"], Invite)
}
-}
+};
diff --git a/src/server/api/lib/fakeservice.js b/src/server/api/lib/fakeservice.js
index 547d2e08d..ef54b96e2 100644
--- a/src/server/api/lib/fakeservice.js
+++ b/src/server/api/lib/fakeservice.js
@@ -1,6 +1,6 @@
-import { getLastMessage } from './message-sending'
-import { Message, PendingMessagePart, r } from '../../models'
-import { log } from '../../../lib'
+import { getLastMessage } from "./message-sending";
+import { Message, PendingMessagePart, r } from "../../models";
+import { log } from "../../../lib";
// This 'fakeservice' allows for fake-sending messages
// that end up just in the db appropriately and then using sendReply() graphql
@@ -9,24 +9,22 @@ import { log } from '../../../lib'
async function sendMessage(message, contact, trx) {
const newMessage = new Message({
...message,
- service: 'fakeservice',
- send_status: 'SENT',
- sent_at: new Date(),
- })
+ service: "fakeservice",
+ send_status: "SENT",
+ sent_at: new Date()
+ });
if (message && message.id) {
- let request = r.knex('message')
+ let request = r.knex("message");
if (trx) {
- request = request.transacting(trx)
+ request = request.transacting(trx);
}
// updating message!
- await request
- .where('id', message.id)
- .update({
- service: 'fakeservice',
- send_status: 'SENT',
- sent_at: new Date()
- })
+ await request.where("id", message.id).update({
+ service: "fakeservice",
+ send_status: "SENT",
+ sent_at: new Date()
+ });
}
if (contact && /autorespond/.test(message.text)) {
@@ -38,29 +36,32 @@ async function sendMessage(message, contact, trx) {
service_id: `mockedresponse${Math.random()}`,
is_from_contact: true,
text: `responding to ${message.text}`,
- send_status: 'DELIVERED'
- })
- contact.message_status = 'needsResponse'
- await contact.save()
+ send_status: "DELIVERED"
+ });
+ contact.message_status = "needsResponse";
+ await contact.save();
}
- return newMessage
+ return newMessage;
}
// None of the rest of this is even used for fake-service
// but *would* be used if it was actually an outside service.
async function convertMessagePartsToMessage(messageParts) {
- const firstPart = messageParts[0]
- const userNumber = firstPart.user_number
- const contactNumber = firstPart.contact_number
- const text = firstPart.service_message
+ const firstPart = messageParts[0];
+ const userNumber = firstPart.user_number;
+ const contactNumber = firstPart.contact_number;
+ const text = firstPart.service_message;
const lastMessage = await getLastMessage({
contactNumber
- })
+ });
- const service_id = (firstPart.service_id
- || `fakeservice_${Math.random().toString(36).replace(/[^a-zA-Z1-9]+/g, '')}`)
+ const service_id =
+ firstPart.service_id ||
+ `fakeservice_${Math.random()
+ .toString(36)
+ .replace(/[^a-zA-Z1-9]+/g, "")}`;
return new Message({
contact_number: contactNumber,
user_number: userNumber,
@@ -69,24 +70,24 @@ async function convertMessagePartsToMessage(messageParts) {
service_response: JSON.stringify(messageParts),
service_id,
assignment_id: lastMessage.assignment_id,
- service: 'fakeservice',
- send_status: 'DELIVERED'
- })
+ service: "fakeservice",
+ send_status: "DELIVERED"
+ });
}
async function handleIncomingMessage(message) {
- const { contact_number, user_number, service_id, text } = message
+ const { contact_number, user_number, service_id, text } = message;
const pendingMessagePart = new PendingMessagePart({
- service: 'fakeservice',
+ service: "fakeservice",
service_id,
parent_id: null,
service_message: text,
user_number,
contact_number
- })
+ });
- const part = await pendingMessagePart.save()
- return part.id
+ const part = await pendingMessagePart.save();
+ return part.id;
}
export default {
@@ -94,4 +95,4 @@ export default {
// useless unused stubs
convertMessagePartsToMessage,
handleIncomingMessage
-}
+};
diff --git a/src/server/api/lib/import-script..js b/src/server/api/lib/import-script..js
index 9c582b8c1..bc64ac9e2 100644
--- a/src/server/api/lib/import-script..js
+++ b/src/server/api/lib/import-script..js
@@ -1,296 +1,382 @@
-import {
- google
-} from 'googleapis'
-
-import _ from 'lodash'
-import {
- compose,
- map,
- reduce,
- getOr,
- find,
- filter,
- has
-} from 'lodash/fp'
-
-import {
- r
-} from '../../models'
-
-const textRegex = RegExp('.*[A-Za-z0-9]+.*')
-
-const getDocument = async (documentId) => {
- const auth = google.auth.fromJSON(JSON.parse(process.env.GOOGLE_SECRET))
- auth.scopes = ['https://www.googleapis.com/auth/documents']
+import { google } from "googleapis";
+
+import _ from "lodash";
+import { compose, map, reduce, getOr, find, filter, has } from "lodash/fp";
+
+import { r } from "../../models";
+
+const textRegex = RegExp(".*[A-Za-z0-9]+.*");
+
+const getDocument = async documentId => {
+ const auth = google.auth.fromJSON(JSON.parse(process.env.GOOGLE_SECRET));
+ auth.scopes = ["https://www.googleapis.com/auth/documents"];
const docs = google.docs({
- version: 'v1',
+ version: "v1",
auth
- })
+ });
- let result = null
+ let result = null;
try {
result = await docs.documents.get({
documentId
- })
+ });
} catch (err) {
- console.log(err)
- throw new Error(err.message)
+ console.log(err);
+ throw new Error(err.message);
}
- return result
-}
+ return result;
+};
-const getParagraphStyle = getOr('', 'paragraph.paragraphStyle.namedStyleType')
-const getTextRun = getOr('', 'textRun.content')
-const sanitizeTextRun = (textRun) => textRun.replace('\n', '')
+const getParagraphStyle = getOr("", "paragraph.paragraphStyle.namedStyleType");
+const getTextRun = getOr("", "textRun.content");
+const sanitizeTextRun = textRun => textRun.replace("\n", "");
const getSanitizedTextRun = compose(
sanitizeTextRun,
- getTextRun)
-const concat = (left, right) => left.concat(right)
-const reduceStrings = reduce(concat, String())
+ getTextRun
+);
+const concat = (left, right) => left.concat(right);
+const reduceStrings = reduce(concat, String());
const getParagraphText = compose(
reduceStrings,
map(getSanitizedTextRun),
- getOr([], 'paragraph.elements'))
-const getParagraphIndent = getOr(0, 'paragraph.paragraphStyle.indentFirstLine.magnitude')
+ getOr([], "paragraph.elements")
+);
+const getParagraphIndent = getOr(
+ 0,
+ "paragraph.paragraphStyle.indentFirstLine.magnitude"
+);
const getParagraphBold = compose(
- getOr(false, 'textRun.textStyle.bold'),
+ getOr(false, "textRun.textStyle.bold"),
find(getTextRun),
- getOr([], 'paragraph.elements'))
-const getParagraph = (element) => ({
+ getOr([], "paragraph.elements")
+);
+const getParagraph = element => ({
style: getParagraphStyle(element),
indent: getParagraphIndent(element),
isParagraphBold: getParagraphBold(element),
text: getParagraphText(element)
-})
-const hasParagraph = has('paragraph')
-const hasText = (paragraph) => !!paragraph.text && textRegex.test(paragraph.text.trim())
-const pushAndReturnSection = (sections) => {
+});
+const hasParagraph = has("paragraph");
+const hasText = paragraph =>
+ !!paragraph.text && textRegex.test(paragraph.text.trim());
+const pushAndReturnSection = sections => {
const newSection = {
paragraphs: []
- }
- sections.push(newSection)
- return newSection
-}
+ };
+ sections.push(newSection);
+ return newSection;
+};
-const getLastSection = (sections) => _.last(sections) || pushAndReturnSection(sections)
+const getLastSection = sections =>
+ _.last(sections) || pushAndReturnSection(sections);
const addParagraph = (accumulatorInput, value) => {
- const accumulator = accumulatorInput || []
- getLastSection(accumulator).paragraphs.push(value)
- return accumulator
-}
+ const accumulator = accumulatorInput || [];
+ getLastSection(accumulator).paragraphs.push(value);
+ return accumulator;
+};
-const sanitizeHeaderText = (header) => header.replace(/[^A-Za-z0-9 ]/g, '')
+const sanitizeHeaderText = header => header.replace(/[^A-Za-z0-9 ]/g, "");
const addHeader = (accumulatorInput, value) => {
- const accumulator = accumulatorInput || []
- accumulator.push(_.assign(_.clone(value), {
- text: sanitizeHeaderText(value.text),
- paragraphs: []
- }))
- return accumulator
-}
+ const accumulator = accumulatorInput || [];
+ accumulator.push(
+ _.assign(_.clone(value), {
+ text: sanitizeHeaderText(value.text),
+ paragraphs: []
+ })
+ );
+ return accumulator;
+};
-const addSection = (accumulator, value) => (value.style === 'HEADING_2' ? addHeader(accumulator, value) : addParagraph(accumulator, value))
+const addSection = (accumulator, value) =>
+ value.style === "HEADING_2"
+ ? addHeader(accumulator, value)
+ : addParagraph(accumulator, value);
const getSections = compose(
reduce(addSection, null),
filter(hasText),
map(getParagraph),
filter(hasParagraph)
-)
-
-const getSectionParagraphs = (sections, heading) => (sections.find((section) => section.text && section.text.toLowerCase() === heading.toLowerCase()) || {}).paragraphs
-
-const getInteractions = (sections) => getSectionParagraphs(sections, 'Interactions')
-const getCannedResponses = (sections) => getSectionParagraphs(sections, 'Canned Responses')
-
-const isNextChunkAQuestion = (interactionParagraphs, currentIndent) => interactionParagraphs.length > 1 &&
+);
+
+const getSectionParagraphs = (sections, heading) =>
+ (
+ sections.find(
+ section =>
+ section.text && section.text.toLowerCase() === heading.toLowerCase()
+ ) || {}
+ ).paragraphs;
+
+const getInteractions = sections =>
+ getSectionParagraphs(sections, "Interactions");
+const getCannedResponses = sections =>
+ getSectionParagraphs(sections, "Canned Responses");
+
+const isNextChunkAQuestion = (interactionParagraphs, currentIndent) =>
+ interactionParagraphs.length > 1 &&
interactionParagraphs[0].indent === currentIndent &&
interactionParagraphs[1].indent > currentIndent &&
- interactionParagraphs[1].isParagraphBold
+ interactionParagraphs[1].isParagraphBold;
-const isNextChunkAnAnswer = (interactionParagraphs, currentIndent) => interactionParagraphs.length > 1 &&
+const isNextChunkAnAnswer = (interactionParagraphs, currentIndent) =>
+ interactionParagraphs.length > 1 &&
interactionParagraphs[0].indent === currentIndent &&
interactionParagraphs[1].indent === currentIndent &&
- !interactionParagraphs[1].isParagraphBold
+ !interactionParagraphs[1].isParagraphBold;
-const isError = (interactionParagraphs, currentIndent) => interactionParagraphs.length > 1 &&
+const isError = (interactionParagraphs, currentIndent) =>
+ interactionParagraphs.length > 1 &&
interactionParagraphs[0].indent === currentIndent &&
- ((interactionParagraphs[1].indent === currentIndent && interactionParagraphs[1].isParagraphBold) ||
- (interactionParagraphs[1].indent > currentIndent && !interactionParagraphs[1].isParagraphBold))
-
-const isThisALeaf = (interactionParagraphs, currentIndent) => interactionParagraphs.length < 2 ||
- interactionParagraphs[1].indent < currentIndent
-
-const saveCurrentNodeToParent = (parentHierarchyNode, currentHierarchyNode) => parentHierarchyNode && parentHierarchyNode.children.push(currentHierarchyNode)
-
-const throwInteractionsHierarchyError = (message, interactionsHierarchyNode, interactionParagraphs) => {
+ ((interactionParagraphs[1].indent === currentIndent &&
+ interactionParagraphs[1].isParagraphBold) ||
+ (interactionParagraphs[1].indent > currentIndent &&
+ !interactionParagraphs[1].isParagraphBold));
+
+const isThisALeaf = (interactionParagraphs, currentIndent) =>
+ interactionParagraphs.length < 2 ||
+ interactionParagraphs[1].indent < currentIndent;
+
+const saveCurrentNodeToParent = (parentHierarchyNode, currentHierarchyNode) =>
+ parentHierarchyNode &&
+ parentHierarchyNode.children.push(currentHierarchyNode);
+
+const throwInteractionsHierarchyError = (
+ message,
+ interactionsHierarchyNode,
+ interactionParagraphs
+) => {
const lookFor = [
- ...(interactionsHierarchyNode.answer ? [interactionsHierarchyNode.answer] : []),
- ...(interactionsHierarchyNode.script ? interactionsHierarchyNode.script : []),
+ ...(interactionsHierarchyNode.answer
+ ? [interactionsHierarchyNode.answer]
+ : []),
+ ...(interactionsHierarchyNode.script
+ ? interactionsHierarchyNode.script
+ : []),
...(interactionParagraphs[0] ? [interactionParagraphs[0].text] : []),
...(interactionParagraphs[1] ? [interactionParagraphs[1].text] : [])
- ].join(' | ')
- throw new Error(`${message} Look for ${lookFor}`)
-}
-
-const makeInteractionHierarchy = (interactionParagraphs, parentHierarchyNode) => {
- if (!interactionParagraphs.length || interactionParagraphs[0].indent < (parentHierarchyNode ? parentHierarchyNode.indent : 0)) {
- return parentHierarchyNode
+ ].join(" | ");
+ throw new Error(`${message} Look for ${lookFor}`);
+};
+
+const makeInteractionHierarchy = (
+ interactionParagraphs,
+ parentHierarchyNode
+) => {
+ if (
+ !interactionParagraphs.length ||
+ interactionParagraphs[0].indent <
+ (parentHierarchyNode ? parentHierarchyNode.indent : 0)
+ ) {
+ return parentHierarchyNode;
}
- const currentIndent = interactionParagraphs[0].indent
+ const currentIndent = interactionParagraphs[0].indent;
- let interactionsHierarchyNode = undefined
+ let interactionsHierarchyNode = undefined;
- while (interactionParagraphs[0] && interactionParagraphs[0].indent === currentIndent) {
+ while (
+ interactionParagraphs[0] &&
+ interactionParagraphs[0].indent === currentIndent
+ ) {
interactionsHierarchyNode = {
children: []
- }
+ };
- interactionsHierarchyNode.answer = interactionParagraphs.shift().text
- interactionsHierarchyNode.script = []
+ interactionsHierarchyNode.answer = interactionParagraphs.shift().text;
+ interactionsHierarchyNode.script = [];
- while (interactionParagraphs.length && !interactionParagraphs[0].isParagraphBold) {
- const interactionParagraph = interactionParagraphs.shift()
- interactionsHierarchyNode.script.push(interactionParagraph.text)
+ while (
+ interactionParagraphs.length &&
+ !interactionParagraphs[0].isParagraphBold
+ ) {
+ const interactionParagraph = interactionParagraphs.shift();
+ interactionsHierarchyNode.script.push(interactionParagraph.text);
}
if (!interactionsHierarchyNode.script[0]) {
- throwInteractionsHierarchyError('Interactions format error -- no script.', interactionsHierarchyNode, interactionParagraphs)
+ throwInteractionsHierarchyError(
+ "Interactions format error -- no script.",
+ interactionsHierarchyNode,
+ interactionParagraphs
+ );
}
if (isNextChunkAQuestion(interactionParagraphs, currentIndent)) {
- interactionsHierarchyNode.question = interactionParagraphs.shift().text
- saveCurrentNodeToParent(parentHierarchyNode, interactionsHierarchyNode)
- makeInteractionHierarchy(interactionParagraphs, interactionsHierarchyNode)
+ interactionsHierarchyNode.question = interactionParagraphs.shift().text;
+ saveCurrentNodeToParent(parentHierarchyNode, interactionsHierarchyNode);
+ makeInteractionHierarchy(
+ interactionParagraphs,
+ interactionsHierarchyNode
+ );
} else if (isNextChunkAnAnswer(interactionParagraphs, currentIndent)) {
- saveCurrentNodeToParent(parentHierarchyNode, interactionsHierarchyNode)
- makeInteractionHierarchy(interactionParagraphs, parentHierarchyNode)
+ saveCurrentNodeToParent(parentHierarchyNode, interactionsHierarchyNode);
+ makeInteractionHierarchy(interactionParagraphs, parentHierarchyNode);
} else if (isThisALeaf(interactionParagraphs, currentIndent)) {
- saveCurrentNodeToParent(parentHierarchyNode, interactionsHierarchyNode)
+ saveCurrentNodeToParent(parentHierarchyNode, interactionsHierarchyNode);
} else if (isError(interactionParagraphs, currentIndent)) {
- throwInteractionsHierarchyError('Interactions format error.', interactionsHierarchyNode, interactionParagraphs)
+ throwInteractionsHierarchyError(
+ "Interactions format error.",
+ interactionsHierarchyNode,
+ interactionParagraphs
+ );
} else {
- throwInteractionsHierarchyError('Interactions unexpected format.', interactionsHierarchyNode, interactionParagraphs)
+ throwInteractionsHierarchyError(
+ "Interactions unexpected format.",
+ interactionsHierarchyNode,
+ interactionParagraphs
+ );
}
}
- return interactionsHierarchyNode
-}
-
-const saveInteractionsHierarchyNode = async (trx, campaignId, interactionsHierarchyNode, parentHierarchyNodeId) => {
- const nodeId = await r.knex.insert({
- parent_interaction_id: parentHierarchyNodeId,
- question: interactionsHierarchyNode.question || '',
- script: interactionsHierarchyNode.script.join('\n') || '',
- answer_option: interactionsHierarchyNode.answer || '',
- answer_actions: '',
- campaign_id: campaignId,
- is_deleted: false
- })
- .into('interaction_step')
+ return interactionsHierarchyNode;
+};
+
+const saveInteractionsHierarchyNode = async (
+ trx,
+ campaignId,
+ interactionsHierarchyNode,
+ parentHierarchyNodeId
+) => {
+ const nodeId = await r.knex
+ .insert({
+ parent_interaction_id: parentHierarchyNodeId,
+ question: interactionsHierarchyNode.question || "",
+ script: interactionsHierarchyNode.script.join("\n") || "",
+ answer_option: interactionsHierarchyNode.answer || "",
+ answer_actions: "",
+ campaign_id: campaignId,
+ is_deleted: false
+ })
+ .into("interaction_step")
.transacting(trx)
- .returning('id')
+ .returning("id");
for (const child of interactionsHierarchyNode.children) {
- await saveInteractionsHierarchyNode(trx, campaignId, child, nodeId[0])
+ await saveInteractionsHierarchyNode(trx, campaignId, child, nodeId[0]);
}
-}
+};
-const replaceInteractionsInDatabase = async (campaignId, interactionsHierarchy) => {
+const replaceInteractionsInDatabase = async (
+ campaignId,
+ interactionsHierarchy
+) => {
await r.knex.transaction(async trx => {
try {
- await r.knex('interaction_step')
+ await r
+ .knex("interaction_step")
.transacting(trx)
.where({
campaign_id: campaignId
})
- .delete()
-
- await saveInteractionsHierarchyNode(trx, campaignId, interactionsHierarchy, null)
+ .delete();
+
+ await saveInteractionsHierarchyNode(
+ trx,
+ campaignId,
+ interactionsHierarchy,
+ null
+ );
} catch (exception) {
- console.log(exception)
- throw exception
+ console.log(exception);
+ throw exception;
}
- })
-}
+ });
+};
-const makeCannedResponsesList = (cannedResponsesParagraphs) => {
- const cannedResponses = []
+const makeCannedResponsesList = cannedResponsesParagraphs => {
+ const cannedResponses = [];
while (cannedResponsesParagraphs[0]) {
const cannedResponse = {
text: []
- }
+ };
- const paragraph = cannedResponsesParagraphs.shift()
+ const paragraph = cannedResponsesParagraphs.shift();
if (!paragraph.isParagraphBold) {
- throw new Error(`Canned responses format error -- can't find a bold paragraph. Look for [${paragraph.text}]`)
+ throw new Error(
+ `Canned responses format error -- can't find a bold paragraph. Look for [${paragraph.text}]`
+ );
}
- cannedResponse.title = paragraph.text
-
- while (cannedResponsesParagraphs[0] && !cannedResponsesParagraphs[0].isParagraphBold) {
- const textParagraph = cannedResponsesParagraphs.shift()
- cannedResponse.text.push(textParagraph.text)
+ cannedResponse.title = paragraph.text;
+
+ while (
+ cannedResponsesParagraphs[0] &&
+ !cannedResponsesParagraphs[0].isParagraphBold
+ ) {
+ const textParagraph = cannedResponsesParagraphs.shift();
+ cannedResponse.text.push(textParagraph.text);
}
if (!cannedResponse.text[0]) {
- throw new Error(`Canned responses format error -- canned response has no text. Look for [${cannedResponse.title}]`)
+ throw new Error(
+ `Canned responses format error -- canned response has no text. Look for [${cannedResponse.title}]`
+ );
}
- cannedResponses.push(cannedResponse)
+ cannedResponses.push(cannedResponse);
}
- return cannedResponses
-}
+ return cannedResponses;
+};
-const replaceCannedResponsesInDatabase = async (campaignId, cannedResponses) => {
+const replaceCannedResponsesInDatabase = async (
+ campaignId,
+ cannedResponses
+) => {
await r.knex.transaction(async trx => {
try {
- await r.knex('canned_response')
+ await r
+ .knex("canned_response")
.transacting(trx)
.where({
campaign_id: campaignId
})
- .whereNull('user_id')
- .delete()
+ .whereNull("user_id")
+ .delete();
for (const cannedResponse of cannedResponses) {
- await r.knex.insert({
- campaign_id: campaignId,
- user_id: null,
- title: cannedResponse.title,
- text: cannedResponse.text.join('\n')
- })
- .into('canned_response')
- .transacting(trx)
+ await r.knex
+ .insert({
+ campaign_id: campaignId,
+ user_id: null,
+ title: cannedResponse.title,
+ text: cannedResponse.text.join("\n")
+ })
+ .into("canned_response")
+ .transacting(trx);
}
} catch (exception) {
- console.log(exception)
- throw exception
+ console.log(exception);
+ throw exception;
}
- })
-}
+ });
+};
const importScriptFromDocument = async (campaignId, scriptUrl) => {
- const match = scriptUrl.match(/document\/d\/(.*)\//)
+ const match = scriptUrl.match(/document\/d\/(.*)\//);
if (!match || !match[1]) {
- throw new Error(`Invalid URL. This doesn't seem like a Google Docs URL.`)
+ throw new Error(`Invalid URL. This doesn't seem like a Google Docs URL.`);
}
- const documentId = match[1]
- const result = await getDocument(documentId)
-
- const document = result.data.body.content
- const sections = getSections(document)
-
- const interactionParagraphs = getInteractions(sections)
- const interactionsHierarchy = makeInteractionHierarchy(_.clone(interactionParagraphs), null, 0)
- await replaceInteractionsInDatabase(campaignId, interactionsHierarchy)
-
- const cannedResponsesParagraphs = getCannedResponses(sections)
- const cannedResponsesList = makeCannedResponsesList(_.clone(cannedResponsesParagraphs))
- await replaceCannedResponsesInDatabase(campaignId, cannedResponsesList)
-}
-
-export default importScriptFromDocument
+ const documentId = match[1];
+ const result = await getDocument(documentId);
+
+ const document = result.data.body.content;
+ const sections = getSections(document);
+
+ const interactionParagraphs = getInteractions(sections);
+ const interactionsHierarchy = makeInteractionHierarchy(
+ _.clone(interactionParagraphs),
+ null,
+ 0
+ );
+ await replaceInteractionsInDatabase(campaignId, interactionsHierarchy);
+
+ const cannedResponsesParagraphs = getCannedResponses(sections);
+ const cannedResponsesList = makeCannedResponsesList(
+ _.clone(cannedResponsesParagraphs)
+ );
+ await replaceCannedResponsesInDatabase(campaignId, cannedResponsesList);
+};
+
+export default importScriptFromDocument;
diff --git a/src/server/api/lib/message-sending.js b/src/server/api/lib/message-sending.js
index 673aa56d8..5b6efb403 100644
--- a/src/server/api/lib/message-sending.js
+++ b/src/server/api/lib/message-sending.js
@@ -1,32 +1,40 @@
-import { r } from '../../models'
+import { r } from "../../models";
export async function getLastMessage({ contactNumber, service }) {
- const lastMessage = await r.table('message')
- .getAll(contactNumber, { index: 'contact_number' })
+ const lastMessage = await r
+ .table("message")
+ .getAll(contactNumber, { index: "contact_number" })
.filter({
is_from_contact: false,
service
})
- .orderBy(r.desc('created_at'))
+ .orderBy(r.desc("created_at"))
.limit(1)
- .pluck('assignment_id')(0)
- .default(null)
+ .pluck("assignment_id")(0)
+ .default(null);
- return lastMessage
+ return lastMessage;
}
export async function saveNewIncomingMessage(messageInstance) {
if (messageInstance.service_id) {
- const countResult = await r.getCount(r.knex('message').where('service_id', messageInstance.service_id))
+ const countResult = await r.getCount(
+ r.knex("message").where("service_id", messageInstance.service_id)
+ );
if (countResult) {
- console.error('DUPLICATE MESSAGE SAVED', countResult.count, messageInstance)
+ console.error(
+ "DUPLICATE MESSAGE SAVED",
+ countResult.count,
+ messageInstance
+ );
}
}
- await messageInstance.save()
+ await messageInstance.save();
- await r.table('campaign_contact')
- .getAll(messageInstance.assignment_id, { index: 'assignment_id' })
+ await r
+ .table("campaign_contact")
+ .getAll(messageInstance.assignment_id, { index: "assignment_id" })
.filter({ cell: messageInstance.contact_number })
.limit(1)
- .update({ message_status: 'needsResponse', updated_at: 'now()' })
+ .update({ message_status: "needsResponse", updated_at: "now()" });
}
diff --git a/src/server/api/lib/nexmo.js b/src/server/api/lib/nexmo.js
index 7bf613813..64221db9a 100644
--- a/src/server/api/lib/nexmo.js
+++ b/src/server/api/lib/nexmo.js
@@ -1,32 +1,34 @@
-import Nexmo from 'nexmo'
-import { getFormattedPhoneNumber } from '../../../lib/phone-format'
-import { Message, PendingMessagePart } from '../../models'
-import { getLastMessage } from './message-sending'
-import { log } from '../../../lib'
-import faker from 'faker'
-
-let nexmo = null
-const MAX_SEND_ATTEMPTS = 5
+import Nexmo from "nexmo";
+import { getFormattedPhoneNumber } from "../../../lib/phone-format";
+import { Message, PendingMessagePart } from "../../models";
+import { getLastMessage } from "./message-sending";
+import { log } from "../../../lib";
+import faker from "faker";
+
+let nexmo = null;
+const MAX_SEND_ATTEMPTS = 5;
if (process.env.NEXMO_API_KEY && process.env.NEXMO_API_SECRET) {
nexmo = new Nexmo({
apiKey: process.env.NEXMO_API_KEY,
apiSecret: process.env.NEXMO_API_SECRET
- })
+ });
}
async function convertMessagePartsToMessage(messageParts) {
- const firstPart = messageParts[0]
- const userNumber = firstPart.user_number
- const contactNumber = firstPart.contact_number
- const serviceMessages = messageParts.map((part) => JSON.parse(part.service_message))
+ const firstPart = messageParts[0];
+ const userNumber = firstPart.user_number;
+ const contactNumber = firstPart.contact_number;
+ const serviceMessages = messageParts.map(part =>
+ JSON.parse(part.service_message)
+ );
const text = serviceMessages
- .map((serviceMessage) => serviceMessage.text)
- .join('')
+ .map(serviceMessage => serviceMessage.text)
+ .join("");
const lastMessage = await getLastMessage({
contactNumber,
- service: 'nexmo'
- })
+ service: "nexmo"
+ });
return new Message({
contact_number: contactNumber,
@@ -36,165 +38,195 @@ async function convertMessagePartsToMessage(messageParts) {
service_response: JSON.stringify(serviceMessages),
service_id: serviceMessages[0].service_id,
assignment_id: lastMessage.assignment_id,
- service: 'nexmo',
- send_status: 'DELIVERED'
- })
+ service: "nexmo",
+ send_status: "DELIVERED"
+ });
}
async function findNewCell() {
if (!nexmo) {
- return { numbers: [{ msisdn: getFormattedPhoneNumber(faker.phone.phoneNumber()) }] }
+ return {
+ numbers: [{ msisdn: getFormattedPhoneNumber(faker.phone.phoneNumber()) }]
+ };
}
return new Promise((resolve, reject) => {
- nexmo.number.search('US', { features: 'VOICE,SMS', size: 1 }, (err, response) => {
- if (err) {
- reject(err)
- } else {
- resolve(response)
+ nexmo.number.search(
+ "US",
+ { features: "VOICE,SMS", size: 1 },
+ (err, response) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(response);
+ }
}
- })
- })
+ );
+ });
}
async function rentNewCell() {
if (!nexmo) {
- return getFormattedPhoneNumber(faker.phone.phoneNumber())
+ return getFormattedPhoneNumber(faker.phone.phoneNumber());
}
- const newCell = await findNewCell()
-
- if (newCell && newCell.numbers && newCell.numbers[0] && newCell.numbers[0].msisdn) {
+ const newCell = await findNewCell();
+
+ if (
+ newCell &&
+ newCell.numbers &&
+ newCell.numbers[0] &&
+ newCell.numbers[0].msisdn
+ ) {
return new Promise((resolve, reject) => {
- nexmo.number.buy('US', newCell.numbers[0].msisdn, (err, response) => {
+ nexmo.number.buy("US", newCell.numbers[0].msisdn, (err, response) => {
if (err) {
- reject(err)
+ reject(err);
} else {
// It appears we need to check error-code in the response even if response is returned.
// This library returns responses that look like { error-code: 401, error-label: 'not authenticated'}
// or the bizarrely-named { error-code: 200 } even in the case of success
- if (response['error-code'] !== '200') {
- reject(new Error(response['error-code-label']))
+ if (response["error-code"] !== "200") {
+ reject(new Error(response["error-code-label"]));
} else {
- resolve(newCell.numbers[0].msisdn)
+ resolve(newCell.numbers[0].msisdn);
}
}
- })
- })
+ });
+ });
}
- throw new Error('Did not find any cell')
+ throw new Error("Did not find any cell");
}
async function sendMessage(message, contact, trx) {
if (!nexmo) {
- const options = trx ? { transaction: trx } : {}
- await Message.get(message.id)
- .update({ send_status: 'SENT' }, options)
- return 'test_message_uuid'
+ const options = trx ? { transaction: trx } : {};
+ await Message.get(message.id).update({ send_status: "SENT" }, options);
+ return "test_message_uuid";
}
return new Promise((resolve, reject) => {
// US numbers require that the + be removed when sending via nexmo
- nexmo.message.sendSms(message.user_number.replace(/^\+/, ''),
+ nexmo.message.sendSms(
+ message.user_number.replace(/^\+/, ""),
message.contact_number,
- message.text, {
- 'status-report-req': 1,
- 'client-ref': message.id
- }, (err, response) => {
+ message.text,
+ {
+ "status-report-req": 1,
+ "client-ref": message.id
+ },
+ (err, response) => {
const messageToSave = {
...message
- }
- let hasError = false
+ };
+ let hasError = false;
if (err) {
- hasError = true
+ hasError = true;
}
if (response) {
- response.messages.forEach((serviceMessages) => {
- if (serviceMessages.status !== '0') {
- hasError = true
+ response.messages.forEach(serviceMessages => {
+ if (serviceMessages.status !== "0") {
+ hasError = true;
}
- })
- messageToSave.service_response += JSON.stringify(response)
+ });
+ messageToSave.service_response += JSON.stringify(response);
}
- messageToSave.service = 'nexmo'
+ messageToSave.service = "nexmo";
if (hasError) {
if (messageToSave.service_messages.length >= MAX_SEND_ATTEMPTS) {
- messageToSave.send_status = 'ERROR'
+ messageToSave.send_status = "ERROR";
}
- let options = { conflict: 'update' }
+ let options = { conflict: "update" };
if (trx) {
- options.transaction = trx
+ options.transaction = trx;
}
Message.save(messageToSave, options)
- // eslint-disable-next-line no-unused-vars
- .then((_, newMessage) => {
- reject(err || (response ? new Error(JSON.stringify(response)) : new Error('Encountered unknown error')))
- })
+ // eslint-disable-next-line no-unused-vars
+ .then((_, newMessage) => {
+ reject(
+ err ||
+ (response
+ ? new Error(JSON.stringify(response))
+ : new Error("Encountered unknown error"))
+ );
+ });
} else {
- let options = { conflict: 'update' }
+ let options = { conflict: "update" };
if (trx) {
- options.transaction = trx
+ options.transaction = trx;
}
- Message.save({
- ...messageToSave,
- send_status: 'SENT'
- }, options)
- .then((saveError, newMessage) => {
- resolve(newMessage)
- })
+ Message.save(
+ {
+ ...messageToSave,
+ send_status: "SENT"
+ },
+ options
+ ).then((saveError, newMessage) => {
+ resolve(newMessage);
+ });
}
}
- )
- })
+ );
+ });
}
async function handleDeliveryReport(report) {
- if (report.hasOwnProperty('client-ref')) {
- const message = await Message.get(report['client-ref'])
- message.service_response += JSON.stringify(report)
- if (report.status === 'delivered' || report.status === 'accepted') {
- message.send_status = 'DELIVERED'
- } else if (report.status === 'expired' ||
- report.status === 'failed' ||
- report.status === 'rejected') {
- message.send_status = 'ERROR'
+ if (report.hasOwnProperty("client-ref")) {
+ const message = await Message.get(report["client-ref"]);
+ message.service_response += JSON.stringify(report);
+ if (report.status === "delivered" || report.status === "accepted") {
+ message.send_status = "DELIVERED";
+ } else if (
+ report.status === "expired" ||
+ report.status === "failed" ||
+ report.status === "rejected"
+ ) {
+ message.send_status = "ERROR";
}
- Message.save(message, { conflict: 'update' })
+ Message.save(message, { conflict: "update" });
}
}
async function handleIncomingMessage(message) {
- if (!message.hasOwnProperty('to') ||
- !message.hasOwnProperty('msisdn') ||
- !message.hasOwnProperty('text') ||
- !message.hasOwnProperty('messageId')) {
- log.error(`This is not an incoming message: ${JSON.stringify(message)}`)
+ if (
+ !message.hasOwnProperty("to") ||
+ !message.hasOwnProperty("msisdn") ||
+ !message.hasOwnProperty("text") ||
+ !message.hasOwnProperty("messageId")
+ ) {
+ log.error(`This is not an incoming message: ${JSON.stringify(message)}`);
}
- const { to, msisdn, concat } = message
- const isConcat = concat === 'true'
- const contactNumber = getFormattedPhoneNumber(msisdn)
- const userNumber = getFormattedPhoneNumber(to)
+ const { to, msisdn, concat } = message;
+ const isConcat = concat === "true";
+ const contactNumber = getFormattedPhoneNumber(msisdn);
+ const userNumber = getFormattedPhoneNumber(to);
- let parentId = ''
+ let parentId = "";
if (isConcat) {
- log.info(`Incoming message part (${message['concat-part']} of ${message['concat-total']} for ref ${message['concat-ref']}) from ${contactNumber} to ${userNumber}`)
- parentId = message['concat-ref']
+ log.info(
+ `Incoming message part (${message["concat-part"]} of ${
+ message["concat-total"]
+ } for ref ${
+ message["concat-ref"]
+ }) from ${contactNumber} to ${userNumber}`
+ );
+ parentId = message["concat-ref"];
} else {
- log.info(`Incoming message part from ${contactNumber} to ${userNumber}`)
+ log.info(`Incoming message part from ${contactNumber} to ${userNumber}`);
}
const pendingMessagePart = new PendingMessagePart({
- service: 'nexmo',
- service_id: message['concat-ref'] || message.messageId,
+ service: "nexmo",
+ service_id: message["concat-ref"] || message.messageId,
parent_id: parentId, // do we need this anymore, now we have service_id?
service_message: JSON.stringify(message),
user_number: userNumber,
contact_number: contactNumber
- })
+ });
- const part = await pendingMessagePart.save()
- return part.id
+ const part = await pendingMessagePart.save();
+ return part.id;
}
export default {
@@ -204,4 +236,4 @@ export default {
sendMessage,
handleDeliveryReport,
handleIncomingMessage
-}
+};
diff --git a/src/server/api/lib/services.js b/src/server/api/lib/services.js
index 5320a4aba..6092f3423 100644
--- a/src/server/api/lib/services.js
+++ b/src/server/api/lib/services.js
@@ -1,6 +1,6 @@
-import nexmo from './nexmo'
-import twilio from './twilio'
-import fakeservice from './fakeservice'
+import nexmo from "./nexmo";
+import twilio from "./twilio";
+import fakeservice from "./fakeservice";
// Each service needs the following api points:
// async sendMessage(message, contact, trx) -> void
@@ -12,6 +12,6 @@ const serviceMap = {
nexmo,
twilio,
fakeservice
-}
+};
-export default serviceMap
+export default serviceMap;
diff --git a/src/server/api/lib/twilio.js b/src/server/api/lib/twilio.js
index 6f6603497..158723aec 100644
--- a/src/server/api/lib/twilio.js
+++ b/src/server/api/lib/twilio.js
@@ -1,48 +1,54 @@
-import Twilio from 'twilio'
-import { getFormattedPhoneNumber } from '../../../lib/phone-format'
-import { Log, Message, PendingMessagePart, r } from '../../models'
-import { log } from '../../../lib'
-import { getLastMessage, saveNewIncomingMessage } from './message-sending'
-import faker from 'faker'
+import Twilio from "twilio";
+import { getFormattedPhoneNumber } from "../../../lib/phone-format";
+import { Log, Message, PendingMessagePart, r } from "../../models";
+import { log } from "../../../lib";
+import { getLastMessage, saveNewIncomingMessage } from "./message-sending";
+import faker from "faker";
-let twilio = null
-const MAX_SEND_ATTEMPTS = 5
-const MESSAGE_VALIDITY_PADDING_SECONDS = 30
-const MAX_TWILIO_MESSAGE_VALIDITY = 14400
+let twilio = null;
+const MAX_SEND_ATTEMPTS = 5;
+const MESSAGE_VALIDITY_PADDING_SECONDS = 30;
+const MAX_TWILIO_MESSAGE_VALIDITY = 14400;
if (process.env.TWILIO_API_KEY && process.env.TWILIO_AUTH_TOKEN) {
// eslint-disable-next-line new-cap
- twilio = Twilio(process.env.TWILIO_API_KEY, process.env.TWILIO_AUTH_TOKEN)
+ twilio = Twilio(process.env.TWILIO_API_KEY, process.env.TWILIO_AUTH_TOKEN);
} else {
- log.warn('NO TWILIO CONNECTION')
+ log.warn("NO TWILIO CONNECTION");
}
if (!process.env.TWILIO_MESSAGE_SERVICE_SID) {
- log.warn('Twilio will not be able to send without TWILIO_MESSAGE_SERVICE_SID set')
+ log.warn(
+ "Twilio will not be able to send without TWILIO_MESSAGE_SERVICE_SID set"
+ );
}
function webhook() {
- log.warn('twilio webhook call') // sky: doesn't run this
+ log.warn("twilio webhook call"); // sky: doesn't run this
if (twilio) {
- return Twilio.webhook()
+ return Twilio.webhook();
} else {
- log.warn('NO TWILIO WEB VALIDATION')
- return function (req, res, next) { next() }
+ log.warn("NO TWILIO WEB VALIDATION");
+ return function(req, res, next) {
+ next();
+ };
}
}
async function convertMessagePartsToMessage(messageParts) {
- const firstPart = messageParts[0]
- const userNumber = firstPart.user_number
- const contactNumber = firstPart.contact_number
- const serviceMessages = messageParts.map((part) => JSON.parse(part.service_message))
+ const firstPart = messageParts[0];
+ const userNumber = firstPart.user_number;
+ const contactNumber = firstPart.contact_number;
+ const serviceMessages = messageParts.map(part =>
+ JSON.parse(part.service_message)
+ );
const text = serviceMessages
- .map((serviceMessage) => serviceMessage.Body)
- .join('')
+ .map(serviceMessage => serviceMessage.Body)
+ .join("");
const lastMessage = await getLastMessage({
contactNumber
- })
+ });
return new Message({
contact_number: contactNumber,
user_number: userNumber,
@@ -51,97 +57,114 @@ async function convertMessagePartsToMessage(messageParts) {
service_response: JSON.stringify(serviceMessages),
service_id: serviceMessages[0].MessagingServiceSid,
assignment_id: lastMessage.assignment_id,
- service: 'twilio',
- send_status: 'DELIVERED'
- })
+ service: "twilio",
+ send_status: "DELIVERED"
+ });
}
async function findNewCell() {
if (!twilio) {
- return { availablePhoneNumbers: [{ phone_number: '+15005550006' }] }
+ return { availablePhoneNumbers: [{ phone_number: "+15005550006" }] };
}
return new Promise((resolve, reject) => {
- twilio.availablePhoneNumbers('US').local.list({}, (err, data) => {
+ twilio.availablePhoneNumbers("US").local.list({}, (err, data) => {
if (err) {
- reject(new Error(err))
+ reject(new Error(err));
} else {
- resolve(data)
+ resolve(data);
}
- })
- })
+ });
+ });
}
async function rentNewCell() {
if (!twilio) {
- return getFormattedPhoneNumber(faker.phone.phoneNumber())
+ return getFormattedPhoneNumber(faker.phone.phoneNumber());
}
- const newCell = await findNewCell()
+ const newCell = await findNewCell();
- if (newCell && newCell.availablePhoneNumbers && newCell.availablePhoneNumbers[0] && newCell.availablePhoneNumbers[0].phone_number) {
+ if (
+ newCell &&
+ newCell.availablePhoneNumbers &&
+ newCell.availablePhoneNumbers[0] &&
+ newCell.availablePhoneNumbers[0].phone_number
+ ) {
return new Promise((resolve, reject) => {
- twilio.incomingPhoneNumbers.create({
- phoneNumber: newCell.availablePhoneNumbers[0].phone_number,
- smsApplicationSid: process.env.TWILIO_APPLICATION_SID
- }, (err, purchasedNumber) => {
- if (err) {
- reject(err)
- } else {
- resolve(purchasedNumber.phone_number)
+ twilio.incomingPhoneNumbers.create(
+ {
+ phoneNumber: newCell.availablePhoneNumbers[0].phone_number,
+ smsApplicationSid: process.env.TWILIO_APPLICATION_SID
+ },
+ (err, purchasedNumber) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(purchasedNumber.phone_number);
+ }
}
- })
- })
+ );
+ });
}
-
- throw new Error('Did not find any cell')
+ throw new Error("Did not find any cell");
}
-const mediaExtractor = new RegExp(/\[\s*(http[^\]\s]*)\s*\]/)
+const mediaExtractor = new RegExp(/\[\s*(http[^\]\s]*)\s*\]/);
function parseMessageText(message) {
- const text = message.text || ''
+ const text = message.text || "";
const params = {
- body: text.replace(mediaExtractor, '')
- }
+ body: text.replace(mediaExtractor, "")
+ };
// Image extraction
- const results = text.match(mediaExtractor)
+ const results = text.match(mediaExtractor);
if (results) {
- params.mediaUrl = results[1]
+ params.mediaUrl = results[1];
}
- return params
+ return params;
}
async function sendMessage(message, contact, trx) {
if (!twilio) {
- log.warn('cannot actually send SMS message -- twilio is not fully configured:', message.id)
+ log.warn(
+ "cannot actually send SMS message -- twilio is not fully configured:",
+ message.id
+ );
if (message.id) {
- const options = trx ? { transaction: trx } : {}
- await Message.get(message.id)
- .update({ send_status: 'SENT', sent_at: new Date() }, options)
+ const options = trx ? { transaction: trx } : {};
+ await Message.get(message.id).update(
+ { send_status: "SENT", sent_at: new Date() },
+ options
+ );
}
- return 'test_message_uuid'
+ return "test_message_uuid";
}
return new Promise((resolve, reject) => {
- if (message.service !== 'twilio') {
- log.warn('Message not marked as a twilio message', message.id)
+ if (message.service !== "twilio") {
+ log.warn("Message not marked as a twilio message", message.id);
}
- const messageParams = Object.assign({
- to: message.contact_number,
- body: message.text,
- messagingServiceSid: process.env.TWILIO_MESSAGE_SERVICE_SID,
- statusCallback: process.env.TWILIO_STATUS_CALLBACK_URL
- }, parseMessageText(message))
+ const messageParams = Object.assign(
+ {
+ to: message.contact_number,
+ body: message.text,
+ messagingServiceSid: process.env.TWILIO_MESSAGE_SERVICE_SID,
+ statusCallback: process.env.TWILIO_STATUS_CALLBACK_URL
+ },
+ parseMessageText(message)
+ );
- let twilioValidityPeriod = process.env.TWILIO_MESSAGE_VALIDITY_PERIOD
+ let twilioValidityPeriod = process.env.TWILIO_MESSAGE_VALIDITY_PERIOD;
if (message.send_before) {
// the message is valid no longer than the time between now and
// the send_before time, less 30 seconds
// we subtract the MESSAGE_VALIDITY_PADDING_SECONDS seconds to allow time for the message to be sent by
// a downstream service
- const messageValidityPeriod = Math.ceil((message.send_before - Date.now())/1000) - MESSAGE_VALIDITY_PADDING_SECONDS
+ const messageValidityPeriod =
+ Math.ceil((message.send_before - Date.now()) / 1000) -
+ MESSAGE_VALIDITY_PADDING_SECONDS;
if (messageValidityPeriod < 0) {
// this is an edge case
@@ -150,118 +173,146 @@ async function sendMessage(message, contact, trx) {
}
if (twilioValidityPeriod) {
- twilioValidityPeriod = Math.min(twilioValidityPeriod, messageValidityPeriod, MAX_TWILIO_MESSAGE_VALIDITY)
+ twilioValidityPeriod = Math.min(
+ twilioValidityPeriod,
+ messageValidityPeriod,
+ MAX_TWILIO_MESSAGE_VALIDITY
+ );
} else {
- twilioValidityPeriod = Math.min(messageValidityPeriod, MAX_TWILIO_MESSAGE_VALIDITY)
+ twilioValidityPeriod = Math.min(
+ messageValidityPeriod,
+ MAX_TWILIO_MESSAGE_VALIDITY
+ );
}
}
if (twilioValidityPeriod) {
- messageParams.validityPeriod = twilioValidityPeriod
+ messageParams.validityPeriod = twilioValidityPeriod;
}
twilio.messages.create(messageParams, (err, response) => {
const messageToSave = {
...message
- }
- log.info('messageToSave', messageToSave)
- let hasError = false
+ };
+ log.info("messageToSave", messageToSave);
+ let hasError = false;
if (err) {
- hasError = true
- log.error('Error sending message', err)
- console.log('Error sending message', err)
- messageToSave.service_response += JSON.stringify(err)
+ hasError = true;
+ log.error("Error sending message", err);
+ console.log("Error sending message", err);
+ messageToSave.service_response += JSON.stringify(err);
}
if (response) {
- messageToSave.service_id = response.sid
- hasError = !!response.error_code
- messageToSave.service_response += JSON.stringify(response)
+ messageToSave.service_id = response.sid;
+ hasError = !!response.error_code;
+ messageToSave.service_response += JSON.stringify(response);
}
if (hasError) {
- const SENT_STRING = '"status"' // will appear in responses
- if (messageToSave.service_response.split(SENT_STRING).length >= MAX_SEND_ATTEMPTS + 1) {
- messageToSave.send_status = 'ERROR'
+ const SENT_STRING = '"status"'; // will appear in responses
+ if (
+ messageToSave.service_response.split(SENT_STRING).length >=
+ MAX_SEND_ATTEMPTS + 1
+ ) {
+ messageToSave.send_status = "ERROR";
}
- let options = { conflict: 'update' }
+ let options = { conflict: "update" };
if (trx) {
- options.transaction = trx
+ options.transaction = trx;
}
Message.save(messageToSave, options)
- // eslint-disable-next-line no-unused-vars
- .then((_, newMessage) => {
- reject(err || (response ? new Error(JSON.stringify(response)) : new Error('Encountered unknown error')))
- })
+ // eslint-disable-next-line no-unused-vars
+ .then((_, newMessage) => {
+ reject(
+ err ||
+ (response
+ ? new Error(JSON.stringify(response))
+ : new Error("Encountered unknown error"))
+ );
+ });
} else {
- let options = { conflict: 'update' }
+ let options = { conflict: "update" };
if (trx) {
- options.transaction = trx
+ options.transaction = trx;
}
- Message.save({
- ...messageToSave,
- send_status: 'SENT',
- service: 'twilio',
- sent_at: new Date()
- }, options)
- .then((saveError, newMessage) => {
- resolve(newMessage)
- })
+ Message.save(
+ {
+ ...messageToSave,
+ send_status: "SENT",
+ service: "twilio",
+ sent_at: new Date()
+ },
+ options
+ ).then((saveError, newMessage) => {
+ resolve(newMessage);
+ });
}
- })
- })
+ });
+ });
}
async function handleDeliveryReport(report) {
- const messageSid = report.MessageSid
+ const messageSid = report.MessageSid;
if (messageSid) {
- await Log.save({ message_sid: report.MessageSid, body: JSON.stringify(report) })
- const messageStatus = report.MessageStatus
- const message = await r.table('message')
- .getAll(messageSid, { index: 'service_id' })
+ await Log.save({
+ message_sid: report.MessageSid,
+ body: JSON.stringify(report)
+ });
+ const messageStatus = report.MessageStatus;
+ const message = await r
+ .table("message")
+ .getAll(messageSid, { index: "service_id" })
.limit(1)(0)
- .default(null)
+ .default(null);
if (message) {
- message.service_response_at = new Date()
- if (messageStatus === 'delivered') {
- message.send_status = 'DELIVERED'
- } else if (messageStatus === 'failed' ||
- messageStatus === 'undelivered') {
- message.send_status = 'ERROR'
+ message.service_response_at = new Date();
+ if (messageStatus === "delivered") {
+ message.send_status = "DELIVERED";
+ } else if (
+ messageStatus === "failed" ||
+ messageStatus === "undelivered"
+ ) {
+ message.send_status = "ERROR";
}
- Message.save(message, { conflict: 'update' })
+ Message.save(message, { conflict: "update" });
}
}
}
async function handleIncomingMessage(message) {
- if (!message.hasOwnProperty('From') ||
- !message.hasOwnProperty('To') ||
- !message.hasOwnProperty('Body') ||
- !message.hasOwnProperty('MessageSid')) {
- log.error(`This is not an incoming message: ${JSON.stringify(message)}`)
+ if (
+ !message.hasOwnProperty("From") ||
+ !message.hasOwnProperty("To") ||
+ !message.hasOwnProperty("Body") ||
+ !message.hasOwnProperty("MessageSid")
+ ) {
+ log.error(`This is not an incoming message: ${JSON.stringify(message)}`);
}
- const { From, To, MessageSid } = message
- const contactNumber = getFormattedPhoneNumber(From)
- const userNumber = (To ? getFormattedPhoneNumber(To) : '')
+ const { From, To, MessageSid } = message;
+ const contactNumber = getFormattedPhoneNumber(From);
+ const userNumber = To ? getFormattedPhoneNumber(To) : "";
const pendingMessagePart = new PendingMessagePart({
- service: 'twilio',
+ service: "twilio",
service_id: MessageSid,
parent_id: null,
service_message: JSON.stringify(message),
user_number: userNumber,
contact_number: contactNumber
- })
+ });
- const part = await pendingMessagePart.save()
- const partId = part.id
+ const part = await pendingMessagePart.save();
+ const partId = part.id;
if (process.env.JOBS_SAME_PROCESS) {
- const finalMessage = await convertMessagePartsToMessage([part])
- await saveNewIncomingMessage(finalMessage)
- await r.knex('pending_message_part').where('id', partId).delete()
+ const finalMessage = await convertMessagePartsToMessage([part]);
+ await saveNewIncomingMessage(finalMessage);
+ await r
+ .knex("pending_message_part")
+ .where("id", partId)
+ .delete();
}
- return partId
+ return partId;
}
export default {
@@ -274,4 +325,4 @@ export default {
handleDeliveryReport,
handleIncomingMessage,
parseMessageText
-}
+};
diff --git a/src/server/api/lib/utils.js b/src/server/api/lib/utils.js
index 9c3776f33..65c51416a 100644
--- a/src/server/api/lib/utils.js
+++ b/src/server/api/lib/utils.js
@@ -1,24 +1,26 @@
-import humps from 'humps'
+import humps from "humps";
export function mapFieldsToModel(fields, model) {
- const resolvers = {}
+ const resolvers = {};
- fields.forEach((field) => {
- const snakeKey = humps.decamelize(field, { separator: '_' })
+ fields.forEach(field => {
+ const snakeKey = humps.decamelize(field, { separator: "_" });
// eslint-disable-next-line no-underscore-dangle
if (model._schema._schema.hasOwnProperty(snakeKey)) {
- resolvers[field] = (instance) => instance[snakeKey]
+ resolvers[field] = instance => instance[snakeKey];
} else {
// eslint-disable-next-line no-underscore-dangle
- throw new Error(`Could not find key ${snakeKey} in model ${model._schema._model._name}`)
+ throw new Error(
+ `Could not find key ${snakeKey} in model ${model._schema._model._name}`
+ );
}
- })
- return resolvers
+ });
+ return resolvers;
}
export const capitalizeWord = word => {
if (word) {
- return word[0].toUpperCase() + word.slice(1)
+ return word[0].toUpperCase() + word.slice(1);
}
- return ''
-}
+ return "";
+};
diff --git a/src/server/api/message.js b/src/server/api/message.js
index 50b69ea24..6ffb2422d 100644
--- a/src/server/api/message.js
+++ b/src/server/api/message.js
@@ -1,16 +1,19 @@
-import { mapFieldsToModel } from './lib/utils'
-import { Message } from '../models'
+import { mapFieldsToModel } from "./lib/utils";
+import { Message } from "../models";
export const resolvers = {
Message: {
- ...mapFieldsToModel([
- 'id',
- 'text',
- 'userNumber',
- 'contactNumber',
- 'createdAt',
- 'isFromContact'
- ], Message),
- 'campaignId': (instance) => instance['campaign_id']
+ ...mapFieldsToModel(
+ [
+ "id",
+ "text",
+ "userNumber",
+ "contactNumber",
+ "createdAt",
+ "isFromContact"
+ ],
+ Message
+ ),
+ campaignId: instance => instance["campaign_id"]
}
-}
+};
diff --git a/src/server/api/mocks.js b/src/server/api/mocks.js
index 82a3b582b..3d4d8fff0 100644
--- a/src/server/api/mocks.js
+++ b/src/server/api/mocks.js
@@ -1,17 +1,16 @@
-const randomString = () => (
+const randomString = () =>
Math.random()
.toString(36)
- .replace(/[^a-z]+/g, '')
- .substr(0, 5)
-)
+ .replace(/[^a-z]+/g, "")
+ .substr(0, 5);
const mocks = {
String: () => `STRING_MOCK_${randomString()}`,
Date: () => new Date(),
Int: () => 42,
ID: () => `ID_MOCK_${randomString()}`,
- Phone: () => '+12223334444',
+ Phone: () => "+12223334444",
Timezone: () => ({ offset: -9, hasDST: true }),
JSON: () => '{"field1":"value1", "field2": "value2"}'
-}
+};
-export default mocks
+export default mocks;
diff --git a/src/server/api/opt-out.js b/src/server/api/opt-out.js
index 21e60d9d2..e14633076 100644
--- a/src/server/api/opt-out.js
+++ b/src/server/api/opt-out.js
@@ -1,13 +1,10 @@
-import { mapFieldsToModel } from './lib/utils'
-import { OptOut } from '../models'
+import { mapFieldsToModel } from "./lib/utils";
+import { OptOut } from "../models";
export const resolvers = {
OptOut: {
- ...mapFieldsToModel([
- 'id',
- 'cell',
- 'createdAt'
- ], OptOut),
- assignment: async (optOut, _, { loaders }) => loaders.assignment.load(optOut.assignment_id)
+ ...mapFieldsToModel(["id", "cell", "createdAt"], OptOut),
+ assignment: async (optOut, _, { loaders }) =>
+ loaders.assignment.load(optOut.assignment_id)
}
-}
+};
diff --git a/src/server/api/organization.js b/src/server/api/organization.js
index 799656269..b4e5d26c3 100644
--- a/src/server/api/organization.js
+++ b/src/server/api/organization.js
@@ -1,39 +1,49 @@
-import { mapFieldsToModel } from './lib/utils'
-import { r, Organization } from '../models'
-import { accessRequired } from './errors'
-import { getCampaigns } from './campaign'
-import { buildSortedUserOrganizationQuery } from './user'
+import { mapFieldsToModel } from "./lib/utils";
+import { r, Organization } from "../models";
+import { accessRequired } from "./errors";
+import { getCampaigns } from "./campaign";
+import { buildSortedUserOrganizationQuery } from "./user";
export const resolvers = {
Organization: {
- ...mapFieldsToModel([
- 'id',
- 'name'
- ], Organization),
+ ...mapFieldsToModel(["id", "name"], Organization),
campaigns: async (organization, { cursor, campaignsFilter }, { user }) => {
- await accessRequired(user, organization.id, 'SUPERVOLUNTEER')
- return getCampaigns(organization.id, cursor, campaignsFilter)
+ await accessRequired(user, organization.id, "SUPERVOLUNTEER");
+ return getCampaigns(organization.id, cursor, campaignsFilter);
},
uuid: async (organization, _, { user }) => {
- await accessRequired(user, organization.id, 'SUPERVOLUNTEER')
- const result = await r.knex('organization')
- .column('uuid')
- .where('id', organization.id)
- return result[0].uuid
+ await accessRequired(user, organization.id, "SUPERVOLUNTEER");
+ const result = await r
+ .knex("organization")
+ .column("uuid")
+ .where("id", organization.id);
+ return result[0].uuid;
},
optOuts: async (organization, _, { user }) => {
- await accessRequired(user, organization.id, 'ADMIN')
- return r.table('opt_out')
- .getAll(organization.id, { index: 'organization_id' })
+ await accessRequired(user, organization.id, "ADMIN");
+ return r
+ .table("opt_out")
+ .getAll(organization.id, { index: "organization_id" });
},
people: async (organization, { role, campaignId, sortBy }, { user }) => {
- await accessRequired(user, organization.id, 'SUPERVOLUNTEER')
- return buildSortedUserOrganizationQuery(organization.id, role, campaignId, sortBy)
+ await accessRequired(user, organization.id, "SUPERVOLUNTEER");
+ return buildSortedUserOrganizationQuery(
+ organization.id,
+ role,
+ campaignId,
+ sortBy
+ );
},
- threeClickEnabled: (organization) => organization.features.indexOf('threeClick') !== -1,
- textingHoursEnforced: (organization) => organization.texting_hours_enforced,
- optOutMessage: (organization) => (organization.features && organization.features.indexOf('opt_out_message') !== -1 ? JSON.parse(organization.features).opt_out_message : process.env.OPT_OUT_MESSAGE) || 'I\'m opting you out of texts immediately. Have a great day.',
- textingHoursStart: (organization) => organization.texting_hours_start,
- textingHoursEnd: (organization) => organization.texting_hours_end
+ threeClickEnabled: organization =>
+ organization.features.indexOf("threeClick") !== -1,
+ textingHoursEnforced: organization => organization.texting_hours_enforced,
+ optOutMessage: organization =>
+ (organization.features &&
+ organization.features.indexOf("opt_out_message") !== -1
+ ? JSON.parse(organization.features).opt_out_message
+ : process.env.OPT_OUT_MESSAGE) ||
+ "I'm opting you out of texts immediately. Have a great day.",
+ textingHoursStart: organization => organization.texting_hours_start,
+ textingHoursEnd: organization => organization.texting_hours_end
}
-}
+};
diff --git a/src/server/api/phone.js b/src/server/api/phone.js
index e624e6791..c1b4203ad 100644
--- a/src/server/api/phone.js
+++ b/src/server/api/phone.js
@@ -1,26 +1,29 @@
-import { GraphQLScalarType } from 'graphql'
-import { GraphQLError } from 'graphql/error'
-import { Kind } from 'graphql/language'
+import { GraphQLScalarType } from "graphql";
+import { GraphQLError } from "graphql/error";
+import { Kind } from "graphql/language";
-const identity = value => value
+const identity = value => value;
// Regex taken from http://stackoverflow.com/questions/6478875/regular-expression-matching-e-164-formatted-phone-numbers
-const pattern = /^\+[1-9]\d{1,14}$/
+const pattern = /^\+[1-9]\d{1,14}$/;
export const GraphQLPhone = new GraphQLScalarType({
- name: 'Phone',
- description: 'Phone number',
+ name: "Phone",
+ description: "Phone number",
parseValue: identity,
serialize: identity,
parseLiteral(ast) {
if (ast.kind !== Kind.STRING) {
- throw new GraphQLError(`Query error: Can only parse strings got a: ${ast.kind}`, [ast])
+ throw new GraphQLError(
+ `Query error: Can only parse strings got a: ${ast.kind}`,
+ [ast]
+ );
}
if (!pattern.test(ast.value)) {
- throw new GraphQLError('Query error: Not a valid Phone', [ast])
+ throw new GraphQLError("Query error: Not a valid Phone", [ast]);
}
- return ast.value
+ return ast.value;
}
-})
+});
diff --git a/src/server/api/question-response.js b/src/server/api/question-response.js
index 2629e1400..8c168c908 100644
--- a/src/server/api/question-response.js
+++ b/src/server/api/question-response.js
@@ -1,13 +1,10 @@
-import { mapFieldsToModel } from './lib/utils'
-import { QuestionResponse } from '../models'
+import { mapFieldsToModel } from "./lib/utils";
+import { QuestionResponse } from "../models";
export const resolvers = {
QuestionResponse: {
- ...mapFieldsToModel([
- 'id',
- 'value'
- ], QuestionResponse),
+ ...mapFieldsToModel(["id", "value"], QuestionResponse),
question: async (question, _, { loaders }) =>
- (loaders.question.load(question.id))
+ loaders.question.load(question.id)
}
-}
+};
diff --git a/src/server/api/question.js b/src/server/api/question.js
index e7a9dd4d8..dacecb866 100644
--- a/src/server/api/question.js
+++ b/src/server/api/question.js
@@ -1,46 +1,50 @@
-import { r } from '../models'
+import { r } from "../models";
export const resolvers = {
Question: {
- text: async (interactionStep) => interactionStep.question,
- answerOptions: async (interactionStep) => (
+ text: async interactionStep => interactionStep.question,
+ answerOptions: async interactionStep =>
// this should usually be pre-built from campaign's interactionSteps call
- interactionStep.answerOptions
- || r.table('interaction_step')
+ interactionStep.answerOptions ||
+ r
+ .table("interaction_step")
.filter({ parent_interaction_id: interactionStep.id })
.filter({ is_deleted: false })
- .orderBy('answer_option')
+ .orderBy("answer_option")
.map({
- value: r.row('answer_option'),
- action: r.row('answer_actions'),
- interaction_step_id: r.row('id'),
- parent_interaction_step: r.row('parent_interaction_id')
- })
- ),
- interactionStep: async (interactionStep) => interactionStep
+ value: r.row("answer_option"),
+ action: r.row("answer_actions"),
+ interaction_step_id: r.row("id"),
+ parent_interaction_step: r.row("parent_interaction_id")
+ }),
+ interactionStep: async interactionStep => interactionStep
},
AnswerOption: {
- value: (answer) => answer.value,
- interactionStepId: (answer) => answer.interaction_step_id,
- nextInteractionStep: async (answer) => (
- answer.nextInteractionStep
- || r.table('interaction_step').get(answer.interaction_step_id)),
- responders: async (answer) => (
- r.table('question_response')
- .getAll(answer.parent_interaction_step, { index: 'interaction_step_id' })
+ value: answer => answer.value,
+ interactionStepId: answer => answer.interaction_step_id,
+ nextInteractionStep: async answer =>
+ answer.nextInteractionStep ||
+ r.table("interaction_step").get(answer.interaction_step_id),
+ responders: async answer =>
+ r
+ .table("question_response")
+ .getAll(answer.parent_interaction_step, {
+ index: "interaction_step_id"
+ })
.filter({
value: answer.value
})
- .eqJoin('campaign_contact_id', r.table('campaign_contact'))('right')
- ),
- responderCount: async (answer) => (
- r.table('question_response')
- .getAll(answer.parent_interaction_step, { index: 'interaction_step_id' })
+ .eqJoin("campaign_contact_id", r.table("campaign_contact"))("right"),
+ responderCount: async answer =>
+ r
+ .table("question_response")
+ .getAll(answer.parent_interaction_step, {
+ index: "interaction_step_id"
+ })
.filter({
value: answer.value
})
- .count()
- ),
- question: async (answer) => answer.parent_interaction_step
+ .count(),
+ question: async answer => answer.parent_interaction_step
}
-}
+};
diff --git a/src/server/api/schema.js b/src/server/api/schema.js
index e5263b94d..d9a104244 100644
--- a/src/server/api/schema.js
+++ b/src/server/api/schema.js
@@ -1,14 +1,20 @@
-import camelCaseKeys from 'camelcase-keys'
-import GraphQLDate from 'graphql-date'
-import GraphQLJSON from 'graphql-type-json'
-import { GraphQLError } from 'graphql/error'
-import isUrl from 'is-url'
-import { organizationCache } from '../models/cacheable_queries/organization'
-
-import { gzip, log, makeTree } from '../../lib'
-import { applyScript } from '../../lib/scripts'
-import { capitalizeWord } from './lib/utils'
-import { assignTexters, exportCampaign, importScript, loadContactsFromDataWarehouse, uploadContacts } from '../../workers/jobs'
+import camelCaseKeys from "camelcase-keys";
+import GraphQLDate from "graphql-date";
+import GraphQLJSON from "graphql-type-json";
+import { GraphQLError } from "graphql/error";
+import isUrl from "is-url";
+import { organizationCache } from "../models/cacheable_queries/organization";
+
+import { gzip, log, makeTree } from "../../lib";
+import { applyScript } from "../../lib/scripts";
+import { capitalizeWord } from "./lib/utils";
+import {
+ assignTexters,
+ exportCampaign,
+ importScript,
+ loadContactsFromDataWarehouse,
+ uploadContacts
+} from "../../workers/jobs";
import {
Assignment,
Campaign,
@@ -24,43 +30,45 @@ import {
UserOrganization,
r,
cacheableData
-} from '../models'
+} from "../models";
// import { isBetweenTextingHours } from '../../lib/timezones'
-import { Notifications, sendUserNotification } from '../notifications'
-import { resolvers as assignmentResolvers } from './assignment'
-import { getCampaigns, resolvers as campaignResolvers } from './campaign'
-import { resolvers as campaignContactResolvers } from './campaign-contact'
-import { resolvers as cannedResponseResolvers } from './canned-response'
+import { Notifications, sendUserNotification } from "../notifications";
+import { resolvers as assignmentResolvers } from "./assignment";
+import { getCampaigns, resolvers as campaignResolvers } from "./campaign";
+import { resolvers as campaignContactResolvers } from "./campaign-contact";
+import { resolvers as cannedResponseResolvers } from "./canned-response";
import {
getConversations,
getCampaignIdMessageIdsAndCampaignIdContactIdsMaps,
reassignConversations,
resolvers as conversationsResolver
-} from './conversations'
+} from "./conversations";
import {
accessRequired,
assignmentRequired,
authRequired,
superAdminRequired
-} from './errors'
-import { resolvers as interactionStepResolvers } from './interaction-step'
-import { resolvers as inviteResolvers } from './invite'
-import { saveNewIncomingMessage } from './lib/message-sending'
-import serviceMap from './lib/services'
-import { resolvers as messageResolvers } from './message'
-import { resolvers as optOutResolvers } from './opt-out'
-import { resolvers as organizationResolvers } from './organization'
-import { GraphQLPhone } from './phone'
-import { resolvers as questionResolvers } from './question'
-import { resolvers as questionResponseResolvers } from './question-response'
-import { getUsers, resolvers as userResolvers } from './user'
-import { change } from '../local-auth-helpers'
-
-import { getSendBeforeTimeUtc } from '../../lib/timezones'
-
-const uuidv4 = require('uuid').v4
-const JOBS_SAME_PROCESS = !!(process.env.JOBS_SAME_PROCESS || global.JOBS_SAME_PROCESS)
-const JOBS_SYNC = !!(process.env.JOBS_SYNC || global.JOBS_SYNC)
+} from "./errors";
+import { resolvers as interactionStepResolvers } from "./interaction-step";
+import { resolvers as inviteResolvers } from "./invite";
+import { saveNewIncomingMessage } from "./lib/message-sending";
+import serviceMap from "./lib/services";
+import { resolvers as messageResolvers } from "./message";
+import { resolvers as optOutResolvers } from "./opt-out";
+import { resolvers as organizationResolvers } from "./organization";
+import { GraphQLPhone } from "./phone";
+import { resolvers as questionResolvers } from "./question";
+import { resolvers as questionResponseResolvers } from "./question-response";
+import { getUsers, resolvers as userResolvers } from "./user";
+import { change } from "../local-auth-helpers";
+
+import { getSendBeforeTimeUtc } from "../../lib/timezones";
+
+const uuidv4 = require("uuid").v4;
+const JOBS_SAME_PROCESS = !!(
+ process.env.JOBS_SAME_PROCESS || global.JOBS_SAME_PROCESS
+);
+const JOBS_SYNC = !!(process.env.JOBS_SYNC || global.JOBS_SYNC);
async function editCampaign(id, campaign, loaders, user, origCampaignRecord) {
const {
@@ -76,10 +84,16 @@ async function editCampaign(id, campaign, loaders, user, origCampaignRecord) {
textingHoursStart,
textingHoursEnd,
timezone
- } = campaign
+ } = campaign;
// some changes require ADMIN and we recheck below
- const organizationId = campaign.organizationId || origCampaignRecord.organization_id
- await accessRequired(user, organizationId, 'SUPERVOLUNTEER', /* superadmin*/ true)
+ const organizationId =
+ campaign.organizationId || origCampaignRecord.organization_id;
+ await accessRequired(
+ user,
+ organizationId,
+ "SUPERVOLUNTEER",
+ /* superadmin*/ true
+ );
const campaignUpdates = {
id,
title,
@@ -87,7 +101,7 @@ async function editCampaign(id, campaign, loaders, user, origCampaignRecord) {
due_by: dueBy,
organization_id: organizationId,
use_dynamic_assignment: useDynamicAssignment,
- logo_image_url: isUrl(logoImageUrl) ? logoImageUrl : '',
+ logo_image_url: isUrl(logoImageUrl) ? logoImageUrl : "",
primary_color: primaryColor,
intro_html: introHtml,
override_organization_texting_hours: overrideOrganizationTextingHours,
@@ -95,16 +109,16 @@ async function editCampaign(id, campaign, loaders, user, origCampaignRecord) {
texting_hours_start: textingHoursStart,
texting_hours_end: textingHoursEnd,
timezone
- }
+ };
Object.keys(campaignUpdates).forEach(key => {
- if (typeof campaignUpdates[key] === 'undefined') {
- delete campaignUpdates[key]
+ if (typeof campaignUpdates[key] === "undefined") {
+ delete campaignUpdates[key];
}
- })
+ });
- if (campaign.hasOwnProperty('contacts') && campaign.contacts) {
- await accessRequired(user, organizationId, 'ADMIN', /* superadmin*/ true)
+ if (campaign.hasOwnProperty("contacts") && campaign.contacts) {
+ await accessRequired(user, organizationId, "ADMIN", /* superadmin*/ true);
const contactsToSave = campaign.contacts.map(datum => {
const modelData = {
campaign_id: datum.campaignId,
@@ -114,93 +128,106 @@ async function editCampaign(id, campaign, loaders, user, origCampaignRecord) {
external_id: datum.external_id,
custom_fields: datum.customFields,
zip: datum.zip
- }
- modelData.campaign_id = id
- return modelData
- })
- const compressedString = await gzip(JSON.stringify(contactsToSave))
+ };
+ modelData.campaign_id = id;
+ return modelData;
+ });
+ const compressedString = await gzip(JSON.stringify(contactsToSave));
let job = await JobRequest.save({
queue_name: `${id}:edit_campaign`,
- job_type: 'upload_contacts',
+ job_type: "upload_contacts",
locks_queue: true,
assigned: JOBS_SAME_PROCESS, // can get called immediately, below
campaign_id: id,
// NOTE: stringifying because compressedString is a binary buffer
- payload: compressedString.toString('base64')
- })
+ payload: compressedString.toString("base64")
+ });
if (JOBS_SAME_PROCESS) {
- uploadContacts(job)
+ uploadContacts(job);
}
}
- if (campaign.hasOwnProperty('contactSql') && datawarehouse && user.is_superadmin) {
- await accessRequired(user, organizationId, 'ADMIN', /* superadmin*/ true)
+ if (
+ campaign.hasOwnProperty("contactSql") &&
+ datawarehouse &&
+ user.is_superadmin
+ ) {
+ await accessRequired(user, organizationId, "ADMIN", /* superadmin*/ true);
let job = await JobRequest.save({
queue_name: `${id}:edit_campaign`,
- job_type: 'upload_contacts_sql',
+ job_type: "upload_contacts_sql",
locks_queue: true,
assigned: JOBS_SAME_PROCESS, // can get called immediately, below
campaign_id: id,
payload: campaign.contactSql
- })
+ });
if (JOBS_SAME_PROCESS) {
- loadContactsFromDataWarehouse(job)
+ loadContactsFromDataWarehouse(job);
}
}
- if (campaign.hasOwnProperty('texters')) {
+ if (campaign.hasOwnProperty("texters")) {
let job = await JobRequest.save({
queue_name: `${id}:edit_campaign`,
locks_queue: true,
assigned: JOBS_SAME_PROCESS, // can get called immediately, below
- job_type: 'assign_texters',
+ job_type: "assign_texters",
campaign_id: id,
payload: JSON.stringify({
id,
texters: campaign.texters
})
- })
+ });
if (JOBS_SAME_PROCESS) {
if (JOBS_SYNC) {
- await assignTexters(job)
+ await assignTexters(job);
} else {
- assignTexters(job)
+ assignTexters(job);
}
}
}
- if (campaign.hasOwnProperty('interactionSteps')) {
- await accessRequired(user, organizationId, 'SUPERVOLUNTEER', /* superadmin*/ true)
- await updateInteractionSteps(id, [campaign.interactionSteps], origCampaignRecord)
- await cacheableData.campaign.clear(id)
+ if (campaign.hasOwnProperty("interactionSteps")) {
+ await accessRequired(
+ user,
+ organizationId,
+ "SUPERVOLUNTEER",
+ /* superadmin*/ true
+ );
+ await updateInteractionSteps(
+ id,
+ [campaign.interactionSteps],
+ origCampaignRecord
+ );
+ await cacheableData.campaign.clear(id);
}
- if (campaign.hasOwnProperty('cannedResponses')) {
- const cannedResponses = campaign.cannedResponses
- const convertedResponses = []
+ if (campaign.hasOwnProperty("cannedResponses")) {
+ const cannedResponses = campaign.cannedResponses;
+ const convertedResponses = [];
for (let index = 0; index < cannedResponses.length; index++) {
- const response = cannedResponses[index]
+ const response = cannedResponses[index];
convertedResponses.push({
...response,
campaign_id: id,
id: undefined
- })
+ });
}
await r
- .table('canned_response')
- .getAll(id, { index: 'campaign_id' })
- .filter({ user_id: '' })
- .delete()
- await CannedResponse.save(convertedResponses)
+ .table("canned_response")
+ .getAll(id, { index: "campaign_id" })
+ .filter({ user_id: "" })
+ .delete();
+ await CannedResponse.save(convertedResponses);
await cacheableData.cannedResponse.clearQuery({
- userId: '',
+ userId: "",
campaignId: id
- })
+ });
}
- const newCampaign = await Campaign.get(id).update(campaignUpdates)
- cacheableData.campaign.reload(id)
- return newCampaign || loaders.campaign.load(id)
+ const newCampaign = await Campaign.get(id).update(campaignUpdates);
+ cacheableData.campaign.reload(id);
+ return newCampaign || loaders.campaign.load(id);
}
async function updateInteractionSteps(
@@ -212,9 +239,9 @@ async function updateInteractionSteps(
await interactionSteps.forEach(async is => {
// map the interaction step ids for new ones
if (idMap[is.parentInteractionId]) {
- is.parentInteractionId = idMap[is.parentInteractionId]
+ is.parentInteractionId = idMap[is.parentInteractionId];
}
- if (is.id.indexOf('new') !== -1) {
+ if (is.id.indexOf("new") !== -1) {
const newIstep = await InteractionStep.save({
parent_interaction_id: is.parentInteractionId || null,
question: is.questionText,
@@ -223,17 +250,17 @@ async function updateInteractionSteps(
answer_actions: is.answerActions,
campaign_id: campaignId,
is_deleted: false
- })
- idMap[is.id] = newIstep.id
+ });
+ idMap[is.id] = newIstep.id;
} else {
if (!origCampaignRecord.is_started && is.isDeleted) {
await r
- .knex('interaction_step')
+ .knex("interaction_step")
.where({ id: is.id })
- .delete()
+ .delete();
} else {
await r
- .knex('interaction_step')
+ .knex("interaction_step")
.where({ id: is.id })
.update({
question: is.questionText,
@@ -241,54 +268,60 @@ async function updateInteractionSteps(
answer_option: is.answerOption,
answer_actions: is.answerActions,
is_deleted: is.isDeleted
- })
+ });
}
}
- await updateInteractionSteps(campaignId, is.interactionSteps, origCampaignRecord, idMap)
- })
+ await updateInteractionSteps(
+ campaignId,
+ is.interactionSteps,
+ origCampaignRecord,
+ idMap
+ );
+ });
}
const rootMutations = {
RootMutation: {
userAgreeTerms: async (_, { userId }, { user, loaders }) => {
if (user.id === Number(userId)) {
- return (user.terms ? user : null)
+ return user.terms ? user : null;
}
const currentUser = await r
- .table('user')
+ .table("user")
.get(userId)
.update({
terms: true
- })
- await cacheableData.user.clearUser(user.id, user.auth0_id)
- return currentUser
+ });
+ await cacheableData.user.clearUser(user.id, user.auth0_id);
+ return currentUser;
},
sendReply: async (_, { id, message }, { user, loaders }) => {
- const contact = await loaders.campaignContact.load(id)
- const campaign = await loaders.campaign.load(contact.campaign_id)
+ const contact = await loaders.campaignContact.load(id);
+ const campaign = await loaders.campaign.load(contact.campaign_id);
- await accessRequired(user, campaign.organization_id, 'ADMIN')
+ await accessRequired(user, campaign.organization_id, "ADMIN");
const lastMessage = await r
- .table('message')
- .getAll(contact.assignment_id, { index: 'assignment_id' })
+ .table("message")
+ .getAll(contact.assignment_id, { index: "assignment_id" })
.filter({ contact_number: contact.cell })
.limit(1)(0)
- .default(null)
+ .default(null);
if (!lastMessage) {
throw new GraphQLError({
status: 400,
- message: 'Cannot fake a reply to a contact that has no existing thread yet'
- })
+ message:
+ "Cannot fake a reply to a contact that has no existing thread yet"
+ });
}
- const userNumber = lastMessage.user_number
- const contactNumber = contact.cell
+ const userNumber = lastMessage.user_number;
+ const contactNumber = contact.cell;
const mockId = `mocked_${Math.random()
.toString(36)
- .replace(/[^a-zA-Z1-9]+/g, '')}`
+ .replace(/[^a-zA-Z1-9]+/g, "")}`;
await saveNewIncomingMessage(
new Message({
contact_number: contactNumber,
@@ -303,18 +336,18 @@ const rootMutations = {
service_id: mockId,
assignment_id: lastMessage.assignment_id,
service: lastMessage.service,
- send_status: 'DELIVERED'
+ send_status: "DELIVERED"
})
- )
- return loaders.campaignContact.load(id)
+ );
+ return loaders.campaignContact.load(id);
},
exportCampaign: async (_, { id }, { user, loaders }) => {
- const campaign = await loaders.campaign.load(id)
- const organizationId = campaign.organization_id
- await accessRequired(user, organizationId, 'ADMIN')
+ const campaign = await loaders.campaign.load(id);
+ const organizationId = campaign.organization_id;
+ await accessRequired(user, organizationId, "ADMIN");
const newJob = await JobRequest.save({
queue_name: `${id}:export`,
- job_type: 'export',
+ job_type: "export",
locks_queue: false,
assigned: JOBS_SAME_PROCESS, // can get called immediately, below
campaign_id: id,
@@ -322,237 +355,272 @@ const rootMutations = {
id,
requester: user.id
})
- })
+ });
if (JOBS_SAME_PROCESS) {
- exportCampaign(newJob)
+ exportCampaign(newJob);
}
- return newJob
+ return newJob;
},
- editOrganizationRoles: async (_, { userId, organizationId, roles }, { user, loaders }) => {
+ editOrganizationRoles: async (
+ _,
+ { userId, organizationId, roles },
+ { user, loaders }
+ ) => {
const currentRoles = (await r
- .knex('user_organization')
+ .knex("user_organization")
.where({
organization_id: organizationId,
user_id: userId
})
- .select('role')).map(res => res.role)
- const oldRoleIsOwner = currentRoles.indexOf('OWNER') !== -1
- const newRoleIsOwner = roles.indexOf('OWNER') !== -1
- const roleRequired = oldRoleIsOwner || newRoleIsOwner ? 'OWNER' : 'ADMIN'
- let newOrgRoles = []
+ .select("role")).map(res => res.role);
+ const oldRoleIsOwner = currentRoles.indexOf("OWNER") !== -1;
+ const newRoleIsOwner = roles.indexOf("OWNER") !== -1;
+ const roleRequired = oldRoleIsOwner || newRoleIsOwner ? "OWNER" : "ADMIN";
+ let newOrgRoles = [];
- await accessRequired(user, organizationId, roleRequired)
+ await accessRequired(user, organizationId, roleRequired);
currentRoles.forEach(async curRole => {
if (roles.indexOf(curRole) === -1) {
await r
- .table('user_organization')
- .getAll([organizationId, userId], { index: 'organization_user' })
+ .table("user_organization")
+ .getAll([organizationId, userId], { index: "organization_user" })
.filter({ role: curRole })
- .delete()
+ .delete();
}
- })
+ });
- newOrgRoles = roles.filter(newRole => currentRoles.indexOf(newRole) === -1).map(newRole => ({
- organization_id: organizationId,
- user_id: userId,
- role: newRole
- }))
+ newOrgRoles = roles
+ .filter(newRole => currentRoles.indexOf(newRole) === -1)
+ .map(newRole => ({
+ organization_id: organizationId,
+ user_id: userId,
+ role: newRole
+ }));
if (newOrgRoles.length) {
- await UserOrganization.save(newOrgRoles, { conflict: 'update' })
+ await UserOrganization.save(newOrgRoles, { conflict: "update" });
}
- await cacheableData.user.clearUser(userId)
- return loaders.organization.load(organizationId)
+ await cacheableData.user.clearUser(userId);
+ return loaders.organization.load(organizationId);
},
editUser: async (_, { organizationId, userId, userData }, { user }) => {
if (user.id !== userId) {
// User can edit themselves
- await accessRequired(user, organizationId, 'ADMIN', true)
+ await accessRequired(user, organizationId, "ADMIN", true);
}
const userRes = await r
- .knex('user')
- .join('user_organization', 'user.id', 'user_organization.user_id')
+ .knex("user")
+ .join("user_organization", "user.id", "user_organization.user_id")
.where({
- 'user_organization.organization_id': organizationId,
- 'user.id': userId
+ "user_organization.organization_id": organizationId,
+ "user.id": userId
})
- .limit(1)
+ .limit(1);
if (!userRes || !userRes.length) {
- return null
+ return null;
} else {
- const member = userRes[0]
+ const member = userRes[0];
const newUserData = {
first_name: capitalizeWord(userData.firstName),
last_name: capitalizeWord(userData.lastName),
email: userData.email,
cell: userData.cell
- }
+ };
if (userData) {
const userRes = await r
- .knex('user')
- .where('id', userId)
- .update(newUserData)
- await cacheableData.user.clearUser(member.id, member.auth0_id)
+ .knex("user")
+ .where("id", userId)
+ .update(newUserData);
+ await cacheableData.user.clearUser(member.id, member.auth0_id);
userData = {
id: userId,
...newUserData
- }
+ };
} else {
- userData = member
+ userData = member;
}
- return userData
+ return userData;
}
},
resetUserPassword: async (_, { organizationId, userId }, { user }) => {
if (user.id === userId) {
- throw new Error('You can\'t reset your own password.')
+ throw new Error("You can't reset your own password.");
}
- await accessRequired(user, organizationId, 'ADMIN', true)
+ await accessRequired(user, organizationId, "ADMIN", true);
// Add date at the end in case user record is modified after password is reset
- const passwordResetHash = uuidv4()
- const auth0_id = `reset|${passwordResetHash}|${Date.now()}`
+ const passwordResetHash = uuidv4();
+ const auth0_id = `reset|${passwordResetHash}|${Date.now()}`;
const userRes = await r
- .knex('user')
- .where('id', userId)
+ .knex("user")
+ .where("id", userId)
.update({
auth0_id
- })
- return passwordResetHash
+ });
+ return passwordResetHash;
},
changeUserPassword: async (_, { userId, formData }, { user }) => {
if (user.id !== userId) {
- throw new Error('You can only change your own password.')
+ throw new Error("You can only change your own password.");
}
- const { password, newPassword, passwordConfirm } = formData
+ const { password, newPassword, passwordConfirm } = formData;
- const updatedUser = await change({ user, password, newPassword, passwordConfirm })
+ const updatedUser = await change({
+ user,
+ password,
+ newPassword,
+ passwordConfirm
+ });
- return updatedUser
+ return updatedUser;
},
joinOrganization: async (_, { organizationUuid }, { user, loaders }) => {
- let organization
- ;[organization] = await r.knex('organization').where('uuid', organizationUuid)
+ let organization;
+ [organization] = await r
+ .knex("organization")
+ .where("uuid", organizationUuid);
if (organization) {
const userOrg = await r
- .table('user_organization')
- .getAll(user.id, { index: 'user_id' })
+ .table("user_organization")
+ .getAll(user.id, { index: "user_id" })
.filter({ organization_id: organization.id })
.limit(1)(0)
- .default(null)
+ .default(null);
if (!userOrg) {
await UserOrganization.save({
user_id: user.id,
organization_id: organization.id,
- role: 'TEXTER'
- }).error(function (error) {
+ role: "TEXTER"
+ }).error(function(error) {
// Unexpected errors
- console.log("error on userOrganization save", error)
+ console.log("error on userOrganization save", error);
});
- await cacheableData.user.clearUser(user.id)
- } else { // userOrg exists
- console.log('existing userOrg ' + userOrg.id + ' user ' + user.id + ' organizationUuid ' + organizationUuid)
+ await cacheableData.user.clearUser(user.id);
+ } else {
+ // userOrg exists
+ console.log(
+ "existing userOrg " +
+ userOrg.id +
+ " user " +
+ user.id +
+ " organizationUuid " +
+ organizationUuid
+ );
}
- } else { // no organization
- console.log('no organization with id ' + organizationUuid + ' for user ' + user.id)
+ } else {
+ // no organization
+ console.log(
+ "no organization with id " + organizationUuid + " for user " + user.id
+ );
}
- return organization
+ return organization;
},
- assignUserToCampaign: async (_, { organizationUuid, campaignId }, { user, loaders }) => {
+ assignUserToCampaign: async (
+ _,
+ { organizationUuid, campaignId },
+ { user, loaders }
+ ) => {
const campaign = await r
- .knex('campaign')
- .leftJoin('organization', 'campaign.organization_id', 'organization.id')
+ .knex("campaign")
+ .leftJoin("organization", "campaign.organization_id", "organization.id")
.where({
- 'campaign.id': campaignId,
- 'campaign.use_dynamic_assignment': true,
- 'organization.uuid': organizationUuid
+ "campaign.id": campaignId,
+ "campaign.use_dynamic_assignment": true,
+ "organization.uuid": organizationUuid
})
- .select('campaign.*')
- .first()
+ .select("campaign.*")
+ .first();
if (!campaign) {
throw new GraphQLError({
status: 403,
- message: 'Invalid join request'
- })
+ message: "Invalid join request"
+ });
}
const assignment = await r
- .table('assignment')
- .getAll(user.id, { index: 'user_id' })
+ .table("assignment")
+ .getAll(user.id, { index: "user_id" })
.filter({ campaign_id: campaign.id })
.limit(1)(0)
- .default(null)
+ .default(null);
if (!assignment) {
await Assignment.save({
user_id: user.id,
campaign_id: campaign.id,
- max_contacts: (process.env.MAX_CONTACTS_PER_TEXTER ? parseInt(process.env.MAX_CONTACTS_PER_TEXTER, 10) : null)
- })
+ max_contacts: process.env.MAX_CONTACTS_PER_TEXTER
+ ? parseInt(process.env.MAX_CONTACTS_PER_TEXTER, 10)
+ : null
+ });
}
- return campaign
+ return campaign;
},
updateTextingHours: async (
_,
{ organizationId, textingHoursStart, textingHoursEnd },
{ user }
) => {
- await accessRequired(user, organizationId, 'OWNER')
+ await accessRequired(user, organizationId, "OWNER");
await Organization.get(organizationId).update({
texting_hours_start: textingHoursStart,
texting_hours_end: textingHoursEnd
- })
- cacheableData.organization.clear(organizationId)
+ });
+ cacheableData.organization.clear(organizationId);
- return await Organization.get(organizationId)
+ return await Organization.get(organizationId);
},
updateTextingHoursEnforcement: async (
_,
{ organizationId, textingHoursEnforced },
{ user, loaders }
) => {
- await accessRequired(user, organizationId, 'SUPERVOLUNTEER')
+ await accessRequired(user, organizationId, "SUPERVOLUNTEER");
await Organization.get(organizationId).update({
texting_hours_enforced: textingHoursEnforced
- })
- await cacheableData.organization.clear(organizationId)
+ });
+ await cacheableData.organization.clear(organizationId);
- return await loaders.organization.load(organizationId)
+ return await loaders.organization.load(organizationId);
},
updateOptOutMessage: async (
_,
{ organizationId, optOutMessage },
{ user }
) => {
- await accessRequired(user, organizationId, 'OWNER')
+ await accessRequired(user, organizationId, "OWNER");
- const organization = await Organization.get(organizationId)
- const featuresJSON = JSON.parse(organization.features || '{}')
- featuresJSON.opt_out_message = optOutMessage
- organization.features = JSON.stringify(featuresJSON)
+ const organization = await Organization.get(organizationId);
+ const featuresJSON = JSON.parse(organization.features || "{}");
+ featuresJSON.opt_out_message = optOutMessage;
+ organization.features = JSON.stringify(featuresJSON);
- await organization.save()
- await organizationCache.clear(organizationId)
+ await organization.save();
+ await organizationCache.clear(organizationId);
- return await Organization.get(organizationId)
+ return await Organization.get(organizationId);
},
createInvite: async (_, { user }) => {
if ((user && user.is_superadmin) || !process.env.SUPPRESS_SELF_INVITE) {
const inviteInstance = new Invite({
is_valid: true,
hash: uuidv4()
- })
- const newInvite = await inviteInstance.save()
- return newInvite
+ });
+ const newInvite = await inviteInstance.save();
+ return newInvite;
}
},
createCampaign: async (_, { campaign }, { user, loaders }) => {
- await accessRequired(user, campaign.organizationId, 'ADMIN', /* allowSuperadmin=*/ true)
+ await accessRequired(
+ user,
+ campaign.organizationId,
+ "ADMIN",
+ /* allowSuperadmin=*/ true
+ );
const campaignInstance = new Campaign({
organization_id: campaign.organizationId,
creator_id: user.id,
@@ -561,46 +629,48 @@ const rootMutations = {
due_by: campaign.dueBy,
is_started: false,
is_archived: false
- })
- const newCampaign = await campaignInstance.save()
- return editCampaign(newCampaign.id, campaign, loaders, user)
+ });
+ const newCampaign = await campaignInstance.save();
+ return editCampaign(newCampaign.id, campaign, loaders, user);
},
copyCampaign: async (_, { id }, { user, loaders }) => {
- const campaign = await loaders.campaign.load(id)
- await accessRequired(user, campaign.organization_id, 'ADMIN')
+ const campaign = await loaders.campaign.load(id);
+ await accessRequired(user, campaign.organization_id, "ADMIN");
const campaignInstance = new Campaign({
organization_id: campaign.organization_id,
creator_id: user.id,
- title: 'COPY - ' + campaign.title,
+ title: "COPY - " + campaign.title,
description: campaign.description,
due_by: campaign.dueBy,
is_started: false,
is_archived: false
- })
- const newCampaign = await campaignInstance.save()
- const newCampaignId = newCampaign.id
- const oldCampaignId = campaign.id
+ });
+ const newCampaign = await campaignInstance.save();
+ const newCampaignId = newCampaign.id;
+ const oldCampaignId = campaign.id;
- let interactions = await r.knex('interaction_step').where({ campaign_id: oldCampaignId, is_deleted: false })
+ let interactions = await r
+ .knex("interaction_step")
+ .where({ campaign_id: oldCampaignId, is_deleted: false });
- const interactionsArr = []
+ const interactionsArr = [];
interactions.forEach((interaction, index) => {
if (interaction.parent_interaction_id) {
let is = {
- id: 'new' + interaction.id,
+ id: "new" + interaction.id,
questionText: interaction.question,
script: interaction.script,
answerOption: interaction.answer_option,
answerActions: interaction.answer_actions,
isDeleted: interaction.is_deleted,
campaign_id: newCampaignId,
- parentInteractionId: 'new' + interaction.parent_interaction_id
- }
- interactionsArr.push(is)
+ parentInteractionId: "new" + interaction.parent_interaction_id
+ };
+ interactionsArr.push(is);
} else if (!interaction.parent_interaction_id) {
let is = {
- id: 'new' + interaction.id,
+ id: "new" + interaction.id,
questionText: interaction.question,
script: interaction.script,
answerOption: interaction.answer_option,
@@ -608,254 +678,288 @@ const rootMutations = {
isDeleted: interaction.is_deleted,
campaign_id: newCampaignId,
parentInteractionId: interaction.parent_interaction_id
- }
- interactionsArr.push(is)
+ };
+ interactionsArr.push(is);
}
- })
+ });
let createSteps = updateInteractionSteps(
newCampaignId,
[makeTree(interactionsArr, (id = null))],
campaign,
{}
- )
+ );
- await createSteps
+ await createSteps;
let createCannedResponses = r
- .knex('canned_response')
+ .knex("canned_response")
.where({ campaign_id: oldCampaignId })
- .then(function (res) {
+ .then(function(res) {
res.forEach((response, index) => {
const copiedCannedResponse = new CannedResponse({
campaign_id: newCampaignId,
title: response.title,
text: response.text
- }).save()
- })
- })
+ }).save();
+ });
+ });
- await createCannedResponses
+ await createCannedResponses;
- return newCampaign
+ return newCampaign;
},
unarchiveCampaign: async (_, { id }, { user, loaders }) => {
- const campaign = await loaders.campaign.load(id)
- await accessRequired(user, campaign.organization_id, 'ADMIN')
- campaign.is_archived = false
- await campaign.save()
- await cacheableData.campaign.clear(id)
- return campaign
+ const campaign = await loaders.campaign.load(id);
+ await accessRequired(user, campaign.organization_id, "ADMIN");
+ campaign.is_archived = false;
+ await campaign.save();
+ await cacheableData.campaign.clear(id);
+ return campaign;
},
archiveCampaign: async (_, { id }, { user, loaders }) => {
- const campaign = await loaders.campaign.load(id)
- await accessRequired(user, campaign.organization_id, 'ADMIN')
- campaign.is_archived = true
- await campaign.save()
- await cacheableData.campaign.clear(id)
- return campaign
+ const campaign = await loaders.campaign.load(id);
+ await accessRequired(user, campaign.organization_id, "ADMIN");
+ campaign.is_archived = true;
+ await campaign.save();
+ await cacheableData.campaign.clear(id);
+ return campaign;
},
archiveCampaigns: async (_, { ids }, { user, loaders }) => {
// Take advantage of the cache instead of running a DB query
- const campaigns = await Promise.all(ids.map(id => (
- loaders.campaign.load(id)
- )))
-
- await Promise.all(campaigns.map(campaign => (
- accessRequired(user, campaign.organization_id, 'ADMIN')
- )))
-
- campaigns.forEach(campaign => { campaign.is_archived = true })
- await Promise.all(campaigns.map(async (campaign) => {
- await campaign.save()
- await cacheableData.campaign.clear(campaign.id)
- }))
- return campaigns
+ const campaigns = await Promise.all(
+ ids.map(id => loaders.campaign.load(id))
+ );
+
+ await Promise.all(
+ campaigns.map(campaign =>
+ accessRequired(user, campaign.organization_id, "ADMIN")
+ )
+ );
+
+ campaigns.forEach(campaign => {
+ campaign.is_archived = true;
+ });
+ await Promise.all(
+ campaigns.map(async campaign => {
+ await campaign.save();
+ await cacheableData.campaign.clear(campaign.id);
+ })
+ );
+ return campaigns;
},
startCampaign: async (_, { id }, { user, loaders }) => {
- const campaign = await loaders.campaign.load(id)
- await accessRequired(user, campaign.organization_id, 'ADMIN')
- campaign.is_started = true
+ const campaign = await loaders.campaign.load(id);
+ await accessRequired(user, campaign.organization_id, "ADMIN");
+ campaign.is_started = true;
- await campaign.save()
- cacheableData.campaign.reload(id)
+ await campaign.save();
+ cacheableData.campaign.reload(id);
await sendUserNotification({
type: Notifications.CAMPAIGN_STARTED,
campaignId: id
- })
- return campaign
+ });
+ return campaign;
},
editCampaign: async (_, { id, campaign }, { user, loaders }) => {
- const origCampaign = await Campaign.get(id)
+ const origCampaign = await Campaign.get(id);
if (campaign.organizationId) {
- await accessRequired(user, campaign.organizationId, 'ADMIN')
+ await accessRequired(user, campaign.organizationId, "ADMIN");
} else {
- await accessRequired(user, origCampaign.organization_id, 'SUPERVOLUNTEER')
+ await accessRequired(
+ user,
+ origCampaign.organization_id,
+ "SUPERVOLUNTEER"
+ );
}
- if (origCampaign.is_started && campaign.hasOwnProperty('contacts') && campaign.contacts) {
+ if (
+ origCampaign.is_started &&
+ campaign.hasOwnProperty("contacts") &&
+ campaign.contacts
+ ) {
throw new GraphQLError({
status: 400,
- message: 'Not allowed to add contacts after the campaign starts'
- })
+ message: "Not allowed to add contacts after the campaign starts"
+ });
}
- return editCampaign(id, campaign, loaders, user, origCampaign)
+ return editCampaign(id, campaign, loaders, user, origCampaign);
},
deleteJob: async (_, { campaignId, id }, { user, loaders }) => {
- const campaign = await Campaign.get(campaignId)
- await accessRequired(user, campaign.organization_id, 'ADMIN')
- const res = await r.knex('job_request')
+ const campaign = await Campaign.get(campaignId);
+ await accessRequired(user, campaign.organization_id, "ADMIN");
+ const res = await r
+ .knex("job_request")
.where({
id,
campaign_id: campaignId
})
- .delete()
- return { id }
+ .delete();
+ return { id };
},
createCannedResponse: async (_, { cannedResponse }, { user, loaders }) => {
- authRequired(user)
+ authRequired(user);
const cannedResponseInstance = new CannedResponse({
campaign_id: cannedResponse.campaignId,
user_id: cannedResponse.userId,
title: cannedResponse.title,
text: cannedResponse.text
- }).save()
+ }).save();
// deletes duplicate created canned_responses
let query = r
- .knex('canned_response')
+ .knex("canned_response")
.where(
- 'text',
- 'in',
+ "text",
+ "in",
r
- .knex('canned_response')
+ .knex("canned_response")
.where({
text: cannedResponse.text,
campaign_id: cannedResponse.campaignId
})
- .select('text')
+ .select("text")
)
.andWhere({ user_id: cannedResponse.userId })
- .del()
- await query
+ .del();
+ await query;
cacheableData.cannedResponse.clearQuery({
campaignId: cannedResponse.campaignId,
userId: cannedResponse.userId
- })
+ });
},
- createOrganization: async (_, { name, userId, inviteId }, { loaders, user }) => {
- authRequired(user)
- const invite = await loaders.invite.load(inviteId)
+ createOrganization: async (
+ _,
+ { name, userId, inviteId },
+ { loaders, user }
+ ) => {
+ authRequired(user);
+ const invite = await loaders.invite.load(inviteId);
if (!invite || !invite.is_valid) {
throw new GraphQLError({
status: 400,
- message: 'That invitation is no longer valid'
- })
+ message: "That invitation is no longer valid"
+ });
}
const newOrganization = await Organization.save({
name,
uuid: uuidv4()
- })
+ });
await UserOrganization.save({
- role: 'OWNER',
+ role: "OWNER",
user_id: userId,
organization_id: newOrganization.id
- })
+ });
await Invite.save(
{
id: inviteId,
is_valid: false
},
- { conflict: 'update' }
- )
+ { conflict: "update" }
+ );
- return newOrganization
+ return newOrganization;
},
editCampaignContactMessageStatus: async (
_,
{ messageStatus, campaignContactId },
{ loaders, user }
) => {
- const contact = await loaders.campaignContact.load(campaignContactId)
- await assignmentRequired(user, contact.assignment_id)
- contact.message_status = messageStatus
- return await contact.save()
+ const contact = await loaders.campaignContact.load(campaignContactId);
+ await assignmentRequired(user, contact.assignment_id);
+ contact.message_status = messageStatus;
+ return await contact.save();
},
- getAssignmentContacts: async (_, { assignmentId, contactIds, findNew }, { loaders, user }) => {
- await assignmentRequired(user, assignmentId)
- const contacts = contactIds.map(async (contactId) => {
- const contact = await loaders.campaignContact.load(contactId)
+ getAssignmentContacts: async (
+ _,
+ { assignmentId, contactIds, findNew },
+ { loaders, user }
+ ) => {
+ await assignmentRequired(user, assignmentId);
+ const contacts = contactIds.map(async contactId => {
+ const contact = await loaders.campaignContact.load(contactId);
if (contact && contact.assignment_id === Number(assignmentId)) {
- return contact
+ return contact;
}
- return null
- })
+ return null;
+ });
if (findNew) {
// maybe TODO: we could automatically add dynamic assignments in the same api call
// findNewCampaignContact()
}
- return contacts
+ return contacts;
},
- findNewCampaignContact: async (_, { assignmentId, numberContacts }, { loaders, user }) => {
+ findNewCampaignContact: async (
+ _,
+ { assignmentId, numberContacts },
+ { loaders, user }
+ ) => {
/* This attempts to find a new contact for the assignment, in the case that useDynamicAssigment == true */
- const assignment = await Assignment.get(assignmentId)
- await assignmentRequired(user, assignmentId, assignment)
+ const assignment = await Assignment.get(assignmentId);
+ await assignmentRequired(user, assignmentId, assignment);
- const campaign = await Campaign.get(assignment.campaign_id)
+ const campaign = await Campaign.get(assignment.campaign_id);
if (!campaign.use_dynamic_assignment || assignment.max_contacts === 0) {
- return { found: false }
+ return { found: false };
}
const contactsCount = await r.getCount(
- r.knex('campaign_contact').where('assignment_id', assignmentId)
- )
-
- numberContacts = numberContacts || 1
- if (assignment.max_contacts && contactsCount + numberContacts > assignment.max_contacts) {
- numberContacts = assignment.max_contacts - contactsCount
+ r.knex("campaign_contact").where("assignment_id", assignmentId)
+ );
+
+ numberContacts = numberContacts || 1;
+ if (
+ assignment.max_contacts &&
+ contactsCount + numberContacts > assignment.max_contacts
+ ) {
+ numberContacts = assignment.max_contacts - contactsCount;
}
// Don't add more if they already have that many
const result = await r.getCount(
- r.knex('campaign_contact').where({
+ r.knex("campaign_contact").where({
assignment_id: assignmentId,
- message_status: 'needsMessage',
+ message_status: "needsMessage",
is_opted_out: false
})
- )
+ );
if (result >= numberContacts) {
- return { found: false }
+ return { found: false };
}
const updatedCount = await r
- .knex('campaign_contact')
+ .knex("campaign_contact")
.where(
- 'id',
- 'in',
+ "id",
+ "in",
r
- .knex('campaign_contact')
+ .knex("campaign_contact")
.where({
assignment_id: null,
campaign_id: campaign.id
})
.limit(numberContacts)
- .select('id')
+ .select("id")
)
.update({ assignment_id: assignmentId })
- .catch(log.error)
+ .catch(log.error);
if (updatedCount > 0) {
- return { found: true }
+ return { found: true };
} else {
- return { found: false }
+ return { found: false };
}
},
- createOptOut: async (_, { optOut, campaignContactId }, { loaders, user }) => {
- const contact = await loaders.campaignContact.load(campaignContactId)
- await assignmentRequired(user, contact.assignment_id)
+ createOptOut: async (
+ _,
+ { optOut, campaignContactId },
+ { loaders, user }
+ ) => {
+ const contact = await loaders.campaignContact.load(campaignContactId);
+ await assignmentRequired(user, contact.assignment_id);
- const { assignmentId, cell, reason } = optOut
- const campaign = await loaders.campaign.load(contact.campaign_id)
+ const { assignmentId, cell, reason } = optOut;
+ const campaign = await loaders.campaign.load(contact.campaign_id);
await cacheableData.optOut.save({
cell,
@@ -863,91 +967,97 @@ const rootMutations = {
reason,
assignmentId,
campaign
- })
+ });
- return loaders.campaignContact.load(campaignContactId)
+ return loaders.campaignContact.load(campaignContactId);
},
bulkSendMessages: async (_, { assignmentId }, loaders) => {
if (!process.env.ALLOW_SEND_ALL || !process.env.NOT_IN_USA) {
- log.error('Not allowed to send all messages at once')
+ log.error("Not allowed to send all messages at once");
throw new GraphQLError({
status: 403,
- message: 'Not allowed to send all messages at once'
- })
+ message: "Not allowed to send all messages at once"
+ });
}
- const assignment = await Assignment.get(assignmentId)
- const campaign = await Campaign.get(assignment.campaign_id)
+ const assignment = await Assignment.get(assignmentId);
+ const campaign = await Campaign.get(assignment.campaign_id);
// Assign some contacts
await rootMutations.RootMutation.findNewCampaignContact(
_,
- { assignmentId, numberContacts: Number(process.env.BULK_SEND_CHUNK_SIZE) - 1 },
+ {
+ assignmentId,
+ numberContacts: Number(process.env.BULK_SEND_CHUNK_SIZE) - 1
+ },
loaders
- )
+ );
const contacts = await r
- .knex('campaign_contact')
- .where({ message_status: 'needsMessage' })
+ .knex("campaign_contact")
+ .where({ message_status: "needsMessage" })
.where({ assignment_id: assignmentId })
- .orderByRaw('updated_at')
- .limit(process.env.BULK_SEND_CHUNK_SIZE)
+ .orderByRaw("updated_at")
+ .limit(process.env.BULK_SEND_CHUNK_SIZE);
- const texter = camelCaseKeys(await User.get(assignment.user_id))
- const customFields = Object.keys(JSON.parse(contacts[0].custom_fields))
+ const texter = camelCaseKeys(await User.get(assignment.user_id));
+ const customFields = Object.keys(JSON.parse(contacts[0].custom_fields));
const contactMessages = await contacts.map(async contact => {
const script = await campaignContactResolvers.CampaignContact.currentInteractionStepScript(
contact
- )
- contact.customFields = contact.custom_fields
+ );
+ contact.customFields = contact.custom_fields;
const text = applyScript({
contact: camelCaseKeys(contact),
texter,
script,
customFields
- })
+ });
const contactMessage = {
contactNumber: contact.cell,
userId: assignment.user_id,
text,
assignmentId
- }
+ };
await rootMutations.RootMutation.sendMessage(
_,
{ message: contactMessage, campaignContactId: contact.id },
loaders
- )
- })
+ );
+ });
- return []
+ return [];
},
sendMessage: async (_, { message, campaignContactId }, { loaders }) => {
- const contact = await loaders.campaignContact.load(campaignContactId)
- const campaign = await loaders.campaign.load(contact.campaign_id)
- if (contact.assignment_id !== parseInt(message.assignmentId) || campaign.is_archived) {
+ const contact = await loaders.campaignContact.load(campaignContactId);
+ const campaign = await loaders.campaign.load(contact.campaign_id);
+ if (
+ contact.assignment_id !== parseInt(message.assignmentId) ||
+ campaign.is_archived
+ ) {
throw new GraphQLError({
status: 400,
- message: 'Your assignment has changed'
- })
+ message: "Your assignment has changed"
+ });
}
const organization = await r
- .table('campaign')
+ .table("campaign")
.get(contact.campaign_id)
- .eqJoin('organization_id', r.table('organization'))('right')
+ .eqJoin("organization_id", r.table("organization"))("right");
- const orgFeatures = JSON.parse(organization.features || '{}')
+ const orgFeatures = JSON.parse(organization.features || "{}");
const optOut = await r
- .table('opt_out')
- .getAll(contact.cell, { index: 'cell' })
+ .table("opt_out")
+ .getAll(contact.cell, { index: "cell" })
.filter({ organization_id: organization.id })
.limit(1)(0)
- .default(null)
+ .default(null);
if (optOut) {
throw new GraphQLError({
status: 400,
- message: 'Skipped sending because this contact was already opted out'
- })
+ message: "Skipped sending because this contact was already opted out"
+ });
}
// const zipData = await r.table('zip_code')
@@ -967,106 +1077,121 @@ const rootMutations = {
// })
// }
- const { contactNumber, text } = message
+ const { contactNumber, text } = message;
if (text.length > (process.env.MAX_MESSAGE_LENGTH || 99999)) {
throw new GraphQLError({
status: 400,
- message: 'Message was longer than the limit'
- })
+ message: "Message was longer than the limit"
+ });
}
- const replaceCurlyApostrophes = rawText => rawText.replace(/[\u2018\u2019]/g, "'")
+ const replaceCurlyApostrophes = rawText =>
+ rawText.replace(/[\u2018\u2019]/g, "'");
- let contactTimezone = {}
+ let contactTimezone = {};
if (contact.timezone_offset) {
// couldn't look up the timezone by zip record, so we load it
// from the campaign_contact directly if it's there
- const [offset, hasDST] = contact.timezone_offset.split('_')
- contactTimezone.offset = parseInt(offset, 10)
- contactTimezone.hasDST = hasDST === '1'
+ const [offset, hasDST] = contact.timezone_offset.split("_");
+ contactTimezone.offset = parseInt(offset, 10);
+ contactTimezone.hasDST = hasDST === "1";
}
const sendBefore = getSendBeforeTimeUtc(
contactTimezone,
- { textingHoursEnd: organization.texting_hours_end, textingHoursEnforced: organization.texting_hours_enforced },
+ {
+ textingHoursEnd: organization.texting_hours_end,
+ textingHoursEnforced: organization.texting_hours_enforced
+ },
{
textingHoursEnd: campaign.texting_hours_end,
- overrideOrganizationTextingHours: campaign.override_organization_texting_hours,
+ overrideOrganizationTextingHours:
+ campaign.override_organization_texting_hours,
textingHoursEnforced: campaign.texting_hours_enforced,
timezone: campaign.timezone
}
- )
+ );
- const sendBeforeDate = sendBefore ? sendBefore.toDate() : null
+ const sendBeforeDate = sendBefore ? sendBefore.toDate() : null;
if (sendBeforeDate && sendBeforeDate <= Date.now()) {
throw new GraphQLError({
status: 400,
- message: 'Outside permitted texting time for this recipient'
- })
+ message: "Outside permitted texting time for this recipient"
+ });
}
const messageInstance = new Message({
text: replaceCurlyApostrophes(text),
contact_number: contactNumber,
- user_number: '',
+ user_number: "",
assignment_id: message.assignmentId,
- send_status: JOBS_SAME_PROCESS ? 'SENDING' : 'QUEUED',
- service: orgFeatures.service || process.env.DEFAULT_SERVICE || '',
+ send_status: JOBS_SAME_PROCESS ? "SENDING" : "QUEUED",
+ service: orgFeatures.service || process.env.DEFAULT_SERVICE || "",
is_from_contact: false,
queued_at: new Date(),
send_before: sendBeforeDate
- })
-
- await messageInstance.save()
- const service = serviceMap[messageInstance.service || process.env.DEFAULT_SERVICE || global.DEFAULT_SERVICE]
-
- contact.updated_at = 'now()'
-
- if (contact.message_status === 'needsResponse' || contact.message_status === 'convo') {
- contact.message_status = 'convo'
+ });
+
+ await messageInstance.save();
+ const service =
+ serviceMap[
+ messageInstance.service ||
+ process.env.DEFAULT_SERVICE ||
+ global.DEFAULT_SERVICE
+ ];
+
+ contact.updated_at = "now()";
+
+ if (
+ contact.message_status === "needsResponse" ||
+ contact.message_status === "convo"
+ ) {
+ contact.message_status = "convo";
} else {
- contact.message_status = 'messaged'
+ contact.message_status = "messaged";
}
- await contact.save()
+ await contact.save();
log.info(
- `Sending (${service}): ${messageInstance.user_number} -> ${
- messageInstance.contact_number
- }\nMessage: ${messageInstance.text}`
- )
+ `Sending (${service}): ${messageInstance.user_number} -> ${messageInstance.contact_number}\nMessage: ${messageInstance.text}`
+ );
- service.sendMessage(messageInstance, contact)
- return contact
+ service.sendMessage(messageInstance, contact);
+ return contact;
},
deleteQuestionResponses: async (
_,
{ interactionStepIds, campaignContactId },
{ loaders, user }
) => {
- const contact = await loaders.campaignContact.load(campaignContactId)
- await assignmentRequired(user, contact.assignment_id)
+ const contact = await loaders.campaignContact.load(campaignContactId);
+ await assignmentRequired(user, contact.assignment_id);
// TODO: maybe undo action_handler
await r
- .table('question_response')
- .getAll(campaignContactId, { index: 'campaign_contact_id' })
- .getAll(...interactionStepIds, { index: 'interaction_step_id' })
- .delete()
- return contact
+ .table("question_response")
+ .getAll(campaignContactId, { index: "campaign_contact_id" })
+ .getAll(...interactionStepIds, { index: "interaction_step_id" })
+ .delete();
+ return contact;
},
- updateQuestionResponses: async (_, { questionResponses, campaignContactId }, { loaders }) => {
- const count = questionResponses.length
+ updateQuestionResponses: async (
+ _,
+ { questionResponses, campaignContactId },
+ { loaders }
+ ) => {
+ const count = questionResponses.length;
for (let i = 0; i < count; i++) {
- const questionResponse = questionResponses[i]
- const { interactionStepId, value } = questionResponse
+ const questionResponse = questionResponses[i];
+ const { interactionStepId, value } = questionResponse;
await r
- .table('question_response')
- .getAll(campaignContactId, { index: 'campaign_contact_id' })
+ .table("question_response")
+ .getAll(campaignContactId, { index: "campaign_contact_id" })
.filter({ interaction_step_id: interactionStepId })
- .delete()
+ .delete();
// TODO: maybe undo action_handler if updated answer
@@ -1074,37 +1199,42 @@ const rootMutations = {
campaign_contact_id: campaignContactId,
interaction_step_id: interactionStepId,
value
- }).save()
+ }).save();
const interactionStepResult = await r
- .knex('interaction_step')
+ .knex("interaction_step")
// TODO: is this really parent_interaction_id or just interaction_id?
.where({
parent_interaction_id: interactionStepId,
answer_option: value
})
- .whereNot('answer_actions', '')
- .whereNotNull('answer_actions')
+ .whereNot("answer_actions", "")
+ .whereNotNull("answer_actions");
const interactionStepAction =
- interactionStepResult.length && interactionStepResult[0].answer_actions
+ interactionStepResult.length &&
+ interactionStepResult[0].answer_actions;
if (interactionStepAction) {
// run interaction step handler
try {
- const handler = require(`../action_handlers/${interactionStepAction}.js`)
- handler.processAction(qr, interactionStepResult[0], campaignContactId)
+ const handler = require(`../action_handlers/${interactionStepAction}.js`);
+ handler.processAction(
+ qr,
+ interactionStepResult[0],
+ campaignContactId
+ );
} catch (err) {
console.error(
- 'Handler for InteractionStep',
+ "Handler for InteractionStep",
interactionStepId,
- 'Does Not Exist:',
+ "Does Not Exist:",
interactionStepAction
- )
+ );
}
}
}
- const contact = loaders.campaignContact.load(campaignContactId)
- return contact
+ const contact = loaders.campaignContact.load(campaignContactId);
+ return contact;
},
reassignCampaignContacts: async (
_,
@@ -1112,82 +1242,101 @@ const rootMutations = {
{ user }
) => {
// verify permissions
- await accessRequired(user, organizationId, 'ADMIN', /* superadmin*/ true)
+ await accessRequired(user, organizationId, "ADMIN", /* superadmin*/ true);
// group contactIds by campaign
// group messages by campaign
- const campaignIdContactIdsMap = new Map()
- const campaignIdMessagesIdsMap = new Map()
+ const campaignIdContactIdsMap = new Map();
+ const campaignIdMessagesIdsMap = new Map();
for (const campaignIdContactId of campaignIdsContactIds) {
- const { campaignId, campaignContactId, messageIds } = campaignIdContactId
+ const {
+ campaignId,
+ campaignContactId,
+ messageIds
+ } = campaignIdContactId;
if (!campaignIdContactIdsMap.has(campaignId)) {
- campaignIdContactIdsMap.set(campaignId, [])
+ campaignIdContactIdsMap.set(campaignId, []);
}
- campaignIdContactIdsMap.get(campaignId).push(campaignContactId)
+ campaignIdContactIdsMap.get(campaignId).push(campaignContactId);
if (!campaignIdMessagesIdsMap.has(campaignId)) {
- campaignIdMessagesIdsMap.set(campaignId, [])
+ campaignIdMessagesIdsMap.set(campaignId, []);
}
- campaignIdMessagesIdsMap.get(campaignId).push(...messageIds)
+ campaignIdMessagesIdsMap.get(campaignId).push(...messageIds);
}
- return await reassignConversations(campaignIdContactIdsMap, campaignIdMessagesIdsMap, newTexterUserId)
+ return await reassignConversations(
+ campaignIdContactIdsMap,
+ campaignIdMessagesIdsMap,
+ newTexterUserId
+ );
},
bulkReassignCampaignContacts: async (
_,
- { organizationId, campaignsFilter, assignmentsFilter, contactsFilter, newTexterUserId },
+ {
+ organizationId,
+ campaignsFilter,
+ assignmentsFilter,
+ contactsFilter,
+ newTexterUserId
+ },
{ user }
) => {
// verify permissions
- await accessRequired(user, organizationId, 'ADMIN', /* superadmin*/ true)
- const { campaignIdContactIdsMap, campaignIdMessagesIdsMap } =
- await getCampaignIdMessageIdsAndCampaignIdContactIdsMaps(
- organizationId,
- campaignsFilter,
- assignmentsFilter,
- contactsFilter
- )
-
- return await reassignConversations(campaignIdContactIdsMap, campaignIdMessagesIdsMap, newTexterUserId)
+ await accessRequired(user, organizationId, "ADMIN", /* superadmin*/ true);
+ const {
+ campaignIdContactIdsMap,
+ campaignIdMessagesIdsMap
+ } = await getCampaignIdMessageIdsAndCampaignIdContactIdsMaps(
+ organizationId,
+ campaignsFilter,
+ assignmentsFilter,
+ contactsFilter
+ );
+
+ return await reassignConversations(
+ campaignIdContactIdsMap,
+ campaignIdMessagesIdsMap,
+ newTexterUserId
+ );
},
- importCampaignScript: async (_, {
- campaignId,
- url
- }, {
- loaders
- }) => {
- const campaign = await loaders.campaign.load(campaignId)
+ importCampaignScript: async (_, { campaignId, url }, { loaders }) => {
+ const campaign = await loaders.campaign.load(campaignId);
if (campaign.is_started || campaign.is_archived) {
- throw new GraphQLError('Cannot import a campaign script for a campaign that is started or archived')
+ throw new GraphQLError(
+ "Cannot import a campaign script for a campaign that is started or archived"
+ );
}
- const compressedString = await gzip(JSON.stringify({
- campaignId,
- url
- }))
+ const compressedString = await gzip(
+ JSON.stringify({
+ campaignId,
+ url
+ })
+ );
const job = await JobRequest.save({
queue_name: `${campaignId}:import_script`,
- job_type: 'import_script',
+ job_type: "import_script",
locks_queue: true,
assigned: JOBS_SAME_PROCESS, // can get called immediately, below
campaign_id: campaignId,
// NOTE: stringifying because compressedString is a binary buffer
- payload: compressedString.toString('base64')
- })
+ payload: compressedString.toString("base64")
+ });
- const jobId = job.id
+ const jobId = job.id;
if (JOBS_SAME_PROCESS) {
- importScript(job)
+ importScript(job);
}
- return jobId
+ return jobId;
}
}
-}
+};
const rootResolvers = {
Action: {
@@ -1200,79 +1349,90 @@ const rootResolvers = {
},
RootQuery: {
campaign: async (_, { id }, { loaders, user }) => {
- const campaign = await loaders.campaign.load(id)
- await accessRequired(user, campaign.organization_id, 'SUPERVOLUNTEER')
- return campaign
+ const campaign = await loaders.campaign.load(id);
+ await accessRequired(user, campaign.organization_id, "SUPERVOLUNTEER");
+ return campaign;
},
assignment: async (_, { id }, { loaders, user }) => {
- authRequired(user)
- const assignment = await loaders.assignment.load(id)
- const campaign = await loaders.campaign.load(assignment.campaign_id)
+ authRequired(user);
+ const assignment = await loaders.assignment.load(id);
+ const campaign = await loaders.campaign.load(assignment.campaign_id);
if (assignment.user_id == user.id) {
- await accessRequired(user, campaign.organization_id, 'TEXTER', /* allowSuperadmin=*/ true)
+ await accessRequired(
+ user,
+ campaign.organization_id,
+ "TEXTER",
+ /* allowSuperadmin=*/ true
+ );
} else {
await accessRequired(
user,
campaign.organization_id,
- 'SUPERVOLUNTEER',
+ "SUPERVOLUNTEER",
/* allowSuperadmin=*/ true
- )
+ );
}
- return assignment
+ return assignment;
},
organization: async (_, { id }, { user, loaders }) => {
- await accessRequired(user, id, 'TEXTER')
- return await loaders.organization.load(id)
+ await accessRequired(user, id, "TEXTER");
+ return await loaders.organization.load(id);
},
inviteByHash: async (_, { hash }, { loaders, user }) => {
- authRequired(user)
- return r.table('invite').filter({ hash })
+ authRequired(user);
+ return r.table("invite").filter({ hash });
},
currentUser: async (_, { id }, { user }) => {
if (!user) {
- return null
- }
- else {
- return user
+ return null;
+ } else {
+ return user;
}
},
organizations: async (_, { id }, { user }) => {
if (user.is_superadmin) {
- return r.table('organization')
+ return r.table("organization");
} else {
- return await cacheableData.user.userOrgs(user.id, 'TEXTER')
+ return await cacheableData.user.userOrgs(user.id, "TEXTER");
}
},
availableActions: (_, { organizationId }, { user }) => {
if (!process.env.ACTION_HANDLERS) {
- return []
+ return [];
}
- const allHandlers = process.env.ACTION_HANDLERS.split(',')
+ const allHandlers = process.env.ACTION_HANDLERS.split(",");
const availableHandlers = allHandlers
.map(handler => {
return {
name: handler,
handler: require(`../action_handlers/${handler}.js`)
- }
+ };
})
- .filter(async h => h && (await h.handler.available(organizationId)))
+ .filter(async h => h && (await h.handler.available(organizationId)));
const availableHandlerObjects = availableHandlers.map(handler => {
return {
name: handler.name,
display_name: handler.handler.displayName(),
instructions: handler.handler.instructions()
- }
- })
- return availableHandlerObjects
+ };
+ });
+ return availableHandlerObjects;
},
conversations: async (
_,
- { cursor, organizationId, campaignsFilter, assignmentsFilter, contactsFilter, utc },
+ {
+ cursor,
+ organizationId,
+ campaignsFilter,
+ assignmentsFilter,
+ contactsFilter,
+ utc
+ },
{ user }
) => {
- await accessRequired(user, organizationId, 'SUPERVOLUNTEER', true)
+ await accessRequired(user, organizationId, "SUPERVOLUNTEER", true);
return getConversations(
cursor,
@@ -1281,18 +1441,26 @@ const rootResolvers = {
assignmentsFilter,
contactsFilter,
utc
- )
+ );
},
- campaigns: async (_, { organizationId, cursor, campaignsFilter }, { user }) => {
- await accessRequired(user, organizationId, 'SUPERVOLUNTEER')
- return getCampaigns(organizationId, cursor, campaignsFilter)
+ campaigns: async (
+ _,
+ { organizationId, cursor, campaignsFilter },
+ { user }
+ ) => {
+ await accessRequired(user, organizationId, "SUPERVOLUNTEER");
+ return getCampaigns(organizationId, cursor, campaignsFilter);
},
- people: async (_, { organizationId, cursor, campaignsFilter, role, sortBy }, { user }) => {
- await accessRequired(user, organizationId, 'SUPERVOLUNTEER')
- return getUsers(organizationId, cursor, campaignsFilter, role, sortBy)
+ people: async (
+ _,
+ { organizationId, cursor, campaignsFilter, role, sortBy },
+ { user }
+ ) => {
+ await accessRequired(user, organizationId, "SUPERVOLUNTEER");
+ return getUsers(organizationId, cursor, campaignsFilter, role, sortBy);
}
}
-}
+};
export const resolvers = {
...rootResolvers,
@@ -1313,4 +1481,4 @@ export const resolvers = {
...questionResolvers,
...conversationsResolver,
...rootMutations
-}
+};
diff --git a/src/server/api/user.js b/src/server/api/user.js
index 2f3bb4be6..b62f048ef 100644
--- a/src/server/api/user.js
+++ b/src/server/api/user.js
@@ -1,113 +1,151 @@
-import { mapFieldsToModel } from './lib/utils'
-import { r, User, cacheableData } from '../models'
-import { addCampaignsFilterToQuery } from './campaign'
+import { mapFieldsToModel } from "./lib/utils";
+import { r, User, cacheableData } from "../models";
+import { addCampaignsFilterToQuery } from "./campaign";
-const firstName = '"user"."first_name"'
-const lastName = '"user"."last_name"'
-const created = '"user"."created_at"'
-const oldest = created
-const newest = '"user"."created_at" desc'
+const firstName = '"user"."first_name"';
+const lastName = '"user"."last_name"';
+const created = '"user"."created_at"';
+const oldest = created;
+const newest = '"user"."created_at" desc';
function buildSelect(sortBy) {
- const userStar = '"user".*'
+ const userStar = '"user".*';
- let fragmentArray = undefined
+ let fragmentArray = undefined;
switch (sortBy) {
- case 'COUNT_ONLY':
- return r.knex.countDistinct('user.id')
- case 'LAST_NAME':
- fragmentArray = [userStar]
- break
- case 'NEWEST':
- fragmentArray = [userStar]
- break
- case 'OLDEST':
- fragmentArray = [userStar]
- break
- case 'FIRST_NAME':
+ case "COUNT_ONLY":
+ return r.knex.countDistinct("user.id");
+ case "LAST_NAME":
+ fragmentArray = [userStar];
+ break;
+ case "NEWEST":
+ fragmentArray = [userStar];
+ break;
+ case "OLDEST":
+ fragmentArray = [userStar];
+ break;
+ case "FIRST_NAME":
default:
- fragmentArray = [userStar]
- break
+ fragmentArray = [userStar];
+ break;
}
- return r.knex.select(r.knex.raw(fragmentArray.join(', ')))
+ return r.knex.select(r.knex.raw(fragmentArray.join(", ")));
}
function buildOrderBy(query, sortBy) {
- let fragmentArray = undefined
+ let fragmentArray = undefined;
switch (sortBy) {
- case 'COUNT_ONLY':
- return query
- case 'LAST_NAME':
- fragmentArray = [lastName, firstName, newest]
- break
- case 'NEWEST':
- fragmentArray = [newest]
- break
- case 'OLDEST':
- fragmentArray = [oldest]
- break
- case 'FIRST_NAME':
+ case "COUNT_ONLY":
+ return query;
+ case "LAST_NAME":
+ fragmentArray = [lastName, firstName, newest];
+ break;
+ case "NEWEST":
+ fragmentArray = [newest];
+ break;
+ case "OLDEST":
+ fragmentArray = [oldest];
+ break;
+ case "FIRST_NAME":
default:
- fragmentArray = [firstName, lastName, newest]
- break
+ fragmentArray = [firstName, lastName, newest];
+ break;
}
- return query.orderByRaw(fragmentArray.join(', '))
+ return query.orderByRaw(fragmentArray.join(", "));
}
-export function buildUserOrganizationQuery(queryParam, organizationId, role, campaignId, offset) {
- const roleFilter = role ? { role } : {}
+export function buildUserOrganizationQuery(
+ queryParam,
+ organizationId,
+ role,
+ campaignId,
+ offset
+) {
+ const roleFilter = role ? { role } : {};
let query = queryParam
- .from('user_organization')
- .innerJoin('user', 'user_organization.user_id', 'user.id')
+ .from("user_organization")
+ .innerJoin("user", "user_organization.user_id", "user.id")
.where(roleFilter)
.whereRaw('"user_organization"."organization_id" = ?', organizationId)
- .distinct()
+ .distinct();
if (campaignId) {
- query = query.leftOuterJoin('assignment', 'assignment.user_id', 'user.id')
- .where({ 'assignment.campaign_id': campaignId })
+ query = query
+ .leftOuterJoin("assignment", "assignment.user_id", "user.id")
+ .where({ "assignment.campaign_id": campaignId });
}
- return query
+ return query;
}
-export function buildSortedUserOrganizationQuery(organizationId, role, campaignId, sortBy) {
- const query = buildUserOrganizationQuery(buildSelect(sortBy), organizationId, role, campaignId)
- return buildOrderBy(query, sortBy)
+export function buildSortedUserOrganizationQuery(
+ organizationId,
+ role,
+ campaignId,
+ sortBy
+) {
+ const query = buildUserOrganizationQuery(
+ buildSelect(sortBy),
+ organizationId,
+ role,
+ campaignId
+ );
+ return buildOrderBy(query, sortBy);
}
function buildUsersQuery(organizationId, campaignsFilter, role, sortBy) {
- return buildSortedUserOrganizationQuery(organizationId, role, campaignsFilter && campaignsFilter.campaignId, sortBy)
+ return buildSortedUserOrganizationQuery(
+ organizationId,
+ role,
+ campaignsFilter && campaignsFilter.campaignId,
+ sortBy
+ );
}
-export async function getUsers(organizationId, cursor, campaignsFilter, role, sortBy) {
- let usersQuery = buildUsersQuery(organizationId, campaignsFilter, role, sortBy)
+export async function getUsers(
+ organizationId,
+ cursor,
+ campaignsFilter,
+ role,
+ sortBy
+) {
+ let usersQuery = buildUsersQuery(
+ organizationId,
+ campaignsFilter,
+ role,
+ sortBy
+ );
if (cursor) {
- usersQuery = usersQuery.limit(cursor.limit).offset(cursor.offset)
- const users = await usersQuery
+ usersQuery = usersQuery.limit(cursor.limit).offset(cursor.offset);
+ const users = await usersQuery;
- const usersCountQuery = buildUsersQuery(organizationId, campaignsFilter, role, 'COUNT_ONLY')
+ const usersCountQuery = buildUsersQuery(
+ organizationId,
+ campaignsFilter,
+ role,
+ "COUNT_ONLY"
+ );
- const usersCountArray = await usersCountQuery
+ const usersCountArray = await usersCountQuery;
const pageInfo = {
limit: cursor.limit,
offset: cursor.offset,
total: usersCountArray[0].count
- }
+ };
return {
users,
pageInfo
- }
+ };
} else {
- return usersQuery
+ return usersQuery;
}
}
@@ -115,11 +153,11 @@ export const resolvers = {
UsersReturn: {
__resolveType(obj) {
if (Array.isArray(obj)) {
- return 'UsersList'
- } else if ('users' in obj && 'pageInfo' in obj) {
- return 'PaginatedUsers'
+ return "UsersList";
+ } else if ("users" in obj && "pageInfo" in obj) {
+ return "PaginatedUsers";
}
- return null
+ return null;
}
},
UsersList: {
@@ -128,55 +166,56 @@ export const resolvers = {
PaginatedUsers: {
users: queryResult => queryResult.users,
pageInfo: queryResult => {
- if ('pageInfo' in queryResult) {
- return queryResult.pageInfo
+ if ("pageInfo" in queryResult) {
+ return queryResult.pageInfo;
}
- return null
+ return null;
}
},
User: {
- ...mapFieldsToModel([
- 'id',
- 'firstName',
- 'lastName',
- 'email',
- 'cell',
- 'assignedCell',
- 'terms'
- ], User),
- displayName: (user) => `${user.first_name} ${user.last_name}`,
+ ...mapFieldsToModel(
+ ["id", "firstName", "lastName", "email", "cell", "assignedCell", "terms"],
+ User
+ ),
+ displayName: user => `${user.first_name} ${user.last_name}`,
assignment: async (user, { campaignId }) => {
- if (user.assignment_id && user.assignment_campaign_id === Number(campaignId)) {
+ if (
+ user.assignment_id &&
+ user.assignment_campaign_id === Number(campaignId)
+ ) {
// from context of campaign.texters.assignment
- return { id: user.assignment_id,
- campaign_id: user.assignment_campaign_id,
- max_contacts: user.assignment_max_contacts }
+ return {
+ id: user.assignment_id,
+ campaign_id: user.assignment_campaign_id,
+ max_contacts: user.assignment_max_contacts
+ };
}
- return r.table('assignment')
- .getAll(user.id, { index: 'user_id' })
+ return r
+ .table("assignment")
+ .getAll(user.id, { index: "user_id" })
.filter({ campaign_id: campaignId })
.limit(1)(0)
- .default(null)
+ .default(null);
},
organizations: async (user, { role }) => {
if (!user || !user.id) {
- return []
+ return [];
}
// Note: this only returns {id, name}, but that is all apis need here
- return await cacheableData.user.userOrgs(user.id, role)
+ return await cacheableData.user.userOrgs(user.id, role);
},
- roles: async(user, { organizationId }) => (
- cacheableData.user.orgRoles(user.id, organizationId)
- ),
- todos: async (user, { organizationId }) => (
- r.table('assignment')
- .getAll(user.id, { index: 'assignment.user_id' })
- .eqJoin('campaign_id', r.table('campaign'))
- .filter({ 'is_started': true,
- 'organization_id': organizationId,
- 'is_archived': false }
- )('left')
- ),
+ roles: async (user, { organizationId }) =>
+ cacheableData.user.orgRoles(user.id, organizationId),
+ todos: async (user, { organizationId }) =>
+ r
+ .table("assignment")
+ .getAll(user.id, { index: "assignment.user_id" })
+ .eqJoin("campaign_id", r.table("campaign"))
+ .filter({
+ is_started: true,
+ organization_id: organizationId,
+ is_archived: false
+ })("left"),
cacheable: () => false // FUTURE: Boolean(r.redis) when full assignment data is cached
}
-}
+};
diff --git a/src/server/auth-passport.js b/src/server/auth-passport.js
index 2be73f46e..21827b6e2 100644
--- a/src/server/auth-passport.js
+++ b/src/server/auth-passport.js
@@ -1,126 +1,139 @@
-import passport from 'passport'
-import Auth0Strategy from 'passport-auth0'
-import { Strategy as LocalStrategy } from 'passport-local'
-import { User, cacheableData } from './models'
-import localAuthHelpers from './local-auth-helpers'
-import wrap from './wrap'
-import { capitalizeWord } from './api/lib/utils'
-
+import passport from "passport";
+import Auth0Strategy from "passport-auth0";
+import { Strategy as LocalStrategy } from "passport-local";
+import { User, cacheableData } from "./models";
+import localAuthHelpers from "./local-auth-helpers";
+import wrap from "./wrap";
+import { capitalizeWord } from "./api/lib/utils";
export function setupAuth0Passport() {
- const strategy = new Auth0Strategy({
- domain: process.env.AUTH0_DOMAIN,
- clientID: process.env.AUTH0_CLIENT_ID,
- clientSecret: process.env.AUTH0_CLIENT_SECRET,
- callbackURL: `${process.env.BASE_URL}/login-callback`
- }, (accessToken, refreshToken, extraParams, profile, done) => done(null, profile)
- )
+ const strategy = new Auth0Strategy(
+ {
+ domain: process.env.AUTH0_DOMAIN,
+ clientID: process.env.AUTH0_CLIENT_ID,
+ clientSecret: process.env.AUTH0_CLIENT_SECRET,
+ callbackURL: `${process.env.BASE_URL}/login-callback`
+ },
+ (accessToken, refreshToken, extraParams, profile, done) =>
+ done(null, profile)
+ );
- passport.use(strategy)
+ passport.use(strategy);
passport.serializeUser((user, done) => {
// This is the Auth0 user object, not the db one
// eslint-disable-next-line no-underscore-dangle
- const auth0Id = (user.id || user._json.sub)
- done(null, auth0Id)
- })
+ const auth0Id = user.id || user._json.sub;
+ done(null, auth0Id);
+ });
- passport.deserializeUser(wrap(async (id, done) => {
- // add new cacheable query
- const user = await cacheableData.user.userLoggedIn('auth0_id', id)
- done(null, user || false)
- }))
+ passport.deserializeUser(
+ wrap(async (id, done) => {
+ // add new cacheable query
+ const user = await cacheableData.user.userLoggedIn("auth0_id", id);
+ done(null, user || false);
+ })
+ );
return {
loginCallback: [
- passport.authenticate('auth0', { failureRedirect: '/login' }),
+ passport.authenticate("auth0", { failureRedirect: "/login" }),
wrap(async (req, res) => {
// eslint-disable-next-line no-underscore-dangle
- const auth0Id = (req.user && (req.user.id || req.user._json.sub))
+ const auth0Id = req.user && (req.user.id || req.user._json.sub);
if (!auth0Id) {
- throw new Error('Null user in login callback')
+ throw new Error("Null user in login callback");
}
- const existingUser = await User.filter({ auth0_id: auth0Id })
+ const existingUser = await User.filter({ auth0_id: auth0Id });
if (existingUser.length === 0) {
- const userMetadata = (
+ const userMetadata =
// eslint-disable-next-line no-underscore-dangle
- req.user._json['https://spoke/user_metadata']
+ req.user._json["https://spoke/user_metadata"] ||
// eslint-disable-next-line no-underscore-dangle
- || req.user._json.user_metadata
- || {})
+ req.user._json.user_metadata ||
+ {};
const userData = {
auth0_id: auth0Id,
// eslint-disable-next-line no-underscore-dangle
- first_name: capitalizeWord(userMetadata.given_name) || '',
+ first_name: capitalizeWord(userMetadata.given_name) || "",
// eslint-disable-next-line no-underscore-dangle
- last_name: capitalizeWord(userMetadata.family_name) || '',
- cell: userMetadata.cell || '',
+ last_name: capitalizeWord(userMetadata.family_name) || "",
+ cell: userMetadata.cell || "",
// eslint-disable-next-line no-underscore-dangle
email: req.user._json.email,
is_superadmin: false
- }
- await User.save(userData)
- res.redirect(req.query.state || 'terms')
- return
+ };
+ await User.save(userData);
+ res.redirect(req.query.state || "terms");
+ return;
}
- res.redirect(req.query.state || '/')
- return
- })]
- }
+ res.redirect(req.query.state || "/");
+ return;
+ })
+ ]
+ };
}
export function setupLocalAuthPassport() {
- const strategy = new LocalStrategy({
- usernameField: 'email',
- passReqToCallback: true
- }, wrap(async (req, username, password, done) => {
- const lowerCaseEmail = username.toLowerCase()
- const existingUser = await User.filter({ email: lowerCaseEmail })
- const nextUrl = req.body.nextUrl || ''
- const uuidMatch = nextUrl.match(/\w{8}-(\w{4}\-){3}\w{12}/)
+ const strategy = new LocalStrategy(
+ {
+ usernameField: "email",
+ passReqToCallback: true
+ },
+ wrap(async (req, username, password, done) => {
+ const lowerCaseEmail = username.toLowerCase();
+ const existingUser = await User.filter({ email: lowerCaseEmail });
+ const nextUrl = req.body.nextUrl || "";
+ const uuidMatch = nextUrl.match(/\w{8}-(\w{4}\-){3}\w{12}/);
- // Run login, signup, or reset functions based on request data
- if (req.body.authType && !localAuthHelpers[req.body.authType]) {
- return done(null, false)
- }
- try {
- const user = await localAuthHelpers[req.body.authType]({
- lowerCaseEmail,
- password,
- existingUser,
- nextUrl,
- uuidMatch,
- reqBody: req.body
- })
- return done(null, user)
- } catch (err) {
- return done(null, false, err.message)
- }
- }))
+ // Run login, signup, or reset functions based on request data
+ if (req.body.authType && !localAuthHelpers[req.body.authType]) {
+ return done(null, false);
+ }
+ try {
+ const user = await localAuthHelpers[req.body.authType]({
+ lowerCaseEmail,
+ password,
+ existingUser,
+ nextUrl,
+ uuidMatch,
+ reqBody: req.body
+ });
+ return done(null, user);
+ } catch (err) {
+ return done(null, false, err.message);
+ }
+ })
+ );
- passport.use(strategy)
+ passport.use(strategy);
passport.serializeUser((user, done) => {
- done(null, user.id)
- })
+ done(null, user.id);
+ });
- passport.deserializeUser(wrap(async (id, done) => {
- const user = await cacheableData.user.userLoggedIn('id', parseInt(id, 10))
- done(null, user || false)
- }))
+ passport.deserializeUser(
+ wrap(async (id, done) => {
+ const user = await cacheableData.user.userLoggedIn(
+ "id",
+ parseInt(id, 10)
+ );
+ done(null, user || false);
+ })
+ );
return {
loginCallback: [
- passport.authenticate('local'),
+ passport.authenticate("local"),
(req, res) => {
- res.redirect(req.body.nextUrl || '/')
+ res.redirect(req.body.nextUrl || "/");
}
]
- }
+ };
}
export default {
local: setupLocalAuthPassport,
auth0: setupAuth0Passport
-}
+};
diff --git a/src/server/index.js b/src/server/index.js
index 83e5bf96f..992f38e24 100644
--- a/src/server/index.js
+++ b/src/server/index.js
@@ -1,185 +1,209 @@
-import 'babel-polyfill'
-import bodyParser from 'body-parser'
-import express from 'express'
-import appRenderer from './middleware/app-renderer'
-import { graphqlExpress, graphiqlExpress } from 'apollo-server-express'
-import { makeExecutableSchema, addMockFunctionsToSchema } from 'graphql-tools'
+import "babel-polyfill";
+import bodyParser from "body-parser";
+import express from "express";
+import appRenderer from "./middleware/app-renderer";
+import { graphqlExpress, graphiqlExpress } from "apollo-server-express";
+import { makeExecutableSchema, addMockFunctionsToSchema } from "graphql-tools";
// ORDERING: ./models import must be imported above ./api to help circular imports
-import { createLoaders, createTablesIfNecessary, r } from './models'
-import { resolvers } from './api/schema'
-import { schema } from '../api/schema'
-import mocks from './api/mocks'
-import passport from 'passport'
-import cookieSession from 'cookie-session'
-import passportSetup from './auth-passport'
-import wrap from './wrap'
-import { log } from '../lib'
-import nexmo from './api/lib/nexmo'
-import twilio from './api/lib/twilio'
-import { seedZipCodes } from './seeds/seed-zip-codes'
-import { setupUserNotificationObservers } from './notifications'
-import { TwimlResponse } from 'twilio'
-import { existsSync } from 'fs'
-
-process.on('uncaughtException', (ex) => {
- log.error(ex)
- process.exit(1)
-})
-const DEBUG = process.env.NODE_ENV === 'development'
-
-const loginCallbacks = passportSetup[process.env.PASSPORT_STRATEGY || global.PASSPORT_STRATEGY || 'auth0']()
+import { createLoaders, createTablesIfNecessary, r } from "./models";
+import { resolvers } from "./api/schema";
+import { schema } from "../api/schema";
+import mocks from "./api/mocks";
+import passport from "passport";
+import cookieSession from "cookie-session";
+import passportSetup from "./auth-passport";
+import wrap from "./wrap";
+import { log } from "../lib";
+import nexmo from "./api/lib/nexmo";
+import twilio from "./api/lib/twilio";
+import { seedZipCodes } from "./seeds/seed-zip-codes";
+import { setupUserNotificationObservers } from "./notifications";
+import { TwimlResponse } from "twilio";
+import { existsSync } from "fs";
+
+process.on("uncaughtException", ex => {
+ log.error(ex);
+ process.exit(1);
+});
+const DEBUG = process.env.NODE_ENV === "development";
+
+const loginCallbacks = passportSetup[
+ process.env.PASSPORT_STRATEGY || global.PASSPORT_STRATEGY || "auth0"
+]();
if (!process.env.SUPPRESS_SEED_CALLS) {
- seedZipCodes()
+ seedZipCodes();
}
if (!process.env.SUPPRESS_DATABASE_AUTOCREATE) {
- createTablesIfNecessary().then((didCreate) => {
+ createTablesIfNecessary().then(didCreate => {
// seed above won't have succeeded if we needed to create first
if (didCreate && !process.env.SUPPRESS_SEED_CALLS) {
- seedZipCodes()
+ seedZipCodes();
}
if (!didCreate && !process.env.SUPPRESS_MIGRATIONS) {
- r.k.migrate.latest()
+ r.k.migrate.latest();
}
- })
+ });
} else if (!process.env.SUPPRESS_MIGRATIONS) {
- r.k.migrate.latest()
+ r.k.migrate.latest();
}
-setupUserNotificationObservers()
-const app = express()
+setupUserNotificationObservers();
+const app = express();
// Heroku requires you to use process.env.PORT
-const port = process.env.DEV_APP_PORT || process.env.PORT
+const port = process.env.DEV_APP_PORT || process.env.PORT;
// Don't rate limit heroku
-app.enable('trust proxy')
+app.enable("trust proxy");
// Serve static assets
if (existsSync(process.env.ASSETS_DIR)) {
- app.use('/assets', express.static(process.env.ASSETS_DIR, {
- maxAge: '180 days'
- }))
+ app.use(
+ "/assets",
+ express.static(process.env.ASSETS_DIR, {
+ maxAge: "180 days"
+ })
+ );
}
-app.use(bodyParser.json({ limit: '50mb' }))
-app.use(bodyParser.urlencoded({ extended: true }))
-
-app.use(cookieSession({
- cookie: {
- httpOnly: true,
- secure: !DEBUG,
- maxAge: null
- },
- secret: process.env.SESSION_SECRET || global.SESSION_SECRET
-}))
-app.use(passport.initialize())
-app.use(passport.session())
+app.use(bodyParser.json({ limit: "50mb" }));
+app.use(bodyParser.urlencoded({ extended: true }));
+
+app.use(
+ cookieSession({
+ cookie: {
+ httpOnly: true,
+ secure: !DEBUG,
+ maxAge: null
+ },
+ secret: process.env.SESSION_SECRET || global.SESSION_SECRET
+ })
+);
+app.use(passport.initialize());
+app.use(passport.session());
app.use((req, res, next) => {
- const getContext = app.get('awsContextGetter')
- if (typeof getContext === 'function') {
- const [event, context] = getContext(req, res)
- req.awsEvent = event
- req.awsContext = context
- }
- next()
-})
-
-app.post('/nexmo', wrap(async (req, res) => {
- try {
- const messageId = await nexmo.handleIncomingMessage(req.body)
- res.send(messageId)
- } catch (ex) {
- log.error(ex)
- res.send('done')
- }
-}))
-
-app.post('/twilio', twilio.webhook(), wrap(async (req, res) => {
- try {
- await twilio.handleIncomingMessage(req.body)
- } catch (ex) {
- log.error(ex)
+ const getContext = app.get("awsContextGetter");
+ if (typeof getContext === "function") {
+ const [event, context] = getContext(req, res);
+ req.awsEvent = event;
+ req.awsContext = context;
}
+ next();
+});
+
+app.post(
+ "/nexmo",
+ wrap(async (req, res) => {
+ try {
+ const messageId = await nexmo.handleIncomingMessage(req.body);
+ res.send(messageId);
+ } catch (ex) {
+ log.error(ex);
+ res.send("done");
+ }
+ })
+);
+
+app.post(
+ "/twilio",
+ twilio.webhook(),
+ wrap(async (req, res) => {
+ try {
+ await twilio.handleIncomingMessage(req.body);
+ } catch (ex) {
+ log.error(ex);
+ }
- const resp = new TwimlResponse()
- res.writeHead(200, { 'Content-Type': 'text/xml' })
- res.end(resp.toString())
-}))
-
-app.post('/nexmo-message-report', wrap(async (req, res) => {
- try {
- const body = req.body
- await nexmo.handleDeliveryReport(body)
- } catch (ex) {
- log.error(ex)
- }
- res.send('done')
-}))
-
-app.post('/twilio-message-report', wrap(async (req, res) => {
- try {
- const body = req.body
- await twilio.handleDeliveryReport(body)
- } catch (ex) {
- log.error(ex)
- }
- const resp = new TwimlResponse()
- res.writeHead(200, { 'Content-Type': 'text/xml' })
- res.end(resp.toString())
-}))
+ const resp = new TwimlResponse();
+ res.writeHead(200, { "Content-Type": "text/xml" });
+ res.end(resp.toString());
+ })
+);
+
+app.post(
+ "/nexmo-message-report",
+ wrap(async (req, res) => {
+ try {
+ const body = req.body;
+ await nexmo.handleDeliveryReport(body);
+ } catch (ex) {
+ log.error(ex);
+ }
+ res.send("done");
+ })
+);
+
+app.post(
+ "/twilio-message-report",
+ wrap(async (req, res) => {
+ try {
+ const body = req.body;
+ await twilio.handleDeliveryReport(body);
+ } catch (ex) {
+ log.error(ex);
+ }
+ const resp = new TwimlResponse();
+ res.writeHead(200, { "Content-Type": "text/xml" });
+ res.end(resp.toString());
+ })
+);
// const accountSid = process.env.TWILIO_API_KEY
// const authToken = process.env.TWILIO_AUTH_TOKEN
// const client = require('twilio')(accountSid, authToken)
-app.get('/logout-callback', (req, res) => {
- req.logOut()
- res.redirect('/')
-})
+app.get("/logout-callback", (req, res) => {
+ req.logOut();
+ res.redirect("/");
+});
if (loginCallbacks) {
- app.get('/login-callback', ...loginCallbacks.loginCallback)
- app.post('/login-callback', ...loginCallbacks.loginCallback)
+ app.get("/login-callback", ...loginCallbacks.loginCallback);
+ app.post("/login-callback", ...loginCallbacks.loginCallback);
}
const executableSchema = makeExecutableSchema({
typeDefs: schema,
resolvers,
allowUndefinedInResolve: false
-})
+});
addMockFunctionsToSchema({
schema: executableSchema,
mocks,
preserveResolvers: true
-})
-
-app.use('/graphql', graphqlExpress((request) => ({
- schema: executableSchema,
- context: {
- loaders: createLoaders(),
- user: request.user,
- awsContext: request.awsContext || null,
- awsEvent: request.awsEvent || null,
- remainingMilliseconds: () => (
- (request.awsContext && request.awsContext.getRemainingTimeInMillis)
- ? request.awsContext.getRemainingTimeInMillis()
- : 5 * 60 * 1000 // default saying 5 min, no matter what
- )
- }
-})))
-app.get('/graphiql', graphiqlExpress({
- endpointURL: '/graphql'
-}))
+});
+
+app.use(
+ "/graphql",
+ graphqlExpress(request => ({
+ schema: executableSchema,
+ context: {
+ loaders: createLoaders(),
+ user: request.user,
+ awsContext: request.awsContext || null,
+ awsEvent: request.awsEvent || null,
+ remainingMilliseconds: () =>
+ request.awsContext && request.awsContext.getRemainingTimeInMillis
+ ? request.awsContext.getRemainingTimeInMillis()
+ : 5 * 60 * 1000 // default saying 5 min, no matter what
+ }
+ }))
+);
+app.get(
+ "/graphiql",
+ graphiqlExpress({
+ endpointURL: "/graphql"
+ })
+);
// This middleware should be last. Return the React app only if no other route is hit.
-app.use(appRenderer)
-
+app.use(appRenderer);
if (port) {
app.listen(port, () => {
- log.info(`Node app is running on port ${port}`)
- })
+ log.info(`Node app is running on port ${port}`);
+ });
}
-export default app
+export default app;
diff --git a/src/server/knex-connect.js b/src/server/knex-connect.js
index fdb192944..4f7e07702 100644
--- a/src/server/knex-connect.js
+++ b/src/server/knex-connect.js
@@ -3,10 +3,10 @@
// deprecated, a better pattern would be to instantiate knex here and export
// that instance, for reference everywhere else in the codebase.
const {
- DB_USE_SSL = 'false',
+ DB_USE_SSL = "false",
DB_JSON = global.DB_JSON,
- DB_HOST = '127.0.0.1',
- DB_PORT = '5432',
+ DB_HOST = "127.0.0.1",
+ DB_PORT = "5432",
DB_MIN_POOL = 2,
DB_MAX_POOL = 10,
DB_TYPE,
@@ -15,23 +15,23 @@ const {
DB_USER,
DATABASE_URL,
NODE_ENV
-} = process.env
-const min = parseInt(DB_MIN_POOL, 10)
-const max = parseInt(DB_MAX_POOL, 10)
+} = process.env;
+const min = parseInt(DB_MIN_POOL, 10);
+const max = parseInt(DB_MAX_POOL, 10);
-const pg = require('pg')
+const pg = require("pg");
-const useSSL = DB_USE_SSL === '1' || DB_USE_SSL.toLowerCase() === 'true'
-if (useSSL) pg.defaults.ssl = true
+const useSSL = DB_USE_SSL === "1" || DB_USE_SSL.toLowerCase() === "true";
+if (useSSL) pg.defaults.ssl = true;
// see https://github.com/tgriesser/knex/issues/852
-let config
+let config;
if (DB_JSON) {
- config = JSON.parse(DB_JSON)
+ config = JSON.parse(DB_JSON);
} else if (DB_TYPE) {
config = {
- client: 'pg',
+ client: "pg",
connection: {
host: DB_HOST,
port: DB_PORT,
@@ -41,33 +41,33 @@ if (DB_JSON) {
ssl: useSSL
},
pool: { min, max }
- }
+ };
} else if (DATABASE_URL) {
- const dbType = DATABASE_URL.match(/^\w+/)[0]
+ const dbType = DATABASE_URL.match(/^\w+/)[0];
config = {
- client: (/postgres/.test(dbType) ? 'pg' : dbType),
+ client: /postgres/.test(dbType) ? "pg" : dbType,
connection: DATABASE_URL,
pool: { min, max },
ssl: useSSL
- }
-} else if (NODE_ENV === 'test') {
+ };
+} else if (NODE_ENV === "test") {
config = {
- client: 'pg',
+ client: "pg",
connection: {
host: DB_HOST,
port: DB_PORT,
- database: 'spoke_test',
- password: 'spoke_test',
- user: 'spoke_test',
+ database: "spoke_test",
+ password: "spoke_test",
+ user: "spoke_test",
ssl: useSSL
}
- }
+ };
} else {
config = {
- client: 'sqlite3',
- connection: { filename: './mydb.sqlite' },
+ client: "sqlite3",
+ connection: { filename: "./mydb.sqlite" },
defaultsUnsupported: true
- }
+ };
}
-module.exports = config
+module.exports = config;
diff --git a/src/server/local-auth-helpers.js b/src/server/local-auth-helpers.js
index 549539da4..5a8a35b84 100644
--- a/src/server/local-auth-helpers.js
+++ b/src/server/local-auth-helpers.js
@@ -1,57 +1,50 @@
-import AuthHasher from 'passport-local-authenticate'
-import { User, Invite, Organization } from './models'
-import { capitalizeWord } from './api/lib/utils'
+import AuthHasher from "passport-local-authenticate";
+import { User, Invite, Organization } from "./models";
+import { capitalizeWord } from "./api/lib/utils";
const errorMessages = {
- invalidInvite: 'Invalid invite code. Contact your administrator.',
- invalidCredentials: 'Invalid username or password',
- emailTaken: 'That email is already taken.',
- passwordsDontMatch: 'Passwords don\'t match.',
- invalidResetHash: 'Invalid username or password reset link. Contact your administrator.',
- noSamePassword: 'Old and new password can\'t be the same'
-}
+ invalidInvite: "Invalid invite code. Contact your administrator.",
+ invalidCredentials: "Invalid username or password",
+ emailTaken: "That email is already taken.",
+ passwordsDontMatch: "Passwords don't match.",
+ invalidResetHash:
+ "Invalid username or password reset link. Contact your administrator.",
+ noSamePassword: "Old and new password can't be the same"
+};
const validUuid = async (nextUrl, uuidMatch) => {
- if (!uuidMatch || !nextUrl) throw new Error(errorMessages.invalidInvite)
+ if (!uuidMatch || !nextUrl) throw new Error(errorMessages.invalidInvite);
- let foundUUID
- if (nextUrl.includes('join')) {
- foundUUID = await Organization.filter({ uuid: uuidMatch[0] })
- } else if (nextUrl.includes('invite')) {
- foundUUID = await Invite.filter({ hash: uuidMatch[0] })
+ let foundUUID;
+ if (nextUrl.includes("join")) {
+ foundUUID = await Organization.filter({ uuid: uuidMatch[0] });
+ } else if (nextUrl.includes("invite")) {
+ foundUUID = await Invite.filter({ hash: uuidMatch[0] });
}
- if (foundUUID.length === 0) throw new Error(errorMessages.invalidInvite)
-}
+ if (foundUUID.length === 0) throw new Error(errorMessages.invalidInvite);
+};
-const login = async ({
- password,
- existingUser,
- nextUrl,
- uuidMatch
-}) => {
+const login = async ({ password, existingUser, nextUrl, uuidMatch }) => {
if (existingUser.length === 0) {
- throw new Error(errorMessages.invalidCredentials)
+ throw new Error(errorMessages.invalidCredentials);
}
// Get salt and hash and verify user password
- const pwFieldSplit = existingUser[0].auth0_id.split('|')
+ const pwFieldSplit = existingUser[0].auth0_id.split("|");
const hashed = {
salt: pwFieldSplit[1],
hash: pwFieldSplit[2]
- }
+ };
return new Promise((resolve, reject) => {
- AuthHasher.verify(
- password, hashed,
- (err, verified) => {
- if (err) reject(err)
- if (verified) {
- resolve(existingUser[0])
- }
- reject({ message: errorMessages.invalidCredentials })
+ AuthHasher.verify(password, hashed, (err, verified) => {
+ if (err) reject(err);
+ if (verified) {
+ resolve(existingUser[0]);
}
- )
- })
-}
+ reject({ message: errorMessages.invalidCredentials });
+ });
+ });
+};
const signup = async ({
lowerCaseEmail,
@@ -63,24 +56,24 @@ const signup = async ({
}) => {
// Verify UUID validity
// If there is an error, it will be caught on local strategy invocation
- await validUuid(nextUrl, uuidMatch)
+ await validUuid(nextUrl, uuidMatch);
// Verify user doesn't already exist
if (existingUser.length > 0 && existingUser[0].email === lowerCaseEmail) {
- throw new Error(errorMessages.emailTaken)
+ throw new Error(errorMessages.emailTaken);
}
// Verify password and password confirm fields match
if (password !== reqBody.passwordConfirm) {
- throw new Error(errorMessages.passwordsDontMatch)
+ throw new Error(errorMessages.passwordsDontMatch);
}
// create the user
return new Promise((resolve, reject) => {
- AuthHasher.hash(password, async function (err, hashed) {
- if (err) reject(err)
+ AuthHasher.hash(password, async function(err, hashed) {
+ if (err) reject(err);
// .salt and .hash
- const passwordToSave = `localauth|${hashed.salt}|${hashed.hash}`
+ const passwordToSave = `localauth|${hashed.salt}|${hashed.hash}`;
const user = await User.save({
email: lowerCaseEmail,
auth0_id: passwordToSave,
@@ -88,98 +81,83 @@ const signup = async ({
last_name: capitalizeWord(reqBody.lastName),
cell: reqBody.cell,
is_superadmin: false
- })
- resolve(user)
- })
- })
-}
+ });
+ resolve(user);
+ });
+ });
+};
-const reset = ({
- password,
- existingUser,
- reqBody,
- uuidMatch
-}) => {
+const reset = ({ password, existingUser, reqBody, uuidMatch }) => {
if (existingUser.length === 0) {
- throw new Error(errorMessages.invalidResetHash)
+ throw new Error(errorMessages.invalidResetHash);
}
// Get user resetHash and date of hash creation
- const pwFieldSplit = existingUser[0].auth0_id.split('|')
- const [resetHash, datetime] = [pwFieldSplit[1], pwFieldSplit[2]]
+ const pwFieldSplit = existingUser[0].auth0_id.split("|");
+ const [resetHash, datetime] = [pwFieldSplit[1], pwFieldSplit[2]];
// Verify hash was created in the last 15 mins
- const isExpired = (Date.now() - datetime) / 1000 / 60 > 15
+ const isExpired = (Date.now() - datetime) / 1000 / 60 > 15;
if (isExpired) {
- throw new Error(errorMessages.invalidResetHash)
+ throw new Error(errorMessages.invalidResetHash);
}
// Verify the UUID in request matches hash in DB
if (uuidMatch[0] !== resetHash) {
- throw new Error(errorMessages.invalidResetHash)
+ throw new Error(errorMessages.invalidResetHash);
}
// Verify passwords match
if (password !== reqBody.passwordConfirm) {
- throw new Error(errorMessages.passwordsDontMatch)
+ throw new Error(errorMessages.passwordsDontMatch);
}
// Save new user password to DB
return new Promise((resolve, reject) => {
- AuthHasher.hash(password, async function (err, hashed) {
- if (err) reject(err)
+ AuthHasher.hash(password, async function(err, hashed) {
+ if (err) reject(err);
// .salt and .hash
- const passwordToSave = `localauth|${hashed.salt}|${hashed.hash}`
- const updatedUser = await User
- .get(existingUser[0].id)
+ const passwordToSave = `localauth|${hashed.salt}|${hashed.hash}`;
+ const updatedUser = await User.get(existingUser[0].id)
.update({ auth0_id: passwordToSave })
- .run()
- resolve(updatedUser)
- })
- })
-}
+ .run();
+ resolve(updatedUser);
+ });
+ });
+};
// Only used in the changeUserPassword GraphQl mutation
-export const change = ({
- user,
- password,
- newPassword,
- passwordConfirm
-}) => {
- const pwFieldSplit = user.auth0_id.split('|')
+export const change = ({ user, password, newPassword, passwordConfirm }) => {
+ const pwFieldSplit = user.auth0_id.split("|");
const hashedPassword = {
salt: pwFieldSplit[1],
hash: pwFieldSplit[2]
- }
+ };
// Verify password and password confirm fields match
if (newPassword !== passwordConfirm) {
- throw new Error(errorMessages.passwordsDontMatch)
+ throw new Error(errorMessages.passwordsDontMatch);
}
// Verify old and new passwords are different
if (password === newPassword) {
- throw new Error(errorMessages.noSamePassword)
+ throw new Error(errorMessages.noSamePassword);
}
return new Promise((resolve, reject) => {
- AuthHasher.verify(
- password, hashedPassword,
- (error, verified) => {
- if (error) return reject(error)
- if (!verified) return reject(errorMessages.invalidCredentials)
- return AuthHasher.hash(newPassword, async function (err, hashed) {
- if (err) reject(err)
- // .salt and .hash
- const passwordToSave = `localauth|${hashed.salt}|${hashed.hash}`
- const updatedUser = await User
- .get(user.id)
- .update({ auth0_id: passwordToSave })
- .run()
- resolve(updatedUser)
- })
- }
- )
- })
-}
-export default { login, signup, reset }
+ AuthHasher.verify(password, hashedPassword, (error, verified) => {
+ if (error) return reject(error);
+ if (!verified) return reject(errorMessages.invalidCredentials);
+ return AuthHasher.hash(newPassword, async function(err, hashed) {
+ if (err) reject(err);
+ // .salt and .hash
+ const passwordToSave = `localauth|${hashed.salt}|${hashed.hash}`;
+ const updatedUser = await User.get(user.id)
+ .update({ auth0_id: passwordToSave })
+ .run();
+ resolve(updatedUser);
+ });
+ });
+ });
+};
+export default { login, signup, reset };
diff --git a/src/server/mail.js b/src/server/mail.js
index 6671b9300..c7e38bc9c 100644
--- a/src/server/mail.js
+++ b/src/server/mail.js
@@ -1,44 +1,46 @@
-import { log } from '../lib'
-import nodemailer from 'nodemailer'
-import mailgunConstructor from 'mailgun-js'
+import { log } from "../lib";
+import nodemailer from "nodemailer";
+import mailgunConstructor from "mailgun-js";
const mailgun =
process.env.MAILGUN_API_KEY &&
process.env.MAILGUN_DOMAIN &&
- mailgunConstructor({ apiKey: process.env.MAILGUN_API_KEY, domain: process.env.MAILGUN_DOMAIN })
+ mailgunConstructor({
+ apiKey: process.env.MAILGUN_API_KEY,
+ domain: process.env.MAILGUN_DOMAIN
+ });
const sender =
process.env.MAILGUN_API_KEY && process.env.MAILGUN_DOMAIN
? {
- sendMail: ({ from, to, subject, replyTo, text }) =>
- mailgun.messages().send(
- {
- from,
- 'h:Reply-To': replyTo,
- to,
- subject,
- text
- })
- }
+ sendMail: ({ from, to, subject, replyTo, text }) =>
+ mailgun.messages().send({
+ from,
+ "h:Reply-To": replyTo,
+ to,
+ subject,
+ text
+ })
+ }
: nodemailer.createTransport({
- host: process.env.EMAIL_HOST,
- port: process.env.EMAIL_HOST_PORT,
- secure:
- typeof process.env.EMAIL_HOST_SECURE !== 'undefined'
+ host: process.env.EMAIL_HOST,
+ port: process.env.EMAIL_HOST_PORT,
+ secure:
+ typeof process.env.EMAIL_HOST_SECURE !== "undefined"
? process.env.EMAIL_HOST_SECURE
: true,
- auth: {
- user: process.env.EMAIL_HOST_USER,
- pass: process.env.EMAIL_HOST_PASSWORD
- }
- })
+ auth: {
+ user: process.env.EMAIL_HOST_USER,
+ pass: process.env.EMAIL_HOST_PASSWORD
+ }
+ });
export const sendEmail = async ({ to, subject, text, replyTo }) => {
- log.info(`Sending e-mail to ${to} with subject ${subject}.`)
+ log.info(`Sending e-mail to ${to} with subject ${subject}.`);
- if (process.env.NODE_ENV === 'development') {
- log.debug(`Would send e-mail with subject ${subject} and text ${text}.`)
- return null
+ if (process.env.NODE_ENV === "development") {
+ log.debug(`Would send e-mail with subject ${subject} and text ${text}.`);
+ return null;
}
const params = {
@@ -46,11 +48,11 @@ export const sendEmail = async ({ to, subject, text, replyTo }) => {
to,
subject,
text
- }
+ };
if (replyTo) {
- params['replyTo'] = replyTo
+ params["replyTo"] = replyTo;
}
- return sender.sendMail(params)
-}
+ return sender.sendMail(params);
+};
diff --git a/src/server/middleware/render-index.js b/src/server/middleware/render-index.js
index a4a914565..0824a56a4 100644
--- a/src/server/middleware/render-index.js
+++ b/src/server/middleware/render-index.js
@@ -1,9 +1,9 @@
-const rollbarScript = process.env.ROLLBAR_CLIENT_TOKEN ?
- `` : ''
+ `
+ : "";
// the site is not very useful without auth0, unless you have a session cookie already
// good for doing dev offline
-const externalLinks = (process.env.NO_EXTERNAL_LINKS ? '' :
- '')
+const externalLinks = process.env.NO_EXTERNAL_LINKS
+ ? ""
+ : '';
export default function renderIndex(html, css, assetMap, store) {
return `
@@ -63,21 +65,22 @@ export default function renderIndex(html, css, assetMap, store) {
window.RENDERED_CLASS_NAMES=${JSON.stringify(css.renderedClassNames)}
window.AUTH0_CLIENT_ID="${process.env.AUTH0_CLIENT_ID}"
window.AUTH0_DOMAIN="${process.env.AUTH0_DOMAIN}"
- window.SUPPRESS_SELF_INVITE="${process.env.SUPPRESS_SELF_INVITE || ''}"
+ window.SUPPRESS_SELF_INVITE="${process.env.SUPPRESS_SELF_INVITE || ""}"
window.NODE_ENV="${process.env.NODE_ENV}"
- window.PRIVACY_URL="${process.env.PRIVACY_URL || ''}"
- window.BASE_URL="${process.env.BASE_URL || ''}"
+ window.PRIVACY_URL="${process.env.PRIVACY_URL || ""}"
+ window.BASE_URL="${process.env.BASE_URL || ""}"
window.NOT_IN_USA=${process.env.NOT_IN_USA || 0}
window.ALLOW_SEND_ALL=${process.env.ALLOW_SEND_ALL || 0}
window.BULK_SEND_CHUNK_SIZE=${process.env.BULK_SEND_CHUNK_SIZE || 0}
window.MAX_MESSAGE_LENGTH=${process.env.MAX_MESSAGE_LENGTH || 99999}
- window.TERMS_REQUIRE="${process.env.TERMS_REQUIRE || ''}"
- window.TZ="${process.env.TZ || ''}"
- window.DST_REFERENCE_TIMEZONE="${process.env.DST_REFERENCE_TIMEZONE || 'America/New_York'}"
- window.PASSPORT_STRATEGY="${process.env.PASSPORT_STRATEGY || ''}"
+ window.TERMS_REQUIRE="${process.env.TERMS_REQUIRE || ""}"
+ window.TZ="${process.env.TZ || ""}"
+ window.DST_REFERENCE_TIMEZONE="${process.env.DST_REFERENCE_TIMEZONE ||
+ "America/New_York"}"
+ window.PASSPORT_STRATEGY="${process.env.PASSPORT_STRATEGY || ""}"
-
+