Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Share email #9

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,5 @@ node_modules/

# dotenv environment variables file
.env

cli/dist
Empty file added cli/dist/.gitkeep
Empty file.
20 changes: 20 additions & 0 deletions cli/emailPreview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const Handlebars = require('handlebars')
const { readFileSync, writeFile } = require('fs')
const open = require('open')
const { safeLoad } = require('js-yaml')

const templateName = process.argv[2]
const template = readFileSync(`../functions/templates/${templateName}.hbs`).toString()
const fixture = {
...safeLoad(readFileSync(`./fixtures/${templateName}.yml`)),
translations: safeLoad(readFileSync(`../functions/locales/server/en.yml`)).templates[templateName]
}

Handlebars.registerPartial('cadence', readFileSync('../functions/templates/cadence.hbs').toString())
Handlebars.registerPartial('shareStyles', readFileSync('../functions/templates/shareStyles.hbs').toString())

writeFile(
`./dist/${templateName}.html`,
Handlebars.compile(template)(fixture),
() => open(`dist/${templateName}.html`)
)
File renamed without changes.
47 changes: 47 additions & 0 deletions cli/fixtures/share.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
stats:
- figure: 8
description: Total Scheduled Ceremonies
- figure: 2
description: Average People Per Meeting
- figure: 5
description: Average Ceremonies Per Week
cadences:
sprint:
- id: wednesday-1
name: First Wednesday
ceremonies:
- id: planning
title: Sprint Planning
icon: '👓'
startTime: 10:00am
notes: Rotating team member to bring donuts
general:
- id: daily
name: Daily
ceremonies:
- id: checkin
title: Check in
icon: '🔗'
async: true
notes: We'll do a synchronous stand-up in Slack, with one person bringing a 'Question of the Day'
- id: quarterly
name: Quarterly
ceremonies:
- id: documentation
title: Documentation
icon: '📒'
async: true
people:
- initial: J
name: James
- initial: A
name: Ashlyn
- id: hackathon
title: Hackathon
icon: '🍕'
notes: To be led by the Team leads to gather ideas, we'll take 2 days to spike out some features
async: false
startTime: 9:00am
people:
- initial: J
name: James
16 changes: 16 additions & 0 deletions cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "cli",
"description": "Testing CLI for Minimum Viable Ceremonies",
"engines": {
"node": "10"
},
"scripts": {
"preview": "node ./emailPreview.js"
},
"private": true,
"dependencies": {
"handlebars": "^4.7.6",
"js-yaml": "^3.14.0",
"open": "^7.3.0"
}
}
85 changes: 85 additions & 0 deletions cli/yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
dependencies:
sprintf-js "~1.0.2"

esprima@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==

handlebars@^4.7.6:
version "4.7.6"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e"
integrity sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==
dependencies:
minimist "^1.2.5"
neo-async "^2.6.0"
source-map "^0.6.1"
wordwrap "^1.0.0"
optionalDependencies:
uglify-js "^3.1.4"

is-docker@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156"
integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==

is-wsl@^2.1.1:
version "2.2.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
dependencies:
is-docker "^2.0.0"

js-yaml@^3.14.0:
version "3.14.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"

minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==

neo-async@^2.6.0:
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==

open@^7.3.0:
version "7.3.0"
resolved "https://registry.yarnpkg.com/open/-/open-7.3.0.tgz#45461fdee46444f3645b6e14eb3ca94b82e1be69"
integrity sha512-mgLwQIx2F/ye9SmbrUkurZCnkoXyXyu9EbHtJZrICjVAJfyMArdHp3KkixGdZx1ZHFPNIwl0DDM1dFFqXbTLZw==
dependencies:
is-docker "^2.0.0"
is-wsl "^2.1.1"

source-map@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==

sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=

uglify-js@^3.1.4:
version "3.11.4"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.11.4.tgz#b47b7ae99d4bd1dca65b53aaa69caa0909e6fadf"
integrity sha512-FyYnoxVL1D6+jDGQpbK5jW6y/2JlVfRfEeQ67BPCUg5wfCjaKOpr2XeceE4QL+MkhxliLtf5EbrMDZgzpt2CNw==

wordwrap@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
42 changes: 42 additions & 0 deletions functions/common.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
const { database } = require('firebase-admin')
const { https } = require('firebase-functions')
const Handlebars = require('handlebars')
const { readFileSync } = require('fs')
const phrase = require('random-words')
const setLanguage = require('./locales/node')
const cors = require('cors')
const fs = require('fs')

Handlebars.registerPartial('cadence', fs.readFileSync('./templates/cadence.hbs').toString())
Handlebars.registerPartial('shareStyles', fs.readFileSync('./templates/shareStyles.hbs').toString())

