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/ui/MarkdownContent/stylesheets/code.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
border: 1px var(--color-border-default) solid;
}

pre code {
white-space: pre-wrap;
}

[class~="height-constrained-code-block"] pre {
max-height: 32rem;
overflow: auto;
overflow-y: auto;
}

[class~="code-example"] {
Expand Down
5 changes: 0 additions & 5 deletions data/learning-tracks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ Learning track data for a product is defined in two places:

For example, in `data/learning-tracks/actions.yml`, each of the items from the content file's `learningTracks` array is represented with additional data such as `title`, `description`, and an array of `guides` links.

One learning track in this YAML **per version** must be designated as a "featured" learning track via `featured_track: true`, which will set it to appear at the top of the product guides page. A test will fail if this property is missing.

The `featured_track` property can be a simple boolean (i.e., `featured_track: true`) or it can be a string that includes versioning statements (e.g., `featured_track: '{% ifversion fpt %}true{% else %}false{% endif %}'`). If you use versioning, you'll have multiple `featured_track`s per YML file, but make sure that only one will render in each currently supported version. A test will fail if there are more or less than one featured link for each version.

## Versioning

Versioning for learning tracks is processed at page render time. The code lives in [`lib/learning-tracks.js`](lib/learning-tracks.js), which is called by `page.render()`. The processed learning tracks are then rendered by `components/guides`.
Expand All @@ -41,7 +37,6 @@ For example:
learning_track_name:
title: 'Learning track title'
description: 'Learning track description'
featured_track: true
versions:
ghes: '>=3.0'
guides:
Expand Down
1 change: 0 additions & 1 deletion data/learning-tracks/actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ getting_started:
- /actions/using-workflows/about-workflows
- /actions/using-workflows/reusing-workflows
- /actions/security-guides/security-hardening-for-github-actions
featured_track: true
adopting_github_actions_for_your_enterprise_ghec:
title: Adopt GitHub Actions for your enterprise
description: >-
Expand Down
2 changes: 0 additions & 2 deletions data/learning-tracks/admin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ get_started_with_github_ae:
description: >-
Learn about {% data variables.product.prodname_ghe_managed %} and complete
the initial configuration of a new enterprise.
featured_track: true
versions:
ghae: '*'
guides:
Expand All @@ -20,7 +19,6 @@ deploy_an_instance:
description: >-
Install {% data variables.product.prodname_ghe_server %} on your platform of
choice and configure SAML authentication.
featured_track: true
versions:
ghes: '*'
guides:
Expand Down
2 changes: 0 additions & 2 deletions data/learning-tracks/code-security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ security_advisories:
description: >-
Using repository security advisories to privately fix a reported
vulnerability and get a CVE.
featured_track: '{% ifversion fpt or ghec %}true{% else %}false{% endif %}'
guides:
- >-
/code-security/security-advisories/guidance-on-reporting-and-writing/about-coordinated-disclosure-of-security-vulnerabilities
Expand Down Expand Up @@ -174,7 +173,6 @@ code_security_actions:
description: >-
Check your default branch and every pull request to keep vulnerabilities and
errors out of your repository.
featured_track: '{% ifversion ghae or ghes %}true{% else %}false{% endif %}'
guides:
- >-
/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/about-code-scanning
Expand Down
6 changes: 1 addition & 5 deletions lib/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,7 @@ class Page {

// Learning tracks may contain Liquid and need to have versioning processed.
if (this.rawLearningTracks) {
const { featuredTrack, learningTracks } = await processLearningTracks(
this.rawLearningTracks,
context,
)
this.featuredTrack = featuredTrack
const { learningTracks } = await processLearningTracks(this.rawLearningTracks, context)
this.learningTracks = learningTracks
}

Expand Down
3 changes: 0 additions & 3 deletions src/content-linter/lib/learning-tracks-schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ export default {
type: 'array',
items: { type: 'string' },
},
featured_track: {
type: ['boolean', 'string'],
},
versions: versionsProps,
},
},
Expand Down
46 changes: 1 addition & 45 deletions src/content-linter/tests/lint-files.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,13 @@ import { frontmatter, deprecatedProperties } from '../../../lib/frontmatter.js'
import languages from '../../../lib/languages.js'
import releaseNotesSchema from '../lib/release-notes-schema.js'
import learningTracksSchema from '../lib/learning-tracks-schema.js'
import { renderContent, liquid } from '#src/content-render/index.js'
import getApplicableVersions from '../../../lib/get-applicable-versions.js'
import { allVersions } from '../../../lib/all-versions.js'
import { liquid } from '#src/content-render/index.js'
import { getDiffFiles } from '../lib/diff-files.js'
import { formatAjvErrors } from '../../../tests/helpers/schemas.js'

jest.useFakeTimers({ legacyFakeTimers: true })

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const enterpriseServerVersions = Object.keys(allVersions).filter((v) =>
v.startsWith('enterprise-server@'),
)

const rootDir = path.join(__dirname, '../../..')
const contentDir = path.join(rootDir, 'content')
Expand Down Expand Up @@ -1023,45 +1018,6 @@ describe('lint learning tracks', () => {
expect(valid, errors).toBe(true)
})

