Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Release] Stage to Main #2479

Merged
merged 7 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/label-zero-impact.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { getLocalConfigs } = require('./helpers.js');

Check warning on line 1 in .github/workflows/label-zero-impact.js

View workflow job for this annotation

GitHub Actions / Running eslint

[eslint] reported by reviewdog 🐶 File ignored by default. Use a negated ignore pattern (like "--ignore-pattern '!<relative/path/to/filename>'") to override. Raw Output: {"fatal":false,"severity":1,"message":"File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!<relative/path/to/filename>'\") to override."}
const zeroImpactDirs = [
'.github',
'.kodiak',
Expand All @@ -11,6 +11,7 @@
'LICENSE',
'codecov.yaml',
'.gitignore',
'package-lock.json',
];
const zeroImpactLabel = 'zero-impact';

Expand Down
43 changes: 39 additions & 4 deletions .github/workflows/merge-to-stage.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const {

Check warning on line 1 in .github/workflows/merge-to-stage.js

View workflow job for this annotation

GitHub Actions / Running eslint

[eslint] reported by reviewdog 🐶 File ignored by default. Use a negated ignore pattern (like "--ignore-pattern '!<relative/path/to/filename>'") to override. Raw Output: {"fatal":false,"severity":1,"message":"File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!<relative/path/to/filename>'\") to override."}
slackNotification,
getLocalConfigs,
isWithinRCP,
Expand Down Expand Up @@ -55,6 +55,28 @@
name !== 'merge-to-stage' && conclusion === 'failure'
);

const commentOnPR = async (comment, prNumber) => {
console.log(comment); // Logs for debugging the action.
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number: prNumber,
});

const dayAgo = new Date(new Date().getTime() - 24 * 60 * 60 * 1000);
const hasRecentComment = comments
.filter(({ created_at }) => new Date(created_at) > dayAgo)
.some(({ body }) => body === comment);
if (hasRecentComment) return console.log('Comment exists for', prNumber);

await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: comment,
});
};

const getPRs = async () => {
let prs = await github.rest.pulls
.list({ owner, repo, state: 'open', per_page: 100, base: STAGE })
Expand All @@ -69,13 +91,19 @@

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

const approvals = reviews.filter(({ state }) => state === 'APPROVED');
if (approvals.length < REQUIRED_APPROVALS) {
console.log(`Skipping ${number}: ${title} due to insufficient approvals`);
commentOnPR(
`Skipped merging ${number}: ${title} due to insufficient approvals. Required: ${REQUIRED_APPROVALS} approvals`,
number
);
return false;
}

Expand Down Expand Up @@ -103,7 +131,10 @@
for await (const { number, files, html_url, title } of prs) {
try {
if (files.some((file) => SEEN[file])) {
console.log(`Skipping ${number}: ${title} due to overlap in files.`);
commentOnPR(
`Skipped ${number}: ${title} due to file overlap. Merging will be attempted in the next batch`,
number
);
continue;
}
if (type !== LABELS.zeroImpact) {
Expand All @@ -130,7 +161,7 @@
);
await new Promise((resolve) => setTimeout(resolve, 5000));
} catch (error) {
console.log(`Error merging ${number}: ${title}`, error.message);
commentOnPR(`Error merging ${number}: ${title} ` + error.message, number);
}
}
};
Expand Down Expand Up @@ -187,6 +218,10 @@
});

await slackNotification(SLACK.openedSyncPr({ html_url, number }));
await slackNotification(
SLACK.openedSyncPr({ html_url, number }),
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');
Expand Down
2 changes: 1 addition & 1 deletion libs/blocks/marketo-config/marketo-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
} catch {
const keyValuePairs = field.options.split(',').map((item) => {
const [key, val] = item.trim().split(':');
return key.trim() ? [key.trim(), `${(val || key).trim()} (${key.trim()})`] : ['', 'Choose an option...'];
return key.trim() ? [key.trim(), `${(val || key).trim()}`] : ['', 'Choose an option...'];
});
options = Object.fromEntries(keyValuePairs);
}
Expand Down Expand Up @@ -184,7 +184,7 @@
const validatedState = validateState(state, panelsData);
setPreferences(validatedState);
saveStateToLocalStorage(validatedState, lsKey);
}, [state]);

Check warning on line 187 in libs/blocks/marketo-config/marketo-config.js

View workflow job for this annotation

GitHub Actions / Running eslint

