Skip to content

Commit

Permalink
Update server url based on variable selection
Browse files Browse the repository at this point in the history
  • Loading branch information
BrettJephson committed Sep 5, 2024
1 parent d056253 commit c84cf3d
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export default async function Page(props: {
// Display the page feedback in the page footer if the aside is not visible
withPageFeedback && !page.layout.outline
}
searchParams={searchParams}
/>
{page.layout.outline ? (
<PageAside
Expand Down
2 changes: 2 additions & 0 deletions packages/gitbook/src/components/DocumentView/DocumentView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export interface DocumentContext {
* https://linear.app/gitbook-x/issue/RND-3588/gitbook-open-code-syntax-highlighting-runs-out-of-memory-after-a
*/
shouldHighlightCode: (spaceId: string | undefined) => boolean;

searchParams?: Record<string, string>;
}

export interface DocumentContextProps {
Expand Down
34 changes: 32 additions & 2 deletions packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DocumentBlockSwagger } from '@gitbook/api';
import { Icon } from '@gitbook/icons';
import { OpenAPIOperation } from '@gitbook/react-openapi';
import { OpenAPIOperation, OpenAPIOperationData } from '@gitbook/react-openapi';
import React from 'react';

import { LoadingPane } from '@/components/primitives';
Expand Down Expand Up @@ -34,8 +34,8 @@ export async function OpenAPI(props: BlockProps<DocumentBlockSwagger>) {

async function OpenAPIBody(props: BlockProps<DocumentBlockSwagger>) {
const { block, context } = props;
const { data, specUrl, error } = await fetchOpenAPIBlock(block, context.resolveContentRef);

const { data, specUrl, error } = await fetchOpenAPIBlock(block, context.resolveContentRef);
if (error) {
return (
<div className={tcls('hidden')}>
Expand All @@ -50,6 +50,10 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockSwagger>) {
return null;
}

const enumSelectors =
context.searchParams && context.searchParams.block === block.key
? parseModifiers(data, context.searchParams)
: undefined;
return (
<OpenAPIOperation
data={data}
Expand All @@ -61,6 +65,8 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockSwagger>) {
CodeBlock: PlainCodeBlock,
defaultInteractiveOpened: context.mode === 'print',
specUrl,
enumSelectors,
blockKey: block.key,
}}
className="openapi-block"
/>
Expand Down Expand Up @@ -95,3 +101,27 @@ function OpenAPIFallback() {
</div>
);
}

function parseModifiers(data: OpenAPIOperationData, params: Record<string, string>) {
if (!data) {
return;
}
const { servers } = params;
const serverIndex =
servers && !isNaN(Number(servers))
? Math.min(0, Math.max(Number(servers), servers.length - 1))
: 0;
const server = data.servers[serverIndex];
if (server && server.variables) {
return Object.keys(server.variables).reduce<Record<string, number>>(
(result, key) => {
const selection = Number(params[key]);
if (!isNaN(selection)) {
result[key] = selection;
}
return result;
},
{ servers: serverIndex },
);
}
}
4 changes: 3 additions & 1 deletion packages/gitbook/src/components/PageBody/PageBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export function PageBody(props: {
document: JSONDocument | null;
context: ContentRefContext;
withPageFeedback: boolean;
searchParams: Record<string, string>;
}) {
const {
space,
Expand All @@ -43,6 +44,7 @@ export function PageBody(props: {
page,
document,
withPageFeedback,
searchParams,
} = props;

const asFullWidth = document ? hasFullWidthBlock(document) : false;
Expand All @@ -53,7 +55,6 @@ export function PageBody(props: {
'siteId' in contentPointer
? { organizationId: contentPointer.organizationId, siteId: contentPointer.siteId }
: undefined;

return (
<>
<main
Expand Down Expand Up @@ -95,6 +96,7 @@ export function PageBody(props: {
resolveContentRef: (ref, options) =>
resolveContentRef(ref, context, options),
shouldHighlightCode,
searchParams,
}}
/>
) : (
Expand Down
2 changes: 1 addition & 1 deletion packages/react-openapi/src/OpenAPICodeSample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function OpenAPICodeSample(props: {
const requestBodyContent = requestBody ? Object.entries(requestBody.content)[0] : undefined;

const input: CodeSampleInput = {
url: getServersURL(data.servers) + data.path,
url: getServersURL(data.servers, context.enumSelectors) + data.path,
method: data.method,
body: requestBodyContent
? generateMediaTypeExample(requestBodyContent[1], { onlyRequired: true })
Expand Down
25 changes: 21 additions & 4 deletions packages/react-openapi/src/OpenAPIOperation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { OpenAPIOperationData, toJSON } from './fetchOpenAPIOperation';
import { Markdown } from './Markdown';
import { OpenAPICodeSample } from './OpenAPICodeSample';
import { OpenAPIResponseExample } from './OpenAPIResponseExample';
import { OpenAPIServerURL } from './OpenAPIServerURL';
import { getServersURL, OpenAPIServerURL } from './OpenAPIServerURL';
import { OpenAPISpec } from './OpenAPISpec';
import { OpenAPIClientContext, OpenAPIContextProps } from './types';
import { ApiClientModalProvider } from '@scalar/api-client-react';

/**
* Display an interactive OpenAPI operation.
*/
export function OpenAPIOperation(props: {
export async function OpenAPIOperation(props: {
className?: string;
data: OpenAPIOperationData;
context: OpenAPIContextProps;
Expand All @@ -25,11 +25,14 @@ export function OpenAPIOperation(props: {
defaultInteractiveOpened: context.defaultInteractiveOpened,
specUrl: context.specUrl,
icons: context.icons,
blockKey: context.blockKey,
enumSelectors: context.enumSelectors,
};

const config = await getConfiguration(context);
return (
<ApiClientModalProvider
configuration={{ spec: { url: context.specUrl } }}
configuration={config}
initialRequest={{ path: data.path, method: data.method }}
>
<div className={classNames('openapi-operation', className)}>
Expand All @@ -48,7 +51,7 @@ export function OpenAPIOperation(props: {
{method.toUpperCase()}
</span>
<span className="openapi-url">
<OpenAPIServerURL servers={servers} />
<OpenAPIServerURL servers={servers} context={clientContext} />
{path}
</span>
</div>
Expand All @@ -68,3 +71,17 @@ export function OpenAPIOperation(props: {
</ApiClientModalProvider>
);
}

async function getConfiguration(context: OpenAPIContextProps) {
const response = await fetch(context.specUrl);
const doc = await response.json();

return {
spec: {
content: {
...doc,
servers: [{ url: getServersURL(doc.servers, context.enumSelectors) }],
},
},
};
}
27 changes: 19 additions & 8 deletions packages/react-openapi/src/OpenAPIServerURL.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import * as React from 'react';
import { OpenAPIV3 } from 'openapi-types';
import { OpenAPIServerURLVariable } from './OpenAPIServerURLVariable';
import { OpenAPIClientContext } from './types';
import { ServerURLForm } from './OpenAPIServerURLForm';

/**
* Show the url of the server with variables replaced by their default values.
*/
export function OpenAPIServerURL(props: { servers: OpenAPIV3.ServerObject[] }) {
const { servers } = props;
const server = servers[0];

export function OpenAPIServerURL(props: {
servers: OpenAPIV3.ServerObject[];
context: OpenAPIClientContext;
}) {
const { servers, context } = props;
const serverIndex = context.enumSelectors?.servers ?? 0;
const server = servers[serverIndex];
const parts = parseServerURL(server?.url ?? '');

return (
<span>
<ServerURLForm context={context} server={server}>
{parts.map((part, i) => {
if (part.kind === 'text') {
return <span key={i}>{part.text}</span>;
Expand All @@ -26,18 +31,22 @@ export function OpenAPIServerURL(props: { servers: OpenAPIV3.ServerObject[] }) {
key={i}
name={part.name}
variable={server.variables[part.name]}
enumIndex={context.enumSelectors?.[part.name]}
/>
);
}
})}
</span>
</ServerURLForm>
);
}

/**
* Get the default URL for the server.
*/
export function getServersURL(servers: OpenAPIV3.ServerObject[]): string {
export function getServersURL(
servers: OpenAPIV3.ServerObject[],
selectors?: Record<string, number>,
): string {
const server = servers[0];
const parts = parseServerURL(server?.url ?? '');

Expand All @@ -46,7 +55,9 @@ export function getServersURL(servers: OpenAPIV3.ServerObject[]): string {
if (part.kind === 'text') {
return part.text;
} else {
return server.variables?.[part.name]?.default ?? `{${part.name}}`;
return selectors && !isNaN(selectors[part.name])
? server.variables?.[part.name]?.enum?.[selectors[part.name]]
: (server.variables?.[part.name]?.default ?? `{${part.name}}`);
}
})
.join('');
Expand Down
43 changes: 43 additions & 0 deletions packages/react-openapi/src/OpenAPIServerURLForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use client';

import * as React from 'react';
import { useRouter } from 'next/navigation';
import { OpenAPIClientContext } from './types';
import { OpenAPIV3 } from 'openapi-types';
import { useApiClientModal } from '@scalar/api-client-react';

export function ServerURLForm(props: {
children: React.ReactNode;
context: OpenAPIClientContext;
server: OpenAPIV3.ServerObject;
}) {
const { children, context, server } = props;
const router = useRouter();
const client = useApiClientModal();
const [isPending, startTransition] = React.useTransition();

function updateServerUrl(formData: FormData) {
startTransition(() => {
if (!server.variables) {
return;
}
let params = new URLSearchParams(`block=${context.blockKey}`);
const variableKeys = Object.keys(server.variables);
for (const pair of formData.entries()) {
if (variableKeys.includes(pair[0]) && !isNaN(Number(pair[1]))) {
params.set(pair[0], `${pair[1]}`);
}
}
router.push(`?${params}`, { scroll: false });
});
}

return (
<form action={updateServerUrl} className="contents">
<fieldset disabled={isPending} className="contents">
<input type="hidden" name="block" value={context.blockKey} />
<span>{children}</span>
</fieldset>
</form>
);
}
65 changes: 46 additions & 19 deletions packages/react-openapi/src/OpenAPIServerURLVariable.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,63 @@
'use client';

import * as React from 'react';
import { useRouter } from 'next/navigation';
import classNames from 'classnames';
import { OpenAPIV3 } from 'openapi-types';
import { OpenAPIClientContext } from './types';

/**
* Interactive component to show the value of a server variable and let the user change it.
*/
export function OpenAPIServerURLVariable(props: {
name: string;
variable: OpenAPIV3.ServerVariableObject;
enumIndex?: number;
}) {
const { variable } = props;
const { enumIndex, name, variable } = props;

if (variable.enum && variable.enum.length > 0) {
return (<select
className={classNames(
'openapi-section-select',
'openapi-select',
)}
value={variable.default}
>
{
variable.enum?.map((value: string) => {
return (
<option key={value} value={value}>
{value}
</option>
);
}) ?? null}
</select>);

return (
<EnumSelect
name={name}
variable={variable}
value={
!isNaN(Number(enumIndex))
? enumIndex
: variable.enum.findIndex((v) => v === variable.default)
}
/>
);
}

return <span className={classNames('openapi-url-var')}>{variable.default}</span>;
}

/**
* Render a select if there is an enum for a Server URL variable
*/
function EnumSelect(props: {
value?: number;
name: string;
variable: OpenAPIV3.ServerVariableObject;
}) {
const { value, name, variable } = props;
return (
<select
name={name}
onChange={(e) => {
e.preventDefault();
e.currentTarget.form?.requestSubmit();
}}
className={classNames('openapi-select')}
value={value}
>
{variable.enum?.map((value: string, index: number) => {
return (
<option key={value} value={index}>
{value}
</option>
);
}) ?? null}
</select>
);
}
4 changes: 4 additions & 0 deletions packages/react-openapi/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export interface OpenAPIClientContext {
* @default false
*/
defaultInteractiveOpened?: boolean;

blockKey?: string;

enumSelectors?: Record<string, number>;
}

export interface OpenAPIFetcher {
Expand Down

0 comments on commit c84cf3d

Please sign in to comment.