From 30db4dfe76e9ea8f22c0146fe99ff40fe9c2b8d3 Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Wed, 15 Jan 2025 16:17:40 +0100 Subject: [PATCH 1/9] fix: unify redoc config --- demo/index.tsx | 2 +- src/components/ApiInfo/ApiInfo.tsx | 37 ++++++------- src/components/JsonViewer/JsonViewer.tsx | 2 +- src/components/Markdown/SanitizedMdBlock.tsx | 4 +- src/components/Schema/ObjectSchema.tsx | 4 +- src/services/RedocNormalizedOptions.ts | 55 ++++++++++++------- src/services/__tests__/models/ApiInfo.test.ts | 9 ++- src/services/models/ApiInfo.ts | 29 +++++----- src/services/models/Operation.ts | 2 +- src/services/models/Schema.ts | 2 +- 10 files changed, 83 insertions(+), 63 deletions(-) diff --git a/demo/index.tsx b/demo/index.tsx index f134eaa797..1e621a45e1 100644 --- a/demo/index.tsx +++ b/demo/index.tsx @@ -122,7 +122,7 @@ class DemoApp extends React.Component< ); diff --git a/src/components/ApiInfo/ApiInfo.tsx b/src/components/ApiInfo/ApiInfo.tsx index 4e3ce62e2d..7d1f75864e 100644 --- a/src/components/ApiInfo/ApiInfo.tsx +++ b/src/components/ApiInfo/ApiInfo.tsx @@ -22,20 +22,14 @@ export interface ApiInfoProps { @observer export class ApiInfo extends React.Component { - handleDownloadClick = e => { - if (!e.target.href) { - e.target.href = this.props.store.spec.info.downloadLink; - } - }; - render() { const { store } = this.props; const { info, externalDocs } = store.spec; - const hideDownloadButton = store.options.hideDownloadButton; - - const downloadFilename = info.downloadFileName; - const downloadLink = info.downloadLink; + const hideDownloadButtons = store.options.hideDownloadButtons; + // FIXME: use downloadUrls + const downloadUrls = info.downloadUrls; + console.log(downloadUrls); const license = (info.license && ( @@ -83,17 +77,22 @@ export class ApiInfo extends React.Component { {info.title} {version} - {!hideDownloadButton && ( + {!hideDownloadButtons && (

{l('downloadSpecification')}: - - {l('download')} - + {downloadUrls?.map(({ title, url }) => { + return ( + + {downloadUrls.length > 1 ? title : l('download')} + + ); + })}

)} diff --git a/src/components/JsonViewer/JsonViewer.tsx b/src/components/JsonViewer/JsonViewer.tsx index 3e31e74a9d..8ae9d9b9e6 100644 --- a/src/components/JsonViewer/JsonViewer.tsx +++ b/src/components/JsonViewer/JsonViewer.tsx @@ -45,7 +45,7 @@ const Json = (props: JsonProps) => { // tslint:disable-next-line ref={node => setNode(node!)} dangerouslySetInnerHTML={{ - __html: jsonToHTML(props.data, options.jsonSampleExpandLevel), + __html: jsonToHTML(props.data, options.jsonSamplesExpandLevel), }} /> )} diff --git a/src/components/Markdown/SanitizedMdBlock.tsx b/src/components/Markdown/SanitizedMdBlock.tsx index d542c8c667..b05e5b50d3 100644 --- a/src/components/Markdown/SanitizedMdBlock.tsx +++ b/src/components/Markdown/SanitizedMdBlock.tsx @@ -10,7 +10,7 @@ const StyledMarkdownSpan = styled(StyledMarkdownBlock)` display: inline; `; -const sanitize = (untrustedSpec, html) => (untrustedSpec ? DOMPurify.sanitize(html) : html); +const sanitize = (sanitize, html) => (sanitize ? DOMPurify.sanitize(html) : html); export function SanitizedMarkdownHTML({ inline, @@ -25,7 +25,7 @@ export function SanitizedMarkdownHTML({ { - const { expandSingleSchemaField, showObjectSchemaExamples, schemaExpansionLevel } = + const { expandSingleSchemaField, showObjectSchemaExamples, schemasExpansionLevel } = React.useContext(OptionsContext); const filteredFields = React.useMemo( @@ -45,7 +45,7 @@ export const ObjectSchema = observer( ); const expandByDefault = - (expandSingleSchemaField && filteredFields.length === 1) || schemaExpansionLevel >= level!; + (expandSingleSchemaField && filteredFields.length === 1) || schemasExpansionLevel >= level!; return ( diff --git a/src/services/RedocNormalizedOptions.ts b/src/services/RedocNormalizedOptions.ts index 0cdd7f9e2a..1a62934e68 100644 --- a/src/services/RedocNormalizedOptions.ts +++ b/src/services/RedocNormalizedOptions.ts @@ -6,23 +6,32 @@ import { setRedocLabels } from './Labels'; import { SideNavStyleEnum } from './types'; import type { LabelsConfigRaw, MDXComponentMeta } from './types'; +export type DownloadUrlsConfig = { + title?: string; + url: string; +}[]; + export interface RedocRawOptions { theme?: ThemeInterface; scrollYOffset?: number | string | (() => number); hideHostname?: boolean | string; expandResponses?: string | 'all'; - requiredPropsFirst?: boolean | string; + requiredPropsFirst?: boolean | string; // remove in next major release + sortRequiredPropsFirst?: boolean | string; sortPropsAlphabetically?: boolean | string; sortEnumValuesAlphabetically?: boolean | string; sortOperationsAlphabetically?: boolean | string; sortTagsAlphabetically?: boolean | string; nativeScrollbars?: boolean | string; pathInMiddlePanel?: boolean | string; - untrustedSpec?: boolean | string; + untrustedSpec?: boolean | string; // remove in next major release + sanitize?: boolean | string; hideLoading?: boolean | string; - hideDownloadButton?: boolean | string; + hideDownloadButton?: boolean | string; // remove in next major release + hideDownloadButtons?: boolean | string; downloadFileName?: string; downloadDefinitionUrl?: string; + downloadUrls?: DownloadUrlsConfig; disableSearch?: boolean | string; onlyRequiredInSamples?: boolean | string; showExtensions?: boolean | string | string[]; @@ -30,12 +39,14 @@ export interface RedocRawOptions { hideSingleRequestSampleTab?: boolean | string; hideRequestPayloadSample?: boolean; menuToggle?: boolean | string; - jsonSampleExpandLevel?: number | string | 'all'; + jsonSampleExpandLevel?: number | string | 'all'; // remove in next major release + jsonSamplesExpandLevel?: number | string | 'all'; hideSchemaTitles?: boolean | string; simpleOneOfTypeLabel?: boolean | string; payloadSampleIdx?: number; expandSingleSchemaField?: boolean | string; - schemaExpansionLevel?: number | string | 'all'; + schemaExpansionLevel?: number | string | 'all'; // remove in next major release + schemasExpansionLevel?: number | string | 'all'; showObjectSchemaExamples?: boolean | string; showSecuritySchemeType?: boolean; hideSecuritySection?: boolean; @@ -216,17 +227,16 @@ export class RedocNormalizedOptions { scrollYOffset: () => number; hideHostname: boolean; expandResponses: { [code: string]: boolean } | 'all'; - requiredPropsFirst: boolean; + sortRequiredPropsFirst: boolean; sortPropsAlphabetically: boolean; sortEnumValuesAlphabetically: boolean; sortOperationsAlphabetically: boolean; sortTagsAlphabetically: boolean; nativeScrollbars: boolean; pathInMiddlePanel: boolean; - untrustedSpec: boolean; - hideDownloadButton: boolean; - downloadFileName?: string; - downloadDefinitionUrl?: string; + sanitize: boolean; + hideDownloadButtons: boolean; + downloadUrls?: DownloadUrlsConfig; disableSearch: boolean; onlyRequiredInSamples: boolean; showExtensions: boolean | string[]; @@ -234,13 +244,13 @@ export class RedocNormalizedOptions { hideSingleRequestSampleTab: boolean; hideRequestPayloadSample: boolean; menuToggle: boolean; - jsonSampleExpandLevel: number; + jsonSamplesExpandLevel: number; enumSkipQuotes: boolean; hideSchemaTitles: boolean; simpleOneOfTypeLabel: boolean; payloadSampleIdx: number; expandSingleSchemaField: boolean; - schemaExpansionLevel: number; + schemasExpansionLevel: number; showObjectSchemaExamples: boolean; showSecuritySchemeType?: boolean; hideSecuritySection?: boolean; @@ -288,17 +298,20 @@ export class RedocNormalizedOptions { this.scrollYOffset = RedocNormalizedOptions.normalizeScrollYOffset(raw.scrollYOffset); this.hideHostname = RedocNormalizedOptions.normalizeHideHostname(raw.hideHostname); this.expandResponses = RedocNormalizedOptions.normalizeExpandResponses(raw.expandResponses); - this.requiredPropsFirst = argValueToBoolean(raw.requiredPropsFirst); + this.sortRequiredPropsFirst = argValueToBoolean( + raw.sortRequiredPropsFirst || raw.requiredPropsFirst, + ); this.sortPropsAlphabetically = argValueToBoolean(raw.sortPropsAlphabetically); this.sortEnumValuesAlphabetically = argValueToBoolean(raw.sortEnumValuesAlphabetically); this.sortOperationsAlphabetically = argValueToBoolean(raw.sortOperationsAlphabetically); this.sortTagsAlphabetically = argValueToBoolean(raw.sortTagsAlphabetically); this.nativeScrollbars = argValueToBoolean(raw.nativeScrollbars); this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel); - this.untrustedSpec = argValueToBoolean(raw.untrustedSpec); - this.hideDownloadButton = argValueToBoolean(raw.hideDownloadButton); - this.downloadFileName = raw.downloadFileName; - this.downloadDefinitionUrl = raw.downloadDefinitionUrl; + this.sanitize = argValueToBoolean(raw.sanitize || raw.untrustedSpec); + this.hideDownloadButtons = argValueToBoolean(raw.hideDownloadButtons || raw.hideDownloadButton); + this.downloadUrls = + raw.downloadUrls || + ([{ title: raw.downloadFileName, url: raw.downloadDefinitionUrl }] as DownloadUrlsConfig); this.disableSearch = argValueToBoolean(raw.disableSearch); this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples); this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions); @@ -306,15 +319,17 @@ export class RedocNormalizedOptions { this.hideSingleRequestSampleTab = argValueToBoolean(raw.hideSingleRequestSampleTab); this.hideRequestPayloadSample = argValueToBoolean(raw.hideRequestPayloadSample); this.menuToggle = argValueToBoolean(raw.menuToggle, true); - this.jsonSampleExpandLevel = RedocNormalizedOptions.normalizeJsonSampleExpandLevel( - raw.jsonSampleExpandLevel, + this.jsonSamplesExpandLevel = RedocNormalizedOptions.normalizeJsonSampleExpandLevel( + raw.jsonSamplesExpandLevel || raw.jsonSampleExpandLevel, ); this.enumSkipQuotes = argValueToBoolean(raw.enumSkipQuotes); this.hideSchemaTitles = argValueToBoolean(raw.hideSchemaTitles); this.simpleOneOfTypeLabel = argValueToBoolean(raw.simpleOneOfTypeLabel); this.payloadSampleIdx = RedocNormalizedOptions.normalizePayloadSampleIdx(raw.payloadSampleIdx); this.expandSingleSchemaField = argValueToBoolean(raw.expandSingleSchemaField); - this.schemaExpansionLevel = argValueToExpandLevel(raw.schemaExpansionLevel); + this.schemasExpansionLevel = argValueToExpandLevel( + raw.schemasExpansionLevel || raw.schemaExpansionLevel, + ); this.showObjectSchemaExamples = argValueToBoolean(raw.showObjectSchemaExamples); this.showSecuritySchemeType = argValueToBoolean(raw.showSecuritySchemeType); this.hideSecuritySection = argValueToBoolean(raw.hideSecuritySection); diff --git a/src/services/__tests__/models/ApiInfo.test.ts b/src/services/__tests__/models/ApiInfo.test.ts index 867d50e981..e446074e9e 100644 --- a/src/services/__tests__/models/ApiInfo.test.ts +++ b/src/services/__tests__/models/ApiInfo.test.ts @@ -139,7 +139,7 @@ describe('Models', () => { } as any; const opts = new RedocNormalizedOptions({ - downloadDefinitionUrl: 'https:test.com/filename.yaml', + downloadUrls: [{ title: 'Openapi description', url: 'https:test.com/filename.yaml' }], }); const info = new ApiInfoModel(parser, opts); expect(info.downloadLink).toEqual('https:test.com/filename.yaml'); @@ -160,6 +160,13 @@ describe('Models', () => { const info = new ApiInfoModel(parser, opts); expect(info.downloadLink).toEqual('https:test.com/filename.yaml'); expect(info.downloadFileName).toEqual('test.yaml'); + + const opts2 = new RedocNormalizedOptions({ + downloadUrls: [{ title: 'test.yaml', url: 'https:test.com/filename.yaml' }], + }); + const info2 = new ApiInfoModel(parser, opts2); + expect(info2.downloadLink).toEqual('https:test.com/filename.yaml'); + expect(info2.downloadFileName).toEqual('test.yaml'); }); }); }); diff --git a/src/services/models/ApiInfo.ts b/src/services/models/ApiInfo.ts index fee2315021..738b98e632 100644 --- a/src/services/models/ApiInfo.ts +++ b/src/services/models/ApiInfo.ts @@ -1,7 +1,7 @@ import type { OpenAPIContact, OpenAPIInfo, OpenAPILicense } from '../../types'; import { IS_BROWSER } from '../../utils/'; import type { OpenAPIParser } from '../OpenAPIParser'; -import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; +import { DownloadUrlsConfig, RedocNormalizedOptions } from '../RedocNormalizedOptions'; export class ApiInfoModel implements OpenAPIInfo { title: string; @@ -13,8 +13,7 @@ export class ApiInfoModel implements OpenAPIInfo { contact?: OpenAPIContact; license?: OpenAPILicense; - downloadLink?: string; - downloadFileName?: string; + downloadUrls?: DownloadUrlsConfig; constructor( private parser: OpenAPIParser, @@ -29,13 +28,20 @@ export class ApiInfoModel implements OpenAPIInfo { this.description = this.description.substring(0, firstHeadingLinePos); } - this.downloadLink = this.getDownloadLink(); - this.downloadFileName = this.getDownloadFileName(); + this.downloadUrls = this.getDownloadUrls(); + } + private getDownloadUrls(): DownloadUrlsConfig | undefined { + return this.options.downloadUrls + ?.map(({ title, url }) => ({ + title: title || 'openapi.json', + url: this.getDownloadLink(url) || '', + })) + .filter(({ title, url }) => title && url); } - private getDownloadLink(): string | undefined { - if (this.options.downloadDefinitionUrl) { - return this.options.downloadDefinitionUrl; + private getDownloadLink(url?: string): string | undefined { + if (url) { + return url; } if (this.parser.specUrl) { @@ -49,11 +55,4 @@ export class ApiInfoModel implements OpenAPIInfo { return window.URL.createObjectURL(blob); } } - - private getDownloadFileName(): string | undefined { - if (!this.parser.specUrl && !this.options.downloadDefinitionUrl) { - return this.options.downloadFileName || 'openapi.json'; - } - return this.options.downloadFileName; - } } diff --git a/src/services/models/Operation.ts b/src/services/models/Operation.ts index cb8bd73ddc..0d0e96c6e4 100644 --- a/src/services/models/Operation.ts +++ b/src/services/models/Operation.ts @@ -247,7 +247,7 @@ export class OperationModel implements IMenuItem { if (this.options.sortPropsAlphabetically) { return sortByField(_parameters, 'name'); } - if (this.options.requiredPropsFirst) { + if (this.options.sortRequiredPropsFirst) { return sortByRequired(_parameters); } diff --git a/src/services/models/Schema.ts b/src/services/models/Schema.ts index 44b04279e0..2a9def5d44 100644 --- a/src/services/models/Schema.ts +++ b/src/services/models/Schema.ts @@ -463,7 +463,7 @@ function buildFields( if (options.sortPropsAlphabetically) { fields = sortByField(fields, 'name'); } - if (options.requiredPropsFirst) { + if (options.sortRequiredPropsFirst) { // if not sort alphabetically sort in the order from required keyword fields = sortByRequired(fields, !options.sortPropsAlphabetically ? schema.required : undefined); } From c8eb5aa7ec85291ede6f4e2c2b460d2a5c168035 Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Wed, 15 Jan 2025 17:01:49 +0100 Subject: [PATCH 2/9] fix: add support downloadUrls --- src/components/ApiInfo/ApiInfo.tsx | 7 +++--- src/services/RedocNormalizedOptions.ts | 8 ++++--- src/services/models/ApiInfo.ts | 31 ++++++++++++++++++-------- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/components/ApiInfo/ApiInfo.tsx b/src/components/ApiInfo/ApiInfo.tsx index 7d1f75864e..6c25c69556 100644 --- a/src/components/ApiInfo/ApiInfo.tsx +++ b/src/components/ApiInfo/ApiInfo.tsx @@ -27,9 +27,8 @@ export class ApiInfo extends React.Component { const { info, externalDocs } = store.spec; const hideDownloadButtons = store.options.hideDownloadButtons; - // FIXME: use downloadUrls const downloadUrls = info.downloadUrls; - console.log(downloadUrls); + const downloadFileName = info.downloadFileName; const license = (info.license && ( @@ -83,11 +82,11 @@ export class ApiInfo extends React.Component { {downloadUrls?.map(({ title, url }) => { return ( {downloadUrls.length > 1 ? title : l('download')} diff --git a/src/services/RedocNormalizedOptions.ts b/src/services/RedocNormalizedOptions.ts index 1a62934e68..7bfbc963eb 100644 --- a/src/services/RedocNormalizedOptions.ts +++ b/src/services/RedocNormalizedOptions.ts @@ -236,6 +236,8 @@ export class RedocNormalizedOptions { pathInMiddlePanel: boolean; sanitize: boolean; hideDownloadButtons: boolean; + downloadFileName?: string; + downloadDefinitionUrl?: string; downloadUrls?: DownloadUrlsConfig; disableSearch: boolean; onlyRequiredInSamples: boolean; @@ -309,9 +311,9 @@ export class RedocNormalizedOptions { this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel); this.sanitize = argValueToBoolean(raw.sanitize || raw.untrustedSpec); this.hideDownloadButtons = argValueToBoolean(raw.hideDownloadButtons || raw.hideDownloadButton); - this.downloadUrls = - raw.downloadUrls || - ([{ title: raw.downloadFileName, url: raw.downloadDefinitionUrl }] as DownloadUrlsConfig); + this.downloadFileName = raw.downloadFileName; + this.downloadDefinitionUrl = raw.downloadDefinitionUrl; + this.downloadUrls = raw.downloadUrls; this.disableSearch = argValueToBoolean(raw.disableSearch); this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples); this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions); diff --git a/src/services/models/ApiInfo.ts b/src/services/models/ApiInfo.ts index 738b98e632..7a7d6db953 100644 --- a/src/services/models/ApiInfo.ts +++ b/src/services/models/ApiInfo.ts @@ -1,7 +1,8 @@ import type { OpenAPIContact, OpenAPIInfo, OpenAPILicense } from '../../types'; import { IS_BROWSER } from '../../utils/'; +import { l } from '../Labels'; import type { OpenAPIParser } from '../OpenAPIParser'; -import { DownloadUrlsConfig, RedocNormalizedOptions } from '../RedocNormalizedOptions'; +import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; export class ApiInfoModel implements OpenAPIInfo { title: string; @@ -13,7 +14,11 @@ export class ApiInfoModel implements OpenAPIInfo { contact?: OpenAPIContact; license?: OpenAPILicense; - downloadUrls?: DownloadUrlsConfig; + downloadUrls: { + title?: string; + url?: string; + }[]; + downloadFileName?: string; constructor( private parser: OpenAPIParser, @@ -29,14 +34,22 @@ export class ApiInfoModel implements OpenAPIInfo { } this.downloadUrls = this.getDownloadUrls(); + this.downloadFileName = this.options.downloadFileName || 'openapi.json'; } - private getDownloadUrls(): DownloadUrlsConfig | undefined { - return this.options.downloadUrls - ?.map(({ title, url }) => ({ - title: title || 'openapi.json', - url: this.getDownloadLink(url) || '', - })) - .filter(({ title, url }) => title && url); + private getDownloadUrls() { + return ( + !this.options.downloadUrls + ? [ + { + title: l('download'), + url: this.getDownloadLink(this.options.downloadDefinitionUrl), + }, + ] + : this.options.downloadUrls.map(({ title, url }) => ({ + title: title || 'Download OpenAPI description', + url: this.getDownloadLink(url), + })) + ).filter(({ title, url }) => title && url); } private getDownloadLink(url?: string): string | undefined { From 417b934ffcf4a97f4dbae6696b5a374150ecd5cc Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Wed, 15 Jan 2025 18:24:13 +0100 Subject: [PATCH 3/9] feat: add support x-enumDescriptions --- demo/openapi.yaml | 4 + src/components/Fields/EnumValues.tsx | 114 +++++++++++----- src/components/Fields/FieldDetails.tsx | 2 +- .../DiscriminatorDropdown.test.tsx.snap | 122 +++++++++++------- .../__snapshots__/FieldDetails.test.tsx.snap | 2 +- .../SecurityRequirement.test.tsx.snap | 20 +-- .../fixtures/nestedEnumDescroptionSample.json | 24 ++++ src/services/__tests__/models/ApiInfo.test.ts | 34 ++++- src/services/__tests__/models/Schema.test.ts | 11 ++ src/services/models/ApiInfo.ts | 9 +- src/services/models/Schema.ts | 3 + .../loadAndBundleSpec.test.ts.snap | 5 + src/utils/openapi.ts | 1 + 13 files changed, 251 insertions(+), 100 deletions(-) create mode 100644 src/services/__tests__/fixtures/nestedEnumDescroptionSample.json diff --git a/demo/openapi.yaml b/demo/openapi.yaml index d715155e63..b91e3622bb 100644 --- a/demo/openapi.yaml +++ b/demo/openapi.yaml @@ -1083,6 +1083,10 @@ components: - available - pending - sold + x-enumDescriptions: + available: Available status + pending: Pending status + sold: Sold status petType: description: Type of a pet type: string diff --git a/src/components/Fields/EnumValues.tsx b/src/components/Fields/EnumValues.tsx index 21448ea575..a26673985f 100644 --- a/src/components/Fields/EnumValues.tsx +++ b/src/components/Fields/EnumValues.tsx @@ -5,17 +5,29 @@ import { l } from '../../services/Labels'; import { OptionsContext } from '../OptionsProvider'; import styled from '../../styled-components'; import { RedocRawOptions } from '../../services/RedocNormalizedOptions'; +import { StyledMarkdownBlock } from '../Markdown/styled.elements'; +import { Markdown } from '../Markdown/Markdown'; export interface EnumValuesProps { - values: string[]; - isArrayType: boolean; + values?: string[] | { [name: string]: string }; + type: string | string[]; } export interface EnumValuesState { collapsed: boolean; } +const DescriptionEnumsBlock = styled(StyledMarkdownBlock)` + table { + margin-bottom: 0.2em; + } +`; + export class EnumValues extends React.PureComponent { + constructor(props: EnumValuesProps) { + super(props); + this.toggle = this.toggle.bind(this); + } state: EnumValuesState = { collapsed: true, }; @@ -27,54 +39,94 @@ export class EnumValues extends React.PureComponent ({ + value, + description, + })); // TODO: provide context interface in more elegant way const { enumSkipQuotes, maxDisplayedEnumValues } = this.context as RedocRawOptions; - if (!values.length) { + if (!enums.length) { return null; } const displayedItems = this.state.collapsed && maxDisplayedEnumValues - ? values.slice(0, maxDisplayedEnumValues) - : values; + ? enums.slice(0, maxDisplayedEnumValues) + : enums; - const showToggleButton = maxDisplayedEnumValues - ? values.length > maxDisplayedEnumValues - : false; + const showToggleButton = maxDisplayedEnumValues ? enums.length > maxDisplayedEnumValues : false; const toggleButtonText = maxDisplayedEnumValues ? collapsed - ? `… ${values.length - maxDisplayedEnumValues} more` + ? `… ${enums.length - maxDisplayedEnumValues} more` : 'Hide' : ''; return (
- - {isArrayType ? l('enumArray') : ''}{' '} - {values.length === 1 ? l('enumSingleValue') : l('enum')}: - {' '} - {displayedItems.map((value, idx) => { - const exampleValue = enumSkipQuotes ? String(value) : JSON.stringify(value); - return ( - - {exampleValue}{' '} - - ); - })} - {showToggleButton ? ( - { - this.toggle(); - }} - > - {toggleButtonText} - - ) : null} + {isDescriptionEnum ? ( + <> + + + + + + + + + + {(displayedItems as { value: string; description: string }[]).map( + ({ description, value }) => { + return ( + + + + + ); + }, + )} + +
+ + {type === 'array' ? l('enumArray') : ''}{' '} + {enums.length === 1 ? l('enumSingleValue') : l('enum')} + {' '} + + Description +
{value} + +
+
+ {showToggleButton ? ( + {toggleButtonText} + ) : null} + + ) : ( + <> + + {type === 'array' ? l('enumArray') : ''}{' '} + {values.length === 1 ? l('enumSingleValue') : l('enum')}: + {' '} + {displayedItems.map((value, idx) => { + const exampleValue = enumSkipQuotes ? String(value) : JSON.stringify(value); + return ( + + {exampleValue}{' '} + + ); + })} + {showToggleButton ? ( + {toggleButtonText} + ) : null} + + )}
); } diff --git a/src/components/Fields/FieldDetails.tsx b/src/components/Fields/FieldDetails.tsx index 570c382f6f..19f58572f8 100644 --- a/src/components/Fields/FieldDetails.tsx +++ b/src/components/Fields/FieldDetails.tsx @@ -99,7 +99,7 @@ export const FieldDetailsComponent = observer((props: FieldProps) => { )} {!renderDiscriminatorSwitch && ( - + )}{' '} {renderedExamples} diff --git a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap index 488199829b..27cb32d2b5 100644 --- a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap @@ -79,12 +79,13 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "disableSearch": false, "downloadDefinitionUrl": undefined, "downloadFileName": undefined, + "downloadUrls": undefined, "enumSkipQuotes": false, "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, "generatedPayloadSamplesMaxDepth": 10, - "hideDownloadButton": false, + "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, "hideRequestPayloadSample": false, @@ -93,7 +94,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, - "jsonSampleExpandLevel": 2, + "jsonSamplesExpandLevel": 2, "maxDisplayedEnumValues": undefined, "menuToggle": true, "minCharacterLengthToInitSearch": 3, @@ -102,8 +103,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "onlyRequiredInSamples": false, "pathInMiddlePanel": false, "payloadSampleIdx": 0, - "requiredPropsFirst": false, - "schemaExpansionLevel": 0, + "sanitize": false, + "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, @@ -114,6 +115,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "sortEnumValuesAlphabetically": false, "sortOperationsAlphabetically": false, "sortPropsAlphabetically": false, + "sortRequiredPropsFirst": false, "sortTagsAlphabetically": false, "theme": { "breakpoints": { @@ -292,7 +294,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView }, }, "unstable_ignoreMimeParameters": false, - "untrustedSpec": false, }, "pattern": undefined, "pointer": "#/components/schemas/Dog/properties/packSize", @@ -313,6 +314,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "type": "number", "typePrefix": "", "writeOnly": false, + "x-enumDescriptions": undefined, }, }, FieldModel { @@ -351,12 +353,13 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "disableSearch": false, "downloadDefinitionUrl": undefined, "downloadFileName": undefined, + "downloadUrls": undefined, "enumSkipQuotes": false, "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, "generatedPayloadSamplesMaxDepth": 10, - "hideDownloadButton": false, + "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, "hideRequestPayloadSample": false, @@ -365,7 +368,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, - "jsonSampleExpandLevel": 2, + "jsonSamplesExpandLevel": 2, "maxDisplayedEnumValues": undefined, "menuToggle": true, "minCharacterLengthToInitSearch": 3, @@ -374,8 +377,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "onlyRequiredInSamples": false, "pathInMiddlePanel": false, "payloadSampleIdx": 0, - "requiredPropsFirst": false, - "schemaExpansionLevel": 0, + "sanitize": false, + "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, @@ -386,6 +389,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "sortEnumValuesAlphabetically": false, "sortOperationsAlphabetically": false, "sortPropsAlphabetically": false, + "sortRequiredPropsFirst": false, "sortTagsAlphabetically": false, "theme": { "breakpoints": { @@ -564,7 +568,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView }, }, "unstable_ignoreMimeParameters": false, - "untrustedSpec": false, }, "pattern": undefined, "pointer": "#/components/schemas/Dog/properties/type", @@ -597,6 +600,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "type": "string", "typePrefix": "", "writeOnly": false, + "x-enumDescriptions": undefined, }, }, ], @@ -610,12 +614,13 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "disableSearch": false, "downloadDefinitionUrl": undefined, "downloadFileName": undefined, + "downloadUrls": undefined, "enumSkipQuotes": false, "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, "generatedPayloadSamplesMaxDepth": 10, - "hideDownloadButton": false, + "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, "hideRequestPayloadSample": false, @@ -624,7 +629,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, - "jsonSampleExpandLevel": 2, + "jsonSamplesExpandLevel": 2, "maxDisplayedEnumValues": undefined, "menuToggle": true, "minCharacterLengthToInitSearch": 3, @@ -633,8 +638,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "onlyRequiredInSamples": false, "pathInMiddlePanel": false, "payloadSampleIdx": 0, - "requiredPropsFirst": false, - "schemaExpansionLevel": 0, + "sanitize": false, + "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, @@ -645,6 +650,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "sortEnumValuesAlphabetically": false, "sortOperationsAlphabetically": false, "sortPropsAlphabetically": false, + "sortRequiredPropsFirst": false, "sortTagsAlphabetically": false, "theme": { "breakpoints": { @@ -823,7 +829,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView }, }, "unstable_ignoreMimeParameters": false, - "untrustedSpec": false, }, "pattern": undefined, "pointer": "#/components/schemas/Dog", @@ -878,6 +883,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "type": "object", "typePrefix": "", "writeOnly": false, + "x-enumDescriptions": undefined, }, SchemaModel { "activeOneOf": 0, @@ -931,12 +937,13 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "disableSearch": false, "downloadDefinitionUrl": undefined, "downloadFileName": undefined, + "downloadUrls": undefined, "enumSkipQuotes": false, "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, "generatedPayloadSamplesMaxDepth": 10, - "hideDownloadButton": false, + "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, "hideRequestPayloadSample": false, @@ -945,7 +952,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, - "jsonSampleExpandLevel": 2, + "jsonSamplesExpandLevel": 2, "maxDisplayedEnumValues": undefined, "menuToggle": true, "minCharacterLengthToInitSearch": 3, @@ -954,8 +961,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "onlyRequiredInSamples": false, "pathInMiddlePanel": false, "payloadSampleIdx": 0, - "requiredPropsFirst": false, - "schemaExpansionLevel": 0, + "sanitize": false, + "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, @@ -966,6 +973,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "sortEnumValuesAlphabetically": false, "sortOperationsAlphabetically": false, "sortPropsAlphabetically": false, + "sortRequiredPropsFirst": false, "sortTagsAlphabetically": false, "theme": { "breakpoints": { @@ -1144,7 +1152,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView }, }, "unstable_ignoreMimeParameters": false, - "untrustedSpec": false, }, "pattern": undefined, "pointer": "#/components/schemas/Cat/properties/type", @@ -1177,6 +1184,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "type": "string", "typePrefix": "", "writeOnly": false, + "x-enumDescriptions": undefined, }, }, FieldModel { @@ -1215,12 +1223,13 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "disableSearch": false, "downloadDefinitionUrl": undefined, "downloadFileName": undefined, + "downloadUrls": undefined, "enumSkipQuotes": false, "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, "generatedPayloadSamplesMaxDepth": 10, - "hideDownloadButton": false, + "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, "hideRequestPayloadSample": false, @@ -1229,7 +1238,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, - "jsonSampleExpandLevel": 2, + "jsonSamplesExpandLevel": 2, "maxDisplayedEnumValues": undefined, "menuToggle": true, "minCharacterLengthToInitSearch": 3, @@ -1238,8 +1247,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "onlyRequiredInSamples": false, "pathInMiddlePanel": false, "payloadSampleIdx": 0, - "requiredPropsFirst": false, - "schemaExpansionLevel": 0, + "sanitize": false, + "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, @@ -1250,6 +1259,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "sortEnumValuesAlphabetically": false, "sortOperationsAlphabetically": false, "sortPropsAlphabetically": false, + "sortRequiredPropsFirst": false, "sortTagsAlphabetically": false, "theme": { "breakpoints": { @@ -1428,7 +1438,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView }, }, "unstable_ignoreMimeParameters": false, - "untrustedSpec": false, }, "pattern": undefined, "pointer": "#/components/schemas/Cat/properties/packSize", @@ -1457,6 +1466,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "type": "number", "typePrefix": "", "writeOnly": false, + "x-enumDescriptions": undefined, }, }, ], @@ -1470,12 +1480,13 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "disableSearch": false, "downloadDefinitionUrl": undefined, "downloadFileName": undefined, + "downloadUrls": undefined, "enumSkipQuotes": false, "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, "generatedPayloadSamplesMaxDepth": 10, - "hideDownloadButton": false, + "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, "hideRequestPayloadSample": false, @@ -1484,7 +1495,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, - "jsonSampleExpandLevel": 2, + "jsonSamplesExpandLevel": 2, "maxDisplayedEnumValues": undefined, "menuToggle": true, "minCharacterLengthToInitSearch": 3, @@ -1493,8 +1504,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "onlyRequiredInSamples": false, "pathInMiddlePanel": false, "payloadSampleIdx": 0, - "requiredPropsFirst": false, - "schemaExpansionLevel": 0, + "sanitize": false, + "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, @@ -1505,6 +1516,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "sortEnumValuesAlphabetically": false, "sortOperationsAlphabetically": false, "sortPropsAlphabetically": false, + "sortRequiredPropsFirst": false, "sortTagsAlphabetically": false, "theme": { "breakpoints": { @@ -1683,7 +1695,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView }, }, "unstable_ignoreMimeParameters": false, - "untrustedSpec": false, }, "pattern": undefined, "pointer": "#/components/schemas/Cat", @@ -1743,6 +1754,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "type": "object", "typePrefix": "", "writeOnly": false, + "x-enumDescriptions": undefined, }, ], "options": RedocNormalizedOptions { @@ -1750,12 +1762,13 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "disableSearch": false, "downloadDefinitionUrl": undefined, "downloadFileName": undefined, + "downloadUrls": undefined, "enumSkipQuotes": false, "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, "generatedPayloadSamplesMaxDepth": 10, - "hideDownloadButton": false, + "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, "hideRequestPayloadSample": false, @@ -1764,7 +1777,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, - "jsonSampleExpandLevel": 2, + "jsonSamplesExpandLevel": 2, "maxDisplayedEnumValues": undefined, "menuToggle": true, "minCharacterLengthToInitSearch": 3, @@ -1773,8 +1786,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "onlyRequiredInSamples": false, "pathInMiddlePanel": false, "payloadSampleIdx": 0, - "requiredPropsFirst": false, - "schemaExpansionLevel": 0, + "sanitize": false, + "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, @@ -1785,6 +1798,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "sortEnumValuesAlphabetically": false, "sortOperationsAlphabetically": false, "sortPropsAlphabetically": false, + "sortRequiredPropsFirst": false, "sortTagsAlphabetically": false, "theme": { "breakpoints": { @@ -1963,7 +1977,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView }, }, "unstable_ignoreMimeParameters": false, - "untrustedSpec": false, }, "pattern": undefined, "pointer": "#/components/schemas/Pet", @@ -2003,6 +2016,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "type": "object", "typePrefix": "", "writeOnly": false, + "x-enumDescriptions": undefined, }, } } @@ -2060,12 +2074,13 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "disableSearch": false, "downloadDefinitionUrl": undefined, "downloadFileName": undefined, + "downloadUrls": undefined, "enumSkipQuotes": false, "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, "generatedPayloadSamplesMaxDepth": 10, - "hideDownloadButton": false, + "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, "hideRequestPayloadSample": false, @@ -2074,7 +2089,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, - "jsonSampleExpandLevel": 2, + "jsonSamplesExpandLevel": 2, "maxDisplayedEnumValues": undefined, "menuToggle": true, "minCharacterLengthToInitSearch": 3, @@ -2083,8 +2098,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "onlyRequiredInSamples": false, "pathInMiddlePanel": false, "payloadSampleIdx": 0, - "requiredPropsFirst": false, - "schemaExpansionLevel": 0, + "sanitize": false, + "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, @@ -2095,6 +2110,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "sortEnumValuesAlphabetically": false, "sortOperationsAlphabetically": false, "sortPropsAlphabetically": false, + "sortRequiredPropsFirst": false, "sortTagsAlphabetically": false, "theme": { "breakpoints": { @@ -2273,7 +2289,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView }, }, "unstable_ignoreMimeParameters": false, - "untrustedSpec": false, }, "pattern": undefined, "pointer": "#/components/schemas/Dog/properties/packSize", @@ -2294,6 +2309,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "type": "number", "typePrefix": "", "writeOnly": false, + "x-enumDescriptions": undefined, }, }, FieldModel { @@ -2332,12 +2348,13 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "disableSearch": false, "downloadDefinitionUrl": undefined, "downloadFileName": undefined, + "downloadUrls": undefined, "enumSkipQuotes": false, "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, "generatedPayloadSamplesMaxDepth": 10, - "hideDownloadButton": false, + "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, "hideRequestPayloadSample": false, @@ -2346,7 +2363,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, - "jsonSampleExpandLevel": 2, + "jsonSamplesExpandLevel": 2, "maxDisplayedEnumValues": undefined, "menuToggle": true, "minCharacterLengthToInitSearch": 3, @@ -2355,8 +2372,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "onlyRequiredInSamples": false, "pathInMiddlePanel": false, "payloadSampleIdx": 0, - "requiredPropsFirst": false, - "schemaExpansionLevel": 0, + "sanitize": false, + "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, @@ -2367,6 +2384,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "sortEnumValuesAlphabetically": false, "sortOperationsAlphabetically": false, "sortPropsAlphabetically": false, + "sortRequiredPropsFirst": false, "sortTagsAlphabetically": false, "theme": { "breakpoints": { @@ -2545,7 +2563,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView }, }, "unstable_ignoreMimeParameters": false, - "untrustedSpec": false, }, "pattern": undefined, "pointer": "#/components/schemas/Dog/properties/type", @@ -2578,6 +2595,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "type": "string", "typePrefix": "", "writeOnly": false, + "x-enumDescriptions": undefined, }, }, ], @@ -2591,12 +2609,13 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "disableSearch": false, "downloadDefinitionUrl": undefined, "downloadFileName": undefined, + "downloadUrls": undefined, "enumSkipQuotes": false, "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, "generatedPayloadSamplesMaxDepth": 10, - "hideDownloadButton": false, + "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, "hideRequestPayloadSample": false, @@ -2605,7 +2624,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideSecuritySection": false, "hideSingleRequestSampleTab": false, "ignoreNamedSchemas": Set {}, - "jsonSampleExpandLevel": 2, + "jsonSamplesExpandLevel": 2, "maxDisplayedEnumValues": undefined, "menuToggle": true, "minCharacterLengthToInitSearch": 3, @@ -2614,8 +2633,8 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "onlyRequiredInSamples": false, "pathInMiddlePanel": false, "payloadSampleIdx": 0, - "requiredPropsFirst": false, - "schemaExpansionLevel": 0, + "sanitize": false, + "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, "showObjectSchemaExamples": false, @@ -2626,6 +2645,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "sortEnumValuesAlphabetically": false, "sortOperationsAlphabetically": false, "sortPropsAlphabetically": false, + "sortRequiredPropsFirst": false, "sortTagsAlphabetically": false, "theme": { "breakpoints": { @@ -2804,7 +2824,6 @@ exports[`Components SchemaView discriminator should correctly render SchemaView }, }, "unstable_ignoreMimeParameters": false, - "untrustedSpec": false, }, "pattern": undefined, "pointer": "#/components/schemas/Dog", @@ -2859,6 +2878,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "type": "object", "typePrefix": "", "writeOnly": false, + "x-enumDescriptions": undefined, } } /> @@ -2921,6 +2941,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat "type": "number", "typePrefix": "", "writeOnly": false, + "x-enumDescriptions": undefined, }, } } @@ -2994,6 +3015,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat "type": "string", "typePrefix": "", "writeOnly": false, + "x-enumDescriptions": undefined, }, } } diff --git a/src/components/__tests__/__snapshots__/FieldDetails.test.tsx.snap b/src/components/__tests__/__snapshots__/FieldDetails.test.tsx.snap index 5d6e6811c8..aa1cc03cb1 100644 --- a/src/components/__tests__/__snapshots__/FieldDetails.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/FieldDetails.test.tsx.snap @@ -159,7 +159,7 @@ exports[`FieldDetailsComponent renders correctly when field items have string ty [ items diff --git a/src/components/__tests__/__snapshots__/SecurityRequirement.test.tsx.snap b/src/components/__tests__/__snapshots__/SecurityRequirement.test.tsx.snap index 67c347821d..cfea66c7fe 100644 --- a/src/components/__tests__/__snapshots__/SecurityRequirement.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/SecurityRequirement.test.tsx.snap @@ -3,21 +3,21 @@ exports[`SecurityRequirement should render SecurityDefs 1`] = ` "

