Skip to content

Commit a12deea

Browse files
committed
Updates to newsletter action
- Fixed send date - Subject line for email - Post update to Slack
1 parent fe04682 commit a12deea

File tree

4 files changed

+73
-6
lines changed

4 files changed

+73
-6
lines changed

.github/actions/render-newsletter/action.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ inputs:
2727
context:
2828
description: 'JSON-encoded context to include when rendering newsletter'
2929
required: false
30+
subject_format:
31+
description: 'Format of the subject, where %s will be replaced by the "title" from the front matter of the post'
32+
require: false
33+
outputs:
34+
send_date:
35+
description: 'ISO 8601-formatted date string'
36+
single_send_url:
37+
description: 'URL of for the new SendGrid single send'
3038

3139
runs:
3240
using: 'node12'

.github/actions/render-newsletter/src/index.ts

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as fs from 'fs';
22
import * as path from 'path';
3+
import { EOL } from 'os';
34
import { promisify } from 'util';
45
import * as gm from 'gray-matter';
56
import * as hb from 'handlebars';
@@ -11,6 +12,17 @@ const readFile = promisify(fs.readFile);
1112
const writeFile = promisify(fs.writeFile);
1213

1314

15+
function escapeData(s: string): string {
16+
return s
17+
.replace(/%/g, '%25')
18+
.replace(/\r/g, '%0D')
19+
.replace(/\n/g, '%0A')
20+
}
21+
22+
function setOutput(key: string, val: string) {
23+
process.stdout.write(`::set-output name=${key}::${escapeData(val)}` + EOL);
24+
}
25+
1426
const API_BASE = 'https://api.sendgrid.com/v3';
1527
type SingleSendParams = {
1628
html: string,
@@ -52,6 +64,7 @@ type Options = {
5264
listId?: string,
5365
suppressionGroupId?: number,
5466
siteYaml?: string,
67+
subject?: string,
5568
};
5669

5770
async function loadTemplate(path?: string, options?: CompileOptions) {
@@ -65,6 +78,7 @@ function splitTitleFromName(basename: string) {
6578
return [m[0], basename.slice(m[0].length)];
6679
}
6780

81+
type PathContext = ReturnType<typeof contextFromPath>;
6882
function contextFromPath(filepath: string) {
6983
const basename = path.basename(filepath);
7084
const [title, ext] = splitTitleFromName(basename);
@@ -87,21 +101,31 @@ function contextFromPath(filepath: string) {
87101
};
88102
}
89103

90-
function postUrl(post: ReturnType<typeof contextFromPath>, site: any) {
104+
function postUrl(post: PathContext, site: any) {
91105
const siteUrl = site.url;
92106
const basePath = site.baseurl ?? '';
93107

94108
return `${siteUrl}${basePath}/${post.year}/${post.month}/${post.day}/${post.slug}.html`;
95109
}
96110

111+
type PostContext = PathContext &
112+
{ url: string } &
113+
{ [k in string]: any };
114+
97115
function postContext(data: any, path: string, site?: any) {
98116
const post = contextFromPath(path);
99117

100118
return Object.assign({ url: site ? postUrl(post, site) : '' },
101-
post, data);
119+
post, data) as PostContext;
102120
}
103121

104-
async function siteContext(path?: string) {
122+
type TemplateContext = {
123+
content: string,
124+
post: PostContext,
125+
site: { [k in string]: any },
126+
} & { [k in string]: any };
127+
128+
async function siteContext(path?: string): Promise<{ [k in string]: any }> {
105129
if (!path)
106130
return {};
107131

@@ -125,7 +149,7 @@ async function render(opts: Options) {
125149
});
126150

127151
const template = await pTemplate;
128-
const context = Object.assign({
152+
const context: TemplateContext = Object.assign({
129153
content: rendered,
130154
post: postContext(data, opts.path, site),
131155
site,
@@ -138,23 +162,41 @@ async function render(opts: Options) {
138162
};
139163
}
140164

165+
function getSendDate(c: TemplateContext) {
166+
let date = c.post.date;
167+
if (date.getTime() <= Date.now()) {
168+
const today = new Date();
169+
date = new Date(today.getFullYear(),
170+
today.getMonth(),
171+
today.getDate()+1);
172+
}
173+
174+
date.setHours(15);
175+
return date;
176+
}
177+
141178
async function run(options: Options) {
142179
const { text, context } = await render(options);
143180
// console.log(context);
144181

145182
if (options.output) {
146183
await writeFile(options.output, text);
147184
} else if (options.apiKey) {
185+
const sendAt = getSendDate(context);
148186
const response = await singleSend({
149187
html: text,
150188
listId: options.listId,
151189
suppressionGroup: options.suppressionGroupId,
152190
token: options.apiKey,
153-
sendAt: new Date(Date.now() + 60000),
154-
subject: `Test Newsletter: ${context.post.title}`,
191+
sendAt,
192+
subject: (options.subject ?? '%s').replace('%s', context.post.title),
155193
});
156194

195+
const url = response.headers.get('location');
157196
console.log(response.status, response.statusText, response.headers, await response.text());
197+
198+
setOutput('send_date', sendAt.toISOString());
199+
setOutput('single_send_url', url);
158200
} else {
159201
console.log(text);
160202
}
@@ -170,6 +212,7 @@ async function runAction() {
170212
INPUT_OUT_PATH: outPath,
171213
INPUT_SUPPRESSION_GROUP_ID: suppressionGroupId,
172214
INPUT_SITE_YAML: siteYaml,
215+
INPUT_SUBJECT_FORMAT: subject = '%s',
173216
} = process.env;
174217

175218
if (!path) {
@@ -188,6 +231,7 @@ async function runAction() {
188231
listId: listId,
189232
suppressionGroupId: suppressionGroupId ? parseInt(suppressionGroupId) : undefined,
190233
siteYaml,
234+
subject,
191235
});
192236
}
193237

.github/workflows/post_to_slack.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/bash
2+
3+
SLACK_URL="$1"
4+
NEWSLETTER_SEND_DATE="$2"
5+
NEWSLETTER_URL="$3"
6+
7+
curl -X POST \
8+
-H 'Content-type: application/json' \
9+
-d '{ "text": "SendGrid single send created for newsletter", "blocks": [{ "type": "section", "text": { "type": "mrkdwn", "text": "Newsletter: <'"$NEWSLETTER_URL"'|Single Send> created." } }] }' \
10+
"$SLACK_URL"

.github/workflows/send-newsletter.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,19 @@ jobs:
1414
id: newsletter-path
1515
- uses: ./.github/actions/render-newsletter
1616
if: ${{ steps.newsletter-path.outputs.post_path != '' }}
17+
id: newsletter
1718
with:
1819
text_path: "${{ steps.newsletter-path.outputs.post_path }}"
1920
template_path: "./.github/workflows/newsletter_template.html"
2021
sendgrid_list_id: "559adb5e-7164-4ac8-bbb5-1398d4ff0df9"
2122
sendgrid_api_key: ${{ secrets.SENDGRID_API_KEY }}
2223
suppression_group_id: 17889
2324
site_yaml: "./_config.yml"
25+
subject_format: "[Code for Boston] %s"
2426
context: >-
2527
{
2628
"domain": "${{ steps.site-domain.outputs.domain }}"
2729
}
30+
- name: Post to slack
31+
run: |
32+
./.github/workflows/post_to_slack.sh "${{ secrets.SLACK_URL }}" "${{ steps.newsletter.send_date }}" "${{ steps.newsletter.url }}"

0 commit comments

Comments
 (0)