repo sync
This commit is contained in:
72
.github/workflows/confirm-internal-staff-work-in-docs.yml
vendored
Normal file
72
.github/workflows/confirm-internal-staff-work-in-docs.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
name: Confirm internal staff meant to post in public
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- reopened
|
||||||
|
- transferred
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- reopened
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-team-membership:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
continue-on-error: true
|
||||||
|
if: github.repository == 'github/docs'
|
||||||
|
steps:
|
||||||
|
- uses: actions/github-script@626af12fe9a53dc2972b48385e7fe7dec79145c9
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.DOCUBOT_FR_PROJECT_BOARD_WORKFLOWS_REPO_ORG_READ_SCOPES }}
|
||||||
|
script: |
|
||||||
|
// Only perform this action with GitHub employees
|
||||||
|
try {
|
||||||
|
await github.teams.getMembershipForUserInOrg({
|
||||||
|
org: 'github',
|
||||||
|
team_slug: 'employees',
|
||||||
|
username: context.payload.sender.login,
|
||||||
|
});
|
||||||
|
} catch(err) {
|
||||||
|
// An error will be thrown if the user is not a GitHub employee
|
||||||
|
// If a user is not a GitHub employee, we should stop here and
|
||||||
|
// Not send a notification
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't perform this action with Docs team members
|
||||||
|
try {
|
||||||
|
await github.teams.getMembershipForUserInOrg({
|
||||||
|
org: 'github',
|
||||||
|
team_slug: 'docs',
|
||||||
|
username: context.payload.sender.login,
|
||||||
|
});
|
||||||
|
// If the user is a Docs team member, we should stop here and not send
|
||||||
|
// a notification
|
||||||
|
return
|
||||||
|
} catch(err) {
|
||||||
|
// An error will be thrown if the user is not a Docs team member
|
||||||
|
// If a user is not a Docs team member we should continue and send
|
||||||
|
// the notification
|
||||||
|
}
|
||||||
|
|
||||||
|
const issueNo = context.number || context.issue.number
|
||||||
|
|
||||||
|
// Create an issue in our private repo
|
||||||
|
await github.issues.create({
|
||||||
|
owner: 'github',
|
||||||
|
repo: 'docs-internal',
|
||||||
|
title: `@${context.payload.sender.login} confirm that \#${issueNo} should be in the public github/docs repo`,
|
||||||
|
body: `@${context.payload.sender.login} opened https://github.com/github/docs/issues/${issueNo} publicly in the github/docs repo, instead of the private github/docs-internal repo.\n\n@${context.payload.sender.login}, please confirm that this belongs in the public repo and that no sensitive information was disclosed by commenting below and closing the issue.\n\nIf this was not intentional and sensitive information was shared, please delete https://github.com/github/docs/issues/${issueNo} and notify us in the \#docs-open-source channel.\n\nThanks! \n\n/cc @github/docs @github/docs-engineering`
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new Error('A Hubber opened an issue on the public github/docs repo');
|
||||||
|
|
||||||
|
- name: Send Slack notification if a GitHub employee who isn't on the docs team opens an issue in public
|
||||||
|
if: ${{ failure() && github.repository == 'github/docs' }}
|
||||||
|
uses: someimportantcompany/github-actions-slack-message@0b470c14b39da4260ed9e3f9a4f1298a74ccdefd
|
||||||
|
with:
|
||||||
|
channel: ${{ secrets.DOCS_OPEN_SOURCE_SLACK_CHANNEL_ID }}
|
||||||
|
bot-token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
|
||||||
|
text: <@${{github.actor}}> opened https://github.com/github/docs/issues/${{ github.event.number || github.event.issue.number }} publicly on the github/docs repo instead of the private github/docs-internal repo. They have been notified via a new issue in the github/docs-internal repo to confirm this was intentional.
|
||||||
@@ -17,7 +17,7 @@ header:
|
|||||||
still in translation. For the most up-to-date and accurate information,
|
still in translation. For the most up-to-date and accurate information,
|
||||||
please visit our
|
please visit our
|
||||||
<a id="to-english-doc" href="/en">English documentation</a>.
|
<a id="to-english-doc" href="/en">English documentation</a>.
|
||||||
early_access: 👋 This page contains content about an early access feature. Please do not share this URL publicly.
|
early_access: 📣 Please <b>do not share</b> this URL publicly. This page contains content about an early access feature.
|
||||||
search:
|
search:
|
||||||
need_help: Need help?
|
need_help: Need help?
|
||||||
placeholder: Search topics, products...
|
placeholder: Search topics, products...
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<nav class="breadcrumbs f5" aria-label="Breadcrumb">
|
<nav class="breadcrumbs f5" aria-label="Breadcrumb">
|
||||||
{% for breadcrumb in breadcrumbs %}
|
{% for breadcrumb in breadcrumbs %}
|
||||||
{% if page.hidden %}
|
{% if breadcrumb[1].href == '' %}
|
||||||
<span class="d-inline-block">{{breadcrumb[1].title}}</span>
|
<span>{{breadcrumb[1].title}}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a title="{{ breadcrumb[0]}}: {{breadcrumb[1].title}}" href="/{{currentLanguage}}{{breadcrumb[1].href}}" class="d-inline-block {% if breadcrumb[1].href == currentPathWithoutLanguage %}text-gray-light{% endif %}">
|
<a title="{{ breadcrumb[0]}}: {{breadcrumb[1].title}}" href="/{{currentLanguage}}{{breadcrumb[1].href}}" class="d-inline-block {% if breadcrumb[1].href == currentPathWithoutLanguage %}text-gray-light{% endif %}">
|
||||||
{{breadcrumb[1].title}}</a>
|
{{breadcrumb[1].title}}</a>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if early_access_notification_type %}
|
{% if early_access_notification_type %}
|
||||||
<div class="header-notifications text-center f5 bg-blue-1 text-gray-dark py-4 px-6 early_access">
|
<div class="header-notifications text-center f5 bg-red-1 text-gray-dark py-4 px-6 early_access">
|
||||||
{{ early_access_notification }}
|
{{ early_access_notification }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -2,9 +2,12 @@ const path = require('path')
|
|||||||
const { getPathWithoutLanguage } = require('../lib/path-utils')
|
const { getPathWithoutLanguage } = require('../lib/path-utils')
|
||||||
|
|
||||||
module.exports = async (req, res, next) => {
|
module.exports = async (req, res, next) => {
|
||||||
|
if (!req.context.page) return next()
|
||||||
|
if (req.context.page.hidden) return next()
|
||||||
|
|
||||||
req.context.breadcrumbs = {}
|
req.context.breadcrumbs = {}
|
||||||
|
|
||||||
if (!req.context.page) return next()
|
// Return an empty object on the landing page
|
||||||
if (req.context.page.relativePath === 'index.md') return next()
|
if (req.context.page.relativePath === 'index.md') return next()
|
||||||
|
|
||||||
const rawPath = getPathWithoutLanguage(req.path)
|
const rawPath = getPathWithoutLanguage(req.path)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
const { uniq } = require('lodash')
|
||||||
|
|
||||||
module.exports = function earlyAccessContext (req, res, next) {
|
module.exports = function earlyAccessContext (req, res, next) {
|
||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
return next(404)
|
return next(404)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a list of all hidden pages per version
|
// Get a list of all hidden pages per version
|
||||||
const earlyAccessPageLinks = req.context.pages
|
const earlyAccessPageLinks = uniq(Object.values(req.context.pages)
|
||||||
.filter(page => page.hidden)
|
.filter(page => page.hidden)
|
||||||
// Do not include early access landing page
|
// Do not include early access landing page
|
||||||
.filter(page => page.relativePath !== 'early-access/index.md')
|
.filter(page => page.relativePath !== 'early-access/index.md')
|
||||||
@@ -12,7 +14,7 @@ module.exports = function earlyAccessContext (req, res, next) {
|
|||||||
.map(page => {
|
.map(page => {
|
||||||
return page.permalinks.map(permalink => `- [${permalink.title}](${permalink.href})`)
|
return page.permalinks.map(permalink => `- [${permalink.title}](${permalink.href})`)
|
||||||
})
|
})
|
||||||
.flat()
|
.flat())
|
||||||
// Get links for the current version
|
// Get links for the current version
|
||||||
.filter(link => link.includes(req.context.currentVersion))
|
.filter(link => link.includes(req.context.currentVersion))
|
||||||
.sort()
|
.sort()
|
||||||
|
|||||||
69
middleware/early-access-breadcrumbs.js
Normal file
69
middleware/early-access-breadcrumbs.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const { getPathWithoutLanguage } = require('../lib/path-utils')
|
||||||
|
|
||||||
|
// Early Access content doesn't conform to the same structure as other products, so we
|
||||||
|
// can't derive breadcrumbs from the siteTree in the same way. Hence, this separate middleware.
|
||||||
|
module.exports = async (req, res, next) => {
|
||||||
|
if (!req.context.page) return next()
|
||||||
|
if (!req.context.page.hidden) return next()
|
||||||
|
|
||||||
|
req.context.breadcrumbs = {}
|
||||||
|
|
||||||
|
const earlyAccessProduct = req.context.siteTree[req.language][req.context.currentVersion].products[req.context.currentProduct]
|
||||||
|
|
||||||
|
req.context.breadcrumbs.earlyAccessProduct = {
|
||||||
|
href: '', // no link for the EA product breadcrumb
|
||||||
|
title: earlyAccessProduct.title
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawPath = getPathWithoutLanguage(req.path)
|
||||||
|
const pathParts = rawPath.split('/')
|
||||||
|
|
||||||
|
// drop first '/'
|
||||||
|
pathParts.shift()
|
||||||
|
|
||||||
|
// drop the version and early-accesss segments so pathParts now starts with /product
|
||||||
|
pathParts.shift()
|
||||||
|
pathParts.shift()
|
||||||
|
|
||||||
|
// Early Access products do not require an index.md, so look for a matching public product
|
||||||
|
const product = req.context.siteTree[req.language][req.context.currentVersion].products[pathParts[0]]
|
||||||
|
|
||||||
|
if (!product) return next()
|
||||||
|
|
||||||
|
req.context.breadcrumbs.product = {
|
||||||
|
href: '', // no link for the EA product breadcrumbs
|
||||||
|
title: product.title
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pathParts[1]) return next()
|
||||||
|
|
||||||
|
// get Early Access category path
|
||||||
|
// e.g., `enforcing-best-practices-with-github-policies` in /free-pro-team@latest/early-access/github/enforcing-best-practices-with-github-policies
|
||||||
|
const categoryPath = path.posix.join('/', req.context.currentVersion, 'early-access', pathParts[0], pathParts[1])
|
||||||
|
const category = req.context.pages[path.posix.join('/en', categoryPath)]
|
||||||
|
|
||||||
|
if (!category) return next()
|
||||||
|
|
||||||
|
req.context.breadcrumbs.category = {
|
||||||
|
href: categoryPath, // do include a link for EA category breadcrumbs
|
||||||
|
title: category.shortTitle || category.title
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pathParts[2]) return next()
|
||||||
|
|
||||||
|
// for Early Access purposes, we don't need to differentiate between map topics and articles breadcrumbs
|
||||||
|
const mapTopicOrArticlePath = path.posix.join(categoryPath, pathParts[2])
|
||||||
|
const mapTopicOrArticle = req.context.pages[path.posix.join('/en', mapTopicOrArticlePath)]
|
||||||
|
|
||||||
|
if (!mapTopicOrArticle) return next()
|
||||||
|
|
||||||
|
const mapTopicOrArticleTitle = await mapTopicOrArticle.renderProp('shortTitle', req.context, { textOnly: true, encodeEntities: true })
|
||||||
|
|
||||||
|
req.context.breadcrumbs.article = {
|
||||||
|
href: mapTopicOrArticlePath, // do include a link for EA map topic/article breadcrumbs
|
||||||
|
title: mapTopicOrArticleTitle
|
||||||
|
}
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
||||||
@@ -73,6 +73,7 @@ module.exports = function (app) {
|
|||||||
app.use(require('./contextualizers/rest'))
|
app.use(require('./contextualizers/rest'))
|
||||||
app.use(require('./contextualizers/webhooks'))
|
app.use(require('./contextualizers/webhooks'))
|
||||||
app.use(require('./breadcrumbs'))
|
app.use(require('./breadcrumbs'))
|
||||||
|
app.use(require('./early-access-breadcrumbs'))
|
||||||
app.use(require('./dev-toc'))
|
app.use(require('./dev-toc'))
|
||||||
app.use(require('./featured-links'))
|
app.use(require('./featured-links'))
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@
|
|||||||
/* Breadcrumbs
|
/* Breadcrumbs
|
||||||
------------------------------------------------------------------------------*/
|
------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
.breadcrumbs a:not(:last-child)::after{
|
.breadcrumbs a:not(:last-child)::after, .breadcrumbs span:not(:last-child)::after {
|
||||||
content: '/';
|
content: '/';
|
||||||
color: $gray-400;
|
color: $gray-400;
|
||||||
padding-right: $spacer-1;
|
padding-right: $spacer-1;
|
||||||
|
|||||||
9
tests/helpers/conditional-runs.js
Normal file
9
tests/helpers/conditional-runs.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const runningActionsOnInternalRepo = process.env.GITHUB_ACTIONS === 'true' && process.env.GITHUB_REPOSITORY === 'github/docs-internal'
|
||||||
|
|
||||||
|
const testViaActionsOnly = runningActionsOnInternalRepo ? test : test.skip
|
||||||
|
const describeViaActionsOnly = runningActionsOnInternalRepo ? describe : describe.skip
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
testViaActionsOnly,
|
||||||
|
describeViaActionsOnly
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
const { getDOM, getJSON } = require('../helpers/supertest')
|
const { getDOM, getJSON } = require('../helpers/supertest')
|
||||||
const nonEnterpriseDefaultVersion = require('../../lib/non-enterprise-default-version')
|
const nonEnterpriseDefaultVersion = require('../../lib/non-enterprise-default-version')
|
||||||
|
|
||||||
|
const describeInternalOnly = process.env.GITHUB_REPOSITORY === 'github/docs-internal' ? describe : describe.skip
|
||||||
|
|
||||||
describe('breadcrumbs', () => {
|
describe('breadcrumbs', () => {
|
||||||
jest.setTimeout(300 * 1000)
|
jest.setTimeout(300 * 1000)
|
||||||
|
|
||||||
@@ -60,6 +62,27 @@ describe('breadcrumbs', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describeInternalOnly('early access rendering', () => {
|
||||||
|
test('top-level product pages have breadcrumbs', async () => {
|
||||||
|
const $ = await getDOM('/early-access/github/articles/using-gist-playground')
|
||||||
|
expect($('.breadcrumbs')).toHaveLength(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('early access article pages have breadcrumbs with product, category, and article', async () => {
|
||||||
|
const $ = await getDOM('/early-access/github/enforcing-best-practices-with-github-policies/about-github-policies')
|
||||||
|
const $breadcrumbSpans = $('.breadcrumbs span')
|
||||||
|
const $breadcrumbLinks = $('.breadcrumbs a')
|
||||||
|
|
||||||
|
expect($breadcrumbSpans).toHaveLength(2)
|
||||||
|
expect($breadcrumbLinks).toHaveLength(2)
|
||||||
|
expect($breadcrumbSpans.eq(0).text()).toBe('Early Access documentation')
|
||||||
|
expect($breadcrumbSpans.eq(1).text()).toBe('GitHub.com')
|
||||||
|
expect($breadcrumbLinks.eq(0).attr('title')).toBe('category: Enforcing best practices with GitHub Policies')
|
||||||
|
expect($breadcrumbLinks.eq(1).attr('title')).toBe('article: About GitHub Policies')
|
||||||
|
expect($breadcrumbLinks.eq(1).hasClass('text-gray-light')).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('context.breadcrumbs object', () => {
|
describe('context.breadcrumbs object', () => {
|
||||||
test('works on product index pages', async () => {
|
test('works on product index pages', async () => {
|
||||||
const breadcrumbs = await getJSON('/en/github?json=breadcrumbs')
|
const breadcrumbs = await getJSON('/en/github?json=breadcrumbs')
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const lodash = require('lodash')
|
const lodash = require('lodash')
|
||||||
const enterpriseServerReleases = require('../../lib/enterprise-server-releases')
|
const enterpriseServerReleases = require('../../lib/enterprise-server-releases')
|
||||||
const { get, getDOM, head } = require('../helpers/supertest')
|
const { get, getDOM, head } = require('../helpers/supertest')
|
||||||
|
const { describeViaActionsOnly } = require('../helpers/conditional-runs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const nonEnterpriseDefaultVersion = require('../../lib/non-enterprise-default-version')
|
const nonEnterpriseDefaultVersion = require('../../lib/non-enterprise-default-version')
|
||||||
const { loadPages } = require('../../lib/pages')
|
const { loadPages } = require('../../lib/pages')
|
||||||
@@ -356,7 +357,7 @@ describe('server', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.skip('Early Access articles', () => {
|
describeViaActionsOnly('Early Access articles', () => {
|
||||||
let hiddenPageHrefs, hiddenPages
|
let hiddenPageHrefs, hiddenPages
|
||||||
|
|
||||||
beforeAll(async (done) => {
|
beforeAll(async (done) => {
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
const fs = require('fs').promises
|
const fs = require('fs').promises
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
const { testViaActionsOnly } = require('../helpers/conditional-runs')
|
||||||
const { GITHUB_ACTIONS, GITHUB_REPOSITORY } = process.env
|
|
||||||
const runningActionsOnInternalRepo = GITHUB_ACTIONS === 'true' && GITHUB_REPOSITORY === 'github/docs-internal'
|
|
||||||
const testViaActionsOnly = runningActionsOnInternalRepo ? test : test.skip
|
|
||||||
|
|
||||||
describe('cloning early-access', () => {
|
describe('cloning early-access', () => {
|
||||||
testViaActionsOnly('the content directory exists', async () => {
|
testViaActionsOnly('the content directory exists', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user