Skip to content

Commit 32a8d91

Browse files
authored
Convert learning-track JS files to TypeScript (#55606)
1 parent cb1e971 commit 32a8d91

File tree

7 files changed

+202
-63
lines changed

7 files changed

+202
-63
lines changed

src/frame/lib/page.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ import { getAlertTitles } from '#src/languages/lib/get-alert-titles.ts'
88
import getTocItems from './get-toc-items.js'
99
import Permalink from './permalink.js'
1010
import { renderContent } from '#src/content-render/index.js'
11-
import processLearningTracks from '#src/learning-track/lib/process-learning-tracks.js'
11+
import processLearningTracks from '#src/learning-track/lib/process-learning-tracks'
1212
import { productMap } from '#src/products/lib/all-products.ts'
1313
import slash from 'slash'
1414
import readFileContents from './read-file-contents.js'
15-
import getLinkData from '#src/learning-track/lib/get-link-data.js'
15+
import getLinkData from '#src/learning-track/lib/get-link-data'
1616
import getDocumentType from '#src/events/lib/get-document-type.ts'
1717
import { allTools } from '#src/tools/lib/all-tools.ts'
1818
import { renderContentWithFallback } from '#src/languages/lib/render-with-fallback.js'

src/landings/middleware/featured-links.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type { Response, NextFunction } from 'express'
22

33
import type { ExtendedRequest, FeaturedLinkExpanded } from '@/types'
4-
import getLinkData from '@/learning-track/lib/get-link-data.js'
5-
import { renderContent } from '@/content-render/index.js'
4+
import getLinkData from '@/learning-track/lib/get-link-data'
5+
import { renderContent } from '@/content-render/index'
66

77
/**
88
* This is the max. number of featured links, by any category, that we
@@ -73,12 +73,20 @@ export default async function featuredLinks(
7373
if (!(key in req.context.page.featuredLinks))
7474
throw new Error('featureLinks key not found in Page')
7575
const pageFeaturedLink = req.context.page.featuredLinks[key]
76-
req.context.featuredLinks[key] = (await getLinkData(
77-
pageFeaturedLink,
76+
// Handle different types of featuredLinks by converting to string array
77+
const stringLinks = Array.isArray(pageFeaturedLink)
78+
? pageFeaturedLink.map((item) => (typeof item === 'string' ? item : item.href))
79+
: []
80+
81+
const linkData = await getLinkData(
82+
stringLinks,
7883
req.context,
7984
{ title: true, intro: true, fullTitle: true },
8085
MAX_FEATURED_LINKS,
81-
)) as FeaturedLinkExpanded[] // Remove ones `getLinkData` is TS
86+
)
87+
// We need to use a type assertion here because the Page interfaces are incompatible
88+
// between our local types and the global types, but the actual runtime objects are compatible
89+
req.context.featuredLinks[key] = (linkData || []) as unknown as FeaturedLinkExpanded[]
8290
}
8391
}
8492

src/learning-track/lib/get-link-data.js renamed to src/learning-track/lib/get-link-data.ts

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,34 @@
11
import path from 'path'
2-
import findPage from '#src/frame/lib/find-page.js'
3-
import nonEnterpriseDefaultVersion from '#src/versions/lib/non-enterprise-default-version.js'
4-
import removeFPTFromPath from '#src/versions/lib/remove-fpt-from-path.js'
5-
import { renderContent } from '#src/content-render/index.js'
6-
import { executeWithFallback } from '#src/languages/lib/render-with-fallback.js'
2+
import findPage from '@/frame/lib/find-page'
3+
import nonEnterpriseDefaultVersion from '@/versions/lib/non-enterprise-default-version'
4+
import removeFPTFromPath from '@/versions/lib/remove-fpt-from-path'
5+
import { renderContent } from '@/content-render/index'
6+
import { executeWithFallback } from '@/languages/lib/render-with-fallback'
7+
import { Context, LinkOptions, ProcessedLink } from './types'
78

89
// rawLinks is an array of paths: [ '/foo' ]
910
// we need to convert it to an array of localized objects: [ { href: '/en/foo', title: 'Foo', intro: 'Description here' } ]
10-
export default async (
11-
rawLinks,
12-
context,
13-
option = { title: true, intro: true, fullTitle: false },
11+
export default async function getLinkData(
12+
rawLinks: string[] | string | undefined,
13+
context: Context,
14+
options: LinkOptions = { title: true, intro: true, fullTitle: false },
1415
maxLinks = Infinity,
15-
) => {
16-
if (!rawLinks) return
16+
): Promise<ProcessedLink[] | undefined> {
17+
if (!rawLinks) return undefined
1718

1819
if (typeof rawLinks === 'string') {
19-
return await processLink(rawLinks, context, option)
20+
const processedLink = await processLink(rawLinks, context, options)
21+
return processedLink ? [processedLink] : undefined
2022
}
2123

22-
const links = []
24+
const links: ProcessedLink[] = []
2325
// Using a for loop here because the async work is not network or
2426
// disk bound. It's CPU bound.
2527
// And if we use a for-loop we can potentially bail early if
2628
// the `maxLinks` is reached. That's instead of computing them all,
2729
// and then slicing the array. So it avoids wasted processing.
2830
for (const link of rawLinks) {
29-
const processedLink = await processLink(link, context, option)
31+
const processedLink = await processLink(link, context, options)
3032
if (processedLink) {
3133
links.push(processedLink)
3234
if (links.length >= maxLinks) {
@@ -38,9 +40,13 @@ export default async (
3840
return links
3941
}
4042

41-
async function processLink(link, context, option) {
42-
const opts = { textOnly: true }
43-
const linkHref = link.href || link
43+
async function processLink(
44+
link: string | { href: string },
45+
context: Context,
46+
options: LinkOptions,
47+
): Promise<ProcessedLink | null> {
48+
const opts: { textOnly: boolean; preferShort?: boolean } = { textOnly: true }
49+
const linkHref = typeof link === 'string' ? link : link.href
4450
// Parse the link in case it includes Liquid conditionals
4551
const linkPath = linkHref.includes('{')
4652
? await executeWithFallback(
@@ -55,29 +61,32 @@ async function processLink(link, context, option) {
5561
if (!linkPath) return null
5662

5763
const version =
58-
context.currentVersion === 'homepage' ? nonEnterpriseDefaultVersion : context.currentVersion
59-
const href = removeFPTFromPath(path.join('/', context.currentLanguage, version, linkPath))
64+
(context.currentVersion === 'homepage'
65+
? nonEnterpriseDefaultVersion
66+
: context.currentVersion) || 'free-pro-team@latest'
67+
const currentLanguage = context.currentLanguage || 'en'
68+
const href = removeFPTFromPath(path.join('/', currentLanguage, version, linkPath))
6069

61-
const linkedPage = findPage(href, context.pages, context.redirects)
70+
const linkedPage = findPage(href, context.pages || {}, context.redirects || {})
6271
if (!linkedPage) {
6372
// This can happen when the link depends on Liquid conditionals,
6473
// like...
6574
// - '{% ifversion ghes %}/admin/foo/bar{% endifversion %}'
6675
return null
6776
}
6877

69-
const result = { href, page: linkedPage }
78+
const result: ProcessedLink = { href, page: linkedPage }
7079

71-
if (option.title) {
80+
if (options.title) {
7281
result.title = await linkedPage.renderTitle(context, opts)
7382
}
7483

75-
if (option.fullTitle) {
84+
if (options.fullTitle) {
7685
opts.preferShort = false
7786
result.fullTitle = await linkedPage.renderTitle(context, opts)
7887
}
7988

80-
if (option.intro) {
89+
if (options.intro) {
8190
result.intro = await linkedPage.renderProp('intro', context, opts)
8291
}
8392
return result

src/learning-track/lib/process-learning-tracks.js renamed to src/learning-track/lib/process-learning-tracks.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
import getLinkData from './get-link-data.js'
2-
import getApplicableVersions from '#src/versions/lib/get-applicable-versions.js'
3-
import { getDataByLanguage } from '#src/data-directory/lib/get-data.js'
4-
import { renderContent } from '#src/content-render/index.js'
5-
import { executeWithFallback } from '#src/languages/lib/render-with-fallback.js'
1+
import getLinkData from './get-link-data'
2+
import getApplicableVersions from '@/versions/lib/get-applicable-versions'
3+
import { getDataByLanguage } from '@/data-directory/lib/get-data'
4+
import { renderContent } from '@/content-render/index'
5+
import { executeWithFallback } from '@/languages/lib/render-with-fallback'
6+
import { Context, TrackGuide, LearningTrack, ProcessedLearningTracks } from './types'
67

78
const renderOpts = { textOnly: true }
89

910
// This module returns an object that contains a single featured learning track
1011
// and an array of all the other learning tracks for the current version.
11-
export default async function processLearningTracks(rawLearningTracks, context) {
12-
const learningTracks = []
12+
export default async function processLearningTracks(
13+
rawLearningTracks: string[],
14+
context: Context,
15+
): Promise<ProcessedLearningTracks> {
16+
const learningTracks: LearningTrack[] = []
1317

1418
if (!context.currentProduct) {
1519
throw new Error(`Missing context.currentProduct value.`)
@@ -59,7 +63,7 @@ export default async function processLearningTracks(rawLearningTracks, context)
5963
// we need to have the English `title` and `description` to
6064
// fall back to.
6165
//
62-
let enTrack
66+
let enTrack: any
6367
if (context.currentLanguage !== 'en') {
6468
enTrack = getDataByLanguage(
6569
`learning-tracks.${context.currentProduct}.${renderedTrackName}`,
@@ -86,26 +90,28 @@ export default async function processLearningTracks(rawLearningTracks, context)
8690
const title = await executeWithFallback(
8791
context,
8892
() => renderContent(track.title, context, renderOpts),
89-
(enContext) => renderContent(enTrack.title, enContext, renderOpts),
93+
(enContext: any) => renderContent(enTrack.title, enContext, renderOpts),
9094
)
9195
const description = await executeWithFallback(
9296
context,
9397
() => renderContent(track.description, context, renderOpts),
94-
(enContext) => renderContent(enTrack.description, enContext, renderOpts),
98+
(enContext: any) => renderContent(enTrack.description, enContext, renderOpts),
9599
)
96100

97-
const learningTrack = {
101+
const guides = (await getLinkData(track.guides, context)) || []
102+
103+
const learningTrack: LearningTrack = {
98104
trackName: renderedTrackName,
99105
trackProduct: context.currentProduct || null,
100106
title,
101107
description,
102108
// getLinkData respects versioning and only returns guides available in the current version;
103109
// if no guides are available, the learningTrack.guides property will be an empty array.
104-
guides: await getLinkData(track.guides, context),
110+
guides: guides as TrackGuide[],
105111
}
106112

107113
// Only add the track to the array of tracks if there are guides in this version and it's not the featured track.
108-
if (learningTrack.guides.length) {
114+
if (Array.isArray(learningTrack.guides) && learningTrack.guides.length > 0) {
109115
learningTracks.push(learningTrack)
110116
}
111117
}

src/learning-track/lib/types.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/**
2+
* Common types used across learning track components
3+
*/
4+
5+
/**
6+
* Basic context interface for rendering operations
7+
*/
8+
export interface Context {
9+
currentProduct?: string
10+
currentLanguage?: string
11+
currentVersion?: string
12+
pages?: any
13+
redirects?: any
14+
// Additional properties that may be needed for rendering
15+
[key: string]: any
16+
}
17+
18+
/**
19+
* Options for retrieving link data
20+
*/
21+
export interface LinkOptions {
22+
title?: boolean
23+
intro?: boolean
24+
fullTitle?: boolean
25+
}
26+
27+
/**
28+
* Result of processing a link
29+
*/
30+
export interface ProcessedLink {
31+
href: string
32+
page: Page
33+
title?: string
34+
fullTitle?: string
35+
intro?: string
36+
}
37+
38+
/**
39+
* Definitions for featured links data
40+
*/
41+
export interface FeaturedLink {
42+
title: string
43+
href: string
44+
}
45+
46+
export interface PageFeaturedLinks {
47+
[key: string]: string[] | FeaturedLink[]
48+
}
49+
50+
/**
51+
* Page interface for basic page properties
52+
*/
53+
export interface Page {
54+
renderTitle: (context: Context, opts: any) => Promise<string>
55+
renderProp: (prop: string, context: Context, opts: any) => Promise<string>
56+
}
57+
58+
/**
59+
* Guide in a learning track
60+
*/
61+
export interface TrackGuide {
62+
href: string
63+
page: Page
64+
title: string
65+
intro?: string
66+
}
67+
68+
/**
69+
* A processed learning track
70+
*/
71+
export interface LearningTrack {
72+
trackName: string
73+
trackProduct: string | null
74+
title: string
75+
description: string
76+
guides: TrackGuide[]
77+
}
78+
79+
/**
80+
* Learning track metadata with guides
81+
*/
82+
export interface LearningTrackMetadata {
83+
title: string
84+
description: string
85+
guides: string[]
86+
versions?: any
87+
}
88+
89+
/**
90+
* Collection of learning tracks by product and track name
91+
*/
92+
export interface LearningTracks {
93+
[productId: string]: {
94+
[trackName: string]: LearningTrackMetadata
95+
}
96+
}
97+
98+
/**
99+
* Return type for processLearningTracks function
100+
*/
101+
export interface ProcessedLearningTracks {
102+
learningTracks: LearningTrack[]
103+
}
104+
105+
/**
106+
* Learning track data for the current guide
107+
*/
108+
export interface CurrentLearningTrack {
109+
trackName: string
110+
trackProduct: string
111+
trackTitle: string
112+
numberOfGuides?: number
113+
currentGuideIndex?: number
114+
nextGuide?: {
115+
href: string
116+
title: string | undefined
117+
}
118+
prevGuide?: {
119+
href: string
120+
title: string | undefined
121+
}
122+
}

0 commit comments

Comments
 (0)