Skip to content
Merged
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: 5 additions & 1 deletion components/article/ArticlePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { PlatformPicker } from 'components/article/PlatformPicker'
import { ToolPicker } from 'components/article/ToolPicker'
import { MiniTocs } from 'components/ui/MiniTocs'
import { ClientSideHighlight } from 'components/ClientSideHighlight'
import { LearningTrackCard } from 'components/article/LearningTrackCard'
import { RestRedirect } from 'components/RestRedirect'

const ClientSideRefresh = dynamic(() => import('components/ClientSideRefresh'), {
Expand Down Expand Up @@ -63,6 +64,8 @@ export const ArticlePage = () => {
const { t } = useTranslation('pages')
const currentPath = router.asPath.split('?')[0]

const isLearningPath = !!currentLearningTrack?.trackName

return (
<DefaultLayout>
{isDev && <ClientSideRefresh />}
Expand Down Expand Up @@ -114,6 +117,7 @@ export const ArticlePage = () => {
</Link>
</div>
)}
{isLearningPath && <LearningTrackCard track={currentLearningTrack} />}
{miniTocItems.length > 1 && <MiniTocs miniTocItems={miniTocItems} />}
</>
}
Expand All @@ -131,7 +135,7 @@ export const ArticlePage = () => {
</div>
</ArticleGridLayout>

{currentLearningTrack?.trackName ? (
{isLearningPath ? (
<div className="mt-4">
<LearningTrackNav track={currentLearningTrack} />
</div>
Expand Down
56 changes: 56 additions & 0 deletions components/article/LearningTrackCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useRouter } from 'next/router'

import { Link } from 'components/Link'
import type { LearningTrack } from 'components/context/ArticleContext'
import { useTranslation } from 'components/hooks/useTranslation'

type Props = {
track: LearningTrack
}
export function LearningTrackCard({ track }: Props) {
const { locale } = useRouter()
const { t } = useTranslation('learning_track_nav')
const { trackTitle, trackName, nextGuide, trackProduct, numberOfGuides, currentGuideIndex } =
track
return (
<div
data-testid="learning-track-card"
className="py-3 px-4 rounded color-bg-default border d-flex flex-justify-between mb-4 mx-2"
>
<div className="d-flex flex-column width-full">
<Link href={`/${locale}/${trackProduct}/guides`} className="h4 color-fg-default mb-1">
{trackTitle}
</Link>
<span className="f5 color-fg-muted">
{t('current_progress')
.replace('{n}', numberOfGuides)
.replace('{i}', currentGuideIndex + 1)}
</span>
<hr />
<span className="h5 color-fg-default">
{nextGuide ? (
<>
{t('next_guide')}:
<Link
href={`${nextGuide.href}?${new URLSearchParams({
learn: trackName,
learnProduct: trackProduct,
})}`}
className="text-bold color-fg f5 ml-1"
>
{nextGuide.title}
</Link>
</>
) : (
<Link
href={`/${locale}/${trackProduct}/guides`}
className="h5 text-bold color-fg f5 ml-1"
>
{t('more_guides')}
</Link>
)}
</span>
</div>
</div>
)
}
26 changes: 15 additions & 11 deletions components/article/LearningTrackNav.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Link } from 'components/Link'
import type { LearningTrack } from 'components/context/ArticleContext'
import { useTranslation } from 'components/hooks/useTranslation'

Expand All @@ -12,30 +13,33 @@ export function LearningTrackNav({ track }: Props) {
data-testid="learning-track-nav"
className="py-3 px-4 rounded color-bg-default border d-flex flex-justify-between"
>
<span className="d-flex flex-column">
<span className="f5 d-flex flex-column">
{prevGuide && (
<>
<span className="f6 color-fg-muted">{t('prevGuide')}</span>
<a
<span className="color-fg-default">{t('prev_guide')}</span>
<Link
href={`${prevGuide.href}?learn=${trackName}&learnProduct=${trackProduct}`}
className="text-bold color-fg-muted"
className="text-bold color-fg"
>
{prevGuide.title}
</a>
</Link>
</>
)}
</span>

<span className="d-flex flex-column flex-items-end">
<span className="f5 d-flex flex-column flex-items-end">
{nextGuide && (
<>
<span className="f6 color-fg-muted">{t('nextGuide')}</span>
<a
href={`${nextGuide.href}?learn=${trackName}&learnProduct=${trackProduct}`}
className="text-bold color-fg-muted text-right f4"
<span className="color-fg-default">{t('next_guide')}</span>
<Link
href={`${nextGuide.href}?${new URLSearchParams({
learn: trackName,
learnProduct: trackProduct,
})}`}
className="text-bold color-fg text-right"
>
{nextGuide.title}
</a>
</Link>
</>
)}
</span>
Expand Down
7 changes: 5 additions & 2 deletions components/context/ArticleContext.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { createContext, useContext } from 'react'

export type LearningTrack = {
trackName?: string
trackProduct?: string
trackTitle: string
trackName: string
trackProduct: string
prevGuide?: { href: string; title: string }
nextGuide?: { href: string; title: string }
numberOfGuides: number
currentGuideIndex: number
}

export type MiniTocItem = {
Expand Down
7 changes: 1 addition & 6 deletions components/context/TocLandingContext.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import pick from 'lodash/pick'
import { createContext, useContext } from 'react'
import { LearningTrack } from './ArticleContext'
import { FeaturedLink, getFeaturedLinksFromReq } from './ProductLandingContext'

export type LearningTrack = {
trackName?: string
prevGuide?: { href: string; title: string }
nextGuide?: { href: string; title: string }
}

export type TocItem = {
fullPath: string
title: string
Expand Down
6 changes: 4 additions & 2 deletions data/ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,10 @@ product_guides:
how_to: How-to guide
reference: Reference
learning_track_nav:
prevGuide: Previous guide
nextGuide: Next guide
prev_guide: Previous
next_guide: Next
more_guides: More guides →
current_progress: '{i} of {n} in learning path'
toggle_images:
off: Images are off, click to show
on: Images are on, click to hide
Expand Down
9 changes: 8 additions & 1 deletion middleware/learning-track.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ export default async function learningTrack(req, res, next) {
const track = allLearningTracks[trackProduct][trackName]
if (!track) return noTrack()

const currentLearningTrack = { trackName, trackProduct }
// The trackTitle comes from a data .yml file and may use Liquid templating, so we need to render it
const renderOpts = { textOnly: true }
const trackTitle = await renderContent(track.title, req.context, renderOpts)

const currentLearningTrack = { trackName, trackProduct, trackTitle }
const guidePath = getPathWithoutLanguage(getPathWithoutVersion(req.pagePath))

// The raw track.guides will return all guide paths, need to use getLinkData
Expand Down Expand Up @@ -64,6 +68,9 @@ export default async function learningTrack(req, res, next) {

if (guideIndex < 0) return noTrack()

currentLearningTrack.numberOfGuides = trackGuidePaths.length
currentLearningTrack.currentGuideIndex = guideIndex

if (guideIndex > 0) {
const prevGuidePath = trackGuidePaths[guideIndex - 1]
const result = await getLinkData(prevGuidePath, req.context, { title: true, intro: false })
Expand Down
36 changes: 35 additions & 1 deletion tests/rendering/learning-tracks.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { jest } from '@jest/globals'
import { expect, jest } from '@jest/globals'

import { getDOM } from '../helpers/e2etest.js'

Expand Down Expand Up @@ -49,6 +49,40 @@ describe('navigation banner', () => {
})
})

test('render guides index page and verify that the first page has guide navigation links', async () => {
let $ = await getDOM('/en/code-security/guides')

let firstLearningPathURL = null
$('a[href]').each((i, element) => {
if (firstLearningPathURL) {
return
}
const href = $(element).attr('href')
if (new URLSearchParams(href.split('?')[1]).has('learn')) {
firstLearningPathURL = href
}
})

expect(firstLearningPathURL).not.toBeNull()

// Navigate to the first learning path URL
$ = await getDOM(firstLearningPathURL)

expect($('[data-testid=learning-track-card]')).toHaveLength(1)
const $navLinks = $('[data-testid=learning-track-card] a')
expect($navLinks).toHaveLength(2)
$navLinks.each((i, elem) => {
// First link is to the guides index page
if (i === 0) {
expect($(elem).attr('href')).toEqual(expect.stringContaining('/guides'))
// Other links are to the next page in the learning track
} else {
expect($(elem).attr('href')).toMatch('learn=security_advisories')
}
})
expect.assertions(1 + 2 + 2)
})

test('render navigation banner when url is a redirect to a learning track URL', async () => {
const $ = await getDOM(
'/en/enterprise/admin/enterprise-management/enabling-automatic-update-checks?learn=upgrade_your_instance'
Expand Down