Skip to content

Commit 672d571

Browse files
authored
implement versioned docs for OpenAPI w split operation pages (#2623)
* copy over utilities from previous view * clean up parse-and-validate-schema * implement versioning for OpenApiDocsViewV2 * update productData to product, for nav context * fix issue with productData * rejig default theme * fix metadata for landing
1 parent 04d9ef0 commit 672d571

File tree

20 files changed

+786
-223
lines changed

20 files changed

+786
-223
lines changed

src/lib/api-docs/parse-and-validate-open-api-schema.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,7 @@ import { OpenAPIV3 } from 'openapi-types'
3030
*/
3131
export async function parseAndValidateOpenApiSchema(
3232
fileString: string,
33-
massageSchemaForClient: (schema: OpenAPIV3.Document) => OpenAPIV3.Document = (
34-
schema
35-
) => schema
33+
schemaTransforms: ((schema: OpenAPIV3.Document) => OpenAPIV3.Document)[] = []
3634
): Promise<OpenAPIV3.Document> {
3735
// Parse the fileString into raw JSON
3836
const rawSchemaJson = JSON.parse(fileString)
@@ -41,7 +39,14 @@ export async function parseAndValidateOpenApiSchema(
4139
const schemaJsonWithRefs = await new OASNormalize(rawSchemaJson).validate({
4240
convertToLatest: true,
4341
})
44-
const massagedSchema = massageSchemaForClient(schemaJsonWithRefs)
42+
43+
/**
44+
* Apply transform functions to the schema
45+
*/
46+
let transformedSchema = schemaJsonWithRefs
47+
for (const schemaTransformFunction of schemaTransforms ?? []) {
48+
transformedSchema = schemaTransformFunction(transformedSchema)
49+
}
4550

4651
/**
4752
* Dereference the schema.
@@ -69,7 +74,7 @@ export async function parseAndValidateOpenApiSchema(
6974
* }
7075
* }
7176
*/
72-
const schemaJson = await new OASNormalize(massagedSchema).deref()
77+
const schemaJson = await new OASNormalize(transformedSchema).deref()
7378
// Return the dereferenced schema.
7479
// We know it's OpenAPI 3.0, so we cast it to the appropriate type.
7580
return schemaJson as OpenAPIV3.Document

src/views/open-api-docs-preview-v2/utils/get-props-from-preview-data.ts

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: MPL-2.0
44
*/
55

6-
import { getStaticProps } from 'views/open-api-docs-view-v2/server'
6+
import { generateStaticProps } from 'views/open-api-docs-view-v2/server'
77
// Utils
88
import { getOperationGroupKeyFromPath } from 'views/open-api-docs-view-v2/utils/get-operation-group-key-from-path'
99
import { schemaTransformShortenHcp } from 'views/open-api-docs-view-v2/schema-transforms/schema-transform-shorten-hcp'
@@ -15,19 +15,13 @@ import type {
1515
OpenApiDocsViewV2Config,
1616
} from 'views/open-api-docs-view-v2/types'
1717
import type { OpenApiPreviewV2InputValues } from '../components/open-api-preview-inputs'
18-
import { schemaModComponent } from 'views/open-api-docs-view/utils/massage-schema-utils'
1918

2019
/**
2120
* Given preview data submitted by the user, which includes OpenAPI JSON,
2221
* and given an optional operation slug that indicates whether to render
2322
* a view for a specific operation,
2423
*
2524
* Return static props for the appropriate OpenAPI docs view.
26-
*
27-
* TODO: this is largely a placeholder for now.
28-
* Will likely require a few more args to pass to getStaticProps, eg productData
29-
* for example, but those types of details are not yet needed by the underlying
30-
* view.
3125
*/
3226
export default async function getPropsFromPreviewData(
3327
previewData: OpenApiPreviewV2InputValues | null,
@@ -59,17 +53,16 @@ export default async function getPropsFromPreviewData(
5953
},
6054
]
6155
// Build page configuration based on the input values
62-
const pageConfig: OpenApiDocsViewV2Config = {
56+
const pageConfig: Omit<OpenApiDocsViewV2Config, 'schemaSource'> = {
6357
basePath: '/open-api-docs-preview-v2',
6458
breadcrumbLinksPrefix: [
6559
{
6660
title: 'Developer',
6761
url: '/',
6862
},
6963
],
70-
operationSlug,
71-
openApiJsonString: previewData.openApiJsonString,
7264
schemaTransforms,
65+
productContext: 'hcp',
7366
// A generic set of resource links, as a preview of what typically
7467
// gets added to an OpenAPI docs page.
7568
resourceLinks: [
@@ -90,8 +83,6 @@ export default async function getPropsFromPreviewData(
9083
href: 'https://www.hashicorp.com/customer-success',
9184
},
9285
],
93-
// Release stage badge, to demo this feature
94-
releaseStage: 'Preview',
9586
// Status indicator for HCP Services generally, to demo this feature
9687
statusIndicatorConfig: {
9788
pageUrl: 'https://status.hashicorp.com',
@@ -106,5 +97,25 @@ export default async function getPropsFromPreviewData(
10697
pageConfig.getOperationGroupKey = getOperationGroupKeyFromPath
10798
}
10899
// Use the page config to generate static props for the view
109-
return await getStaticProps(pageConfig)
100+
const staticProps = await generateStaticProps({
101+
...pageConfig,
102+
versionData: [
103+
{
104+
versionId: 'latest',
105+
releaseStage: 'Preview',
106+
sourceFile: previewData.openApiJsonString,
107+
},
108+
],
109+
urlContext: {
110+
isVersionedUrl: false,
111+
versionId: 'latest',
112+
operationSlug,
113+
},
114+
})
115+
// If the specific view wasn't found, return null
116+
if ('notFound' in staticProps) {
117+
return null
118+
}
119+
// Otherwise, return the props, discarding the enclosing object
120+
return staticProps.props
110121
}

src/views/open-api-docs-view-v2/components/landing-content/index.tsx

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,19 @@ import { DescriptionMdx } from './components/description-mdx'
1515
// Types
1616
import type { MDXRemoteSerializeResult } from 'lib/next-mdx-remote'
1717
import type { StatusIndicatorConfig } from 'views/open-api-docs-view-v2/types'
18-
18+
import type { ReactNode } from 'react'
1919
import type { ProductSlug } from 'types/products'
2020
// Styles
2121
import s from './style.module.css'
2222

2323
export interface LandingContentProps {
24-
badgeText: string
25-
descriptionMdx?: MDXRemoteSerializeResult
2624
heading: string
27-
serviceProductSlug: ProductSlug
25+
schemaFileString?: string
26+
badgeText?: string
27+
descriptionMdx?: MDXRemoteSerializeResult
28+
serviceProductSlug?: ProductSlug
2829
statusIndicatorConfig?: StatusIndicatorConfig
29-
schemaFileString: string
30+
versionSwitcherSlot?: ReactNode
3031
}
3132

3233
export function LandingContent({
@@ -36,6 +37,7 @@ export function LandingContent({
3637
serviceProductSlug,
3738
statusIndicatorConfig,
3839
schemaFileString,
40+
versionSwitcherSlot,
3941
}: LandingContentProps) {
4042
return (
4143
<div className={s.overviewWrapper}>
@@ -53,26 +55,33 @@ export function LandingContent({
5355
/>
5456
) : null}
5557
</span>
56-
<Badge
57-
className={s.releaseStageBadge}
58-
text={badgeText}
59-
type="outlined"
60-
size="small"
61-
/>
58+
{badgeText ? (
59+
<Badge
60+
className={s.releaseStageBadge}
61+
text={badgeText}
62+
type="outlined"
63+
size="small"
64+
/>
65+
) : null}
6266
</header>
67+
{versionSwitcherSlot ? (
68+
<div className={s.versionSwitcherSlot}>{versionSwitcherSlot}</div>
69+
) : null}
6370
</div>
6471
{descriptionMdx ? (
6572
<DescriptionMdx mdxRemoteProps={descriptionMdx} />
6673
) : null}
67-
<StandaloneLink
68-
text="Download Spec"
69-
icon={<IconDownload16 />}
70-
iconPosition="leading"
71-
download="hcp.swagger.json"
72-
href={`data:text/json;charset=utf-8,${encodeURIComponent(
73-
schemaFileString
74-
)}`}
75-
/>
74+
{schemaFileString ? (
75+
<StandaloneLink
76+
text="Download Spec"
77+
icon={<IconDownload16 />}
78+
iconPosition="leading"
79+
download="hcp.swagger.json"
80+
href={`data:text/json;charset=utf-8,${encodeURIComponent(
81+
schemaFileString
82+
)}`}
83+
/>
84+
) : null}
7685
</div>
7786
)
7887
}

src/views/open-api-docs-view-v2/components/operation-content/index.tsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import { OperationExamples } from '../operation-examples'
1111
import { OperationDetails } from '../operation-details'
1212
// Types
1313
import type { PropertyDetailsSectionProps } from '../operation-details'
14+
import type { ReactNode } from 'react'
1415
// Styles
1516
import s from './style.module.css'
1617

1718
export interface OperationContentProps {
18-
heading: string
1919
operationId: string
2020
tags: string[]
2121
slug: string
@@ -32,6 +32,7 @@ export interface OperationContentProps {
3232
* word breaks to allow long URLs to wrap to multiple lines.
3333
*/
3434
urlPathForCodeBlock: string
35+
versionSwitcherSlot?: ReactNode
3536
}
3637

3738
/**
@@ -47,17 +48,22 @@ export interface OperationProps {
4748
* Render detailed content for an individual operation.
4849
*/
4950
export default function OperationContent({
50-
heading,
51-
slug,
51+
operationId,
5252
type,
5353
path,
5454
urlPathForCodeBlock,
5555
requestData,
5656
responseData,
57+
versionSwitcherSlot,
5758
}: OperationContentProps) {
5859
return (
5960
<>
60-
<h1 className={s.heading}>{heading}</h1>
61+
<div className={s.header}>
62+
<h1 className={s.heading}>{operationId}</h1>
63+
{versionSwitcherSlot ? (
64+
<div className={s.versionSwitcherSlot}>{versionSwitcherSlot}</div>
65+
) : null}
66+
</div>
6167
<OperationSections
6268
headerSlot={
6369
<div className={s.methodAndPath}>
@@ -66,7 +72,7 @@ export default function OperationContent({
6672
</div>
6773
}
6874
examplesSlot={
69-
<OperationExamples heading={slug} code={urlPathForCodeBlock} />
75+
<OperationExamples heading={operationId} code={urlPathForCodeBlock} />
7076
}
7177
detailsSlot={
7278
<OperationDetails

src/views/open-api-docs-view-v2/components/operation-content/server.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,30 @@
44
*/
55

66
// Utils
7-
import { getOperationObjects } from '../../utils/get-operation-objects'
7+
88
import { getUrlPathCodeHtml } from '../../utils/get-url-path-code-html'
99
import { truncateHcpOperationPath } from '../../utils/truncate-hcp-operation-path'
1010
import { getRequestData } from '../../utils/get-request-data'
1111
import { getResponseData } from '../../utils/get-response-data'
12+
import { slugifyOperationId } from 'views/open-api-docs-view-v2/utils/slugify-operation-id'
1213
// Types
1314
import type { OpenAPIV3 } from 'openapi-types'
1415
import type { OperationContentProps } from '.'
16+
import type { OperationObject } from '../../utils/get-operation-objects'
1517

1618
/**
1719
* Transform the schemaData into props for an individual operation
1820
*/
1921
export default async function getOperationContentProps(
20-
operationSlug: string,
22+
operation: OperationObject,
2123
schemaData: OpenAPIV3.Document
2224
): Promise<OperationContentProps> {
23-
const operationObjects = getOperationObjects(schemaData)
24-
const operation = operationObjects.find(
25-
(operation) => operation.operationId === operationSlug
26-
)
2725
/**
2826
* The API's base URL is used to prefix the operation path,
2927
* so users can quickly copy the full path to the operation
3028
*/
3129
const apiBaseUrl = getApiBaseUrl(schemaData)
30+
const operationSlug = slugifyOperationId(operation.operationId)
3231
/**
3332
* Parse request and response details for this operation
3433
*/
@@ -53,8 +52,7 @@ export default async function getOperationContentProps(
5352
* Return the operation content props
5453
*/
5554
return {
56-
heading: operationSlug,
57-
operationId: operationSlug,
55+
operationId: operation.operationId,
5856
tags: operation.tags,
5957
slug: operationSlug,
6058
type: operation.type,

src/views/open-api-docs-view-v2/components/operation-content/style.module.css

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33
* SPDX-License-Identifier: MPL-2.0
44
*/
55

6+
/* HEADER AREA */
7+
8+
.header {
9+
display: flex;
10+
flex-wrap: wrap;
11+
justify-content: space-between;
12+
gap: 16px;
13+
}
14+
615
.heading {
716
composes: hds-typography-display-600 from global;
817

@@ -13,7 +22,9 @@
1322
margin: 0 0 9px 0;
1423
}
1524

16-
/* HEADER AREA */
25+
.versionSwitcherSlot {
26+
flex-shrink: 0;
27+
}
1728

1829
.methodAndPath {
1930
display: flex;

0 commit comments

Comments
 (0)