[eslint] reported by reviewdog 🐶 React Hook useEffect has missing dependencies: 'lsKey' and 'panelsData'. Either include them or remove the dependency array. Raw Output: {"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useEffect has missing dependencies: 'lsKey' and 'panelsData'. Either include them or remove the dependency array.","line":187,"column":6,"nodeType":"ArrayExpression","endLine":187,"endColumn":13,"suggestions":[{"desc":"Update the dependencies array to be: [lsKey, panelsData, state]","fix":{"range":[5511,5518],"text":"[lsKey, panelsData, state]"}}]}

useEffect(() => {
const url = getUrl();
Expand All @@ -194,7 +194,7 @@
const contentEl = document.querySelector('.content-panel');
contentEl.append(newBlockEl);
loadBlock(newBlockEl);
}, []);

Check warning on line 197 in libs/blocks/marketo-config/marketo-config.js

View workflow job for this annotation

GitHub Actions / Running eslint

[eslint] reported by reviewdog 🐶 React Hook useEffect has missing dependencies: 'blockClass' and 'getUrl'. Either include them or remove the dependency array. Raw Output: {"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useEffect has missing dependencies: 'blockClass' and 'getUrl'. Either include them or remove the dependency array.","line":197,"column":6,"nodeType":"ArrayExpression","endLine":197,"endColumn":8,"suggestions":[{"desc":"Update the dependencies array to be: [blockClass, getUrl]","fix":{"range":[5892,5894],"text":"[blockClass, getUrl]"}}]}

