From 893c83ed07c6a18ce0412cd3c7ac9a44a02e0d59 Mon Sep 17 00:00:00 2001 From: Matthias Mohr Date: Mon, 10 Sep 2018 17:23:17 +0200 Subject: [PATCH] feat: externalDocumentation rendered for tags, operations and schema fields (#595) The externalDocs were missing for tags, schemata and operations. Added them with this pull requests. Solves #550. Additionally, fixes that the URL in External Documentation Object was specified to be optional, which is not correct according to OpenAPI spec. --- demo/openapi.yaml | 3 ++ src/components/ApiInfo/ApiInfo.tsx | 9 ++---- src/components/ContentItems/ContentItems.tsx | 10 ++++++- .../ExternalDocumentation.tsx | 29 +++++++++++++++++++ src/components/Fields/FieldDetails.tsx | 6 +++- src/components/Markdown/AdvancedMarkdown.tsx | 2 +- src/components/Markdown/Markdown.tsx | 6 ++-- src/components/Operation/Operation.tsx | 14 +++++++-- src/components/Responses/ResponseTitle.tsx | 2 +- src/components/Schema/Schema.tsx | 1 + .../DiscriminatorDropdown.test.tsx.snap | 2 ++ src/services/models/Schema.ts | 4 ++- src/types/open-api.d.ts | 2 +- 13 files changed, 71 insertions(+), 19 deletions(-) create mode 100644 src/components/ExternalDocumentation/ExternalDocumentation.tsx diff --git a/demo/openapi.yaml b/demo/openapi.yaml index 94ca4d11b5..1a06f4f6d6 100644 --- a/demo/openapi.yaml +++ b/demo/openapi.yaml @@ -767,6 +767,9 @@ components: bee: '#/components/schemas/HoneyBee' properties: id: + externalDocs: + description: "Find more info here" + url: "https://example.com" description: Pet ID allOf: - $ref: '#/components/schemas/Id' diff --git a/src/components/ApiInfo/ApiInfo.tsx b/src/components/ApiInfo/ApiInfo.tsx index 80d913a33d..e1fcaaea3f 100644 --- a/src/components/ApiInfo/ApiInfo.tsx +++ b/src/components/ApiInfo/ApiInfo.tsx @@ -4,6 +4,7 @@ import * as React from 'react'; import { AppStore } from '../../services/AppStore'; import { MiddlePanel, Row, Section } from '../../common-elements/'; +import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation'; import { Markdown } from '../Markdown/Markdown'; import { StyledMarkdownBlock } from '../Markdown/styled.elements'; import { @@ -98,15 +99,9 @@ export class ApiInfo extends React.Component { )) || null} - - {(externalDocs && ( -

- {externalDocs.description || externalDocs.url} -

- )) || - null} + {externalDocs && } diff --git a/src/components/ContentItems/ContentItems.tsx b/src/components/ContentItems/ContentItems.tsx index 8bfde5d906..02efc38975 100644 --- a/src/components/ContentItems/ContentItems.tsx +++ b/src/components/ContentItems/ContentItems.tsx @@ -1,6 +1,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; +import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation'; import { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown'; import { H1, H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements'; @@ -64,7 +65,7 @@ const middlePanelWrap = component => {component}; @observer export class SectionItem extends React.Component { render() { - const { name, description, level } = this.props.item as GroupModel; + const { name, description, externalDocs, level } = this.props.item as GroupModel; const Header = level === 2 ? H2 : H1; return ( @@ -78,6 +79,13 @@ export class SectionItem extends React.Component { + {externalDocs && ( + + + + + + )} ); } diff --git a/src/components/ExternalDocumentation/ExternalDocumentation.tsx b/src/components/ExternalDocumentation/ExternalDocumentation.tsx new file mode 100644 index 0000000000..eb76b41452 --- /dev/null +++ b/src/components/ExternalDocumentation/ExternalDocumentation.tsx @@ -0,0 +1,29 @@ +import { observer } from 'mobx-react'; +import * as React from 'react'; +import styled, { withProps } from '../../styled-components'; +import { OpenAPIExternalDocumentation } from '../../types'; +import { linksCss } from '../Markdown/styled.elements'; + +const LinkWrap = withProps<{ compact?: boolean }>(styled.div)` + ${linksCss}; + ${({ compact }) => (!compact ? 'margin: 1em 0' : '')} +`; + +@observer +export class ExternalDocumentation extends React.Component<{ + externalDocs: OpenAPIExternalDocumentation; + compact?: boolean; +}> { + render() { + const { externalDocs } = this.props; + if (!externalDocs || !externalDocs.url) { + return null; + } + + return ( + + {externalDocs.description || externalDocs.url} + + ); + } +} diff --git a/src/components/Fields/FieldDetails.tsx b/src/components/Fields/FieldDetails.tsx index 0d063226e6..838351ec30 100644 --- a/src/components/Fields/FieldDetails.tsx +++ b/src/components/Fields/FieldDetails.tsx @@ -9,6 +9,7 @@ import { TypePrefix, TypeTitle, } from '../../common-elements/fields'; +import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation'; import { Markdown } from '../Markdown/Markdown'; import { EnumValues } from './EnumValues'; import { FieldProps } from './Field'; @@ -51,8 +52,11 @@ export class FieldDetails extends React.PureComponent { {!renderDiscriminatorSwitch && }{' '} {showExamples && }
- +
+ {schema.externalDocs && ( + + )} {(renderDiscriminatorSwitch && renderDiscriminatorSwitch(this.props)) || null} ); diff --git a/src/components/Markdown/AdvancedMarkdown.tsx b/src/components/Markdown/AdvancedMarkdown.tsx index b75631d3c6..0b1bbed778 100644 --- a/src/components/Markdown/AdvancedMarkdown.tsx +++ b/src/components/Markdown/AdvancedMarkdown.tsx @@ -38,7 +38,7 @@ export class AdvancedMarkdown extends React.Component { return parts.map((part, idx) => { if (typeof part === 'string') { return React.cloneElement( - htmlWrap(), + htmlWrap(), { key: idx }, ); } diff --git a/src/components/Markdown/Markdown.tsx b/src/components/Markdown/Markdown.tsx index 0259b04ec4..544a2bc8c8 100644 --- a/src/components/Markdown/Markdown.tsx +++ b/src/components/Markdown/Markdown.tsx @@ -4,7 +4,7 @@ import { MarkdownRenderer } from '../../services'; import { SanitizedMarkdownHTML } from './SanitizedMdBlock'; export interface StylingMarkdownProps { - dense?: boolean; + compact?: boolean; inline?: boolean; } @@ -21,13 +21,13 @@ export type MarkdownProps = BaseMarkdownProps & export class Markdown extends React.Component { render() { - const { source, inline, dense, className } = this.props; + const { source, inline, compact, className } = this.props; const renderer = new MarkdownRenderer(); return ( ); diff --git a/src/components/Operation/Operation.tsx b/src/components/Operation/Operation.tsx index 62a8d05512..320a6621d6 100644 --- a/src/components/Operation/Operation.tsx +++ b/src/components/Operation/Operation.tsx @@ -9,6 +9,7 @@ import { OptionsContext } from '../OptionsProvider'; import { ShareLink } from '../../common-elements/linkify'; import { Endpoint } from '../Endpoint/Endpoint'; +import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation'; import { Markdown } from '../Markdown/Markdown'; import { Parameters } from '../Parameters/Parameters'; import { RequestSamples } from '../RequestSamples/RequestSamples'; @@ -25,7 +26,7 @@ const OperationRow = styled(Row)` overflow: hidden; `; -const Description = styled(Markdown)` +const Description = styled.div` margin-bottom: ${({ theme }) => theme.spacing.unit * 6}px; `; @@ -38,7 +39,9 @@ export class Operation extends React.Component { render() { const { operation } = this.props; - const { name: summary, description, deprecated } = operation; + const { name: summary, description, deprecated, externalDocs } = operation; + const hasDescription = !!(description || externalDocs); + return ( {options => ( @@ -49,7 +52,12 @@ export class Operation extends React.Component { {summary} {deprecated && Deprecated } {options.pathInMiddlePanel && } - {description !== undefined && } + {hasDescription && ( + + {description !== undefined && } + {externalDocs && } + + )} diff --git a/src/components/Responses/ResponseTitle.tsx b/src/components/Responses/ResponseTitle.tsx index 00169340f9..be66fdc86d 100644 --- a/src/components/Responses/ResponseTitle.tsx +++ b/src/components/Responses/ResponseTitle.tsx @@ -27,7 +27,7 @@ export class ResponseTitle extends React.PureComponent { /> )} {code} - + ); } diff --git a/src/components/Schema/Schema.tsx b/src/components/Schema/Schema.tsx index d875071029..22e1eabc03 100644 --- a/src/components/Schema/Schema.tsx +++ b/src/components/Schema/Schema.tsx @@ -75,6 +75,7 @@ export class Schema extends React.Component> { name: '', required: false, description: schema.description, + externalDocs: schema.externalDocs, deprecated: false, toggle: () => null, expanded: false, diff --git a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap index ab1a367c03..5aa3ccfec8 100644 --- a/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap +++ b/src/components/__tests__/__snapshots__/DiscriminatorDropdown.test.tsx.snap @@ -24,6 +24,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat "displayType": "number", "enum": Array [], "example": undefined, + "externalDocs": undefined, "format": undefined, "isCircular": undefined, "isPrimitive": true, @@ -72,6 +73,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat "displayType": "string", "enum": Array [], "example": undefined, + "externalDocs": undefined, "format": undefined, "isCircular": undefined, "isPrimitive": true, diff --git a/src/services/models/Schema.ts b/src/services/models/Schema.ts index 8e1d377747..69f0e0b098 100644 --- a/src/services/models/Schema.ts +++ b/src/services/models/Schema.ts @@ -1,6 +1,6 @@ import { action, observable } from 'mobx'; -import { OpenAPISchema, Referenced } from '../../types'; +import { OpenAPIExternalDocumentation, OpenAPISchema, Referenced } from '../../types'; import { OpenAPIParser } from '../OpenAPIParser'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; @@ -25,6 +25,7 @@ export class SchemaModel { typePrefix: string = ''; title: string; description: string; + externalDocs?: OpenAPIExternalDocumentation; isPrimitive: boolean; isCircular: boolean = false; @@ -101,6 +102,7 @@ export class SchemaModel { this.example = schema.example; this.deprecated = !!schema.deprecated; this.pattern = schema.pattern; + this.externalDocs = schema.externalDocs; this.constraints = humanizeConstraints(schema); this.displayType = this.type; diff --git a/src/types/open-api.d.ts b/src/types/open-api.d.ts index 2d6287363f..4fb04a83d1 100644 --- a/src/types/open-api.d.ts +++ b/src/types/open-api.d.ts @@ -255,7 +255,7 @@ export interface OpenAPITag { export interface OpenAPIExternalDocumentation { description?: string; - url?: string; + url: string; } export interface OpenAPIContact {