diff --git a/components/context/MainContext.tsx b/components/context/MainContext.tsx index e06ef3cfcf..f14b0ac877 100644 --- a/components/context/MainContext.tsx +++ b/components/context/MainContext.tsx @@ -13,13 +13,6 @@ export type ProductT = { versions?: Array } -export type ProductGroupT = { - name: string - icon: string - octicon: string - children: Array -} - type VersionItem = { // free-pro-team@latest, enterprise-cloud@latest, enterprise-server@3.3 ... version: string @@ -77,7 +70,6 @@ export type MainContextT = { article?: BreadcrumbT } activeProducts: Array - productGroups: Array communityRedirect: { name: string href: string @@ -137,7 +129,6 @@ export const getMainContext = async (req: any, res: any): Promise return { breadcrumbs: req.context.breadcrumbs || {}, activeProducts: req.context.activeProducts, - productGroups: req.context.productGroups, communityRedirect: req.context.page?.communityRedirect || {}, currentProduct: req.context.productMap[req.context.currentProduct] || null, currentLayoutName: req.context.currentLayoutName, diff --git a/components/homepage/ProductSelectionCard.tsx b/components/homepage/ProductSelectionCard.tsx index 7a758a4cd4..6d9b5065b4 100644 --- a/components/homepage/ProductSelectionCard.tsx +++ b/components/homepage/ProductSelectionCard.tsx @@ -1,4 +1,5 @@ -import { ProductT, ProductGroupT, useMainContext } from 'components/context/MainContext' +import { ProductT, useMainContext } from 'components/context/MainContext' +import type { ProductGroupT } from 'components/homepage/ProductSelections' import React from 'react' import { useRouter } from 'next/router' diff --git a/components/homepage/ProductSelections.tsx b/components/homepage/ProductSelections.tsx index 2aa0d014a2..852fedaf00 100644 --- a/components/homepage/ProductSelections.tsx +++ b/components/homepage/ProductSelections.tsx @@ -1,11 +1,20 @@ -import { useMainContext } from 'components/context/MainContext' - import React from 'react' + +import type { ProductT } from 'components/context/MainContext' import { ProductSelectionCard } from './ProductSelectionCard' -export const ProductSelections = () => { - const { productGroups } = useMainContext() +export type ProductGroupT = { + name: string + icon: string + octicon: string + children: Array +} +type Props = { + productGroups: Array +} + +export const ProductSelections = ({ productGroups }: Props) => { return (
diff --git a/middleware/context.js b/middleware/context.js index 6cc53abb73..6c892a780d 100644 --- a/middleware/context.js +++ b/middleware/context.js @@ -1,7 +1,7 @@ import languages from '../lib/languages.js' import enterpriseServerReleases from '../lib/enterprise-server-releases.js' import { allVersions } from '../lib/all-versions.js' -import { productMap, getProductGroups } from '../lib/all-products.js' +import { productMap } from '../lib/all-products.js' import pathUtils from '../lib/path-utils.js' import productNames from '../lib/product-names.js' import warmServer from '../lib/warm-server.js' @@ -39,7 +39,6 @@ export default async function contextualize(req, res, next) { req.context.currentProduct = getProductStringFromPath(req.pagePath) req.context.currentCategory = getCategoryStringFromPath(req.pagePath) req.context.productMap = productMap - req.context.productGroups = getProductGroups(pageMap, req.language) req.context.activeProducts = activeProducts req.context.allVersions = allVersions req.context.currentPathWithoutLanguage = getPathWithoutLanguage(req.pagePath) diff --git a/middleware/contextualizers/product-groups.js b/middleware/contextualizers/product-groups.js new file mode 100644 index 0000000000..74bcb0cc95 --- /dev/null +++ b/middleware/contextualizers/product-groups.js @@ -0,0 +1,32 @@ +import { getProductGroups } from '../../lib/all-products.js' +import warmServer from '../../lib/warm-server.js' +import { languageKeys } from '../../lib/languages.js' +import { allVersionKeys } from '../../lib/all-versions.js' + +const isHomepage = (path) => { + const split = path.split('/') + // E.g. `/foo` but not `foo/bar` or `foo/` + if (split.length === 2 && split[1] && !split[0]) { + return languageKeys.includes(split[1]) + } + // E.g. `/foo/possiblyproductname` but not `foo/possiblyproductname` or + // `/foo/something/` + if (split.length === 3 && !split[0] && split[2]) { + return allVersionKeys.includes(split[2]) + } + return false +} + +export default async function productGroups(req, res, next) { + // It's important to use `req.pathPage` instead of `req.path` because + // the request could be the client-side routing from Next where the URL + // might be something like `/_next/data/foo/bar.json` which is translated, + // in another middleware, to what it would equate to if it wasn't + // client-side routing. + if (isHomepage(req.pagePath)) { + const { pages } = await warmServer() + req.context.productGroups = getProductGroups(pages, req.language) + } + + return next() +} diff --git a/middleware/index.js b/middleware/index.js index 610e593d0e..dff852abe9 100644 --- a/middleware/index.js +++ b/middleware/index.js @@ -45,6 +45,7 @@ import genericToc from './contextualizers/generic-toc.js' import breadcrumbs from './contextualizers/breadcrumbs.js' import features from './contextualizers/features.js' import productExamples from './contextualizers/product-examples.js' +import productGroups from './contextualizers/product-groups.js' import featuredLinks from './featured-links.js' import learningTrack from './learning-track.js' import next from './next.js' @@ -267,6 +268,7 @@ export default function (app) { app.use(asyncMiddleware(instrument(breadcrumbs, './contextualizers/breadcrumbs'))) app.use(instrument(features, './contextualizers/features')) app.use(asyncMiddleware(instrument(productExamples, './contextualizers/product-examples'))) + app.use(asyncMiddleware(instrument(productGroups, './contextualizers/product-groups'))) app.use(asyncMiddleware(instrument(featuredLinks, './featured-links'))) app.use(asyncMiddleware(instrument(learningTrack, './learning-track'))) diff --git a/pages/index.tsx b/pages/index.tsx index 751d5d1997..15c86d3d50 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,12 +1,13 @@ -import { GetServerSideProps } from 'next' +import React from 'react' +import type { GetServerSideProps } from 'next' import { MainContextT, MainContext, getMainContext } from 'components/context/MainContext' -import React from 'react' import { DefaultLayout } from 'components/DefaultLayout' import { useTranslation } from 'components/hooks/useTranslation' import { ArticleList } from 'components/landing/ArticleList' import { HomePageHero } from 'components/homepage/HomePageHero' +import type { ProductGroupT } from 'components/homepage/ProductSelections' import { ProductSelections } from 'components/homepage/ProductSelections' type FeaturedLink = { @@ -19,13 +20,23 @@ type Props = { mainContext: MainContextT popularLinks: Array gettingStartedLinks: Array + productGroups: Array } -export default function MainHomePage({ mainContext, gettingStartedLinks, popularLinks }: Props) { +export default function MainHomePage({ + mainContext, + gettingStartedLinks, + popularLinks, + productGroups, +}: Props) { return ( - + ) @@ -34,15 +45,16 @@ export default function MainHomePage({ mainContext, gettingStartedLinks, popular type HomePageProps = { popularLinks: Array gettingStartedLinks: Array + productGroups: Array } function HomePage(props: HomePageProps) { - const { gettingStartedLinks, popularLinks } = props + const { gettingStartedLinks, popularLinks, productGroups } = props const { t } = useTranslation(['toc']) return (
- +
@@ -67,6 +79,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => return { props: { mainContext: await getMainContext(req, res), + productGroups: req.context.productGroups, gettingStartedLinks: req.context.featuredLinks.gettingStarted.map( ({ title, href, intro }: any) => ({ title, href, intro }) ), diff --git a/tests/rendering/homepage.js b/tests/rendering/homepage.js new file mode 100644 index 0000000000..b303360811 --- /dev/null +++ b/tests/rendering/homepage.js @@ -0,0 +1,31 @@ +import { expect, jest } from '@jest/globals' + +import { getDOM } from '../helpers/e2etest.js' + +describe('rendering the home page(s)', () => { + jest.setTimeout(5 * 60 * 1000) + + test('homepage has product links', async () => { + const $ = await getDOM('/en') + const products = $('[data-testid=product]') + expect(products.length).toBe(1) + }) + + test('homepage in non-default language has product links', async () => { + const $ = await getDOM('/ja') + const products = $('[data-testid=product]') + expect(products.length).toBe(1) + }) + + test('homepage in non-default product', async () => { + const $ = await getDOM('/en/enterprise-cloud@latest') + const products = $('[data-testid=product]') + expect(products.length).toBe(1) + }) + + test('homepage in non-default product in non-default language', async () => { + const $ = await getDOM('/ja/enterprise-cloud@latest') + const products = $('[data-testid=product]') + expect(products.length).toBe(1) + }) +})