Skip to content

Commit

Permalink
Merge branch 'stage' into fix-main
Browse files Browse the repository at this point in the history
  • Loading branch information
mokimo committed Nov 13, 2024
2 parents ee9d4a1 + 343ba6e commit 966fae7
Show file tree
Hide file tree
Showing 354 changed files with 23,350 additions and 14,605 deletions.
64 changes: 64 additions & 0 deletions .github/workflows/check-feds-files.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const { slackNotification, getLocalConfigs } = require('./helpers.js');

const fedsFolders = [
'libs/blocks/global-navigation',
'libs/blocks/global-footer',
'libs/blocks/modal',
'libs/navigation',
'libs/scripts/delayed',
'libs/utils/utils',
'libs/utils/locales',
'libs/utils/federated',
'libs/martech/attributes',
];

const safely = (fn) => {
return (...args) => {
try {
fn(...args);
} catch (e) {
console.error(e);
}
};
}

const main = safely(async ({ github, context }) => {
const number = context.issue?.number || process.env.ISSUE;
const owner = context.repo.owner;
const repo = context.repo.repo;
const { data: files } = await github.rest.pulls.listFiles({
owner,
repo,
pull_number: number,
});

const affectedFedsFiles = files
.map(({ filename }) => filename)
.filter(filename => fedsFolders.some(x => filename.startsWith(x)));

const message = `> PR <https://github.com/adobecom/milo/pull/${number}|#${number}> affects:\n> \`\`\`${affectedFedsFiles.join('\n')}\`\`\``;
const webhook = process.env.FEDS_WATCH_HOOK;

if (!affectedFedsFiles.length) {
console.log("No affected Feds Files. Exiting");
return;
}
slackNotification(message, webhook)
.then((resp) => {
if (resp.ok) console.log('message posted on slack');
else throw new Error(resp);
})
.catch(console.error);
});

if (process.env.LOCAL_RUN) {
const { github, context } = getLocalConfigs();
main({
github,
context,
});
}



module.exports = main
25 changes: 25 additions & 0 deletions .github/workflows/check-feds-files.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Feds File Watch

on:
pull_request_target:
types: [opened, labeled]

env:
FEDS_WATCH_HOOK: ${{ secrets.FEDS_WATCH_HOOK }}

jobs:
send_alert:
if: github.repository_owner == 'adobecom'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}

- name: Send Slack messages if a PR contains feds related files.
uses: actions/github-script@v7
with:
script: |
const main = require('./.github/workflows/check-feds-files.js')
main({ github, context })
1 change: 1 addition & 0 deletions .github/workflows/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,5 @@ module.exports = {
slackNotification,
pulls: { addLabels, addFiles, getChecks, getReviews },
isWithinRCP,
RCPDates,
};
91 changes: 45 additions & 46 deletions .github/workflows/merge-to-stage.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ const {
// Run from the root of the project for local testing: node --env-file=.env .github/workflows/merge-to-stage.js
const PR_TITLE = '[Release] Stage to Main';
const SEEN = {};
const REQUIRED_APPROVALS = process.env.REQUIRED_APPROVALS || 2;
const REQUIRED_APPROVALS = process.env.REQUIRED_APPROVALS ? Number(process.env.REQUIRED_APPROVALS) : 2;
const MAX_MERGES = process.env.MAX_PRS_PER_BATCH ? Number(process.env.MAX_PRS_PER_BATCH) : 8;
let existingPRCount = 0;
const STAGE = 'stage';
const PROD = 'main';
const LABELS = {
Expand All @@ -25,13 +27,13 @@ const TEAM_MENTIONS = [
'@adobecom/document-cloud-sot',
];
const SLACK = {
merge: ({ html_url, number, title, prefix = '' }) =>
`:merged: PR merged to stage: ${prefix} <${html_url}|${number}: ${title}>.`,
openedSyncPr: ({ html_url, number }) =>
`:fast_forward: Created <${html_url}|Stage to Main PR ${number}>`,
merge: ({ html_url, number, title, prefix = '' }) => `:merged: PR merged to stage: ${prefix} <${html_url}|${number}: ${title}>.`,
openedSyncPr: ({ html_url, number }) => `:fast_forward: Created <${html_url}|Stage to Main PR ${number}>`,
};

let github, owner, repo;
let github;
let owner;
let repo;

let body = `
## common base root URLs
Expand All @@ -49,11 +51,7 @@ let body = `
const isHighPrio = (labels) => labels.includes(LABELS.highPriority);
const isZeroImpact = (labels) => labels.includes(LABELS.zeroImpact);

const hasFailingChecks = (checks) =>
checks.some(
({ conclusion, name }) =>
name !== 'merge-to-stage' && conclusion === 'failure'
);
const hasFailingChecks = (checks) => checks.some(({ conclusion, name }) => name !== 'merge-to-stage' && conclusion === 'failure');

const commentOnPR = async (comment, prNumber) => {
console.log(comment); // Logs for debugging the action.
Expand Down Expand Up @@ -91,18 +89,15 @@ const getPRs = async () => {

prs = prs.filter(({ checks, reviews, number, title }) => {
if (hasFailingChecks(checks)) {
commentOnPR(
`Skipped merging ${number}: ${title} due to failing checks`,
number
);
commentOnPR(`Skipped merging ${number}: ${title} due to failing checks`, number);
return false;
}

const approvals = reviews.filter(({ state }) => state === 'APPROVED');
if (approvals.length < REQUIRED_APPROVALS) {
commentOnPR(
`Skipped merging ${number}: ${title} due to insufficient approvals. Required: ${REQUIRED_APPROVALS} approvals`,
number
number,
);
return false;
}
Expand All @@ -121,7 +116,7 @@ const getPRs = async () => {
}
return categorizedPRs;
},
{ zeroImpactPRs: [], highImpactPRs: [], normalPRs: [] }
{ zeroImpactPRs: [], highImpactPRs: [], normalPRs: [] },
);
};

Expand All @@ -130,11 +125,12 @@ const merge = async ({ prs, type }) => {

for await (const { number, files, html_url, title } of prs) {
try {
if (mergeLimitExceeded()) return;
const fileOverlap = files.find((file) => SEEN[file]);
if (fileOverlap) {
commentOnPR(
`Skipped ${number}: "${title}" due to file "${fileOverlap}" overlap. Merging will be attempted in the next batch`,
number
number,
);
continue;
}
Expand All @@ -150,6 +146,8 @@ const merge = async ({ prs, type }) => {
merge_method: 'squash',
});
}
existingPRCount++;
console.log(`Current number of PRs merged: ${existingPRCount}`);
const prefix = type === LABELS.zeroImpact ? ' [ZERO IMPACT]' : '';
body = `-${prefix} ${html_url}\n${body}`;
await slackNotification(
Expand All @@ -158,26 +156,25 @@ const merge = async ({ prs, type }) => {
number,
title,
prefix,
})
}),
).catch(console.error);
await new Promise((resolve) => setTimeout(resolve, 5000));
} catch (error) {
files.forEach((file) => (SEEN[file] = false));
commentOnPR(`Error merging ${number}: ${title} ` + error.message, number);
commentOnPR(`Error merging ${number}: ${title} ${error.message}`, number);
}
}
};

