Skip to content

Commit c10d066

Browse files
committed
Added a send_at option to render-newsletter config
1 parent 3129144 commit c10d066

File tree

5 files changed

+81
-9
lines changed

5 files changed

+81
-9
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ inputs:
1111
after_date:
1212
description: 'Only render posts that have a send date >= this ISO 8601-formatted date'
1313
required: false
14+
send_at:
15+
description: 'Time of day to send the newsletter, formatted as HH:MM+ZZZZ'
16+
required: false
1417

1518
out_path:
1619
description: 'Path where the output should be written'

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

Lines changed: 2 additions & 2 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: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import fetch from 'node-fetch';
1010

1111
import * as SG from './sendgrid';
1212

13+
import { setTime, parseTimeSpec, TimeSpec } from './util';
14+
1315
const readFile = promisify(fs.readFile);
1416
const writeFile = promisify(fs.writeFile);
1517
const readdir = promisify(fs.readdir);
@@ -60,6 +62,7 @@ type Options = {
6062
subject?: string,
6163
slackUrl?: string,
6264
index?: SG.SingleSendIndex,
65+
sendAt?: TimeSpec,
6366
};
6467

6568
async function loadTemplate(path?: string, options?: CompileOptions) {
@@ -160,7 +163,7 @@ async function render(opts: Options) {
160163
const dateStr =
161164
(d: Date | string) => ((typeof d === 'string' ? d : d.toISOString()).split('T', 1)[0]);
162165

163-
function getSendDate(c: TemplateContext) {
166+
function getSendDate(c: TemplateContext, timeSpec?: TimeSpec) {
164167
let date = c.post.date;
165168
if (date.getTime() <= Date.now()) {
166169
const today = new Date();
@@ -169,8 +172,7 @@ function getSendDate(c: TemplateContext) {
169172
today.getDate()+1);
170173
}
171174

172-
date.setHours(15);
173-
return date;
175+
return setTime(date, timeSpec)
174176
}
175177

176178
function singleSendId(context: TemplateContext, index?: SG.SingleSendIndex) {
@@ -194,7 +196,7 @@ async function run(options: Options) {
194196
if (options.output) {
195197
await writeFile(options.output, text);
196198
} else if (options.apiKey) {
197-
const sendAt = getSendDate(context);
199+
const sendAt = getSendDate(context, options.sendAt);
198200
const id = singleSendId(context, options.index);
199201

200202
if (id)
@@ -242,10 +244,14 @@ type RunOptions = Omit<Options, 'filePath'> & {
242244
filter?: PathFilter,
243245
};
244246

245-
function dateFilter(after: number) {
247+
248+
/**
249+
* @param timeSpec Set the time on the date parsed from the file name
250+
*/
251+
function dateFilter(after: number, timeSpec?: TimeSpec) {
246252
return (path: string) => {
247253
const ctx = contextFromPath(path);
248-
return ctx.date.getTime() >= after;
254+
return setTime(ctx.date, timeSpec).getTime() >= after;
249255
};
250256
}
251257

@@ -304,6 +310,7 @@ async function runAction() {
304310
INPUT_POSTS_DIR: postsDir,
305311
INPUT_SLACK_URL: slackUrl,
306312
INPUT_AFTER_DATE: today,
313+
INPUT_SEND_AT: sendAtRaw,
307314
} = process.env;
308315

309316
if (!(path || postsDir)) {
@@ -313,6 +320,8 @@ async function runAction() {
313320
process.exit(1);
314321
}
315322

323+
const timeSpec = parseTimeSpec(sendAtRaw);
324+
316325
await runAll({
317326
apiKey,
318327
source: path ? { file: path } : { dir: postsDir },
@@ -325,7 +334,9 @@ async function runAction() {
325334
siteYaml,
326335
subject,
327336
slackUrl,
328-
filter: dateFilter(today ? new Date(today).getTime() : Date.now()),
337+
// Filter out posts from before this date.
338+
filter: dateFilter(today ? new Date(today).getTime() : Date.now(), timeSpec),
339+
sendAt: timeSpec,
329340
});
330341
}
331342

@@ -338,6 +349,7 @@ async function testRun() {
338349
process.env['INPUT_CONTEXT'] = `{}`;
339350
process.env['INPUT_SUPPRESSION_GROUP_ID'] = '17889';
340351
process.env['INPUT_SITE_YAML'] = __dirname + '/../../../../_config.yml';
352+
process.env['INPUT_SEND_AT'] = '10:00-0500';
341353

342354

343355
await runAction();
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
export type TimeSpecTuple = [number, number, number];
2+
export type TimeSpec = string | TimeSpecTuple;
3+
4+
export function tzOffset(offset: string) {
5+
const n = offset.slice(1, 5);
6+
const [h, m] =
7+
n.length >= 3 ? [n.slice(0, -2), n.slice(-2)] : [n, '0'];
8+
9+
const vala = parseInt(h, 10);
10+
const valb = parseInt(m, 10);
11+
const minutes = vala*60 + valb;
12+
13+
return offset[0] === '+' ? minutes : -minutes;
14+
}
15+
16+
export function parseTimeSpec(timeSpec?: TimeSpec): TimeSpecTuple {
17+
if (!timeSpec)
18+
return [10, 0, -300];
19+
20+
if (Array.isArray(timeSpec))
21+
return timeSpec;
22+
23+
const m = timeSpec.match(/^(\d{1,2})(:(\d\d))?([+-]\d{4})?$/);
24+
25+
if (!m) {
26+
throw new Error(`Couldn't parse timeSpec option: ${timeSpec}`);
27+
}
28+
29+
return [parseInt(m[1]), m[3] ? parseInt(m[3]) : 0, m[4] ? tzOffset(m[4]) : -300];
30+
}
31+
32+
export function setTime(date: string | number | Date, time?: TimeSpec) {
33+
if (!time)
34+
return new Date(date);
35+
36+
let [h, m, tz] = parseTimeSpec(time);
37+
const d = new Date(date);
38+
tz += d.getTimezoneOffset();
39+
40+
return new Date((d.setHours(h, m, 0, 0)) - (tz*60000));
41+
}
42+
43+
44+
function test() {
45+
const specs = [undefined, '12:00', '9:00-0600'];
46+
for (const specString of specs) {
47+
const spec = parseTimeSpec(specString);
48+
console.log(spec);
49+
50+
const sendAt = setTime(new Date(), spec);
51+
console.log(sendAt);
52+
}
53+
}
54+
55+
if (require.main === module)
56+
test();

.github/workflows/send-newsletter.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ jobs:
2828
subject_format: "[Code for Boston] %s"
2929
slack_url: ${{ secrets.SLACK_URL }}
3030
after_date: ${{ steps.commit-date.outputs.commit-date }}
31+
send_at: "10:00-0500"
3132
context: >-
3233
{
3334
"domain": "${{ steps.site-domain.outputs.domain }}"

0 commit comments

Comments
 (0)