petstore_auth

Get access to data while protecting your account credentials. OAuth2 is also a safer and more secure way to give you access.

-
Security Scheme Type: OAuth2
Flow type: implicit
Scopes:
  • write:pets -

    modify pets in your account

    +
    Security Scheme Type: OAuth2
    Flow type: implicit
    Scopes:
    • write:pets -

      modify pets in your account

    • read:pets -

      read your pets

      -

GitLab_PersonalAccessToken

GitLab Personal Access Token description

-
Security Scheme Type: API Key
Header parameter name: PRIVATE-TOKEN

GitLab_OpenIdConnect

GitLab OpenIdConnect description

-
Security Scheme Type: OpenID Connect

basicAuth

Security Scheme Type: HTTP
HTTP Authorization Scheme: basic
" +

GitLab_PersonalAccessToken

GitLab Personal Access Token description

+
Security Scheme Type: API Key
Header parameter name: PRIVATE-TOKEN

GitLab_OpenIdConnect

GitLab OpenIdConnect description

+
Security Scheme Type: OpenID Connect

basicAuth

Security Scheme Type: HTTP
HTTP Authorization Scheme: basic
" `; -exports[`SecurityRequirement should render authDefinition 1`] = `"
Authorizations:
(API Key: GitLab_PersonalAccessTokenOpenID Connect: GitLab_OpenIdConnectHTTP: basicAuth) OAuth2: petstore_auth
,"`; +exports[`SecurityRequirement should render authDefinition 1`] = `"
Authorizations:
(API Key: GitLab_PersonalAccessTokenOpenID Connect: GitLab_OpenIdConnectHTTP: basicAuth) OAuth2: petstore_auth
,"`; exports[`SecurityRequirement should render authDefinition 2`] = ` -"
Authorizations:
(API Key: GitLab_PersonalAccessTokenOpenID Connect: GitLab_OpenIdConnectHTTP: basicAuth) OAuth2: petstore_auth (write:petsread:pets)
OAuth2: petstore_auth

