diff --git a/.github/workflows/high-impact-alert.js b/.github/workflows/high-impact-alert.js index 9a5a5833fb..5858af61b5 100644 --- a/.github/workflows/high-impact-alert.js +++ b/.github/workflows/high-impact-alert.js @@ -13,9 +13,12 @@ const main = async (params) => { } const { html_url, number, title } = context.payload.pull_request; - console.log('High impact label detected, sending Slack notification'); + console.log('High impact label detected, sending Slack notifications'); slackNotification(`:alert: High Impact PR has been opened: <${html_url}|#${number}: ${title}>.` + ` Please prioritize testing the proposed changes.`, process.env.SLACK_HIGH_IMPACT_PR_WEBHOOK); + slackNotification(`:alert: High Impact PR has been opened: <${html_url}|#${number}: ${title}>.` + + ` Please review the PR details promptly and raise any concerns or questions.`, + process.env.SLACK_MILO_UPDATES_WEBHOOK); } catch (error) { console.error(error); } diff --git a/.github/workflows/high-impact-alert.yml b/.github/workflows/high-impact-alert.yml index 9fa01dacc2..3c3ea56f2d 100644 --- a/.github/workflows/high-impact-alert.yml +++ b/.github/workflows/high-impact-alert.yml @@ -7,6 +7,7 @@ on: env: SLACK_HIGH_IMPACT_PR_WEBHOOK: ${{ secrets.SLACK_HIGH_IMPACT_PR_WEBHOOK }} + SLACK_MILO_UPDATES_WEBHOOK: ${{ secrets.SLACK_MILO_UPDATES_WEBHOOK }} jobs: send_alert: diff --git a/libs/blocks/accordion/accordion.css b/libs/blocks/accordion/accordion.css index 16a9f4a11b..0cdc4e2c2d 100644 --- a/libs/blocks/accordion/accordion.css +++ b/libs/blocks/accordion/accordion.css @@ -32,7 +32,7 @@ dl.accordion { line-height: var(--type-heading-s-lh); padding: var(--spacing-s) var(--spacing-m) var(--spacing-s) var(--spacing-xs); position: relative; - text-align: left; + text-align: start; width: 100%; -webkit-text-size-adjust: 100%; } diff --git a/libs/blocks/global-navigation/global-navigation.js b/libs/blocks/global-navigation/global-navigation.js index 0242698af9..a83ba5d6bc 100644 --- a/libs/blocks/global-navigation/global-navigation.js +++ b/libs/blocks/global-navigation/global-navigation.js @@ -405,13 +405,15 @@ class Gnav { try { this.block.removeEventListener('click', this.loadDelayed); this.block.removeEventListener('keydown', this.loadDelayed); - const [ - Search, - ] = await Promise.all([ - loadBlock('../features/search/gnav-search.js'), - loadStyles(rootPath('features/search/gnav-search.css')), - ]); - this.Search = Search; + if (this.searchPresent()) { + const [ + Search, + ] = await Promise.all([ + loadBlock('../features/search/gnav-search.js'), + loadStyles(rootPath('features/search/gnav-search.css')), + ]); + this.Search = Search; + } if (!this.useUniversalNav) { const [ProfileDropdown] = await Promise.all([ diff --git a/libs/navigation/bootstrapper.js b/libs/navigation/bootstrapper.js index e98941060b..e644702992 100644 --- a/libs/navigation/bootstrapper.js +++ b/libs/navigation/bootstrapper.js @@ -1,8 +1,6 @@ -export default async function bootstrapBlock(miloConfigs, blockConfig) { - const { miloLibs } = miloConfigs; +export default async function bootstrapBlock(miloLibs, blockConfig) { const { name, targetEl } = blockConfig; - const { getConfig, setConfig, createTag, loadLink, loadScript } = await import(`${miloLibs}/utils/utils.js`); - setConfig({ ...miloConfigs }); + const { getConfig, createTag, loadLink, loadScript } = await import(`${miloLibs}/utils/utils.js`); const { default: initBlock } = await import(`${miloLibs}/blocks/${name}/${name}.js`); const styles = [`${miloLibs}/blocks/${name}/${name}.css`, `${miloLibs}/navigation/navigation.css`]; @@ -12,6 +10,24 @@ export default async function bootstrapBlock(miloConfigs, blockConfig) { const block = createTag(targetEl, { class: name }); document.body[blockConfig.appendType](block); } + // Configure Unav components and redirect uri + if (blockConfig.targetEl === 'header') { + const metaTags = [ + { key: 'unavComponents', name: 'universal-nav' }, + { key: 'redirect', name: 'adobe-home-redirect' }, + ]; + metaTags.forEach((tag) => { + const { key } = tag; + if (blockConfig[key]) { + const metaTag = createTag('meta', { + name: tag.name, + content: blockConfig[key], + }); + document.head.append(metaTag); + } + }); + } + initBlock(document.querySelector(targetEl)); if (blockConfig.targetEl === 'footer') { const { loadPrivacy } = await import(`${miloLibs}/scripts/delayed.js`); diff --git a/libs/navigation/navigation.css b/libs/navigation/navigation.css index e3ef7f1241..96ac72d5de 100644 --- a/libs/navigation/navigation.css +++ b/libs/navigation/navigation.css @@ -1,5 +1,5 @@ /* Extracting the essential styles required for rendering the component independently */ - .global-footer, .dialog-modal { + .global-navigation, .global-footer, .dialog-modal { font-family: 'Adobe Clean', adobe-clean, 'Trebuchet MS', sans-serif; line-height: 27px; color: #2c2c2c; @@ -7,7 +7,7 @@ -webkit-font-smoothing: antialiased; } -.global-footer a, .dialog-modal a { +.global-navigation a, .global-footer a, .dialog-modal a { text-decoration: none; } @@ -15,7 +15,16 @@ color: #035FE6; } -.global-footer img { +.global-navigation img, .global-footer img { max-width: 100%; height: auto; } + +.global-navigation { + font-size: 18px; +} + +header.global-navigation { + height: 64px; + visibility: hidden; +} diff --git a/libs/navigation/navigation.js b/libs/navigation/navigation.js index 5329b5a87d..c74bcaed2e 100644 --- a/libs/navigation/navigation.js +++ b/libs/navigation/navigation.js @@ -1,35 +1,73 @@ -const blockConfig = { - footer: { +const blockConfig = [ + { + key: 'header', + name: 'global-navigation', + targetEl: 'header', + appendType: 'prepend', + params: ['imsClientId'], + }, + { + key: 'footer', name: 'global-footer', targetEl: 'footer', appendType: 'appendChild', + params: ['privacyId', 'privacyLoadDelay'], }, -}; +]; const envMap = { prod: 'https://www.adobe.com', stage: 'https://www.stage.adobe.com', - qa: 'https://feds--milo--adobecom.hlx.page', + qa: 'https://gnav--milo--adobecom.hlx.page', }; + +function getParamsConfigs(configs) { + return blockConfig.reduce((acc, block) => { + block.params.forEach((param) => { + const value = configs[block.key]?.[param]; + if (value !== undefined) { + acc[param] = value; + } + }); + return acc; + }, {}); +} + export default async function loadBlock(configs, customLib) { - const { footer, locale, env = 'prod' } = configs || {}; + const { header, footer, authoringPath, env = 'prod', locale = '' } = configs || {}; const branch = new URLSearchParams(window.location.search).get('navbranch'); const miloLibs = branch ? `https://${branch}--milo--adobecom.hlx.page` : customLib || envMap[env]; - + if (!header && !footer) { + console.error('Global navigation Error: header and footer configurations are missing.'); + return; + } // Relative path can't be used, as the script will run on consumer's app - const { default: bootstrapBlock } = await import(`${miloLibs}/libs/navigation/bootstrapper.js`); - const { default: locales } = await import(`${miloLibs}/libs/utils/locales.js`); + const [{ default: bootstrapBlock }, { default: locales }, { setConfig }] = await Promise.all([ + import(`${miloLibs}/libs/navigation/bootstrapper.js`), + import(`${miloLibs}/libs/utils/locales.js`), + import(`${miloLibs}/libs/utils/utils.js`), + ]); + + const paramConfigs = getParamsConfigs(configs, miloLibs); const clientConfig = { - origin: miloLibs, + origin: `https://main--federal--adobecom.hlx.${env === 'prod' ? 'live' : 'page'}`, miloLibs: `${miloLibs}/libs`, - pathname: `/${locale || ''}`, + pathname: `/${locale}`, locales: configs.locales || locales, + contentRoot: authoringPath || footer.authoringPath, + ...paramConfigs, }; - if (footer) { - const { authoringPath, privacyId, privacyLoadDelay = 3000 } = footer; - blockConfig.delay = privacyLoadDelay; - bootstrapBlock({ ...clientConfig, contentRoot: authoringPath, privacyId }, blockConfig.footer); - } + setConfig(clientConfig); + + blockConfig.forEach((block) => { + const configBlock = configs[block.key]; + if (configBlock) { + bootstrapBlock(`${miloLibs}/libs`, { + ...block, + ...(block.key === 'header' && { unavComponents: configBlock.unavComponents, redirect: configBlock.redirect }), + }); + } + }); } window.loadNavigation = loadBlock; diff --git a/libs/utils/utils.js b/libs/utils/utils.js index 2cd929eb77..4402f394ae 100644 --- a/libs/utils/utils.js +++ b/libs/utils/utils.js @@ -696,7 +696,7 @@ function decorateHeader() { header.remove(); return; } - header.className = headerMeta || 'gnav'; + header.className = headerMeta || 'global-navigation'; const metadataConfig = getMetadata('breadcrumbs')?.toLowerCase() || getConfig().breadcrumbs; if (metadataConfig === 'off') return; @@ -738,7 +738,7 @@ async function loadFooter() { footer.remove(); return; } - footer.className = footerMeta || 'footer'; + footer.className = footerMeta || 'global-footer'; await loadBlock(footer); } diff --git a/test/navigation/bootstrapper.test.js b/test/navigation/bootstrapper.test.js index 4415788452..18379d4a27 100644 --- a/test/navigation/bootstrapper.test.js +++ b/test/navigation/bootstrapper.test.js @@ -4,21 +4,25 @@ import { stub, useFakeTimers, restore } from 'sinon'; import loadBlock from '../../libs/navigation/bootstrapper.js'; import fetchedFooter from '../blocks/global-footer/mocks/fetched-footer.js'; import placeholders from '../blocks/global-navigation/mocks/placeholders.js'; +import { setConfig } from '../../libs/utils/utils.js'; document.body.innerHTML = await readFile({ path: './mocks/body.html' }); const blockConfig = { - name: 'global-footer', - targetEl: 'footer', - appendType: 'append', - footer: { authoringPath: '/federal/home', privacyLoadDelay: 0 }, + footer: { + name: 'global-footer', + targetEl: 'footer', + appendType: 'append', + }, + header: { + name: 'global-navigation', + targetEl: 'header', + appendType: 'prepend', + unavComponents: 'profile', + }, }; -const miloConfigs = { - origin: 'https://feds--milo--adobecom.hlx.page', - miloLibs: 'http://localhost:2000/libs', - pathname: '/', -}; +const miloLibs = 'http://localhost:2000/libs'; const mockRes = ({ payload, status = 200, ok = true } = {}) => new Promise((resolve) => { resolve({ @@ -43,6 +47,7 @@ describe('Bootstrapper', async () => { if (url.includes('/footer.plain.html')) return mockRes({ payload: await readFile({ path: '../blocks/region-nav/mocks/regions.html' }) }); return null; }); + setConfig({ miloLibs, contentRoot: '/federal/dev' }); }); afterEach(() => { @@ -50,7 +55,7 @@ describe('Bootstrapper', async () => { }); it('Renders the footer block', async () => { - await loadBlock(miloConfigs, blockConfig); + await loadBlock(miloLibs, blockConfig.footer); const clock = useFakeTimers({ toFake: ['setTimeout'], shouldAdvanceTime: true, @@ -59,4 +64,10 @@ describe('Bootstrapper', async () => { const el = document.getElementsByTagName('footer'); expect(el).to.exist; }); + + it('Renders the header block', async () => { + await loadBlock(miloLibs, blockConfig.header); + const el = document.getElementsByTagName('header'); + expect(el).to.exist; + }); }); diff --git a/test/navigation/navigation.test.js b/test/navigation/navigation.test.js index 28b3bacc13..466452b269 100644 --- a/test/navigation/navigation.test.js +++ b/test/navigation/navigation.test.js @@ -6,8 +6,23 @@ document.body.innerHTML = await readFile({ path: './mocks/body.html' }); describe('Navigation component', async () => { it('Renders the footer block', async () => { - await loadBlock({ footer: { authoringPath: '/federal/home' }, env: 'qa' }, 'http://localhost:2000'); + await loadBlock({ authoringPath: '/federal/dev', footer: { privacyId: '12343' }, env: 'qa' }, 'http://localhost:2000'); const el = document.getElementsByTagName('footer'); expect(el).to.exist; }); + + it('Renders the header block', async () => { + await loadBlock({ authoringPath: '/federal/dev', header: { imsClientId: 'fedsmilo' }, env: 'qa' }, 'http://localhost:2000'); + const el = document.getElementsByTagName('header'); + expect(el).to.exist; + }); + + it('Does not render either header or footer if not found in configs', async () => { + document.body.innerHTML = await readFile({ path: './mocks/body.html' }); + await loadBlock({ authoringPath: '/federal/dev', env: 'qa' }, 'http://localhost:2000'); + const header = document.getElementsByTagName('header'); + const footer = document.getElementsByTagName('footer'); + expect(header).to.be.empty; + expect(footer).to.be.empty; + }); });