const getStageToMainPR = () =>
github.rest.pulls
.list({ owner, repo, state: 'open', base: PROD })
.then(({ data } = {}) => data.find(({ title } = {}) => title === PR_TITLE))
.then((pr) => pr && addLabels({ pr, github, owner, repo }))
.then((pr) => pr && addFiles({ pr, github, owner, repo }))
.then((pr) => {
pr?.files.forEach((file) => (SEEN[file] = true));
return pr;
});
const getStageToMainPR = () => github.rest.pulls
.list({ owner, repo, state: 'open', base: PROD })
.then(({ data } = {}) => data.find(({ title } = {}) => title === PR_TITLE))
.then((pr) => pr && addLabels({ pr, github, owner, repo }))
.then((pr) => pr && addFiles({ pr, github, owner, repo }))
.then((pr) => {
pr?.files.forEach((file) => (SEEN[file] = true));
return pr;
});

const openStageToMainPR = async () => {
const { data: comparisonData } = await github.rest.repos.compareCommits({
Expand All @@ -188,22 +185,19 @@ const openStageToMainPR = async () => {
});

for (const commit of comparisonData.commits) {
const { data: pullRequestData } =
await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner,
repo,
commit_sha: commit.sha,
});
const { data: pullRequestData } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner,
repo,
commit_sha: commit.sha,
});

for (const pr of pullRequestData) {
if (!body.includes(pr.html_url)) body = `- ${pr.html_url}\n${body}`;
}
}

