Skip to content

Commit

Permalink
Merge branch 'moveon_main_12_2' into wfp_staging_20180902
Browse files Browse the repository at this point in the history
  • Loading branch information
lperson committed Dec 2, 2018
2 parents 0422681 + 6ee1b07 commit 7598a7a
Show file tree
Hide file tree
Showing 49 changed files with 1,001 additions and 169 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -8,7 +8,9 @@ __test__/test_data/*
production-env.json
claudia.json
stage.json
beta.json
stage-env.json
beta-env.json
test.sqlite
*~
*#
Expand Down
8 changes: 1 addition & 7 deletions .travis.yml
Expand Up @@ -14,13 +14,7 @@ before_script:
- psql -c 'CREATE DATABASE spoke_test;' -U postgres
- psql -c "CREATE USER spoke_test WITH PASSWORD 'spoke_test';" -U postgres
- psql -c 'GRANT ALL PRIVILEGES ON DATABASE spoke_test TO spoke_test;' -U postgres
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
- chmod +x ./cc-test-reporter
- ./cc-test-reporter before-build
- chmod +x ./travis-run-e2e-tests
script:
- npm run test-coverage
- CC_TEST_RESULT=$?
- npm run test
- ./travis-run-e2e-tests
after_script:
- ./cc-test-reporter after-build --exit-code $CC_TEST_RESULT
97 changes: 97 additions & 0 deletions __test__/e2e/page-functions/campaigns.js
Expand Up @@ -115,5 +115,102 @@ export const campaigns = {
// Validate Started
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('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('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'))
})

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()
})
})
},
editCampaign(driver, campaign) {
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 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 () => {
// Expand Basics section
await wait.andClick(driver, form.basics.section)
// Change Title
await wait.andType(driver, form.basics.title, campaign.basics.title_changed, { clear: false })
// Save
await wait.andClick(driver, form.save)
})

it('reopens the Basics section to verify title', async () => {
// Expand Basics section
await wait.andClick(driver, form.basics.section, { waitAfterVisible: 2000 })
// Verify Title
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', () => {
_.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))
})
})
})
}
}
4 changes: 4 additions & 0 deletions __test__/e2e/page-objects/campaigns.js
Expand Up @@ -3,6 +3,10 @@ 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}]`) },
form: {
basics: {
title: By.css('[data-test=title]'),
Expand Down
2 changes: 2 additions & 0 deletions docs/REFERENCE-environment_variables.md
Expand Up @@ -73,3 +73,5 @@ WAREHOUSE_DB_{TYPE,HOST,PORT,NAME,USER,PASSWORD} | Enables ability to load con
WAREHOUSE_DB_LAMBDA_ITERATION | If the WAREHOUSE_DB_ connection/feature is enabled, then on AWS Lambda, queries that take longer than 5min can expire. This will enable incrementing through queries on new lambda invocations to avoid timeouts.
WEBPACK_HOST | Host domain or IP for Webpack development server. _Default_: 127.0.0.1.
WEBPACK_PORT | Port for Webpack development server. _Defaut_: 3000.
FIX_ORGLESS | Set to any truthy value only if you want to run the job that automatically assigns the default org (see DEFAULT_ORG) to new users who have no assigned org.
DEFAULT_ORG | Set only with FIX_ORGLESS. Set to integer organization.id corresponding to the organization you want orgless users to be assigned to.
3 changes: 2 additions & 1 deletion jest.config.e2e.js
Expand Up @@ -4,7 +4,8 @@ const config = require('./jest.config')
const overrides = {
setupTestFrameworkScriptFile: '<rootDir>/__test__/e2e/util/setup.js',
testMatch: ['**/__test__/e2e/**/*.test.js'],
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/__test__/e2e/util/', '<rootDir>/__test__/e2e/pom/']
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/__test__/e2e/util/', '<rootDir>/__test__/e2e/pom/'],
bail: true // To learn about errors sooner
}
const merges = {
// Merge in changes to deeper objects
Expand Down
12 changes: 12 additions & 0 deletions lambda.js
Expand Up @@ -11,6 +11,17 @@ const jobs = require('./build/server/workers/job-processes')
// See: http://docs.aws.amazon.com/lambda/latest/dg/best-practices.html#function-code
// "Separate the Lambda handler (entry point) from your core logic"

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']
}
if (event.multiValueHeaders) {
delete event.multiValueHeaders['X-Twilio-Body']
}
}

