Skip to content

Commit a3b3625

Browse files
authored
repo sync
2 parents 2db4f82 + cc719ff commit a3b3625

File tree

12 files changed

+275
-2
lines changed

12 files changed

+275
-2
lines changed

content/admin/release-notes.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
title: Release notes
3+
intro: The release notes for {{ allVersions[currentVersion].versionTitle }}.
4+
layout: release-notes
5+
versions:
6+
enterprise-server: '*'
7+
---

data/release-notes/.gitkeep

Whitespace-only changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{% assign sectionTitle = section.title | default: 'Misc' %}
2+
3+
{% case sectionTitle %}
4+
{% when "New Feature" %}
5+
{% assign colors = "bg-orange text-white" %}
6+
{% when "Bugs" %}
7+
{% assign colors = "bg-purple text-white" %}
8+
{% else %}
9+
{% assign colors = "bg-blue text-white" %}
10+
{% endcase %}
11+
12+
<span class="{{ colors }} px-3 py-2 f5 text-uppercase text-mono">{{ sectionTitle }}</span>

layouts/release-notes.html

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
{% assign product = siteTree[currentLanguage][currentVersion].products[currentProduct] %}
2+
3+
<!doctype html>
4+
<html lang="{{currentLanguage}}">
5+
{% include head %}
6+
7+
<body class="d-lg-flex">
8+
{% include sidebar %}
9+
10+
<main class="width-full">
11+
{% include header %}
12+
{% include deprecation-banner %}
13+
14+
<div class="container-xl px-3 px-md-6 my-4 my-lg-4 markdown-body">
15+
<div class="d-lg-flex flex-justify-between">
16+
<div class="d-block d-lg-none">{% include article-version-switcher %}</div>
17+
<div class="d-flex flex-items-center" style="height: 39px;">
18+
{% include breadcrumbs %}
19+
</div>
20+
<div class="d-none d-lg-block">{% include article-version-switcher %}</div>
21+
</div>
22+
23+
<article class="mt-2 d-flex gutter">
24+
<div class="col-12 col-md-10">
25+
<header>
26+
<h1>{{ allVersions[currentVersion].versionTitle }}</h1>
27+
</header>
28+
29+
{% for patch in releaseNotes %}
30+
<details id="{{ patch.version }}" class="details-reset mb-3" {% if forloop.first %}open{% endif %}>
31+
<summary class="position-sticky top-0 bg-white pb-2 d-flex flex-items-center flex-justify-between border-bottom">
32+
<h2 class="d-flex flex-items-center border-bottom-0">
33+
{{ patch.version }}
34+
35+
{% if patch.release_candidate %}
36+
<span class="IssueLabel bg-orange text-white mx-2">Release Candidate</span>
37+
{% endif %}
38+
</h2>
39+
40+
<div>
41+
<span class="text-gray-light text-mono text-small" style="margin-left: auto">{{ patch.date | date: "%B %d, %Y" }}</span>
42+
43+
<div class="mt-1 d-flex flex-items-center f5">
44+
<a href="https://enterprise.github.com/releases/{{ patch.version }}/download">
45+
Download GHES {{ patch.version }}
46+
</a>
47+
48+
<button class="js-print btn-link ml-6">
49+
Print
50+
</button>
51+
</div>
52+
</div>
53+
</summary>
54+
55+
<p>{{ patch.intro }}</p>
56+
57+
{% for section in patch.sortedNotes %}
58+
<div class="py-6 border-bottom d-block d-md-flex flex-items-baseline">
59+
<div class="mr-2 flex-shrink-0 mb-3">
60+
{% include 'release-notes-category-label' %}
61+
</div>
62+
63+
<ul class="flex-auto container-xl">
64+
{% for note in section.notes %}
65+
{% if note.note and note.note != '<p>n/a</p>' %}
66+
<li>
67+
{{ note.note }}
68+
</li>
69+
{% endif %}
70+
{% endfor %}
71+
</ul>
72+
</div>
73+
{% endfor %}
74+
</details>
75+
{% endfor %}
76+
</div>
77+
78+
<aside class="col-2 d-none d-md-block no-print">
79+
<h3 class="f5">Patch versions</h3>
80+
<ul>
81+
{% for patch in releaseNotes %}
82+
<li><a href="#{{ patch.version }}">{{ patch.version }}</a></li>
83+
{% endfor %}
84+
</ul>
85+
</aside>
86+
</article>
87+
</div>
88+
89+
{% include support %}
90+
{% include small-footer %}
91+
</main>
92+
</body>
93+
</html>