exports.endpoint = (origin, fn) =>
https.onRequest((req, res) =>
setLanguage(req).then(t =>
Expand Down Expand Up @@ -38,6 +43,43 @@ exports.createRoom = ({
return { uuid, name, features, weekCount, ceremonies }
}

exports.snapshotToShare = (snapshot, t) => {
const { ceremonies = [] } = snapshot.toJSON() || {}
const cadences = Object.values(ceremonies).reduce((result, ceremony) => {
if (['void', 'undecided'].includes(ceremony.placement)) { return result }
result[ceremony.placement] = result[ceremony.placement] || {
id: ceremony.placement,
name: t(`app:cadences.${ceremony.placement}.name`),
ceremonies: []
}
result[ceremony.placement].ceremonies.unshift({
...ceremony,
title: ceremony.title || t(`app:ceremonies.${ceremony.id}.name`),
pill: (
(ceremony.async && t('templates.share.async')) ||
(ceremony.startTime && dayjs().set('hour', 0).set('minute', ceremony.startTime).format('H:mm a'))
),
people: Object.values(ceremony.people || [])
.filter(person => person.label)
.map(({ label }) => ({ initial: label[0], name: label }))
})
return result
}, [])

return {
cadences,
stats: {
// TODO: calculate stats
ceremonies: 6,
people: 2,
weekly: 4.3
}
}
}

exports.compileTemplate = (template, data) =>
Handlebars.compile(readFileSync(`./templates/${template}.hbs`).toString())(data)

const setResponse = (req, res, fn, t) =>
Promise.resolve(fn(req, t))
.then(([status, body]) => status === 302
Expand Down
2 changes: 1 addition & 1 deletion functions/locales
Submodule locales updated 2 files
+4 −2 node.js
+1 −1 server/en.yml
3 changes: 2 additions & 1 deletion functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
"accept-language-parser": "^1.5.0",
"axios": "^0.20.0",
"crypto": "^1.0.1",
"dayjs": "^1.9.5",
"firebase-admin": "^8.10.0",
"firebase-functions": "^3.11.0",
"handlebars": "^4.7.6",
"i18next": "^19.8.2",
"i18next-fs-backend": "^1.0.7",
"ical-generator": "^1.12.1",
"moment": "^2.27.0",
"querystring": "^0.2.0",
"random-words": "^1.1.1",
"tsscmp": "^1.0.6"
Expand Down
3 changes: 2 additions & 1 deletion functions/sendgrid/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ const { config } = require('firebase-functions')
const { endpoint } = require('../common')

module.exports = {
subscribe: endpoint(config().marketing.cors_origin, require('./subscribe'))
subscribe: endpoint(config().marketing.cors_origin, require('./subscribe')),
share: endpoint(config().app.cors_origin, require('./share'))
}
34 changes: 34 additions & 0 deletions functions/sendgrid/share.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const axios = require('axios')
const i18n = require('i18next')
const { compileTemplate, snapshotToShare } = require('../common')
const { database } = require('firebase-admin')
const { config } = require('firebase-functions')
const dayjs = require('dayjs')

module.exports = (req, t) =>
i18n.loadNamespaces('app').then(() =>
database().ref(`/rooms/${req.body.uuid}`).once('value')
.then(snapshot =>
axios.post('https://api.sendgrid.com/v3/mail/send', {
from: { email: "[email protected]", name: "Minimum Viable Ceremonies" },
personalizations: [{
to: [{ email: req.body.recipients || '[email protected]' }],
subject: t('templates.share.title'),
}],
content: [{
type: "text/html",
value: compileTemplate('share', {
...snapshotToShare(snapshot, t),
translations: {
...t('templates.share', { returnObjects: true }),
footer: t('templates.share.footer', { name: (snapshot.toJSON() || {}).name }),
roomUrl: `${config().app.cors_origin}/room/${req.body.uuid}`
}
})
}]
}, {
headers: {
'Authorization': `Bearer ${config().sendgrid.api_key}`
}
}).then(() => [200, { status: t('common.messages.200') }])
.catch(({ request, response }) => [response.status, response.data])))
2 changes: 1 addition & 1 deletion functions/slack/common.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const crypto = require('crypto')
const tsscmp = require('tsscmp')
const { config } = require('firebase-admin')
const { config } = require('firebase-functions')

exports.returnMessage = text =>
{ blocks: [{ type: 'section', text: { type: 'mrkdwn', text } }] }
Expand Down
5 changes: 4 additions & 1 deletion functions/slack/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@ const { endpoint } = require('../common')

module.exports = {
authorize: endpoint(config().marketing.cors_origin, require('./authorize')),
create: endpoint(config().slack.cors_origin, require('./create'))
create: endpoint(config().slack.cors_origin, require('./create')),
share: endpoint(config().app.cors_origin, require('./share')),
join: endpoint(config().app.cors_origin, require('./join')),
list: endpoint(config().app.cors_origin, require('./list')),
}
15 changes: 15 additions & 0 deletions functions/slack/join.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const axios = require('axios')
const i18n = require('i18next')
const { config } = require('firebase-functions')

module.exports = (req, t) =>
axios.post('https://slack.com/api/conversations.join', {
channel: req.body.channel
}, {
headers: { 'Authorization': `Bearer ${config().slack.access_token}` }
}).catch(error => [400, t('slack.errors.400', { errors: error })])
.then(({ data: { ok, error, conversations } }) =>
ok
? [200, { status: t('common.messages.200') }]
: [400, { status: t('slack.errors.400', { errors: error }) }]
)
20 changes: 20 additions & 0 deletions functions/slack/list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const axios = require('axios')
const i18n = require('i18next')
const { config } = require('firebase-functions')

module.exports = (req, t) =>
axios.post('https://slack.com/api/conversations.list', {}, {
headers: { 'Authorization': `Bearer ${config().slack.access_token}` }
}).catch(error => [400, t('slack.errors.400', { errors: error })])
.then(({ data: { ok, error, channels } }) =>
ok
? [200, { channels: transformChannels(channels) }]
: [400, { status: t('slack.errors.400', { errors: error }) }])

const transformChannels = channels =>
channels
.filter(channel => !channel.is_archived)
.map(channel => ({
id: channel.id,
name: channel.name,
}))
Loading