Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RND-2311: enums for server url in openapi block #2452

Closed
wants to merge 66 commits into from
Closed
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
4d476a8
Select variables in server url
BrettJephson Aug 27, 2024
7e79e14
Update server url based on variable selection
BrettJephson Sep 3, 2024
e852206
Update server choice based on selection
BrettJephson Sep 12, 2024
cb454a3
Format
BrettJephson Sep 12, 2024
ca4518d
Changeset
BrettJephson Sep 12, 2024
2d15453
Revised client state with context
BrettJephson Sep 20, 2024
d919d2c
Add icons for editing url
BrettJephson Sep 20, 2024
adaf0de
Fix type
BrettJephson Sep 20, 2024
69414bd
Formatting
BrettJephson Sep 20, 2024
3b0f5d8
Add server url cache to update request code sample
BrettJephson Sep 24, 2024
087a783
Formatting
BrettJephson Sep 24, 2024
9573535
Update icon
BrettJephson Sep 24, 2024
e793a17
Select variables in server url
BrettJephson Aug 27, 2024
b87ec84
Update server url based on variable selection
BrettJephson Sep 3, 2024
4f59d18
Update server choice based on selection
BrettJephson Sep 12, 2024
c338bca
Format
BrettJephson Sep 12, 2024
7244a04
Changeset
BrettJephson Sep 12, 2024
d29a7a6
Revised client state with context
BrettJephson Sep 20, 2024
a6adfaa
Add icons for editing url
BrettJephson Sep 20, 2024
3d14790
Fix type
BrettJephson Sep 20, 2024
28c59d8
Formatting
BrettJephson Sep 20, 2024
96d5537
Add server url cache to update request code sample
BrettJephson Sep 24, 2024
768b555
Formatting
BrettJephson Sep 24, 2024
ea4f70e
Update icon
BrettJephson Sep 24, 2024
0b44528
Merge branch 'main' into brett/RND-2311-openapi-enum
BrettJephson Sep 24, 2024
949f25c
Merge branch 'brett/RND-2311-openapi-enum' of github.com:GitbookIO/gi…
BrettJephson Sep 24, 2024
f6b75e7
Self review changes
BrettJephson Sep 24, 2024
4b9f206
Merge branch 'main' into brett/RND-2311-openapi-enum
BrettJephson Sep 25, 2024
4026fc1
Merge branch 'main' into brett/RND-2311-openapi-enum
BrettJephson Sep 25, 2024
8d1c349
Don't make server url editable if no update function or context
BrettJephson Sep 25, 2024
5b93377
Merge remote-tracking branch 'origin/main' into brett/RND-2311-openap…
BrettJephson Oct 1, 2024
ce890c6
Refactor - naming and comments
BrettJephson Oct 2, 2024
cb3c1f9
Formatting
BrettJephson Oct 2, 2024
abf7ec3
Remove unused imports
BrettJephson Oct 2, 2024
f98b7b2
Merge branch 'main' into brett/RND-2311-openapi-enum
BrettJephson Oct 2, 2024
39d8886
Formatting
BrettJephson Oct 2, 2024
586e66d
Update comment
BrettJephson Oct 2, 2024
60a9b95
Merge branch 'main' into brett/RND-2311-openapi-enum
BrettJephson Oct 3, 2024
c1193fd
Select variables in server url
BrettJephson Aug 27, 2024
1ae1715
Update server url based on variable selection
BrettJephson Sep 3, 2024
77d64d0
Update server choice based on selection
BrettJephson Sep 12, 2024
9437aa2
Format
BrettJephson Sep 12, 2024
f6823e9
Changeset
BrettJephson Sep 12, 2024
ac492e0
Revised client state with context
BrettJephson Sep 20, 2024
f65e3f5
Add icons for editing url
BrettJephson Sep 20, 2024
f67b143
Fix type
BrettJephson Sep 20, 2024
9b78692
Formatting
BrettJephson Sep 20, 2024
184d616
Add server url cache to update request code sample
BrettJephson Sep 24, 2024
74392ec
Formatting
BrettJephson Sep 24, 2024
7b02f72
Update icon
BrettJephson Sep 24, 2024
dce39c4
Update server url based on variable selection
BrettJephson Sep 3, 2024
e866080
Update server choice based on selection
BrettJephson Sep 12, 2024
662df6f
Format
BrettJephson Sep 12, 2024
65d7729
Revised client state with context
BrettJephson Sep 20, 2024
2d4ddc9
Formatting
BrettJephson Sep 20, 2024
c7ca921
Self review changes
BrettJephson Sep 24, 2024
9675290
Don't make server url editable if no update function or context
BrettJephson Sep 25, 2024
95e8772
Refactor - naming and comments
BrettJephson Oct 2, 2024
0ccc503
Formatting
BrettJephson Oct 2, 2024
3ee149d
Remove unused imports
BrettJephson Oct 2, 2024
152aad2
Update comment
BrettJephson Oct 2, 2024
d19e350
Merge branch 'brett/RND-2311-openapi-enum' of github.com:GitbookIO/gi…
BrettJephson Oct 9, 2024
5beb2db
Tidy
BrettJephson Oct 9, 2024
3d9aefa
Merge remote-tracking branch 'origin/main' into brett/RND-2311-openap…
BrettJephson Oct 14, 2024
846de0e
Merge branch 'main' into brett/RND-2311-openapi-enum
BrettJephson Oct 18, 2024
d9ee1a7
Merge branch 'main' into brett/RND-2311-openapi-enum
BrettJephson Oct 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/long-shrimps-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@gitbook/react-openapi': minor
'gitbook': minor
---