lib/liquid-tags/extended-markdown.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const tags = {
1111
danger: 'border rounded-1 mb-4 p-3 border-red bg-red-light f5'
1212
}
1313

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

1616
class ExtendedMarkdown extends Liquid.Block {
1717
async render (context) {

lib/release-notes-schema.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
module.exports = {
2+
properties: {
3+
intro: {
4+
type: 'string',
5+
required: true
6+
},
7+
date: {
8+
type: 'string',
9+
format: 'date',
10+
required: true
11+
},
12+
release_candidate: {
13+
type: 'boolean',
14+
default: false
15+
},
16+
notes: {
17+
required: true,
18+
type: 'array',
19+
items: {
20+
type: 'object',
21+
properties: {
22+
note: {
23+
type: 'string',
24+
required: true
25+
},
26+
type: {
27+
type: 'string',
28+
required: true
29+
}
30+
}
31+
}
32+
}
33+
}
34+
}

lib/render-content.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ renderContent.liquid.registerFilters({
2626
obj_size: (input) => {
2727
if (!input) return 0
2828
return Object.keys(input).length
29+
},
30+
/**
31+
* Returns the version number of a GHES version string
32+
* ex: enterprise-server@2.22 => 2.22
33+
*/
34+
version_num: (input) => {
35+
return input.split('@')[1]
2936
}
3037
})
3138

lib/warm-server.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
const statsd = require('./statsd')
12
const fetchEarlyAccessPaths = require('./fetch-early-access-paths')
23
let pages, site, redirects, siteTree, earlyAccessPaths
34

4-
module.exports = async function warmServer () {
5+
async function warmServer () {
56
if (!pages) {
67
if (process.env.NODE_ENV !== 'test') {
78
console.log('Priming context information')
@@ -22,3 +23,7 @@ module.exports = async function warmServer () {
2223
pages, site, redirects, siteTree, earlyAccessPaths
2324
}
2425
}
26+
27+
// Instrument the `warmServer` function so that
28+
// it's wrapped in a timer that reports to Datadog
29+
module.exports = statsd.asyncTimer(warmServer, 'warm_server')
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
const renderContent = require('../../lib/render-content')
2+
const patterns = require('../../lib/patterns')
3+
4+
module.exports = async (req, res, next) => {
5+
// The `/release-notes` sub-path
6+
if (!req.path.endsWith('/release-notes')) return next()
7+
8+
// ignore paths that don't have an enterprise version number
9+
if (!patterns.getEnterpriseServerNumber.test(req.path)) return next()
10+
11+
// extract enterprise version from path, e.g. 2.16
12+
const requestedVersion = req.path.match(patterns.getEnterpriseServerNumber)[1]
13+
14+
const versionString = `${requestedVersion.replace(/\./g, '-')}`
15+
16+
const allReleaseNotes = req.context.site.data['release-notes']
17+
18+
// This version doesn't have any release notes - let's be helpful and redirect
19+
// to the notes on `enterprise.github.com`
20+
if (!allReleaseNotes || !allReleaseNotes[versionString]) {
21+
return res.redirect(`https://enterprise.github.com/releases/${requestedVersion}.0/notes`)
22+
}
23+
24+
const releaseNotes = allReleaseNotes[versionString]
25+
const keys = Object.keys(releaseNotes)
26+
// Turn { [key]: { notes, intro, date } }
27+
// into [{ version, notes, intro, date }]
28+
const patches = keys
29+
.sort((a, b) => {
30+
if (a > b) return -1
31+
if (a < b) return 1
32+
return 0
33+
})
34+
.map(key => ({ version: `${requestedVersion}.${key}`, ...releaseNotes[key] }))
35+
36+
const renderedPatches = await Promise.all(patches.map(async patch => {
37+
// Render the intro block, it might contain markdown formatting
38+
patch.intro = await renderContent(patch.intro, req.context)
39+
40+
// Run the notes through the markdown rendering pipeline
41+
patch.notes = await Promise.all(patch.notes.map(async note => {
42+
if (note.note) note.note = await renderContent(note.note, req.context)
43+
return note
44+
}))
45+
46+
// Sort the notes into sections
47+
// Takes an array of notes: Array<{ note, type }>
48+
// Turns it into { [type]: [{ note }] }
49+
patch.sortedNotes = patch.notes.reduce((prev, curr) => {
50+
const existingObj = prev.find(o => o.title === curr.type)
51+
if (!existingObj) {
52+
prev.push({ title: curr.type, notes: [curr] })
53+
} else {
54+
existingObj.notes.push(curr)
55+
}
56+
return prev
57+
}, [])
58+
59+
return patch
60+
}))
61+
62+
req.context.releaseNotes = renderedPatches
63+
64+
return next()
65+
}

middleware/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ module.exports = function (app) {
6969
app.get('/_500', asyncMiddleware(require('./trigger-error')))
7070

7171
// *** Preparation for render-page ***
72+
app.use(asyncMiddleware(require('./contextualizers/enterprise-release-notes')))
7273
app.use(require('./contextualizers/graphql'))
7374
app.use(require('./contextualizers/rest'))
7475
app.use(require('./contextualizers/webhooks'))

tests/content/lint-files.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const { zip } = require('lodash')
77
const yaml = require('js-yaml')
88
const languages = require('../../lib/languages')
99
const { tags } = require('../../lib/liquid-tags/extended-markdown')
10+
const ghesReleaseNotesSchema = require('../../lib/release-notes-schema')
11+
const revalidator = require('revalidator')
1012

1113
const rootDir = path.join(__dirname, '../..')
1214
const contentDir = path.join(rootDir, 'content')
@@ -373,6 +375,32 @@ describe('lint-files', () => {
373375
})
374376
}
375377
)
378+
379+
// GHES release notes
380+
const ghesReleaseNotesDir = path.join(__dirname, '../../data/release-notes')
381+
const ghesReleaseNotesYamlAbsPaths = walk(ghesReleaseNotesDir, yamlWalkOptions).sort()
382+
const ghesReleaseNotesYamlRelPaths = ghesReleaseNotesYamlAbsPaths.map(p => path.relative(rootDir, p))
383+
const ghesReleaseNotesYamlTuples = zip(ghesReleaseNotesYamlRelPaths, ghesReleaseNotesYamlAbsPaths)
384+
385+
if (ghesReleaseNotesYamlTuples.length > 0) {
386+
describe.each(ghesReleaseNotesYamlTuples)(
387+
'in "%s"',
388+
(yamlRelPath, yamlAbsPath) => {
389+
let dictionary
390+
391+
beforeAll(async () => {
392+
const fileContents = await fs.promises.readFile(yamlAbsPath, 'utf8')
393+
dictionary = yaml.safeLoad(fileContents, { filename: yamlRelPath })
394+
})
395+
396+
it('matches the schema', () => {
397+
const { errors } = revalidator.validate(dictionary, ghesReleaseNotesSchema)
398+
const errorMessage = errors.map(error => `- [${error.property}]: ${error.attribute}, ${error.message}`).join('\n')
399+
expect(errors.length, errorMessage).toBe(0)
400+
})
401+
}
402+
)
403+
}
376404
})
377405

378406
function formatLinkError (message, links) {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const { get } = require('../helpers')
2+
3+
describe('enterprise release notes', () => {
4+
jest.setTimeout(60 * 1000)
5+
6+
beforeAll(async () => {
7+
// The first page load takes a long time so let's get it out of the way in
8+
// advance to call out that problem specifically rather than misleadingly
9+
// attributing it to the first test
10+
await get('/')
11+
})
12+
13+
it('redirects to the release notes on enterprise.github.com if none are present for this version here', async () => {
14+
const res = await get('/en/enterprise-server@2.21/admin/release-notes')
15+
expect(res.statusCode).toBe(302)
16+
expect(res.headers.location).toBe('https://enterprise.github.com/releases/2.21.0/notes')
17+
})
18+
19+
// We can't write this test until we have real release notes
20+
it.todo('renders the release-notes layout if this version\'s release notes are in this repo')
21+
})

0 commit comments

Comments
 (0)