From bdd6303bccdfb6b1054abd12c0a495c69295cea4 Mon Sep 17 00:00:00 2001 From: "Nolann B." <100787331+nolannbiron@users.noreply.github.com> Date: Mon, 24 Feb 2025 11:33:47 +0100 Subject: [PATCH] Rearrange item types in OpenAPI blocks (#2862) --- .../components/DocumentView/OpenAPI/style.css | 12 +- packages/react-openapi/src/OpenAPISchema.tsx | 119 +++++++++--------- .../react-openapi/src/OpenAPISchemaName.tsx | 42 ++++++- packages/react-openapi/src/utils.ts | 6 +- 4 files changed, 108 insertions(+), 71 deletions(-) diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css index 170c61cdc..cdcc2209c 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css @@ -21,7 +21,7 @@ } .openapi-deprecated-sunset-date { - @apply font-semibold font-mono; + @apply font-semibold font-mono truncate; } .openapi-description.openapi-markdown { @@ -181,7 +181,7 @@ .openapi-schema-name { /* To make double click on the property name select only the name, we disable selection on the parent and re-enable it on the children. */ - @apply select-none flex gap-2.5 items-baseline text-sm; + @apply select-none flex gap-x-2.5 items-baseline text-sm flex-wrap; } .openapi-schema-name .openapi-deprecated { @@ -571,11 +571,15 @@ /* Disclosure */ .openapi-disclosure-trigger { - @apply transition-all duration-300 hover:text-tint-strong rounded-2xl border border-tint-subtle px-2.5 py-1 text-[0.813rem] text-tint flex flex-row items-center gap-1.5 -outline-offset-1; + @apply transition-all truncate duration-300 max-w-full hover:text-tint-strong rounded-2xl border border-tint-subtle px-2.5 py-1 text-[0.813rem] text-tint flex flex-row items-center gap-1.5 -outline-offset-1; +} + +.openapi-disclosure-trigger span { + @apply truncate; } .openapi-disclosure svg { - @apply size-3 transition-transform duration-300; + @apply size-3 shrink-0 transition-transform duration-300; } .openapi-disclosure-trigger[aria-expanded='true'] svg { diff --git a/packages/react-openapi/src/OpenAPISchema.tsx b/packages/react-openapi/src/OpenAPISchema.tsx index d364a96af..445a45c49 100644 --- a/packages/react-openapi/src/OpenAPISchema.tsx +++ b/packages/react-openapi/src/OpenAPISchema.tsx @@ -47,23 +47,6 @@ export function OpenAPISchemaProperty( ? null : getSchemaAlternatives(schema, new Set(circularRefs.keys())); - if ((properties && properties.length > 0) || schema.type === 'object') { - return ( - - - {properties && properties.length > 0 ? ( - - - - ) : null} - - ); - } - if (alternatives?.[0]?.length) { return ( @@ -80,6 +63,23 @@ export function OpenAPISchemaProperty( ); } + if ((properties && properties.length > 0) || schema.type === 'object') { + return ( + + + {properties && properties.length > 0 ? ( + + + + ) : null} + + ); + } + return ( @@ -166,16 +166,24 @@ function OpenAPISchemaAlternative(props: { const { schema, circularRefs, context } = props; const id = useId(); const subProperties = getSchemaProperties(schema); + const description = resolveDescription(schema); return ( - - - + <> + {description ? ( + + ) : null} + + + + ); } @@ -219,7 +227,7 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) { const shouldDisplayExample = (schema: OpenAPIV3.SchemaObject): boolean => { return ( - typeof schema.example === 'string' || + (typeof schema.example === 'string' && !!schema.example) || typeof schema.example === 'number' || typeof schema.example === 'boolean' || (Array.isArray(schema.example) && schema.example.length > 0) || @@ -234,10 +242,10 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) { return (
{schema['x-deprecated-sunset'] ? (
@@ -276,17 +284,6 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) { * Get the sub-properties of a schema. */ function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISchemaPropertyEntry[] { - if (schema.allOf) { - return schema.allOf.reduce((acc, subSchema) => { - const properties = getSchemaProperties(subSchema) ?? [ - { - schema: subSchema, - }, - ]; - return [...acc, ...properties]; - }, [] as OpenAPISchemaPropertyEntry[]); - } - // check array AND schema.items as this is sometimes null despite what the type indicates if (schema.type === 'array' && !!schema.items) { const items = schema.items; @@ -295,6 +292,11 @@ function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISche return itemProperties; } + // If the items are a primitive type, we don't need to display them + if (['string', 'number', 'boolean', 'integer'].includes(items.type) && !items.enum) { + return null; + } + return [ { propertyName: 'items', @@ -351,8 +353,7 @@ export function getSchemaAlternatives( } if (schema.allOf) { - // allOf is managed in `getSchemaProperties` - return null; + return [flattenAlternatives('allOf', schema.allOf, downAncestors), schema.discriminator]; } return null; @@ -378,11 +379,6 @@ export function getSchemaTitle( /** If the title is inferred in a oneOf with discriminator, we can use it to optimize the title */ discriminator?: OpenAPIV3.DiscriminatorObject, ): string { - if (schema.title) { - // If the schema has a title, use it - return schema.title; - } - // Try using the discriminator if (discriminator?.propertyName && schema.properties) { const discriminatorProperty = schema.properties[discriminator.propertyName]; @@ -419,21 +415,22 @@ export function getSchemaTitle( type = 'not'; } - if (schema.minimum || schema.minLength) { - type += ` · min: ${schema.minimum || schema.minLength}`; - } - - if (schema.maximum || schema.maxLength) { - type += ` · max: ${schema.maximum || schema.maxLength}`; - } - - if (schema.default) { - type += ` · default: ${schema.default}`; - } - - if (schema.nullable) { - type = `${type} | nullable`; - } - return type; } + +function getDisclosureLabel(schema: OpenAPIV3.SchemaObject): string | undefined { + if (schema.type === 'array' && !!schema.items) { + if (schema.items.oneOf) { + return 'available items'; + } + + // Fallback to "child attributes" for enums and objects + if (schema.items.enum || schema.items.type === 'object') { + return; + } + + return schema.items.title ?? schema.title ?? getSchemaTitle(schema.items); + } + + return schema.title; +} diff --git a/packages/react-openapi/src/OpenAPISchemaName.tsx b/packages/react-openapi/src/OpenAPISchemaName.tsx index c3ff40e26..bab615823 100644 --- a/packages/react-openapi/src/OpenAPISchemaName.tsx +++ b/packages/react-openapi/src/OpenAPISchemaName.tsx @@ -1,8 +1,10 @@ +import { OpenAPIV3 } from '@gitbook/openapi-parser'; + interface OpenAPISchemaNameProps { + schema?: OpenAPIV3.SchemaObject; propertyName?: string | JSX.Element; required?: boolean; type?: string; - deprecated?: boolean; } /** @@ -10,18 +12,48 @@ interface OpenAPISchemaNameProps { * It includes the property name, type, required and deprecated status. */ export function OpenAPISchemaName(props: OpenAPISchemaNameProps): JSX.Element { - const { type, propertyName, required, deprecated } = props; + const { schema, type, propertyName, required } = props; + + const additionalItems = schema && getAdditionalItems(schema); return (
{propertyName ? ( - + {propertyName} ) : null} - {type ? {type} : null} + + {type ? {type} : null} + {additionalItems ? ( + {additionalItems} + ) : null} + {required ? required : null} - {deprecated ? Deprecated : null} + {schema?.deprecated ? Deprecated : null}
); } + +function getAdditionalItems(schema: OpenAPIV3.SchemaObject): string { + let additionalItems = ''; + + if (schema.minimum || schema.minLength) { + additionalItems += ` · min: ${schema.minimum || schema.minLength}`; + } + + if (schema.maximum || schema.maxLength) { + additionalItems += ` · max: ${schema.maximum || schema.maxLength}`; + } + + // If the schema has a default value, we display it + if (typeof schema.default !== 'undefined') { + additionalItems += ` · default: ${schema.default}`; + } + + if (schema.nullable) { + additionalItems = ` | nullable`; + } + + return additionalItems; +} diff --git a/packages/react-openapi/src/utils.ts b/packages/react-openapi/src/utils.ts index 5130c35ab..664bd43f7 100644 --- a/packages/react-openapi/src/utils.ts +++ b/packages/react-openapi/src/utils.ts @@ -13,7 +13,11 @@ export function createStateKey(key: string, scope?: string) { /** * Resolve the description of an object. */ -export function resolveDescription(object: AnyObject) { +export function resolveDescription(object: OpenAPIV3.SchemaObject | AnyObject) { + if ('items' in object && object.items) { + return resolveDescription(object.items); + } + return 'x-gitbook-description-html' in object && typeof object['x-gitbook-description-html'] === 'string' ? object['x-gitbook-description-html'].trim()