Skip to content

Commit

Permalink
Merge branch 'main' into sophietheking-personalaccount
Browse files Browse the repository at this point in the history
  • Loading branch information
sophietheking authored Apr 11, 2022
2 parents eb4bb77 + 16a0488 commit aacdaad
Show file tree
Hide file tree
Showing 110 changed files with 317,916 additions and 241,636 deletions.
123 changes: 123 additions & 0 deletions components/lib/get-rest-code-samples.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { parseTemplate } from 'url-template'
import { stringify } from 'javascript-stringify'

import type { CodeSample, Operation } from '../rest/types'

/*
Generates a curl example
For example:
curl \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
https://{hostname}/api/v3/repos/OWNER/REPO/deployments \
-d '{"ref":"topic-branch","payload":"{ \"deploy\": \"migrate\" }","description":"Deploy request from hubot"}'
*/
export function getShellExample(operation: Operation, codeSample: CodeSample) {
// This allows us to display custom media types like application/sarif+json
const defaultAcceptHeader = codeSample?.response?.contentType?.includes('+json')
? codeSample.response.contentType
: 'application/vnd.github.v3+json'

const requestPath = codeSample?.request?.parameters
? parseTemplate(operation.requestPath).expand(codeSample.request.parameters)
: operation.requestPath

let requestBodyParams = ''
if (codeSample?.request?.bodyParameters) {
requestBodyParams = `-d '${JSON.stringify(codeSample.request.bodyParameters)}'`

// If the content type is application/x-www-form-urlencoded the format of
// the shell example is --data-urlencode param1=value1 --data-urlencode param2=value2
// For example, this operation:
// https://docs.github.com/en/enterprise/rest/reference/enterprise-admin#enable-or-disable-maintenance-mode
if (codeSample.request.contentType === 'application/x-www-form-urlencoded') {
requestBodyParams = ''
const paramNames = Object.keys(codeSample.request.bodyParameters)
paramNames.forEach((elem) => {
requestBodyParams = `${requestBodyParams} --data-urlencode ${elem}=${codeSample.request.bodyParameters[elem]}`
})
}
}

const args = [
operation.verb !== 'get' && `-X ${operation.verb.toUpperCase()}`,
`-H "Accept: ${defaultAcceptHeader}"`,
`${operation.serverUrl}${requestPath}`,
requestBodyParams,
].filter(Boolean)
return `curl \\\n ${args.join(' \\\n ')}`
}

/*
Generates a GitHub CLI example
For example:
gh api \
-X POST \
-H "Accept: application/vnd.github.v3+json" \
/repos/OWNER/REPO/deployments \
-fref,topic-branch=0,payload,{ "deploy": "migrate" }=1,description,Deploy request from hubot=2
*/
export function getGHExample(operation: Operation, codeSample: CodeSample) {
const defaultAcceptHeader = codeSample?.response?.contentType?.includes('+json')
? codeSample.response.contentType
: 'application/vnd.github.v3+json'
const hostname = operation.serverUrl !== 'https://api.github.com' ? '--hostname HOSTNAME' : ''

const requestPath = codeSample?.request?.parameters
? parseTemplate(operation.requestPath).expand(codeSample.request.parameters)
: operation.requestPath

let requestBodyParams = ''
if (codeSample?.request?.bodyParameters) {
const bodyParamValues = Object.values(codeSample.request.bodyParameters)
// GitHub CLI does not support sending Objects and arrays using the -F or
// -f flags. That support may be added in the future. It is possible to
// use gh api --input to take a JSON object from standard input
// constructed by jq and piped to gh api. However, we'll hold off on adding
// that complexity for now.
if (bodyParamValues.some((elem) => typeof elem === 'object')) {
return undefined
}
requestBodyParams = Object.keys(codeSample.request.bodyParameters)
.map((key) => {
if (typeof codeSample.request.bodyParameters[key] === 'string') {
return `-f ${key}='${codeSample.request.bodyParameters[key]}'`
} else {
return `-F ${key}=${codeSample.request.bodyParameters[key]}`
}
})
.join(' ')
}
const args = [
operation.verb !== 'get' && `--method ${operation.verb.toUpperCase()}`,
`-H "Accept: ${defaultAcceptHeader}"`,
hostname,
requestPath,
requestBodyParams,
].filter(Boolean)
return `gh api \\\n ${args.join(' \\\n ')}`
}

/*
Generates an octokit.js example
For example:
await octokit.request('POST /repos/{owner}/{repo}/deployments'{
"owner": "OWNER",
"repo": "REPO",
"ref": "topic-branch",
"payload": "{ \"deploy\": \"migrate\" }",
"description": "Deploy request from hubot"
})
*/
export function getJSExample(operation: Operation, codeSample: CodeSample) {
const parameters = codeSample.request
? { ...codeSample.request.parameters, ...codeSample.request.bodyParameters }
: {}
return `await octokit.request('${operation.verb.toUpperCase()} ${
operation.requestPath
}', ${stringify(parameters, null, 2)})`
}
33 changes: 13 additions & 20 deletions components/rest/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
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
// Only Code samples should have a copy icon - if there's a headingLang it's a code sample
headingLang?: string
headingLang?: ReactNode | string
codeBlock: string
highlight?: string
}
Expand All @@ -20,20 +18,12 @@ export function CodeBlock({ verb, headingLang, codeBlock, highlight }: Props) {
})