it('has one and only one featured track per supported version', async () => {
// Use the YAML filename to determine which product this refers to, and then peek
// inside the product TOC frontmatter to see which versions the product is available in.
const product = path.posix.basename(yamlRelPath, '.yml')
const productTocPath = path.posix.join('content', product, 'index.md')
const productContents = await fs.readFile(productTocPath, 'utf8')
const { data } = frontmatter(productContents)
const productVersions = getApplicableVersions(data.versions, productTocPath)

const featuredTracks = {}
const context = { enterpriseServerVersions }

// For each of the product's versions, render the learning track data and look for a featured track.
await Promise.all(
productVersions.map(async (version) => {
const featuredTracksPerVersion = []

for (const entry of Object.values(dictionary)) {
if (!entry.featured_track) return
context.currentVersion = version
context[allVersions[version].shortName] = true
const isFeaturedLink =
typeof entry.featured_track === 'boolean' ||
(await renderContent(entry.featured_track, context, {
textOnly: true,
})) === 'true'
featuredTracksPerVersion.push(isFeaturedLink)
}

featuredTracks[version] = featuredTracksPerVersion.length
}),
)

Object.entries(featuredTracks).forEach(([version, numOfFeaturedTracks]) => {
const errorMessage = `Expected 1 featured learning track but found ${numOfFeaturedTracks} for ${version} in ${yamlAbsPath}`
expect(numOfFeaturedTracks, errorMessage).toBe(1)
})
})

