Skip to content

Commit

Permalink
add operation content to OpenAPI v2 template (#2618)
Browse files Browse the repository at this point in the history
* add in components and utils from prev view

* fix schema transform execution

* implement operation content
  • Loading branch information
zchsh authored Nov 7, 2024
1 parent 3ca98db commit 1f08a05
Show file tree
Hide file tree
Showing 20 changed files with 1,540 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import { getStaticProps } 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'
import { schemaTransformComponent } from 'views/open-api-docs-view-v2/schema-transforms/schema-transform-component'
import { shortenProtobufAnyDescription } from 'views/open-api-docs-view-v2/schema-transforms/shorten-protobuf-any-description'
// Types
import type {
OpenApiDocsViewV2Props,
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,
Expand All @@ -38,7 +41,23 @@ export default async function getPropsFromPreviewData(
// and prefer to have content updates made at the content source... but
// some shims are used often enough that they feel worth including in the
// preview too. Namely, shortening to `HCP` in the spec title.
const schemaTransforms = [schemaTransformShortenHcp]
const schemaTransforms = [
schemaTransformShortenHcp,
(schema) => {
return schemaTransformComponent(
schema,
'protobufAny',
shortenProtobufAnyDescription
)
},
(schema) => {
return schemaTransformComponent(
schema,
'google.protobuf.Any',
shortenProtobufAnyDescription
)
},
]
// Build page configuration based on the input values
const pageConfig: OpenApiDocsViewV2Config = {
basePath: '/open-api-docs-preview-v2',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

.root {
--permalink-opacity: 0;

display: flex;
gap: 8px;
align-items: center;

&:hover {
--permalink-opacity: 1;
}
}

.permalink {
--icon-color: var(--token-color-foreground-action);

composes: g-focus-ring-from-box-shadow from global;
border-radius: 6px;
display: flex;
opacity: var(--permalink-opacity);
padding: 4px;

@media (prefers-reduced-motion: no-preference) {
transition: opacity 0.2s ease;
}

&:hover {
--icon-color: var(--token-color-foreground-action-hover);

opacity: 1;
box-shadow: var(--token-surface-mid-box-shadow);
}

&:focus-visible {
--icon-color: var(--token-color-foreground-action-active);

opacity: 1;
background: var(--token-color-surface-faint);
}
}

.permalinkIcon {
color: var(--permalink-icon-color);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

import Link from 'next/link'
import { IconLink16 } from '@hashicorp/flight-icons/svg-react/link-16'
// Types
import type { PropsWithChildren } from 'react'
// Styles
import s from './content-with-permalink.module.css'
import classNames from 'classnames'

/**
* Renders the provided `children` alongside a permalink `<a />` element.
*
* `ariaLabel` is required to ensure the permalink element has meaningful
* text for screen readers. For simple use cases where `children` is a
* string, the `ariaLabel` can be passed the same value as `children`.
* For more complex use cases, the consumer should determine what text
* would be appropriate to pass to `ariaLabel`.
*
* Note that this component does _not_ handle placing the provided `id`
* in the DOM. It requires the consumer to place the `id` on an appropriate
* element, typically an element rendered in the provided `children`.
*/
export function ContentWithPermalink({
id,
ariaLabel,
children,
className,
}: PropsWithChildren<{ id: string; className?: string; ariaLabel: string }>) {
return (
<div className={classNames(s.root, className)}>
{children}
<Link className={s.permalink} aria-label={ariaLabel} href={`#${id}`}>
<IconLink16 className={s.permalinkIcon} />
</Link>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,78 @@
* SPDX-License-Identifier: MPL-2.0
*/

// Components
import Badge from 'components/badge'
// Local components
import { OperationSections } from '../operation-sections'
import { OperationExamples } from '../operation-examples'
import { OperationDetails } from '../operation-details'
// Types
import type { OpenAPIV3 } from 'openapi-types'
import type { PropertyDetailsSectionProps } from '../operation-details'
// Styles
import s from './style.module.css'

export interface OperationContentProps {
heading: string
operationId: string
tags: string[]
slug: string
type: string
path: {
full: string
truncated: string
}
requestData: PropertyDetailsSectionProps
responseData: PropertyDetailsSectionProps
summary?: string
/**
* TODO: discard once view can be identified without this
* Syntax-highlighted HTML that represents the URL path, with
* word breaks to allow long URLs to wrap to multiple lines.
*/
_placeholder: any
urlPathForCodeBlock: string
}

/**
* TODO: implement this content area
* Operations are specific request types to specific endpoints.
* They form the basis of OpenAPI docs pages.
*/
export default function OperationContent(props: OperationContentProps) {
export interface OperationProps {
slug: string
type: string
}

/**
* Render detailed content for an individual operation.
*/
export default function OperationContent({
heading,
slug,
type,
path,
urlPathForCodeBlock,
requestData,
responseData,
}: OperationContentProps) {
return (
<>
<pre style={{ whiteSpace: 'pre-wrap' }}>
<code>{JSON.stringify(props, null, 2)}</code>
</pre>
<h1 className={s.heading}>{heading}</h1>
<OperationSections
headerSlot={
<div className={s.methodAndPath}>
<Badge className={s.method} type="outlined" text={type} />
<p className={s.path}>{path.truncated}</p>
</div>
}
examplesSlot={
<OperationExamples heading={slug} code={urlPathForCodeBlock} />
}
detailsSlot={
<OperationDetails
requestData={requestData}
responseData={responseData}
/>
}
/>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,91 @@
* SPDX-License-Identifier: MPL-2.0
*/

// 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'
// Types
import type { OpenAPIV3 } from 'openapi-types'
import type { OperationContentProps } from '.'

/**
* TODO: transform the schemaData into useful props
* Transform the schemaData into props for an individual operation
*/
export default async function getOperationContentProps(
operationSlug: string,
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)
/**
* Parse request and response details for this operation
*/
const parameters = 'parameters' in operation ? operation.parameters : []
const requestDataSlug = `${operationSlug}_request`
const requestData = {
heading: { text: 'Request', slug: requestDataSlug },
noGroupsMessage: 'No request data.',
groups: await getRequestData(
parameters,
operation.requestBody,
requestDataSlug
),
}
const responseDataSlug = `${operationSlug}_response`
const responseData = {
heading: { text: 'Response', slug: responseDataSlug },
noGroupsMessage: 'No response data.',
groups: await getResponseData(operation.responses, responseDataSlug),
}
/**
* Return the operation content props
*/
return {
_placeholder: {
viewToImplement: `Operation view for ${operationSlug}`,
schemaSample: schemaData.info,
heading: operationSlug,
operationId: operationSlug,
tags: operation.tags,
slug: operationSlug,
type: operation.type,
path: {
full: operation.path,
truncated: truncateHcpOperationPath(operation.path),
},
requestData: requestData,
responseData: responseData,
urlPathForCodeBlock: getUrlPathCodeHtml(apiBaseUrl + operation.path),
}
}

/**
* Given some OpenAPI schemaData,
* Return a string representing the base URL for the API.
*
* The base URL is derived from the first server URL in the schemaData.
*
* @param schemaData
* @returns {string} The base URL for the API
*/
function getApiBaseUrl(schemaData: OpenAPIV3.Document): string {
let baseUrl = ''
if (schemaData.servers?.length > 0) {
const rawBaseUrl = schemaData.servers[0].url
/**
* If we up-converted from an older OpenAPI version, then the spec would
* have defined a `host` rather than explicit server URL, and the resulting
* server URL will start with `//` rather than a valid protocol.
* In this case we assume HTTPs, and update the baseUrl accordingly.
*/
baseUrl = rawBaseUrl.replace(/^\/\//, 'https://')
}
return baseUrl
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.heading {
composes: hds-typography-display-600 from global;

/* Ensure we jump to the very top of the page when linking to this item */
scroll-margin-top: calc(var(--total-scroll-offset) + 999vh);
font-weight: var(--token-typography-font-weight-bold);
color: var(--token-color-foreground-strong);
margin: 0 0 9px 0;
}

/* HEADER AREA */

.methodAndPath {
display: flex;
align-items: flex-start;
gap: 8px;
}

.method {
text-transform: uppercase;
}

.path {
composes: hds-typography-code-300 from global;
color: var(--token-color-foreground-primary);
overflow-wrap: break-word;
flex-shrink: 1;
min-width: 0;
}
Loading

0 comments on commit 1f08a05

Please sign in to comment.