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

[WIP] enable Sentinel tutorials without requiring them #2569

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
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
6 changes: 3 additions & 3 deletions scripts/warm-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from 'lib/learn-client/api/collection'
import { splitProductFromFilename } from 'views/tutorial-view/utils'
import config from '../config/base.json'
import { activeProductSlugs } from 'lib/products'
import { productSlugs } from 'lib/products'
import { ProductSlug } from 'types/products'

const DEV_PORTAL_URL = config.dev_dot.canonical_base_url
Expand All @@ -27,7 +27,7 @@ const fetch = createFetch(null, { timeout: 900 * 1000 })
async function warmDeveloperDocsCache() {
const url = new URL('/api/revalidate', DEV_PORTAL_URL)

for (const productSlug of activeProductSlugs) {
for (const productSlug of productSlugs) {
const body = JSON.stringify({ product: productSlug })

try {
Expand Down Expand Up @@ -93,7 +93,7 @@ async function getTutorialUrlsToCache(product: ProductSlug): Promise<string[]> {
try {
const tutorialUrls = (
await Promise.all(
activeProductSlugs.map((product: ProductSlug) =>
productSlugs.map((product: ProductSlug) =>
getTutorialUrlsToCache(product)
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const generateBasicSuggestedPages = (productSlug: ProductSlug) => {

/**
* These are pages listed after the main pages for a product, and just before a
* link to the Ttorials Library.
* link to the Tutorials Library.
*/
const EXTRA_PAGES: Record<
Exclude<ProductSlug, 'sentinel'>,
Expand Down
4 changes: 3 additions & 1 deletion src/components/icon-tile/icon-tile.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@
/**
* Colors
*/
.color-neutral {
.color-neutral,
.color-sentinel,
.color-hcp {
--icon-color: var(--token-color-foreground-faint);
--border-color: var(--token-color-border-primary);
--background: var(--token-color-surface-faint);
Expand Down
7 changes: 2 additions & 5 deletions src/components/icon-tile/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@

import { ProductSlug } from 'types/products'

export type ProductBrandColor =
| 'neutral'
| 'neutral-dark'
| Exclude<ProductSlug, 'sentinel' | 'hcp'>
export type ProductBrandColor = 'neutral' | 'neutral-dark' | ProductSlug

export interface IconTileProps {
/** Pass a single child, which should be a Flight icon. For 'small' and 'medium' size, pass the 16px icon size; for other sizes pass the 24px icon size. Note that non-"color" icons will be colored using the "brandColor". */
children: React.ReactNode
/** Note: the "extra-large" option is not documented in the design system. It's being used for the IconTileLogo component, as used on the /{product} view pages. */
size?: 'small' | 'medium' | 'large' | 'extra-large'
/** Optional product slug to use for brand color theming. If not provided, defaults to "neutral". Note that "sentinel" and "hcp" are not supported. */
/** Optional product slug to use for brand color theming. If not provided, defaults to "neutral". Note that "sentinel" and "hcp" currently map to "neutral". */
brandColor?: ProductBrandColor
className?: string
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ export function getNavItems(currentProduct: ProductData): NavItem[] {
* Tutorials
*
* Note: we exclude Sentinel, as it does not have tutorials yet.
* Once Sentinel tutorials are published, we can remove this exclusion.
* PR to publish Sentinel tutorials:
* https://github.com/hashicorp/tutorials/pull/2169
*/
if (currentProduct.slug !== 'sentinel') {
items.push({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,14 @@ export const generateProductLandingSidebarMenuItems = (
menuItems.push(introNavItem)
}

// Add a "Tutorials" link for all products except sentinel
/**
* Add a "Tutorials" link for all products.
*
* Note: we exclude Sentinel, as it does not have tutorials yet.
* Once Sentinel tutorials are published, we can remove this exclusion.
* PR to publish Sentinel tutorials:
* https://github.com/hashicorp/tutorials/pull/2169
*/
if (product.slug !== 'sentinel') {
menuItems.push({
title: 'Tutorials',
Expand Down
14 changes: 5 additions & 9 deletions src/components/sidebar/helpers/generate-resources-nav-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,15 +131,11 @@ function generateResourcesNavItems(

return [
{ heading: 'Resources' },
...(productSlug !== 'sentinel'
? [
{
// Add a "Tutorials" link for all products except Sentinel
title: 'Tutorial Library',
href: getTutorialLibraryUrl(productSlug),
},
]
: []),
{
// Add a "Tutorials" link for all products except Sentinel
title: 'Tutorial Library',
href: getTutorialLibraryUrl(productSlug),
},
...getCertificationsLink(productSlug),
{
title: 'Community Forum',
Expand Down
1 change: 1 addition & 0 deletions src/components/tutorial-card/helpers/build-aria-label.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const PRODUCT_LABEL_MAP: Record<ProductOption, string> = {
vault: 'Vault',
vagrant: 'Vagrant',
waypoint: 'Waypoint',
sentinel: 'Sentinel',
}

export function getSpeakableDuration(duration: TutorialCardProps['duration']) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const PRODUCT_ICON_MAP: Record<ProductOption, JSX.Element> = {
vault: <ProductIcon productSlug="vault" />,
vagrant: <ProductIcon productSlug="vagrant" />,
waypoint: <ProductIcon productSlug="waypoint" />,
sentinel: <ProductIcon productSlug="hcp" />,
}
/**
* Map all card badge options to icons
Expand All @@ -46,6 +47,7 @@ const PRODUCT_LABEL_MAP: Record<ProductOption, string> = {
vault: 'Vault',
vagrant: 'Vagrant',
waypoint: 'Waypoint',
sentinel: 'Sentinel',
}
/**
* Map all card badge options to badge labels
Expand Down
5 changes: 0 additions & 5 deletions src/data/sentinel.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,6 @@
},
"version": "0.18.6",
"subnavItems": [
{
"url": "/sentinel/intro",
"text": "Intro",
"type": "inbound"
},
{
"url": "/sentinel",
"text": "Docs",
Expand Down
23 changes: 0 additions & 23 deletions src/lib/__tests__/products.test.ts

This file was deleted.

26 changes: 19 additions & 7 deletions src/lib/learn-client/api/collection/fetch-product-collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
uuid,
ProductOption,
AllCollectionsProductOptions,
ThemeOption,
} from 'lib/learn-client/types'
import { get, toError } from '../../index'

Expand All @@ -27,18 +28,29 @@ export const PRODUCT_COLLECTION_API_ROUTE = (
* includes filtering for theme
*/
export async function fetchAllCollectionsByProduct(
product: AllCollectionsProductOptions
product: AllCollectionsProductOptions,
/**
* All `ProductOption` values except `sentinel` can be used as "theme" options.
* Theme is mainly used to add a product logo to various UI elements, and
* since Sentinel doesn't have a logo, it's not a valid theme option.
*
* Note: an alternative here might be to implement a `theme` option for
* Sentinel, and for now, set it to render a HashiCorp logo. This might
* be a more future-proof approach. This would require updates to `learn-api`:
* https://github.com/hashicorp/learn-api/blob/main/src/models/collection.ts#L17
*/
theme?: Exclude<ProductOption, 'sentinel'> | ThemeOption
): Promise<Collection[]> {
const baseUrl = PRODUCT_COLLECTION_API_ROUTE(product.slug)
let route = baseUrl

if (product.sidebarSort) {
const params = new URLSearchParams([
['topLevelCategorySort', 'true'],
['theme', product.slug],
])

route = baseUrl + `?${params.toString()}`
const params = []
params.push(['topLevelCategorySort', 'true'])
if (theme) {
params.push(['theme', theme])
}
route = baseUrl + `?${new URLSearchParams(params).toString()}`
}

const getProductCollectionsRes = await get(route)
Expand Down
15 changes: 13 additions & 2 deletions src/lib/learn-client/api/collection/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,19 @@ export async function getAllCollections(

// check if the product option is valid, i.e. not 'cloud' or 'hashicorp'
if (options?.product && themeIsProduct(options.product.slug)) {
const allCollections = await fetchAllCollectionsByProduct(options.product)

/**
* Sentinel cannot use "theme", as the `learn-api` doesn't support a
* `sentinel` theme value. We expect authors to use `theme: hashicorp`
* on Sentinel collections. We could provide "hashicorp" here instead
* of `null`, but that might result in unexpected filtering out if
* authors use a different `theme` for any Sentinel collection.
*/
const theme =
options.product.slug === 'sentinel' ? null : options.product.slug
const allCollections = await fetchAllCollectionsByProduct(
options.product,
theme
)
collections = [...allCollections]
} else {
const limit = options?.limit?.toString()
Expand Down
1 change: 1 addition & 0 deletions src/lib/learn-client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ export enum ProductOption {
vagrant = 'vagrant',
vault = 'vault',
waypoint = 'waypoint',
sentinel = 'sentinel',
}

export enum SectionOption {
Expand Down
70 changes: 62 additions & 8 deletions src/lib/products.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,69 @@ import {
/**
* A map of product slugs to their proper noun names.
*
* 🚨 NOTE: the order of this object matters for the Home page.
* 🚨 NOTE: the order of the keys in this object matters. It determines
* the order in which products are displayed in certain locations.
* The inclusion of product slugs in this map also has many side effects.
* Specifically, we iterate over the `Object.keys()` of this object
* in the following places:
*
* LANDING PAGE RELATED USES (assumes link to `/<product>`)
* - generate-top-level-sub-nav-items.ts (all "mobile" menus)
* - getAllProductsNavItems (home page and old products dropdown)
* - PRODUCT_LINK_CARDS (for 404 pages)
* - <Chiclets /> (used on the home page)
*
* DOCS-RELATED USES (assumes link to `/<product>/docs`)
* - getStaticPaths (for `/<product>/docs` landing pages)
* - warm-cache.ts - warmDeveloperDocsCache (relates to /<product>/docs)
* - normalizeRemoteLoaderSlug (relates to /<product>/docs)
* - rewrite-tutorial-links.test.ts (devDotDocsPath)
* - getProductUrlAdjuster - this was intended for use during migration, after
* which MDX content would be updated to avoid having to rewrite links. The
* MDX content rewrite never happened, i think cause versioned docs made it
* seem impossible to execute in a reasonable way. So, authors still write
* links as if the content exists on dot-io domains. The root issue here is
* something that'll hopefully be much easier to fix once we've migrated
* content to `hashicorp/web-unified-docs`. In the meantime, it'd probably
* still be worth it to use a more specific dot-io-targeted variable.
* - rewrite-docs-url.test.ts (tests getProductUrlAdjuster)
*
* TUTORIAL-RELATED USES (assumes link to `/<product>/tutorials`)
* - warm-cache.ts - anonymous function (relates to tutorialUrls)
* - VALID_PRODUCT_SLUGS_FOR_FILTERING (for Tutorials Library sidebar filter)
* - via productSlugsToNames, really only uses slugs
* - getTutorialLandingPaths (for tutorials included in the sitemap)
* - getStaticPaths (for individual [...tutorialSlug] pages)
* - generateProductTutorialHomePaths (for /<product>/tutorials landing pages)
* - rewrite-tutorial-links.test.ts (devDotTutorialsPath)
*
* TYPE ASSERTION USES
* - isProductSlug - this feels like it might be used as generic assertion
* across content types. It might be helpful to create more specific type
* guards, eg `isProductSlugWithLogo`, `isProductSlugWithDocs`,
* `isProductSlugWithLandingPage`, `isProductSlugWithTutorials`,
* `isProductSlugWithIntegrations`, etc.
*
* LEGACY DOT-IO MIGRATION USES (assumes a dot-io site existed for the product)
* - getIsRewriteableDocsLink (and related tests)
* - rewrite-tutorial-links tests ("Links to .io home pages are not rewritten")
*
* We already have at least one instance (for HCP Vault secrets) where we've
* avoided adding to this constant because of how it's intertwined with other
* purposes. It probably makes sense for us to refactor some code so that we're
* only ever using this constant as a way to get the product name from a given
* product slug (where "product slug" is any valid product slug across any
* use case).
*
* For all other uses cases, it might feel duplicative, but one approach might
* be to explicitly declare new constants for each use case. Or maybe, since
* much of these use cases rely on data that could be encoded in our existing
* `src/data/<product>.json` files, we'd could gather everything related to
* each product in those files, and derive the maps we need from the already
* exported `PRODUCT_DATA_MAP`. Or maybe there's some other approach that we
* could use to simplify our setup... It feels a bit convoluted right now.
*/
const productSlugsToNames: { [slug in ProductSlug]: ProductName } = {
sentinel: 'Sentinel',
hcp: 'HashiCorp Cloud Platform',
terraform: 'Terraform',
packer: 'Packer',
Expand All @@ -28,6 +87,7 @@ const productSlugsToNames: { [slug in ProductSlug]: ProductName } = {
nomad: 'Nomad',
waypoint: 'Waypoint',
vagrant: 'Vagrant',
sentinel: 'Sentinel',
}

/**
Expand Down Expand Up @@ -219,11 +279,6 @@ function isProductSlug(string: string): string is ProductSlug {
*/
const productSlugs = Object.keys(productSlugsToNames) as ProductSlug[]

/**
* An array of product slugs which are "active" on the site. Currently all but sentinel.
*/
const activeProductSlugs = productSlugs.filter((slug) => slug !== 'sentinel')

/**
* Generates an array of Product objects from `productSlugs`.
*/
Expand All @@ -233,7 +288,6 @@ const products: Product[] = productSlugs.map((slug: ProductSlug) => {
})

export {
activeProductSlugs,
isProductSlug,
products,
productSlugs,
Expand Down
4 changes: 2 additions & 2 deletions src/lib/sitemap/tutorials-content-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

import { getAllCollections } from 'lib/learn-client/api/collection'
import { SectionOption } from 'lib/learn-client/types'
import { activeProductSlugs } from 'lib/products'
import { productSlugs } from 'lib/products'
import tutorialMap from 'data/_tutorial-map.generated.json'
import { ProductSlug } from 'types/products'
import { getCollectionSlug } from 'views/collection-view/helpers'
import { makeSitemapField } from './helpers'
import { Collection as ClientCollection } from 'lib/learn-client/types'

function getTutorialLandingPaths(): string[] {
return activeProductSlugs.map(
return productSlugs.map(
(productSlug: ProductSlug) => `${productSlug}/tutorials`
)
}
Expand Down
4 changes: 2 additions & 2 deletions src/pages/[productSlug]/docs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
import { ProductSlug } from 'types/products'
import { getStaticProps } from 'views/product-root-docs-path-landing/server'
import ProductRootDocsPathLanding from 'views/product-root-docs-path-landing'
import { activeProductSlugs } from 'lib/products'
import { productSlugs } from 'lib/products'

/**
* Generates the paths for all /:productSlug/docs routes.
*/
const getStaticPaths = async () => {
const paths = activeProductSlugs.map((productSlug: ProductSlug) => ({
const paths = productSlugs.map((productSlug: ProductSlug) => ({
params: { productSlug },
}))

Expand Down
Loading