Skip to content
This repository was archived by the owner on Sep 3, 2020. It is now read-only.

Commit e27165c

Browse files
author
Cristiano Betta
committed
Flattened history
0 parents  commit e27165c

20 files changed

+6729
-0
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SURVEY_MONKEY_ACCESS_TOKEN=

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# package directories
2+
node_modules
3+
jspm_packages
4+
5+
# Serverless directories
6+
.serverless
7+
8+
# yarn incidentals
9+
yarn-error.log
10+
11+
# secrets
12+
.env

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
8.10

.travis.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
language: node_js
2+
cache: yarn
3+
node_js:
4+
- 8.10
5+
script:
6+
- yarn test
7+
before_deploy:
8+
- npm install serverless -g
9+
- sls config credentials -p aws -k $AWS_KEY -s $AWS_SECRET -o
10+
deploy:
11+
- provider: script
12+
script: yarn deploy:prod
13+
on:
14+
branch: master
15+
- provider: script
16+
script: yarn deploy:dev
17+
on:
18+
branch: develop

README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Serverless Feedback Component for SurveyMonkey
2+
3+
### Prerequisites
4+
5+
Install Node 8.10 and yarn:
6+
7+
```sh
8+
nvm install 8.10
9+
nvm use 8.10
10+
npm install -g yarn
11+
yarn install
12+
```
13+
14+
Install serverless CLI:
15+
16+
```sh
17+
npm install -g serverless
18+
sls login
19+
sls config credentials --provider aws --key [AWS_KEY] --secret [AWS_SECRET] -o
20+
```
21+
22+
### Deployment
23+
24+
#### Production
25+
26+
Production is pushed to AWS using Travis.
27+
28+
#### "Staging"
29+
30+
You can test a staging environment by deploying the dev stage to AWS.
31+
32+
```sh
33+
yarn deploy # deploys to AWS using the DEV stage
34+
```
35+
36+
This will output a URL in the form of:
37+
38+
```sh
39+
https://[ID].execute-api.us-east-1.amazonaws.com/[env]/feedback
40+
```
41+
42+
### Testing locally
43+
44+
This does post to an actual response!
45+
46+
```sh
47+
yarn invoke:local:short # writes a short yes/no response
48+
49+
# for the next two, you will need to update the short.update.json
50+
# and long.json samples with the ID from the response above
51+
yarn invoke:local:update # updates the yes/no response
52+
yarn invoke:local:long # updates the yes/no response with name, email, and note
53+
```

package.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "documentation-feedback-serverless",
3+
"version": "1.0.0",
4+
"description": "Serverless component for our feedback widget",
5+
"main": "handler.js",
6+
"scripts": {
7+
"deploy:dev": "sls deploy -s dev",
8+
"deploy:prod": "sls deploy -s prod",
9+
"deploy": "yarn deploy:dev",
10+
"invoke:short": "sls invoke -f short -p samples/short.json -s dev",
11+
"invoke:update": "sls invoke -f short -p samples/short.update.json -s dev",
12+
"invoke:long": "sls invoke -f long -p samples/long.json -s dev",
13+
"invoke:local:short": "SLS_DEBUG=* sls invoke local -f short -p samples/short.json -s dev",
14+
"invoke:local:update": "SLS_DEBUG=* sls invoke local -f short -p samples/short.update.json -s dev",
15+
"invoke:local:long": "SLS_DEBUG=* sls invoke local -f long -p samples/long.json -s dev",
16+
"test": "jest",
17+
"pretest": "yarn lint",
18+
"lint": "standard"
19+
},
20+
"author": "Cristiano Betta <cbetta@box.com>",
21+
"license": "Apache-2.0",
22+
"devDependencies": {
23+
"jest": "^23.6.0",
24+
"serverless": "^1.32.0",
25+
"standard": "^12.0.1"
26+
},
27+
"dependencies": {
28+
"dotenv": "^6.1.0",
29+
"node-fetch": "^2.2.0"
30+
},
31+
"standard": {
32+
"env": [
33+
"jest"
34+
]
35+
}
36+
}

samples/long.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"useful": true,
3+
"url": "http://google.com",
4+
"name": "Cristiano",
5+
"email": "",
6+
"note": "Test",
7+
"id": "10299904370"
8+
}

