fix: No maxLength label is displayed for arrays of items #1701

This commit is contained in:
Andriy Zaleskyy 2021-10-12 14:58:12 +03:00
parent b5fb407470
commit 74bcf11514
6 changed files with 114 additions and 76 deletions

View File

@ -0,0 +1,27 @@
import * as React from 'react';
import { TypeFormat, TypeName, TypePrefix } from '../../common-elements/fields';
import { ConstraintsView } from './FieldContstraints';
import { Pattern } from './Pattern';
import { SchemaModel } from '../../services';
import styled from '../../styled-components';
export function ArrayItemDetails({ schema }: { schema: SchemaModel }) {
if (!schema || schema.type === 'string' && !schema.constraints.length) return null;
return (
<ArrayItemDetailsStyled>
<TypePrefix>{schema.typePrefix}</TypePrefix>
<TypeName>{schema.displayType}</TypeName>
{schema.displayFormat && (
<TypeFormat>{` &lt;${schema.displayFormat}&gt; `}</TypeFormat>
)}
<ConstraintsView constraints={schema.constraints} />
<Pattern schema={schema}/>
{schema.items && <ArrayItemDetails schema={schema.items}/> }
</ArrayItemDetailsStyled>
);
}
const ArrayItemDetailsStyled = styled.div`
padding-left: 7px;
`;

View File

@ -8,7 +8,7 @@ import { RedocRawOptions } from '../../services/RedocNormalizedOptions';
export interface EnumValuesProps {
values: string[];
type: string | string[];
isArrayType: boolean;
}
export interface EnumValuesState {
@ -27,7 +27,7 @@ export class EnumValues extends React.PureComponent<EnumValuesProps, EnumValuesS
}
render() {
const { values, type } = this.props;
const { values, isArrayType } = this.props;
const { collapsed } = this.state;
// TODO: provide context interface in more elegant way
@ -55,7 +55,7 @@ export class EnumValues extends React.PureComponent<EnumValuesProps, EnumValuesS
return (
<div>
<FieldLabel>
{type === 'array' ? l('enumArray') : ''}{' '}
{isArrayType ? l('enumArray') : ''}{' '}
{values.length === 1 ? l('enumSingleValue') : l('enum')}:
</FieldLabel>{' '}
{displayedItems.map((value, idx) => {

View File

@ -0,0 +1,35 @@
import * as React from 'react';
import { FieldLabel, ExampleValue } from '../../common-elements/fields';
import { getSerializedValue } from '../../utils';
import { l } from '../../services/Labels';
import { FieldModel } from '../../services';
import styled from '../../styled-components';
export function Examples({ field }: { field: FieldModel }) {
if (!field.examples) {
return null;
}
return (
<>
<FieldLabel> {l('examples')}: </FieldLabel>
<ExamplesList>
{Object.values(field.examples).map((example, idx) => {
return (
<li key={idx}>
<ExampleValue>{getSerializedValue(field, example.value)}</ExampleValue> - {example.summary || example.description}
</li>
);
})}
</ExamplesList>
</>
);
}
const ExamplesList = styled.ul`
margin-top: 1em;
padding-left: 0;
list-style-position: inside;
`;

View File

@ -1,22 +1,19 @@
import * as React from 'react';
import {
PatternLabel,
RecursiveLabel,
TypeFormat,
TypeName,
TypePrefix,
TypeTitle,
ToggleButton,
FieldLabel,
ExampleValue,
} from '../../common-elements/fields';
import { serializeParameterValue } from '../../utils/openapi';
import { getSerializedValue } from '../../utils';
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
import { Markdown } from '../Markdown/Markdown';
import { EnumValues } from './EnumValues';
import { Extensions } from './Extensions';
import { FieldProps } from './Field';
import { Examples } from './Examples';
import { ConstraintsView } from './FieldContstraints';
import { FieldDetail } from './FieldDetail';
@ -24,30 +21,19 @@ import { Badge } from '../../common-elements/';
import { l } from '../../services/Labels';
import { OptionsContext } from '../OptionsProvider';
import { FieldModel } from '../../services/models/Field';
import styled from '../../styled-components';
const MAX_PATTERN_LENGTH = 45;
import { Pattern } from './Pattern';
import { ArrayItemDetails } from './ArrayItemDetails';
export class FieldDetails extends React.PureComponent<FieldProps, { patternShown: boolean }> {
state = {
patternShown: false,
};
static contextType = OptionsContext;
togglePattern = () => {
this.setState({
patternShown: !this.state.patternShown,
});
};
render() {
const { showExamples, field, renderDiscriminatorSwitch } = this.props;
const { patternShown } = this.state;
const { enumSkipQuotes, hideSchemaTitles, hideSchemaPattern } = this.context;
const { enumSkipQuotes, hideSchemaTitles } = this.context;
const { schema, description, example, deprecated, examples } = field;
const { type } = schema;
const isArrayType = type === 'array';
const rawDefault = !!enumSkipQuotes || field.in === 'header'; // having quotes around header field default values is confusing and inappropriate
@ -92,20 +78,7 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
)}
{schema.title && !hideSchemaTitles && <TypeTitle> ({schema.title}) </TypeTitle>}
<ConstraintsView constraints={schema.constraints} />
{schema.pattern && !hideSchemaPattern && (
<>
<PatternLabel>
{patternShown || schema.pattern.length < MAX_PATTERN_LENGTH
? schema.pattern
: `${schema.pattern.substr(0, MAX_PATTERN_LENGTH)}...`}
</PatternLabel>
{schema.pattern.length > MAX_PATTERN_LENGTH && (
<ToggleButton onClick={this.togglePattern}>
{patternShown ? 'Hide pattern' : 'Show pattern'}
</ToggleButton>
)}
</>
)}
<Pattern schema={schema}/>
{schema.isCircular && <RecursiveLabel> {l('recursive')} </RecursiveLabel>}
</div>
{deprecated && (
@ -114,7 +87,7 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
</div>
)}
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} />
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '}
{!renderDiscriminatorSwitch && <EnumValues isArrayType={isArrayType} values={schema.enum} />}{' '}
{renderedExamples}
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />}
<div>
@ -125,44 +98,8 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
)}
{(renderDiscriminatorSwitch && renderDiscriminatorSwitch(this.props)) || null}
{field.const && (<FieldDetail label={l('const') + ':'} value={field.const} />) || null}
{isArrayType && schema.items && <ArrayItemDetails schema={schema.items}/>}
</div>
);
}
}
function Examples({ field }: { field: FieldModel }) {
if (!field.examples) {
return null;
}
return (
<>
<FieldLabel> {l('examples')}: </FieldLabel>
<ExamplesList>
{Object.values(field.examples).map((example, idx) => {
return (
<li key={idx}>
<ExampleValue>{getSerializedValue(field, example.value)}</ExampleValue> - {example.summary || example.description}
</li>
);
})}
</ExamplesList>
</>
);
}
function getSerializedValue(field: FieldModel, example: any) {
if (field.in) {
// decode for better readability in examples: see https://github.com/Redocly/redoc/issues/1138
return decodeURIComponent(serializeParameterValue(field, example));
} else {
return example;
}
}
const ExamplesList = styled.ul`
margin-top: 1em;
padding-left: 0;
list-style-position: inside;
`;

