Skip to content

Commit 695d347

Browse files
committed
GitHub Actions: Render newsletter (updates)
1 parent 79d27f8 commit 695d347

File tree

4 files changed

+127
-26
lines changed

4 files changed

+127
-26
lines changed

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ description: Render the newsletter
33
inputs:
44
text_path:
55
description: 'Path to Markdown file containing the post body'
6-
required: true
6+
required: false
7+
posts_dir:
8+
description: 'Path to directory containing posts'
9+
required: false
10+
711
out_path:
812
description: 'Path where the output should be written'
913
required: false
@@ -30,11 +34,15 @@ inputs:
3034
subject_format:
3135
description: 'Format of the subject, where %s will be replaced by the "title" from the front matter of the post'
3236
require: false
37+
slack_url:
38+
description: 'Post link to SendGrid single send'
39+
require: false
40+
3341
outputs:
3442
send_date:
3543
description: 'ISO 8601-formatted date string'
3644
single_send_url:
37-
description: 'URL of for the new SendGrid single send'
45+
description: 'URL of the new SendGrid single send'
3846

3947
runs:
4048
using: 'node12'

.github/actions/render-newsletter/dist/index.js

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 108 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import fetch from 'node-fetch';
1010

1111
const readFile = promisify(fs.readFile);
1212
const writeFile = promisify(fs.writeFile);
13+
const readdir = promisify(fs.readdir);
1314

1415

