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

implement versioned docs for OpenAPI w split operation pages #2623

Merged
merged 7 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 10 additions & 5 deletions src/lib/api-docs/parse-and-validate-open-api-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ import { OpenAPIV3 } from 'openapi-types'
*/
export async function parseAndValidateOpenApiSchema(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This file was already lifted into the shared lib folder. I think we have a better pattern available for applying schema transforms, established in previous PRs... but previously we were apply the transforms after de-referencing the schema.

With this in mind, I've lightly modified the interface of this function to accommodate an array of transform functions, rather than a single function as was supported previously. I've made corresponding updates in all places where this function is used.

fileString: string,
massageSchemaForClient: (schema: OpenAPIV3.Document) => OpenAPIV3.Document = (
schema
) => schema
schemaTransforms: ((schema: OpenAPIV3.Document) => OpenAPIV3.Document)[] = []
): Promise<OpenAPIV3.Document> {
// Parse the fileString into raw JSON
const rawSchemaJson = JSON.parse(fileString)
Expand All @@ -41,7 +39,14 @@ export async function parseAndValidateOpenApiSchema(
const schemaJsonWithRefs = await new OASNormalize(rawSchemaJson).validate({
convertToLatest: true,
})
const massagedSchema = massageSchemaForClient(schemaJsonWithRefs)

/**
* Apply transform functions to the schema
*/
let transformedSchema = schemaJsonWithRefs
for (const schemaTransformFunction of schemaTransforms ?? []) {
transformedSchema = schemaTransformFunction(transformedSchema)
}

/**
* Dereference the schema.
Expand Down Expand Up @@ -69,7 +74,7 @@ export async function parseAndValidateOpenApiSchema(
* }
* }
*/
const schemaJson = await new OASNormalize(massagedSchema).deref()
const schemaJson = await new OASNormalize(transformedSchema).deref()
// Return the dereferenced schema.
// We know it's OpenAPI 3.0, so we cast it to the appropriate type.
return schemaJson as OpenAPIV3.Document
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: MPL-2.0
*/

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

/**
* Given preview data submitted by the user, which includes OpenAPI JSON,
* and given an optional operation slug that indicates whether to render
* a view for a specific operation,
*
* Return static props for the appropriate OpenAPI docs view.
*
* TODO: this is largely a placeholder for now.
* Will likely require a few more args to pass to getStaticProps, eg productData
* for example, but those types of details are not yet needed by the underlying
* view.
*/
export default async function getPropsFromPreviewData(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In order to accommodate versioning, I made updates to the generateStaticProps function for the new "V2" version of the OpenAPI view. The changes in this file update the preview tool's props generation workflow accordingly.

previewData: OpenApiPreviewV2InputValues | null,
Expand Down Expand Up @@ -59,17 +53,16 @@ export default async function getPropsFromPreviewData(
},
]
// Build page configuration based on the input values
const pageConfig: OpenApiDocsViewV2Config = {
const pageConfig: Omit<OpenApiDocsViewV2Config, 'schemaSource'> = {
basePath: '/open-api-docs-preview-v2',
breadcrumbLinksPrefix: [
{
title: 'Developer',
url: '/',
},
],
operationSlug,
openApiJsonString: previewData.openApiJsonString,
schemaTransforms,
productContext: 'hcp',
// A generic set of resource links, as a preview of what typically
// gets added to an OpenAPI docs page.
resourceLinks: [
Expand All @@ -90,8 +83,6 @@ export default async function getPropsFromPreviewData(
href: 'https://www.hashicorp.com/customer-success',
},
],
// Release stage badge, to demo this feature
releaseStage: 'Preview',
// Status indicator for HCP Services generally, to demo this feature
statusIndicatorConfig: {
pageUrl: 'https://status.hashicorp.com',
Expand All @@ -106,5 +97,25 @@ export default async function getPropsFromPreviewData(
pageConfig.getOperationGroupKey = getOperationGroupKeyFromPath
}
// Use the page config to generate static props for the view
return await getStaticProps(pageConfig)
const staticProps = await generateStaticProps({
...pageConfig,
versionData: [
{
versionId: 'latest',
releaseStage: 'Preview',
sourceFile: previewData.openApiJsonString,
},
],
urlContext: {
isVersionedUrl: false,
versionId: 'latest',
operationSlug,
},
})
// If the specific view wasn't found, return null
if ('notFound' in staticProps) {
return null
}
// Otherwise, return the props, discarding the enclosing object
return staticProps.props
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,19 @@ import { DescriptionMdx } from './components/description-mdx'
// Types
import type { MDXRemoteSerializeResult } from 'lib/next-mdx-remote'
import type { StatusIndicatorConfig } from 'views/open-api-docs-view-v2/types'

import type { ReactNode } from 'react'
import type { ProductSlug } from 'types/products'
// Styles
import s from './style.module.css'

export interface LandingContentProps {
badgeText: string
descriptionMdx?: MDXRemoteSerializeResult
heading: string
serviceProductSlug: ProductSlug
schemaFileString?: string
badgeText?: string
descriptionMdx?: MDXRemoteSerializeResult
serviceProductSlug?: ProductSlug
statusIndicatorConfig?: StatusIndicatorConfig
schemaFileString: string
versionSwitcherSlot?: ReactNode
}

export function LandingContent({
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The changes in this file add support for an optional versionSwitcherSlot into the landing page content. I also made some arguments optional, to reflect that they're optional in config as well.

Expand All @@ -36,6 +37,7 @@ export function LandingContent({
serviceProductSlug,
statusIndicatorConfig,
schemaFileString,
versionSwitcherSlot,
}: LandingContentProps) {
return (
<div className={s.overviewWrapper}>
Expand All @@ -53,26 +55,33 @@ export function LandingContent({
/>
) : null}
</span>
<Badge
className={s.releaseStageBadge}
text={badgeText}
type="outlined"
size="small"
/>
{badgeText ? (
<Badge
className={s.releaseStageBadge}
text={badgeText}
type="outlined"
size="small"
/>
) : null}
</header>
{versionSwitcherSlot ? (
<div className={s.versionSwitcherSlot}>{versionSwitcherSlot}</div>
) : null}
</div>
{descriptionMdx ? (
<DescriptionMdx mdxRemoteProps={descriptionMdx} />
) : null}
<StandaloneLink
text="Download Spec"
icon={<IconDownload16 />}
iconPosition="leading"
download="hcp.swagger.json"
href={`data:text/json;charset=utf-8,${encodeURIComponent(
schemaFileString
)}`}
/>
{schemaFileString ? (
<StandaloneLink
text="Download Spec"
icon={<IconDownload16 />}
iconPosition="leading"
download="hcp.swagger.json"
href={`data:text/json;charset=utf-8,${encodeURIComponent(
schemaFileString
)}`}
/>
) : null}
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import { OperationExamples } from '../operation-examples'
import { OperationDetails } from '../operation-details'
// Types
import type { PropertyDetailsSectionProps } from '../operation-details'
import type { ReactNode } from 'react'
// Styles
import s from './style.module.css'

export interface OperationContentProps {
heading: string
operationId: string
tags: string[]
slug: string
Expand All @@ -32,6 +32,7 @@ export interface OperationContentProps {
* word breaks to allow long URLs to wrap to multiple lines.
*/
urlPathForCodeBlock: string
versionSwitcherSlot?: ReactNode
}

/**
Expand All @@ -47,17 +48,22 @@ export interface OperationProps {
* Render detailed content for an individual operation.
*/
export default function OperationContent({
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Similar to landing content, the changes in this file add support for a versionSwitcherSlot in OperationContent.

heading,
slug,
operationId,
type,
path,
urlPathForCodeBlock,
requestData,
responseData,
versionSwitcherSlot,
}: OperationContentProps) {
return (
<>
<h1 className={s.heading}>{heading}</h1>
<div className={s.header}>
<h1 className={s.heading}>{operationId}</h1>
{versionSwitcherSlot ? (
<div className={s.versionSwitcherSlot}>{versionSwitcherSlot}</div>
) : null}
</div>
<OperationSections
headerSlot={
<div className={s.methodAndPath}>
Expand All @@ -66,7 +72,7 @@ export default function OperationContent({
</div>
}
examplesSlot={
<OperationExamples heading={slug} code={urlPathForCodeBlock} />
<OperationExamples heading={operationId} code={urlPathForCodeBlock} />
}
detailsSlot={
<OperationDetails
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,30 @@
*/

// Utils
import { getOperationObjects } from '../../utils/get-operation-objects'

import { getUrlPathCodeHtml } from '../../utils/get-url-path-code-html'
import { truncateHcpOperationPath } from '../../utils/truncate-hcp-operation-path'
import { getRequestData } from '../../utils/get-request-data'
import { getResponseData } from '../../utils/get-response-data'
import { slugifyOperationId } from 'views/open-api-docs-view-v2/utils/slugify-operation-id'
// Types
import type { OpenAPIV3 } from 'openapi-types'
import type { OperationContentProps } from '.'
import type { OperationObject } from '../../utils/get-operation-objects'

/**
* Transform the schemaData into props for an individual operation
*/
export default async function getOperationContentProps(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Made some minor modifications here to avoid passing duplicate data, and to lift the targetOperation determination process into the enclosing generateStaticProps.

operationSlug: string,
operation: OperationObject,
schemaData: OpenAPIV3.Document
): Promise<OperationContentProps> {
const operationObjects = getOperationObjects(schemaData)
const operation = operationObjects.find(
(operation) => operation.operationId === operationSlug
)
/**
* The API's base URL is used to prefix the operation path,
* so users can quickly copy the full path to the operation
*/
const apiBaseUrl = getApiBaseUrl(schemaData)
const operationSlug = slugifyOperationId(operation.operationId)
/**
* Parse request and response details for this operation
*/
Expand All @@ -53,8 +52,7 @@ export default async function getOperationContentProps(
* Return the operation content props
*/
return {
heading: operationSlug,
operationId: operationSlug,
operationId: operation.operationId,
tags: operation.tags,
slug: operationSlug,
type: operation.type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
* SPDX-License-Identifier: MPL-2.0
*/

/* HEADER AREA */

.header {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

OperationContent header area styles needed some minor updates to accommodate the new versionSwitcherSlot.

display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 16px;
}

.heading {
composes: hds-typography-display-600 from global;

Expand All @@ -13,7 +22,9 @@
margin: 0 0 9px 0;
}

/* HEADER AREA */
.versionSwitcherSlot {
flex-shrink: 0;
}

.methodAndPath {
display: flex;
Expand Down
Loading
Loading