Skip to content

Commit

Permalink
Merge branch 'stage' of https://github.com/adobecom/milo into mwpw-15…
Browse files Browse the repository at this point in the history
…9595-relative-urls
  • Loading branch information
robert-bogos committed Oct 21, 2024
2 parents ea07686 + 332c3a5 commit b2f54da
Show file tree
Hide file tree
Showing 37 changed files with 760 additions and 109 deletions.
89 changes: 44 additions & 45 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,15 +216,16 @@ 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;
Expand All @@ -241,10 +236,14 @@ const main = async (params) => {
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
1 change: 1 addition & 0 deletions .github/workflows/merge-to-stage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ 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}}

jobs:
merge-to-stage:
Expand Down
5 changes: 0 additions & 5 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,9 @@
# See https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners

# Milo Core (alphabetical order)
/fstab.yaml @adobecom/admins
/libs/features/ @adobecom/admins
/libs/features/seotech/ @hparra
/libs/features/title-append/ @hparra
/libs/martech/ @adobecom/admins
/libs/scripts/ @adobecom/admins
/libs/scripts/taxonomy.js @meganthecoder @JasonHowellSlavin @Brandon32 @rgclayton
/libs/utils/ @adobecom/admins

# Milo Blocks (alphabetical order)
/libs/blocks/accordion/ @fullcolorcoder @ryanmparrish
Expand Down
32 changes: 25 additions & 7 deletions libs/blocks/fragment/fragment.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,32 @@ const removeHash = (url) => {
const isCircularRef = (href) => [...Object.values(fragMap)]
.some((tree) => {
const node = tree.find(href);
return node ? !(node.isLeaf) : false;
return node?.isRecursive;
});

const updateFragMap = (fragment, a, href) => {
const fragLinks = [...fragment.querySelectorAll('a')]
.filter((link) => localizeLink(link.href).includes('/fragments/'));
if (!fragLinks.length) return;

if (document.body.contains(a)) { // is fragment on page (not nested)
if (document.body.contains(a) && !a.parentElement?.closest('.fragment')) {
// eslint-disable-next-line no-use-before-define
fragMap[href] = new Tree(href);
fragLinks.forEach((link) => fragMap[href].insert(href, localizeLink(removeHash(link.href))));
} else {
Object.values(fragMap).forEach((tree) => {
if (tree.find(href)) {
fragLinks.forEach((link) => tree.insert(href, localizeLink(removeHash(link.href))));
}
const hrefNode = tree.find(href);
if (!hrefNode) return;

fragLinks.forEach((link) => {
const localizedHref = localizeLink(removeHash(link.href));
const parentNodeSameHref = hrefNode.findParent(localizedHref);
if (parentNodeSameHref) {
parentNodeSameHref.isRecursive = true;
} else {
hrefNode.addChild(localizedHref);
}
});
});
}
};
Expand Down Expand Up @@ -143,10 +152,19 @@ class Node {
this.value = value;
this.parent = parent;
this.children = [];
this.isRecursive = false;
}

addChild(key, value = key) {
const alreadyHasChild = this.children.some((n) => n.key === key);
if (!alreadyHasChild) {
this.children.push(new Node(key, value, this));
}
}

get isLeaf() {
return this.children.length === 0;
findParent(key) {
if (this.parent?.key === key) return this.parent;
return this.parent?.findParent(key);
}
}

Expand Down
17 changes: 9 additions & 8 deletions libs/blocks/global-navigation/global-navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import {

import { replaceKey, replaceKeyArray } from '../../features/placeholders.js';

const SIGNIN_CONTEXT = getConfig()?.signInContext;

function getHelpChildren() {
const { unav } = getConfig();
return unav?.unavHelpChildren || [
Expand Down Expand Up @@ -94,8 +96,8 @@ export const CONFIG = {
},
},
callbacks: {
onSignIn: () => { window.adobeIMS?.signIn(); },
onSignUp: () => { window.adobeIMS?.signIn(); },
onSignIn: () => { window.adobeIMS?.signIn(SIGNIN_CONTEXT); },
onSignUp: () => { window.adobeIMS?.signIn(SIGNIN_CONTEXT); },
},
},
},
Expand Down Expand Up @@ -147,13 +149,12 @@ export const LANGMAP = {
};

// signIn, decorateSignIn and decorateProfileTrigger can be removed if IMS takes over the profile
const signIn = () => {
const signIn = (options = {}) => {
if (typeof window.adobeIMS?.signIn !== 'function') {
lanaLog({ message: 'IMS signIn method not available', tags: 'errorType=warn,module=gnav' });
return;
}

window.adobeIMS.signIn();
window.adobeIMS.signIn(options);
};

const decorateSignIn = async ({ rawElem, decoratedElem }) => {
Expand All @@ -166,7 +167,7 @@ const decorateSignIn = async ({ rawElem, decoratedElem }) => {

signInElem.addEventListener('click', (e) => {
e.preventDefault();
signIn();
signIn(SIGNIN_CONTEXT);
});
} else {
signInElem = toFragment`<button daa-ll="${signInLabel}" class="feds-signIn" aria-expanded="false" aria-haspopup="true">${signInLabel}</button>`;
Expand All @@ -183,7 +184,7 @@ const decorateSignIn = async ({ rawElem, decoratedElem }) => {
dropdownSignInAnchor.replaceWith(dropdownSignInButton);
dropdownSignInButton.addEventListener('click', (e) => {
e.preventDefault();
signIn();
signIn(SIGNIN_CONTEXT);
});
} else {
lanaLog({ message: 'Sign in link not found in dropdown.', tags: 'errorType=warn,module=gnav' });
Expand Down Expand Up @@ -354,6 +355,7 @@ class Gnav {
${this.decorateBrand()}
</div>
${this.elements.navWrapper}
${getConfig().searchEnabled === 'on' ? toFragment`<div class="feds-client-search"></div>` : ''}
${this.useUniversalNav ? this.blocks.universalNav : ''}
${(!this.useUniversalNav && this.blocks.profile.rawElem) ? this.blocks.profile.decoratedElem : ''}
${this.decorateLogo()}
Expand Down Expand Up @@ -838,7 +840,6 @@ class Gnav {
${isDesktop.matches ? '' : this.decorateSearch()}
${this.elements.mainNav}
${isDesktop.matches ? this.decorateSearch() : ''}
${getConfig().searchEnabled === 'on' ? toFragment`<div class="feds-client-search"></div>` : ''}
</div>
`;

Expand Down
1 change: 0 additions & 1 deletion libs/blocks/global-navigation/utilities/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,6 @@ export async function fetchAndProcessPlainHtml({ url, shouldDecorateLinks = true
if (mepFragment?.targetManifestId) body.dataset.adobeTargetTestid = mepFragment.targetManifestId;
const commands = mepGnav?.commands;
if (commands?.length) {
/* c8 ignore next 4 */
const { handleCommands, deleteMarkedEls } = await import('../../../features/personalization/personalization.js');
handleCommands(commands, body, true, true);
deleteMarkedEls(body);
Expand Down
3 changes: 2 additions & 1 deletion libs/blocks/gnav/gnav.js
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,8 @@ class Gnav {
}
signInEl.addEventListener('click', (e) => {
e.preventDefault();
window.adobeIMS.signIn();
const { signInContext } = getConfig();
window.adobeIMS.signIn(signInContext);
});
profileEl.append(signIn);
};
Expand Down
Loading

0 comments on commit b2f54da

Please sign in to comment.