samples/short.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"useful": true,
3+
"url": "http://google.com"
4+
}

samples/short.update.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "10299774573",
3+
"useful": false,
4+
"url": "http://google.com"
5+
}

serverless.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
app: documentation
2+
service: feedback
3+
tenant: boxdev
4+
5+
custom:
6+
stage: ${opt:stage, self:provider.stage}
7+
env:
8+
SM_PRODUCTION:
9+
prod: "true"
10+
dev: ""
11+
12+
provider:
13+
name: aws
14+
runtime: nodejs8.10
15+
environment:
16+
SM_ACCESS_TOKEN: ${file(./src/utils/dotenv.js):vars.sm_access_token}
17+
SM_PRODUCTION: ${self:custom.env.SM_PRODUCTION.${self:custom.stage}}
18+
19+
functions:
20+
short:
21+
handler: src/handlers/short.handler
22+
events:
23+
- http:
24+
path: feedback/short
25+
method: post
26+
cors: true
27+
28+
long:
29+
handler: src/handlers/long.handler
30+
events:
31+
- http:
32+
path: feedback/long
33+
method: post
34+
cors: true

src/handlers/long.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const Template = require('../utils/template').default
2+
const HTTP = require('../utils/http').default
3+
4+
class Handler {
5+
constructor () {
6+
this.template = new Template()
7+
this.http = new HTTP()
8+
}
9+
10+
async handle (event) {
11+
let data = event.body ? JSON.parse(event.body) : event
12+
let response = await this.submitLong(data)
13+
14+
return {
15+
statusCode: 201,
16+
body: JSON.stringify({ id: response.id }),
17+
headers: HTTP.corsHeaders()
18+
}
19+
}
20+
21+
async submitLong (data) {
22+
let answer = this.template.renderLong(data)
23+
return this.http.putResponse(data.id, answer)
24+
}
25+
}
26+
27+
exports.default = Handler
28+
exports.handler = (event) => {
29+
return new Handler().handle(event)
30+
}

src/handlers/short.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const Template = require('../utils/template').default
2+
const HTTP = require('../utils/http').default
3+
4+
class Handler {
5+
constructor () {
6+
this.template = new Template()
7+
this.http = new HTTP()
8+
}
9+
10+
async handle (event) {
11+
let data = event.body ? JSON.parse(event.body) : event
12+
let response = data.id
13+
? (await this.updateShort(data))
14+
: (await this.submitShort(data))
15+
16+
return {
17+
statusCode: 201,
18+
body: JSON.stringify({ id: response.id }),
19+
headers: HTTP.corsHeaders()
20+
}
21+
}
22+
23+
// Submits the feedback to SurveyMonkey
24+
async submitShort (data) {
25+
let answer = this.template.renderShort(data)
26+
return this.http.postResponse(answer)
27+
}
28+
29+
// Updates a short feedback to SurveyMonkey
30+
async updateShort (data) {
31+
let answer = this.template.renderShort(data)
32+
return this.http.putResponse(data.id, answer)
33+
}
34+
}
35+
36+
exports.default = Handler
37+
exports.handler = (event) => {
38+
return new Handler().handle(event)
39+
}

src/utils/dotenv.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const dotenv = require('dotenv')
2+
3+
dotenv.config()
4+
5+
module.exports.vars = () => ({
6+
sm_access_token: process.env.SURVEY_MONKEY_ACCESS_TOKEN
7+
})