return (
<div className={headingLang && 'code-extra'}>
<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 === 'JavaScript' ? (
<span>
{headingLang} (
<a className="text-underline" href="https://github.com/octokit/core.js#readme">
@octokit/core.js
</a>
)
</span>
) : (
`${headingLang}`
)}
{headingLang}
<Tooltip direction="w" aria-label={isCopied ? 'Copied!' : 'Copy to clipboard'}>
<button className="js-btn-copy btn-octicon" onClick={() => setCopied()}>
{isCopied ? <CheckIcon /> : <CopyIcon />}
Expand All @@ -44,10 +34,13 @@ export function CodeBlock({ verb, headingLang, codeBlock, highlight }: Props) {
<pre 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>
)}{' '}
<>
<span className="color-bg-accent-emphasis color-fg-on-emphasis rounded-1 text-uppercase p-1">
{verb}
</span>
<> </>
</>
)}
{codeBlock}
</code>
</pre>
Expand Down
10 changes: 4 additions & 6 deletions components/rest/PreviewsRow.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { xGitHub } from './types'
import { useTranslation } from 'components/hooks/useTranslation'

type Props = {
slug: string
xGitHub: xGitHub
numPreviews: number
}

export function PreviewsRow({ slug, xGitHub }: Props) {
export function PreviewsRow({ slug, numPreviews }: Props) {
const { t } = useTranslation('products')
const hasPreviews = xGitHub.previews && xGitHub.previews.length > 0

return (
<tr>
Expand All @@ -21,9 +19,9 @@ export function PreviewsRow({ slug, xGitHub }: Props) {
<p className="m-0">
Setting to
<code>application/vnd.github.v3+json</code> is recommended.
{hasPreviews && (
{numPreviews > 0 && (
<a href={`#${slug}-preview-notices`} className="d-inline">
{xGitHub.previews.length > 1
{numPreviews > 1
? ` ${t('rest.reference.see_preview_notices')}`
: ` ${t('rest.reference.see_preview_notice')}`}
</a>
Expand Down
97 changes: 77 additions & 20 deletions components/rest/RestCodeSamples.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,92 @@
import type { xCodeSample } from './types'
import type { Operation } from './types'
import { useTranslation } from 'components/hooks/useTranslation'
import { CodeBlock } from './CodeBlock'
import { Fragment } from 'react'
import { getShellExample, getGHExample, getJSExample } from '../lib/get-rest-code-samples'

type Props = {
slug: string
xCodeSamples: Array<xCodeSample>
operation: Operation
}

export function RestCodeSamples({ slug, xCodeSamples }: Props) {
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>
)

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 = {
curl: getShellExample(operation, sample),
javascript: getJSExample(operation, sample),
ghcli: getGHExample(operation, sample),
}
return Object.assign({}, sample, languageExamples)
})

return (
<Fragment key={xCodeSamples + slug}>
<>
<h4 id={`${slug}--code-samples`}>
<a href={`#${slug}--code-samples`}>{`${t('rest.reference.code_samples')}`}</a>
</h4>
{xCodeSamples.map((sample, index) => {
const sampleElements: JSX.Element[] = []
if (sample.lang !== 'Ruby') {
sampleElements.push(
<CodeBlock
key={sample.lang + index}
headingLang={sample.lang}
codeBlock={sample.source}
highlight={sample.lang === 'JavaScript' ? 'javascript' : 'curl'}
></CodeBlock>
)
}
return sampleElements
})}
</Fragment>
{languageExamples.map((sample, index) => (
<div key={`${JSON.stringify(sample)}-${index}`}>
{/* 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" />
)}
</>
)}

{/* Title of the response */}
{sample.response && (
<>
<h5 dangerouslySetInnerHTML={{ __html: sample.response.description }} />
{/* Status code */}
{sample.response.statusCode && (
<CodeBlock codeBlock={`Status: ${sample.response.statusCode}`} />
)}

{/* Example response */}
{sample.response.example && (
<CodeBlock
codeBlock={JSON.stringify(sample.response.example, null, 2)}
highlight="json"
/>
)}
</>
)}
</div>
))}
</>
)
}
10 changes: 0 additions & 10 deletions components/rest/RestHTTPMethod.tsx

This file was deleted.

24 changes: 10 additions & 14 deletions components/rest/RestNotes.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
import { useTranslation } from 'components/hooks/useTranslation'
import { useRouter } from 'next/router'

type Props = {
notes: Array<string>
enabledForGitHubApps: boolean
}
import { useTranslation } from 'components/hooks/useTranslation'
import { Link } from 'components/Link'

export function RestNotes({ notes, enabledForGitHubApps }: Props) {
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">
{enabledForGitHubApps && (
<li>
<a href="/developers/apps">Works with GitHub Apps</a>
</li>
)}
{notes.map((note: string) => {
return <li>{note}</li>
})}
<li>
<Link href={`/${router.locale}/developers/apps`}>
{t('rest.reference.works_with_github_apps')}
</Link>
</li>
</ul>
</>
)
Expand Down
Loading

0 comments on commit aacdaad

Please sign in to comment.