diff --git a/.pnp.cjs b/.pnp.cjs index f7ec3a15a8..f80af9077f 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -3285,10 +3285,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { }]\ ]],\ ["@fern-fern/registry-browser", [\ - ["npm:0.14.1-1-gf6c42e8", {\ - "packageLocation": "./.yarn/cache/@fern-fern-registry-browser-npm-0.14.1-1-gf6c42e8-2f28f31dd1-ee946a2b12.zip/node_modules/@fern-fern/registry-browser/",\ + ["npm:0.14.1-3-g6c09dff", {\ + "packageLocation": "./.yarn/cache/@fern-fern-registry-browser-npm-0.14.1-3-g6c09dff-78450409fb-506a2449eb.zip/node_modules/@fern-fern/registry-browser/",\ "packageDependencies": [\ - ["@fern-fern/registry-browser", "npm:0.14.1-1-gf6c42e8"],\ + ["@fern-fern/registry-browser", "npm:0.14.1-3-g6c09dff"],\ ["@types/url-join", "npm:4.0.1"],\ ["@ungap/url-search-params", "npm:0.2.2"],\ ["axios", "npm:1.4.0"],\ @@ -3378,7 +3378,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "packageLocation": "./packages/ui/fe-bundle/",\ "packageDependencies": [\ ["@fern-ui/fe-bundle", "workspace:packages/ui/fe-bundle"],\ - ["@fern-fern/registry-browser", "npm:0.14.1-1-gf6c42e8"],\ + ["@fern-fern/registry-browser", "npm:0.14.1-3-g6c09dff"],\ ["@fern-ui/core-utils", "workspace:packages/commons/core-utils"],\ ["@fern-ui/theme", "workspace:packages/commons/react/theme"],\ ["@fern-ui/ui", "workspace:packages/ui/app"],\ @@ -3631,7 +3631,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@blueprintjs/icons", "npm:4.9.0"],\ ["@blueprintjs/popover2", "virtual:78e90867e46510db2994cf6ed805ab4ba701c55b6d7a2c33f6cc04865dc082c172bef019989477dda8bc6824512d13cb3bcb74ef12bd113a2f6bdcd291ad70f1#npm:1.8.0"],\ ["@blueprintjs/select", "virtual:78e90867e46510db2994cf6ed805ab4ba701c55b6d7a2c33f6cc04865dc082c172bef019989477dda8bc6824512d13cb3bcb74ef12bd113a2f6bdcd291ad70f1#npm:4.5.1"],\ - ["@fern-fern/registry-browser", "npm:0.14.1-1-gf6c42e8"],\ + ["@fern-fern/registry-browser", "npm:0.14.1-3-g6c09dff"],\ ["@fern-ui/core-utils", "workspace:packages/commons/core-utils"],\ ["@fern-ui/react-commons", "workspace:packages/commons/react/react-commons"],\ ["@fern-ui/routing-utils", "workspace:packages/commons/react/routing-utils"],\ diff --git a/.yarn/cache/@fern-fern-registry-browser-npm-0.14.1-1-gf6c42e8-2f28f31dd1-ee946a2b12.zip b/.yarn/cache/@fern-fern-registry-browser-npm-0.14.1-3-g6c09dff-78450409fb-506a2449eb.zip similarity index 73% rename from .yarn/cache/@fern-fern-registry-browser-npm-0.14.1-1-gf6c42e8-2f28f31dd1-ee946a2b12.zip rename to .yarn/cache/@fern-fern-registry-browser-npm-0.14.1-3-g6c09dff-78450409fb-506a2449eb.zip index c6ebcbe572..fadb836938 100644 Binary files a/.yarn/cache/@fern-fern-registry-browser-npm-0.14.1-1-gf6c42e8-2f28f31dd1-ee946a2b12.zip and b/.yarn/cache/@fern-fern-registry-browser-npm-0.14.1-3-g6c09dff-78450409fb-506a2449eb.zip differ diff --git a/packages/ui/app/package.json b/packages/ui/app/package.json index 8bece476ee..14e7f5d5c7 100644 --- a/packages/ui/app/package.json +++ b/packages/ui/app/package.json @@ -33,7 +33,7 @@ "@blueprintjs/icons": "^4.4.0", "@blueprintjs/popover2": "^1.8.0", "@blueprintjs/select": "^4.4.2", - "@fern-fern/registry-browser": "0.14.1-1-gf6c42e8", + "@fern-fern/registry-browser": "0.14.1-3-g6c09dff", "@fern-ui/core-utils": "workspace:*", "@fern-ui/react-commons": "workspace:*", "@fern-ui/routing-utils": "workspace:*", diff --git a/packages/ui/app/src/ResolvedUrlPath.ts b/packages/ui/app/src/ResolvedUrlPath.ts index 8a245f6463..98a35368a7 100644 --- a/packages/ui/app/src/ResolvedUrlPath.ts +++ b/packages/ui/app/src/ResolvedUrlPath.ts @@ -8,8 +8,10 @@ export type ResolvedUrlPath = | ResolvedUrlPath.Api | ResolvedUrlPath.ClientLibraries | ResolvedUrlPath.TopLevelEndpoint + | ResolvedUrlPath.TopLevelWebhook | ResolvedUrlPath.ApiSubpackage - | ResolvedUrlPath.Endpoint; + | ResolvedUrlPath.Endpoint + | ResolvedUrlPath.Webhook; export declare namespace ResolvedUrlPath { export interface Section { @@ -48,6 +50,14 @@ export declare namespace ResolvedUrlPath { endpoint: FernRegistryApiRead.EndpointDefinition; } + export interface TopLevelWebhook { + type: "topLevelWebhook"; + apiSection: FernRegistryDocsRead.ApiSection; + apiSlug: string; + slug: string; + webhook: FernRegistryApiRead.WebhookDefinition; + } + export interface ApiSubpackage { type: "apiSubpackage"; apiSection: FernRegistryDocsRead.ApiSection; @@ -64,4 +74,13 @@ export declare namespace ResolvedUrlPath { endpoint: FernRegistryApiRead.EndpointDefinition; parent: FernRegistryApiRead.ApiDefinitionSubpackage; } + + export interface Webhook { + type: "webhook"; + apiSection: FernRegistryDocsRead.ApiSection; + apiSlug: string; + slug: string; + webhook: FernRegistryApiRead.WebhookDefinition; + parent: FernRegistryApiRead.ApiDefinitionSubpackage; + } } diff --git a/packages/ui/app/src/api-page/ApiPackageContents.tsx b/packages/ui/app/src/api-page/ApiPackageContents.tsx index d513fdb779..d594e58622 100644 --- a/packages/ui/app/src/api-page/ApiPackageContents.tsx +++ b/packages/ui/app/src/api-page/ApiPackageContents.tsx @@ -3,7 +3,8 @@ import { useApiDefinitionContext } from "../api-context/useApiDefinitionContext" import { joinUrlSlugs } from "../docs-context/joinUrlSlugs"; import { Endpoint } from "./endpoints/Endpoint"; import { ApiSubpackage } from "./subpackages/ApiSubpackage"; -import { doesSubpackageHaveEndpointsRecursive } from "./subpackages/doesSubpackageHaveEndpointsRecursive"; +import { doesSubpackageHaveEndpointsOrWebhooksRecursive } from "./subpackages/doesSubpackageHaveEndpointsOrWebhooksRecursive"; +import { Webhook } from "./webhooks/Webhook"; export declare namespace ApiPackageContents { export interface Props { @@ -31,8 +32,17 @@ export const ApiPackageContents: React.FC = ({ package={package_} /> ))} + {package_.webhooks.map((webhook, idx) => ( + + ))} {package_.subpackages.map((subpackageId, idx) => { - if (!doesSubpackageHaveEndpointsRecursive(subpackageId, resolveSubpackageById)) { + if (!doesSubpackageHaveEndpointsOrWebhooksRecursive(subpackageId, resolveSubpackageById)) { return null; } const subpackage = resolveSubpackageById(subpackageId); diff --git a/packages/ui/app/src/api-page/subpackages/doesSubpackageHaveEndpointsRecursive.ts b/packages/ui/app/src/api-page/subpackages/doesSubpackageHaveEndpointsOrWebhooksRecursive.ts similarity index 69% rename from packages/ui/app/src/api-page/subpackages/doesSubpackageHaveEndpointsRecursive.ts rename to packages/ui/app/src/api-page/subpackages/doesSubpackageHaveEndpointsOrWebhooksRecursive.ts index 4e0c372988..da9c8635dc 100644 --- a/packages/ui/app/src/api-page/subpackages/doesSubpackageHaveEndpointsRecursive.ts +++ b/packages/ui/app/src/api-page/subpackages/doesSubpackageHaveEndpointsOrWebhooksRecursive.ts @@ -1,12 +1,12 @@ import * as FernRegistryApiRead from "@fern-fern/registry-browser/api/resources/api/resources/v1/resources/read"; -export function doesSubpackageHaveEndpointsRecursive( +export function doesSubpackageHaveEndpointsOrWebhooksRecursive( subpackageId: FernRegistryApiRead.SubpackageId, resolveSubpackage: (subpackageId: FernRegistryApiRead.SubpackageId) => FernRegistryApiRead.ApiDefinitionSubpackage ): boolean { const subpackage = resolveSubpackage(subpackageId); - if (subpackage.endpoints.length > 0) { + if (subpackage.endpoints.length > 0 || subpackage.webhooks.length > 0) { return true; } - return subpackage.subpackages.some((s) => doesSubpackageHaveEndpointsRecursive(s, resolveSubpackage)); + return subpackage.subpackages.some((s) => doesSubpackageHaveEndpointsOrWebhooksRecursive(s, resolveSubpackage)); } diff --git a/packages/ui/app/src/api-page/webhooks/Webhook.tsx b/packages/ui/app/src/api-page/webhooks/Webhook.tsx new file mode 100644 index 0000000000..45a05adee0 --- /dev/null +++ b/packages/ui/app/src/api-page/webhooks/Webhook.tsx @@ -0,0 +1,28 @@ +import * as FernRegistryApiRead from "@fern-fern/registry-browser/api/resources/api/resources/v1/resources/read"; +import { useApiPageCenterElement } from "../useApiPageCenterElement"; +import { WebhookContextProvider } from "./webhook-context/WebhookContextProvider"; +import { WebhookContent } from "./WebhookContent"; + +export declare namespace Webhook { + export interface Props { + webhook: FernRegistryApiRead.WebhookDefinition; + isLastInApi: boolean; + package: FernRegistryApiRead.ApiDefinitionPackage; + slug: string; + } +} + +export const Webhook: React.FC = ({ webhook, slug, package: package_, isLastInApi }) => { + const { setTargetRef } = useApiPageCenterElement({ slug }); + + return ( + + + + ); +}; diff --git a/packages/ui/app/src/api-page/webhooks/WebhookContent.tsx b/packages/ui/app/src/api-page/webhooks/WebhookContent.tsx new file mode 100644 index 0000000000..78f8222096 --- /dev/null +++ b/packages/ui/app/src/api-page/webhooks/WebhookContent.tsx @@ -0,0 +1,144 @@ +import * as FernRegistryApiRead from "@fern-fern/registry-browser/api/resources/api/resources/v1/resources/read"; +import useSize from "@react-hook/size"; +import classNames from "classnames"; +import { snakeCase } from "lodash-es"; +import React, { useCallback, useRef } from "react"; +import { isSubpackage } from "../../util/package"; +import { JsonPropertyPath } from "../examples/json-example/contexts/JsonPropertyPath"; +import { Markdown } from "../markdown/Markdown"; +import { ApiPageMargins } from "../page-margins/ApiPageMargins"; +import { SubpackageTitle } from "../subpackages/SubpackageTitle"; +import { useWebhookContext } from "./webhook-context/useWebhookContext"; +import { WebhookExample } from "./webhook-examples/WebhookExample"; +import { WebhookHeadersSection } from "./WebhookHeadersSection"; +import { WebhookPayloadSection } from "./WebhookPayloadSection"; +import { WebhookResponseSection } from "./WebhookResponseSection"; +import { WebhookSection } from "./WebhookSection"; + +export declare namespace WebhookContent { + export interface Props { + webhook: FernRegistryApiRead.WebhookDefinition; + package: FernRegistryApiRead.ApiDefinitionPackage; + hideBottomSeparator?: boolean; + setContainerRef: (ref: HTMLElement | null) => void; + } +} + +export const WebhookContent = React.memo(function WebhookContent({ + webhook, + package: package_, + hideBottomSeparator = false, + setContainerRef, +}) { + const { setHoveredPayloadPropertyPath } = useWebhookContext(); + const onHoverPayloadProperty = useCallback( + (jsonPropertyPath: JsonPropertyPath, { isHovering }: { isHovering: boolean }) => { + setHoveredPayloadPropertyPath(isHovering ? jsonPropertyPath : undefined); + }, + [setHoveredPayloadPropertyPath] + ); + + const computeAnchor = useCallback( + ( + attributeType: "payload" | "response", + attribute?: + | FernRegistryApiRead.ObjectProperty + | FernRegistryApiRead.PathParameter + | FernRegistryApiRead.QueryParameter + ) => { + let anchor = ""; + if (isSubpackage(package_)) { + anchor += snakeCase(package_.urlSlug) + "_"; + } + anchor += snakeCase(webhook.id); + anchor += "-" + attributeType; + if (attribute?.key != null) { + anchor += "-" + snakeCase(attribute.key); + } + return anchor; + }, + [package_, webhook] + ); + + const titleSectionRef = useRef(null); + const [, titleSectionHeight] = useSize(titleSectionRef); + + const example = webhook.examples[0]; // TODO: Need a way to show all the examples + + const webhookExample = example ? : null; + + return ( + +
+
+
+ {isSubpackage(package_) && ( +
+ +
+ )} +
+ {webhook.name} +
+
+ {webhook.description != null && {webhook.description}} + + {webhook.headers.length > 0 && ( +
+
+ + + +
+
+ )} + +
+
+ + computeAnchor("payload", property)} + /> + +
+
+ +
+
+ + + +
+
+
+ {titleSectionHeight > 0 && ( +
+ {webhookExample} +
+ )} + +
{webhookExample}
+
+
+ ); +}); diff --git a/packages/ui/app/src/api-page/webhooks/WebhookHeadersSection.tsx b/packages/ui/app/src/api-page/webhooks/WebhookHeadersSection.tsx new file mode 100644 index 0000000000..1cb336e932 --- /dev/null +++ b/packages/ui/app/src/api-page/webhooks/WebhookHeadersSection.tsx @@ -0,0 +1,36 @@ +import type * as FernRegistryApiRead from "@fern-fern/registry-browser/api/resources/api/resources/v1/resources/read"; +import { MonospaceText } from "../../commons/monospace/MonospaceText"; +import { Markdown } from "../markdown/Markdown"; +import { TypeReferenceDefinitions } from "../types/type-reference/TypeReferenceDefinitions"; +import { TypeShorthand } from "../types/type-shorthand/TypeShorthand"; +import { TypeComponentSeparator } from "../types/TypeComponentSeparator"; + +export declare namespace WebhookHeadersSection { + export interface Props { + webhook: FernRegistryApiRead.WebhookDefinition; + } +} + +export const WebhookHeadersSection: React.FC = ({ webhook }) => { + return ( +
+ {webhook.headers.map((header, index) => ( +
+ +
+
+ + {header.key} + +
+ +
+
+ {header.description != null && {header.description}} + +
+
+ ))} +
+ ); +}; diff --git a/packages/ui/app/src/api-page/webhooks/WebhookPayloadSection.tsx b/packages/ui/app/src/api-page/webhooks/WebhookPayloadSection.tsx new file mode 100644 index 0000000000..c85c21c9e3 --- /dev/null +++ b/packages/ui/app/src/api-page/webhooks/WebhookPayloadSection.tsx @@ -0,0 +1,53 @@ +import * as FernRegistryApiRead from "@fern-fern/registry-browser/api/resources/api/resources/v1/resources/read"; +import { visitDiscriminatedUnion } from "@fern-ui/core-utils"; +import { JsonPropertyPath } from "../examples/json-example/contexts/JsonPropertyPath"; +import { TypeDefinition } from "../types/type-definition/TypeDefinition"; +import { TypeReferenceDefinitions } from "../types/type-reference/TypeReferenceDefinitions"; +import { TypeShorthand } from "../types/type-shorthand/TypeShorthand"; + +export declare namespace WebhookPayloadSection { + export interface Props { + payload: FernRegistryApiRead.WebhookPayload; + onHoverProperty?: (path: JsonPropertyPath, opts: { isHovering: boolean }) => void; + getPropertyAnchor?: (property: FernRegistryApiRead.ObjectProperty) => string; + } +} + +export const WebhookPayloadSection: React.FC = ({ + payload, + onHoverProperty, + getPropertyAnchor, +}) => { + return ( +
+
+ {"The payload of this webhook request is "} + {visitDiscriminatedUnion(payload.type, "type")._visit({ + object: () => "an object", + reference: (type) => , + _other: () => "unknown", + })} + . +
+ {visitDiscriminatedUnion(payload.type, "type")._visit({ + object: (object) => ( + + ), + reference: (type) => ( + + ), + _other: () => null, + })} +
+ ); +}; diff --git a/packages/ui/app/src/api-page/webhooks/WebhookResponseSection.tsx b/packages/ui/app/src/api-page/webhooks/WebhookResponseSection.tsx new file mode 100644 index 0000000000..ed117242fa --- /dev/null +++ b/packages/ui/app/src/api-page/webhooks/WebhookResponseSection.tsx @@ -0,0 +1,19 @@ +import { Markdown } from "../markdown/Markdown"; + +const STATUS_200_TEXT = "Return a 200 status to indicate that the data was received successfully."; + +export const WebhookResponseSection: React.FC = () => { + return ( +
+
+
+
{200}
+
any
+
+
+ {STATUS_200_TEXT} +
+
+
+ ); +}; diff --git a/packages/ui/app/src/api-page/webhooks/WebhookSection.tsx b/packages/ui/app/src/api-page/webhooks/WebhookSection.tsx new file mode 100644 index 0000000000..f83bfbd1f8 --- /dev/null +++ b/packages/ui/app/src/api-page/webhooks/WebhookSection.tsx @@ -0,0 +1,29 @@ +import { AbsolutelyPositionedAnchor } from "../../commons/AbsolutelyPositionedAnchor"; +import { Markdown } from "../markdown/Markdown"; + +export declare namespace WebhookSection { + export type Props = React.PropsWithChildren<{ + title: string; + description?: string; + anchor: string; + }>; +} + +export const WebhookSection: React.FC = ({ title, description, anchor, children }) => { + return ( +
+
+ +
+ {title} +
+
+ {description != null && ( +
+ {description} +
+ )} +
{children}
+
+ ); +}; diff --git a/packages/ui/app/src/api-page/webhooks/webhook-context/WebhookContext.ts b/packages/ui/app/src/api-page/webhooks/webhook-context/WebhookContext.ts new file mode 100644 index 0000000000..2f4aede88c --- /dev/null +++ b/packages/ui/app/src/api-page/webhooks/webhook-context/WebhookContext.ts @@ -0,0 +1,11 @@ +import { createContext } from "react"; +import { JsonPropertyPath } from "../../examples/json-example/contexts/JsonPropertyPath"; + +export const WebhookContext = createContext<() => WebhookContextValue>(() => { + throw new Error("WebhookContextProvider not found in tree"); +}); + +export interface WebhookContextValue { + hoveredPayloadPropertyPath: JsonPropertyPath | undefined; + setHoveredPayloadPropertyPath: (path: JsonPropertyPath | undefined) => void; +} diff --git a/packages/ui/app/src/api-page/webhooks/webhook-context/WebhookContextProvider.tsx b/packages/ui/app/src/api-page/webhooks/webhook-context/WebhookContextProvider.tsx new file mode 100644 index 0000000000..3bd2d237ec --- /dev/null +++ b/packages/ui/app/src/api-page/webhooks/webhook-context/WebhookContextProvider.tsx @@ -0,0 +1,17 @@ +import { useCallback, useState } from "react"; +import { JsonPropertyPath } from "../../examples/json-example/contexts/JsonPropertyPath"; +import { WebhookContext, WebhookContextValue } from "./WebhookContext"; + +export const WebhookContextProvider: React.FC = ({ children }) => { + const [hoveredPayloadPropertyPath, setHoveredPayloadPropertyPath] = useState(); + + const contextValue = useCallback( + (): WebhookContextValue => ({ + hoveredPayloadPropertyPath, + setHoveredPayloadPropertyPath, + }), + [hoveredPayloadPropertyPath, setHoveredPayloadPropertyPath] + ); + + return {children}; +}; diff --git a/packages/ui/app/src/api-page/webhooks/webhook-context/useWebhookContext.ts b/packages/ui/app/src/api-page/webhooks/webhook-context/useWebhookContext.ts new file mode 100644 index 0000000000..0ce89a969d --- /dev/null +++ b/packages/ui/app/src/api-page/webhooks/webhook-context/useWebhookContext.ts @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import { WebhookContext, WebhookContextValue } from "./WebhookContext"; + +export function useWebhookContext(): WebhookContextValue { + return useContext(WebhookContext)(); +} diff --git a/packages/ui/app/src/api-page/webhooks/webhook-examples/WebhookExample.tsx b/packages/ui/app/src/api-page/webhooks/webhook-examples/WebhookExample.tsx new file mode 100644 index 0000000000..0b40fd5cc3 --- /dev/null +++ b/packages/ui/app/src/api-page/webhooks/webhook-examples/WebhookExample.tsx @@ -0,0 +1,40 @@ +import * as FernRegistryApiRead from "@fern-fern/registry-browser/api/resources/api/resources/v1/resources/read"; +import { JsonExample } from "../../examples/json-example/JsonExample"; +import { TitledExample } from "../../examples/TitledExample"; +import { useWebhookContext } from "../webhook-context/useWebhookContext"; + +export declare namespace WebhookExample { + export interface Props { + example: FernRegistryApiRead.ExampleWebhookPayload; + } +} + +export const WebhookExample: React.FC = ({ example }) => { + const { hoveredPayloadPropertyPath } = useWebhookContext(); + + return ( +
+
+
+ {example.payload != null && ( + { + e.stopPropagation(); + }} + > + {(parent) => ( + + )} + + )} +
+
+
+ ); +}; diff --git a/packages/ui/app/src/bottom-navigation-buttons/BottomNavigationButton.tsx b/packages/ui/app/src/bottom-navigation-buttons/BottomNavigationButton.tsx index 6b3cdd3da9..d5430a44d0 100644 --- a/packages/ui/app/src/bottom-navigation-buttons/BottomNavigationButton.tsx +++ b/packages/ui/app/src/bottom-navigation-buttons/BottomNavigationButton.tsx @@ -50,6 +50,9 @@ export const BottomNavigationButton: React.FC = ({ case "endpoint": case "topLevelEndpoint": return path.apiSection.title; + case "webhook": + case "topLevelWebhook": + return path.apiSection.title; default: assertNever(path); } diff --git a/packages/ui/app/src/docs-context/DocsContextProvider.tsx b/packages/ui/app/src/docs-context/DocsContextProvider.tsx index f46b0e94ea..f20f6b43ec 100644 --- a/packages/ui/app/src/docs-context/DocsContextProvider.tsx +++ b/packages/ui/app/src/docs-context/DocsContextProvider.tsx @@ -41,8 +41,10 @@ export const DocsContextProvider: React.FC = ({ switch (resolvedUrlPath.type) { case "clientLibraries": case "endpoint": + case "webhook": case "mdx-page": case "topLevelEndpoint": + case "topLevelWebhook": case "apiSubpackage": return getFullSlug(resolvedUrlPath.slug); case "api": diff --git a/packages/ui/app/src/docs/DocsMainContent.tsx b/packages/ui/app/src/docs/DocsMainContent.tsx index 9c03a50754..9a678a3e93 100644 --- a/packages/ui/app/src/docs/DocsMainContent.tsx +++ b/packages/ui/app/src/docs/DocsMainContent.tsx @@ -23,7 +23,9 @@ export const DocsMainContent: React.FC = () => { case "clientLibraries": case "apiSubpackage": case "endpoint": + case "webhook": case "topLevelEndpoint": + case "topLevelWebhook": return ( { case "endpoint": case "topLevelEndpoint": return ; + case "webhook": + case "topLevelWebhook": + return resolvedPathFromUrl.webhook.name ?? ""; case "mdx-page": return resolvedPathFromUrl.page.title; case "clientLibraries": diff --git a/packages/ui/app/src/sidebar/ApiPackageSidebarSectionContents.tsx b/packages/ui/app/src/sidebar/ApiPackageSidebarSectionContents.tsx index 341ed5ad39..c4258deda6 100644 --- a/packages/ui/app/src/sidebar/ApiPackageSidebarSectionContents.tsx +++ b/packages/ui/app/src/sidebar/ApiPackageSidebarSectionContents.tsx @@ -2,6 +2,7 @@ import * as FernRegistryApiRead from "@fern-fern/registry-browser/api/resources/ import { joinUrlSlugs } from "../docs-context/joinUrlSlugs"; import { ApiSubpackages } from "./ApiSubpackages"; import { EndpointSidebarItem } from "./EndpointSidebarItem"; +import { WebhookSidebarItem } from "./WebhookSidebarItem"; export declare namespace ApiPackageSidebarSectionContents { export interface Props { @@ -23,6 +24,9 @@ export const ApiPackageSidebarSectionContents: React.FC ))} + {package_.webhooks.map((webhook, webhookIndex) => ( + + ))} ); diff --git a/packages/ui/app/src/sidebar/ApiSubpackageSidebarSection.tsx b/packages/ui/app/src/sidebar/ApiSubpackageSidebarSection.tsx index 9b64d21d5c..b35679023a 100644 --- a/packages/ui/app/src/sidebar/ApiSubpackageSidebarSection.tsx +++ b/packages/ui/app/src/sidebar/ApiSubpackageSidebarSection.tsx @@ -2,7 +2,7 @@ import * as FernRegistryApiRead from "@fern-fern/registry-browser/api/resources/ import { useContext, useMemo } from "react"; import { HiOutlineChevronDown } from "react-icons/hi2"; import { useApiDefinitionContext } from "../api-context/useApiDefinitionContext"; -import { doesSubpackageHaveEndpointsRecursive } from "../api-page/subpackages/doesSubpackageHaveEndpointsRecursive"; +import { doesSubpackageHaveEndpointsOrWebhooksRecursive } from "../api-page/subpackages/doesSubpackageHaveEndpointsOrWebhooksRecursive"; import { SubpackageTitle } from "../api-page/subpackages/SubpackageTitle"; import { useDocsContext } from "../docs-context/useDocsContext"; import { ApiPackageSidebarSectionContents } from "./ApiPackageSidebarSectionContents"; @@ -22,8 +22,8 @@ export const ApiSubpackageSidebarSection: React.FC doesSubpackageHaveEndpointsRecursive(subpackage.subpackageId, resolveSubpackageById), + const hasEndpointsOrWebhooks = useMemo( + () => doesSubpackageHaveEndpointsOrWebhooksRecursive(subpackage.subpackageId, resolveSubpackageById), [resolveSubpackageById, subpackage.subpackageId] ); @@ -32,7 +32,7 @@ export const ApiSubpackageSidebarSection: React.FC = ({ slug, webhook }) => { + return } />; +}; diff --git a/packages/ui/fe-bundle/package.json b/packages/ui/fe-bundle/package.json index 4bc1080ec2..d69e2f0bf3 100644 --- a/packages/ui/fe-bundle/package.json +++ b/packages/ui/fe-bundle/package.json @@ -38,7 +38,7 @@ "lint:fern-prod": "yarn compile && yarn env:fern-prod next lint" }, "dependencies": { - "@fern-fern/registry-browser": "0.14.1-1-gf6c42e8", + "@fern-fern/registry-browser": "0.14.1-3-g6c09dff", "@fern-ui/core-utils": "workspace:*", "@fern-ui/theme": "workspace:*", "@fern-ui/ui": "workspace:*", diff --git a/packages/ui/fe-bundle/src/url-path-resolver/UrlPathResolver.ts b/packages/ui/fe-bundle/src/url-path-resolver/UrlPathResolver.ts index ff4401a557..ca750c3157 100644 --- a/packages/ui/fe-bundle/src/url-path-resolver/UrlPathResolver.ts +++ b/packages/ui/fe-bundle/src/url-path-resolver/UrlPathResolver.ts @@ -77,6 +77,14 @@ export class UrlPathResolver { slug: node.slug, endpoint: node.endpoint, }; + case "topLevelWebhook": + return { + type: "topLevelWebhook", + apiSection: node.apiSection, + apiSlug: node.apiSlug, + slug: node.slug, + webhook: node.webhook, + }; case "apiSubpackage": return { type: "apiSubpackage", @@ -94,6 +102,15 @@ export class UrlPathResolver { parent: node.parent, endpoint: node.endpoint, }; + case "webhook": + return { + type: "webhook", + apiSection: node.apiSection, + apiSlug: node.apiSlug, + slug: node.slug, + parent: node.parent, + webhook: node.webhook, + }; default: assertNever(node); } diff --git a/packages/ui/fe-bundle/src/url-path-resolver/UrlSlugTree.ts b/packages/ui/fe-bundle/src/url-path-resolver/UrlSlugTree.ts index 14edc51ab3..cfbcad1cf2 100644 --- a/packages/ui/fe-bundle/src/url-path-resolver/UrlSlugTree.ts +++ b/packages/ui/fe-bundle/src/url-path-resolver/UrlSlugTree.ts @@ -85,8 +85,10 @@ export class UrlSlugTree { break; case "clientLibraries": case "endpoint": + case "webhook": case "page": case "topLevelEndpoint": + case "topLevelWebhook": break; default: assertNever(node); @@ -120,7 +122,9 @@ export class UrlSlugTree { case "clientLibraries": case "page": case "topLevelEndpoint": + case "topLevelWebhook": case "endpoint": + case "webhook": return undefined; default: assertNever(child); @@ -248,13 +252,26 @@ export class UrlSlugTree { }, {} ), + ...apiDefinition.rootPackage.webhooks.reduce>( + (acc, topLevelWebhook, index) => { + acc[topLevelWebhook.urlSlug] = this.constructTopLevelWebhookNode({ + apiSection, + topLevelWebhook, + apiSlug: slug, + isFirstItemInApi: index === 0, + }); + return acc; + }, + {} + ), ...this.constructSlugToApiSubpackageRecord({ apiDefinition, apiSection, package_: apiDefinition.rootPackage, apiSlug: slug, slugInsideApi: "", - isFirstItemInApi: apiDefinition.rootPackage.endpoints.length === 0, + isFirstItemInApi: + apiDefinition.rootPackage.endpoints.length === 0 || apiDefinition.rootPackage.webhooks.length === 0, }), }); @@ -281,7 +298,11 @@ export class UrlSlugTree { if (subpackage == null) { throw new Error("Subpackage does not exist: " + subpackageId); } - if (doesSubpackageHaveEndpointsRecursive(subpackageId, (id) => resolveSubpackage(apiDefinition, id))) { + if ( + doesSubpackageHaveEndpointsOrWebhooksRecursive(subpackageId, (id) => + resolveSubpackage(apiDefinition, id) + ) + ) { const resolvedSubpackage = resolveSubpackage(apiDefinition, subpackageId); acc[subpackage.urlSlug] = this.constructApiSubpackageNode({ apiDefinition, @@ -317,6 +338,27 @@ export class UrlSlugTree { }; } + private constructTopLevelWebhookNode({ + apiSection, + topLevelWebhook, + isFirstItemInApi, + apiSlug, + }: { + apiSection: FernRegistryDocsRead.ApiSection; + topLevelWebhook: FernRegistryApiRead.WebhookDefinition; + isFirstItemInApi: boolean; + apiSlug: string; + }): UrlSlugTreeNode.TopLevelWebhook { + return { + type: "topLevelWebhook", + apiSection, + apiSlug, + slug: joinUrlSlugs(apiSlug, topLevelWebhook.urlSlug), + webhook: topLevelWebhook, + isFirstItemInApi, + }; + } + private constructApiSubpackageNode({ apiDefinition, apiSection, @@ -354,6 +396,12 @@ export class UrlSlugTree { apiSlug, slugInsideApi, }), + ...this.constructSlugToWebhookRecord({ + apiSection, + subpackage, + apiSlug, + slugInsideApi, + }), }, }; } @@ -381,6 +429,29 @@ export class UrlSlugTree { }, {}); } + private constructSlugToWebhookRecord({ + apiSection, + subpackage, + apiSlug, + slugInsideApi, + }: { + apiSection: FernRegistryDocsRead.ApiSection; + subpackage: FernRegistryApiRead.ApiDefinitionSubpackage; + apiSlug: string; + slugInsideApi: string; + }): Record { + return subpackage.webhooks.reduce>((acc, webhook) => { + acc[webhook.urlSlug] = this.constructWebhookNode({ + apiSection, + apiSlug, + slugInsideApi: joinUrlSlugs(slugInsideApi, webhook.urlSlug), + parent: subpackage, + webhook, + }); + return acc; + }, {}); + } + private constructEndpointNode({ apiSection, apiSlug, @@ -404,6 +475,29 @@ export class UrlSlugTree { }; } + private constructWebhookNode({ + apiSection, + apiSlug, + slugInsideApi, + parent, + webhook, + }: { + apiSection: FernRegistryDocsRead.ApiSection; + apiSlug: string; + slugInsideApi: string; + parent: FernRegistryApiRead.ApiDefinitionSubpackage; + webhook: FernRegistryApiRead.WebhookDefinition; + }): UrlSlugTreeNode.Webhook { + return { + type: "webhook", + apiSection, + apiSlug, + slug: joinUrlSlugs(apiSlug, slugInsideApi), + parent, + webhook, + }; + } + private inOrderTraverse(nodes: UrlSlugTreeNode[]): UrlSlugTreeNode[] { const inOrder: UrlSlugTreeNode[] = []; @@ -418,7 +512,9 @@ export class UrlSlugTree { case "clientLibraries": case "page": case "topLevelEndpoint": + case "topLevelWebhook": case "endpoint": + case "webhook": break; default: assertNever(node); @@ -437,8 +533,10 @@ export type UrlSlugTreeNode = | UrlSlugTreeNode.Api | UrlSlugTreeNode.ClientLibraries | UrlSlugTreeNode.TopLevelEndpoint + | UrlSlugTreeNode.TopLevelWebhook | UrlSlugTreeNode.ApiSubpackage - | UrlSlugTreeNode.Endpoint; + | UrlSlugTreeNode.Endpoint + | UrlSlugTreeNode.Webhook; export declare namespace UrlSlugTreeNode { export interface Section extends BaseNode { @@ -454,7 +552,7 @@ export declare namespace UrlSlugTreeNode { export interface Api extends BaseNode, BaseApiNode { type: "api"; - children: Record; + children: Record; } export interface ClientLibraries extends BaseNode, BaseApiNode { @@ -468,11 +566,17 @@ export declare namespace UrlSlugTreeNode { isFirstItemInApi: boolean; } + export interface TopLevelWebhook extends BaseNode, BaseApiNode { + type: "topLevelWebhook"; + webhook: FernRegistryApiRead.WebhookDefinition; + isFirstItemInApi: boolean; + } + export interface ApiSubpackage extends BaseNode, BaseApiNode { type: "apiSubpackage"; subpackage: FernRegistryApiRead.ApiDefinitionSubpackage; isFirstItemInApi: boolean; - children: Record; + children: Record; } export interface Endpoint extends BaseNode, BaseApiNode { @@ -481,6 +585,12 @@ export declare namespace UrlSlugTreeNode { parent: FernRegistryApiRead.ApiDefinitionSubpackage; } + export interface Webhook extends BaseNode, BaseApiNode { + type: "webhook"; + webhook: FernRegistryApiRead.WebhookDefinition; + parent: FernRegistryApiRead.ApiDefinitionSubpackage; + } + export interface BaseNode { slug: string; } @@ -504,6 +614,7 @@ function getIndexOfFirstNavigatableItem(nodes: UrlSlugTreeNode[], { startingAt } case "clientLibraries": return startingAt + i; case "topLevelEndpoint": + case "topLevelWebhook": case "apiSubpackage": if (node.isFirstItemInApi) { return startingAt + i; @@ -511,6 +622,7 @@ function getIndexOfFirstNavigatableItem(nodes: UrlSlugTreeNode[], { startingAt } break; case "api": case "endpoint": + case "webhook": case "section": break; default: @@ -525,8 +637,10 @@ function getApiSlug(node: UrlSlugTreeNode): string | undefined { case "api": case "clientLibraries": case "endpoint": + case "webhook": case "apiSubpackage": case "topLevelEndpoint": + case "topLevelWebhook": return node.apiSlug; case "section": case "page": @@ -569,13 +683,13 @@ function resolveSubpackage( } } -function doesSubpackageHaveEndpointsRecursive( +function doesSubpackageHaveEndpointsOrWebhooksRecursive( subpackageId: FernRegistryApiRead.SubpackageId, resolveSubpackage: (subpackageId: FernRegistryApiRead.SubpackageId) => FernRegistryApiRead.ApiDefinitionSubpackage ): boolean { const subpackage = resolveSubpackage(subpackageId); - if (subpackage.endpoints.length > 0) { + if (subpackage.endpoints.length > 0 || subpackage.webhooks.length > 0) { return true; } - return subpackage.subpackages.some((s) => doesSubpackageHaveEndpointsRecursive(s, resolveSubpackage)); + return subpackage.subpackages.some((s) => doesSubpackageHaveEndpointsOrWebhooksRecursive(s, resolveSubpackage)); } diff --git a/yarn.lock b/yarn.lock index 6901228aa3..2a6151544e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1833,16 +1833,16 @@ __metadata: languageName: node linkType: hard -"@fern-fern/registry-browser@npm:0.14.1-1-gf6c42e8": - version: 0.14.1-1-gf6c42e8 - resolution: "@fern-fern/registry-browser@npm:0.14.1-1-gf6c42e8" +"@fern-fern/registry-browser@npm:0.14.1-3-g6c09dff": + version: 0.14.1-3-g6c09dff + resolution: "@fern-fern/registry-browser@npm:0.14.1-3-g6c09dff" dependencies: "@types/url-join": 4.0.1 "@ungap/url-search-params": 0.2.2 axios: 1.4.0 js-base64: 3.7.2 url-join: 4.0.1 - checksum: ee946a2b125159b16bd44a6fc4f2950107cd8fd8c6daa2ccc4d49dcb2028f318b2b8a5fadd2f13980c77f85b66c0a403dc8e1997cd19087d337cbb2a0da810a9 + checksum: 506a2449ebb6e9d0d9f22a4245f5769c69bab834bccbe16e5e4b66cf4ddf04f4e5dbfe19a9554a7be20dc6d7e105ff24891c37adddf089556660b60f2710635b languageName: node linkType: hard @@ -1919,7 +1919,7 @@ __metadata: version: 0.0.0-use.local resolution: "@fern-ui/fe-bundle@workspace:packages/ui/fe-bundle" dependencies: - "@fern-fern/registry-browser": 0.14.1-1-gf6c42e8 + "@fern-fern/registry-browser": 0.14.1-3-g6c09dff "@fern-ui/core-utils": "workspace:*" "@fern-ui/theme": "workspace:*" "@fern-ui/ui": "workspace:*" @@ -2154,7 +2154,7 @@ __metadata: "@blueprintjs/icons": ^4.4.0 "@blueprintjs/popover2": ^1.8.0 "@blueprintjs/select": ^4.4.2 - "@fern-fern/registry-browser": 0.14.1-1-gf6c42e8 + "@fern-fern/registry-browser": 0.14.1-3-g6c09dff "@fern-ui/core-utils": "workspace:*" "@fern-ui/react-commons": "workspace:*" "@fern-ui/routing-utils": "workspace:*"