feat: Optionally render object schema title and descriptions

This commit is contained in:
Leo Liang 2024-01-05 09:50:16 +00:00
parent 21b961dffa
commit eb7227f85f
9 changed files with 242 additions and 159 deletions

View File

@ -6,7 +6,7 @@ Each deployment type has documentation on how to configure options for that type
**Versions: 2.x**
{% admonition type="success" name="Client-side configuration" %}
{% admonition type="success" name="Client-side configuration" %}
Using Redoc as a standalone (HTML or React) tool, these options must be presented in [kebab case](https://en.wikipedia.org/wiki/Letter_case#Kebab_case).
For example, `scrollYOffset` becomes `scroll-y-offset`, and `expandResponses` becomes `expand-responses`.
@ -66,6 +66,14 @@ If set to `true`, the pattern is not shown in the schema.
Hides the schema title next to to the type.
### hideObjectTitle
Hides the object title in the schema.
### hideObjectDescription
Hides the object description in the schema.
### hideSecuritySection
Hides the Security panel section.

View File

@ -104,7 +104,8 @@ export class Field extends React.Component<FieldProps> {
schema={field.schema}
skipReadOnly={this.props.skipReadOnly}
skipWriteOnly={this.props.skipWriteOnly}
showTitle={this.props.showTitle}
hideObjectTitle={this.props.hideObjectTitle}
hideObjectDescription={this.props.hideObjectDescription}
level={this.props.level}
/>
</InnerPropertiesWrap>

View File

@ -1,6 +1,7 @@
import * as React from 'react';
import { DropdownOrLabel, DropdownOrLabelProps } from '../DropdownOrLabel/DropdownOrLabel';
import { ParametersGroup } from './ParametersGroup';
import { OptionsContext } from '../OptionsProvider';
import { UnderlinedHeader } from '../../common-elements';
@ -29,6 +30,8 @@ export interface ParametersProps {
const PARAM_PLACES = ['path', 'query', 'cookie', 'header'];
export class Parameters extends React.PureComponent<ParametersProps> {
static contextType = OptionsContext;
orderParams(params: FieldModel[]): Record<string, FieldModel[]> {
const res = {};
params.forEach(param => {
@ -38,6 +41,7 @@ export class Parameters extends React.PureComponent<ParametersProps> {
}
render() {
const { hideObjectTitle, hideObjectDescription } = this.context;
const { body, parameters = [] } = this.props;
if (body === undefined && parameters === undefined) {
return null;
@ -63,6 +67,8 @@ export class Parameters extends React.PureComponent<ParametersProps> {
content={bodyContent}
description={bodyDescription}
bodyRequired={bodyRequired}
hideObjectTitle={hideObjectTitle}
hideObjectDescription={hideObjectDescription}
/>
)}
</>
@ -90,8 +96,10 @@ export function BodyContent(props: {
content: MediaContentModel;
description?: string;
bodyRequired?: boolean;
hideObjectTitle?: boolean;
hideObjectDescription?: boolean;
}): JSX.Element {
const { content, description, bodyRequired } = props;
const { content, description, bodyRequired, hideObjectTitle, hideObjectDescription } = props;
const { isRequestType } = content;
return (
<MediaTypesSwitch
@ -108,6 +116,8 @@ export function BodyContent(props: {
<Schema
skipReadOnly={isRequestType}
skipWriteOnly={!isRequestType}
hideObjectTitle={hideObjectTitle}
hideObjectDescription={hideObjectDescription}
key="schema"
schema={schema}
/>

View File

@ -11,9 +11,13 @@ import { Extensions } from '../Fields/Extensions';
import { Markdown } from '../Markdown/Markdown';
import { ResponseHeaders } from './ResponseHeaders';
import { ConstraintsView } from '../Fields/FieldConstraints';
import { OptionsContext } from '../OptionsProvider';
export class ResponseDetails extends React.PureComponent<{ response: ResponseModel }> {
static contextType = OptionsContext;
render() {
const { hideObjectTitle, hideObjectDescription } = this.context;
const { description, extensions, headers, content } = this.props.response;
return (
<>
@ -27,7 +31,13 @@ export class ResponseDetails extends React.PureComponent<{ response: ResponseMod
{schema?.type === 'object' && (
<ConstraintsView constraints={schema?.constraints || []} />
)}
<Schema skipWriteOnly={true} key="schema" schema={schema} />
<Schema
hideObjectTitle={hideObjectTitle}
hideObjectDescription={hideObjectDescription}
skipWriteOnly={true}
key="schema"
schema={schema}
/>
</>
);
}}

View File

@ -1,9 +1,13 @@
import { observer } from 'mobx-react';
import * as React from 'react';
import styled from '../../styled-components';
import { H3 } from '../../common-elements/headers';
import { Markdown } from '../Markdown/Markdown';
import { SchemaModel } from '../../services/models';
import { PropertiesTable, PropertiesTableCaption } from '../../common-elements/fields-layout';
import { PropertiesTable } from '../../common-elements/fields-layout';
import { Field } from '../Fields/Field';
import { DiscriminatorDropdown } from './DiscriminatorDropdown';
import { SchemaProps } from './Schema';
@ -18,13 +22,26 @@ export interface ObjectSchemaProps extends SchemaProps {
};
}
export const ObjectSchemaDetails = styled.div`
margin: 0 0 0.5em 0;
`;
export const ObjectSchemaTitle = styled(H3)`
margin: 0.5em 0 0 0;
`;
export const ObjectSchemaDescription = styled.div`
margin: 0.5em 0 0 0;
`;
export const ObjectSchema = observer(
({
schema: { fields = [], title },
showTitle,
schema: { fields = [], title, description },
discriminator,
skipReadOnly,
skipWriteOnly,
hideObjectTitle,
hideObjectDescription,
level,
}: ObjectSchemaProps) => {
const { expandSingleSchemaField, showObjectSchemaExamples, schemaExpansionLevel } =
@ -48,37 +65,48 @@ export const ObjectSchema = observer(
(expandSingleSchemaField && filteredFields.length === 1) || schemaExpansionLevel >= level!;
return (
<PropertiesTable>
{showTitle && <PropertiesTableCaption>{title}</PropertiesTableCaption>}
<tbody>
{mapWithLast(filteredFields, (field, isLast) => {
return (
<Field
key={field.name}
isLast={isLast}
field={field}
expandByDefault={expandByDefault}
renderDiscriminatorSwitch={
discriminator?.fieldName === field.name
? () => (
<DiscriminatorDropdown
parent={discriminator!.parentSchema}
enumValues={field.schema.enum}
/>
)
: undefined
}
className={field.expanded ? 'expanded' : undefined}
showExamples={showObjectSchemaExamples}
skipReadOnly={skipReadOnly}
skipWriteOnly={skipWriteOnly}
showTitle={showTitle}
level={level}
/>
);
})}
</tbody>
</PropertiesTable>
<div>
<ObjectSchemaDetails>
{!hideObjectTitle && <ObjectSchemaTitle>{title}</ObjectSchemaTitle>}
{!hideObjectDescription && (
<ObjectSchemaDescription>
<Markdown compact={true} source={description} />
</ObjectSchemaDescription>
)}
</ObjectSchemaDetails>
<PropertiesTable>
<tbody>
{mapWithLast(filteredFields, (field, isLast) => {
return (
<Field
key={field.name}
isLast={isLast}
field={field}
expandByDefault={expandByDefault}
renderDiscriminatorSwitch={
discriminator?.fieldName === field.name
? () => (
<DiscriminatorDropdown
parent={discriminator!.parentSchema}
enumValues={field.schema.enum}
/>
)
: undefined
}
className={field.expanded ? 'expanded' : undefined}
showExamples={showObjectSchemaExamples}
skipReadOnly={skipReadOnly}
skipWriteOnly={skipWriteOnly}
hideObjectTitle={hideObjectTitle}
hideObjectDescription={hideObjectDescription}
level={level}
/>
);
})}
</tbody>
</PropertiesTable>
</div>
);
},
);

View File

@ -13,9 +13,10 @@ import { RecursiveSchema } from './RecursiveSchema';
import { isArray } from '../../utils/helpers';
export interface SchemaOptions {
showTitle?: boolean;
skipReadOnly?: boolean;
skipWriteOnly?: boolean;
hideObjectTitle?: boolean;
hideObjectDescription?: boolean;
level?: number;
}

View File

@ -14,6 +14,8 @@ export interface ObjectDescriptionProps {
exampleRef?: string;
showReadOnly?: boolean;
showWriteOnly?: boolean;
showObjectTitle?: boolean;
showObjectDescription?: boolean;
showExample?: boolean;
parser: OpenAPIParser;
options: RedocNormalizedOptions;
@ -54,7 +56,13 @@ export class SchemaDefinition extends React.PureComponent<ObjectDescriptionProps
}
render() {
const { showReadOnly = true, showWriteOnly = false, showExample = true } = this.props;
const {
showReadOnly = true,
showWriteOnly = false,
showExample = true,
showObjectTitle = false,
showObjectDescription = false,
} = this.props;
return (
<Section>
<Row>
@ -62,6 +70,8 @@ export class SchemaDefinition extends React.PureComponent<ObjectDescriptionProps
<Schema
skipWriteOnly={!showWriteOnly}
skipReadOnly={!showReadOnly}
hideObjectTitle={!showObjectTitle}
hideObjectDescription={!showObjectDescription}
schema={this.mediaModel.schema}
/>
</MiddlePanel>

View File

@ -2865,143 +2865,156 @@ exports[`Components SchemaView discriminator should correctly render SchemaView
`;
exports[`Components SchemaView discriminator should correctly render discriminator dropdown 1`] = `
<styled.table>
<tbody>
<Field
expandByDefault={false}
field={
FieldModel {
"const": "",
"deprecated": false,
"description": "",
"example": undefined,
"expanded": undefined,
"explode": false,
"in": undefined,
"kind": "field",
"name": "packSize",
"required": false,
"schema": SchemaModel {
"activeOneOf": 0,
<div>
<styled.div>
<Styled(styled.h2)>
Dog
</Styled(styled.h2)>
<styled.div>
<Markdown
compact={true}
source=""
/>
</styled.div>
</styled.div>
<styled.table>
<tbody>
<Field
expandByDefault={false}
field={
FieldModel {
"const": "",
"constraints": Array [],
"contentEncoding": undefined,
"contentMediaType": undefined,
"default": undefined,
"deprecated": false,
"description": "",
"displayFormat": undefined,
"displayType": "number",
"enum": Array [],
"example": undefined,
"examples": undefined,
"externalDocs": undefined,
"format": undefined,
"isCircular": false,
"isPrimitive": true,
"maxItems": undefined,
"minItems": undefined,
"options": "<<<filtered>>>",
"pattern": undefined,
"pointer": "#/components/schemas/Dog/properties/packSize",
"rawSchema": Object {
"expanded": undefined,
"explode": false,
"in": undefined,
"kind": "field",
"name": "packSize",
"required": false,
"schema": SchemaModel {
"activeOneOf": 0,
"const": "",
"constraints": Array [],
"contentEncoding": undefined,
"contentMediaType": undefined,
"default": undefined,
"deprecated": false,
"description": "",
"displayFormat": undefined,
"displayType": "number",
"enum": Array [],
"example": undefined,
"examples": undefined,
"externalDocs": undefined,
"format": undefined,
"isCircular": false,
"isPrimitive": true,
"maxItems": undefined,
"minItems": undefined,
"options": "<<<filtered>>>",
"pattern": undefined,
"pointer": "#/components/schemas/Dog/properties/packSize",
"rawSchema": Object {
"default": undefined,
"type": "number",
},
"readOnly": false,
"refsStack": Array [
"#/components/schemas/Dog",
"#/components/schemas/Dog/properties/packSize",
],
"schema": Object {
"default": undefined,
"type": "number",
},
"title": "",
"type": "number",
"typePrefix": "",
"writeOnly": false,
},
"readOnly": false,
"refsStack": Array [
"#/components/schemas/Dog",
"#/components/schemas/Dog/properties/packSize",
],
"schema": Object {
"default": undefined,
"type": "number",
},
"title": "",
"type": "number",
"typePrefix": "",
"writeOnly": false,
},
}
}
}
isLast={false}
key="packSize"
showExamples={false}
/>
<Field
expandByDefault={false}
field={
FieldModel {
"const": "",
"deprecated": false,
"description": "",
"example": undefined,
"expanded": undefined,
"explode": false,
"in": undefined,
"kind": "field",
"name": "type",
"required": true,
"schema": SchemaModel {
"activeOneOf": 0,
isLast={false}
key="packSize"
showExamples={false}
/>
<Field
expandByDefault={false}
field={
FieldModel {
"const": "",
"constraints": Array [],
"contentEncoding": undefined,
"contentMediaType": undefined,
"default": undefined,
"deprecated": false,
"description": "",
"displayFormat": undefined,
"displayType": "string",
"enum": Array [],
"example": undefined,
"examples": undefined,
"externalDocs": undefined,
"format": undefined,
"isCircular": false,
"isPrimitive": true,
"maxItems": undefined,
"minItems": undefined,
"options": "<<<filtered>>>",
"pattern": undefined,
"pointer": "#/components/schemas/Dog/properties/type",
"rawSchema": Object {
"expanded": undefined,
"explode": false,
"in": undefined,
"kind": "field",
"name": "type",
"required": true,
"schema": SchemaModel {
"activeOneOf": 0,
"const": "",
"constraints": Array [],
"contentEncoding": undefined,
"contentMediaType": undefined,
"default": undefined,
"type": "string",
"x-refsStack": Array [
"deprecated": false,
"description": "",
"displayFormat": undefined,
"displayType": "string",
"enum": Array [],
"example": undefined,
"examples": undefined,
"externalDocs": undefined,
"format": undefined,
"isCircular": false,
"isPrimitive": true,
"maxItems": undefined,
"minItems": undefined,
"options": "<<<filtered>>>",
"pattern": undefined,
"pointer": "#/components/schemas/Dog/properties/type",
"rawSchema": Object {
"default": undefined,
"type": "string",
"x-refsStack": Array [
"#/components/schemas/Dog",
"#/components/schemas/Pet",
],
},
"readOnly": false,
"refsStack": Array [
"#/components/schemas/Dog",
"#/components/schemas/Dog",
"#/components/schemas/Pet",
],
},
"readOnly": false,
"refsStack": Array [
"#/components/schemas/Dog",
"#/components/schemas/Dog",
"#/components/schemas/Pet",
"#/components/schemas/Dog",
"#/components/schemas/Pet",
"#/components/schemas/Dog/properties/type",
],
"schema": Object {
"default": undefined,
"type": "string",
"x-refsStack": Array [
"#/components/schemas/Dog",
"#/components/schemas/Pet",
"#/components/schemas/Dog/properties/type",
],
"schema": Object {
"default": undefined,
"type": "string",
"x-refsStack": Array [
"#/components/schemas/Dog",
"#/components/schemas/Pet",
],
},
"title": "",
"type": "string",
"typePrefix": "",
"writeOnly": false,
},
"title": "",
"type": "string",
"typePrefix": "",
"writeOnly": false,
},
}
}
}
isLast={true}
key="type"
renderDiscriminatorSwitch={[Function]}
showExamples={false}
/>
</tbody>
</styled.table>
isLast={true}
key="type"
renderDiscriminatorSwitch={[Function]}
showExamples={false}
/>
</tbody>
</styled.table>
</div>
`;

View File

@ -39,6 +39,8 @@ export interface RedocRawOptions {
showObjectSchemaExamples?: boolean | string;
showSecuritySchemeType?: boolean;
hideSecuritySection?: boolean;
hideObjectTitle?: boolean | string;
hideObjectDescription?: boolean | string;
unstable_ignoreMimeParameters?: boolean;