Get access to data while protecting your account credentials. +"

Authorizations:
(API Key: GitLab_PersonalAccessTokenOpenID Connect: GitLab_OpenIdConnectHTTP: basicAuth) OAuth2: petstore_auth (write:petsread:pets)
OAuth2: petstore_auth

Get access to data while protecting your account credentials. OAuth2 is also a safer and more secure way to give you access.

-
Flow type: implicit
Required scopes: write:pets read:pets
Scopes:
  • write:pets -

    modify pets in your account

    +
    Flow type: implicit
    Required scopes: write:pets read:pets
    Scopes:
    • write:pets -

      modify pets in your account

    • read:pets -

      read your pets

      -
API Key: GitLab_PersonalAccessToken

GitLab Personal Access Token description

-
Header parameter name: PRIVATE-TOKEN
OpenID Connect: GitLab_OpenIdConnect

GitLab OpenIdConnect description

-
HTTP: basicAuth
HTTP Authorization Scheme: basic
," +
API Key: GitLab_PersonalAccessToken

GitLab Personal Access Token description

+
Header parameter name: PRIVATE-TOKEN
OpenID Connect: GitLab_OpenIdConnect

GitLab OpenIdConnect description

+
HTTP: basicAuth
HTTP Authorization Scheme: basic
," `; diff --git a/src/services/__tests__/fixtures/nestedEnumDescroptionSample.json b/src/services/__tests__/fixtures/nestedEnumDescroptionSample.json new file mode 100644 index 0000000000..86108b015f --- /dev/null +++ b/src/services/__tests__/fixtures/nestedEnumDescroptionSample.json @@ -0,0 +1,24 @@ +{ + "openapi": "3.0.0", + "info": { + "version": "1.0", + "title": "Test" + }, + "components": { + "schemas": { + "Test": { + "type": "array", + "description": "test description", + "items": { + "type": "string", + "description": "test description", + "enum": ["authorize", "do-nothing"], + "x-enumDescriptions": { + "authorize-and-void": "Will create an authorize transaction in the amount/currency of the request, followed by a void", + "do-nothing": "Will do nothing, and return an approved `setup` transaction. This is the default behavior." + } + } + } + } + } +} diff --git a/src/services/__tests__/models/ApiInfo.test.ts b/src/services/__tests__/models/ApiInfo.test.ts index e446074e9e..9b9ed51ee2 100644 --- a/src/services/__tests__/models/ApiInfo.test.ts +++ b/src/services/__tests__/models/ApiInfo.test.ts @@ -142,7 +142,15 @@ describe('Models', () => { downloadUrls: [{ title: 'Openapi description', url: 'https:test.com/filename.yaml' }], }); const info = new ApiInfoModel(parser, opts); - expect(info.downloadLink).toEqual('https:test.com/filename.yaml'); + expect(info.downloadUrls).toMatchInlineSnapshot(` + [ + { + "title": "Openapi description", + "url": "https:test.com/filename.yaml", + }, + ] + `); + expect(info.downloadFileName).toMatchInlineSnapshot(`"openapi.json"`); }); test('should correctly populate download link and download file name', () => { @@ -158,15 +166,29 @@ describe('Models', () => { downloadFileName: 'test.yaml', }); const info = new ApiInfoModel(parser, opts); - expect(info.downloadLink).toEqual('https:test.com/filename.yaml'); - expect(info.downloadFileName).toEqual('test.yaml'); + expect(info.downloadUrls).toMatchInlineSnapshot(` + [ + { + "title": "Download", + "url": "https:test.com/filename.yaml", + }, + ] + `); + expect(info.downloadFileName).toMatchInlineSnapshot(`"test.yaml"`); const opts2 = new RedocNormalizedOptions({ - downloadUrls: [{ title: 'test.yaml', url: 'https:test.com/filename.yaml' }], + downloadUrls: [{ title: 'Download file', url: 'https:test.com/filename.yaml' }], }); const info2 = new ApiInfoModel(parser, opts2); - expect(info2.downloadLink).toEqual('https:test.com/filename.yaml'); - expect(info2.downloadFileName).toEqual('test.yaml'); + expect(info2.downloadUrls).toMatchInlineSnapshot(` + [ + { + "title": "Download file", + "url": "https:test.com/filename.yaml", + }, + ] + `); + expect(info2.downloadFileName).toMatchInlineSnapshot(`"openapi.json"`); }); }); }); diff --git a/src/services/__tests__/models/Schema.test.ts b/src/services/__tests__/models/Schema.test.ts index 9dab8a1b4a..514599d97f 100644 --- a/src/services/__tests__/models/Schema.test.ts +++ b/src/services/__tests__/models/Schema.test.ts @@ -13,6 +13,17 @@ describe('Models', () => { describe('Schema', () => { let parser; + test('parsing nested x-enumDescription', () => { + const spec = require('../fixtures/nestedEnumDescroptionSample.json'); + parser = new OpenAPIParser(spec, undefined, opts); + const testSchema = spec.components.schemas.Test; + const schemaModel = new SchemaModel(parser, testSchema, '', opts); + + expect(schemaModel['x-enumDescriptions']).toStrictEqual( + testSchema.items['x-enumDescriptions'], + ); + }); + test('discriminator with one field', () => { const spec = require('../fixtures/discriminator.json'); parser = new OpenAPIParser(spec, undefined, opts); diff --git a/src/services/models/ApiInfo.ts b/src/services/models/ApiInfo.ts index 7a7d6db953..325161fe86 100644 --- a/src/services/models/ApiInfo.ts +++ b/src/services/models/ApiInfo.ts @@ -34,7 +34,7 @@ export class ApiInfoModel implements OpenAPIInfo { } this.downloadUrls = this.getDownloadUrls(); - this.downloadFileName = this.options.downloadFileName || 'openapi.json'; + this.downloadFileName = this.getDownloadFileName(); } private getDownloadUrls() { return ( @@ -68,4 +68,11 @@ export class ApiInfoModel implements OpenAPIInfo { return window.URL.createObjectURL(blob); } } + + private getDownloadFileName(): string | undefined { + if (!this.parser.specUrl && !this.options.downloadDefinitionUrl) { + return this.options.downloadFileName || 'openapi.json'; + } + return this.options.downloadFileName; + } } diff --git a/src/services/models/Schema.ts b/src/services/models/Schema.ts index 2a9def5d44..a07e071949 100644 --- a/src/services/models/Schema.ts +++ b/src/services/models/Schema.ts @@ -65,6 +65,7 @@ export class SchemaModel { rawSchema: OpenAPISchema; schema: MergedOpenAPISchema; extensions?: Record; + 'x-enumDescriptions': { [name: string]: string }; const: any; contentEncoding?: string; contentMediaType?: string; @@ -122,6 +123,7 @@ export class SchemaModel { this.type = schema.type || detectType(schema); this.format = schema.format; this.enum = schema.enum || []; + this['x-enumDescriptions'] = schema['x-enumDescriptions']; this.example = schema.example; this.examples = schema.examples; this.deprecated = !!schema.deprecated; @@ -221,6 +223,7 @@ export class SchemaModel { } if (this.items?.isPrimitive) { this.enum = this.items.enum; + this['x-enumDescriptions'] = this.items['x-enumDescriptions']; } if (isArray(this.type)) { const filteredType = this.type.filter(item => item !== 'array'); diff --git a/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap b/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap index cefc7d3736..21942671cc 100644 --- a/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap +++ b/src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap @@ -311,6 +311,11 @@ exports[`#loadAndBundleSpec should load And Bundle Spec demo/openapi.yaml 1`] = "sold", ], "type": "string", + "x-enumDescriptions": { + "available": "Available status", + "pending": "Pending status", + "sold": "Sold status", + }, }, "tags": { "description": "Tags attached to the pet", diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index 1d3a33c5f4..4c9b8395cd 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -654,6 +654,7 @@ export function isRedocExtension(key: string): boolean { 'x-codeSamples': true, 'x-displayName': true, 'x-examples': true, + 'x-enumDescriptions': true, 'x-ignoredHeaderParameters': true, 'x-logo': true, 'x-nullable': true, From bf97710c59831ad78935c5ea60c3fd2c08b13249 Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Fri, 17 Jan 2025 16:16:04 +0100 Subject: [PATCH 4/9] feat: add support schemaDefinitionsTagName --- src/services/MenuBuilder.ts | 27 +++++++++++++++++++++----- src/services/RedocNormalizedOptions.ts | 3 +++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/services/MenuBuilder.ts b/src/services/MenuBuilder.ts index 4ad28f7b77..e09c03bf79 100644 --- a/src/services/MenuBuilder.ts +++ b/src/services/MenuBuilder.ts @@ -1,4 +1,4 @@ -import type { OpenAPISpec, OpenAPIPaths, OpenAPITag, OpenAPISchema } from '../types'; +import type { OpenAPIPaths, OpenAPITag, OpenAPISchema } from '../types'; import { isOperationName, JsonPointer, alphabeticallyByProp } from '../utils'; import { MarkdownRenderer } from './MarkdownRenderer'; import { GroupModel, OperationModel } from './models'; @@ -17,9 +17,19 @@ export class MenuBuilder { options: RedocNormalizedOptions, ): ContentItemModel[] { const spec = parser.spec; + const { schemaDefinitionsTagName } = options; const items: ContentItemModel[] = []; - const tagsMap = MenuBuilder.getTagsWithOperations(parser, spec); + const tags = [...(spec.tags || [])]; + const hasAutogenerated = [...(spec.tags || [])].find( + tag => tag?.name === schemaDefinitionsTagName, + ); + console.log('hasAutogenerated', hasAutogenerated, schemaDefinitionsTagName); + if (!hasAutogenerated && schemaDefinitionsTagName) { + tags.push({ name: schemaDefinitionsTagName }); + } + const tagsMap = MenuBuilder.getTagsWithOperations(parser, tags); + items.push(...MenuBuilder.addMarkdownItems(spec.info.description || '', undefined, 1, options)); if (spec['x-tagGroups'] && spec['x-tagGroups'].length > 0) { items.push( @@ -28,6 +38,7 @@ export class MenuBuilder { } else { items.push(...MenuBuilder.getTagsItems(parser, tagsMap, undefined, undefined, options)); } + console.log('items', items); return items; } @@ -141,6 +152,7 @@ export class MenuBuilder { parser, tag, parent: item, + schemaDefinitionsTagName: options.schemaDefinitionsTagName, }); item.items = [ @@ -195,10 +207,11 @@ export class MenuBuilder { /** * collects tags and maps each tag to list of operations belonging to this tag */ - static getTagsWithOperations(parser: OpenAPIParser, spec: OpenAPISpec): TagsInfoMap { + static getTagsWithOperations(parser: OpenAPIParser, explicitTags: OpenAPITag[]): TagsInfoMap { + const { spec } = parser; const tags: TagsInfoMap = {}; const webhooks = spec['x-webhooks'] || spec.webhooks; - for (const tag of spec.tags || []) { + for (const tag of explicitTags || []) { tags[tag.name] = { ...tag, operations: [] }; } @@ -260,14 +273,18 @@ export class MenuBuilder { parser, tag, parent, + schemaDefinitionsTagName, }: { parser: OpenAPIParser; tag: TagInfo; parent: GroupModel; + schemaDefinitionsTagName?: string; }): GroupModel[] { + const defaultTags = schemaDefinitionsTagName ? [schemaDefinitionsTagName] : []; + return Object.entries(parser.spec.components?.schemas || {}) .map(([schemaName, schema]) => { - const schemaTags = schema['x-tags']; + const schemaTags = schema['x-tags'] || defaultTags; if (!schemaTags?.includes(tag.name)) return null; const item = new GroupModel( diff --git a/src/services/RedocNormalizedOptions.ts b/src/services/RedocNormalizedOptions.ts index 7bfbc963eb..951871c7ef 100644 --- a/src/services/RedocNormalizedOptions.ts +++ b/src/services/RedocNormalizedOptions.ts @@ -47,6 +47,7 @@ export interface RedocRawOptions { expandSingleSchemaField?: boolean | string; schemaExpansionLevel?: number | string | 'all'; // remove in next major release schemasExpansionLevel?: number | string | 'all'; + schemaDefinitionsTagName?: string; showObjectSchemaExamples?: boolean | string; showSecuritySchemeType?: boolean; hideSecuritySection?: boolean; @@ -253,6 +254,7 @@ export class RedocNormalizedOptions { payloadSampleIdx: number; expandSingleSchemaField: boolean; schemasExpansionLevel: number; + schemaDefinitionsTagName?: string; showObjectSchemaExamples: boolean; showSecuritySchemeType?: boolean; hideSecuritySection?: boolean; @@ -332,6 +334,7 @@ export class RedocNormalizedOptions { this.schemasExpansionLevel = argValueToExpandLevel( raw.schemasExpansionLevel || raw.schemaExpansionLevel, ); + this.schemaDefinitionsTagName = raw.schemaDefinitionsTagName; this.showObjectSchemaExamples = argValueToBoolean(raw.showObjectSchemaExamples); this.showSecuritySchemeType = argValueToBoolean(raw.showSecuritySchemeType); this.hideSecuritySection = argValueToBoolean(raw.hideSecuritySection); From 349a19487914b81294224421ecfd0d9edcdf9a1e Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Fri, 17 Jan 2025 16:27:01 +0100 Subject: [PATCH 5/9] fix: add support generatedSamplesMaxDepth instead of generatedPayloadSamplesMaxDepth --- .../DiscriminatorDropdown.test.tsx.snap | 30 ++++++++++++------- src/services/RedocNormalizedOptions.ts | 12 ++++---- src/services/models/MediaType.ts | 6 ++-- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap index 27cb32d2b5..75ff287938 100644 --- a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap @@ -84,7 +84,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, - "generatedPayloadSamplesMaxDepth": 10, + "generatedSamplesMaxDepth": 10, "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, @@ -104,6 +104,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "pathInMiddlePanel": false, "payloadSampleIdx": 0, "sanitize": false, + "schemaDefinitionsTagName": undefined, "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, @@ -358,7 +359,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, - "generatedPayloadSamplesMaxDepth": 10, + "generatedSamplesMaxDepth": 10, "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, @@ -378,6 +379,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "pathInMiddlePanel": false, "payloadSampleIdx": 0, "sanitize": false, + "schemaDefinitionsTagName": undefined, "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, @@ -619,7 +621,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, - "generatedPayloadSamplesMaxDepth": 10, + "generatedSamplesMaxDepth": 10, "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, @@ -639,6 +641,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "pathInMiddlePanel": false, "payloadSampleIdx": 0, "sanitize": false, + "schemaDefinitionsTagName": undefined, "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, @@ -942,7 +945,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, - "generatedPayloadSamplesMaxDepth": 10, + "generatedSamplesMaxDepth": 10, "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, @@ -962,6 +965,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "pathInMiddlePanel": false, "payloadSampleIdx": 0, "sanitize": false, + "schemaDefinitionsTagName": undefined, "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, @@ -1228,7 +1232,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, - "generatedPayloadSamplesMaxDepth": 10, + "generatedSamplesMaxDepth": 10, "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, @@ -1248,6 +1252,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "pathInMiddlePanel": false, "payloadSampleIdx": 0, "sanitize": false, + "schemaDefinitionsTagName": undefined, "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, @@ -1485,7 +1490,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, - "generatedPayloadSamplesMaxDepth": 10, + "generatedSamplesMaxDepth": 10, "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, @@ -1505,6 +1510,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "pathInMiddlePanel": false, "payloadSampleIdx": 0, "sanitize": false, + "schemaDefinitionsTagName": undefined, "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, @@ -1767,7 +1773,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, - "generatedPayloadSamplesMaxDepth": 10, + "generatedSamplesMaxDepth": 10, "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, @@ -1787,6 +1793,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "pathInMiddlePanel": false, "payloadSampleIdx": 0, "sanitize": false, + "schemaDefinitionsTagName": undefined, "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, @@ -2079,7 +2086,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, - "generatedPayloadSamplesMaxDepth": 10, + "generatedSamplesMaxDepth": 10, "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, @@ -2099,6 +2106,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "pathInMiddlePanel": false, "payloadSampleIdx": 0, "sanitize": false, + "schemaDefinitionsTagName": undefined, "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, @@ -2353,7 +2361,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, - "generatedPayloadSamplesMaxDepth": 10, + "generatedSamplesMaxDepth": 10, "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, @@ -2373,6 +2381,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "pathInMiddlePanel": false, "payloadSampleIdx": 0, "sanitize": false, + "schemaDefinitionsTagName": undefined, "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, @@ -2614,7 +2623,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "expandDefaultServerVariables": false, "expandResponses": {}, "expandSingleSchemaField": false, - "generatedPayloadSamplesMaxDepth": 10, + "generatedSamplesMaxDepth": 10, "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, @@ -2634,6 +2643,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "pathInMiddlePanel": false, "payloadSampleIdx": 0, "sanitize": false, + "schemaDefinitionsTagName": undefined, "schemasExpansionLevel": 0, "scrollYOffset": [Function], "showExtensions": false, diff --git a/src/services/RedocNormalizedOptions.ts b/src/services/RedocNormalizedOptions.ts index 951871c7ef..35b906a9ab 100644 --- a/src/services/RedocNormalizedOptions.ts +++ b/src/services/RedocNormalizedOptions.ts @@ -64,7 +64,8 @@ export interface RedocRawOptions { maxDisplayedEnumValues?: number; ignoreNamedSchemas?: string[] | string; hideSchemaPattern?: boolean; - generatedPayloadSamplesMaxDepth?: number; + generatedPayloadSamplesMaxDepth?: number; // remove in next major release + generatedSamplesMaxDepth?: number; nonce?: string; hideFab?: boolean; minCharacterLengthToInitSearch?: number; @@ -268,7 +269,7 @@ export class RedocNormalizedOptions { ignoreNamedSchemas: Set; hideSchemaPattern: boolean; - generatedPayloadSamplesMaxDepth: number; + generatedSamplesMaxDepth: number; hideFab: boolean; minCharacterLengthToInitSearch: number; showWebhookVerb: boolean; @@ -350,10 +351,9 @@ export class RedocNormalizedOptions { : raw.ignoreNamedSchemas?.split(',').map(s => s.trim()); this.ignoreNamedSchemas = new Set(ignoreNamedSchemas); this.hideSchemaPattern = argValueToBoolean(raw.hideSchemaPattern); - this.generatedPayloadSamplesMaxDepth = - RedocNormalizedOptions.normalizeGeneratedPayloadSamplesMaxDepth( - raw.generatedPayloadSamplesMaxDepth, - ); + this.generatedSamplesMaxDepth = RedocNormalizedOptions.normalizeGeneratedPayloadSamplesMaxDepth( + raw.generatedSamplesMaxDepth || raw.generatedPayloadSamplesMaxDepth, + ); this.nonce = raw.nonce; this.hideFab = argValueToBoolean(raw.hideFab); this.minCharacterLengthToInitSearch = argValueToNumber(raw.minCharacterLengthToInitSearch) || 3; diff --git a/src/services/models/MediaType.ts b/src/services/models/MediaType.ts index 1b7263ae95..de80042a68 100644 --- a/src/services/models/MediaType.ts +++ b/src/services/models/MediaType.ts @@ -14,7 +14,7 @@ export class MediaTypeModel { name: string; isRequestType: boolean; onlyRequiredInSamples: boolean; - generatedPayloadSamplesMaxDepth: number; + generatedSamplesMaxDepth: number; /** * @param isRequestType needed to know if skipe RO/RW fields in objects @@ -30,7 +30,7 @@ export class MediaTypeModel { this.isRequestType = isRequestType; this.schema = info.schema && new SchemaModel(parser, info.schema, '', options); this.onlyRequiredInSamples = options.onlyRequiredInSamples; - this.generatedPayloadSamplesMaxDepth = options.generatedPayloadSamplesMaxDepth; + this.generatedSamplesMaxDepth = options.generatedSamplesMaxDepth; if (info.examples !== undefined) { this.examples = mapValues( info.examples, @@ -55,7 +55,7 @@ export class MediaTypeModel { skipReadOnly: this.isRequestType, skipWriteOnly: !this.isRequestType, skipNonRequired: this.isRequestType && this.onlyRequiredInSamples, - maxSampleDepth: this.generatedPayloadSamplesMaxDepth, + maxSampleDepth: this.generatedSamplesMaxDepth, }; if (this.schema && this.schema.oneOf) { this.examples = {}; From 93d6b6c431ada51607adfa6aec1a9fcb7609badf Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Tue, 21 Jan 2025 11:56:37 +0100 Subject: [PATCH 6/9] feat: add support hidePropertiesPrefix --- src/components/Fields/Field.tsx | 20 ++++++++++++++++--- src/components/Schema/ArraySchema.tsx | 14 +++++++++++-- src/components/Schema/ObjectSchema.tsx | 3 +++ src/components/Schema/Schema.tsx | 1 + .../DiscriminatorDropdown.test.tsx.snap | 12 +++++++++++ src/services/MenuBuilder.ts | 4 ++-- src/services/RedocNormalizedOptions.ts | 3 +++ 7 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/components/Fields/Field.tsx b/src/components/Fields/Field.tsx index d142c9a5c4..8ac4ef7cf2 100644 --- a/src/components/Fields/Field.tsx +++ b/src/components/Fields/Field.tsx @@ -19,6 +19,8 @@ import { Schema } from '../Schema/Schema'; import type { SchemaOptions } from '../Schema/Schema'; import type { FieldModel } from '../../services/models'; +import { OptionsContext } from '../OptionsProvider'; +import { RedocNormalizedOptions } from '../../services/RedocNormalizedOptions'; export interface FieldProps extends SchemaOptions { className?: string; @@ -27,12 +29,15 @@ export interface FieldProps extends SchemaOptions { field: FieldModel; expandByDefault?: boolean; - + fieldParentsName?: string[]; renderDiscriminatorSwitch?: (opts: FieldProps) => JSX.Element; } @observer export class Field extends React.Component { + static contextType = OptionsContext; + context: RedocNormalizedOptions; + toggle = () => { if (this.props.field.expanded === undefined && this.props.expandByDefault) { this.props.field.collapse(); @@ -49,12 +54,12 @@ export class Field extends React.Component { }; render() { - const { className = '', field, isLast, expandByDefault } = this.props; + const { hidePropertiesPrefix } = this.context; + const { className = '', field, isLast, expandByDefault, fieldParentsName = [] } = this.props; const { name, deprecated, required, kind } = field; const withSubSchema = !field.schema.isPrimitive && !field.schema.isCircular; const expanded = field.expanded === undefined ? expandByDefault : field.expanded; - const labels = ( <> {kind === 'additionalProperties' && additional property} @@ -75,6 +80,10 @@ export class Field extends React.Component { onKeyPress={this.handleKeyPress} aria-label={`expand ${name}`} > + {!hidePropertiesPrefix && + fieldParentsName.map( + name => name + '.\u200B', // zero-width space, a special character is used for correct line breaking + )} {name} @@ -83,6 +92,10 @@ export class Field extends React.Component { ) : ( + {!hidePropertiesPrefix && + fieldParentsName.map( + name => name + '.\u200B', // zero-width space, a special character is used for correct line breaking + )} {name} {labels} @@ -102,6 +115,7 @@ export class Field extends React.Component { { render() { const schema = this.props.schema; const itemsSchema = schema.items; + const fieldParentsName = this.props.fieldParentsName; const minMaxItems = schema.minItems === undefined && schema.maxItems === undefined ? '' : `(${humanizeConstraints(schema)})`; + const updatedParentsArray = fieldParentsName + ? [...fieldParentsName.slice(0, -1), fieldParentsName[fieldParentsName.length - 1] + '[]'] + : fieldParentsName; if (schema.fields) { - return ; + return ( + + ); } if (schema.displayType && !itemsSchema && !minMaxItems.length) { return ( @@ -37,7 +47,7 @@ export class ArraySchema extends React.PureComponent {
Array {minMaxItems} - +
diff --git a/src/components/Schema/ObjectSchema.tsx b/src/components/Schema/ObjectSchema.tsx index 765230cbf1..b59ea989f5 100644 --- a/src/components/Schema/ObjectSchema.tsx +++ b/src/components/Schema/ObjectSchema.tsx @@ -16,6 +16,7 @@ export interface ObjectSchemaProps extends SchemaProps { fieldName: string; parentSchema: SchemaModel; }; + fieldParentsName?: string[]; } export const ObjectSchema = observer( @@ -26,6 +27,7 @@ export const ObjectSchema = observer( skipReadOnly, skipWriteOnly, level, + fieldParentsName, }: ObjectSchemaProps) => { const { expandSingleSchemaField, showObjectSchemaExamples, schemasExpansionLevel } = React.useContext(OptionsContext); @@ -58,6 +60,7 @@ export const ObjectSchema = observer( isLast={isLast} field={field} expandByDefault={expandByDefault} + fieldParentsName={Number(level) > 1 ? fieldParentsName : []} renderDiscriminatorSwitch={ discriminator?.fieldName === field.name ? () => ( diff --git a/src/components/Schema/Schema.tsx b/src/components/Schema/Schema.tsx index c0d38b1ea8..b8c08cd512 100644 --- a/src/components/Schema/Schema.tsx +++ b/src/components/Schema/Schema.tsx @@ -21,6 +21,7 @@ export interface SchemaOptions { export interface SchemaProps extends SchemaOptions { schema: SchemaModel; + fieldParentsName?: string[]; } @observer diff --git a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap index 75ff287938..f43b92da0b 100644 --- a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap @@ -88,6 +88,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, + "hidePropertiesPrefix": true, "hideRequestPayloadSample": false, "hideSchemaPattern": false, "hideSchemaTitles": false, @@ -363,6 +364,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, + "hidePropertiesPrefix": true, "hideRequestPayloadSample": false, "hideSchemaPattern": false, "hideSchemaTitles": false, @@ -625,6 +627,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, + "hidePropertiesPrefix": true, "hideRequestPayloadSample": false, "hideSchemaPattern": false, "hideSchemaTitles": false, @@ -949,6 +952,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, + "hidePropertiesPrefix": true, "hideRequestPayloadSample": false, "hideSchemaPattern": false, "hideSchemaTitles": false, @@ -1236,6 +1240,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, + "hidePropertiesPrefix": true, "hideRequestPayloadSample": false, "hideSchemaPattern": false, "hideSchemaTitles": false, @@ -1494,6 +1499,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, + "hidePropertiesPrefix": true, "hideRequestPayloadSample": false, "hideSchemaPattern": false, "hideSchemaTitles": false, @@ -1777,6 +1783,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, + "hidePropertiesPrefix": true, "hideRequestPayloadSample": false, "hideSchemaPattern": false, "hideSchemaTitles": false, @@ -2090,6 +2097,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, + "hidePropertiesPrefix": true, "hideRequestPayloadSample": false, "hideSchemaPattern": false, "hideSchemaTitles": false, @@ -2365,6 +2373,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, + "hidePropertiesPrefix": true, "hideRequestPayloadSample": false, "hideSchemaPattern": false, "hideSchemaTitles": false, @@ -2627,6 +2636,7 @@ exports[`Components SchemaView discriminator should correctly render SchemaView "hideDownloadButtons": false, "hideFab": false, "hideHostname": false, + "hidePropertiesPrefix": true, "hideRequestPayloadSample": false, "hideSchemaPattern": false, "hideSchemaTitles": false, @@ -2955,6 +2965,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat }, } } + fieldParentsName={[]} isLast={false} key="packSize" showExamples={false} @@ -3029,6 +3040,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat }, } } + fieldParentsName={[]} isLast={true} key="type" renderDiscriminatorSwitch={[Function]} diff --git a/src/services/MenuBuilder.ts b/src/services/MenuBuilder.ts index e09c03bf79..fa50239e95 100644 --- a/src/services/MenuBuilder.ts +++ b/src/services/MenuBuilder.ts @@ -24,7 +24,7 @@ export class MenuBuilder { const hasAutogenerated = [...(spec.tags || [])].find( tag => tag?.name === schemaDefinitionsTagName, ); - console.log('hasAutogenerated', hasAutogenerated, schemaDefinitionsTagName); + if (!hasAutogenerated && schemaDefinitionsTagName) { tags.push({ name: schemaDefinitionsTagName }); } @@ -38,7 +38,7 @@ export class MenuBuilder { } else { items.push(...MenuBuilder.getTagsItems(parser, tagsMap, undefined, undefined, options)); } - console.log('items', items); + return items; } diff --git a/src/services/RedocNormalizedOptions.ts b/src/services/RedocNormalizedOptions.ts index 35b906a9ab..a84cb95af9 100644 --- a/src/services/RedocNormalizedOptions.ts +++ b/src/services/RedocNormalizedOptions.ts @@ -70,6 +70,7 @@ export interface RedocRawOptions { hideFab?: boolean; minCharacterLengthToInitSearch?: number; showWebhookVerb?: boolean; + hidePropertiesPrefix?: boolean; } export function argValueToBoolean(val?: string | boolean, defaultValue?: boolean): boolean { @@ -273,6 +274,7 @@ export class RedocNormalizedOptions { hideFab: boolean; minCharacterLengthToInitSearch: number; showWebhookVerb: boolean; + hidePropertiesPrefix?: boolean; nonce?: string; @@ -358,5 +360,6 @@ export class RedocNormalizedOptions { this.hideFab = argValueToBoolean(raw.hideFab); this.minCharacterLengthToInitSearch = argValueToNumber(raw.minCharacterLengthToInitSearch) || 3; this.showWebhookVerb = argValueToBoolean(raw.showWebhookVerb); + this.hidePropertiesPrefix = argValueToBoolean(raw.hidePropertiesPrefix, true); } } From f6debde4c96708b238879a6deab2502550944671 Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Wed, 22 Jan 2025 13:02:12 +0200 Subject: [PATCH 7/9] chore: improve code quality Co-authored-by: Ivan Kropyvnytskyi <130547411+ivankropyvnytskyi@users.noreply.github.com> --- src/services/MenuBuilder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/MenuBuilder.ts b/src/services/MenuBuilder.ts index fa50239e95..d4b2450748 100644 --- a/src/services/MenuBuilder.ts +++ b/src/services/MenuBuilder.ts @@ -21,7 +21,7 @@ export class MenuBuilder { const items: ContentItemModel[] = []; const tags = [...(spec.tags || [])]; - const hasAutogenerated = [...(spec.tags || [])].find( + const hasAutogenerated = tags.find( tag => tag?.name === schemaDefinitionsTagName, ); From a680dd716b042e104d0bea776372b829de19b51d Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Thu, 30 Jan 2025 11:34:09 +0100 Subject: [PATCH 8/9] fix: behaviour for downloadUrls and extend examples to description file --- demo/museum.yaml | 6 ++++++ demo/playground/hmr-playground.tsx | 6 +++++- src/components/ApiInfo/ApiInfo.tsx | 2 +- src/services/models/ApiInfo.ts | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/demo/museum.yaml b/demo/museum.yaml index 8473be695a..ccbed4d22a 100644 --- a/demo/museum.yaml +++ b/demo/museum.yaml @@ -309,6 +309,9 @@ components: enum: - event - general + x-enumDescriptions: + event: Special event ticket + general: General museum entry ticket example: event Date: type: string @@ -776,6 +779,9 @@ x-tagGroups: - name: Purchases tags: - Tickets + - name: Entities + tags: + - schemas security: - MuseumPlaceholderAuth: [] diff --git a/demo/playground/hmr-playground.tsx b/demo/playground/hmr-playground.tsx index 16bb507b76..8594482870 100644 --- a/demo/playground/hmr-playground.tsx +++ b/demo/playground/hmr-playground.tsx @@ -11,7 +11,11 @@ const userUrl = window.location.search.match(/url=(.*)$/); const specUrl = (userUrl && userUrl[1]) || (swagger ? 'museum.yaml' : big ? 'big-openapi.json' : 'museum.yaml'); -const options: RedocRawOptions = { nativeScrollbars: false, maxDisplayedEnumValues: 3 }; +const options: RedocRawOptions = { + nativeScrollbars: false, + maxDisplayedEnumValues: 3, + schemaDefinitionsTagName: 'schemas', +}; const container = document.getElementById('example'); const root = createRoot(container!); diff --git a/src/components/ApiInfo/ApiInfo.tsx b/src/components/ApiInfo/ApiInfo.tsx index 6c25c69556..75e004387a 100644 --- a/src/components/ApiInfo/ApiInfo.tsx +++ b/src/components/ApiInfo/ApiInfo.tsx @@ -88,7 +88,7 @@ export class ApiInfo extends React.Component { rel="noreferrer" key={url} > - {downloadUrls.length > 1 ? title : l('download')} + {title} ); })} diff --git a/src/services/models/ApiInfo.ts b/src/services/models/ApiInfo.ts index 325161fe86..db6d3fa534 100644 --- a/src/services/models/ApiInfo.ts +++ b/src/services/models/ApiInfo.ts @@ -46,7 +46,7 @@ export class ApiInfoModel implements OpenAPIInfo { }, ] : this.options.downloadUrls.map(({ title, url }) => ({ - title: title || 'Download OpenAPI description', + title: title || l('download'), url: this.getDownloadLink(url), })) ).filter(({ title, url }) => title && url); From 682e38e397d9b676fbf2e53c9f142880830feae1 Mon Sep 17 00:00:00 2001 From: Alex Varchuk Date: Thu, 30 Jan 2025 12:55:56 +0200 Subject: [PATCH 9/9] Update demo/museum.yaml --- demo/museum.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/museum.yaml b/demo/museum.yaml index ccbed4d22a..5c34236e7f 100644 --- a/demo/museum.yaml +++ b/demo/museum.yaml @@ -781,7 +781,7 @@ x-tagGroups: - Tickets - name: Entities tags: - - schemas + - Schemas security: - MuseumPlaceholderAuth: []