Skip to content

Commit

Permalink
🏗 Generate release body for release tagger (#35507)
Browse files Browse the repository at this point in the history
  • Loading branch information
estherkim committed Aug 3, 2021
1 parent 4c7d5d7 commit e8e6e51
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 18 deletions.
90 changes: 87 additions & 3 deletions build-system/release-tagger/create-release.js
Expand Up @@ -23,20 +23,104 @@
*/

const argv = require('minimist')(process.argv.slice(2));
const dedent = require('dedent');
const {
createRelease,
getPullRequestsBetweenCommits,
getRelease,
} = require('./utils');
const {GraphQlQueryResponseData} = require('@octokit/graphql'); //eslint-disable-line no-unused-vars

/**
* Format pull request line
* @param {GraphQlQueryResponseData} pr
* @return {string}
*/
function _formatPullRequestLine(pr) {
const {mergeCommit, number, title, url} = pr;
const {abbreviatedOid, commitUrl} = mergeCommit;
return dedent`\
<a href="${commitUrl}"><code>${abbreviatedOid}</code></a> ${title} \
(<a href="${url}">#${number}</a>)`;
}

/**
* Organize pull requests into sections
* @param {Array<GraphQlQueryResponseData>} prs
* @return {Array<string>}
*/
function _createSections(prs) {
const sections = {
'ads': [],
'build-system': [],
'src': [],
'third_party': [],
'validator': [],
'package updates': new Array(), // [] defaults to never[], which tsc flags
};

for (const pr of prs) {
// renovate bot
if (pr.author.login === 'renovate-bot') {
sections['package updates'].push(_formatPullRequestLine(pr));
continue;
}

// directories
for (const key of Object.keys(sections)) {
if (pr.files.nodes.some((node) => node.path.startsWith(`${key}/`))) {
sections[key].push(_formatPullRequestLine(pr));
}
}

// components
for (const node of pr.files.nodes) {
if (node.path.startsWith('extensions/')) {
const component = node.path.split('/')[1];
if (!Object.keys(sections).includes(component)) {
sections[component] = [];
}
sections[component].push(_formatPullRequestLine(pr));
}
}
}

const sectionsMarkdown = [];
for (const key of Object.keys(sections).sort()) {
const orderedPrs = sections[key].sort();
const template = dedent`
<details>\
<summary>\
${key} (${orderedPrs.length})\
</summary>\
${orderedPrs.join('<br />')}\
</details>`;
sectionsMarkdown.push(template);
}

return sectionsMarkdown;
}

/**
* Create body for GitHub release
* @param {string} base
* @param {Array<GraphQlQueryResponseData>} prs
* @return {string}
*/
function _createBody(prs) {
return prs.map((pr) => pr.number).join('\n'); // TODO: build this out please
function _createBody(base, prs) {
const rawNotes = prs.map(_formatPullRequestLine);
const sections = _createSections(prs);
const template = dedent`\
#### *Baseline release: [${base}]\
(https://github.com/ampproject/amphtml/releases/${base})*
#### Raw notes
${rawNotes.join('\n')}
#### Breakdown by component
${sections.join('')}\
`;
return template;
}

/**
Expand All @@ -49,7 +133,7 @@ async function main(tag, previousTag) {
const {'target_commitish': commit} = await getRelease(tag);
const {'target_commitish': previousCommit} = await getRelease(previousTag);
const prs = await getPullRequestsBetweenCommits(commit, previousCommit);
const body = _createBody(prs);
const body = _createBody(previousTag, prs);
return await createRelease(tag, commit, body);
}

Expand Down
138 changes: 128 additions & 10 deletions build-system/release-tagger/test/create-release.test.js
Expand Up @@ -25,52 +25,170 @@ test.after(() => {
});

test('create', async (t) => {
const pr1 =
'<a href="https://github.com/ampproject/amphtml/commit/1abcdef">' +
'<code>1abc</code></a> Bunch of changes ' +
'(<a href="https://github.com/ampproject/amphtml/pull/1">#1</a>)';
const pr2 =
'<a href="https://github.com/ampproject/amphtml/commit/2abcdef">' +
'<code>2abc</code></a> `README` updates ' +
'(<a href="https://github.com/ampproject/amphtml/pull/2">#2</a>)';
const pr3 =
'<a href="https://github.com/ampproject/amphtml/commit/3abcdef">' +
'<code>3abc</code></a> Update packages ' +
'(<a href="https://github.com/ampproject/amphtml/pull/3">#3</a>)';

const rest = nock('https://api.github.com')
// https://docs.github.com/en/rest/reference/repos#get-a-release-by-tag-name
.get('/repos/ampproject/amphtml/releases/tags/2107280123000')
.reply(200, {
id: 2,
'target_commitish': 'abcde3',
'target_commitish': '3abcdef',
})
// https://docs.github.com/en/rest/reference/repos#get-a-release-by-tag-name
.get('/repos/ampproject/amphtml/releases/tags/2107210123000')
.reply(200, {
id: 1,
'target_commitish': 'abcde1',
'target_commitish': '1abcdef',
})
// https://docs.github.com/en/rest/reference/repos#compare-two-commits
.get('/repos/ampproject/amphtml/compare/abcde1...abcde3')
.get('/repos/ampproject/amphtml/compare/1abcdef...3abcdef')
.reply(200, {
commits: [{sha: 'abcde1'}, {sha: 'abcde2'}, {sha: 'abcde3'}],
commits: [{sha: '1abcdef'}, {sha: '2abcdef'}, {sha: '3abcdef'}],
})
// https://docs.github.com/en/rest/reference/repos#create-a-release
.post('/repos/ampproject/amphtml/releases', {
name: '2107280123000',
'tag_name': '2107280123000',
'target_commitish': 'abcde3',
body: '',
'target_commitish': '3abcdef',
prerelease: true,
body:
'#### *Baseline release: [2107210123000]' +
'(https://github.com/ampproject/amphtml/releases/2107210123000)*\n\n' +
'#### Raw notes\n' +
`${pr1}\n${pr2}\n${pr3}\n\n` +
'#### Breakdown by component\n' +
`<details><summary>ads (1)</summary>${pr1}</details>` +
`<details><summary>amp-bind (1)</summary>${pr1}</details>` +
`<details><summary>build-system (1)</summary>${pr2}</details>` +
`<details><summary>package updates (1)</summary>${pr3}</details>` +
`<details><summary>src (1)</summary>${pr1}</details>` +
`<details><summary>third_party (2)</summary>${pr1}<br />${pr2}</details>` +
`<details><summary>validator (1)</summary>${pr1}</details>`,
})
.reply(200, {});

const graphql = nock('https://api.github.com/graphql')
.post(
'',
'{"query":"query {' +
'pr0: search(query:\\"repo:ampproject/amphtml sha:abcde1\\", ' +
'pr0: search(query:\\"repo:ampproject/amphtml sha:1abcdef\\", ' +
'type:ISSUE first:100){nodes { ... on PullRequest { id title number ' +
'url author { login } files(first:100) { nodes { path }} mergeCommit ' +
'{ commitUrl oid abbreviatedOid }}}} ' +
'pr1: search(query:\\"repo:ampproject/amphtml sha:abcde2\\", ' +
'pr1: search(query:\\"repo:ampproject/amphtml sha:2abcdef\\", ' +
'type:ISSUE first:100){nodes { ... on PullRequest { id title number ' +
'url author { login } files(first:100) { nodes { path }} mergeCommit ' +
'{ commitUrl oid abbreviatedOid }}}} ' +
'pr2: search(query:\\"repo:ampproject/amphtml sha:abcde3\\", ' +
'pr2: search(query:\\"repo:ampproject/amphtml sha:3abcdef\\", ' +
'type:ISSUE first:100){nodes { ... on PullRequest { id title number ' +
'url author { login } files(first:100) { nodes { path }} mergeCommit ' +
'{ commitUrl oid abbreviatedOid }}}}}"}'
)
.reply(200, {data: {pr0: 'todo', pr1: 'todo', pr2: 'todo'}});
.reply(200, {
data: {
pr0: {
nodes: [
{
id: 'MDExOlB1bGxSZXF1ZXN0Mjk4OTg2MDI4',
title: 'Bunch of changes',
number: 1,
url: 'https://github.com/ampproject/amphtml/pull/1',
author: {login: 'testauthor'},
files: {
nodes: [
{
'path': 'ads/readme.md',
},
{
'path': 'extensions/amp-bind/readme.md',
},
{
'path': 'src/readme.md',
},
{
'path': 'third_party/tasks/e2e/readme.md',
},
{
'path': 'validator/readme.md',
},
],
},
mergeCommit: {
commitUrl:
'https://github.com/ampproject/amphtml/commit/1abcdef',
oid: '1abcdef',
abbreviatedOid: '1abc',
},
},
],
},
pr1: {
nodes: [
{
id: 'MDExOlB1bGxSZXF1ZXN0MzAyMTU2MjIy',
title: '`README` updates',
number: 2,
url: 'https://github.com/ampproject/amphtml/pull/2',
author: {login: 'testauthor'},
files: {
nodes: [
{
'path': 'build-system/tasks/e2e/readme.md',
},
{
'path': 'third_party/tasks/e2e/readme.md',
},
],
},
mergeCommit: {
commitUrl:
'https://github.com/ampproject/amphtml/commit/2abcdef',
oid: '2abcdef',
abbreviatedOid: '2abc',
},
},
],
},
pr2: {
nodes: [
{
id: 'MDExOlB1bGxSZXF1ZXN0MzAyMTU4NDIw',
title: 'Update packages',
number: 3,
url: 'https://github.com/ampproject/amphtml/pull/3',
author: {login: 'renovate-bot'},
files: {
nodes: [
{
'path': 'extensions/amp-bind/readme.md',
},
{
'path': 'extensions/amp-story/readme.md',
},
],
},
mergeCommit: {
commitUrl:
'https://github.com/ampproject/amphtml/commit/3abcdef',
oid: '3abcdef',
abbreviatedOid: '3abc',
},
},
],
},
},
});

await createRelease('2107280123000', '2107210123000');
t.true(rest.isDone());
Expand Down
21 changes: 16 additions & 5 deletions build-system/release-tagger/utils.js
Expand Up @@ -27,15 +27,14 @@ const {Octokit} = require('@octokit/rest');

// setup
const octokit = new Octokit({
auth: '',
auth: process.env.GITHUB_TOKEN,
userAgent: 'amp release tagger',
previews: ['groot-preview'], // to list pull requests by commit
timeZone: 'America/New_York',
});

const graphqlWithAuth = graphql.defaults({
headers: {
authorization: `token todo`,
authorization: `token ${process.env.GITHUB_TOKEN}`,
},
});
const owner = 'ampproject';
Expand All @@ -58,7 +57,8 @@ async function _runQueryInBatches(queryType, queries) {
for (let i = 0; i < queries.length; i += config.batchSize) {
const join = queries.slice(i, config.batchSize).join(' ');
const query = `${queryType} {${join}}`;
responses.push(await graphqlWithAuth(query));
const data = await graphqlWithAuth(query);
responses.push(...Object.values(data));
}
return responses;
}
Expand Down Expand Up @@ -140,7 +140,18 @@ async function getPullRequests(shas) {
mergeCommit { commitUrl oid abbreviatedOid }}}}`
);
}
return await _runQueryInBatches('query', queries);
const nodesLists = await _runQueryInBatches('query', queries);

// Only return pull requests with the merge commit shas
const prs = [];
for (const nodesList of nodesLists) {
for (const node of nodesList.nodes) {
if (node.mergeCommit && shas.includes(node.mergeCommit.oid)) {
prs.push(node);
}
}
}
return prs;
}

/**
Expand Down

0 comments on commit e8e6e51

Please sign in to comment.