From 7f196f54f5ad9a70e41a0186f537ea45f04c6afb Mon Sep 17 00:00:00 2001 From: Ruchika Sinha <69535463+Ruchika4@users.noreply.github.com> Date: Mon, 29 Apr 2024 08:04:54 -0700 Subject: [PATCH 01/11] [Release] Stage to Main (#2208) * Update code owners for feds (#2194) Co-authored-by: Blaine Gunn Co-authored-by: Robert Bogos <146744221+robert-bogos@users.noreply.github.com> * Harden preflight link checking (#2169) * Filter out empty `hrefs` before sending to spidy. * Harden link check to be more robust against Spidy API. * Update link check language. * Better onboarding support. * Adds fallbacks if `.milo/config` has not been added. * Fix for missing Word doc reference, under general tab. Resolves: [MWPW-146695](https://jira.corp.adobe.com/browse/MWPW-146695) Co-authored-by: Ryan Clayton * MWPW-146755: RTL merch icon padding (#2162) * RTL padding merch icon * dep update * MWPW-146001 parallelize literals call (#2187) * MWPW-146001 parallelize literals call * MWPW-146001 move promise to the function * MWPW-146001 fix commerce library * MWPW-146001 update literals endpoint * MWPW-146001 fixing commerce ut --------- Co-authored-by: Narcis Radu Co-authored-by: Blaine Gunn Co-authored-by: Robert Bogos <146744221+robert-bogos@users.noreply.github.com> Co-authored-by: Ryan Clayton Co-authored-by: Ryan Clayton Co-authored-by: Axel Cureno Basurto Co-authored-by: Nicolas Peltier <1032754+npeltier@users.noreply.github.com> --- CODEOWNERS | 4 +- libs/blocks/merch/merch.js | 14 +- libs/blocks/preflight/panels/general.js | 1 + libs/blocks/preflight/panels/seo.js | 192 +++++++++++++----------- libs/blocks/preflight/preflight.css | 45 +++--- libs/deps/commerce.js | 6 +- libs/deps/merch-card.js | 6 +- test/blocks/merch/merch.test.js | 5 +- 8 files changed, 155 insertions(+), 118 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index c384cc4a5a..109eed6e17 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -28,8 +28,8 @@ /libs/blocks/featured-article/ @meganthecoder @JasonHowellSlavin @Brandon32 @rgclayton /libs/blocks/form/ @elan-tbx @rgclayton /libs/blocks/fragment-personalization/ @chrischrischris -/libs/blocks/global-footer/ @overmyheadandbody @mokimo @narcis-radu -/libs/blocks/global-navigation/ @overmyheadandbody @mokimo @narcis-radu +/libs/blocks/global-footer/ @salonijain3 @bandana147 @sharmrj @nishantka @Deva309 @sonawanesnehal3 +/libs/blocks/global-navigation/ @salonijain3 @bandana147 @sharmrj @nishantka @Deva309 @sonawanesnehal3 /libs/blocks/how-to/ @fullcolorcoder @ryanmparrish /libs/blocks/icon-block/ @elan-tbx /libs/blocks/local-nav/ @seanchoi-dev diff --git a/libs/blocks/merch/merch.js b/libs/blocks/merch/merch.js index ea15332cd2..bd7578b4c2 100644 --- a/libs/blocks/merch/merch.js +++ b/libs/blocks/merch/merch.js @@ -3,7 +3,7 @@ import { } from '../../utils/utils.js'; import { replaceKey } from '../../features/placeholders.js'; -export const PRICE_LITERALS_URL = 'https://milo.adobe.com/libs/commerce/price-literals.json'; +export const PRICE_LITERALS_URL = 'https://www.adobe.com/federal/commerce/price-literals.json'; export const CHECKOUT_LINK_CONFIG_PATH = '/commerce/checkout-link.json'; // relative to libs. export const PRICE_TEMPLATE_DISCOUNT = 'discount'; @@ -142,6 +142,14 @@ export async function fetchEntitlements() { return fetchEntitlements.promise; } +export async function fetchLiterals(url) { + fetchLiterals.promise = fetchLiterals.promise ?? new Promise((resolve) => { + fetch(url) + .then((response) => response.json().then(({ data }) => resolve(data))); + }); + return fetchLiterals.promise; +} + export async function fetchCheckoutLinkConfigs(base = '') { fetchCheckoutLinkConfigs.promise = fetchCheckoutLinkConfigs.promise ?? fetch(`${base}${CHECKOUT_LINK_CONFIG_PATH}`).catch(() => { @@ -318,10 +326,10 @@ export async function initService(force = false) { fetchEntitlements.promise = undefined; fetchCheckoutLinkConfigs.promise = undefined; } + const { env, commerce = {}, locale } = getConfig(); + commerce.priceLiteralsPromise = fetchLiterals(PRICE_LITERALS_URL); initService.promise = initService.promise ?? polyfills().then(async () => { const commerceLib = await import('../../deps/commerce.js'); - const { env, commerce = {}, locale } = getConfig(); - commerce.priceLiteralsURL = PRICE_LITERALS_URL; const service = await commerceLib.init(() => ({ env, commerce, diff --git a/libs/blocks/preflight/panels/general.js b/libs/blocks/preflight/panels/general.js index 77793b762c..53d8fcda3c 100644 --- a/libs/blocks/preflight/panels/general.js +++ b/libs/blocks/preflight/panels/general.js @@ -131,6 +131,7 @@ function prettyPath(url) { function Item({ name, item, idx }) { const isChecked = item.checked ? ' is-checked' : ''; const isFetching = item.edit ? '' : ' is-fetching'; + if (!item.url) return undefined; return html`
arr.slice(i * size, i * size + size)); } -async function checkLinks() { - const { spidy } = await getServiceConfig(window.location.origin); - if (linksResult.value.checked) return; - - const connectionError = () => { - linksResult.value = { - icon: fail, - title: 'Links', - description: `A VPN connection is required to use the link check service. - Please turn on VPN and refresh the page. If VPN is running contact your site engineers for help.`, - }; +const connectionError = () => { + linksResult.value = { + icon: fail, + title: 'Links', + description: `A VPN connection is required to use the link check service. + Please turn on VPN and refresh the page. If VPN is running contact your site engineers for help.`, }; +}; - // Check to see if Spidy is available. +async function spidyCheck(url) { try { - const resp = await fetch(spidy.url, { method: 'HEAD' }); - if (!resp.ok) { - connectionError(); - return; - } + const resp = await fetch(url, { method: 'HEAD' }); + if (resp.ok) return true; + connectionError(); } catch (e) { connectionError(); - // eslint-disable-next-line no-console - console.error(`There was a problem connecting to the link check API ${spidy.url}. ${e}`); - return; + window.lana.log(`There was a problem connecting to the link check API ${url}. ${e}`); } + return false; +} + +async function getSpidyResults(url, opts) { + try { + const resp = await fetch(`${url}/api/url-http-status`, opts); + if (!resp.ok) return []; + + const json = await resp.json(); + if (!json.data || json.data.length === 0) return []; + + return json.data.reduce((acc, result) => { + const status = result.status === 'ECONNREFUSED' ? 503 : result.status; + if (status >= 399) { + result.status = status; + acc.push(result); + } + return acc; + }, []); + } catch (e) { + window.lana.log(`There was a problem connecting to the link check API ${url}/api/url-http-status. ${e}`); + return []; + } +} + +function compareResults(result, link) { + const match = link.liveHref === result.url; + if (!match) return false; + if (link.closest('header')) link.parent = 'gnav'; + if (link.closest('main')) link.parent = 'main'; + if (link.closest('footer')) link.parent = 'footer'; + link.classList.add('problem-link'); + link.status = result.status; + link.dataset.status = link.status; + return true; +} + +async function checkLinks() { + const { spidy, preflight } = await getServiceConfig(window.location.origin); + // Do not re-check if the page has already been checked + if (linksResult.value.checked) return; - const result = { ...linksResult.value }; + // Check to see if Spidy is available. + const spidyUrl = spidy?.url || SPIDY_URL_FALLBACK; + const canSpidy = await spidyCheck(spidyUrl); + if (!canSpidy) return; - /* Find all links. - * Remove any local or existing preflight links. - * Set link to use hlx.live + /** + * Find all links with an href. + * Filter out any local or existing preflight links. + * Set link to use hlx.live so the service can see them without auth * */ + const knownBadUrls = preflight?.ignoreDomains + ? preflight?.ignoreDomains.split(',').map((url) => url.trim()) + : KNOWN_BAD_URLS; + const links = [...document.querySelectorAll('a')] .filter((link) => { - if (!link.href.includes('local') && !link.closest('.preflight')) { - link.dataset.liveHref = link.href.replace('hlx.page', 'hlx.live'); + if ( + link.href // Has an href tag + && !link.href.includes('local') // Is not a local link + && !link.closest('.preflight') // Is not inside preflight + && !knownBadUrls.some((url) => url === link.hostname) // Is not a known bad url + ) { + link.liveHref = link.href.replace('hlx.page', 'hlx.live'); return true; } return false; }); + const groups = makeGroups(links); + const baseOpts = { method: 'POST', headers: { 'Content-Type': 'application/json' } }; + const badResults = []; for (const group of groups) { - const urls = group.map((link) => { - const { liveHref } = link.dataset; - return liveHref; - }); - const opts = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ urls }), - }; - let resp; - - try { - resp = await fetch(`${spidy.url}/api/url-http-status`, opts); - if (!resp.ok) return; - } catch (e) { - // eslint-disable-next-line no-console - console.error(`There was a problem connecting to the link check API ${spidy.url}/api/url-http-status. ${e}`); - } - - const json = await resp.json(); - if (!json) return; - json.data.forEach((linkResult) => { - const status = linkResult.status === 'ECONNREFUSED' ? 503 : linkResult.status; - // Response will come back out of order, use ID to find the correct index - group[linkResult.id].status = status; - - if (status >= 399) { - let parent = ''; - if (group[linkResult.id].closest('header')) parent = 'Gnav'; - if (group[linkResult.id].closest('main')) parent = 'Main content'; - if (group[linkResult.id].closest('footer')) parent = 'Footer'; - badLinks.value = [...badLinks.value, - { - // Diplay .hlx.live URL in broken link list for relative links - href: group[linkResult.id].dataset.liveHref, - status: group[linkResult.id].status, - parent, - }]; - group[linkResult.id].classList.add('broken-link'); - group[linkResult.id].dataset.status = status; - } - }); + const urls = group.map((link) => link.liveHref); + const opts = { ...baseOpts, body: JSON.stringify({ urls }) }; + const spidyResults = await getSpidyResults(spidyUrl, opts); + badResults.push(...spidyResults); } - if (badLinks.value.length) { - result.icon = fail; - result.description = `Reason: ${badLinks.value.length} broken link(s) found on the page. Use the list below to identify and fix them.`; - } + badLinks.value = badResults.map((result) => links.find((link) => compareResults(result, link))); - // No broken links - if (badLinks.value.length === 0) { - result.icon = pass; - result.description = 'Links are valid.'; - } - linksResult.value = { ...result, checked: true }; + // Format the results for display + const count = badLinks.value.length; + const linkText = count > 1 || count === 0 ? 'links' : 'link'; + const badDesc = `Reason: ${count} problem ${linkText}. Use the list below to fix them.`; + const goodDesc = 'Links are valid.'; + + // Build the result display object + linksResult.value = { + title: linksResult.value.title, + checked: true, + icon: count > 0 ? fail : pass, + description: count > 0 ? badDesc : goodDesc, + }; } export async function sendResults() { @@ -324,20 +340,20 @@ export default function Panel() { <${SeoItem} icon=${descResult.value.icon} title=${descResult.value.title} description=${descResult.value.description} />
-