src/utils/http.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const fetch = require('node-fetch')
2+
3+
const BASE_URL = 'https://api.surveymonkey.com'
4+
5+
exports.default = class HTTP {
6+
constructor () {
7+
this.fetch = fetch
8+
9+
this.SURVEY_COLLECTOR_ID = process.env.SM_PRODUCTION ? '219155553' : '219156108'
10+
this.SURVEY_MONKEY_ACCESS_TOKEN = process.env.SM_ACCESS_TOKEN
11+
}
12+
13+
// POST a new short response
14+
postResponse (data) {
15+
let url = `${BASE_URL}/v3/collectors/${this.SURVEY_COLLECTOR_ID}/responses`
16+
return this.fetch(url, this.options('POST', data))
17+
.then(res => res.json())
18+
.catch(console.error)
19+
}
20+
21+
// PUT an update to a response
22+
putResponse (id, data) {
23+
let url = `${BASE_URL}/v3/collectors/${this.SURVEY_COLLECTOR_ID}/responses/${id}`
24+
return this.fetch(url, this.options('PUT', data))
25+
.then(res => res.json())
26+
.catch(console.error)
27+
}
28+
29+
// Determines the CORS headers for this environment
30+
static corsHeaders () {
31+
let origin = (process.env.SM_PRODUCTION ? 'https://developer.box.com' : '*')
32+
33+
return {
34+
'Access-Control-Allow-Origin': origin,
35+
'Access-Control-Allow-Credentials': true
36+
}
37+
}
38+
39+
// PRIVATE
40+
41+
options (method, data) {
42+
return {
43+
method: method,
44+
mode: 'cors',
45+
headers: {
46+
'Content-Type': 'application/json',
47+
'Authorization': `Bearer ${this.SURVEY_MONKEY_ACCESS_TOKEN}`
48+
},
49+
body: JSON.stringify(data)
50+
}
51+
}
52+
}

src/utils/template.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
exports.default = class Template {
2+
constructor () {
3+
this.responseTemplate = {
4+
pages: [
5+
{
6+
id: (process.env.SM_PRODUCTION ? '47128554' : '47129534'),
7+
questions: [
8+
// URL
9+
{ id: (process.env.SM_PRODUCTION ? '164337216' : '164341787'), answers: [{}] },
10+
// yes/no
11+
{ id: (process.env.SM_PRODUCTION ? '' : '164341788'), answers: [{}] }
12+
]
13+
}
14+
]
15+
}
16+
17+
this.nameTemplate = { id: (process.env.SM_PRODUCTION ? '164337973' : '164341789'), answers: [{}] }
18+
this.emailTemplate = { id: (process.env.SM_PRODUCTION ? '164338049' : '164341790'), answers: [{}] }
19+
this.noteTemplate = { id: (process.env.SM_PRODUCTION ? '164338156' : '164341791'), answers: [{}] }
20+
21+
// The ID's of the answers to the question "Was this page useful?"
22+
this.usefulAnswerID = (process.env.SM_PRODUCTION ? '1155084831' : '1155115696')
23+
this.notUsefulAnswerID = (process.env.SM_PRODUCTION ? '1155084834' : '1155115697')
24+
}
25+
26+
// Turns a simple feedback into a SurveyMonkey
27+
// compatible format
28+
renderShort (feedback) {
29+
this.responseTemplate.response_status = 'partial'
30+
// Extract the URL
31+
this.responseTemplate.pages[0].questions[0].answers[0].text = feedback.url
32+
// Extract if the feedback was positive
33+
this.responseTemplate.pages[0].questions[1].answers[0].choice_id = feedback.useful
34+
? this.usefulAnswerID
35+
: this.notUsefulAnswerID
36+
return this.responseTemplate
37+
}
38+
39+
renderLong (feedback) {
40+
this.responseTemplate.response_status = 'completed'
41+
// Extract the URL
42+
this.responseTemplate.pages[0].questions[0].answers[0].text = feedback.url
43+
// Extract if the feedback was positive
44+
this.responseTemplate.pages[0].questions[1].answers[0].choice_id = feedback.useful
45+
? this.usefulAnswerID
46+
: this.notUsefulAnswerID
47+
48+
// Extract the email
49+
if (feedback.email) {
50+
this.emailTemplate.answers[0].text = feedback.email
51+
this.responseTemplate.pages[0].questions.push(this.emailTemplate)
52+
}
53+
// Extract the name
54+
if (feedback.name) {
55+
this.nameTemplate.answers[0].text = feedback.name
56+
this.responseTemplate.pages[0].questions.push(this.nameTemplate)
57+
}
58+
59+
// Extract the note
60+
this.noteTemplate.answers[0].text = feedback.note
61+
this.responseTemplate.pages[0].questions.push(this.noteTemplate)
62+
return this.responseTemplate
63+
}
64+
}

0 commit comments

Comments
 (0)