exports.handler = (event, context, handleCallback) => {
// Note: When lambda is called with invoke() we MUST call handleCallback with a success
// or Lambda will re-run/re-try the invocation twice:
Expand All @@ -21,6 +32,7 @@ exports.handler = (event, context, handleCallback) => {
if (!event.command) {
// default web server stuff
const startTime = (context.getRemainingTimeInMillis ? context.getRemainingTimeInMillis() : 0)
cleanHeaders(event)
const webResponse = awsServerlessExpress.proxy(server, event, context)
if (process.env.DEBUG_SCALING) {
const endTime = (context.getRemainingTimeInMillis ? context.getRemainingTimeInMillis() : 0)
Expand Down
8 changes: 4 additions & 4 deletions package.json
Expand Up @@ -8,11 +8,11 @@
"npm": "3.10.10"
},
"scripts": {
"test": "jest",
"test": "jest --runInBand --forceExit",
"test-e2e": "jest --runInBand --config jest.config.e2e.js",
"test-sqlite": "jest --config jest.config.sqlite.js",
"test-sqlite": "jest --runInBand --config jest.config.sqlite.js",
"test-coverage": "jest --coverage",
"test-coverage-bothbackends": "jest --config jest.config.sqlite.js -- __test__/backend.test.js && jest --coverage",
"test-coverage-bothbackends": "jest --runInBand --config jest.config.sqlite.js -- __test__/backend.test.js && jest --coverage",
"clean": "rm -rf $OUTPUT_DIR",
"lint": "eslint --fix --ext js --ext jsx src",
"prod-build-client": "webpack --config ./webpack/config.js",
Expand Down Expand Up @@ -134,7 +134,7 @@
"redux": "^3.7.2",
"redux-thunk": "^2.1.0",
"request": "^2.81.0",
"rethink-knex-adapter": "^0.4.14",
"rethink-knex-adapter": "^0.4.17",
"rollbar": "^0.6.2",
"thinky": "^2.3.3",
"timezonecomplete": "^5.5.0",
Expand Down
1 change: 1 addition & 0 deletions src/api/campaign.js
Expand Up @@ -34,6 +34,7 @@ export const schema = `
contacts: [CampaignContact]
contactsCount: Int
hasUnassignedContacts: Boolean
hasUnsentInitialMessages: Boolean
customFields: [String]
cannedResponses(userId: String): [CannedResponse]
stats: CampaignStats,
Expand Down
2 changes: 1 addition & 1 deletion src/api/organization.js
Expand Up @@ -4,7 +4,7 @@ export const schema = `
uuid: String
name: String
campaigns(cursor:OffsetLimitCursor, campaignsFilter: CampaignsFilter): CampaignsReturn
people(role: String): [User]
people(role: String, campaignId: String): [User]
optOuts: [OptOut]
threeClickEnabled: Boolean
textingHoursEnforced: Boolean
Expand Down
7 changes: 3 additions & 4 deletions src/api/schema.js
Expand Up @@ -127,7 +127,7 @@ const rootSchema = `
message: MessageInput!
campaignContactId: String!
}
input OffsetLimitCursor {
offset: Int!
limit: Int!
Expand All @@ -153,7 +153,7 @@ const rootSchema = `
type FoundContact {
found: Boolean
}
type PageInfo {
limit: Int!
offset: Int!
Expand Down Expand Up @@ -190,7 +190,7 @@ const rootSchema = `
createCannedResponse(cannedResponse:CannedResponseInput!): CannedResponse
createOrganization(name: String!, userId: String!, inviteId: String!): Organization
joinOrganization(organizationUuid: String!): Organization
editOrganizationRoles(organizationId: String!, userId: String!, roles: [String]): Organization
editOrganizationRoles(organizationId: String!, userId: String!, campaignId: String, roles: [String]): Organization
editUser(organizationId: String!, userId: Int!, userData:UserInput): User
updateTextingHours( organizationId: String!, textingHoursStart: Int!, textingHoursEnd: Int!): Organization
updateTextingHoursEnforcement( organizationId: String!, textingHoursEnforced: Boolean!): Organization
Expand Down Expand Up @@ -236,4 +236,3 @@ export const schema = [
inviteSchema,
conversationSchema
]

4 changes: 3 additions & 1 deletion src/components/CampaignContactsForm.jsx
Expand Up @@ -74,7 +74,9 @@ export default class CampaignContactsForm extends React.Component {
errors.push('Do not include a trailing (or any) ";"')
}
if (!errors.length) {
this.setState({ contactSqlError: null })
this.setState({
contactSqlError: null
})
this.props.onChange({
contactSql: sql
})
Expand Down
6 changes: 3 additions & 3 deletions src/components/CampaignTextingHoursForm.jsx
Expand Up @@ -158,8 +158,8 @@ export default class CampaignTextingHoursForm extends React.Component {
onSubmit={this.props.onSubmit}
>
<CampaignFormSectionHeading
title="Texting hours for campaign"
subtitle="You can use the texting-hours configuration for your organization, or configure texting hours for this campaign."
title='Texting hours for campaign'
subtitle='You can use the texting-hours configuration for your organization, or configure texting hours for this campaign.'
/>

{this.addToggleFormField(
Expand Down Expand Up @@ -215,7 +215,7 @@ export default class CampaignTextingHoursForm extends React.Component {
)}

<Form.Button
type="submit"
type='submit'
disabled={this.props.saveDisabled}
label={this.props.saveLabel}
/>
Expand Down

0 comments on commit 7598a7a

Please sign in to comment.