Allow selection of server url
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Metadata, Viewport } from 'next';
import { notFound, redirect } from 'next/navigation';
import React from 'react';

import { serverUrlCache } from '@/components/DocumentView/OpenAPI/ServerUrlCache';
import { PageAside } from '@/components/PageAside';
import { PageBody, PageCover } from '@/components/PageBody';
import { PageHrefContext, absoluteHref, pageHref } from '@/lib/links';
Expand All @@ -26,6 +27,8 @@ export default async function Page(props: {
}) {
const { params, searchParams } = props;

serverUrlCache.parse(searchParams);

const {
content: contentPointer,
contentTarget,
Expand Down
37 changes: 23 additions & 14 deletions packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { LoadingPane } from '@/components/primitives';
import { fetchOpenAPIBlock } from '@/lib/openapi';
import { tcls } from '@/lib/tailwind';

import OpenAPIContext from './OpenAPIContext';
import { serverUrlCache } from './ServerUrlCache';
import { BlockProps } from '../Block';
import { PlainCodeBlock } from '../CodeBlock';

Expand Down Expand Up @@ -45,21 +47,28 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockSwagger>) {
return null;
}

const serverUrl = serverUrlCache.get('serverUrl');

return (
<OpenAPIOperation
data={data}
context={{
icons: {
chevronDown: <Icon icon="chevron-down" />,
chevronRight: <Icon icon="chevron-right" />,
},
CodeBlock: PlainCodeBlock,
defaultInteractiveOpened: context.mode === 'print',
id: block.meta?.id,
blockKey: block.key,
}}
className="openapi-block"
/>
<OpenAPIContext block={block} data={data}>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we not supposed to have a onUpdate here?

Copy link
Contributor Author

@BrettJephson BrettJephson Oct 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This component is a wrapper around the actual context provider (which has the onUpdate) so I could keep it as a client component without polluting the rest of the tree which contains server components. I've renamed it and commented as admittedly wasn't very clear.

<OpenAPIOperation
data={data}
context={{
icons: {
chevronDown: <Icon icon="chevron-down" />,
chevronRight: <Icon icon="chevron-right" />,
edit: <Icon icon="edit" />,
editDone: <Icon icon="check" />,
},
CodeBlock: PlainCodeBlock,
defaultInteractiveOpened: context.mode === 'print',
id: block.meta?.id,
blockKey: block.key,
serverUrl,
}}
className="openapi-block"
/>
</OpenAPIContext>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client';

import { DocumentBlock } from '@gitbook/api';
import { OpenAPIOperationData } from '@gitbook/react-openapi';
import { OpenAPIContextProvider } from '@gitbook/react-openapi/client';
import { useRouter, useSearchParams } from 'next/navigation';
import * as React from 'react';

export default function OpenAPIContext(props: {
children: React.ReactNode;
block: DocumentBlock;
data: OpenAPIOperationData;
}) {
const { block, children, data } = props;
const [isPending, startTransition] = React.useTransition();
const router = useRouter();
const searchParams = useSearchParams();

return (
<OpenAPIContextProvider
isPending={isPending}
data={data}
params={
searchParams.get('block') === block.key
? Object.fromEntries(searchParams.entries())
: undefined
}
onUpdate={async (params) => {
startTransition(() => {
const queryParams = new URLSearchParams(params ?? '');
router.replace(`?${queryParams}`, { scroll: false });
});
}}
>
{children}
</OpenAPIContextProvider>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createSearchParamsCache, parseAsString } from 'nuqs/server';

export const serverUrlCache = createSearchParamsCache({
serverUrl: parseAsString,
});
19 changes: 18 additions & 1 deletion packages/gitbook/src/components/DocumentView/OpenAPI/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
/** URL */

.openapi-url {
@apply font-mono text-sm text-dark/8 dark:text-light/8;
@apply flex items-center font-mono text-sm text-dark/8 dark:text-light/8;
}

.openapi-url-var {
Expand Down Expand Up @@ -364,3 +364,20 @@
.openapi-markdown > *:last-child {
@apply mb-0;
}

.openapi-select-button,
.openapi-edit-button {
@apply p-0.5 inline-flex text-dark/6 dark:text-light/6 hover:opacity-8;
}

.openapi-edit-button {
@apply p-0.5 ml-4 size-4;
}

.openapi-edit-button > * {
@apply size-full;
}

.openapi-select-button {
@apply leading-[1cap] disabled:opacity-5;
}
1 change: 0 additions & 1 deletion packages/gitbook/src/components/PageBody/PageBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ export function PageBody(props: {
'siteId' in contentPointer
? { organizationId: contentPointer.organizationId, siteId: contentPointer.siteId }
: undefined;

return (
<>
<main
Expand Down
3 changes: 2 additions & 1 deletion packages/react-openapi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"types": "./dist/index.d.ts",
"development": "./src/index.ts",
"default": "./dist/index.js"
}
},
"./client": "./src/client.ts"
BrettJephson marked this conversation as resolved.
Show resolved Hide resolved
},
"version": "0.6.0",
"dependencies": {
Expand Down
10 changes: 3 additions & 7 deletions packages/react-openapi/src/OpenAPICodeSample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import { CodeSampleInput, codeSampleGenerators } from './code-samples';
import { OpenAPIOperationData, toJSON } from './fetchOpenAPIOperation';
import { generateMediaTypeExample, generateSchemaExample } from './generateSchemaExample';
import { InteractiveSection } from './InteractiveSection';
import { getServersURL } from './OpenAPIServerURL';
import { ScalarApiButton } from './ScalarApiButton';
import { OpenAPIContextProps } from './types';
import { noReference } from './utils';
import { getServersURL, noReference } from './utils';

/**
* Display code samples to execute the operation.
Expand Down Expand Up @@ -49,14 +48,11 @@ export function OpenAPICodeSample(props: {
}
});

const serverUrl = context.serverUrl ?? getServersURL(data.servers);
const requestBody = noReference(data.operation.requestBody);
const requestBodyContent = requestBody ? Object.entries(requestBody.content)[0] : undefined;

const input: CodeSampleInput = {
url:
getServersURL(data.servers) +
data.path +
(searchParams.size ? `?${searchParams.toString()}` : ''),
url: serverUrl + data.path + (searchParams.size ? `?${searchParams.toString()}` : ''),
method: data.method,
body: requestBodyContent
? generateMediaTypeExample(requestBodyContent[1], { onlyRequired: true })
Expand Down
72 changes: 72 additions & 0 deletions packages/react-openapi/src/OpenAPIContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use client';

import * as React from 'react';
import { OpenAPIOperationData } from './fetchOpenAPIOperation';
import { getServersURL } from './utils';

type OpenAPIContextProps = {
isPending?: boolean;
serverUrl?: string;
state?: Record<string, string> | null;
onUpdate: (params: Record<string, string> | null) => void;
};
BrettJephson marked this conversation as resolved.
Show resolved Hide resolved
const OpenAPIContext = React.createContext<OpenAPIContextProps | null>(null);

export function useOpenAPIContext() {
return React.useContext(OpenAPIContext);
}

export function OpenAPIContextProvider(props: {
children: React.ReactNode;
data: OpenAPIOperationData;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing the data here goes against the whole server component approach. This means we'll include the OpenAPI operation data for every element in HTML, resulting in very big HTML output.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've refactored this a bit but could you explain the comment a bit more?

The component is a client component that provides a React context, so I'm not sure how it affects the HTML output. But happy to admit it if I'm wrong as still getting a handle on some of the details of server components.

isPending?: boolean;
params?: Record<string, string>;
onUpdate: OpenAPIContextProps['onUpdate'];
}) {
const { children, data, isPending, params, onUpdate } = props;

const clientState = React.useMemo(() => {
if (!params) {
return null;
}
return parseClientStateModifiers(data, params);
}, [data, params]);
const serverUrl = getServersURL(data.servers, clientState ?? undefined);

return (
<OpenAPIContext.Provider
value={{
isPending,
state: clientState,
serverUrl,
onUpdate,
}}
>
{children}
</OpenAPIContext.Provider>
);
}

function parseClientStateModifiers(data: OpenAPIOperationData, params: Record<string, string>) {
if (!data) {
return null;
}
const serverQueryParam = params['server'];
const serverIndex =
serverQueryParam && !isNaN(Number(serverQueryParam))
? Math.max(0, Math.min(Number(serverQueryParam), data.servers.length - 1))
: 0;
const server = data.servers[serverIndex];
return server
? Object.keys(server.variables ?? {}).reduce<Record<string, string>>(
(result, key) => {
const selection = params[key];
if (!isNaN(Number(selection))) {
result[key] = selection;
}
return result;
},
{ server: `${serverIndex}`, edit: params['edit'] },
)
: null;
}
9 changes: 6 additions & 3 deletions packages/react-openapi/src/OpenAPIOperation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function OpenAPIOperation(props: {
{operation.description ? (
<Markdown className="openapi-description" source={operation.description} />
) : null}
<div className="openapi-target">
<div className="openapi-target flex items-center">
<span
className={classNames(
'openapi-method',
Expand All @@ -47,8 +47,11 @@ export function OpenAPIOperation(props: {
{method.toUpperCase()}
</span>
<span className="openapi-url">
<OpenAPIServerURL servers={servers} />
{path}
<OpenAPIServerURL
servers={servers}
context={clientContext}
path={path}
/>
</span>
</div>
</div>
Expand Down
56 changes: 20 additions & 36 deletions packages/react-openapi/src/OpenAPIServerURL.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
'use client';

import * as React from 'react';
import { OpenAPIV3 } from 'openapi-types';
import { OpenAPIServerURLVariable } from './OpenAPIServerURLVariable';
import { OpenAPIClientContext } from './types';
import { ServerURLForm } from './OpenAPIServerURLForm';
import { useOpenAPIContext } from './OpenAPIContextProvider';
import { parseServerURL } from './utils';

/**
* 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;
path?: string;
}) {
const { path, servers, context } = props;
const ctx = useOpenAPIContext();

const serverIndex = !isNaN(Number(ctx?.state?.server)) ? Number(ctx?.state?.server) : 0;
const server = servers[serverIndex];
const parts = parseServerURL(server?.url ?? '');

return (
<span>
<ServerURLForm context={context} servers={servers} serverIndex={serverIndex}>
{parts.map((part, i) => {
if (part.kind === 'text') {
return <span key={i}>{part.text}</span>;
Expand All @@ -26,41 +38,13 @@ export function OpenAPIServerURL(props: { servers: OpenAPIV3.ServerObject[] }) {
key={i}
name={part.name}
variable={server.variables[part.name]}
selectionIndex={Number(ctx?.state?.[part.name])}
selectable={Boolean(ctx?.state?.edit)}
/>
);
}
})}
</span>
{path}
</ServerURLForm>
);
}

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

return parts
.map((part) => {
if (part.kind === 'text') {
return part.text;
} else {
return server.variables?.[part.name]?.default ?? `{${part.name}}`;
}
})
.join('');
}

function parseServerURL(url: string) {
const parts = url.split(/{([^}]+)}/g);
const result: Array<{ kind: 'variable'; name: string } | { kind: 'text'; text: string }> = [];
for (let i = 0; i < parts.length; i++) {
if (i % 2 === 0) {
result.push({ kind: 'text', text: parts[i] });
} else {
result.push({ kind: 'variable', name: parts[i] });
}
}
return result;
}
Loading
Loading