From 02dbebbef3390b2fd7f5c05e500bc1fe2021dab1 Mon Sep 17 00:00:00 2001 From: Rachael Sewell Date: Mon, 11 Apr 2022 09:09:03 -0700 Subject: [PATCH] render existing openapi examples (#26405) --- components/lib/get-rest-code-samples.ts | 123 + components/rest/CodeBlock.tsx | 33 +- components/rest/PreviewsRow.tsx | 10 +- components/rest/RestCodeSamples.tsx | 97 +- components/rest/RestHTTPMethod.tsx | 10 - components/rest/RestNotes.tsx | 24 +- components/rest/RestOperation.tsx | 58 +- components/rest/RestOperationHeading.tsx | 6 +- components/rest/RestParameterTable.tsx | 8 +- components/rest/RestPreviewNotice.tsx | 27 +- components/rest/RestReferencePage.tsx | 7 +- components/rest/RestResponse.tsx | 30 - components/rest/RestStatusCodes.tsx | 37 +- components/rest/types.ts | 47 +- lib/rest/index.js | 2 +- lib/rest/static/apps/enabled-for-apps.json | 3114 - lib/rest/static/decorated/api.github.com.json | 118685 +++++++++------ lib/rest/static/decorated/ghes-3.1.json | 94370 +++++++----- lib/rest/static/decorated/ghes-3.2.json | 97278 ++++++------ lib/rest/static/decorated/ghes-3.3.json | 97132 ++++++------ lib/rest/static/decorated/ghes-3.4.json | 102816 +++++++------ lib/rest/static/decorated/github.ae.json | 89696 ++++++----- package-lock.json | 133 +- package.json | 6 +- script/rest/update-files.js | 26 +- script/rest/utils/create-code-samples.js | 186 - script/rest/utils/create-rest-examples.js | 331 + script/rest/utils/get-operations.js | 5 +- script/rest/utils/operation-schema.js | 73 +- script/rest/utils/operation.js | 270 +- tests/rendering/rest.js | 6 + tests/unit/openapi-schema.js | 151 +- 32 files changed, 340459 insertions(+), 264338 deletions(-) create mode 100644 components/lib/get-rest-code-samples.ts delete mode 100644 components/rest/RestHTTPMethod.tsx delete mode 100644 components/rest/RestResponse.tsx delete mode 100644 script/rest/utils/create-code-samples.js create mode 100644 script/rest/utils/create-rest-examples.js diff --git a/components/lib/get-rest-code-samples.ts b/components/lib/get-rest-code-samples.ts new file mode 100644 index 0000000000..a7a72b178b --- /dev/null +++ b/components/lib/get-rest-code-samples.ts @@ -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)})` +} diff --git a/components/rest/CodeBlock.tsx b/components/rest/CodeBlock.tsx index 40c9ce137b..709b1941a0 100644 --- a/components/rest/CodeBlock.tsx +++ b/components/rest/CodeBlock.tsx @@ -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 } @@ -20,20 +18,12 @@ export function CodeBlock({ verb, headingLang, codeBlock, highlight }: Props) { }) return ( -
+
+ {/* Only Code samples should have a copy icon + If there's a headingLang it's a code sample */} {headingLang && (
- {headingLang === 'JavaScript' ? ( - - {headingLang} ( - - @octokit/core.js - - ) - - ) : ( - `${headingLang}` - )} + {headingLang}