it('contains valid liquid', () => {
const toLint = []
Object.values(dictionary).forEach(({ title, description }) => {
Expand Down
15 changes: 0 additions & 15 deletions src/landings/components/GuidesHero.module.scss

This file was deleted.

81 changes: 3 additions & 78 deletions src/landings/components/GuidesHero.tsx
Original file line number Diff line number Diff line change
@@ -1,92 +1,17 @@
import cx from 'classnames'
import { useProductGuidesContext } from 'src/landings/components/ProductGuidesContext'
import { ArrowRightIcon, StarFillIcon } from '@primer/octicons-react'
import { useTranslation } from 'components/hooks/useTranslation'
import { Link } from 'components/Link'
import { TruncateLines } from 'components/ui/TruncateLines'
import { Lead } from 'components/ui/Lead'
import styles from './GuidesHero.module.scss'

export const GuidesHero = () => {
const { title, intro, featuredTrack } = useProductGuidesContext()
const { t } = useTranslation('product_guides')
const cardWidth = 280
const firstCardWidth = cardWidth + 10 // so the english text doesn't wrap

const guideItems = featuredTrack?.guides?.map((guide) => (
<li className="px-2 d-flex flex-shrink-0" key={guide.href} style={{ width: cardWidth }}>
<Link
href={`${guide.href}?learn=${featuredTrack.trackName}&learnProduct=${featuredTrack.trackProduct}`}
className="d-inline-block Box p-5 color-bg-default color-border-default no-underline"
>
<div className="d-flex flex-justify-between flex-items-center">
<div
className="color-bg-default color-fg-accent border d-inline-flex flex-items-center flex-justify-center circle"
style={{ width: 40, height: 40 }}
>
{featuredTrack.guides && (
<span className="f2 lh-condensed-ultra text-center text-bold">
{featuredTrack.guides?.indexOf(guide) + 1}
</span>
)}
</div>
<div className="color-fg-muted h6 text-uppercase">
{t('guide_types')[guide.page?.type || '']}
</div>
</div>
<h3 className="text-semibold my-4 color-fg-default">{guide.title}</h3>
<TruncateLines maxLines={8} className="color-fg-muted f5 my-4">
<span dangerouslySetInnerHTML={{ __html: guide.intro }} />
</TruncateLines>
</Link>
</li>
))
export function GuidesHero() {
const { title, intro } = useProductGuidesContext()

return (
<div>
<header className="d-flex gutter mb-6 my-4">
<header className="gutter mb-6 my-4">
<div className="col-12">
<h1 id="title-h1">{title}</h1>
{intro && <Lead data-search="lead">{intro}</Lead>}
</div>
</header>
{featuredTrack && featuredTrack.guides && featuredTrack.guides.length > 0 && (
<div className="mb-6 position-relative overflow-hidden mr-n3 ml-n3 px-3">
<ul
data-testid="feature-track"
className="list-style-none d-flex flex-nowrap overflow-x-scroll px-2"
>
<li className="px-2 d-flex flex-shrink-0" style={{ width: firstCardWidth }}>
<div className="d-inline-block Box p-5 color-bg-subtle">
<div
className="d-inline-flex flex-items-center flex-justify-center circle border"
style={{ width: 40, height: 40, border: '2px white solid' }}
>
<StarFillIcon size={24} />
</div>
<h2 className="text-semibold my-4 f3">{featuredTrack.title}</h2>
<div className="f5 my-4">{featuredTrack.description}</div>
<Link
{...{ 'aria-label': `${featuredTrack.title} - ${t('start_path')}` }}
className="d-inline-flex flex-items-center flex-justify-center btn ws-normal px-4 py-2 f5 no-underline text-bold"
role="button"
href={`${featuredTrack.guides[0].href}?learn=${featuredTrack.trackName}&learnProduct=${featuredTrack.trackProduct}`}
>
{t(`start_path`)}
<ArrowRightIcon size={20} className="ml-2" />
</Link>
</div>
</li>
{guideItems}
</ul>
<div
className={cx('position-absolute top-0 bottom-0 left-0 ml-3 pl-3', styles.fadeLeft)}
></div>
<div
className={cx('position-absolute top-0 bottom-0 right-0 mr-3 pr-3', styles.fadeRight)}
></div>
</div>
)}
</div>
)
}
13 changes: 2 additions & 11 deletions src/landings/components/ProductGuidesContext.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createContext, useContext } from 'react'
import pick from 'lodash/pick'

export type FeaturedTrack = {
export type LearningTrack = {
trackName: string
trackProduct: string
title: string
Expand All @@ -20,8 +20,7 @@ export type ArticleGuide = {
export type ProductGuidesContextT = {
title: string
intro: string
featuredTrack?: FeaturedTrack
learningTracks?: Array<FeaturedTrack>
learningTracks?: Array<LearningTrack>
includeGuides?: Array<ArticleGuide>
allTopics?: Array<string>
}
Expand All @@ -45,14 +44,6 @@ export const getProductGuidesContextFromRequest = (req: any): ProductGuidesConte

return {
...pick(page, ['title', 'intro', 'allTopics']),
featuredTrack: page.featuredTrack
? {
...pick(page.featuredTrack, ['title', 'description', 'trackName', 'trackProduct']),
guides: (page.featuredTrack?.guides || []).map((guide: any) => {
return pick(guide, ['title', 'intro', 'href', 'page.type'])
}),
}
: null,
learningTracks: (page.learningTracks || []).map((track: any) => ({
...pick(track, ['title', 'description', 'trackName', 'trackProduct']),
guides: (track.guides || []).map((guide: any) => {
Expand Down
4 changes: 2 additions & 2 deletions src/learning-track/components/guides/LearningTrack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { useTranslation } from 'components/hooks/useTranslation'
import { ArrowRightIcon } from '@primer/octicons-react'
import { ActionList } from '@primer/react'
import { useState } from 'react'
import { FeaturedTrack } from 'src/landings/components/ProductGuidesContext'
import { LearningTrack as LearningTrackT } from 'src/landings/components/ProductGuidesContext'
import { TruncateLines } from 'components/ui/TruncateLines'
import { slug } from 'github-slugger'
import styles from './LearningTrack.module.scss'
import { Link } from 'components/Link'

type Props = {
track: FeaturedTrack
track: LearningTrackT
}

const DEFAULT_VISIBLE_GUIDES = 4
Expand Down
24 changes: 3 additions & 21 deletions src/learning-track/lib/process-learning-tracks.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,11 @@ const renderOpts = { textOnly: true }
export default async function processLearningTracks(rawLearningTracks, context) {
const learningTracks = []

let featuredTrack

if (!context.currentProduct) {
throw new Error(`Missing context.currentProduct value.`)
}

for (const rawTrackName of rawLearningTracks) {
let isFeaturedTrack = false

// Track names in frontmatter may include Liquid conditionals.
const renderedTrackName = await renderContent(rawTrackName, context, renderOpts)
if (!renderedTrackName) continue
Expand Down Expand Up @@ -48,7 +44,7 @@ export default async function processLearningTracks(rawLearningTracks, context)
// We do this for two reasons:
//
// 1. For each learning-track .yml file (in data) always want the
// English values for `guides`, `versions`, `featured_track`.
// English values for `guides`, `versions`.
// Meaning, for the translated learning tracks we only keep the
// `title` and `description`.
//
Expand All @@ -69,7 +65,6 @@ export default async function processLearningTracks(rawLearningTracks, context)
// from the English equivalent.
track.guides = enTrack.guides
track.versions = enTrack.versions
track.featured_track = enTrack.featured_track
}

// If there is no `versions` prop in the learning track frontmatter, assume the track should display in all versions.
Expand Down Expand Up @@ -103,24 +98,11 @@ export default async function processLearningTracks(rawLearningTracks, context)
guides: await getLinkData(track.guides, context),
}

// Determine if this is the featured track.
if (track.featured_track) {
// Featured track properties may be booleans or string that include Liquid conditionals with versioning.
// We need to parse any strings to determine if the featured track is relevant for this version.
isFeaturedTrack =
track.featured_track === true ||
(await renderContent(track.featured_track, context, renderOpts)) === 'true'

if (isFeaturedTrack) {
featuredTrack = learningTrack
}
}

// 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 && !isFeaturedTrack) {
if (learningTrack.guides.length) {
learningTracks.push(learningTrack)
}
}

return { featuredTrack, learningTracks }
return { learningTracks }
}
Loading