View File

@ -0,0 +1,30 @@
import { PatternLabel, ToggleButton } from '../../common-elements/fields';
import * as React from 'react';
import { OptionsContext } from '../OptionsProvider';
import { useCallback, useContext, useState } from 'react';
import { SchemaModel } from '../../services';
const MAX_PATTERN_LENGTH = 45;
export function Pattern(props: {schema: SchemaModel}) {
const pattern = props.schema.pattern;
const { hideSchemaPattern } = useContext(OptionsContext);
const [patternShown, usePatternShown] = useState(false);
const togglePattern = useCallback(() => usePatternShown(!patternShown), [patternShown]);
if (!pattern || hideSchemaPattern) return null;
return <>
<PatternLabel>
{patternShown || pattern.length < MAX_PATTERN_LENGTH
? pattern
: `${pattern.substr(0, MAX_PATTERN_LENGTH)}...`}
</PatternLabel>
{pattern.length > MAX_PATTERN_LENGTH && (
<ToggleButton onClick={togglePattern}>
{patternShown ? 'Hide pattern' : 'Show pattern'}
</ToggleButton>
)}
</>
}

View File

@ -362,6 +362,15 @@ export function serializeParameterValue(
}
}
export function getSerializedValue(field: FieldModel, example: any) {
if (field.in) {
// decode for better readability in examples: see https://github.com/Redocly/redoc/issues/1138
return decodeURIComponent(serializeParameterValue(field, example));
} else {
return example;
}
}
export function langFromMime(contentType: string): string {
if (contentType.search(/xml/i) !== -1) {
return 'xml';