try {
const {
data: { html_url, number },
} = await github.rest.pulls.create({
const { data: { html_url, number } } = await github.rest.pulls.create({
owner,
repo,
title: PR_TITLE,
Expand All @@ -222,29 +216,34 @@ const openStageToMainPR = async () => {
await slackNotification(SLACK.openedSyncPr({ html_url, number }));
await slackNotification(
SLACK.openedSyncPr({ html_url, number }),
process.env.MILO_STAGE_SLACK_WH
process.env.MILO_STAGE_SLACK_WH,
);
} catch (error) {
if (error.message.includes('No commits between main and stage'))
return console.log('No new commits, no stage->main PR opened');
if (error.message.includes('No commits between main and stage')) return console.log('No new commits, no stage->main PR opened');
throw error;
}
};

const mergeLimitExceeded = () => MAX_MERGES - existingPRCount < 0;

const main = async (params) => {
github = params.github;
owner = params.context.repo.owner;
repo = params.context.repo.repo;
if (isWithinRCP(2)) return console.log('Stopped, within RCP period.');
if (isWithinRCP(process.env.STAGE_RCP_OFFSET_DAYS || 2)) return console.log('Stopped, within RCP period.');

try {
const stageToMainPR = await getStageToMainPR();
console.log('has Stage to Main PR:', !!stageToMainPR);
if (stageToMainPR) body = stageToMainPR.body;
existingPRCount = body.match(/https:\/\/github\.com\/adobecom\/milo\/pull\/\d+/g)?.length || 0;
console.log(`Number of PRs already in the batch: ${existingPRCount}`);

if (mergeLimitExceeded()) return console.log(`Maximum number of '${MAX_MERGES}' PRs already merged. Stopping execution`);

const { zeroImpactPRs, highImpactPRs, normalPRs } = await getPRs();
await merge({ prs: zeroImpactPRs, type: LABELS.zeroImpact });
if (stageToMainPR?.labels.some((label) => label.includes(LABELS.SOTPrefix)))
return console.log('PR exists & testing started. Stopping execution.');
if (stageToMainPR?.labels.some((label) => label.includes(LABELS.SOTPrefix))) return console.log('PR exists & testing started. Stopping execution.');
await merge({ prs: highImpactPRs, type: LABELS.highPriority });
await merge({ prs: normalPRs, type: 'normal' });
if (!stageToMainPR) await openStageToMainPR();
Expand All @@ -254,7 +253,7 @@ const main = async (params) => {
owner,
repo,
pull_number: stageToMainPR.number,
body: body,
body,
});
}
console.log('Process successfully executed.');
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/merge-to-stage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ env:
REQUIRED_APPROVALS: ${{ secrets.REQUIRED_APPROVALS }}
SLACK_HIGH_IMPACT_PR_WEBHOOK: ${{ secrets.SLACK_HIGH_IMPACT_PR_WEBHOOK }}
MILO_STAGE_SLACK_WH: ${{secrets.MILO_STAGE_SLACK_WH}}
MAX_PRS_PER_BATCH: ${{secrets.MAX_PRS_PER_BATCH}}
STAGE_RCP_OFFSET_DAYS: ${{secrets.STAGE_RCP_OFFSET_DAYS}}

jobs:
merge-to-stage:
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/rcp-notifier.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const {
slackNotification,
getLocalConfigs,
RCPDates,
} = require('./helpers.js');

const isWithin24Hours = (targetDate) =>
Math.abs(new Date() - targetDate) <= 24 * 60 * 60 * 1000;

const calculateDateOffset = (date, offset) => {
const newDate = new Date(date);
newDate.setDate(newDate.getDate() - offset);
return newDate;
};

// Run from the root of the project for local testing: node --env-file=.env .github/workflows/rcp-notifier.js
const main = async () => {
console.log('Action: RCP Notifier started');
for (const rcp of RCPDates) {
const start = new Date(rcp.start);
const end = new Date(rcp.end);
const tenDaysBefore = calculateDateOffset(start, 10);
const fourDaysBefore = calculateDateOffset(start, 4);
const stageOffset = Number(process.env.STAGE_RCP_OFFSET_DAYS) || 2;
const slackText = (days) =>
`Reminder RCP starts in ${days} days: from ${start.toUTCString()} to ${end.toUTCString()}. Merges to stage will be disabled beginning ${calculateDateOffset(start, stageOffset).toUTCString()}.`;
if (isWithin24Hours(tenDaysBefore)) {
console.log('Is within 24 hours of 10 days before RCP');
await slackNotification(slackText(10), process.env.MILO_DEV_HOOK);
}

if (isWithin24Hours(fourDaysBefore)) {
console.log('Is within 24 hours of 4 days before RCP');
await slackNotification(slackText(4), process.env.MILO_DEV_HOOK);
}
}

console.log('Action: RCP Notifier completed');
};

if (process.env.LOCAL_RUN) {
const { context } = getLocalConfigs();
main({ context });
}

module.exports = main;
24 changes: 24 additions & 0 deletions .github/workflows/rcp-notifier.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: RCP Notifier

on:
schedule:
- cron: '43 9 * * *' # 9.43am UTC

env:
STAGE_RCP_OFFSET_DAYS: ${{ secrets.STAGE_RCP_OFFSET_DAYS }}
MILO_DEV_HOOK: ${{ secrets.MILO_DEV_HOOK }}

jobs:
rcp-notification:
if: github.repository_owner == 'adobecom'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Create RCP Notification
uses: actions/github-script@v7
with:
script: |
const main = require('./.github/workflows/rcp-notifier.js')
main({ github, context })
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ logs/*
**/mas/*/stats.json
test-html-results/
test-results/
test-a11y-results/
Loading

0 comments on commit 966fae7

Please sign in to comment.