From d56032647adab193ed961e9a48c4296a55b42a3e Mon Sep 17 00:00:00 2001 From: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> Date: Wed, 15 Oct 2025 10:14:35 -0700 Subject: [PATCH 1/5] chore: automate building our testing sheet --- scripts/getCommits.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 scripts/getCommits.js diff --git a/scripts/getCommits.js b/scripts/getCommits.js new file mode 100644 index 00000000000..72543de686e --- /dev/null +++ b/scripts/getCommits.js @@ -0,0 +1,34 @@ +const Octokit = require('@octokit/rest'); + +const octokit = new Octokit(); + +const startDate = new Date('2025-10-12').toISOString(); +const endDate = new Date('2025-10-15').toISOString(); + +listCommits(); + +async function listCommits() { + let res = await octokit.request(`GET /repos/adobe/react-spectrum/commits?sha=main&since=${startDate}&until=${endDate}`, { + owner: 'adobe', + repo: 'react-spectrum', + headers: { + 'X-GitHub-Api-Version': '2022-11-28' + } + }) + + let data = res.data; + + let pr = []; + for (let d of data) { + let regex = /\(#(\d+)\)/g; + let messages = d.commit.message.split('\n'); + let m = messages[0]; + if (regex.test(m)) { + pr.push(m.match(regex)[0]) + } + } + + console.log(pr); + + +} \ No newline at end of file From d5717f95b3b9c7c92a0ea1a1c3a435f93f6492b1 Mon Sep 17 00:00:00 2001 From: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> Date: Thu, 16 Oct 2025 16:28:14 -0700 Subject: [PATCH 2/5] write csv --- scripts/getCommits.js | 34 ---------- scripts/getCommitsForTesting.js | 116 ++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 34 deletions(-) delete mode 100644 scripts/getCommits.js create mode 100644 scripts/getCommitsForTesting.js diff --git a/scripts/getCommits.js b/scripts/getCommits.js deleted file mode 100644 index 72543de686e..00000000000 --- a/scripts/getCommits.js +++ /dev/null @@ -1,34 +0,0 @@ -const Octokit = require('@octokit/rest'); - -const octokit = new Octokit(); - -const startDate = new Date('2025-10-12').toISOString(); -const endDate = new Date('2025-10-15').toISOString(); - -listCommits(); - -async function listCommits() { - let res = await octokit.request(`GET /repos/adobe/react-spectrum/commits?sha=main&since=${startDate}&until=${endDate}`, { - owner: 'adobe', - repo: 'react-spectrum', - headers: { - 'X-GitHub-Api-Version': '2022-11-28' - } - }) - - let data = res.data; - - let pr = []; - for (let d of data) { - let regex = /\(#(\d+)\)/g; - let messages = d.commit.message.split('\n'); - let m = messages[0]; - if (regex.test(m)) { - pr.push(m.match(regex)[0]) - } - } - - console.log(pr); - - -} \ No newline at end of file diff --git a/scripts/getCommitsForTesting.js b/scripts/getCommitsForTesting.js new file mode 100644 index 00000000000..f234afd94d6 --- /dev/null +++ b/scripts/getCommitsForTesting.js @@ -0,0 +1,116 @@ +const Octokit = require('@octokit/rest'); +const fs = require('fs'); +let {parseArgs} = require('util'); + +const octokit = new Octokit(); + +let options = { + startDate: { + type: 'string' + }, + endDate: { + type: 'string' + } +}; + +writeTestingCSV(); + +async function writeTestingCSV() { + let data = await listCommits(); + let prs = []; + for (let d of data) { + let row = []; + + // Get the PR Title from the commit + let regex = /\(#(\d+)\)/g; + let messages = d.commit.message.split('\n'); + let title = messages[0]; + row.push(title) + + + // Get info about the PR using PR number + if (regex.test(title)) { + let num = title.match(regex)[0].replace(/[\(\)#]/g, ''); + let info = await getPR(num); + + // Get testing instructions if it exists + let content = info.data.body; + const match = content.match(/## 📝 Test Instructions:\s*([\s\S]*?)(?=##|$)/); + let testInstructions = ''; + if (match) { + testInstructions = match[1]; + testInstructions = testInstructions.replace(//g, ''); + testInstructions = testInstructions.trim(); + } + + row.push(escapeCSV(testInstructions)); + row.push(info.data.html_url) + } + prs.push(row); + } + + let csvRows = ''; + for (let pr of prs) { + csvRows += pr.join(); + csvRows += '\n' + } + + fs.writeFileSync('output.csv', csvRows, 'utf-8') + +} + +async function listCommits() { + let args = parseArgs({options, allowPositionals: true}); + if (args.positionals.length < 2) { + console.error('Expected at least two arguments'); + process.exit(1); + } + + let start = args.positionals[0]; + let end = args.positionals[1]; + + let startDate = new Date(start).toISOString(); + let endDate = new Date(end).toISOString(); + + if (isNaN(startDate) || isNaN(endDate)) { + console.error('Please verify that your date is correctly formatted') + } + + let res = await octokit.request(`GET /repos/adobe/react-spectrum/commits?sha=main&since=${startDate}&until=${endDate}`, { + owner: 'adobe', + repo: 'react-spectrum', + headers: { + 'X-GitHub-Api-Version': '2022-11-28' + } + }) + + return res.data; +} + +async function getPR(num) { + let res = await octokit.request(`GET /repos/adobe/react-spectrum/pulls/${num}`, { + owner: 'adobe', + repo: 'react-spectrum', + pull_number: `${num}`, + headers: { + 'X-GitHub-Api-Version': '2022-11-28' + } + }) + + return res +} + +function escapeCSV(value) { + if (!value) { + return ''; + } + + // normalize newlines for CSV compatibility + let stringValue = String(value).replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + + // escape any internal double quotes + let escaped = stringValue.replace(/"/g, '""'); + + // wrap in quotes so commas/newlines don't break the cell + return `"${escaped}"`; +} From 899b429ed342c4a05d413cb814cc3a44e29aded0 Mon Sep 17 00:00:00 2001 From: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> Date: Thu, 16 Oct 2025 16:34:30 -0700 Subject: [PATCH 3/5] update comments --- scripts/getCommitsForTesting.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/getCommitsForTesting.js b/scripts/getCommitsForTesting.js index f234afd94d6..1f144f6f5b0 100644 --- a/scripts/getCommitsForTesting.js +++ b/scripts/getCommitsForTesting.js @@ -105,12 +105,12 @@ function escapeCSV(value) { return ''; } - // normalize newlines for CSV compatibility + // Normalize newlines for CSV compatibility let stringValue = String(value).replace(/\r\n/g, '\n').replace(/\r/g, '\n'); - // escape any internal double quotes + // Escape any internal double quotes let escaped = stringValue.replace(/"/g, '""'); - // wrap in quotes so commas/newlines don't break the cell + // Wrap in quotes so commas/newlines don't break the cell return `"${escaped}"`; } From 99d22cb7b823d2dc1db5a9c5a79ee3f93e12b20b Mon Sep 17 00:00:00 2001 From: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> Date: Fri, 17 Oct 2025 11:46:44 -0700 Subject: [PATCH 4/5] filter PRs using label --- scripts/getCommitsForTesting.js | 96 ++++++++++++++++++++++++--------- 1 file changed, 72 insertions(+), 24 deletions(-) diff --git a/scripts/getCommitsForTesting.js b/scripts/getCommitsForTesting.js index 1f144f6f5b0..ac706eebf41 100644 --- a/scripts/getCommitsForTesting.js +++ b/scripts/getCommitsForTesting.js @@ -17,7 +17,12 @@ writeTestingCSV(); async function writeTestingCSV() { let data = await listCommits(); - let prs = []; + + let s2PRs = []; + let racPRs = []; + let v3PRs = []; + let otherPRs = []; + for (let d of data) { let row = []; @@ -25,38 +30,68 @@ async function writeTestingCSV() { let regex = /\(#(\d+)\)/g; let messages = d.commit.message.split('\n'); let title = messages[0]; - row.push(title) - + row.push(title); // Get info about the PR using PR number if (regex.test(title)) { let num = title.match(regex)[0].replace(/[\(\)#]/g, ''); let info = await getPR(num); - // Get testing instructions if it exists - let content = info.data.body; - const match = content.match(/## 📝 Test Instructions:\s*([\s\S]*?)(?=##|$)/); - let testInstructions = ''; - if (match) { - testInstructions = match[1]; - testInstructions = testInstructions.replace(//g, ''); - testInstructions = testInstructions.trim(); + // Check for "needs testing" label on the PR + if (isReadyForTesting(info.data.labels)) { + // Get testing instructions if it exists + let content = info.data.body; + const match = content.match(/## 📝 Test Instructions:\s*([\s\S]*?)(?=##|$)/); + let testInstructions = ''; + if (match) { + testInstructions = match[1]; + testInstructions = testInstructions.replace(//g, ''); + testInstructions = testInstructions.trim(); + testInstructions = escapeCSV(testInstructions); + } + + if (testInstructions.length > 350) { + row.push('See PR for testing instructions'); + } else { + row.push(testInstructions); + } + row.push(info.data.html_url); + + if ((/\bs2\b/gi).test(title)) { + s2PRs.push(row); + } else if ((/\brac\b/gi).test(title)) { + racPRs.push(row); + } else if ((/\bv3\b/gi).test(title)) { + v3PRs.push(row); + } else { + otherPRs.push(row); + } } - - row.push(escapeCSV(testInstructions)); - row.push(info.data.html_url) } - prs.push(row); } let csvRows = ''; - for (let pr of prs) { - csvRows += pr.join(); - csvRows += '\n' + csvRows += 'V3 \n'; + for (let v3 of v3PRs) { + csvRows += v3.join() + '\n'; + } + + csvRows += '\nRainbow \n' + for (let s2 of s2PRs) { + csvRows += s2.join() + '\n'; + } + + csvRows += '\nRAC \n' + for (let rac of racPRs) { + csvRows += rac.join() + '\n'; } - fs.writeFileSync('output.csv', csvRows, 'utf-8') + csvRows += '\nOther \n' + for (let other of otherPRs) { + csvRows += other.join() + '\n'; + } + fs.writeFileSync('output.csv', csvRows, 'utf-8'); } async function listCommits() { @@ -73,7 +108,7 @@ async function listCommits() { let endDate = new Date(end).toISOString(); if (isNaN(startDate) || isNaN(endDate)) { - console.error('Please verify that your date is correctly formatted') + console.error('Please verify that your date is correctly formatted'); } let res = await octokit.request(`GET /repos/adobe/react-spectrum/commits?sha=main&since=${startDate}&until=${endDate}`, { @@ -82,7 +117,7 @@ async function listCommits() { headers: { 'X-GitHub-Api-Version': '2022-11-28' } - }) + }); return res.data; } @@ -95,9 +130,8 @@ async function getPR(num) { headers: { 'X-GitHub-Api-Version': '2022-11-28' } - }) - - return res + }); + return res; } function escapeCSV(value) { @@ -114,3 +148,17 @@ function escapeCSV(value) { // Wrap in quotes so commas/newlines don't break the cell return `"${escaped}"`; } + + +function isReadyForTesting(labels){ + if (labels.length === 0) { + return false; + } + for (let label of labels) { + if (label.name === 'needs testing') { + return true; + } + } + + return false; +} \ No newline at end of file From d8a2c64e46e86dcc2a1d566a68c8645e3fc3f8fa Mon Sep 17 00:00:00 2001 From: Yihui Liao <44729383+yihuiliao@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:26:31 -0700 Subject: [PATCH 5/5] get all commits, verify date arguments --- scripts/getCommitsForTesting.js | 94 ++++++++++++++++----------------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/scripts/getCommitsForTesting.js b/scripts/getCommitsForTesting.js index ac706eebf41..bae0c91c8d9 100644 --- a/scripts/getCommitsForTesting.js +++ b/scripts/getCommitsForTesting.js @@ -37,35 +37,32 @@ async function writeTestingCSV() { let num = title.match(regex)[0].replace(/[\(\)#]/g, ''); let info = await getPR(num); - // Check for "needs testing" label on the PR - if (isReadyForTesting(info.data.labels)) { - // Get testing instructions if it exists - let content = info.data.body; - const match = content.match(/## 📝 Test Instructions:\s*([\s\S]*?)(?=##|$)/); - let testInstructions = ''; - if (match) { - testInstructions = match[1]; - testInstructions = testInstructions.replace(//g, ''); - testInstructions = testInstructions.trim(); - testInstructions = escapeCSV(testInstructions); - } - - if (testInstructions.length > 350) { - row.push('See PR for testing instructions'); - } else { - row.push(testInstructions); - } - row.push(info.data.html_url); - - if ((/\bs2\b/gi).test(title)) { - s2PRs.push(row); - } else if ((/\brac\b/gi).test(title)) { - racPRs.push(row); - } else if ((/\bv3\b/gi).test(title)) { - v3PRs.push(row); - } else { - otherPRs.push(row); - } + // Get testing instructions if it exists + let content = info.data.body; + const match = content.match(/## 📝 Test Instructions:\s*([\s\S]*?)(?=##|$)/); + let testInstructions = ''; + if (match) { + testInstructions = match[1]; + testInstructions = testInstructions.replace(//g, ''); + testInstructions = testInstructions.trim(); + testInstructions = escapeCSV(testInstructions); + } + + if (testInstructions.length > 350) { + row.push('See PR for testing instructions'); + } else { + row.push(testInstructions); + } + row.push(info.data.html_url); + + if ((/\bs2\b/gi).test(title)) { + s2PRs.push(row); + } else if ((/\brac\b/gi).test(title)) { + racPRs.push(row); + } else if ((/\bv3\b/gi).test(title)) { + v3PRs.push(row); + } else { + otherPRs.push(row); } } } @@ -101,16 +98,17 @@ async function listCommits() { process.exit(1); } - let start = args.positionals[0]; - let end = args.positionals[1]; + let start = new Date(args.positionals[0]); + let end = new Date(args.positionals[1]); + + if (isNaN(start.getTime()) || isNaN(end.getTime())) { + console.error('Please verify that your date is correctly formatted') + process.exit(1) + } let startDate = new Date(start).toISOString(); let endDate = new Date(end).toISOString(); - if (isNaN(startDate) || isNaN(endDate)) { - console.error('Please verify that your date is correctly formatted'); - } - let res = await octokit.request(`GET /repos/adobe/react-spectrum/commits?sha=main&since=${startDate}&until=${endDate}`, { owner: 'adobe', repo: 'react-spectrum', @@ -149,16 +147,16 @@ function escapeCSV(value) { return `"${escaped}"`; } - -function isReadyForTesting(labels){ - if (labels.length === 0) { - return false; - } - for (let label of labels) { - if (label.name === 'needs testing') { - return true; - } - } - - return false; -} \ No newline at end of file +// We can bring this back if we start using the "needs testing" label +// function isReadyForTesting(labels){ +// if (labels.length === 0) { +// return false; +// } +// for (let label of labels) { +// if (label.name === 'needs testing') { +// return true; +// } +// } + +// return false; +// }