return html`
<div class="tool-header">
Expand Down
5 changes: 5 additions & 0 deletions libs/blocks/merch-card/merch-card.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ div[class*="-merch-card"] > div,
color: var(--text-color);
}

.twp.merch-card .merch-card-price {
font-weight: 700;
margin: 0;
}

merch-card.special-offers del span[is="inline-price"] {
text-decoration: line-through;
}
Expand Down
187 changes: 119 additions & 68 deletions libs/blocks/merch-card/merch-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,24 @@ import '../../deps/merch-card.js';

const TAG_PATTERN = /^[a-zA-Z0-9_-]+:[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-].*$/;

const CARD_TYPES = ['segment', 'special-offers', 'plans', 'catalog', 'product', 'inline-heading', 'image', 'mini-compare-chart'];
const SEGMENT = 'segment';
const SPECIAL_OFFERS = 'special-offers';
const PLANS = 'plans';
const CATALOG = 'catalog';
const PRODUCT = 'product';
const MINI_COMPARE_CHART = 'mini-compare-chart';
const TWP = 'twp';
const CARD_TYPES = [
SEGMENT,
SPECIAL_OFFERS,
PLANS,
CATALOG,
PRODUCT,
'inline-heading',
'image',
MINI_COMPARE_CHART,
TWP,
];

const CARD_SIZES = ['wide', 'super-wide'];

Expand All @@ -25,13 +42,9 @@ const HEADING_MAP = {
},
};

const MINI_COMPARE_CHART = 'mini-compare-chart';
const PLANS = 'plans';
const SEGMENT = 'segment';

const INNER_ELEMENTS_SELECTOR = 'h2, h3, h4, h5, p, ul, em';

const MULTI_OFFER_CARDS = ['plans', 'product', MINI_COMPARE_CHART];
const MULTI_OFFER_CARDS = [PLANS, PRODUCT, MINI_COMPARE_CHART, TWP];
// Force cards to refresh once they become visible so that the footer rows are properly aligned.
const intersectionObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
Expand All @@ -46,10 +59,10 @@ const getPodType = (styles) => styles?.find((style) => CARD_TYPES.includes(style
const isHeadingTag = (tagName) => /^H[2-5]$/.test(tagName);
const isParagraphTag = (tagName) => tagName === 'P';

const appendSlot = (slotEls, slotName, merchCard) => {
const appendSlot = (slotEls, slotName, merchCard, nodeName = 'p') => {
if (slotEls.length === 0 || merchCard.variant !== MINI_COMPARE_CHART) return;
const newEl = createTag(
'p',
nodeName,
{ slot: slotName, class: slotName },
);
slotEls.forEach((e) => {
Expand All @@ -72,6 +85,68 @@ export async function loadMnemonicList(foreground) {
}
}

function extractQuantitySelect(el) {
const quantitySelectConfig = [...el.querySelectorAll('ul')]
.find((ul) => ul.querySelector('li')?.innerText?.includes('Quantity'));
const configMarkup = quantitySelectConfig?.querySelector('ul');
if (!configMarkup) return null;
const config = configMarkup.children;
if (config.length !== 2) return null;
const attributes = {};
attributes.title = config[0].textContent.trim();
const values = config[1].textContent.split(',')
.map((value) => value.trim())
.filter((value) => /^\d*$/.test(value))
.map((value) => (value === '' ? undefined : Number(value)));
quantitySelectConfig.remove();
if (![3, 4, 5].includes(values.length)) return null;
import('../../deps/merch-quantity-select.js');
[attributes.min, attributes.max, attributes.step, attributes['default-value'], attributes['max-input']] = values;
const quantitySelect = createTag('merch-quantity-select', attributes);
return quantitySelect;
}

const parseTwpContent = async (el, merchCard) => {
const quantitySelect = extractQuantitySelect(el);
if (quantitySelect) {
merchCard.append(quantitySelect);
}
let allElements = el?.children[0]?.children[0]?.children;
if (!allElements?.length) return;
allElements = [...allElements];
const contentGroups = allElements.reduce((acc, curr) => {
if (curr.tagName.toLowerCase() === 'p' && curr.textContent.trim() === '--') {
acc.push([]);
} else {
acc[acc.length - 1].push(curr);
}
return acc;
}, [[]]);

contentGroups.forEach((group, index) => {
if (index === 0) { // Top section
const headings = group.filter((e) => e.tagName.toLowerCase() === 'h3');
const topBody = group.filter((e) => e.tagName.toLowerCase() === 'p');
appendSlot(headings, 'heading-xs', merchCard);
appendSlot(topBody, 'body-xs-top', merchCard);
} else if (index === 1) { // Body section
const content = group.filter((e) => e.tagName.toLowerCase() === 'p' || e.tagName.toLowerCase() === 'ul');
const bodySlot = createTag('div', { slot: 'body-xs' }, content);
merchCard.append(bodySlot);
} else if (index === 2) { // Footer section
const footerContent = group.filter((e) => ['h5', 'p'].includes(e.tagName.toLowerCase()));
const footer = createTag('div', { slot: 'footer' }, footerContent);
merchCard.append(footer);
}
});

const offerSelection = el.querySelector('ul');
if (offerSelection) {
const { initOfferSelection } = await import('./merch-offer-select.js');
await initOfferSelection(merchCard, offerSelection);
}
};

const parseContent = async (el, merchCard) => {
let bodySlotName = `body-${merchCard.variant !== MINI_COMPARE_CHART ? 'xs' : 'm'}`;
let headingMCount = 0;
Expand Down Expand Up @@ -201,7 +276,7 @@ const decorateMerchCardLinkAnalytics = (el) => {
};

const addStock = (merchCard, styles) => {
if (styles.includes('add-stock')) {
if (styles.includes('add-stock') && merchCard.variant !== TWP) {
let stock;
const selector = styles.includes('edu') ? '.merch-offers.stock.edu > *' : '.merch-offers.stock > *';
const [label, ...rest] = [...document.querySelectorAll(selector)];
Expand All @@ -225,27 +300,6 @@ const simplifyHrs = (el) => {
});
};

async function extractQuantitySelect(el) {
const quantitySelectConfig = el.querySelector('ul');
if (!quantitySelectConfig) return null;
const configMarkup = quantitySelectConfig.querySelector('li');
if (!configMarkup || !configMarkup.textContent.includes('Quantity')) return null;
const config = configMarkup.querySelector('ul').querySelectorAll('li');
if (config.length !== 2) return null;
const attributes = {};
attributes.title = config[0].textContent.trim();
const values = config[1].textContent.split(',')
.map((value) => value.trim())
.filter((value) => /^\d*$/.test(value))
.map((value) => (value === '' ? undefined : Number(value)));
if (![3, 4, 5].includes(values.length)) return null;
await import('../../deps/merch-quantity-select.js');
[attributes.min, attributes.max, attributes.step, attributes['default-value'], attributes['max-input']] = values;
const quantitySelect = createTag('merch-quantity-select', attributes);
quantitySelectConfig.remove();
return quantitySelect;
}

const getMiniCompareChartFooterRows = (el) => {
let footerRows = Array.from(el.children).slice(1);
footerRows = footerRows.filter((row) => !row.querySelector('.footer-row-cell'));
Expand Down Expand Up @@ -289,7 +343,7 @@ const setMiniCompareOfferSlot = (merchCard, offers) => {
export default async function init(el) {
if (!el.querySelector(INNER_ELEMENTS_SELECTOR)) return el;
const styles = [...el.classList];
const cardType = getPodType(styles) || 'product';
const cardType = getPodType(styles) || PRODUCT;
if (!styles.includes(cardType)) {
styles.push(cardType);
}
Expand Down Expand Up @@ -344,7 +398,6 @@ export default async function init(el) {
);
merchCard.setAttribute('badge-color', badge.badgeColor);
merchCard.setAttribute('badge-text', badge.badgeText);
if (document.querySelector('html').dir === 'rtl') merchCard.setAttribute('is-rtl', 'true');
merchCard.classList.add('badge-card');
}
}
Expand Down Expand Up @@ -372,7 +425,7 @@ export default async function init(el) {
}
}
});
const actionMenuContent = cardType === 'catalog'
const actionMenuContent = cardType === CATALOG
? getActionMenuContent(el)
: null;
if (actionMenuContent) {
Expand All @@ -397,7 +450,6 @@ export default async function init(el) {
imageSlot.appendChild(image);
merchCard.appendChild(imageSlot);
}
parseContent(el, merchCard);
if (!icons || icons.length > 0) {
const iconImgs = Array.from(icons).map((icon) => {
const img = {
Expand All @@ -421,46 +473,45 @@ export default async function init(el) {
merchCard.setAttribute('filters', categories.join(','));
merchCard.setAttribute('types', types.join(','));

const footer = createTag('div', { slot: 'footer' });
if (ctas) {
if (merchCard.variant === 'mini-compare-chart') {
decorateButtons(ctas, 'button-l');
} else {
decorateButtons(ctas);
}
const links = ctas.querySelectorAll('a');
ctas.remove();
footer.append(...links);
}
merchCard.appendChild(footer);
if (merchCard.variant !== TWP) {
parseContent(el, merchCard);

if (MULTI_OFFER_CARDS.includes(cardType)) {
if (merchCard.variant === MINI_COMPARE_CHART) {
const miniCompareOffers = createTag('div', { slot: 'offers' });
merchCard.append(miniCompareOffers);
}
const quantitySelect = await extractQuantitySelect(el, cardType);
const offerSelection = el.querySelector('ul');
if (offerSelection) {
const { initOfferSelection } = await import('./merch-offer-select.js');
setMiniCompareOfferSlot(merchCard, undefined);
initOfferSelection(merchCard, offerSelection, quantitySelect);
const footer = createTag('div', { slot: 'footer' });
if (ctas) {
decorateButtons(ctas, (merchCard.variant === MINI_COMPARE_CHART) ? 'button-l' : undefined);
footer.append(ctas);
}
if (quantitySelect) {
merchCard.appendChild(footer);

if (MULTI_OFFER_CARDS.includes(cardType)) {
const quantitySelect = extractQuantitySelect(el);
const offerSelection = el.querySelector('ul');
if (merchCard.variant === MINI_COMPARE_CHART) {
setMiniCompareOfferSlot(merchCard, quantitySelect);
} else {
const bodySlot = merchCard.querySelector('div[slot="body-xs"]');
bodySlot.append(quantitySelect);
const miniCompareOffers = createTag('div', { slot: 'offers' });
merchCard.append(miniCompareOffers);
}
if (offerSelection) {
const { initOfferSelection } = await import('./merch-offer-select.js');
setMiniCompareOfferSlot(merchCard, undefined);
initOfferSelection(merchCard, offerSelection, quantitySelect);
}
if (quantitySelect) {
if (merchCard.variant === MINI_COMPARE_CHART) {
setMiniCompareOfferSlot(merchCard, quantitySelect);
} else {
const bodySlot = merchCard.querySelector('div[slot="body-xs"]');
bodySlot.append(quantitySelect);
}
}
}

decorateBlockHrs(merchCard);
simplifyHrs(merchCard);
if (merchCard.classList.contains('has-divider')) merchCard.setAttribute('custom-hr', true);
decorateFooterRows(merchCard, footerRows);
} else {
parseTwpContent(el, merchCard);
}
decorateBlockHrs(merchCard);
simplifyHrs(merchCard);
if (merchCard.classList.contains('has-divider')) {
merchCard.setAttribute('custom-hr', true);
}
decorateFooterRows(merchCard, footerRows);
el.replaceWith(merchCard);
decorateMerchCardLinkAnalytics(merchCard);
return merchCard;
Expand Down
Loading
Loading