From e8e6e51a093b1ab6a6d7dacc90afa27557812230 Mon Sep 17 00:00:00 2001 From: Esther Kim <44627152+estherkim@users.noreply.github.com> Date: Tue, 3 Aug 2021 18:44:09 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=8F=97=20=20Generate=20release=20body=20f?= =?UTF-8?q?or=20release=20tagger=20(#35507)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build-system/release-tagger/create-release.js | 90 +++++++++++- .../test/create-release.test.js | 138 ++++++++++++++++-- build-system/release-tagger/utils.js | 21 ++- 3 files changed, 231 insertions(+), 18 deletions(-) diff --git a/build-system/release-tagger/create-release.js b/build-system/release-tagger/create-release.js index 4c86fc61b295..091d8c395194 100644 --- a/build-system/release-tagger/create-release.js +++ b/build-system/release-tagger/create-release.js @@ -23,6 +23,7 @@ */ const argv = require('minimist')(process.argv.slice(2)); +const dedent = require('dedent'); const { createRelease, getPullRequestsBetweenCommits, @@ -30,13 +31,96 @@ const { } = 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`\ + ${abbreviatedOid} ${title} \ + (#${number})`; +} + +/** + * Organize pull requests into sections + * @param {Array} prs + * @return {Array} + */ +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` +
\ + \ + ${key} (${orderedPrs.length})\ + \ + ${orderedPrs.join('
')}\ +
`; + sectionsMarkdown.push(template); + } + + return sectionsMarkdown; +} + /** * Create body for GitHub release + * @param {string} base * @param {Array} 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; } /** @@ -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); } diff --git a/build-system/release-tagger/test/create-release.test.js b/build-system/release-tagger/test/create-release.test.js index c7452ac410d0..33089277826b 100644 --- a/build-system/release-tagger/test/create-release.test.js +++ b/build-system/release-tagger/test/create-release.test.js @@ -25,31 +25,56 @@ test.after(() => { }); test('create', async (t) => { + const pr1 = + '' + + '1abc Bunch of changes ' + + '(#1)'; + const pr2 = + '' + + '2abc `README` updates ' + + '(#2)'; + const pr3 = + '' + + '3abc Update packages ' + + '(#3)'; + 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' + + `
ads (1)${pr1}
` + + `
amp-bind (1)${pr1}
` + + `
build-system (1)${pr2}
` + + `
package updates (1)${pr3}
` + + `
src (1)${pr1}
` + + `
third_party (2)${pr1}
${pr2}
` + + `
validator (1)${pr1}
`, }) .reply(200, {}); @@ -57,20 +82,113 @@ test('create', async (t) => { .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()); diff --git a/build-system/release-tagger/utils.js b/build-system/release-tagger/utils.js index af927b669809..9b81106e987f 100644 --- a/build-system/release-tagger/utils.js +++ b/build-system/release-tagger/utils.js @@ -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'; @@ -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; } @@ -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; } /**