1516
function escapeData(s: string): string {
@@ -23,6 +24,27 @@ function setOutput(key: string, val: string) {
2324
process.stdout.write(`::set-output name=${key}::${escapeData(val)}${EOL}`);
2425
}
2526

27+
async function postToSlack(slackUrl: string, url: string) {
28+
await fetch(slackUrl, {
29+
method: 'POST',
30+
headers: {
31+
'Content-type': 'application/json',
32+
},
33+
body: JSON.stringify({
34+
text: "SendGrid single send created for newsletter",
35+
blocks: [
36+
{
37+
type: "section",
38+
text: {
39+
type: "mrkdwn",
40+
text: `Newsletter: <${url}|Single Send> created.`
41+
}
42+
}
43+
]
44+
})
45+
});
46+
}
47+
2648
const API_BASE = 'https://api.sendgrid.com/v3';
2749
type SingleSendParams = {
2850
html: string,
@@ -40,7 +62,7 @@ async function singleSend(params: SingleSendParams) {
4062
'Content-type': 'application/json',
4163
},
4264
body: JSON.stringify({
43-
name: 'Test Send',
65+
name: `Newsletter: ${params.subject}`,
4466
send_at: params.sendAt?.toISOString(),
4567
send_to: {
4668
list_ids: [params.listId]
@@ -54,17 +76,25 @@ async function singleSend(params: SingleSendParams) {
5476
});
5577
}
5678

79+
type GetSingleSendsParams = {
80+
81+
};
82+
83+
async function getSingleSends(params: GetSingleSendsParams) {
84+
85+
}
5786

5887
type Options = {
5988
apiKey?: string,
60-
path: string,
89+
filePath: string,
6190
output?: string,
6291
template?: string,
6392
context?: any,
6493
listId?: string,
6594
suppressionGroupId?: number,
6695
siteYaml?: string,
6796
subject?: string,
97+
slackUrl?: string,
6898
};
6999

70100
async function loadTemplate(path?: string, options?: CompileOptions) {
@@ -134,7 +164,7 @@ async function siteContext(path?: string): Promise<{ [k in string]: any }> {
134164
}
135165

136166
async function render(opts: Options) {
137-
const pFile = readFile(opts.path);
167+
const pFile = readFile(opts.filePath);
138168
const pTemplate = loadTemplate(opts.template);
139169
const site = await siteContext(opts.siteYaml);
140170

@@ -151,7 +181,7 @@ async function render(opts: Options) {
151181
const template = await pTemplate;
152182
const context: TemplateContext = Object.assign({
153183
content: rendered,
154-
post: postContext(data, opts.path, site),
184+
post: postContext(data, opts.filePath, site),
155185
site,
156186
}, opts.context);
157187
const text = template(context);
@@ -197,11 +227,65 @@ async function run(options: Options) {
197227

198228
setOutput('send_date', sendAt.toISOString());
199229
setOutput('single_send_url', url);
230+
231+
return {
232+
sendAt,
233+
url
234+
};
200235
} else {
201236
console.log(text);
202237
}
203238
}
204239

240+
type PathFilter = (path: string) => boolean;
241+
type RunOptions = Omit<Options, 'filePath'> & {
242+
source: { file: string } | { dir: string },
243+
filter?: PathFilter,
244+
};
245+
246+
function dateFilter(after: number) {
247+
return (path: string) => {
248+
const ctx = contextFromPath(path);
249+
return ctx.date.getTime() > after;
250+
};
251+
}
252+
253+
async function toFileList(source: RunOptions['source'], filter?: PathFilter) {
254+
if ('file' in source)
255+
return [source.file];
256+
257+
const paths = await readdir(source.dir)
258+
.then(names => names.map(n => path.join(source.dir, n)));
259+
260+
return filter ?
261+
paths.filter(filter) : paths;
262+
}
263+
264+
async function runAll(options: RunOptions) {
265+
const posts = await toFileList(options.source, options.filter);
266+
const urls: string[] = [];
267+
const promises: Promise<any>[] = [];
268+
269+
if (!posts.length) {
270+
console.log('No posts to send.');
271+
return;
272+
}
273+
274+
for (const post of posts) {
275+
const result = await run({
276+
...options,
277+
filePath: post
278+
});
279+
280+
if (result?.url && options.slackUrl) {
281+
promises.push(postToSlack(options.slackUrl, result.url));
282+
}
283+
urls.push(result.url);
284+
}
285+
286+
await Promise.all(urls);
287+
}
288+
205289
async function runAction() {
206290
const {
207291
INPUT_SENDGRID_LIST_ID: listId,
@@ -213,38 +297,52 @@ async function runAction() {
213297
INPUT_SUPPRESSION_GROUP_ID: suppressionGroupId,
214298
INPUT_SITE_YAML: siteYaml,
215299
INPUT_SUBJECT_FORMAT: subject = '%s',
300+
INPUT_POSTS_DIR: postsDir,
301+
INPUT_SLACK_URL: slackUrl,
302+
TODAY_OVERRIDE: today,
216303
} = process.env;
217304

218-
if (!path) {
305+
if (!(path || postsDir)) {
219306
console.error(
220-
'Missing required environment variable INPUT_TEXT_PATH'
307+
'Either INPUT_TEXT_PATH or INPUT_POSTS_DIR must be non-empty'
221308
);
222309
process.exit(1);
223310
}
224311

225-
await run({
312+
await runAll({
226313
apiKey,
227-
path,
314+
source: path ? { file: path } : { dir: postsDir },
228315
template,
229316
output: outPath,
230317
context: context ? JSON.parse(context) : {},
231318
listId: listId,
232319
suppressionGroupId: suppressionGroupId ? parseInt(suppressionGroupId) : undefined,
233320
siteYaml,
234321
subject,
322+
slackUrl,
323+
filter: dateFilter(today ? new Date(today).getTime() : Date.now()),
235324
});
236325
}
237326

238327
async function testRun() {
328+
// const apiKey = 'REAS-yuff0naum!krar';
239329
process.env['INPUT_SENDGRID_LIST_ID'] = "559adb5e-7164-4ac8-bbb5-1398d4ff0df9";
240330
// process.env['INPUT_SENDGRID_API_KEY'] = apiKey;
241-
process.env['INPUT_TEXT_PATH'] = __dirname + '/../../../../_posts/2021-11-16-communications-lead.md';
331+
// process.env['INPUT_TEXT_PATH'] = __dirname + '/../../../../_posts/2021-11-16-communications-lead.md';
332+
process.env['INPUT_POSTS_DIR'] = __dirname + '/../../../../_posts';
242333
process.env['INPUT_TEMPLATE_PATH'] = __dirname + '/../../../workflows/newsletter_template.html';
243334
process.env['INPUT_CONTEXT'] = `{}`;
244335
process.env['INPUT_SUPPRESSION_GROUP_ID'] = '17889';
245336
process.env['INPUT_SITE_YAML'] = __dirname + '/../../../../_config.yml';
337+
process.env['INPUT_SLACK_URL'] = 'https://hooks.slack.com/services/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';
338+
process.env['TODAY_OVERRIDE'] = '2022-01-10';
246339

247340
await runAction();
248341
}
249342

250-
runAction();
343+
344+
if (process.env['NODE_ENV'] === 'test')
345+
testRun();
346+
else
347+
runAction();
348+
Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
name: send-newsletter
2-
on: push
2+
on:
3+
push:
4+
branches:
5+
- master
36
jobs:
47
send-newsletter:
58
runs-on: ubuntu-latest
@@ -9,27 +12,19 @@ jobs:
912
- run: ./.github/workflows/get_domain.sh
1013
shell: bash
1114
id: site-domain
12-
- name: Find new post
13-
id: newsletter-path
14-
run: ./.github/workflows/find-modified-newsletter.sh
15-
shell: bash
1615
- name: Render newsletter
17-
if: ${{ steps.newsletter-path.outputs.post_path != '' }}
1816
id: newsletter
1917
uses: ./.github/actions/render-newsletter
2018
with:
21-
text_path: "${{ steps.newsletter-path.outputs.post_path }}"
19+
posts_dir: "./_posts"
2220
template_path: "./.github/workflows/newsletter_template.html"
2321
sendgrid_list_id: "559adb5e-7164-4ac8-bbb5-1398d4ff0df9"
2422
sendgrid_api_key: ${{ secrets.SENDGRID_API_KEY }}
2523
suppression_group_id: 17889
2624
site_yaml: "./_config.yml"
2725
subject_format: "[Code for Boston] %s"
26+
slack_url: ${{ secrets.SLACK_URL }}
2827
context: >-
2928
{
3029
"domain": "${{ steps.site-domain.outputs.domain }}"
3130
}
32-
- name: Post to slack
33-
if: ${{ steps.newsletter.outputs.url != '' }}
34-
run: |
35-
./.github/workflows/post_to_slack.sh "${{ secrets.SLACK_URL }}" "${{ steps.newsletter.outputs.send_date }}" "${{ steps.newsletter.outputs.url }}"

0 commit comments

Comments
 (0)