Skip to content

Commit

Permalink
Render Enterprise release notes on docs.github.com (github#16367)
Browse files Browse the repository at this point in the history
* Create basic layout

* Create stubbed out release note YAML

* Get a real implementation going

* Simplify using site-data

* Add a real page to send from render-page.js

* Use array of patches

* Render patches

* Add sidebar and breadcrumbs

* Reverse order

* Add date

* Tweak labels

* Redirect to entweb for missing versions

* Render patch.intro

* Move to separate files for patches

* Show support for RC versions

* Improve some comments

* Sticky headers!

* Remove a console log

* Improve example formatting

* Add a link on /admin

* Add a schema and test

* Move to /index.md, add version_num filter

* Improve layout

* Use <details>

* Placeholder more realistic notes

* Don't require links in index pages

* Remove admin/index link for now

* Remove unused frontmatter field

* Add a test for middleware

* Fix remaining YAML file to test CI

* Update 2-rc.yml

* Don't call it RC

* Just push

* Make a main a div

* Fix a borked class

* Lint YAML files

* Improve Download link

Co-authored-by: Sarah Schneider <sarahs@users.noreply.github.com>

* Improve check order

* Move to contextualizers

* Use alternative version thing

Co-authored-by: Sarah Schneider <sarahs@users.noreply.github.com>

* Move back to `release-notes.md`

* Use version for anchor IDs

* Undo category-pages test change

* Fix borked details layout in Chrome

* Improve mobile setup

* Render markdown in note tags

* Use allVersions[currentVersion] again

Co-authored-by: Sarah Schneider <sarahs@users.noreply.github.com>

* Undo change to extended-markdown

* Add whitespace so it renders markdown bits

* Remove 2-22 files

* Add check for any release notes

* Fix the failing tests

Co-authored-by: Kevin Heis <heiskr@users.noreply.github.com>
Co-authored-by: Sarah Schneider <sarahs@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 20, 2020
1 parent d970bbe commit cc719ff
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 1 deletion.
7 changes: 7 additions & 0 deletions content/admin/release-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: Release notes
intro: The release notes for {{ allVersions[currentVersion].versionTitle }}.
layout: release-notes
versions:
enterprise-server: '*'
---
Empty file added data/release-notes/.gitkeep
Empty file.
12 changes: 12 additions & 0 deletions includes/release-notes-category-label.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{% assign sectionTitle = section.title | default: 'Misc' %}

{% case sectionTitle %}
{% when "New Feature" %}
{% assign colors = "bg-orange text-white" %}
{% when "Bugs" %}
{% assign colors = "bg-purple text-white" %}
{% else %}
{% assign colors = "bg-blue text-white" %}
{% endcase %}

<span class="{{ colors }} px-3 py-2 f5 text-uppercase text-mono">{{ sectionTitle }}</span>
93 changes: 93 additions & 0 deletions layouts/release-notes.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{% assign product = siteTree[currentLanguage][currentVersion].products[currentProduct] %}

<!doctype html>
<html lang="{{currentLanguage}}">
{% include head %}

<body class="d-lg-flex">
{% include sidebar %}

<main class="width-full">
{% include header %}
{% include deprecation-banner %}

<div class="container-xl px-3 px-md-6 my-4 my-lg-4 markdown-body">
<div class="d-lg-flex flex-justify-between">
<div class="d-block d-lg-none">{% include article-version-switcher %}</div>
<div class="d-flex flex-items-center" style="height: 39px;">
{% include breadcrumbs %}
</div>
<div class="d-none d-lg-block">{% include article-version-switcher %}</div>
</div>

<article class="mt-2 d-flex gutter">
<div class="col-12 col-md-10">
<header>
<h1>{{ allVersions[currentVersion].versionTitle }}</h1>
</header>

{% for patch in releaseNotes %}
<details id="{{ patch.version }}" class="details-reset mb-3" {% if forloop.first %}open{% endif %}>
<summary class="position-sticky top-0 bg-white pb-2 d-flex flex-items-center flex-justify-between border-bottom">
<h2 class="d-flex flex-items-center border-bottom-0">
{{ patch.version }}

{% if patch.release_candidate %}
<span class="IssueLabel bg-orange text-white mx-2">Release Candidate</span>
{% endif %}
</h2>

<div>
<span class="text-gray-light text-mono text-small" style="margin-left: auto">{{ patch.date | date: "%B %d, %Y" }}</span>

<div class="mt-1 d-flex flex-items-center f5">
<a href="https://enterprise.github.com/releases/{{ patch.version }}/download">
Download GHES {{ patch.version }}
</a>

<button class="js-print btn-link ml-6">
Print
</button>
</div>
</div>
</summary>

<p>{{ patch.intro }}</p>

{% for section in patch.sortedNotes %}
<div class="py-6 border-bottom d-block d-md-flex flex-items-baseline">
<div class="mr-2 flex-shrink-0 mb-3">
{% include 'release-notes-category-label' %}
</div>

<ul class="flex-auto container-xl">
{% for note in section.notes %}
{% if note.note and note.note != '<p>n/a</p>' %}
<li>
{{ note.note }}
</li>
{% endif %}
{% endfor %}
</ul>
</div>
{% endfor %}
</details>
{% endfor %}
</div>

<aside class="col-2 d-none d-md-block no-print">
<h3 class="f5">Patch versions</h3>
<ul>
{% for patch in releaseNotes %}
<li><a href="#{{ patch.version }}">{{ patch.version }}</a></li>
{% endfor %}
</ul>
</aside>
</article>
</div>

{% include support %}
{% include small-footer %}
</main>
</body>
</html>
2 changes: 1 addition & 1 deletion lib/liquid-tags/extended-markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const tags = {
danger: 'border rounded-1 mb-4 p-3 border-red bg-red-light f5'
}

const template = '<div class="extended-markdown {{ tagName }} {{ classes }}">{{ output }}</div>'
const template = '<div class="extended-markdown {{ tagName }} {{ classes }}">\n{{ output }}\n</div>'

class ExtendedMarkdown extends Liquid.Block {
async render (context) {
Expand Down
34 changes: 34 additions & 0 deletions lib/release-notes-schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module.exports = {
properties: {
intro: {
type: 'string',
required: true
},
date: {
type: 'string',
format: 'date',
required: true
},
release_candidate: {
type: 'boolean',
default: false
},
notes: {
required: true,
type: 'array',
items: {
type: 'object',
properties: {
note: {
type: 'string',
required: true
},
type: {
type: 'string',
required: true
}
}
}
}
}
}
7 changes: 7 additions & 0 deletions lib/render-content.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ renderContent.liquid.registerFilters({
obj_size: (input) => {
if (!input) return 0
return Object.keys(input).length
},
/**
* Returns the version number of a GHES version string
* ex: enterprise-server@2.22 => 2.22
*/
version_num: (input) => {
return input.split('@')[1]
}
})

Expand Down
65 changes: 65 additions & 0 deletions middleware/contextualizers/enterprise-release-notes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const renderContent = require('../../lib/render-content')
const patterns = require('../../lib/patterns')

module.exports = async (req, res, next) => {
// The `/release-notes` sub-path
if (!req.path.endsWith('/release-notes')) return next()

// ignore paths that don't have an enterprise version number
if (!patterns.getEnterpriseServerNumber.test(req.path)) return next()

// extract enterprise version from path, e.g. 2.16
const requestedVersion = req.path.match(patterns.getEnterpriseServerNumber)[1]

const versionString = `${requestedVersion.replace(/\./g, '-')}`

const allReleaseNotes = req.context.site.data['release-notes']

// This version doesn't have any release notes - let's be helpful and redirect
// to the notes on `enterprise.github.com`
if (!allReleaseNotes || !allReleaseNotes[versionString]) {
return res.redirect(`https://enterprise.github.com/releases/${requestedVersion}.0/notes`)
}

const releaseNotes = allReleaseNotes[versionString]
const keys = Object.keys(releaseNotes)
// Turn { [key]: { notes, intro, date } }
// into [{ version, notes, intro, date }]
const patches = keys
.sort((a, b) => {
if (a > b) return -1
if (a < b) return 1
return 0
})
.map(key => ({ version: `${requestedVersion}.${key}`, ...releaseNotes[key] }))

const renderedPatches = await Promise.all(patches.map(async patch => {
// Render the intro block, it might contain markdown formatting
patch.intro = await renderContent(patch.intro, req.context)

// Run the notes through the markdown rendering pipeline
patch.notes = await Promise.all(patch.notes.map(async note => {
if (note.note) note.note = await renderContent(note.note, req.context)
return note
}))

// Sort the notes into sections
// Takes an array of notes: Array<{ note, type }>
// Turns it into { [type]: [{ note }] }
patch.sortedNotes = patch.notes.reduce((prev, curr) => {
const existingObj = prev.find(o => o.title === curr.type)
if (!existingObj) {
prev.push({ title: curr.type, notes: [curr] })
} else {
existingObj.notes.push(curr)
}
return prev
}, [])

return patch
}))

req.context.releaseNotes = renderedPatches

return next()
}
1 change: 1 addition & 0 deletions middleware/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ module.exports = function (app) {
app.get('/_500', asyncMiddleware(require('./trigger-error')))

// *** Preparation for render-page ***
app.use(asyncMiddleware(require('./contextualizers/enterprise-release-notes')))
app.use(require('./contextualizers/graphql'))
app.use(require('./contextualizers/rest'))
app.use(require('./contextualizers/webhooks'))
Expand Down
28 changes: 28 additions & 0 deletions tests/content/lint-files.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const { zip } = require('lodash')
const yaml = require('js-yaml')
const languages = require('../../lib/languages')
const { tags } = require('../../lib/liquid-tags/extended-markdown')
const ghesReleaseNotesSchema = require('../../lib/release-notes-schema')
const revalidator = require('revalidator')

const rootDir = path.join(__dirname, '../..')
const contentDir = path.join(rootDir, 'content')
Expand Down Expand Up @@ -373,6 +375,32 @@ describe('lint-files', () => {
})
}
)

// GHES release notes
const ghesReleaseNotesDir = path.join(__dirname, '../../data/release-notes')
const ghesReleaseNotesYamlAbsPaths = walk(ghesReleaseNotesDir, yamlWalkOptions).sort()
const ghesReleaseNotesYamlRelPaths = ghesReleaseNotesYamlAbsPaths.map(p => path.relative(rootDir, p))
const ghesReleaseNotesYamlTuples = zip(ghesReleaseNotesYamlRelPaths, ghesReleaseNotesYamlAbsPaths)

if (ghesReleaseNotesYamlTuples.length > 0) {
describe.each(ghesReleaseNotesYamlTuples)(
'in "%s"',
(yamlRelPath, yamlAbsPath) => {
let dictionary

beforeAll(async () => {
const fileContents = await fs.promises.readFile(yamlAbsPath, 'utf8')
dictionary = yaml.safeLoad(fileContents, { filename: yamlRelPath })
})

it('matches the schema', () => {
const { errors } = revalidator.validate(dictionary, ghesReleaseNotesSchema)
const errorMessage = errors.map(error => `- [${error.property}]: ${error.attribute}, ${error.message}`).join('\n')
expect(errors.length, errorMessage).toBe(0)
})
}
)
}
})

function formatLinkError (message, links) {
Expand Down
21 changes: 21 additions & 0 deletions tests/routing/enterprise-release-notes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const { get } = require('../helpers')

describe('enterprise release notes', () => {
jest.setTimeout(60 * 1000)

beforeAll(async () => {
// The first page load takes a long time so let's get it out of the way in
// advance to call out that problem specifically rather than misleadingly
// attributing it to the first test
await get('/')
})

it('redirects to the release notes on enterprise.github.com if none are present for this version here', async () => {
const res = await get('/en/enterprise-server@2.21/admin/release-notes')
expect(res.statusCode).toBe(302)
expect(res.headers.location).toBe('https://enterprise.github.com/releases/2.21.0/notes')
})

// We can't write this test until we have real release notes
it.todo('renders the release-notes layout if this version\'s release notes are in this repo')
})

0 comments on commit cc719ff

Please sign in to comment.