1
0
mirror of synced 2025-12-19 09:57:42 -05:00

Convert learning-track JS files to TypeScript (#55606)

This commit is contained in:
Kevin Heis
2025-05-20 11:05:51 -07:00
committed by GitHub
parent cb1e971c78
commit 32a8d91b16
7 changed files with 202 additions and 63 deletions

View File

@@ -8,11 +8,11 @@ import { getAlertTitles } from '#src/languages/lib/get-alert-titles.ts'
import getTocItems from './get-toc-items.js'
import Permalink from './permalink.js'
import { renderContent } from '#src/content-render/index.js'
import processLearningTracks from '#src/learning-track/lib/process-learning-tracks.js'
import processLearningTracks from '#src/learning-track/lib/process-learning-tracks'
import { productMap } from '#src/products/lib/all-products.ts'
import slash from 'slash'
import readFileContents from './read-file-contents.js'
import getLinkData from '#src/learning-track/lib/get-link-data.js'
import getLinkData from '#src/learning-track/lib/get-link-data'
import getDocumentType from '#src/events/lib/get-document-type.ts'
import { allTools } from '#src/tools/lib/all-tools.ts'
import { renderContentWithFallback } from '#src/languages/lib/render-with-fallback.js'

View File

@@ -1,8 +1,8 @@
import type { Response, NextFunction } from 'express'
import type { ExtendedRequest, FeaturedLinkExpanded } from '@/types'
import getLinkData from '@/learning-track/lib/get-link-data.js'
import { renderContent } from '@/content-render/index.js'
import getLinkData from '@/learning-track/lib/get-link-data'
import { renderContent } from '@/content-render/index'
/**
* This is the max. number of featured links, by any category, that we
@@ -73,12 +73,20 @@ export default async function featuredLinks(
if (!(key in req.context.page.featuredLinks))
throw new Error('featureLinks key not found in Page')
const pageFeaturedLink = req.context.page.featuredLinks[key]
req.context.featuredLinks[key] = (await getLinkData(
pageFeaturedLink,
// Handle different types of featuredLinks by converting to string array
const stringLinks = Array.isArray(pageFeaturedLink)
? pageFeaturedLink.map((item) => (typeof item === 'string' ? item : item.href))
: []
const linkData = await getLinkData(
stringLinks,
req.context,
{ title: true, intro: true, fullTitle: true },
MAX_FEATURED_LINKS,
)) as FeaturedLinkExpanded[] // Remove ones `getLinkData` is TS
)
// We need to use a type assertion here because the Page interfaces are incompatible
// between our local types and the global types, but the actual runtime objects are compatible
req.context.featuredLinks[key] = (linkData || []) as unknown as FeaturedLinkExpanded[]
}
}

View File

@@ -1,32 +1,34 @@
import path from 'path'
import findPage from '#src/frame/lib/find-page.js'
import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-default-version.js'
import removeFPTFromPath from '#src/versions/lib/remove-fpt-from-path.js'
import { renderContent } from '#src/content-render/index.js'
import { executeWithFallback } from '#src/languages/lib/render-with-fallback.js'
import findPage from '@/frame/lib/find-page'
import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-version'
import removeFPTFromPath from '@/versions/lib/remove-fpt-from-path'
import { renderContent } from '@/content-render/index'
import { executeWithFallback } from '@/languages/lib/render-with-fallback'
import { Context, LinkOptions, ProcessedLink } from './types'
// rawLinks is an array of paths: [ '/foo' ]
// we need to convert it to an array of localized objects: [ { href: '/en/foo', title: 'Foo', intro: 'Description here' } ]
export default async (
rawLinks,
context,
option = { title: true, intro: true, fullTitle: false },
export default async function getLinkData(
rawLinks: string[] | string | undefined,
context: Context,
options: LinkOptions = { title: true, intro: true, fullTitle: false },
maxLinks = Infinity,
) => {
if (!rawLinks) return
): Promise<ProcessedLink[] | undefined> {
if (!rawLinks) return undefined
if (typeof rawLinks === 'string') {
return await processLink(rawLinks, context, option)
const processedLink = await processLink(rawLinks, context, options)
return processedLink ? [processedLink] : undefined
}
const links = []
const links: ProcessedLink[] = []
// Using a for loop here because the async work is not network or
// disk bound. It's CPU bound.
// And if we use a for-loop we can potentially bail early if
// the `maxLinks` is reached. That's instead of computing them all,
// and then slicing the array. So it avoids wasted processing.
for (const link of rawLinks) {
const processedLink = await processLink(link, context, option)
const processedLink = await processLink(link, context, options)
if (processedLink) {
links.push(processedLink)
if (links.length >= maxLinks) {
@@ -38,9 +40,13 @@ export default async (
return links
}
async function processLink(link, context, option) {
const opts = { textOnly: true }
const linkHref = link.href || link
async function processLink(
link: string | { href: string },
context: Context,
options: LinkOptions,
): Promise<ProcessedLink | null> {
const opts: { textOnly: boolean; preferShort?: boolean } = { textOnly: true }
const linkHref = typeof link === 'string' ? link : link.href
// Parse the link in case it includes Liquid conditionals
const linkPath = linkHref.includes('{')
? await executeWithFallback(
@@ -55,10 +61,13 @@ async function processLink(link, context, option) {
if (!linkPath) return null
const version =
context.currentVersion === 'homepage' ? nonEnterpriseDefaultVersion : context.currentVersion
const href = removeFPTFromPath(path.join('/', context.currentLanguage, version, linkPath))
(context.currentVersion === 'homepage'
? nonEnterpriseDefaultVersion
: context.currentVersion) || 'free-pro-team@latest'
const currentLanguage = context.currentLanguage || 'en'
const href = removeFPTFromPath(path.join('/', currentLanguage, version, linkPath))
const linkedPage = findPage(href, context.pages, context.redirects)
const linkedPage = findPage(href, context.pages || {}, context.redirects || {})
if (!linkedPage) {
// This can happen when the link depends on Liquid conditionals,
// like...
@@ -66,18 +75,18 @@ async function processLink(link, context, option) {
return null
}
const result = { href, page: linkedPage }
const result: ProcessedLink = { href, page: linkedPage }
if (option.title) {
if (options.title) {
result.title = await linkedPage.renderTitle(context, opts)
}
if (option.fullTitle) {
if (options.fullTitle) {
opts.preferShort = false
result.fullTitle = await linkedPage.renderTitle(context, opts)
}
if (option.intro) {
if (options.intro) {
result.intro = await linkedPage.renderProp('intro', context, opts)
}
return result

View File

@@ -1,15 +1,19 @@
import getLinkData from './get-link-data.js'
import getApplicableVersions from '#src/versions/lib/get-applicable-versions.js'
import { getDataByLanguage } from '#src/data-directory/lib/get-data.js'
import { renderContent } from '#src/content-render/index.js'
import { executeWithFallback } from '#src/languages/lib/render-with-fallback.js'
import getLinkData from './get-link-data'
import getApplicableVersions from '@/versions/lib/get-applicable-versions'
import { getDataByLanguage } from '@/data-directory/lib/get-data'
import { renderContent } from '@/content-render/index'
import { executeWithFallback } from '@/languages/lib/render-with-fallback'
import { Context, TrackGuide, LearningTrack, ProcessedLearningTracks } from './types'
const renderOpts = { textOnly: true }
// This module returns an object that contains a single featured learning track
// and an array of all the other learning tracks for the current version.
export default async function processLearningTracks(rawLearningTracks, context) {
const learningTracks = []
export default async function processLearningTracks(
rawLearningTracks: string[],
context: Context,
): Promise<ProcessedLearningTracks> {
const learningTracks: LearningTrack[] = []
if (!context.currentProduct) {
throw new Error(`Missing context.currentProduct value.`)
@@ -59,7 +63,7 @@ export default async function processLearningTracks(rawLearningTracks, context)
// we need to have the English `title` and `description` to
// fall back to.
//
let enTrack
let enTrack: any
if (context.currentLanguage !== 'en') {
enTrack = getDataByLanguage(
`learning-tracks.${context.currentProduct}.${renderedTrackName}`,
@@ -86,26 +90,28 @@ export default async function processLearningTracks(rawLearningTracks, context)
const title = await executeWithFallback(
context,
() => renderContent(track.title, context, renderOpts),
(enContext) => renderContent(enTrack.title, enContext, renderOpts),
(enContext: any) => renderContent(enTrack.title, enContext, renderOpts),
)
const description = await executeWithFallback(
context,
() => renderContent(track.description, context, renderOpts),
(enContext) => renderContent(enTrack.description, enContext, renderOpts),
(enContext: any) => renderContent(enTrack.description, enContext, renderOpts),
)
const learningTrack = {
const guides = (await getLinkData(track.guides, context)) || []
const learningTrack: LearningTrack = {
trackName: renderedTrackName,
trackProduct: context.currentProduct || null,
title,
description,
// getLinkData respects versioning and only returns guides available in the current version;
// if no guides are available, the learningTrack.guides property will be an empty array.
guides: await getLinkData(track.guides, context),
guides: guides as TrackGuide[],
}
// Only add the track to the array of tracks if there are guides in this version and it's not the featured track.
if (learningTrack.guides.length) {
if (Array.isArray(learningTrack.guides) && learningTrack.guides.length > 0) {
learningTracks.push(learningTrack)
}
}

View File

@@ -0,0 +1,122 @@
/**
* Common types used across learning track components
*/
/**
* Basic context interface for rendering operations
*/
export interface Context {
currentProduct?: string
currentLanguage?: string
currentVersion?: string
pages?: any
redirects?: any
// Additional properties that may be needed for rendering
[key: string]: any
}
/**
* Options for retrieving link data
*/
export interface LinkOptions {
title?: boolean
intro?: boolean
fullTitle?: boolean
}
/**
* Result of processing a link
*/
export interface ProcessedLink {
href: string
page: Page
title?: string
fullTitle?: string
intro?: string
}
/**
* Definitions for featured links data
*/
export interface FeaturedLink {
title: string
href: string
}
export interface PageFeaturedLinks {
[key: string]: string[] | FeaturedLink[]
}
/**
* Page interface for basic page properties
*/
export interface Page {
renderTitle: (context: Context, opts: any) => Promise<string>
renderProp: (prop: string, context: Context, opts: any) => Promise<string>
}
/**
* Guide in a learning track
*/
export interface TrackGuide {
href: string
page: Page
title: string
intro?: string
}
/**
* A processed learning track
*/
export interface LearningTrack {
trackName: string
trackProduct: string | null
title: string
description: string
guides: TrackGuide[]
}
/**
* Learning track metadata with guides
*/
export interface LearningTrackMetadata {
title: string
description: string
guides: string[]
versions?: any
}
/**
* Collection of learning tracks by product and track name
*/
export interface LearningTracks {
[productId: string]: {
[trackName: string]: LearningTrackMetadata
}
}
/**
* Return type for processLearningTracks function
*/
export interface ProcessedLearningTracks {
learningTracks: LearningTrack[]
}
/**
* Learning track data for the current guide
*/
export interface CurrentLearningTrack {
trackName: string
trackProduct: string
trackTitle: string
numberOfGuides?: number
currentGuideIndex?: number
nextGuide?: {
href: string
title: string | undefined
}
prevGuide?: {
href: string
title: string | undefined
}
}

View File

@@ -1,28 +1,22 @@
import type { Response, NextFunction } from 'express'
import type {
Context,
ExtendedRequest,
LearningTrack,
LearningTracks,
TrackGuide,
Page,
} from '@/types'
import type { ExtendedRequest, LearningTracks } from '@/types'
import type { Context, CurrentLearningTrack, TrackGuide } from '../lib/types'
import { getPathWithoutLanguage, getPathWithoutVersion } from '@/frame/lib/path-utils.js'
import getLinkData from '../lib/get-link-data.js'
import getLinkData from '../lib/get-link-data'
import { renderContent } from '@/content-render/index.js'
import { executeWithFallback } from '@/languages/lib/render-with-fallback.js'
import { getDeepDataByLanguage } from '@/data-directory/lib/get-data.js'
export default async function learningTrack(
req: ExtendedRequest,
req: ExtendedRequest & { context: Context },
res: Response,
next: NextFunction,
) {
if (!req.context) throw new Error('request is not contextualized')
const noTrack = () => {
req.context!.currentLearningTrack = null
req.context.currentLearningTrack = null
return next()
}
@@ -94,12 +88,12 @@ export default async function learningTrack(
() => '', // todo use english track.title
)
const currentLearningTrack: LearningTrack = { trackName, trackProduct, trackTitle }
const currentLearningTrack: CurrentLearningTrack = { trackName, trackProduct, trackTitle }
const guidePath = getPathWithoutLanguage(getPathWithoutVersion(req.pagePath))
// The raw track.guides will return all guide paths, need to use getLinkData
// so we only get guides available in the current version
const trackGuides = (await getLinkData(track.guides, req.context)) as TrackGuide[]
const trackGuides = ((await getLinkData(track.guides, req.context)) || []) as TrackGuide[]
const trackGuidePaths = trackGuides.map((guide) => {
return getPathWithoutLanguage(getPathWithoutVersion(guide.href))
@@ -137,8 +131,8 @@ export default async function learningTrack(
intro: false,
fullTitle: false,
})
if (!resultData) return noTrack()
const result = resultData as { href: string; page: Page; title: string }
if (!resultData || !resultData.length) return noTrack()
const result = resultData[0]
const href = result.href
const title = result.title
@@ -152,8 +146,8 @@ export default async function learningTrack(
intro: false,
fullTitle: false,
})
if (!resultData) return noTrack()
const result = resultData as { href: string; page: Page; title: string }
if (!resultData || !resultData.length) return noTrack()
const result = resultData[0]
const href = result.href
const title = result.title

View File

@@ -185,11 +185,11 @@ export type LearningTrack = {
currentGuideIndex?: number
nextGuide?: {
href: string
title: string
title: string | undefined
}
prevGuide?: {
href: string
title: string
title: string | undefined
}
}