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

@ -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. 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 ### hideSecuritySection
Hides the Security panel section. Hides the Security panel section.

View File

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

View File

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

View File

@ -11,9 +11,13 @@ import { Extensions } from '../Fields/Extensions';
import { Markdown } from '../Markdown/Markdown'; import { Markdown } from '../Markdown/Markdown';
import { ResponseHeaders } from './ResponseHeaders'; import { ResponseHeaders } from './ResponseHeaders';
import { ConstraintsView } from '../Fields/FieldConstraints'; import { ConstraintsView } from '../Fields/FieldConstraints';
import { OptionsContext } from '../OptionsProvider';
export class ResponseDetails extends React.PureComponent<{ response: ResponseModel }> { export class ResponseDetails extends React.PureComponent<{ response: ResponseModel }> {
static contextType = OptionsContext;
render() { render() {
const { hideObjectTitle, hideObjectDescription } = this.context;
const { description, extensions, headers, content } = this.props.response; const { description, extensions, headers, content } = this.props.response;
return ( return (
<> <>
@ -27,7 +31,13 @@ export class ResponseDetails extends React.PureComponent<{ response: ResponseMod
{schema?.type === 'object' && ( {schema?.type === 'object' && (
<ConstraintsView constraints={schema?.constraints || []} /> <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 { observer } from 'mobx-react';
import * as React from '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 { 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 { Field } from '../Fields/Field';
import { DiscriminatorDropdown } from './DiscriminatorDropdown'; import { DiscriminatorDropdown } from './DiscriminatorDropdown';
import { SchemaProps } from './Schema'; 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( export const ObjectSchema = observer(
({ ({
schema: { fields = [], title }, schema: { fields = [], title, description },
showTitle,
discriminator, discriminator,
skipReadOnly, skipReadOnly,
skipWriteOnly, skipWriteOnly,
hideObjectTitle,
hideObjectDescription,
level, level,
}: ObjectSchemaProps) => { }: ObjectSchemaProps) => {
const { expandSingleSchemaField, showObjectSchemaExamples, schemaExpansionLevel } = const { expandSingleSchemaField, showObjectSchemaExamples, schemaExpansionLevel } =
@ -48,37 +65,48 @@ export const ObjectSchema = observer(
(expandSingleSchemaField && filteredFields.length === 1) || schemaExpansionLevel >= level!; (expandSingleSchemaField && filteredFields.length === 1) || schemaExpansionLevel >= level!;
return ( return (
<PropertiesTable> <div>
{showTitle && <PropertiesTableCaption>{title}</PropertiesTableCaption>} <ObjectSchemaDetails>
<tbody> {!hideObjectTitle && <ObjectSchemaTitle>{title}</ObjectSchemaTitle>}
{mapWithLast(filteredFields, (field, isLast) => { {!hideObjectDescription && (
return ( <ObjectSchemaDescription>
<Field <Markdown compact={true} source={description} />
key={field.name} </ObjectSchemaDescription>
isLast={isLast} )}
field={field} </ObjectSchemaDetails>
expandByDefault={expandByDefault}
renderDiscriminatorSwitch={ <PropertiesTable>
discriminator?.fieldName === field.name <tbody>
? () => ( {mapWithLast(filteredFields, (field, isLast) => {
<DiscriminatorDropdown return (
parent={discriminator!.parentSchema} <Field
enumValues={field.schema.enum} key={field.name}
/> isLast={isLast}
) field={field}
: undefined expandByDefault={expandByDefault}
} renderDiscriminatorSwitch={
className={field.expanded ? 'expanded' : undefined} discriminator?.fieldName === field.name
showExamples={showObjectSchemaExamples} ? () => (
skipReadOnly={skipReadOnly} <DiscriminatorDropdown
skipWriteOnly={skipWriteOnly} parent={discriminator!.parentSchema}
showTitle={showTitle} enumValues={field.schema.enum}
level={level} />
/> )
); : undefined
})} }
</tbody> className={field.expanded ? 'expanded' : undefined}
</PropertiesTable> 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'; import { isArray } from '../../utils/helpers';
export interface SchemaOptions { export interface SchemaOptions {
showTitle?: boolean;
skipReadOnly?: boolean; skipReadOnly?: boolean;
skipWriteOnly?: boolean; skipWriteOnly?: boolean;
hideObjectTitle?: boolean;
hideObjectDescription?: boolean;
level?: number; level?: number;
} }

View File

@ -14,6 +14,8 @@ export interface ObjectDescriptionProps {
exampleRef?: string; exampleRef?: string;
showReadOnly?: boolean; showReadOnly?: boolean;
showWriteOnly?: boolean; showWriteOnly?: boolean;
showObjectTitle?: boolean;
showObjectDescription?: boolean;
showExample?: boolean; showExample?: boolean;
parser: OpenAPIParser; parser: OpenAPIParser;
options: RedocNormalizedOptions; options: RedocNormalizedOptions;
@ -54,7 +56,13 @@ export class SchemaDefinition extends React.PureComponent<ObjectDescriptionProps
} }
render() { render() {
const { showReadOnly = true, showWriteOnly = false, showExample = true } = this.props; const {
showReadOnly = true,
showWriteOnly = false,
showExample = true,
showObjectTitle = false,
showObjectDescription = false,
} = this.props;
return ( return (
<Section> <Section>
<Row> <Row>
@ -62,6 +70,8 @@ export class SchemaDefinition extends React.PureComponent<ObjectDescriptionProps
<Schema <Schema
skipWriteOnly={!showWriteOnly} skipWriteOnly={!showWriteOnly}
skipReadOnly={!showReadOnly} skipReadOnly={!showReadOnly}
hideObjectTitle={!showObjectTitle}
hideObjectDescription={!showObjectDescription}
schema={this.mediaModel.schema} schema={this.mediaModel.schema}
/> />
</MiddlePanel> </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`] = ` exports[`Components SchemaView discriminator should correctly render discriminator dropdown 1`] = `
<styled.table> <div>
<tbody> <styled.div>
<Field <Styled(styled.h2)>
expandByDefault={false} Dog
field={ </Styled(styled.h2)>
FieldModel { <styled.div>
"const": "", <Markdown
"deprecated": false, compact={true}
"description": "", source=""
"example": undefined, />
"expanded": undefined, </styled.div>
"explode": false, </styled.div>
"in": undefined, <styled.table>
"kind": "field", <tbody>
"name": "packSize", <Field
"required": false, expandByDefault={false}
"schema": SchemaModel { field={
"activeOneOf": 0, FieldModel {
"const": "", "const": "",
"constraints": Array [],
"contentEncoding": undefined,
"contentMediaType": undefined,
"default": undefined,
"deprecated": false, "deprecated": false,
"description": "", "description": "",
"displayFormat": undefined,
"displayType": "number",
"enum": Array [],
"example": undefined, "example": undefined,
"examples": undefined, "expanded": undefined,
"externalDocs": undefined, "explode": false,
"format": undefined, "in": undefined,
"isCircular": false, "kind": "field",
"isPrimitive": true, "name": "packSize",
"maxItems": undefined, "required": false,
"minItems": undefined, "schema": SchemaModel {
"options": "<<<filtered>>>", "activeOneOf": 0,
"pattern": undefined, "const": "",
"pointer": "#/components/schemas/Dog/properties/packSize", "constraints": Array [],
"rawSchema": Object { "contentEncoding": undefined,
"contentMediaType": undefined,
"default": 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", "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}
isLast={false} key="packSize"
key="packSize" showExamples={false}
showExamples={false} />
/> <Field
<Field expandByDefault={false}
expandByDefault={false} field={
field={ FieldModel {
FieldModel {
"const": "",
"deprecated": false,
"description": "",
"example": undefined,
"expanded": undefined,
"explode": false,
"in": undefined,
"kind": "field",
"name": "type",
"required": true,
"schema": SchemaModel {
"activeOneOf": 0,
"const": "", "const": "",
"constraints": Array [],
"contentEncoding": undefined,
"contentMediaType": undefined,
"default": undefined,
"deprecated": false, "deprecated": false,
"description": "", "description": "",
"displayFormat": undefined,
"displayType": "string",
"enum": Array [],
"example": undefined, "example": undefined,
"examples": undefined, "expanded": undefined,
"externalDocs": undefined, "explode": false,
"format": undefined, "in": undefined,
"isCircular": false, "kind": "field",
"isPrimitive": true, "name": "type",
"maxItems": undefined, "required": true,
"minItems": undefined, "schema": SchemaModel {
"options": "<<<filtered>>>", "activeOneOf": 0,
"pattern": undefined, "const": "",
"pointer": "#/components/schemas/Dog/properties/type", "constraints": Array [],
"rawSchema": Object { "contentEncoding": undefined,
"contentMediaType": undefined,
"default": undefined, "default": undefined,
"type": "string", "deprecated": false,
"x-refsStack": Array [ "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/Dog",
"#/components/schemas/Pet", "#/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/Dog",
"#/components/schemas/Pet", "#/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}
isLast={true} key="type"
key="type" renderDiscriminatorSwitch={[Function]}
renderDiscriminatorSwitch={[Function]} showExamples={false}
showExamples={false} />
/> </tbody>
</tbody> </styled.table>
</styled.table> </div>
`; `;

View File

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