Merge branch 'main' into sophie-6156-rename
@@ -13,7 +13,7 @@ module.exports = {
|
||||
babelOptions: { configFile: './.babelrc' },
|
||||
sourceType: 'module',
|
||||
},
|
||||
ignorePatterns: ['tmp/*', '!/.*', '/.next/', 'script/bookmarklets/*'],
|
||||
ignorePatterns: ['tmp/*', '!/.*', '/.next/', 'script/bookmarklets/*', 'lib/sigsci.js'],
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': ['error', { packageDir: '.' }],
|
||||
},
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
import * as github from '@actions/github'
|
||||
import { setOutput } from '@actions/core'
|
||||
|
||||
import { getContents } from '../../script/helpers/git-utils.js'
|
||||
import parse from '../../lib/read-frontmatter.js'
|
||||
import getApplicableVersions from '../../lib/get-applicable-versions.js'
|
||||
import nonEnterpriseDefaultVersion from '../../lib/non-enterprise-default-version.js'
|
||||
import { allVersionShortnames } from '../../lib/all-versions.js'
|
||||
|
||||
const { GITHUB_TOKEN, APP_URL } = process.env
|
||||
const context = github.context
|
||||
|
||||
@@ -14,13 +20,14 @@ if (!APP_URL) {
|
||||
throw new Error(`APP_URL environment variable not set`)
|
||||
}
|
||||
|
||||
const PROD_URL = 'https://docs.github.com'
|
||||
const octokit = github.getOctokit(GITHUB_TOKEN)
|
||||
|
||||
const response = await octokit.rest.repos.compareCommits({
|
||||
// get the list of file changes from the PR
|
||||
const response = await octokit.rest.repos.compareCommitsWithBasehead({
|
||||
owner: context.repo.owner,
|
||||
repo: context.payload.repository.name,
|
||||
base: context.payload.pull_request.base.sha,
|
||||
head: context.payload.pull_request.head.sha,
|
||||
basehead: `${context.payload.pull_request.base.sha}...${context.payload.pull_request.head.sha}`,
|
||||
})
|
||||
|
||||
const { files } = response.data
|
||||
@@ -36,16 +43,74 @@ for (const file of articleFiles) {
|
||||
const sourceUrl = file.blob_url
|
||||
const fileName = file.filename.slice(pathPrefix.length)
|
||||
const fileUrl = fileName.slice(0, fileName.lastIndexOf('.'))
|
||||
const previewLink = `${APP_URL}/${fileUrl}`
|
||||
const productionLink = `https://docs.github.com/${fileUrl}`
|
||||
let markdownLine = ''
|
||||
|
||||
if (file.status === 'modified') {
|
||||
markdownLine = `| [content/${fileName}](${sourceUrl}) | [Modified](${previewLink}) | [Original](${productionLink}) | |\n`
|
||||
} else if (file.status === 'added') {
|
||||
markdownLine = `| New file: [content/${fileName}](${sourceUrl}) | [Modified](${previewLink}) | | |\n`
|
||||
// get the file contents and decode them
|
||||
// this script is called from the main branch, so we need the API call to get the contents from the branch, instead
|
||||
const fileContents = await getContents(
|
||||
context.repo.owner,
|
||||
context.payload.repository.name,
|
||||
context.payload.pull_request.head.sha,
|
||||
file.filename
|
||||
)
|
||||
|
||||
// parse the frontmatter
|
||||
const { data } = parse(fileContents)
|
||||
|
||||
let contentCell = ''
|
||||
let previewCell = ''
|
||||
let prodCell = ''
|
||||
|
||||
if (file.status === 'added') contentCell = `New file: `
|
||||
contentCell += `[\`${fileName}\`](${sourceUrl})`
|
||||
|
||||
try {
|
||||
// the try/catch is needed because getApplicableVersions() returns either [] or throws an error when it can't parse the versions frontmatter
|
||||
// try/catch can be removed if docs-engineering#1821 is resolved
|
||||
// i.e. for feature based versioning, like ghae: 'issue-6337'
|
||||
const fileVersions = getApplicableVersions(data.versions)
|
||||
|
||||
for (const plan in allVersionShortnames) {
|
||||
// plan is the shortName (i.e., fpt)
|
||||
// allVersionShortNames[plan] is the planName (i.e., free-pro-team)
|
||||
|
||||
// walk by the plan names since we generate links differently for most plans
|
||||
const versions = fileVersions.filter((fileVersion) =>
|
||||
fileVersion.includes(allVersionShortnames[plan])
|
||||
)
|
||||
|
||||
if (versions.length === 1) {
|
||||
// for fpt, ghec, and ghae
|
||||
|
||||
if (versions.toString() === nonEnterpriseDefaultVersion) {
|
||||
// omit version from fpt url
|
||||
|
||||
previewCell += `[${plan}](${APP_URL}/${fileUrl})<br>`
|
||||
prodCell += `[${plan}](${PROD_URL}/${fileUrl})<br>`
|
||||
} else {
|
||||
// for non-versioned releases (ghae, ghec) use full url
|
||||
|
||||
previewCell += `[${plan}](${APP_URL}/${versions}/${fileUrl})<br>`
|
||||
prodCell += `[${plan}](${PROD_URL}/${versions}/${fileUrl})<br>`
|
||||
}
|
||||
markdownTable += markdownLine
|
||||
}
|
||||
} else if (versions.length) {
|
||||
// for ghes releases, link each version
|
||||
|
||||
previewCell += `${plan}@ `
|
||||
prodCell += `${plan}@ `
|
||||
|
||||
versions.forEach((version) => {
|
||||
previewCell += `[${version.split('@')[1]}](${APP_URL}/${version}/${fileUrl}) `
|
||||
prodCell += `[${version.split('@')[1]}](${PROD_URL}/${version}/${fileUrl}) `
|
||||
})
|
||||
previewCell += '<br>'
|
||||
prodCell += '<br>'
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`Version information for ${file.filename} couldn't be determined from its frontmatter.`
|
||||
)
|
||||
}
|
||||
markdownTable += `| ${contentCell} | ${previewCell} | ${prodCell} | |\n`
|
||||
}
|
||||
setOutput('changesTable', markdownTable)
|
||||
|
||||
@@ -110,20 +110,20 @@ This file should be automatically updated, but you can also run `script/update-e
|
||||
|
||||
### 🚢 🛳️ 🚢 Shipping the release branch
|
||||
|
||||
- [ ] The megabranch creator should push the search index LFS objects for the public `github/docs` repo. The LFS objects were already pushed for the internal repo after the `sync-english-index-for-<PLAN@RELEASE>` was added to the megabranch. To push the LFS objects to the public repo:
|
||||
1. First navigate to the [sync search indices workflow](https://github.com/github/docs-internal/actions/workflows/sync-search-indices.yml).
|
||||
2. Then, to run the workflow with parameters, click on `Run workflow` button.
|
||||
3. A modal will pop up where you will set the following inputs:
|
||||
- Branch: The new `ghes-<RELEASE>-megabranch` version megabranch you're working on
|
||||
- Version: `enterprise-server@<RELEASE>`
|
||||
- Language: `en`
|
||||
4. Run the job. The workflow job may fail on the first run—so retry the failed job if needed.
|
||||
- [ ] Remove `[DO NOT MERGE]` and other meta information from the PR title 😜.
|
||||
- [ ] The `github/docs-internal` repo is frozen, and the `Repo Freeze Check / Prevent merging during deployment freezes (pull_request_target)` test is expected to fail.
|
||||
|
||||
Use admin permissions to ship the release branch with this failure. Make sure that the merge's commit title does not include anything like `[DO NOT MERGE]`, and remove all the branch's commit details from the merge's commit message except for the co-author list.
|
||||
- [ ] Do any required smoke tests listed in the opening post in the megabranch PR. You can monitor and check when the production deploy completed by viewing the [`docs-internal` deployments page](https://github.com/github/docs-internal/deployments).
|
||||
- [ ] Once smoke tests have passed, you can [unfreeze the repos](https://github.com/github/docs-content/blob/main/docs-content-docs/docs-content-workflows/freezing.md) and post an announcement in Slack.
|
||||
- [ ] After unfreezing, the megabranch creator should push the search index LFS objects for the public `github/docs` repo. The LFS objects were already pushed for the internal repo after the `sync-english-index-for-<PLAN@RELEASE>` was added to the megabranch. To push the LFS objects to the public repo:
|
||||
1. First navigate to the [sync search indices workflow](https://github.com/github/docs-internal/actions/workflows/sync-search-indices.yml).
|
||||
2. Then, to run the workflow with parameters, click on `Run workflow` button.
|
||||
3. A modal will pop up where you will set the following inputs:
|
||||
- Branch: The new version megabranch you're working on
|
||||
- Version: `enterprise-server@<RELEASE>`
|
||||
- Language: `en`
|
||||
4. Run the job. The workflow job may fail on the first run—so retry the failed job if needed.
|
||||
- [ ] After unfreezing, alert the Ecosystem-API team in #ecosystem-api the docs freeze is finished/thawed and the release has shipped.
|
||||
- [ ] You (or they) can now remove your blocking review on the auto-generated "Update OpenAPI Descriptions" PR in public REST API description (the `rest-api-descriptions` repo). (although it's likely newer PRs have been created since yours with the blocking review, in which case the Ecosystem-API team will close your PR and perform the next step on the most recent PR).
|
||||
- [ ] The Ecosystem-API team will merge the latest auto-generated "Update OpenAPI Descriptions" PR (which will contain the OpenAPI schema config that changed `published` to `true` for the release).
|
||||
|
||||
2
.github/review-template.md
vendored
@@ -1,7 +1,7 @@
|
||||
## Author self-review
|
||||
|
||||
- [ ] The changes in this PR meet the user experience and goals outlined in the content design plan.
|
||||
- [ ] I've compared my PR's source changes to staging and reviewed for versioning issues, redirects, the [style guide](https://github.com/github/docs/blob/main/contributing/content-style-guide.md), [content model](https://github.com/github/docs-content-strategy/blob/main/content-design/models.md), or [localization checklist](https://github.com/github/docs/blob/main/contributing/localization-checklist.md) rendering problems, typos, and wonky screenshots.
|
||||
- [ ] I've compared my PR's source changes to staging and reviewed for versioning issues, redirects, the [style guide](https://github.com/github/docs/blob/main/contributing/content-style-guide.md), [content model](https://github.com/github/docs/blob/main/contributing/content-model.md), or [localization checklist](https://github.com/github/docs/blob/main/contributing/localization-checklist.md) rendering problems, typos, and wonky screenshots.
|
||||
- [ ] I've worked through build failures and tests are passing.
|
||||
- [ ] For REST API content, I've verified that endpoints, parameters, and responses are correct and work as expected and provided curl samples below.
|
||||
|
||||
|
||||
@@ -187,6 +187,8 @@ jobs:
|
||||
cache-from: type=registry,ref=${{ secrets.NONPROD_REGISTRY_SERVER }}/${{ github.repository }}:main-preview
|
||||
# `main-docker-cache.yml` handles updating the remote cache so we don't pollute it with PR specific code
|
||||
cache-to: ''
|
||||
build-args: |
|
||||
BUILD_SHA=${{ github.sha }}
|
||||
|
||||
# Succeed despite any non-zero exit code (e.g. if there is no deployment to cancel)
|
||||
- name: 'Cancel any existing deployments for this PR'
|
||||
|
||||
@@ -85,6 +85,8 @@ jobs:
|
||||
tags: ${{ env.DOCKER_IMAGE }}, ${{ env.DOCKER_IMAGE_CACHE_REF }}
|
||||
cache-from: type=registry,ref=${{ env.DOCKER_IMAGE_CACHE_REF }}
|
||||
cache-to: type=registry,mode=max,ref=${{ env.DOCKER_IMAGE_CACHE_REF }}
|
||||
build-args: |
|
||||
BUILD_SHA=${{ github.sha }}
|
||||
|
||||
- name: 'Update docker-compose.prod.yaml template file'
|
||||
run: |
|
||||
|
||||
4
.github/workflows/needs-sme-workflow.yml
vendored
@@ -16,7 +16,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
add-issue-comment:
|
||||
if: ${{ github.repository == 'github/docs' && (github.event.label.name == 'needs SME' && github.event_name == 'issues' || github.event_name == 'pull_request_target') }}
|
||||
if: ${{ github.repository == 'github/docs' && (github.event.label.name == 'needs SME' && github.event_name == 'issues') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: peter-evans/create-or-update-comment@a35cf36e5301d70b76f316e867e7788a55a31dae
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
Thanks for opening an issue! We've triaged this issue for technical review by a subject matter expert :eyes:
|
||||
|
||||
add-pr-comment:
|
||||
if: ${{ github.repository == 'github/docs' && github.event.label.name == 'needs SME' }}
|
||||
if: ${{ github.repository == 'github/docs' && (github.event.label.name == 'needs SME' && github.event_name == 'pull_request_target') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: peter-evans/create-or-update-comment@a35cf36e5301d70b76f316e867e7788a55a31dae
|
||||
|
||||
@@ -78,6 +78,11 @@ ENV PORT 4000
|
||||
|
||||
ENV ENABLED_LANGUAGES "en"
|
||||
|
||||
# This makes it possible to set `--build-arg BUILD_SHA=abc123`
|
||||
# and it then becomes available as an environment variable in the docker run.
|
||||
ARG BUILD_SHA
|
||||
ENV BUILD_SHA=$BUILD_SHA
|
||||
|
||||
# Copy only what's needed to run the server
|
||||
COPY --chown=node:node package.json ./
|
||||
COPY --chown=node:node assets ./assets
|
||||
|
||||
BIN
assets/images/actions-job-summary-simple-example.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
assets/images/enterprise/3.5/repository/pr-merge-squash.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 23 KiB |
BIN
assets/images/enterprise/maintenance/ip-exception-enabled.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/images/enterprise/maintenance/ip-exception-save.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 852 KiB |
|
After Width: | Height: | Size: 48 KiB |
BIN
assets/images/help/classroom/org-view-codespaces-eligibility.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 64 KiB |
BIN
assets/images/help/classroom/student-codespaces-readme-link.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
assets/images/help/classroom/student-launch-new-codespace.png
Normal file
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 43 KiB |
BIN
assets/images/help/codespaces/prebuilds-disable.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
BIN
assets/images/help/enterprises/license-sync-now-ghes.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
assets/images/help/enterprises/settings-for-ghec-org.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 31 KiB |
BIN
assets/images/help/repository/restrict-branch-create.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 19 KiB |
BIN
assets/images/help/server-statistics/enterprise-settings.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
assets/images/help/server-statistics/export-button.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 13 KiB |
@@ -9,7 +9,8 @@ import { useTranslation } from 'components/hooks/useTranslation'
|
||||
|
||||
export const ProductGuides = () => {
|
||||
const { title, learningTracks, includeGuides } = useProductGuidesContext()
|
||||
const { t } = useTranslation('sub_landing')
|
||||
const { t } = useTranslation('product_guides')
|
||||
|
||||
return (
|
||||
<DefaultLayout>
|
||||
<LandingSection className="pt-3">
|
||||
|
||||
@@ -83,9 +83,9 @@ export function getGHExample(operation: Operation, codeSample: CodeSample) {
|
||||
requestBodyParams = Object.keys(codeSample.request.bodyParameters)
|
||||
.map((key) => {
|
||||
if (typeof codeSample.request.bodyParameters[key] === 'string') {
|
||||
return `-f ${key}='${codeSample.request.bodyParameters[key]}'`
|
||||
return `-f ${key}='${codeSample.request.bodyParameters[key]}'\n`
|
||||
} else {
|
||||
return `-F ${key}=${codeSample.request.bodyParameters[key]}`
|
||||
return `-F ${key}=${codeSample.request.bodyParameters[key]}\n`
|
||||
}
|
||||
})
|
||||
.join(' ')
|
||||
@@ -97,7 +97,9 @@ export function getGHExample(operation: Operation, codeSample: CodeSample) {
|
||||
requestPath,
|
||||
requestBodyParams,
|
||||
].filter(Boolean)
|
||||
return `gh api \\\n ${args.join(' \\\n ')}`
|
||||
return `# GitHub CLI api\n# https://cli.github.com/manual/gh_api\n\ngh api \\\n ${args.join(
|
||||
' \\\n '
|
||||
)}`
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -135,8 +137,14 @@ export function getJSExample(operation: Operation, codeSample: CodeSample) {
|
||||
queryParameters = `{?${queryParms.join(',')}}`
|
||||
}
|
||||
}
|
||||
const comment = `// Octokit.js\n// https://github.com/octokit/core.js#readme\n`
|
||||
const require = `const octokit = new Octokit(${stringify(
|
||||
{ auth: 'personal-access-token123' },
|
||||
null,
|
||||
2
|
||||
)})\n\n`
|
||||
|
||||
return `await octokit.request('${operation.verb.toUpperCase()} ${
|
||||
return `${comment}${require}await octokit.request('${operation.verb.toUpperCase()} ${
|
||||
operation.requestPath
|
||||
}${queryParameters}', ${stringify(parameters, null, 2)})`
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import { Fragment } from 'react'
|
||||
import type { BodyParameter } from './types'
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
import { ChildBodyParametersRows } from './ChildBodyParametersRows'
|
||||
|
||||
type Props = {
|
||||
slug: string
|
||||
bodyParameters: BodyParameter[]
|
||||
}
|
||||
|
||||
export function BodyParameterRows({ slug, bodyParameters }: Props) {
|
||||
const { t } = useTranslation('products')
|
||||
|
||||
return (
|
||||
<>
|
||||
{bodyParameters.map((bodyParam) => {
|
||||
return (
|
||||
<Fragment key={bodyParam.name + bodyParam.description}>
|
||||
<tr>
|
||||
<td>
|
||||
<code>{bodyParam.name}</code>
|
||||
</td>
|
||||
<td>{bodyParam.type}</td>
|
||||
<td>{bodyParam.in}</td>
|
||||
<td>
|
||||
<div dangerouslySetInnerHTML={{ __html: bodyParam.description }} />
|
||||
{bodyParam.default && (
|
||||
<p>
|
||||
{t('rest.reference.default')}: <code>{bodyParam.default}</code>
|
||||
</p>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
{bodyParam.childParamsGroups && bodyParam.childParamsGroups.length > 0 && (
|
||||
<ChildBodyParametersRows
|
||||
slug={slug}
|
||||
childParamsGroups={bodyParam.childParamsGroups}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
import { ParameterRow } from './ParameterRow'
|
||||
import type { ChildParamsGroup } from './types'
|
||||
|
||||
type Props = {
|
||||
@@ -12,14 +13,9 @@ export function ChildBodyParametersRows({ slug, childParamsGroups }: Props) {
|
||||
return (
|
||||
<tr className="border-none">
|
||||
<td colSpan={4} className="has-nested-table">
|
||||
{childParamsGroups?.map((childParamGroup) => {
|
||||
return (
|
||||
{childParamsGroups?.map((childParamGroup) => (
|
||||
<details key={childParamGroup.id}>
|
||||
<summary
|
||||
role="button"
|
||||
aria-expanded="false"
|
||||
className="keyboard-focus color-fg-muted"
|
||||
>
|
||||
<summary role="button" aria-expanded="false" className="keyboard-focus color-fg-muted">
|
||||
<span className="d-inline-block mb-3" id={`${slug}-${childParamGroup.id}`}>
|
||||
Properties of the
|
||||
<code>{childParamGroup.parentName}</code>
|
||||
@@ -30,32 +26,33 @@ export function ChildBodyParametersRows({ slug, childParamsGroups }: Props) {
|
||||
id={`${childParamGroup.parentName}-object`}
|
||||
className="ml-4 mb-4 mt-2 color-bg-subtle"
|
||||
>
|
||||
<thead>
|
||||
<thead className="visually-hidden">
|
||||
<tr>
|
||||
<th>
|
||||
{t('rest.reference.name')} ({t('rest.reference.type')})
|
||||
{`${t('rest.reference.name')}, ${t('rest.reference.type')}, ${t(
|
||||
'rest.reference.description'
|
||||
)}`}
|
||||
</th>
|
||||
<th>{t('rest.reference.description')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{childParamGroup.params.map((childParam) => {
|
||||
return (
|
||||
<tr key={`${childParam.name}-${childParam.description}`}>
|
||||
<td className="color-bg-subtle">
|
||||
<code>{childParam.name}</code> ({childParam.type})
|
||||
</td>
|
||||
<td className="color-bg-subtle">
|
||||
<div dangerouslySetInnerHTML={{ __html: childParam.description }} />
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
{childParamGroup.params.map((childParam, index) => (
|
||||
<ParameterRow
|
||||
name={childParam.name}
|
||||
description={childParam.description}
|
||||
type={childParam.type}
|
||||
isRequired={childParam.isRequired}
|
||||
defaultValue={childParam.default}
|
||||
enumValues={childParam.enum}
|
||||
slug={slug}
|
||||
isChild={true}
|
||||
key={`${index}-${childParam}`}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</details>
|
||||
)
|
||||
})}
|
||||
))}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
.codeBlock {
|
||||
max-height: 32rem;
|
||||
overflow: auto;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.45;
|
||||
background-color: var(--color-canvas-subtle);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
|
||||
code {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import cx from 'classnames'
|
||||
import { CheckIcon, CopyIcon } from '@primer/octicons-react'
|
||||
import { Tooltip } from '@primer/react'
|
||||
import useClipboard from 'components/hooks/useClipboard'
|
||||
import styles from './CodeBlock.module.scss'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
type Props = {
|
||||
verb?: string
|
||||
headingLang?: ReactNode | string
|
||||
codeBlock: string
|
||||
highlight?: string
|
||||
}
|
||||
|
||||
export function CodeBlock({ verb, headingLang, codeBlock, highlight }: Props) {
|
||||
const [isCopied, setCopied] = useClipboard(codeBlock, {
|
||||
successDuration: 1400,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={headingLang ? 'code-extra' : undefined}>
|
||||
{/* Only Code samples should have a copy icon
|
||||
If there's a headingLang it's a code sample */}
|
||||
{headingLang && (
|
||||
<header className="d-flex flex-justify-between flex-items-center p-2 text-small rounded-top-1 border">
|
||||
{headingLang}
|
||||
<Tooltip direction="w" aria-label={isCopied ? 'Copied!' : 'Copy to clipboard'}>
|
||||
<button className="js-btn-copy btn-octicon" onClick={() => setCopied()}>
|
||||
{isCopied ? <CheckIcon /> : <CopyIcon />}
|
||||
</button>
|
||||
</Tooltip>
|
||||
</header>
|
||||
)}
|
||||
<div className={cx(styles.codeBlock, 'rounded-1 border')} data-highlight={highlight}>
|
||||
<code>
|
||||
{verb && (
|
||||
<>
|
||||
<span className="color-bg-accent-emphasis color-fg-on-emphasis rounded-1 text-uppercase p-1">
|
||||
{verb}
|
||||
</span>
|
||||
<> </>
|
||||
</>
|
||||
)}
|
||||
{codeBlock}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
90
components/rest/ParameterRow.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
import { ChildBodyParametersRows } from './ChildBodyParametersRows'
|
||||
import type { ChildParamsGroup } from './types'
|
||||
|
||||
type Props = {
|
||||
name: string
|
||||
type: string | string[]
|
||||
description: string
|
||||
isRequired?: boolean
|
||||
defaultValue?: string
|
||||
enumValues?: string[]
|
||||
slug: string
|
||||
childParamsGroups?: ChildParamsGroup[] | null
|
||||
numPreviews?: number
|
||||
isChild?: boolean
|
||||
}
|
||||
|
||||
export function ParameterRow({
|
||||
name,
|
||||
type,
|
||||
description,
|
||||
isRequired,
|
||||
defaultValue,
|
||||
enumValues,
|
||||
slug,
|
||||
childParamsGroups = null,
|
||||
numPreviews = 0,
|
||||
isChild = false,
|
||||
}: Props) {
|
||||
const { t } = useTranslation('products')
|
||||
|
||||
return (
|
||||
<>
|
||||
<tr className={`${isChild ? 'color-bg-subtle' : ''}`}>
|
||||
<td className={`${isChild ? 'pl-2' : ''}`}>
|
||||
<div>
|
||||
<code className={`text-bold ${isChild ? 'f6' : 'f5'}`}>{name}</code>
|
||||
<span className="color-fg-muted pl-2 f5">
|
||||
{Array.isArray(type) ? type.join(' or ') : type}
|
||||
</span>
|
||||
{isRequired ? (
|
||||
<span className={`color-fg-attention f5 ${isChild ? 'pl-3' : 'float-right'}`}>
|
||||
{t('rest.reference.required')}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="pl-1 pt-2 color-fg-muted f5">
|
||||
<div dangerouslySetInnerHTML={{ __html: description }} />
|
||||
{numPreviews > 0 && (
|
||||
<a href={`#${slug}-preview-notices`} className="d-inline">
|
||||
{numPreviews > 1
|
||||
? ` ${t('rest.reference.see_preview_notices')}`
|
||||
: ` ${t('rest.reference.see_preview_notice')}`}
|
||||
</a>
|
||||
)}
|
||||
<div className="pt-2">
|
||||
{defaultValue && (
|
||||
<p>
|
||||
<span>{t('rest.reference.default')}: </span>
|
||||
<code>{defaultValue.toString()}</code>
|
||||
</p>
|
||||
)}
|
||||
{enumValues && (
|
||||
<p>
|
||||
<span>{t('rest.reference.enum_description_title')}: </span>
|
||||
|
||||
{enumValues.map((item, index) => {
|
||||
return index !== enumValues.length - 1 ? (
|
||||
<span key={item + index}>
|
||||
<code>{item}</code>,{' '}
|
||||
</span>
|
||||
) : (
|
||||
<span key={item + index}>
|
||||
<code>{item}</code>
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{childParamsGroups && childParamsGroups.length > 0 && (
|
||||
<ChildBodyParametersRows slug={slug} childParamsGroups={childParamsGroups} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { Parameter } from './types'
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
|
||||
type Props = {
|
||||
parameters: Parameter[]
|
||||
}
|
||||
|
||||
export function ParameterRows({ parameters }: Props) {
|
||||
const { t } = useTranslation('products')
|
||||
|
||||
return (
|
||||
<>
|
||||
{parameters.map((param) => (
|
||||
<tr key={`${param.name}-${param.descriptionHTML}`}>
|
||||
<td>
|
||||
<code>{param.name}</code>
|
||||
</td>
|
||||
<td>{param.schema.type}</td>
|
||||
<td>{param.in}</td>
|
||||
<td>
|
||||
{param.descriptionHTML && (
|
||||
<div dangerouslySetInnerHTML={{ __html: param.descriptionHTML }} />
|
||||
)}
|
||||
{param.schema.default && (
|
||||
<p>
|
||||
{t('rest.reference.default')}: <code>{param.schema.default}</code>
|
||||
</p>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
|
||||
type Props = {
|
||||
slug: string
|
||||
numPreviews: number
|
||||
}
|
||||
|
||||
export function PreviewsRow({ slug, numPreviews }: Props) {
|
||||
const { t } = useTranslation('products')
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>
|
||||
<code>accept</code>
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>header</td>
|
||||
<td>
|
||||
<p className="m-0">
|
||||
Setting to
|
||||
<code>application/vnd.github.v3+json</code> is recommended.
|
||||
{numPreviews > 0 && (
|
||||
<a href={`#${slug}-preview-notices`} className="d-inline">
|
||||
{numPreviews > 1
|
||||
? ` ${t('rest.reference.see_preview_notices')}`
|
||||
: ` ${t('rest.reference.see_preview_notice')}`}
|
||||
</a>
|
||||
)}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
31
components/rest/RestCodeSamples.module.scss
Normal file
@@ -0,0 +1,31 @@
|
||||
@import "@primer/css/support/index.scss";
|
||||
|
||||
.codeBlock {
|
||||
overflow: auto;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.45;
|
||||
background-color: var(--color-canvas-subtle);
|
||||
font-size: 90%;
|
||||
|
||||
code {
|
||||
background-color: transparent;
|
||||
padding: 8px 8px 16px;
|
||||
white-space: pre;
|
||||
@include breakpoint(lg) {
|
||||
max-width: 40vw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.method {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.requestCodeBlock {
|
||||
max-height: 30vh;
|
||||
min-height: 70px;
|
||||
}
|
||||
|
||||
.responseCodeBlock {
|
||||
min-height: 70px;
|
||||
}
|
||||
@@ -1,92 +1,333 @@
|
||||
import type { Operation } from './types'
|
||||
import { useState, useEffect, useRef, FormEvent } from 'react'
|
||||
import { FormControl, Select, Tooltip, UnderlineNav } from '@primer/react'
|
||||
import { CheckIcon, CopyIcon } from '@primer/octicons-react'
|
||||
import Cookies from 'js-cookie'
|
||||
import cx from 'classnames'
|
||||
|
||||
import hljs from 'highlight.js/lib/core'
|
||||
import json from 'highlight.js/lib/languages/json'
|
||||
import javascript from 'highlight.js/lib/languages/javascript'
|
||||
import hljsCurl from 'highlightjs-curl'
|
||||
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
import { CodeBlock } from './CodeBlock'
|
||||
import { getShellExample, getGHExample, getJSExample } from '../lib/get-rest-code-samples'
|
||||
import useClipboard from 'components/hooks/useClipboard'
|
||||
import { getShellExample, getGHExample, getJSExample } from 'components/lib/get-rest-code-samples'
|
||||
import styles from './RestCodeSamples.module.scss'
|
||||
import { RestMethod } from './RestMethod'
|
||||
import type { Operation, ExampleT, LanguageOptionT } from './types'
|
||||
|
||||
type Props = {
|
||||
slug: string
|
||||
operation: Operation
|
||||
}
|
||||
|
||||
const GHCLIKEY = 'ghcli'
|
||||
const JSKEY = 'javascript'
|
||||
const CURLKEY = 'curl'
|
||||
|
||||
// Add as needed. It's pretty cheap to add but please don't use
|
||||
// highlight.js import that loads all and everything.
|
||||
hljs.registerLanguage('json', json)
|
||||
hljs.registerLanguage('javascript', javascript)
|
||||
hljs.registerLanguage('curl', hljsCurl)
|
||||
|
||||
const responseSelectOptions = [
|
||||
{ key: 'example', text: 'Example response' },
|
||||
{ key: 'schema', text: 'Response schema' },
|
||||
]
|
||||
|
||||
function getLanguageHighlight(selectedLanguage: string) {
|
||||
return selectedLanguage === JSKEY ? 'javascript' : 'curl'
|
||||
}
|
||||
|
||||
export function RestCodeSamples({ operation, slug }: Props) {
|
||||
const { t } = useTranslation('products')
|
||||
|
||||
const JAVASCRIPT_HEADING = (
|
||||
<span>
|
||||
JavaScript{' '}
|
||||
<a className="text-underline" href="https://github.com/octokit/core.js#readme">
|
||||
@octokit/core.js
|
||||
</a>
|
||||
</span>
|
||||
)
|
||||
// Refs to track the request example, response example
|
||||
// and the first render
|
||||
const requestCodeExample = useRef<HTMLSpanElement>(null)
|
||||
const responseCodeExample = useRef<HTMLSpanElement>(null)
|
||||
const firstRender = useRef(true)
|
||||
const scrollRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const GH_CLI_HEADING = (
|
||||
<span>
|
||||
GitHub CLI{' '}
|
||||
<a className="text-underline" href="https://cli.github.com/manual/gh_api">
|
||||
gh api
|
||||
</a>
|
||||
</span>
|
||||
)
|
||||
|
||||
// Format the example properties into different language examples
|
||||
const languageExamples = operation.codeExamples.map((sample) => {
|
||||
const languageExamples = {
|
||||
// Get format examples for each language
|
||||
const languageExamples = operation.codeExamples.map((sample) => ({
|
||||
description: sample.request.description,
|
||||
curl: getShellExample(operation, sample),
|
||||
javascript: getJSExample(operation, sample),
|
||||
ghcli: getGHExample(operation, sample),
|
||||
response: sample.response,
|
||||
}))
|
||||
|
||||
// Menu options for the language selector
|
||||
const languageSelectOptions: LanguageOptionT[] = [
|
||||
{ key: CURLKEY, text: 'cURL' },
|
||||
{ key: JSKEY, text: 'JavaScript' },
|
||||
]
|
||||
// Not all examples support the GH CLI language option. If any of
|
||||
// the examples don't support it, we don't show GH CLI as an option.
|
||||
if (!languageExamples.some((example) => example.ghcli === undefined)) {
|
||||
languageSelectOptions.push({ key: GHCLIKEY, text: 'GitHub CLI' })
|
||||
}
|
||||
return Object.assign({}, sample, languageExamples)
|
||||
|
||||
// Menu options for the example selector
|
||||
const exampleSelectOptions = languageExamples.map((example, index) => ({
|
||||
text: example.description,
|
||||
// maps to the index of the example in the languageExamples array
|
||||
languageIndex: index,
|
||||
}))
|
||||
|
||||
const [selectedLanguage, setSelectedLanguage] = useState<keyof ExampleT>(
|
||||
languageSelectOptions[0].key
|
||||
)
|
||||
const [selectedExample, setSelectedExample] = useState(exampleSelectOptions[0])
|
||||
const [selectedResponse, setSelectedResponse] = useState(responseSelectOptions[0].key)
|
||||
const [responseMaxHeight, setResponseMaxHeight] = useState(0)
|
||||
|
||||
const isSingleExample = languageExamples.length === 1
|
||||
const displayedExample: ExampleT = languageExamples[selectedExample.languageIndex]
|
||||
|
||||
const handleExampleSelection = (event: FormEvent<HTMLSelectElement>) => {
|
||||
setSelectedExample(exampleSelectOptions[Number(event.currentTarget.value)])
|
||||
}
|
||||
|
||||
const handleResponseSelection = (responseKey: string) => {
|
||||
setSelectedResponse(responseKey)
|
||||
}
|
||||
|
||||
const handleLanguageSelection = (languageKey: keyof ExampleT) => {
|
||||
setSelectedLanguage(languageKey)
|
||||
Cookies.set('codeSampleLanguagePreferred', languageKey, {
|
||||
sameSite: 'strict',
|
||||
secure: true,
|
||||
})
|
||||
}
|
||||
|
||||
const handleResponseResize = () => {
|
||||
if (requestCodeExample.current) {
|
||||
const requestCodeHeight = requestCodeExample.current.clientHeight || 0
|
||||
const { innerHeight: height } = window
|
||||
if (responseCodeExample) {
|
||||
// 520 pixels roughly accounts for the space taken up by the
|
||||
// nav bar, headers, language picker, method section, and response
|
||||
// picker
|
||||
setResponseMaxHeight(height - requestCodeHeight - 520)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Change the language based on cookies
|
||||
useEffect(() => {
|
||||
// If the user previously selected a language preference and the language
|
||||
// is available in this component set it as the selected language
|
||||
const cookieValue = Cookies.get('codeSampleLanguagePreferred')
|
||||
const preferredCodeLanguage = languageSelectOptions.find((item) => item.key === cookieValue)
|
||||
if (cookieValue && preferredCodeLanguage) {
|
||||
setSelectedLanguage(cookieValue as keyof ExampleT)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Handle syntax higlighting when the language changes or
|
||||
// a cookie is set
|
||||
useEffect(() => {
|
||||
const reqElem = requestCodeExample.current
|
||||
|
||||
// Do not highlight on the first render because the
|
||||
// intersection observer syntax highlighting
|
||||
// (ClientSideHighlightJS) will have already handled highlighting
|
||||
if (reqElem && !firstRender.current) {
|
||||
reqElem.className = 'hljs'
|
||||
hljs.highlightElement(reqElem)
|
||||
handleResponseResize()
|
||||
}
|
||||
}, [selectedLanguage])
|
||||
|
||||
// Handle syntax highlighting and scroll position when the language changes or
|
||||
// a cookie is set, changing the default language
|
||||
useEffect(() => {
|
||||
const reqElem = responseCodeExample.current
|
||||
const scrollElem = scrollRef.current
|
||||
|
||||
// Reset scroll position to the top when switching between example response and
|
||||
// response schema
|
||||
if (scrollElem) {
|
||||
scrollElem.scrollTop = 0
|
||||
}
|
||||
// Do not highlight on the first render because the
|
||||
// intersection observer syntax highlighting
|
||||
// (ClientSideHighlightJS) will have already handled highlighting
|
||||
if (reqElem && !firstRender.current) {
|
||||
reqElem.className = 'hljs'
|
||||
hljs.highlightElement(reqElem)
|
||||
}
|
||||
}, [selectedResponse])
|
||||
|
||||
// Handle highlighting when there is more than one example and
|
||||
// the example changes.
|
||||
useEffect(() => {
|
||||
const reqElem = requestCodeExample.current
|
||||
if (reqElem) {
|
||||
reqElem.className = 'hljs'
|
||||
hljs.highlightElement(reqElem)
|
||||
}
|
||||
|
||||
const resElem = responseCodeExample.current
|
||||
if (resElem) {
|
||||
resElem.className = 'hljs'
|
||||
hljs.highlightElement(resElem)
|
||||
}
|
||||
}, [selectedExample])
|
||||
|
||||
// Keep track of the first render so we can skip highlighting
|
||||
useEffect(() => {
|
||||
if (firstRender.current) {
|
||||
firstRender.current = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Handle the resizing of the response section when the window is resized
|
||||
useEffect(() => {
|
||||
handleResponseResize()
|
||||
window.addEventListener('resize', handleResponseResize)
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResponseResize)
|
||||
}
|
||||
})
|
||||
|
||||
const [isCopied, setCopied] = useClipboard(displayedExample[selectedLanguage] as string, {
|
||||
successDuration: 1400,
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<h4 id={`${slug}--code-samples`}>
|
||||
<h3 className="mt-0 pt-0 h4" id={`${slug}--code-samples`}>
|
||||
<a href={`#${slug}--code-samples`}>{`${t('rest.reference.code_samples')}`}</a>
|
||||
</h4>
|
||||
{languageExamples.map((sample, index) => (
|
||||
<div key={`${JSON.stringify(sample)}-${index}`}>
|
||||
</h3>
|
||||
|
||||
{/* Display an example selector if more than one example */}
|
||||
{!isSingleExample && (
|
||||
<div className="pb-5 pt-2">
|
||||
<FormControl id="example-type-picker">
|
||||
<FormControl.Label visuallyHidden>Select the example type</FormControl.Label>
|
||||
<Select onChange={handleExampleSelection}>
|
||||
{exampleSelectOptions.map((option) => (
|
||||
<Select.Option key={option.languageIndex} value={option.languageIndex.toString()}>
|
||||
{option.text}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Request example section */}
|
||||
<div className="rounded-1 border">
|
||||
<div className="my-0 p-3">
|
||||
<RestMethod verb={operation.verb} requestPath={operation.requestPath} />
|
||||
</div>
|
||||
<div className="border-top d-inline-flex flex-justify-between width-full flex-items-center">
|
||||
<div className="d-inline-flex ml-2">
|
||||
<UnderlineNav aria-label="Example language selector">
|
||||
{languageSelectOptions.map((option) => (
|
||||
<UnderlineNav.Link
|
||||
key={option.key}
|
||||
as="button"
|
||||
onClick={() => {
|
||||
handleLanguageSelection(option.key)
|
||||
}}
|
||||
href={option.key}
|
||||
selected={option.key === selectedLanguage}
|
||||
className="pr-3 mr-0 keyboard-focus"
|
||||
>
|
||||
{option.text}
|
||||
</UnderlineNav.Link>
|
||||
))}
|
||||
</UnderlineNav>
|
||||
</div>
|
||||
<div className="mr-2">
|
||||
<Tooltip
|
||||
className="mr-2"
|
||||
direction="w"
|
||||
aria-label={isCopied ? 'Copied!' : 'Copy to clipboard'}
|
||||
>
|
||||
<button className="js-btn-copy btn-octicon" onClick={() => setCopied()}>
|
||||
{isCopied ? <CheckIcon /> : <CopyIcon />}
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Example requests */}
|
||||
{sample.request && (
|
||||
<>
|
||||
{/* Title of the code sample block */}
|
||||
<h5 dangerouslySetInnerHTML={{ __html: sample.request.description }} />
|
||||
{sample.curl && (
|
||||
<CodeBlock headingLang="Shell" codeBlock={sample.curl} highlight="curl" />
|
||||
)}
|
||||
{sample.javascript && (
|
||||
<CodeBlock
|
||||
headingLang={JAVASCRIPT_HEADING}
|
||||
codeBlock={sample.javascript}
|
||||
highlight="javascript"
|
||||
/>
|
||||
)}
|
||||
{sample.ghcli && (
|
||||
<CodeBlock headingLang={GH_CLI_HEADING} codeBlock={sample.ghcli} highlight="curl" />
|
||||
)}
|
||||
</>
|
||||
<div
|
||||
className={cx(
|
||||
styles.codeBlock,
|
||||
styles.requestCodeBlock,
|
||||
`border-top rounded-1 my-0 ${getLanguageHighlight(selectedLanguage)}`
|
||||
)}
|
||||
data-highlight={getLanguageHighlight(selectedLanguage)}
|
||||
>
|
||||
<code ref={requestCodeExample}>{displayedExample[selectedLanguage]}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Title of the response */}
|
||||
{sample.response && (
|
||||
<>
|
||||
<h5 dangerouslySetInnerHTML={{ __html: sample.response.description }} />
|
||||
{/* Response section */}
|
||||
<h5
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: displayedExample.response.description || t('rest.reference.response'),
|
||||
}}
|
||||
></h5>
|
||||
|
||||
<div className="border rounded-1">
|
||||
{displayedExample.response.schema ? (
|
||||
<UnderlineNav aria-label="Example response format selector">
|
||||
{responseSelectOptions.map((option) => {
|
||||
if (!displayedExample.response.schema) return null
|
||||
|
||||
return (
|
||||
<UnderlineNav.Link
|
||||
key={option.key}
|
||||
as="button"
|
||||
onClick={() => {
|
||||
handleResponseSelection(option.key)
|
||||
}}
|
||||
href={option.key}
|
||||
selected={option.key === selectedResponse}
|
||||
className="pr-3 mr-0 keyboard-focus ml-2"
|
||||
>
|
||||
{option.text}
|
||||
</UnderlineNav.Link>
|
||||
)
|
||||
})}
|
||||
</UnderlineNav>
|
||||
) : null}
|
||||
<div className="">
|
||||
{/* Status code */}
|
||||
{sample.response.statusCode && (
|
||||
<CodeBlock codeBlock={`Status: ${sample.response.statusCode}`} />
|
||||
{displayedExample.response.statusCode && (
|
||||
<div className={cx(styles.codeBlock, 'rounded-1 p-3 my-0 color-bg-default')}>
|
||||
<code>{`Status: ${displayedExample.response.statusCode}`}</code>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Example response */}
|
||||
{sample.response.example && (
|
||||
<CodeBlock
|
||||
codeBlock={JSON.stringify(sample.response.example, null, 2)}
|
||||
highlight="json"
|
||||
/>
|
||||
{displayedExample.response.example && (
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className={cx(
|
||||
styles.codeBlock,
|
||||
styles.responseCodeBlock,
|
||||
'border-top rounded-1 my-0'
|
||||
)}
|
||||
</>
|
||||
data-highlight={'json'}
|
||||
style={{ maxHeight: responseMaxHeight }}
|
||||
>
|
||||
<code ref={responseCodeExample}>
|
||||
{selectedResponse === 'example'
|
||||
? JSON.stringify(displayedExample.response.example, null, 2)
|
||||
: JSON.stringify(displayedExample.response.schema, null, 2)}
|
||||
</code>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
22
components/rest/RestMethod.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import cx from 'classnames'
|
||||
|
||||
import styles from './RestCodeSamples.module.scss'
|
||||
|
||||
type RestMethodT = {
|
||||
verb: string
|
||||
requestPath: string
|
||||
}
|
||||
|
||||
export function RestMethod({ verb, requestPath }: RestMethodT) {
|
||||
// If the path is long, we want to break it up into multiple lines,
|
||||
// breaking before the / character.
|
||||
const displayPath = requestPath.length > 25 ? requestPath.replaceAll('/', '<wbr/>/') : requestPath
|
||||
return (
|
||||
<div className={cx(styles.method, 'my-0 text-mono d-flex flex-row flex-items-start ')}>
|
||||
<span className="IssueLabel IssueLabel--big color-bg-accent-emphasis color-fg-on-emphasis text-uppercase mr-2">
|
||||
{verb}
|
||||
</span>
|
||||
<span dangerouslySetInnerHTML={{ __html: displayPath }} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
import { Link } from 'components/Link'
|
||||
|
||||
export function RestNotes() {
|
||||
const { t } = useTranslation('products')
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<>
|
||||
<h4 className="pt-4">{t('rest.reference.notes')}</h4>
|
||||
<ul className="mt-2 pl-3 pb-2">
|
||||
<li>
|
||||
<Link href={`/${router.locale}/developers/apps`}>
|
||||
{t('rest.reference.works_with_github_apps')}
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
}
|
||||
14
components/rest/RestOperation.module.scss
Normal file
@@ -0,0 +1,14 @@
|
||||
.restOperation {
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--color-fg-default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.statusTable {
|
||||
table-layout: fixed !important;
|
||||
}
|
||||
@@ -1,23 +1,25 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import slugger from 'github-slugger'
|
||||
import { CheckCircleFillIcon } from '@primer/octicons-react'
|
||||
import cx from 'classnames'
|
||||
|
||||
import { RestOperationHeading } from './RestOperationHeading'
|
||||
import { CodeBlock } from './CodeBlock'
|
||||
import { Link } from 'components/Link'
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
import { RestPreviewNotice } from './RestPreviewNotice'
|
||||
import styles from './RestOperation.module.scss'
|
||||
import { RestParameterTable } from './RestParameterTable'
|
||||
import { RestCodeSamples } from './RestCodeSamples'
|
||||
import { RestStatusCodes } from './RestStatusCodes'
|
||||
import { Operation } from './types'
|
||||
import { RestNotes } from './RestNotes'
|
||||
import { RestPreviewNotice } from './RestPreviewNotice'
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
|
||||
type Props = {
|
||||
operation: Operation
|
||||
}
|
||||
|
||||
export function RestOperation({ operation }: Props) {
|
||||
const { t } = useTranslation('products')
|
||||
|
||||
const slug = slugger.slug(operation.title)
|
||||
const { t } = useTranslation('products')
|
||||
const router = useRouter()
|
||||
|
||||
const numPreviews = operation.previews.length
|
||||
const hasStatusCodes = operation.statusCodes.length > 0
|
||||
@@ -25,16 +27,26 @@ export function RestOperation({ operation }: Props) {
|
||||
const hasParameters = operation.parameters.length > 0 || operation.bodyParameters.length > 0
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RestOperationHeading
|
||||
slug={slug}
|
||||
title={operation.title}
|
||||
descriptionHTML={operation.descriptionHTML}
|
||||
/>
|
||||
|
||||
{operation.requestPath && (
|
||||
<CodeBlock verb={operation.verb} codeBlock={operation.requestPath}></CodeBlock>
|
||||
<div className="pb-8">
|
||||
<h2 className="d-flex flex-md-row mb-6" id={slug}>
|
||||
<a href={`#${slug}`}>{operation.title}</a>
|
||||
</h2>
|
||||
{operation.enabledForGitHubApps && (
|
||||
<div className="d-flex">
|
||||
<span className="mr-2 d-flex flex-items-center">
|
||||
<CheckCircleFillIcon size={16} />
|
||||
</span>
|
||||
<span>
|
||||
{t('rest.reference.works_with') + ' '}
|
||||
<Link className="" href={`/${router.locale}/developers/apps`}>
|
||||
GitHub Apps
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={cx(styles.restOperation, 'd-flex flex-wrap gutter mt-4')}>
|
||||
<div className="col-md-12 col-lg-6">
|
||||
<div dangerouslySetInnerHTML={{ __html: operation.descriptionHTML }} />
|
||||
|
||||
{hasParameters && (
|
||||
<RestParameterTable
|
||||
@@ -45,18 +57,17 @@ export function RestOperation({ operation }: Props) {
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasStatusCodes && <RestStatusCodes statusCodes={operation.statusCodes} slug={slug} />}
|
||||
</div>
|
||||
<div
|
||||
className="col-md-12 col-lg-6 position-sticky flex-self-start"
|
||||
style={{ top: '6.5em' }}
|
||||
>
|
||||
{hasCodeSamples && <RestCodeSamples operation={operation} slug={slug} />}
|
||||
|
||||
{hasStatusCodes && (
|
||||
<RestStatusCodes
|
||||
heading={t('rest.reference.status_codes')}
|
||||
statusCodes={operation.statusCodes}
|
||||
/>
|
||||
)}
|
||||
|
||||
{operation.enabledForGitHubApps && <RestNotes />}
|
||||
|
||||
{numPreviews > 0 && <RestPreviewNotice slug={slug} previews={operation.previews} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { LinkIcon } from '@primer/octicons-react'
|
||||
|
||||
type Props = {
|
||||
slug: string
|
||||
title: string
|
||||
descriptionHTML: string
|
||||
}
|
||||
|
||||
export function RestOperationHeading({ slug, title, descriptionHTML }: Props) {
|
||||
return (
|
||||
<>
|
||||
<h2 id={slug}>
|
||||
<a href={`#${slug}`}>
|
||||
<LinkIcon size={16} className="m-1" />
|
||||
</a>
|
||||
{title}
|
||||
</h2>
|
||||
<div dangerouslySetInnerHTML={{ __html: descriptionHTML }} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
.parameterTable {
|
||||
table-layout: fixed !important;
|
||||
z-index: 0;
|
||||
|
||||
thead {
|
||||
tr {
|
||||
border-top: none;
|
||||
|
||||
th {
|
||||
border: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr:nth-child(2n) {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0.75rem 0.5rem !important;
|
||||
border: 0 !important;
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr td {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
tr td:first-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table tr td:not(:first-child) {
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: var(--color-canvas-subtle);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import cx from 'classnames'
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
|
||||
import { ParameterRow } from './ParameterRow'
|
||||
import { BodyParameter, Parameter } from './types'
|
||||
import styles from './RestParameterTable.module.scss'
|
||||
import { PreviewsRow } from './PreviewsRow'
|
||||
import { ParameterRows } from './ParameterRows'
|
||||
import { BodyParameterRows } from './BodyParametersRows'
|
||||
|
||||
type Props = {
|
||||
slug: string
|
||||
@@ -15,25 +13,131 @@ type Props = {
|
||||
|
||||
export function RestParameterTable({ slug, numPreviews, parameters, bodyParameters }: Props) {
|
||||
const { t } = useTranslation('products')
|
||||
const queryParams = parameters.filter((param) => param.in === 'query')
|
||||
const pathParams = parameters.filter((param) => param.in === 'path')
|
||||
|
||||
return (
|
||||
<>
|
||||
<h4 className="mt-4 mb-3 pt-3" id={`${slug}--parameters`}>
|
||||
<h3 className="mt-4 mb-3 pt-3 h4" id={`${slug}--parameters`}>
|
||||
<a href={`#${slug}--parameters`}>{t('rest.reference.parameters')}</a>
|
||||
</h4>
|
||||
<table className={cx(styles.parameterTable)}>
|
||||
</h3>
|
||||
<table
|
||||
className={cx('d-block')}
|
||||
summary="Column one has the type of parameter and the other columns show the name, type, and description of the parameters"
|
||||
>
|
||||
<thead>
|
||||
<tr className="text-left">
|
||||
<th>{t('rest.reference.name')}</th>
|
||||
<th>{t('rest.reference.type')}</th>
|
||||
<th>{t('rest.reference.in')}</th>
|
||||
<th>{t('rest.reference.description')}</th>
|
||||
<tr>
|
||||
<th id="header" scope="col" className="text-bold pl-0">
|
||||
{t('rest.reference.headers')}
|
||||
</th>
|
||||
</tr>
|
||||
<tr className="visually-hidden">
|
||||
<th scope="col">
|
||||
{`${t('rest.reference.name')}, ${t('rest.reference.type')}, ${t(
|
||||
'rest.reference.description'
|
||||
)}`}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<PreviewsRow slug={slug} numPreviews={numPreviews} />
|
||||
<ParameterRows parameters={parameters} />
|
||||
<BodyParameterRows slug={slug} bodyParameters={bodyParameters} />
|
||||
<ParameterRow
|
||||
name={'accept'}
|
||||
type={'string'}
|
||||
description={`<p>Setting to <code>application/vnd.github.v3+json</code> is recommended.</p>`}
|
||||
isRequired={false}
|
||||
slug={slug}
|
||||
numPreviews={numPreviews}
|
||||
/>
|
||||
{pathParams.length > 0 && (
|
||||
<>
|
||||
<tr className="border-top-0">
|
||||
<th className="text-bold pl-0 border-top-0" scope="colgroup">
|
||||
{t('rest.reference.path')}
|
||||
</th>
|
||||
</tr>
|
||||
<tr className="visually-hidden">
|
||||
<th scope="colgroup">
|
||||
{`${t('rest.reference.name')}, ${t('rest.reference.type')}, ${t(
|
||||
'rest.reference.description'
|
||||
)}`}
|
||||
</th>
|
||||
</tr>
|
||||
{pathParams.map((param, index) => (
|
||||
<ParameterRow
|
||||
name={param.name}
|
||||
type={param.schema.type}
|
||||
description={param.description}
|
||||
isRequired={param.required}
|
||||
defaultValue={param.schema.default}
|
||||
enumValues={param.schema.enum}
|
||||
slug={slug}
|
||||
key={`${index}-${param}`}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
{queryParams.length > 0 && (
|
||||
<>
|
||||
<tr className="border-top-0">
|
||||
<th className="text-bold pl-0" scope="colgroup">
|
||||
{t('rest.reference.query')}
|
||||
</th>
|
||||
</tr>
|
||||
<tr className="visually-hidden">
|
||||
<th scope="colgroup">
|
||||
{`${t('rest.reference.name')}, ${t('rest.reference.type')}, ${t(
|
||||
'rest.reference.description'
|
||||
)}`}
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
{queryParams.map((param, index) => (
|
||||
<ParameterRow
|
||||
name={param.name}
|
||||
type={param.schema.type}
|
||||
description={param.description}
|
||||
isRequired={param.required}
|
||||
defaultValue={param.schema.default}
|
||||
enumValues={param.schema.enum}
|
||||
slug={slug}
|
||||
key={`${index}-${param}`}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
{bodyParameters.length > 0 && (
|
||||
<>
|
||||
<tr className="border-top-0">
|
||||
<th scope="colgroup" className="text-bold pl-0">
|
||||
{t('rest.reference.body')}
|
||||
</th>
|
||||
</tr>
|
||||
<tr className="visually-hidden">
|
||||
<th scope="colgroup">
|
||||
{`${t('rest.reference.name')}, ${t('rest.reference.type')}, ${t(
|
||||
'rest.reference.description'
|
||||
)}`}
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
{bodyParameters.map((param, index) => (
|
||||
<ParameterRow
|
||||
name={param.name}
|
||||
type={param.type}
|
||||
description={param.description}
|
||||
isRequired={param.isRequired}
|
||||
defaultValue={param.default}
|
||||
enumValues={param.enum}
|
||||
slug={slug}
|
||||
childParamsGroups={param.childParamsGroups}
|
||||
key={`${index}-${param}`}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
|
||||
@@ -7,14 +7,15 @@ type Props = {
|
||||
|
||||
export function RestPreviewNotice({ slug, previews }: Props) {
|
||||
const { t } = useTranslation('products')
|
||||
|
||||
return (
|
||||
<>
|
||||
<h4 id={`${slug}-preview-notices`}>
|
||||
<h3 className="h4" id={`${slug}-preview-notices`}>
|
||||
<a href={`${slug}-preview-notices`}>
|
||||
{previews.length > 1
|
||||
? `${t('rest.reference.preview_notices')}`
|
||||
: `${t('rest.reference.preview_notice')}`}
|
||||
</h4>
|
||||
</a>
|
||||
</h3>
|
||||
{previews.map((preview, index) => (
|
||||
<div
|
||||
className="extended-markdown note border rounded-1 mb-6 p-3 color-border-accent-emphasis color-bg-accent f5"
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import dynamic from 'next/dynamic'
|
||||
import cx from 'classnames'
|
||||
|
||||
import { DefaultLayout } from 'components/DefaultLayout'
|
||||
import { MarkdownContent } from 'components/ui/MarkdownContent'
|
||||
import { Lead } from 'components/ui/Lead'
|
||||
import { RestOperation } from './RestOperation'
|
||||
import styles from './RestOperation.module.scss'
|
||||
import { useRestContext } from 'components/context/RestContext'
|
||||
import { Operation } from './types'
|
||||
import { RestOperation } from './RestOperation'
|
||||
|
||||
const ClientSideHighlightJS = dynamic(() => import('components/article/ClientSideHighlightJS'), {
|
||||
ssr: false,
|
||||
@@ -95,7 +97,7 @@ export const RestReferencePage = ({ restOperations }: StructuredContentT) => {
|
||||
never render anything. It always just return null. */}
|
||||
{loadClientsideRedirectExceptions && <ClientSideRedirectExceptions />}
|
||||
{lazyLoadHighlightJS && <ClientSideHighlightJS />}
|
||||
<div className={'px-3 px-md-6 my-4 mx-xl-12 mx-lg-12'}>
|
||||
<div className={cx(styles.restOperation, 'px-3 px-md-6 my-4 container-xl')}>
|
||||
<h1 className="mb-3">{title}</h1>
|
||||
{intro && (
|
||||
<Lead data-testid="lead" data-search="lead" className="markdown-body">
|
||||
@@ -103,14 +105,18 @@ export const RestReferencePage = ({ restOperations }: StructuredContentT) => {
|
||||
</Lead>
|
||||
)}
|
||||
<MarkdownContent>
|
||||
<MarkdownContent>{renderedPage}</MarkdownContent>
|
||||
{renderedPage && <MarkdownContent className="pt-3 pb-4">{renderedPage}</MarkdownContent>}
|
||||
{restOperations &&
|
||||
restOperations.length > 0 &&
|
||||
restOperations.map((operation, index) => (
|
||||
<React.Fragment
|
||||
key={`${operation.title}-${operation.category}-${operation.subcategory}`}
|
||||
>
|
||||
<RestOperation
|
||||
key={`restOperation-${operation.title}-${index}`}
|
||||
operation={operation}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</MarkdownContent>
|
||||
</div>
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
.restResponseTable {
|
||||
table-layout: fixed !important;
|
||||
|
||||
thead {
|
||||
tr {
|
||||
border-top: none;
|
||||
|
||||
th {
|
||||
border: 0;
|
||||
font-weight: normal;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
th:first-child {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
th:nth-child(2) {
|
||||
width: 75%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr:nth-child(2n) {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0.75rem 0.5rem !important;
|
||||
border: 0 !important;
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr td:first-child {
|
||||
width: 30%;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tr td:nth-child(2) {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
table tr td:not(:first-child) {
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,37 @@
|
||||
import cx from 'classnames'
|
||||
import { StatusCode } from './types'
|
||||
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
import styles from './RestResponseTable.module.scss'
|
||||
import styles from './RestOperation.module.scss'
|
||||
import { StatusCode } from './types'
|
||||
|
||||
type Props = {
|
||||
heading: string
|
||||
statusCodes: Array<StatusCode>
|
||||
slug: string
|
||||
}
|
||||
|
||||
export function RestStatusCodes({ heading, statusCodes }: Props) {
|
||||
export function RestStatusCodes({ statusCodes, slug }: Props) {
|
||||
const { t } = useTranslation('products')
|
||||
|
||||
return (
|
||||
<>
|
||||
<h4>{heading}</h4>
|
||||
<table className={cx(styles.restResponseTable)}>
|
||||
<h3 className="mt-4 mb-3 pt-3 h4" id={`${slug}--status-codes`}>
|
||||
<a href={`#${slug}--status-codes`}>{t('rest.reference.http_status_code')}</a>
|
||||
</h3>
|
||||
|
||||
<table className={cx(styles.restResponseTable, 'd-block')}>
|
||||
<thead>
|
||||
<tr className="text-left">
|
||||
<th>{t('rest.reference.http_status_code')}</th>
|
||||
<th>{t('rest.reference.status_code')}</th>
|
||||
<th>{t('rest.reference.description')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{statusCodes.map((statusCode, index) => {
|
||||
return (
|
||||
{statusCodes.map((statusCode, index) => (
|
||||
<tr key={`${statusCode.description}-${index}}`}>
|
||||
<td>
|
||||
<code>{statusCode.httpStatusCode}</code>
|
||||
</td>
|
||||
<td>
|
||||
<td className="color-fg-muted">
|
||||
{statusCode.description ? (
|
||||
<div dangerouslySetInnerHTML={{ __html: statusCode.description }} />
|
||||
) : (
|
||||
@@ -36,8 +39,7 @@ export function RestStatusCodes({ heading, statusCodes }: Props) {
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
|
||||
@@ -17,11 +17,12 @@ export interface Operation {
|
||||
export interface Parameter {
|
||||
in: string
|
||||
name: string
|
||||
descriptionHTML: string
|
||||
description: string
|
||||
required: boolean
|
||||
schema: {
|
||||
type: string
|
||||
default?: string
|
||||
enum?: Array<string>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,10 +52,12 @@ export interface CodeSample {
|
||||
export interface BodyParameter {
|
||||
in: string
|
||||
name: string
|
||||
childParamsGroups?: Array<ChildParamsGroup>
|
||||
default?: string
|
||||
description: string
|
||||
type: string
|
||||
isRequired: boolean
|
||||
default?: string
|
||||
enum?: Array<string>
|
||||
childParamsGroups?: Array<ChildParamsGroup>
|
||||
}
|
||||
|
||||
export interface ChildParamsGroup {
|
||||
@@ -68,4 +71,26 @@ export interface ChildParameter {
|
||||
name: string
|
||||
description: string
|
||||
type: string
|
||||
isRequired: boolean
|
||||
enum?: Array<string>
|
||||
default?: string
|
||||
}
|
||||
|
||||
export type ExampleT = {
|
||||
description: string
|
||||
curl: string
|
||||
javascript: string
|
||||
ghcli?: string
|
||||
response: {
|
||||
statusCode: string
|
||||
contentType?: string
|
||||
description: string
|
||||
example?: Object
|
||||
schema?: Object
|
||||
}
|
||||
}
|
||||
|
||||
export type LanguageOptionT = {
|
||||
key: keyof ExampleT
|
||||
text: string
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ export const RestCollapsibleSection = (props: SectionProps) => {
|
||||
const { routePath, defaultOpen, title, page, isStandaloneCategory } = props
|
||||
const [isOpen, setIsOpen] = useState(defaultOpen)
|
||||
const [currentAnchor, setCurrentAnchor] = useState('')
|
||||
const [visibleAnchor, setVisibleAnchor] = useState('')
|
||||
|
||||
const onToggle = (e: SyntheticEvent) => {
|
||||
const newIsOpen = (e.target as HTMLDetailsElement).open
|
||||
setIsOpen(newIsOpen)
|
||||
@@ -38,6 +40,14 @@ export const RestCollapsibleSection = (props: SectionProps) => {
|
||||
})
|
||||
}
|
||||
|
||||
const miniTocItems =
|
||||
router.query.productId === 'rest' ||
|
||||
// These pages need the Article Page mini tocs instead of the Rest Pages
|
||||
router.asPath.includes('/rest/guides') ||
|
||||
router.asPath.includes('/rest/overview')
|
||||
? []
|
||||
: useRestContext().miniTocItems
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentAnchor) {
|
||||
setCurrentAnchor(window.location.hash)
|
||||
@@ -54,14 +64,36 @@ export const RestCollapsibleSection = (props: SectionProps) => {
|
||||
}
|
||||
}, [])
|
||||
|
||||
const miniTocItems =
|
||||
router.query.productId === 'rest' ||
|
||||
// These pages need the Article Page mini tocs instead of the Rest Pages
|
||||
router.asPath.includes('/rest/guides') ||
|
||||
router.asPath.includes('/rest/overview')
|
||||
? []
|
||||
: useRestContext().miniTocItems
|
||||
useEffect(() => {
|
||||
if (!router.asPath.includes('guides') && !router.asPath.includes('overview')) {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.target.id) {
|
||||
const anchor = '#' + entry.target.id.split('--')[0]
|
||||
if (entry.isIntersecting === true) setVisibleAnchor(anchor)
|
||||
} else if (router.asPath.includes('#')) {
|
||||
setVisibleAnchor('#' + router.asPath.split('#')[1])
|
||||
} else {
|
||||
setVisibleAnchor('')
|
||||
}
|
||||
})
|
||||
},
|
||||
{ rootMargin: '0px 0px -85% 0px' }
|
||||
)
|
||||
// TODO: When we add the ## About the {title} API to each operation
|
||||
// we can remove the h2 here
|
||||
const headingsList = Array.from(document.querySelectorAll('h2, h3'))
|
||||
|
||||
headingsList.forEach((heading) => {
|
||||
observer.observe(heading)
|
||||
})
|
||||
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
}
|
||||
}
|
||||
}, [miniTocItems])
|
||||
// This wrapper solves the issue of having standalone categories not
|
||||
// link to the new page. We want standalone categories to have links
|
||||
// just like maptopics/subcategories.
|
||||
@@ -71,8 +103,7 @@ export const RestCollapsibleSection = (props: SectionProps) => {
|
||||
const renderRestAnchorLink = (miniTocItem: MiniTocItem) => {
|
||||
const miniTocAnchor = miniTocItem.contents.href
|
||||
const title = miniTocItem.contents.title
|
||||
const isCurrent = currentAnchor === miniTocAnchor
|
||||
|
||||
const isCurrent = visibleAnchor === miniTocAnchor
|
||||
return {
|
||||
text: title,
|
||||
renderItem: () => (
|
||||
@@ -96,6 +127,7 @@ export const RestCollapsibleSection = (props: SectionProps) => {
|
||||
'd-block pl-6 pr-5 py-1 no-underline width-full',
|
||||
isCurrent ? 'color-fg-accent' : 'color-fg-default'
|
||||
)}
|
||||
onClick={() => setVisibleAnchor(miniTocAnchor)}
|
||||
href={miniTocAnchor}
|
||||
>
|
||||
{title}
|
||||
@@ -117,7 +149,10 @@ export const RestCollapsibleSection = (props: SectionProps) => {
|
||||
)}
|
||||
>
|
||||
<div className="d-flex flex-justify-between">
|
||||
<div className="pl-4 pr-1 py-2 f5 d-block flex-auto mr-3 color-fg-default no-underline text-bold">
|
||||
<div
|
||||
data-testid="rest-category"
|
||||
className="pl-4 pr-1 py-2 f5 d-block flex-auto mr-3 color-fg-default no-underline text-bold"
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
<span style={{ marginTop: 7 }} className="flex-shrink-0 pr-3">
|
||||
@@ -132,7 +167,7 @@ export const RestCollapsibleSection = (props: SectionProps) => {
|
||||
{/* <!-- Render the maptopic level subcategory operation links e.g. --> */}
|
||||
<ul className="list-style-none position-relative">
|
||||
{page.childPages.length <= 0 ? (
|
||||
<div data-testid="sidebar-article-group" className="pb-0">
|
||||
<div className="pb-0">
|
||||
{miniTocItems.length > 0 && (
|
||||
<ActionList
|
||||
{...{ as: 'ul' }}
|
||||
@@ -159,12 +194,7 @@ export const RestCollapsibleSection = (props: SectionProps) => {
|
||||
className="details-reset"
|
||||
>
|
||||
<summary>
|
||||
<div
|
||||
data-testid="sidebar-rest-subcategory"
|
||||
className={cx('pl-4 pr-5 py-2 no-underline')}
|
||||
>
|
||||
{childTitle}
|
||||
</div>
|
||||
<div className={cx('pl-4 pr-5 py-2 no-underline')}>{childTitle}</div>
|
||||
</summary>
|
||||
<div className="pb-0">
|
||||
{miniTocItems.length > 0 && (
|
||||
@@ -183,7 +213,7 @@ export const RestCollapsibleSection = (props: SectionProps) => {
|
||||
// We're not on the current page so don't have any minitoc
|
||||
// data so just render a link to the category page.
|
||||
return (
|
||||
<li key={childTitle} data-testid="sidebar-article-group" className="pb-0">
|
||||
<li data-testid="rest-subcategory" key={childTitle} className="pb-0">
|
||||
<Link
|
||||
href={childPage.href}
|
||||
className={cx(
|
||||
|
||||
@@ -87,7 +87,7 @@ export const SidebarProduct = () => {
|
||||
)
|
||||
return (
|
||||
<>
|
||||
<li className="my-3" data-testid="rest-sidebar-items">
|
||||
<li className="my-3">
|
||||
<ul className="list-style-none">
|
||||
{conceptualPages.map((childPage, i) => {
|
||||
const isStandaloneCategory = childPage.page.documentType === 'article'
|
||||
@@ -135,7 +135,6 @@ export const SidebarProduct = () => {
|
||||
const defaultOpen = hasExactCategory ? isActive : false
|
||||
return (
|
||||
<li
|
||||
data-testid="rest-sidebar-items"
|
||||
key={childPage.href + i}
|
||||
data-is-active-category={isActive}
|
||||
data-is-current-page={isActive && isStandaloneCategory}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
[class~="highlight"] pre,
|
||||
pre {
|
||||
margin-top: 0.5rem;
|
||||
border: 1px var(--color-border-default) solid;
|
||||
}
|
||||
|
||||
[class~="height-constrained-code-block"] pre {
|
||||
|
||||
@@ -51,7 +51,7 @@ You can also view whether an enterprise owner has a specific role in the organiz
|
||||
| Enterprise owner | Organization owner | Able to configure organization settings and manage access to the organization's resources through teams, etc. |
|
||||
| Enterprise owner | Organization member | Able to access organization resources and content, such as repositories, without access to the organization's settings. |
|
||||
|
||||
To review all roles in an organization, see "[Roles in an organization](/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization)." {% ifversion ghec %} An organization member can also have a custom role for a specific repository. For more information, see "[Managing custom repository roles for an organization](/organizations/managing-peoples-access-to-your-organization-with-roles/managing-custom-repository-roles-for-an-organization)."{% endif %}
|
||||
To review all roles in an organization, see "[Roles in an organization](/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization)." {% if custom-repository-roles %} An organization member can also have a custom role for a specific repository. For more information, see "[Managing custom repository roles for an organization](/organizations/managing-peoples-access-to-your-organization-with-roles/managing-custom-repository-roles-for-an-organization)."{% endif %}
|
||||
|
||||
For more information about the enterprise owner role, see "[Roles in an enterprise](/admin/user-management/managing-users-in-your-enterprise/roles-in-an-enterprise#enterprise-owner)."
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ shortTitle: Build & test Java & Gradle
|
||||
|
||||
## Introduction
|
||||
|
||||
This guide shows you how to create a workflow that performs continuous integration (CI) for your Java project using the Gradle build system. The workflow you create will allow you to see when commits to a pull request cause build or test failures against your default branch; this approach can help ensure that your code is always healthy. You can extend your CI workflow to cache files and upload artifacts from a workflow run.
|
||||
This guide shows you how to create a workflow that performs continuous integration (CI) for your Java project using the Gradle build system. The workflow you create will allow you to see when commits to a pull request cause build or test failures against your default branch; this approach can help ensure that your code is always healthy. You can extend your CI workflow to {% if actions-caching %}cache files and{% endif %} upload artifacts from a workflow run.
|
||||
|
||||
{% ifversion ghae %}
|
||||
{% data reusables.actions.self-hosted-runners-software %}
|
||||
@@ -110,12 +110,16 @@ steps:
|
||||
arguments: -b ci.gradle package
|
||||
```
|
||||
|
||||
{% if actions-caching %}
|
||||
|
||||
## Caching dependencies
|
||||
|
||||
When using {% data variables.product.prodname_dotcom %}-hosted runners, your build dependencies can be cached to speed up your workflow runs. After a successful run, the `gradle/gradle-build-action` caches important parts of the Gradle user home directory. In future jobs, the cache will be restored so that build scripts won't need to be recompiled and dependencies won't need to be downloaded from remote package repositories.
|
||||
Your build dependencies can be cached to speed up your workflow runs. After a successful run, the `gradle/gradle-build-action` caches important parts of the Gradle user home directory. In future jobs, the cache will be restored so that build scripts won't need to be recompiled and dependencies won't need to be downloaded from remote package repositories.
|
||||
|
||||
Caching is enabled by default when using the `gradle/gradle-build-action` action. For more information, see [`gradle/gradle-build-action`](https://github.com/gradle/gradle-build-action#caching).
|
||||
|
||||
{% endif %}
|
||||
|
||||
## Packaging workflow data as artifacts
|
||||
|
||||
After your build has succeeded and your tests have passed, you may want to upload the resulting Java packages as a build artifact. This will store the built packages as part of the workflow run, and allow you to download them. Artifacts can help you test and debug pull requests in your local environment before they're merged. For more information, see "[Persisting workflow data using artifacts](/actions/automating-your-workflow-with-github-actions/persisting-workflow-data-using-artifacts)."
|
||||
|
||||
@@ -22,7 +22,7 @@ shortTitle: Build & test Java with Maven
|
||||
|
||||
## Introduction
|
||||
|
||||
This guide shows you how to create a workflow that performs continuous integration (CI) for your Java project using the Maven software project management tool. The workflow you create will allow you to see when commits to a pull request cause build or test failures against your default branch; this approach can help ensure that your code is always healthy. You can extend your CI workflow to cache files and upload artifacts from a workflow run.
|
||||
This guide shows you how to create a workflow that performs continuous integration (CI) for your Java project using the Maven software project management tool. The workflow you create will allow you to see when commits to a pull request cause build or test failures against your default branch; this approach can help ensure that your code is always healthy. You can extend your CI workflow to {% if actions-caching %}cache files and{% endif %} upload artifacts from a workflow run.
|
||||
|
||||
{% ifversion ghae %}
|
||||
{% data reusables.actions.self-hosted-runners-software %}
|
||||
@@ -99,9 +99,11 @@ steps:
|
||||
run: mvn --batch-mode --update-snapshots verify
|
||||
```
|
||||
|
||||
{% if actions-caching %}
|
||||
|
||||
## Caching dependencies
|
||||
|
||||
When using {% data variables.product.prodname_dotcom %}-hosted runners, you can cache your dependencies to speed up your workflow runs. After a successful run, your local Maven repository will be stored on GitHub Actions infrastructure. In future workflow runs, the cache will be restored so that dependencies don't need to be downloaded from remote Maven repositories. You can cache dependencies simply using the [`setup-java` action](https://github.com/marketplace/actions/setup-java-jdk) or can use [`cache` action](https://github.com/actions/cache) for custom and more advanced configuration.
|
||||
You can cache your dependencies to speed up your workflow runs. After a successful run, your local Maven repository will be stored in a cache. In future workflow runs, the cache will be restored so that dependencies don't need to be downloaded from remote Maven repositories. You can cache dependencies simply using the [`setup-java` action](https://github.com/marketplace/actions/setup-java-jdk) or can use [`cache` action](https://github.com/actions/cache) for custom and more advanced configuration.
|
||||
|
||||
```yaml{:copy}
|
||||
steps:
|
||||
@@ -118,6 +120,8 @@ steps:
|
||||
|
||||
This workflow will save the contents of your local Maven repository, located in the `.m2` directory of the runner's home directory. The cache key will be the hashed contents of _pom.xml_, so changes to _pom.xml_ will invalidate the cache.
|
||||
|
||||
{% endif %}
|
||||
|
||||
## Packaging workflow data as artifacts
|
||||
|
||||
After your build has succeeded and your tests have passed, you may want to upload the resulting Java packages as a build artifact. This will store the built packages as part of the workflow run, and allow you to download them. Artifacts can help you test and debug pull requests in your local environment before they're merged. For more information, see "[Persisting workflow data using artifacts](/actions/automating-your-workflow-with-github-actions/persisting-workflow-data-using-artifacts)."
|
||||
|
||||
@@ -120,7 +120,7 @@ steps:
|
||||
run: dotnet add package Newtonsoft.Json --version 12.0.1
|
||||
```
|
||||
|
||||
{% ifversion fpt or ghec %}
|
||||
{% if actions-caching %}
|
||||
|
||||
### Caching dependencies
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ If you don't specify a Node.js version, {% data variables.product.prodname_dotco
|
||||
|
||||
{% data variables.product.prodname_dotcom %}-hosted runners have npm and Yarn dependency managers installed. You can use npm and Yarn to install dependencies in your workflow before building and testing your code. The Windows and Linux {% data variables.product.prodname_dotcom %}-hosted runners also have Grunt, Gulp, and Bower installed.
|
||||
|
||||
When using {% data variables.product.prodname_dotcom %}-hosted runners, you can also cache dependencies to speed up your workflow. For more information, see "<a href="/actions/guides/caching-dependencies-to-speed-up-workflows" class="dotcom-only">Caching dependencies to speed up workflows</a>."
|
||||
{% if actions-caching %}You can also cache dependencies to speed up your workflow. For more information, see "[Caching dependencies to speed up workflows](/actions/using-workflows/caching-dependencies-to-speed-up-workflows)."{% endif %}
|
||||
|
||||
### Example using npm
|
||||
|
||||
@@ -228,9 +228,11 @@ The example above creates an *.npmrc* file with the following contents:
|
||||
always-auth=true
|
||||
```
|
||||
|
||||
{% if actions-caching %}
|
||||
|
||||
### Example caching dependencies
|
||||
|
||||
When using {% data variables.product.prodname_dotcom %}-hosted runners, you can cache and restore the dependencies using the [`setup-node` action](https://github.com/actions/setup-node).
|
||||
You can cache and restore the dependencies using the [`setup-node` action](https://github.com/actions/setup-node).
|
||||
|
||||
The following example caches dependencies for npm.
|
||||
|
||||
@@ -278,7 +280,9 @@ steps:
|
||||
- run: pnpm test
|
||||
```
|
||||
|
||||
If you have a custom requirement or need finer controls for caching, you can use the [`cache` action](https://github.com/marketplace/actions/cache). For more information, see "<a href="/actions/guides/caching-dependencies-to-speed-up-workflows" class="dotcom-only">Caching dependencies to speed up workflows</a>".
|
||||
If you have a custom requirement or need finer controls for caching, you can use the [`cache` action](https://github.com/marketplace/actions/cache). For more information, see "[Caching dependencies to speed up workflows](/actions/using-workflows/caching-dependencies-to-speed-up-workflows)."
|
||||
|
||||
{% endif %}
|
||||
|
||||
## Building and testing your code
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ The table below describes the locations for various PowerShell modules in each {
|
||||
|
||||
{% endnote %}
|
||||
|
||||
When using {% data variables.product.prodname_dotcom %}-hosted runners, you can also cache dependencies to speed up your workflow. For more information, see "<a href="/actions/guides/caching-dependencies-to-speed-up-workflows" class="dotcom-only">Caching dependencies to speed up workflows</a>."
|
||||
{% if actions-caching %}You can also cache dependencies to speed up your workflow. For more information, see "[Caching dependencies to speed up workflows](/actions/using-workflows/caching-dependencies-to-speed-up-workflows)."{% endif %}
|
||||
|
||||
For example, the following job installs the `SqlServer` and `PSScriptAnalyzer` modules:
|
||||
|
||||
@@ -128,9 +128,11 @@ jobs:
|
||||
|
||||
{% endnote %}
|
||||
|
||||
{% if actions-caching %}
|
||||
|
||||
### Caching dependencies
|
||||
|
||||
When using {% data variables.product.prodname_dotcom %}-hosted runners, you can cache PowerShell dependencies using a unique key, which allows you to restore the dependencies for future workflows with the [`cache`](https://github.com/marketplace/actions/cache) action. For more information, see "<a href="/actions/guides/caching-dependencies-to-speed-up-workflows" class="dotcom-only">Caching dependencies to speed up workflows</a>."
|
||||
You can cache PowerShell dependencies using a unique key, which allows you to restore the dependencies for future workflows with the [`cache`](https://github.com/marketplace/actions/cache) action. For more information, see "[Caching dependencies to speed up workflows](/actions/using-workflows/caching-dependencies-to-speed-up-workflows)."
|
||||
|
||||
PowerShell caches its dependencies in different locations, depending on the runner's operating system. For example, the `path` location used in the following Ubuntu example will be different for a Windows operating system.
|
||||
|
||||
@@ -151,6 +153,8 @@ steps:
|
||||
Install-Module SqlServer, PSScriptAnalyzer -ErrorAction Stop
|
||||
```
|
||||
|
||||
{% endif %}
|
||||
|
||||
## Testing your code
|
||||
|
||||
You can use the same commands that you use locally to build and test your code.
|
||||
|
||||
@@ -197,7 +197,7 @@ We recommend using `setup-python` to configure the version of Python used in you
|
||||
|
||||
{% data variables.product.prodname_dotcom %}-hosted runners have the pip package manager installed. You can use pip to install dependencies from the PyPI package registry before building and testing your code. For example, the YAML below installs or upgrades the `pip` package installer and the `setuptools` and `wheel` packages.
|
||||
|
||||
When using {% data variables.product.prodname_dotcom %}-hosted runners, you can also cache dependencies to speed up your workflow. For more information, see "<a href="/actions/guides/caching-dependencies-to-speed-up-workflows" class="dotcom-only">Caching dependencies to speed up workflows</a>."
|
||||
{% if actions-caching %}You can also cache dependencies to speed up your workflow. For more information, see "[Caching dependencies to speed up workflows](/actions/using-workflows/caching-dependencies-to-speed-up-workflows)."{% endif %}
|
||||
|
||||
```yaml{:copy}
|
||||
steps:
|
||||
@@ -227,9 +227,11 @@ steps:
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
{% if actions-caching %}
|
||||
|
||||
### Caching Dependencies
|
||||
|
||||
When using {% data variables.product.prodname_dotcom %}-hosted runners, you can cache and restore the dependencies using the [`setup-python` action](https://github.com/actions/setup-python).
|
||||
You can cache and restore the dependencies using the [`setup-python` action](https://github.com/actions/setup-python).
|
||||
|
||||
The following example caches dependencies for pip.
|
||||
|
||||
@@ -244,10 +246,12 @@ steps:
|
||||
- run: pip test
|
||||
```
|
||||
|
||||
By default, the `setup-python` action searches for the dependency file (`requirements.txt` for pip or `Pipfile.lock` for pipenv) in the whole repository. For more information, see "<a href="/actions/guides/caching-dependencies-to-speed-up-workflows" class="dotcom-only">Caching packages dependencies</a>" in the `setup-python` actions README.
|
||||
By default, the `setup-python` action searches for the dependency file (`requirements.txt` for pip or `Pipfile.lock` for pipenv) in the whole repository. For more information, see "[Caching packages dependencies](https://github.com/actions/setup-python#caching-packages-dependencies)" in the `setup-python` README.
|
||||
|
||||
If you have a custom requirement or need finer controls for caching, you can use the [`cache` action](https://github.com/marketplace/actions/cache). Pip caches dependencies in different locations, depending on the operating system of the runner. The path you'll need to cache may differ from the Ubuntu example above, depending on the operating system you use. For more information, see [Python caching examples](https://github.com/actions/cache/blob/main/examples.md#python---pip) in the `cache` action repository.
|
||||
|
||||
{% endif %}
|
||||
|
||||
## Testing your code
|
||||
|
||||
You can use the same commands that you use locally to build and test your code.
|
||||
|
||||
@@ -144,9 +144,11 @@ steps:
|
||||
- run: bundle install
|
||||
```
|
||||
|
||||
{% if actions-caching %}
|
||||
|
||||
### Caching dependencies
|
||||
|
||||
If you are using {% data variables.product.prodname_dotcom %}-hosted runners, the `setup-ruby` actions provides a method to automatically handle the caching of your gems between runs.
|
||||
The `setup-ruby` actions provides a method to automatically handle the caching of your gems between runs.
|
||||
|
||||
To enable caching, set the following.
|
||||
|
||||
@@ -159,11 +161,11 @@ steps:
|
||||
```
|
||||
{% endraw %}
|
||||
|
||||
This will configure bundler to install your gems to `vendor/cache`. For each successful run of your workflow, this folder will be cached by Actions and re-downloaded for subsequent workflow runs. A hash of your gemfile.lock and the Ruby version are used as the cache key. If you install any new gems, or change a version, the cache will be invalidated and bundler will do a fresh install.
|
||||
This will configure bundler to install your gems to `vendor/cache`. For each successful run of your workflow, this folder will be cached by {% data variables.product.prodname_actions %} and re-downloaded for subsequent workflow runs. A hash of your gemfile.lock and the Ruby version are used as the cache key. If you install any new gems, or change a version, the cache will be invalidated and bundler will do a fresh install.
|
||||
|
||||
**Caching without setup-ruby**
|
||||
|
||||
For greater control over caching, if you are using {% data variables.product.prodname_dotcom %}-hosted runners, you can use the `actions/cache` Action directly. For more information, see "<a href="/actions/guides/caching-dependencies-to-speed-up-workflows" class="dotcom-only">Caching dependencies to speed up workflows</a>."
|
||||
For greater control over caching, you can use the `actions/cache` action directly. For more information, see "[Caching dependencies to speed up workflows](/actions/using-workflows/caching-dependencies-to-speed-up-workflows)."
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
@@ -195,6 +197,8 @@ steps:
|
||||
bundle install --jobs 4 --retry 3
|
||||
```
|
||||
|
||||
{% endif %}
|
||||
|
||||
## Matrix testing your code
|
||||
|
||||
The following example matrix tests all stable releases and head versions of MRI, JRuby and TruffleRuby on Ubuntu and macOS.
|
||||
|
||||
@@ -75,7 +75,7 @@ Before you begin, you'll create a repository on {% ifversion ghae %}{% data vari
|
||||
outputs:
|
||||
random-number:
|
||||
description: "Random number"
|
||||
value: ${{ steps.random-number-generator.outputs.random-id }}
|
||||
value: ${{ steps.random-number-generator.outputs.random-number }}
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
|
||||
@@ -286,7 +286,7 @@ steps:
|
||||
|
||||
**Example: Using status check functions**
|
||||
|
||||
The `my backup step` only runs when the previous step of a composite action fails. For more information, see "[Expressions](/actions/learn-github-actions/expressions#job-status-check-functions)."
|
||||
The `my backup step` only runs when the previous step of a composite action fails. For more information, see "[Expressions](/actions/learn-github-actions/expressions#status-check-functions)."
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
|
||||
@@ -31,7 +31,7 @@ You can configure your CD workflow to run when a {% data variables.product.produ
|
||||
|
||||
{% data variables.product.prodname_actions %} provides features that give you more control over deployments. For example, you can use environments to require approval for a job to proceed, restrict which branches can trigger a workflow, or limit access to secrets. {% ifversion fpt or ghae or ghes > 3.1 or ghec %}You can use concurrency to limit your CD pipeline to a maximum of one in-progress deployment and one pending deployment. {% endif %}For more information about these features, see "[Deploying with GitHub Actions](/actions/deployment/deploying-with-github-actions)" and "[Using environments for deployment](/actions/deployment/using-environments-for-deployment)."
|
||||
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 %}
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
|
||||
|
||||
## Using OpenID Connect to access cloud resources
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ This guide explains how to use {% data variables.product.prodname_actions %} to
|
||||
|
||||
On every new push to `main` in your {% data variables.product.company_short %} repository, the {% data variables.product.prodname_actions %} workflow builds and pushes a new container image to Amazon ECR, and then deploys a new task definition to Amazon ECS.
|
||||
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 %}
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
|
||||
|
||||
{% note %}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ topics:
|
||||
|
||||
This guide explains how to use {% data variables.product.prodname_actions %} to build and deploy a Docker container to [Azure App Service](https://azure.microsoft.com/services/app-service/).
|
||||
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 %}
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
|
||||
|
||||
{% note %}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ topics:
|
||||
|
||||
This guide explains how to use {% data variables.product.prodname_actions %} to build and deploy a Java project to [Azure App Service](https://azure.microsoft.com/services/app-service/).
|
||||
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 %}
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
|
||||
|
||||
{% note %}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ topics:
|
||||
|
||||
This guide explains how to use {% data variables.product.prodname_actions %} to build and deploy a .NET project to [Azure App Service](https://azure.microsoft.com/services/app-service/).
|
||||
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 %}
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
|
||||
|
||||
{% note %}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ topics:
|
||||
|
||||
This guide explains how to use {% data variables.product.prodname_actions %} to build, test, and deploy a Node.js project to [Azure App Service](https://azure.microsoft.com/services/app-service/).
|
||||
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 %}
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
|
||||
|
||||
{% note %}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ topics:
|
||||
|
||||
This guide explains how to use {% data variables.product.prodname_actions %} to build and deploy a PHP project to [Azure App Service](https://azure.microsoft.com/services/app-service/).
|
||||
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 %}
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
|
||||
|
||||
{% note %}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ topics:
|
||||
|
||||
This guide explains how to use {% data variables.product.prodname_actions %} to build and deploy a Python project to [Azure App Service](https://azure.microsoft.com/services/app-service/).
|
||||
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 %}
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
|
||||
|
||||
{% note %}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ topics:
|
||||
|
||||
This guide explains how to use {% data variables.product.prodname_actions %} to build and deploy a project to [Azure Kubernetes Service](https://azure.microsoft.com/services/kubernetes-service/).
|
||||
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 %}
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
|
||||
|
||||
{% note %}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ topics:
|
||||
|
||||
This guide explains how to use {% data variables.product.prodname_actions %} to build and deploy a web app to [Azure Static Web Apps](https://azure.microsoft.com/services/app-service/static/).
|
||||
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 %}
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
|
||||
|
||||
{% note %}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ This guide explains how to use {% data variables.product.prodname_actions %} to
|
||||
|
||||
GKE is a managed Kubernetes cluster service from Google Cloud that can host your containerized workloads in the cloud or in your own datacenter. For more information, see [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine).
|
||||
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 %}
|
||||
{% ifversion fpt or ghec or ghae-issue-4856 or ghes > 3.4 %}
|
||||
|
||||
{% note %}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ versions:
|
||||
fpt: '*'
|
||||
ghae: issue-4856
|
||||
ghec: '*'
|
||||
ghes: '>=3.5'
|
||||
type: tutorial
|
||||
topics:
|
||||
- Security
|
||||
@@ -66,11 +67,14 @@ The following example OIDC token uses a subject (`sub`) that references a job en
|
||||
"jti": "example-id",
|
||||
"sub": "repo:octo-org/octo-repo:environment:prod",
|
||||
"environment": "prod",
|
||||
"aud": "https://github.com/octo-org",
|
||||
"aud": "{% ifversion ghes %}https://HOSTNAME{% else %}https://github.com{% endif %}/octo-org",
|
||||
"ref": "refs/heads/main",
|
||||
"sha": "example-sha",
|
||||
"repository": "octo-org/octo-repo",
|
||||
"repository_owner": "octo-org",
|
||||
"actor_id": "12",
|
||||
"repository_id": "74",
|
||||
"repository_owner_id": "65",
|
||||
"run_id": "example-run-id",
|
||||
"run_number": "10",
|
||||
"run_attempt": "2",
|
||||
@@ -81,21 +85,22 @@ The following example OIDC token uses a subject (`sub`) that references a job en
|
||||
"event_name": "workflow_dispatch",
|
||||
"ref_type": "branch",
|
||||
"job_workflow_ref": "octo-org/octo-automation/.github/workflows/oidc.yml@refs/heads/main",
|
||||
"iss": "https://token.actions.githubusercontent.com",
|
||||
"iss": "{% ifversion ghes %}https://HOSTNAME/_services/token{% else %}https://token.actions.githubusercontent.com{% endif %}",
|
||||
"nbf": 1632492967,
|
||||
"exp": 1632493867,
|
||||
"iat": 1632493567
|
||||
}
|
||||
```
|
||||
|
||||
To see all the claims supported by {% data variables.product.prodname_dotcom %}'s OIDC provider, review the `claims_supported` entries at https://token.actions.githubusercontent.com/.well-known/openid-configuration.
|
||||
To see all the claims supported by {% data variables.product.prodname_dotcom %}'s OIDC provider, review the `claims_supported` entries at
|
||||
{% ifversion ghes %}`https://HOSTNAME/_services/token/.well-known/openid-configuration`{% else %}https://token.actions.githubusercontent.com/.well-known/openid-configuration{% endif %}.
|
||||
|
||||
The token includes the standard audience, issuer, and subject claims:
|
||||
|
||||
| Claim | Description |
|
||||
| ----------- | ---------------------- |
|
||||
| `aud`| _(Audience)_ By default, this is the URL of the repository owner, such as the organization that owns the repository. This is the only claim that can be customized. You can set a custom audience with a toolkit command: [`core.getIDToken(audience)`](https://www.npmjs.com/package/@actions/core/v/1.6.0) |
|
||||
| `iss`| _(Issuer)_ The issuer of the OIDC token: `https://token.actions.githubusercontent.com` |
|
||||
| `iss`| _(Issuer)_ The issuer of the OIDC token: {% ifversion ghes %}`https://HOSTNAME/_services/token`{% else %}`https://token.actions.githubusercontent.com`{% endif %} |
|
||||
| `sub`| _(Subject)_ Defines the subject claim that is to be validated by the cloud provider. This setting is essential for making sure that access tokens are only allocated in a predictable way.|
|
||||
|
||||
The OIDC token also includes additional standard claims:
|
||||
@@ -115,6 +120,7 @@ The token also includes custom claims provided by {% data variables.product.prod
|
||||
| Claim | Description |
|
||||
| ----------- | ---------------------- |
|
||||
| `actor`| The personal account that initiated the workflow run. |
|
||||
| `actor_id`| The ID of personal account that initiated the workflow run. |
|
||||
| `base_ref`| The target branch of the pull request in a workflow run. |
|
||||
| `environment`| The name of the environment used by the job. |
|
||||
| `event_name`| The name of the event that triggered the workflow run. |
|
||||
@@ -123,7 +129,9 @@ The token also includes custom claims provided by {% data variables.product.prod
|
||||
| `ref`| _(Reference)_ The git ref that triggered the workflow run. |
|
||||
| `ref_type`| The type of `ref`, for example: "branch". |
|
||||
| `repository`| The repository from where the workflow is running. |
|
||||
| `repository_id`| The ID of the repository from where the workflow is running. |
|
||||
| `repository_owner`| The name of the organization in which the `repository` is stored. |
|
||||
| `repository_owner_id`| The ID of the organization in which the `repository` is stored. |
|
||||
| `run_id`| The ID of the workflow run that triggered the workflow. |
|
||||
| `run_number`| The number of times this workflow has been run. |
|
||||
| `run_attempt`| The number of times this workflow run has been retried. |
|
||||
@@ -201,7 +209,7 @@ To configure the subject in your cloud provider's trust relationship, you must a
|
||||
|
||||
| | |
|
||||
| ------ | ----------- |
|
||||
| Amazon Web Services | `"token.actions.githubusercontent.com:sub": "repo:octo-org/octo-repo:ref:refs/heads/demo-branch"` |
|
||||
| Amazon Web Services | `"{% ifversion ghes %}HOSTNAME/_services/token{% else %}token.actions.githubusercontent.com{% endif %}:sub": "repo:octo-org/octo-repo:ref:refs/heads/demo-branch"` |
|
||||
| Azure| `repo:octo-org/octo-repo:ref:refs/heads/demo-branch` |
|
||||
| Google Cloud Platform| `(assertion.sub=='repo:octo-org/octo-repo:ref:refs/heads/demo-branch')` |
|
||||
| HashiCorp Vault| `bound_subject="repo:octo-org/octo-repo:ref:refs/heads/demo-branch" ` |
|
||||
|
||||
@@ -7,6 +7,7 @@ versions:
|
||||
fpt: '*'
|
||||
ghae: issue-4856
|
||||
ghec: '*'
|
||||
ghes: '>=3.5'
|
||||
type: tutorial
|
||||
topics:
|
||||
- Security
|
||||
@@ -31,7 +32,7 @@ This guide explains how to configure AWS to trust {% data variables.product.prod
|
||||
|
||||
To add the {% data variables.product.prodname_dotcom %} OIDC provider to IAM, see the [AWS documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html).
|
||||
|
||||
- For the provider URL: Use `https://token.actions.githubusercontent.com`
|
||||
- For the provider URL: Use {% ifversion ghes %}`https://HOSTNAME/_services/token`{% else %}`https://token.actions.githubusercontent.com`{% endif %}
|
||||
- For the "Audience": Use `sts.amazonaws.com` if you are using the [official action](https://github.com/aws-actions/configure-aws-credentials).
|
||||
|
||||
### Configuring the role and trust policy
|
||||
@@ -43,8 +44,8 @@ Edit the trust relationship to add the `sub` field to the validation conditions.
|
||||
```json{:copy}
|
||||
"Condition": {
|
||||
"ForAllValues:StringEquals": {
|
||||
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
|
||||
"token.actions.githubusercontent.com:sub": "repo:octo-org/octo-repo:ref:refs/heads/octo-branch"
|
||||
"{% ifversion ghes %}HOSTNAME/_services/token{% else %}token.actions.githubusercontent.com{% endif %}:aud": "sts.amazonaws.com",
|
||||
"{% ifversion ghes %}HOSTNAME/_services/token{% else %}token.actions.githubusercontent.com{% endif %}:sub": "repo:octo-org/octo-repo:ref:refs/heads/octo-branch"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -7,6 +7,7 @@ versions:
|
||||
fpt: '*'
|
||||
ghae: issue-4856
|
||||
ghec: '*'
|
||||
ghes: '>=3.5'
|
||||
type: tutorial
|
||||
topics:
|
||||
- Security
|
||||
|
||||
@@ -7,6 +7,7 @@ versions:
|
||||
fpt: '*'
|
||||
ghae: issue-4856
|
||||
ghec: '*'
|
||||
ghes: '>=3.5'
|
||||
type: tutorial
|
||||
topics:
|
||||
- Security
|
||||
|
||||
@@ -7,6 +7,7 @@ versions:
|
||||
fpt: '*'
|
||||
ghae: issue-4856
|
||||
ghec: '*'
|
||||
ghes: '>=3.5'
|
||||
type: tutorial
|
||||
topics:
|
||||
- Security
|
||||
@@ -39,7 +40,7 @@ Additional guidance for configuring the identity provider:
|
||||
|
||||
- For security hardening, make sure you've reviewed ["Configuring the OIDC trust with the cloud"](/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#configuring-the-oidc-trust-with-the-cloud). For an example, see ["Configuring the subject in your cloud provider"](/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#configuring-the-subject-in-your-cloud-provider).
|
||||
- For the service account to be available for configuration, it needs to be assigned to the `roles/iam.workloadIdentityUser` role. For more information, see [the GCP documentation](https://cloud.google.com/iam/docs/workload-identity-federation?_ga=2.114275588.-285296507.1634918453#conditions).
|
||||
- The Issuer URL to use: `https://token.actions.githubusercontent.com`
|
||||
- The Issuer URL to use: {% ifversion ghes %}`https://HOSTNAME/_services/token`{% else %}`https://token.actions.githubusercontent.com`{% endif %}
|
||||
|
||||
## Updating your {% data variables.product.prodname_actions %} workflow
|
||||
|
||||
|
||||