-
Notifications
You must be signed in to change notification settings - Fork 1
/
handler.js
123 lines (101 loc) · 3.71 KB
/
handler.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
const Airtable = require('airtable');
const GitHub = require('github-api');
const randomColor = require('random-color');
const gh = new GitHub({ token: process.env.GITHUB_API_KEY });
const issues = gh.getIssues(process.env.REPO_OWNER, process.env.REPO_NAME);
const airtable = new Airtable().base(process.env.AIRTABLE_BASE);
function name2Label(name) {
return `proj:${name.toLowerCase().replace(/ /g, '-')}`;
}
async function syncProjectLabels() {
const projId2Label = new Map((await issues.listLabels({ AcceptHeader: 'symmetra-preview' }))
.data.map(label => [label.description, label.name]));
const projects = (await airtable('Projects').select({
fields: ['Name'],
filterByFormula: '{GitHub} = 1',
}).all());
await Promise.all(projects.map(proj => {
if (projId2Label.has(proj.id)) return;
return issues.createLabel({
name: name2Label(proj.get('Name')),
color: randomColor().hexString().slice(1),
description: proj.id,
AcceptHeader: 'symmetra-preview',
});
}));
return projId2Label;
}
function getIssueBody(task) {
const desc = (task.get('Notes') || '').trim();
let body = desc ? `${desc}\n\n` : '';
body += `Time Estimate (days): ${task.get('Time Estimate')}`;
body += `\n\n[Airtable link](${process.env.AIRTABLE_LINK_PRE}${task.id})`;
return body;
}
function setEq(a, b) {
const bSet = new Set(b);
return a.reduce((eq, v) => (eq && bSet.has(v)), true);
}
module.exports.init = async (_event, _context) => {
// delete all existing labels
await Promise.all((await issues.listLabels({})).data
.map(label => issues.deleteLabel(label.name)));
return syncProjectLabels();
};
module.exports.transferTasks = async (_event, _context) => {
const projId2Label = await syncProjectLabels();
const openTasks = await airtable('Tasks').select({
fields: ['Name', 'Notes', 'Time Estimate', 'Project'],
view: 'Main View',
filterByFormula: 'NOT({Status} = "done")',
}).all();
const openTaskIds = new Set(openTasks.map(task => task.id));
const openIssues = (await issues.listIssues({ state: 'open' })).data;
const openIssueByTaskId = new Map(openIssues
.map(issue => {
const match = /\[Airtable link\].*\/(rec\w+)\)/.exec(issue.body);
if (match === null) return null;
return [match[1], issue];
})
.filter(kv => kv !== undefined));
function getIssueLabels(task) {
return task.get('Project')
.map(projId => projId2Label.get(projId))
.filter(label => label !== undefined);
}
const createIssuesFromTasks = openTasks.map(task => {
if (openIssueByTaskId.has(task.id)) return;
const labels = getIssueLabels(task);
if (labels.length === 0) return;
return issues.createIssue({
title: task.get('Name'),
body: getIssueBody(task),
labels,
});
});
const closeCompletedTaskIssues = Array.from(openIssueByTaskId.entries())
.map(([taskId, issue]) => {
if (openTaskIds.has(taskId)) return;
return issues.editIssue(issue.number, { state: 'closed' });
});
const updateIssuesFromChangedTasks = openTasks
.map(task => {
const issue = openIssueByTaskId.get(task.id);
if (issue === undefined) return;
const newBody = getIssueBody(task);
const curLabels = issue.labels.map(l => l.name);
const newLabels = getIssueLabels(task);
const changed = issue.title !== task.get('Name')
|| issue.body !== newBody
|| !setEq(curLabels, newLabels);
if (!changed) return;
return issues.editIssue(issue.number, {
title: task.get('Name'),
body: newBody,
labels: newLabels,
});
});
return Promise.all(createIssuesFromTasks
.concat(closeCompletedTaskIssues)
.concat(updateIssuesFromChangedTasks));
};