From eabec491ba8d5be99689c1d636c3763fd8c77120 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 15:52:35 +0000 Subject: [PATCH 1/5] Initial plan From ff2c81cc03258574cfbb6c262ceb46a62d9a4a5f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 15:59:59 +0000 Subject: [PATCH 2/5] feat: auto-skip release validation from PR labels Co-authored-by: davidmurdoch <187813+davidmurdoch@users.noreply.github.com> --- .../scripts/post-merge-validation-tracker.mjs | 92 +++++++++++++++++-- 1 file changed, 85 insertions(+), 7 deletions(-) diff --git a/.github/scripts/post-merge-validation-tracker.mjs b/.github/scripts/post-merge-validation-tracker.mjs index 0ed0e89b..d892ac7b 100644 --- a/.github/scripts/post-merge-validation-tracker.mjs +++ b/.github/scripts/post-merge-validation-tracker.mjs @@ -22,6 +22,7 @@ const AUTOMATED_TEST_PATTERNS = [ /(^|\/)e2e\//, /(^|\/)wdio\// ]; +const SKIP_RELEASE_VALIDATION_LABEL = 'skip-release-validation'; if (!githubToken) throw new Error('Missing GITHUB_TOKEN env var'); if (!spreadsheetId) throw new Error('Missing SHEET_ID env var'); @@ -70,6 +71,7 @@ function headerRowFor(type) { 'Team Responsible', colG, colH, + 'Comment', ]; } @@ -140,7 +142,7 @@ async function createSheetFromTemplateOrBlank(authClient, sheetsList, title, pla await sheets.spreadsheets.values.update({ spreadsheetId, auth: authClient, - range: `${title}!A2:H2`, + range: `${title}!A2:I2`, valueInputOption: 'USER_ENTERED', requestBody: { values: [headerRowFor(platformType)] }, }); @@ -196,7 +198,7 @@ async function createSheetFromTemplateOrBlank(authClient, sheetsList, title, pla await sheets.spreadsheets.values.update({ spreadsheetId, auth: authClient, - range: `${title}!A2:H2`, + range: `${title}!A2:I2`, valueInputOption: 'USER_ENTERED', requestBody: { values: [headerRowFor(platformType)] }, }); @@ -223,7 +225,7 @@ async function readRows(authClient, title) { const res = await sheets.spreadsheets.values.get({ spreadsheetId, auth: authClient, - range: `${title}!A3:H`, + range: `${title}!A3:I`, }); return res.data.values || []; } catch (e) { @@ -237,7 +239,7 @@ async function appendRows(authClient, title, rows) { await sheets.spreadsheets.values.append({ spreadsheetId, auth: authClient, - range: `${title}!A4:H`, + range: `${title}!A4:I`, valueInputOption: 'USER_ENTERED', insertDataOption: 'INSERT_ROWS', requestBody: { values: rows }, @@ -384,6 +386,76 @@ function splitByReleaseAndTitle(items) { return { relevant, skippedByTitle }; } +function getAutoSkipLabelsForPR(labels, repoName) { + const labelNames = (labels || []).map((label) => label.name).filter(Boolean); + const hasSkipAll = labelNames.includes(SKIP_RELEASE_VALIDATION_LABEL); + + const granularLabels = labelNames.filter((name) => + /^skip-release-validation\[(android|ios|design|chrome|firefox)\]$/i.test(name), + ); + + const lowerRepoName = String(repoName || '').toLowerCase(); + const isMobile = lowerRepoName.endsWith('-mobile'); + const isExtension = lowerRepoName.endsWith('-extension'); + + const validGranularLabels = granularLabels.filter((name) => { + const target = name.toLowerCase(); + if (isMobile && (target.includes('[chrome]') || target.includes('[firefox]'))) { + return false; + } + if (isExtension && (target.includes('[android]') || target.includes('[ios]'))) { + return false; + } + return true; + }); + + return { + hasSkipAll, + validLabels: validGranularLabels, + }; +} + +function applyAutoSkipToRow(row, labels, repoName, isMainRelease) { + if (!isMainRelease) { + return row; + } + + const { hasSkipAll, validLabels } = getAutoSkipLabelsForPR(labels, repoName); + if (!hasSkipAll && validLabels.length === 0) { + return row; + } + + const updatedRow = [...row]; + const validLabelSet = new Set(validLabels.map((label) => label.toLowerCase())); + const isMobile = String(repoName || '').toLowerCase().endsWith('-mobile'); + + const shouldSkipFirstValidated = + hasSkipAll || + validLabelSet.has('skip-release-validation[design]') || + validLabelSet.has(isMobile ? 'skip-release-validation[android]' : 'skip-release-validation[chrome]'); + const shouldSkipSecondValidated = + hasSkipAll || + validLabelSet.has('skip-release-validation[design]') || + validLabelSet.has(isMobile ? 'skip-release-validation[ios]' : 'skip-release-validation[firefox]'); + + if (shouldSkipFirstValidated) { + updatedRow[6] = 'Skipped'; + } + if (shouldSkipSecondValidated) { + updatedRow[7] = 'Skipped'; + } + + const labelsForComment = hasSkipAll + ? [SKIP_RELEASE_VALIDATION_LABEL, ...validLabels] + : validLabels; + + if (shouldSkipFirstValidated || shouldSkipSecondValidated) { + updatedRow[8] = `Release validation automatically skipped due to PR labels: ${labelsForComment.join(', ')}`; + } + + return updatedRow; +} + // Add efficient version detection with caching let versionCache = new Map(); // Cache version bumps per repo @@ -548,8 +620,13 @@ async function buildTabGrouping(owner, repo, relevantItems, sinceDateISO) { extractTeam(pr.labels || []), '', '', + '', ]; - tabToRows.get(title).entries.push({ row, mergedAtIso: pr.closed_at || '' }); + tabToRows.get(title).entries.push({ + row, + mergedAtIso: pr.closed_at || '', + labels: pr.labels || [], + }); } } @@ -733,10 +810,11 @@ async function processTab(authClient, title, entries, platformType) { .filter((n) => n !== null) .map((n) => uniqKey(n)), ); + const isMainRelease = !actualTitle.startsWith('pre-'); const sortedRows = entries .slice() .sort((a, b) => new Date(a.mergedAtIso) - new Date(b.mergedAtIso)) - .map((e) => e.row); + .map((e) => applyAutoSkipToRow(e.row, e.labels, repo, isMainRelease)); const deduped = []; for (const r of sortedRows) { const num = parsePrNumberFromCell(r[0]); @@ -830,4 +908,4 @@ async function main() { main().catch((e) => { console.error(e); process.exit(1); -}); \ No newline at end of file +}); From de0de82364db2025e64c4a3cf570f7bbaf03600d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 17:10:53 +0000 Subject: [PATCH 3/5] fix: use existing template comments column Co-authored-by: davidmurdoch <187813+davidmurdoch@users.noreply.github.com> --- .github/scripts/post-merge-validation-tracker.mjs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/scripts/post-merge-validation-tracker.mjs b/.github/scripts/post-merge-validation-tracker.mjs index d892ac7b..1dd4af58 100644 --- a/.github/scripts/post-merge-validation-tracker.mjs +++ b/.github/scripts/post-merge-validation-tracker.mjs @@ -71,7 +71,6 @@ function headerRowFor(type) { 'Team Responsible', colG, colH, - 'Comment', ]; } @@ -142,7 +141,7 @@ async function createSheetFromTemplateOrBlank(authClient, sheetsList, title, pla await sheets.spreadsheets.values.update({ spreadsheetId, auth: authClient, - range: `${title}!A2:I2`, + range: `${title}!A2:H2`, valueInputOption: 'USER_ENTERED', requestBody: { values: [headerRowFor(platformType)] }, }); @@ -198,7 +197,7 @@ async function createSheetFromTemplateOrBlank(authClient, sheetsList, title, pla await sheets.spreadsheets.values.update({ spreadsheetId, auth: authClient, - range: `${title}!A2:I2`, + range: `${title}!A2:H2`, valueInputOption: 'USER_ENTERED', requestBody: { values: [headerRowFor(platformType)] }, }); @@ -225,7 +224,7 @@ async function readRows(authClient, title) { const res = await sheets.spreadsheets.values.get({ spreadsheetId, auth: authClient, - range: `${title}!A3:I`, + range: `${title}!A3:J`, }); return res.data.values || []; } catch (e) { @@ -239,7 +238,7 @@ async function appendRows(authClient, title, rows) { await sheets.spreadsheets.values.append({ spreadsheetId, auth: authClient, - range: `${title}!A4:I`, + range: `${title}!A4:J`, valueInputOption: 'USER_ENTERED', insertDataOption: 'INSERT_ROWS', requestBody: { values: rows }, @@ -450,7 +449,7 @@ function applyAutoSkipToRow(row, labels, repoName, isMainRelease) { : validLabels; if (shouldSkipFirstValidated || shouldSkipSecondValidated) { - updatedRow[8] = `Release validation automatically skipped due to PR labels: ${labelsForComment.join(', ')}`; + updatedRow[9] = `Release validation automatically skipped due to PR labels: ${labelsForComment.join(', ')}`; } return updatedRow; @@ -621,6 +620,7 @@ async function buildTabGrouping(owner, repo, relevantItems, sinceDateISO) { '', '', '', + '', ]; tabToRows.get(title).entries.push({ row, From a3726b226d26f15a9a25e720e34146798fddc1bb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 17:11:42 +0000 Subject: [PATCH 4/5] refactor: clarify release sheet row column placeholders Co-authored-by: davidmurdoch <187813+davidmurdoch@users.noreply.github.com> --- .github/scripts/post-merge-validation-tracker.mjs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/scripts/post-merge-validation-tracker.mjs b/.github/scripts/post-merge-validation-tracker.mjs index 1dd4af58..a2bf2083 100644 --- a/.github/scripts/post-merge-validation-tracker.mjs +++ b/.github/scripts/post-merge-validation-tracker.mjs @@ -609,6 +609,10 @@ async function buildTabGrouping(owner, repo, relevantItems, sinceDateISO) { for (const pr of prs) { // Check if PR modifies automated test files const automatedTestsModified = await checkAutomatedTestFiles(owner, repo, pr.number); + const validatedA = ''; + const validatedB = ''; + const designValidation = ''; + const comments = ''; const row = [ makePrHyperlinkCell(pr.html_url, pr.title, pr.number), @@ -617,10 +621,10 @@ async function buildTabGrouping(owner, repo, relevantItems, sinceDateISO) { extractSize(pr.labels || []), automatedTestsModified, extractTeam(pr.labels || []), - '', - '', - '', - '', + validatedA, + validatedB, + designValidation, + comments, ]; tabToRows.get(title).entries.push({ row, From d8b84622c2659bccde19b1d7e973a1816f66c9c3 Mon Sep 17 00:00:00 2001 From: Javier Briones <1674192+jvbriones@users.noreply.github.com> Date: Fri, 20 Mar 2026 09:04:33 +0100 Subject: [PATCH 5/5] fix: apply skip logic to both main and release tabs --- .github/scripts/post-merge-validation-tracker.mjs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/scripts/post-merge-validation-tracker.mjs b/.github/scripts/post-merge-validation-tracker.mjs index a2bf2083..b693e6a0 100644 --- a/.github/scripts/post-merge-validation-tracker.mjs +++ b/.github/scripts/post-merge-validation-tracker.mjs @@ -414,10 +414,7 @@ function getAutoSkipLabelsForPR(labels, repoName) { }; } -function applyAutoSkipToRow(row, labels, repoName, isMainRelease) { - if (!isMainRelease) { - return row; - } +function applyAutoSkipToRow(row, labels, repoName) { const { hasSkipAll, validLabels } = getAutoSkipLabelsForPR(labels, repoName); if (!hasSkipAll && validLabels.length === 0) { @@ -814,11 +811,10 @@ async function processTab(authClient, title, entries, platformType) { .filter((n) => n !== null) .map((n) => uniqKey(n)), ); - const isMainRelease = !actualTitle.startsWith('pre-'); const sortedRows = entries .slice() .sort((a, b) => new Date(a.mergedAtIso) - new Date(b.mergedAtIso)) - .map((e) => applyAutoSkipToRow(e.row, e.labels, repo, isMainRelease)); + .map((e) => applyAutoSkipToRow(e.row, e.labels, repo)); const deduped = []; for (const r of sortedRows) { const num = parsePrNumberFromCell(r[0]);