Skip to content

Commit da9655f

Browse files
committed
v1 webhook block
1 parent e4c7307 commit da9655f

16 files changed

+414
-102
lines changed

packages/gitbook/src/components/DocumentView/Block.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { IntegrationBlock } from './Integration';
2525
import { List } from './List';
2626
import { ListItem } from './ListItem';
2727
import { BlockMath } from './Math';
28-
import { OpenAPIOperation, OpenAPISchemas } from './OpenAPI';
28+
import { OpenAPIOperation, OpenAPISchemas, OpenAPIWebhook } from './OpenAPI';
2929
import { Paragraph } from './Paragraph';
3030
import { Quote } from './Quote';
3131
import { ReusableContent } from './ReusableContent';
@@ -85,6 +85,8 @@ export function Block<T extends DocumentBlock>(props: BlockProps<T>) {
8585
return <OpenAPIOperation {...props} block={block} />;
8686
case 'openapi-schemas':
8787
return <OpenAPISchemas {...props} block={block} />;
88+
case 'openapi-webhook':
89+
return <OpenAPIWebhook {...props} block={block} />;
8890
case 'embed':
8991
return <Embed {...props} block={block} />;
9092
case 'blockquote':
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { OpenAPIWebhook as BaseOpenAPIWebhook } from '@gitbook/react-openapi';
2+
3+
import { resolveOpenAPIWebhookBlock } from '@/lib/openapi/resolveOpenAPIWebhookBlock';
4+
import { tcls } from '@/lib/tailwind';
5+
6+
import type { BlockProps } from '../Block';
7+
8+
import './scalar.css';
9+
import './style.css';
10+
import type { OpenAPIWebhookBlock } from '@/lib/openapi/types';
11+
import { getOpenAPIContext } from './context';
12+
13+
/**
14+
* Render an openapi block or an openapi-webhook block.
15+
*/
16+
export async function OpenAPIWebhook(props: BlockProps<OpenAPIWebhookBlock>) {
17+
const { style } = props;
18+
return (
19+
<div className={tcls('flex w-full min-w-0', style, 'max-w-full')}>
20+
<OpenAPIWebhookBody {...props} />
21+
</div>
22+
);
23+
}
24+
25+
async function OpenAPIWebhookBody(props: BlockProps<OpenAPIWebhookBlock>) {
26+
const { block, context } = props;
27+
28+
if (!context.contentContext) {
29+
return null;
30+
}
31+
32+
const { data, specUrl, error } = await resolveOpenAPIWebhookBlock({
33+
block,
34+
context: context.contentContext,
35+
});
36+
37+
if (error) {
38+
return (
39+
<div className="hidden">
40+
<p>
41+
Error with {specUrl}: {error.message}
42+
</p>
43+
</div>
44+
);
45+
}
46+
47+
if (!data || !specUrl) {
48+
return null;
49+
}
50+
51+
return (
52+
<BaseOpenAPIWebhook
53+
data={data}
54+
context={getOpenAPIContext({ props, specUrl })}
55+
className="openapi-block"
56+
/>
57+
);
58+
}
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './OpenAPIOperation';
22
export * from './OpenAPISchemas';
3+
export * from './OpenAPIWebhook';

packages/gitbook/src/components/DocumentView/OpenAPI/style.css

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* Layout Components */
22
.openapi-operation,
3-
.openapi-schemas {
3+
.openapi-schemas,
4+
.openapi-webhook {
45
@apply flex-1 flex flex-col gap-8 mb-14 min-w-0;
56
}
67

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { fetchOpenAPIFilesystem } from '@/lib/openapi/fetch';
2+
import { OpenAPIParseError } from '@gitbook/openapi-parser';
3+
import { type OpenAPIWebhookData, resolveOpenAPIWebhook } from '@gitbook/react-openapi';
4+
import type {
5+
OpenAPIWebhookBlock,
6+
ResolveOpenAPIBlockArgs,
7+
ResolveOpenAPIBlockResult,
8+
} from './types';
9+
10+
type ResolveOpenAPIWebhookBlockResult = ResolveOpenAPIBlockResult<OpenAPIWebhookData>;
11+
12+
const weakmap = new WeakMap<OpenAPIWebhookBlock, Promise<ResolveOpenAPIWebhookBlockResult>>();
13+
14+
/**
15+
* Cache the result of resolving an OpenAPI block.
16+
* It is important because the resolve is called in sections and in the block itself.
17+
*/
18+
export function resolveOpenAPIWebhookBlock(
19+
args: ResolveOpenAPIBlockArgs<OpenAPIWebhookBlock>
20+
): Promise<ResolveOpenAPIWebhookBlockResult> {
21+
if (weakmap.has(args.block)) {
22+
return weakmap.get(args.block)!;
23+
}
24+
25+
const result = baseResolveOpenAPIWebhookBlock(args);
26+
weakmap.set(args.block, result);
27+
return result;
28+
}
29+
30+
/**
31+
* Resolve OpenAPI webhook block.
32+
*/
33+
async function baseResolveOpenAPIWebhookBlock(
34+
args: ResolveOpenAPIBlockArgs<OpenAPIWebhookBlock>
35+
): Promise<ResolveOpenAPIWebhookBlockResult> {
36+
const { context, block } = args;
37+
if (!block.data.path || !block.data.method) {
38+
return { data: null, specUrl: null };
39+
}
40+
41+
try {
42+
const { filesystem, specUrl } = await fetchOpenAPIFilesystem({ block, context });
43+
44+
if (!filesystem) {
45+
return { data: null, specUrl: null };
46+
}
47+
48+
const data = await resolveOpenAPIWebhook(filesystem, {
49+
name: block.data.name,
50+
method: block.data.method,
51+
});
52+
53+
return { data, specUrl };
54+
} catch (error) {
55+
if (error instanceof OpenAPIParseError) {
56+
return { error };
57+
}
58+
59+
throw error;
60+
}
61+
}

packages/gitbook/src/lib/openapi/types.ts

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
DocumentBlockOpenAPI,
33
DocumentBlockOpenAPIOperation,
44
DocumentBlockOpenAPISchemas,
5+
// DocumentBlockOpenAPIWebhook,
56
} from '@gitbook/api';
67
import type { Filesystem, OpenAPIParseError, OpenAPIV3xDocument } from '@gitbook/openapi-parser';
78
import type { GitBookAnyContext } from '@v2/lib/context';
@@ -16,6 +17,11 @@ export type AnyOpenAPIOperationsBlock = DocumentBlockOpenAPI | DocumentBlockOpen
1617
*/
1718
export type OpenAPISchemasBlock = DocumentBlockOpenAPISchemas;
1819

20+
/**
21+
* Type for OpenAPI Webhook block
22+
*/
23+
export type OpenAPIWebhookBlock = any; //DocumentBlockOpenAPIWebhook
24+
1925
/**
2026
* Arguments for resolving OpenAPI block.
2127
*/
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
11
import clsx from 'clsx';
2-
3-
import type {
4-
OpenAPICustomOperationProperties,
5-
OpenAPIStability,
6-
OpenAPIV3,
7-
} from '@gitbook/openapi-parser';
8-
import { Markdown } from './Markdown';
92
import { OpenAPICodeSample } from './OpenAPICodeSample';
10-
import { OpenAPIPath } from './OpenAPIPath';
113
import { OpenAPIResponseExample } from './OpenAPIResponseExample';
12-
import { OpenAPISpec } from './OpenAPISpec';
13-
import { getOpenAPIClientContext } from './context';
4+
import { OpenAPIColumnSpec } from './common/OpenAPIColumnSpec';
5+
import { OpenAPISummary } from './common/OpenAPISummary';
146
import type { OpenAPIContext, OpenAPIOperationData } from './types';
15-
import { resolveDescription } from './utils';
167

178
/**
189
* Display an interactive OpenAPI operation.
@@ -23,46 +14,12 @@ export function OpenAPIOperation(props: {
2314
context: OpenAPIContext;
2415
}) {
2516
const { className, data, context } = props;
26-
const { operation } = data;
27-
28-
const clientContext = getOpenAPIClientContext(context);
2917

3018
return (
3119
<div className={clsx('openapi-operation', className)}>
32-
<div className="openapi-summary" id={operation.summary ? undefined : context.id}>
33-
{(operation.deprecated || operation['x-stability']) && (
34-
<div className="openapi-summary-tags">
35-
{operation.deprecated && (
36-
<div className="openapi-deprecated">Deprecated</div>
37-
)}
38-
{operation['x-stability'] && (
39-
<OpenAPIOperationStability stability={operation['x-stability']} />
40-
)}
41-
</div>
42-
)}
43-
{operation.summary
44-
? context.renderHeading({
45-
deprecated: operation.deprecated ?? false,
46-
stability: operation['x-stability'],
47-
title: operation.summary,
48-
})
49-
: null}
50-
<OpenAPIPath data={data} />
51-
</div>
20+
<OpenAPISummary data={data} context={context} />
5221
<div className="openapi-columns">
53-
<div className="openapi-column-spec">
54-
{operation['x-deprecated-sunset'] ? (
55-
<div className="openapi-deprecated-sunset openapi-description openapi-markdown">
56-
This operation is deprecated and will be sunset on{' '}
57-
<span className="openapi-deprecated-sunset-date">
58-
{operation['x-deprecated-sunset']}
59-
</span>
60-
{'.'}
61-
</div>
62-
) : null}
63-
<OpenAPIOperationDescription operation={operation} context={context} />
64-
<OpenAPISpec data={data} context={clientContext} />
65-
</div>
22+
<OpenAPIColumnSpec data={data} context={context} />
6623
<div className="openapi-column-preview">
6724
<div className="openapi-column-preview-body">
6825
<OpenAPICodeSample {...props} />
@@ -73,52 +30,3 @@ export function OpenAPIOperation(props: {
7330
</div>
7431
);
7532
}
76-
77-
function OpenAPIOperationDescription(props: {
78-
operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
79-
context: OpenAPIContext;
80-
}) {
81-
const { operation } = props;
82-
if (operation['x-gitbook-description-document']) {
83-
return (
84-
<div className="openapi-intro">
85-
{props.context.renderDocument({
86-
document: operation['x-gitbook-description-document'],
87-
})}
88-
</div>
89-
);
90-
}
91-
92-
const description = resolveDescription(operation);
93-
if (!description) {
94-
return null;
95-
}
96-
97-
return (
98-
<div className="openapi-intro">
99-
<Markdown className="openapi-description" source={description} />
100-
</div>
101-
);
102-
}
103-
104-
const stabilityEnum = {
105-
experimental: 'Experimental',
106-
alpha: 'Alpha',
107-
beta: 'Beta',
108-
} as const;
109-
110-
function OpenAPIOperationStability(props: { stability: OpenAPIStability }) {
111-
const { stability } = props;
112-
113-
const foundStability = stabilityEnum[stability];
114-
115-
if (!foundStability) {
116-
return null;
117-
}
118-
119-
return (
120-
<div className={`openapi-stability openapi-stability-${foundStability.toLowerCase()}`}>
121-
{foundStability}
122-
</div>
123-
);
124-
}

packages/react-openapi/src/OpenAPISpec.tsx

+8-3
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,22 @@ import { OpenAPIResponses } from './OpenAPIResponses';
55
import { OpenAPISchemaProperties } from './OpenAPISchemaServer';
66
import { OpenAPISecurities } from './OpenAPISecurities';
77
import { StaticSection } from './StaticSection';
8-
import type { OpenAPIClientContext, OpenAPIOperationData } from './types';
8+
import type { OpenAPIClientContext, OpenAPIOperationData, OpenAPIWebhookData } from './types';
99
import { parameterToProperty } from './utils';
1010

11-
export function OpenAPISpec(props: { data: OpenAPIOperationData; context: OpenAPIClientContext }) {
11+
export function OpenAPISpec(props: {
12+
data: OpenAPIOperationData | OpenAPIWebhookData;
13+
context: OpenAPIClientContext;
14+
}) {
1215
const { data, context } = props;
1316

14-
const { operation, securities } = data;
17+
const { operation } = data;
1518

1619
const parameters = operation.parameters ?? [];
1720
const parameterGroups = groupParameters(parameters);
1821

22+
const securities = 'securities' in data ? data.securities : [];
23+
1924
return (
2025
<>
2126
{securities.length > 0 ? (
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import clsx from 'clsx';
2+
import { OpenAPIExample, getExampleFromSchema } from './OpenAPIExample';
3+
import { OpenAPIColumnSpec } from './common/OpenAPIColumnSpec';
4+
import { OpenAPISummary } from './common/OpenAPISummary';
5+
import type { OpenAPIContext, OpenAPIWebhookData } from './types';
6+
7+
/**
8+
* Display an interactive OpenAPI webhook.
9+
*/
10+
export function OpenAPIWebhook(props: {
11+
className?: string;
12+
data: OpenAPIWebhookData;
13+
context: OpenAPIContext;
14+
}) {
15+
const { className, data, context } = props;
16+
const { operation } = data;
17+
18+
return (
19+
<div className={clsx('openapi-webhook', className)}>
20+
<OpenAPISummary data={data} context={context} />
21+
<div className="openapi-columns">
22+
<OpenAPIColumnSpec data={data} context={context} />
23+
<div className="openapi-column-preview">
24+
<div className="openapi-column-preview-body">
25+
<div className="openapi-panel">
26+
<h4 className="openapi-panel-heading">Payload</h4>
27+
<div className="openapi-panel-body">
28+
<OpenAPIExample
29+
example={getExampleFromSchema({
30+
schema: operation.schema,
31+
})}
32+
context={context}
33+
syntax="json"
34+
/>
35+
</div>
36+
</div>
37+
</div>
38+
</div>
39+
</div>
40+
</div>
41+
);
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { OpenAPISpec } from '../OpenAPISpec';
2+
import { getOpenAPIClientContext } from '../context';
3+
import type { OpenAPIContext, OpenAPIOperationData, OpenAPIWebhookData } from '../types';
4+
import { OpenAPIOperationDescription } from './OpenAPIOperationDescription';
5+
6+
export function OpenAPIColumnSpec(props: {
7+
data: OpenAPIOperationData | OpenAPIWebhookData;
8+
context: OpenAPIContext;
9+
}) {
10+
const { data, context } = props;
11+
const { operation } = data;
12+
13+
const clientContext = getOpenAPIClientContext(context);
14+
15+
return (
16+
<div className="openapi-column-spec">
17+
{operation['x-deprecated-sunset'] ? (
18+
<div className="openapi-deprecated-sunset openapi-description openapi-markdown">
19+
This operation is deprecated and will be sunset on{' '}
20+
<span className="openapi-deprecated-sunset-date">
21+
{operation['x-deprecated-sunset']}
22+
</span>
23+
{'.'}
24+
</div>
25+
) : null}
26+
<OpenAPIOperationDescription operation={operation} context={context} />
27+
<OpenAPISpec data={data} context={clientContext} />
28+
</div>
29+
);
30+
}

0 commit comments

Comments
 (0)