Skip to content

Commit

Permalink
OpenAPI V2 - implement landing content (#2606)
Browse files Browse the repository at this point in the history
* port over components from existing view

* implement landing-content to match upstream

* add demo data to preview
  • Loading branch information
zchsh authored Nov 4, 2024
1 parent ad26974 commit 3ca98db
Show file tree
Hide file tree
Showing 15 changed files with 641 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ export default async function getPropsFromPreviewData(
// Build page configuration based on the input values
const pageConfig: OpenApiDocsViewV2Config = {
basePath: '/open-api-docs-preview-v2',
breadcrumbLinksPrefix: [
{
title: 'Developer',
url: '/',
},
],
operationSlug,
openApiJsonString: previewData.openApiJsonString,
schemaTransforms,
Expand All @@ -65,6 +71,14 @@ 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',
endpointUrl:
'https://status.hashicorp.com/api/v2/components/0q55nwmxngkc.json',
},
}
// If the user has requested to group operations by path, we'll do so
// by providing a custom `getOperationGroupKey` function. If this is omitted,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

.root {
composes: hds-typography-body-300 from global;

& > *:first-child {
margin-top: 0;
}

& > *:last-child {
margin-bottom: 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

import { MDXRemote, MDXRemoteSerializeResult } from 'lib/next-mdx-remote'
import {
MdxA,
MdxOrderedList,
MdxUnorderedList,
MdxListItem,
MdxTable,
MdxH1,
MdxH2,
MdxH3,
MdxH4,
MdxH5,
MdxH6,
MdxP,
MdxInlineCode,
MdxBlockquote,
MdxPre,
} from 'components/dev-dot-content/mdx-components'
import s from './description-mdx.module.css'

type MdxComponents = Record<string, (props: unknown) => JSX.Element>

const DEFAULT_MDX_COMPONENTS: MdxComponents = {
a: MdxA,
blockquote: MdxBlockquote,
h1: MdxH1,
h2: MdxH2,
h3: MdxH3,
h4: MdxH4,
h5: MdxH5,
h6: MdxH6,
inlineCode: MdxInlineCode,
li: MdxListItem,
ol: MdxOrderedList,
p: MdxP,
pre: MdxPre,
table: MdxTable,
ul: MdxUnorderedList,
}

/**
* Renders CommonMark-compliant markdown content using our established set
* of MDX custom components, via next-mdx-remote.
*/
export function DescriptionMdx({
mdxRemoteProps,
}: {
mdxRemoteProps: MDXRemoteSerializeResult
}) {
return (
<div className={s.root}>
<MDXRemote {...mdxRemoteProps} components={DEFAULT_MDX_COMPONENTS} />
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

// Third-party
import { IconExternalLink16 } from '@hashicorp/flight-icons/svg-react/external-link-16'
// Components
import ServiceStatusBadge from 'components/service-status-badge'
import StandaloneLink from 'components/standalone-link'
// Local
import { useServiceStatus } from './utils/use-service-status'
// Types
import { StatusIndicatorConfig } from 'views/open-api-docs-view/types'
// Styles
import s from './status.module.css'

/**
* Displays a `ServiceStatusBadge` with data from the provided `endpointUrl`,
* alongside an external link to the provided `pageUrl`.
*
* We expect the `endpointUrl` to be a status-page component data URL, like:
* - https://status.hashicorp.com/api/v2/components/{componentId}.json
*
* We expect the `pageUrl` to be a browser-friendly status page URL, such as:
* - https://status.hashicorp.com
*/
export function Status({ endpointUrl, pageUrl }: StatusIndicatorConfig) {
const status = useServiceStatus(endpointUrl)
return (
<div className={s.wrapper}>
<ServiceStatusBadge status={status} />
<StandaloneLink
text="Status"
icon={<IconExternalLink16 />}
iconPosition="trailing"
color="secondary"
href={pageUrl}
size="small"
opensInNewTab
/>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

.wrapper {
display: flex;
gap: 8px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/

import { useEffect, useState } from 'react'
import { ServiceStatus } from 'components/service-status-badge'

/**
* Hook to use the StatusPage service status at a given endpoint.
*/
export function useServiceStatus(endpointUrl: string) {
const [status, setStatus] = useState<ServiceStatus>('loading')

useEffect(() => {
const asyncEffect = async () => {
setStatus(await fetchServiceStatus(endpointUrl))
}
asyncEffect()
}, [endpointUrl])

return status
}

/**
* Fetches the service status from a given StatusPage component
* endpoint URL. URLs are expected to be something like:
* https://status.hashicorp.com/api/v2/components/{componentId}.json
*
* If the retrieved data does not match the expected shape
*/
async function fetchServiceStatus(url: string): Promise<ServiceStatus> {
let status: ServiceStatus
try {
const data = (await fetchJson(url)) as ExpectedStatusPageData
status = data.component?.status
if (typeof status !== 'string') {
throw new Error(
`In the "useServiceStatus" hook, the status data did not match expected shape. Please ensure GET requests to the endpoint ${url} yield data with a string at "responseData.component.status".`
)
}
} catch (e) {
console.error(`Failed to parse valid status page data from ${url}.`)
console.error(e)
// Return 'unknown' as a fallback.
status = 'unknown'
}
return status
}

/**
* The shape of data we expect to receive from a provided `endpointUrl`.
*/
interface ExpectedStatusPageData {
component: {
status: ServiceStatus
}
}

/**
* Fetch JSON data from a provided URL.
*/
async function fetchJson(url: string) {
const response = await fetch(url)
if (!response.ok) {
throw new Error(
`HTTP error when fetching from ${url}. Status: ${response.status}`
)
}
return await response.json()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,76 @@
* SPDX-License-Identifier: MPL-2.0
*/

// Components
import Badge from 'components/badge'
import IconTile from 'components/icon-tile'
import ProductIcon from 'components/product-icon'
import StandaloneLink from '@components/standalone-link'
import { IconDownload16 } from '@hashicorp/flight-icons/svg-react/download-16'
// Local
import { Status } from './components/status'
import { DescriptionMdx } from './components/description-mdx'
// Types
import type { OpenAPIV3 } from 'openapi-types'
import type { MDXRemoteSerializeResult } from 'lib/next-mdx-remote'
import type { StatusIndicatorConfig } from 'views/open-api-docs-view-v2/types'

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

export interface LandingContentProps {
/**
* TODO: discard once view can be identified without this
*/
_placeholder: any
badgeText: string
descriptionMdx?: MDXRemoteSerializeResult
heading: string
serviceProductSlug: ProductSlug
statusIndicatorConfig?: StatusIndicatorConfig
schemaFileString: string
}

/**
* TODO: implement this content area
*/
export default function LandingContent(props: LandingContentProps) {
export function LandingContent({
badgeText,
descriptionMdx,
heading,
serviceProductSlug,
statusIndicatorConfig,
schemaFileString,
}: LandingContentProps) {
return (
<>
<pre style={{ whiteSpace: 'pre-wrap' }}>
<code>{JSON.stringify(props, null, 2)}</code>
</pre>
</>
<div className={s.overviewWrapper}>
<div className={s.headerAndVersionSwitcher}>
<header className={s.header}>
<IconTile size="medium" className={s.icon}>
<ProductIcon productSlug={serviceProductSlug} />
</IconTile>
<span>
<h1 className={s.heading}>{heading}</h1>
{statusIndicatorConfig ? (
<Status
endpointUrl={statusIndicatorConfig.endpointUrl}
pageUrl={statusIndicatorConfig.pageUrl}
/>
) : null}
</span>
<Badge
className={s.releaseStageBadge}
text={badgeText}
type="outlined"
size="small"
/>
</header>
</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
)}`}
/>
</div>
)
}

This file was deleted.

Loading

0 comments on commit 3ca98db

Please sign in to comment.