Skip to content

Commit

Permalink
avoid bad links in version dropdowns (#2501)
Browse files Browse the repository at this point in the history
* add fallback case for no latest version

* add get-valid-versions function

* use get-valid-versions function
  • Loading branch information
zchsh authored Jul 18, 2024
1 parent f5af503 commit a2d49f7
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 3 deletions.
6 changes: 6 additions & 0 deletions src/components/docs-version-switcher/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ const DocsVersionSwitcher = ({
selectedOption = options.find(
(option: VersionSelectItem) => option.isLatest === true
)
// In some edge cases, there may be no latest version, such as for
// versioned docs that no longer exist in the latest version. For these
// cases, fallback to selecting the first option.
if (!selectedOption) {
selectedOption = options[0]
}
}

const projectNameForLabel = setProjectForAriaLabel(
Expand Down
22 changes: 19 additions & 3 deletions src/views/docs-view/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
import tutorialMap from 'data/_tutorial-map.generated.json'

// Local imports
import { getValidVersions } from './utils/get-valid-versions'
import { getProductUrlAdjuster } from './utils/product-url-adjusters'
import { getBackToLink } from './utils/get-back-to-link'
import { getDeployPreviewLoader } from './utils/get-deploy-preview-loader'
Expand Down Expand Up @@ -384,6 +385,21 @@ export function getStaticGenerationFunctions<
mainWidth: isDocsLanding ? 'wide' : 'narrow',
}

/**
* Filter versions to include only those where this document exists
*/
// Construct a document path that the content API will recognize
const pathWithoutVersion = pathParts
.filter((part) => part !== versionPathPart)
.join('/')
const fullPath = `doc#${path.join(basePathForLoader, pathWithoutVersion)}`
// Filter for valid versions, fetching from the content API under the hood
const validVersions = await getValidVersions(
versions,
fullPath,
productSlugForLoader
)

/**
* Determine whether to show the version selector
*
Expand All @@ -392,8 +408,8 @@ export function getStaticGenerationFunctions<
* (We use `v0.0.x` as a placeholder version for un-versioned documentation)
*/
const hasMeaningfulVersions =
versions.length > 0 &&
(versions.length > 1 || versions[0].version !== 'v0.0.x')
validVersions.length > 0 &&
(validVersions.length > 1 || validVersions[0].version !== 'v0.0.x')

/**
* We want to show "Edit on GitHub" links for public content repos only.
Expand Down Expand Up @@ -440,7 +456,7 @@ export function getStaticGenerationFunctions<
!hideVersionSelector &&
!isReleaseNotesPage(currentPathUnderProduct) && // toggle version dropdown
hasMeaningfulVersions
? versions
? validVersions
: null,
}

Expand Down
64 changes: 64 additions & 0 deletions src/views/docs-view/utils/get-valid-versions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

// Types
import type { VersionSelectItem } from '../loaders/remote-content'

const CONTENT_API_URL = process.env.MKTG_CONTENT_API
const VERSIONS_ENDPOINT = '/api/content-versions'

/**
* Given a list of all possible versions, as well as a document path and
* content repo identifier for our content API,
* Return a filter list of versions that includes only those versions
* where this document exists.
*
* To determine in which versions this document exists, we make a request
* to a content API that returns a list of strings representing known versions.
* We use this to filter out unknown versions from our incoming version list.
*/
export async function getValidVersions(
/**
* An array of version select items representing all possible versions for
* the content source repository in question (`productSlugForLoader`).
* May be undefined or empty if versioned docs are not enabled, for example
* during local preview.
*/
versions: VersionSelectItem[],
/**
* A identifier for the document, consumable by our content API.
* For markdown documents, this is `doc#` followed by the full path of the
* document within the content source repository.
*/
fullPath: string,
/**
* The product slug for the document, consumable by our content API.
* The naming here is difficult, as the actual function here is to identify
* specific content source repositories. These are often but not always
* product slugs. For example Terraform has multiple content source repos
* for different parts of the product.
*/
productSlugForLoader: string
): Promise<VersionSelectItem[]> {
// If versions are falsy or empty, we can skip the API calls and return []
if (!versions || versions.length === 0) return []
try {
// Build the URL to fetch known versions of this document
const validVersionsUrl = new URL(VERSIONS_ENDPOINT, CONTENT_API_URL)
validVersionsUrl.searchParams.set('product', productSlugForLoader)
validVersionsUrl.searchParams.set('fullPath', fullPath)
// Fetch known versions of this document
const response = await fetch(validVersionsUrl.toString())
const { versions: knownVersions } = await response.json()
// Apply the filter, and return the valid versions
return versions.filter((option) => knownVersions.includes(option.version))
} catch (error) {
console.error(
`[docs-view/server] error fetching known versions for "${productSlugForLoader}" document "${fullPath}". Falling back to showing all versions.`,
error
)
return versions
}
}

0 comments on commit a2d49f7

Please sign in to comment.