From 43597244345c3d2989563944fb06703cd9805305 Mon Sep 17 00:00:00 2001 From: Dimitar Nanov Date: Fri, 19 Oct 2018 19:53:19 +0300 Subject: [PATCH] add section menus for tags and object description --- demo/openapi.yaml | 23 ++++++- .../ObjectDescription/ObjectDescription.tsx | 64 +++++++++++++++++++ src/services/AppStore.ts | 14 +++- src/services/MenuBuilder.ts | 16 +++-- src/services/models/Group.model.ts | 7 ++ src/utils/openapi.ts | 1 + 6 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 src/components/ObjectDescription/ObjectDescription.tsx diff --git a/demo/openapi.yaml b/demo/openapi.yaml index 5c10ab88..f36f5b79 100644 --- a/demo/openapi.yaml +++ b/demo/openapi.yaml @@ -58,9 +58,21 @@ externalDocs: url: 'https://github.com/Rebilly/generator-openapi-repo' tags: - name: pet - description: Everything about your Pets + description: | + Everything about your Pets + + ## The Pet Object + + + - name: store - description: Access to Petstore orders + description: | + Access to Petstore orders + + ## The Order Object + + + - name: user description: Operations about user x-tagGroups: @@ -926,3 +938,10 @@ components: type: apiKey name: api_key in: header + examples: + Order: + value: + quantity: 1, + shipDate: 2018-10-19T16:46:45Z, + status: placed, + complete: false diff --git a/src/components/ObjectDescription/ObjectDescription.tsx b/src/components/ObjectDescription/ObjectDescription.tsx new file mode 100644 index 00000000..7a14b6de --- /dev/null +++ b/src/components/ObjectDescription/ObjectDescription.tsx @@ -0,0 +1,64 @@ +import * as React from 'react'; +import { Schema } from '../Schema'; + +import { MiddlePanel, Row, Section, DarkRightPanel } from '../../common-elements'; +import { OpenAPIParser, RedocNormalizedOptions, MediaTypeModel } from '../../services'; +import { MediaTypeSamples } from '../PayloadSamples/MediaTypeSamples'; +import { OpenAPIMediaType } from '../../types'; + +export interface ObjectDescriptionProps { + schemaRef: string; + examplesRef?: string; + parser: OpenAPIParser; + options: RedocNormalizedOptions; +} + +export class ObjectDescription extends React.PureComponent { + private mediaModel: MediaTypeModel; + + constructor(props: ObjectDescriptionProps) { + super(props); + this.mediaModel = ObjectDescription.getMediaModel(this.props); + } + + render() { + return ( +
+ + + + + + + + +
+ ); + } + + private static getMediaType(schemaRef, examplesRef): OpenAPIMediaType { + if (!schemaRef) return {}; + + const info: OpenAPIMediaType = { + schema: { $ref: schemaRef }, + }; + + if (examplesRef) info.examples = { object: { $ref: examplesRef } }; + return info; + } + + private static getMediaModel({ + schemaRef, + examplesRef, + parser, + options, + }: ObjectDescriptionProps) { + return new MediaTypeModel( + parser, + 'json', + false, + ObjectDescription.getMediaType(schemaRef, examplesRef), + options, + ); + } +} diff --git a/src/services/AppStore.ts b/src/services/AppStore.ts index 75fb64d9..2f004454 100644 --- a/src/services/AppStore.ts +++ b/src/services/AppStore.ts @@ -10,8 +10,12 @@ import { RedocNormalizedOptions, RedocRawOptions } from './RedocNormalizedOption import { ScrollService } from './ScrollService'; import { SearchStore } from './SearchStore'; +import { ObjectDescription } from '../components/ObjectDescription/ObjectDescription'; import { SecurityDefs } from '../components/SecuritySchemes/SecuritySchemes'; -import { SECURITY_DEFINITIONS_COMPONENT_NAME } from '../utils/openapi'; +import { + SECURITY_DEFINITIONS_COMPONENT_NAME, + OBJECT_DEFINTION_COMPONENT_NAME, +} from '../utils/openapi'; export interface StoreState { menu: { @@ -151,5 +155,13 @@ const DEFAULT_OPTIONS: RedocRawOptions = { securitySchemes: store.spec.securitySchemes, }), }, + [OBJECT_DEFINTION_COMPONENT_NAME]: { + component: ObjectDescription, + propsSelector: (store: AppStore) => ({ + securitySchemes: store.spec.securitySchemes, + parser: store.spec.parser, + options: store.options, + }), + }, }, }; diff --git a/src/services/MenuBuilder.ts b/src/services/MenuBuilder.ts index 78876102..99543f92 100644 --- a/src/services/MenuBuilder.ts +++ b/src/services/MenuBuilder.ts @@ -42,7 +42,7 @@ export class MenuBuilder { const items: ContentItemModel[] = []; const tagsMap = MenuBuilder.getTagsWithOperations(spec); - items.push(...MenuBuilder.addMarkdownItems(spec.info.description || '', options)); + items.push(...MenuBuilder.addMarkdownItems(spec.info.description || '', undefined, options)); if (spec['x-tagGroups'] && spec['x-tagGroups'].length > 0) { items.push( ...MenuBuilder.getTagGroupsItems(parser, undefined, spec['x-tagGroups'], tagsMap, options), @@ -59,6 +59,7 @@ export class MenuBuilder { */ static addMarkdownItems( description: string, + parent: GroupModel | undefined, options: RedocNormalizedOptions, ): ContentItemModel[] { const renderer = new MarkdownRenderer(options); @@ -82,7 +83,7 @@ export class MenuBuilder { return group; }); - return mapHeadingsDeep(undefined, headings); + return mapHeadingsDeep(parent, headings, 1); } /** @@ -144,15 +145,22 @@ export class MenuBuilder { } const item = new GroupModel('tag', tag, parent); item.depth = GROUP_DEPTH + 1; - item.items = this.getOperationsItems(parser, item, tag, item.depth + 1, options); // don't put empty tag into content, instead put its operations if (tag.name === '') { - const items = this.getOperationsItems(parser, undefined, tag, item.depth + 1, options); + const items = [ + ...MenuBuilder.addMarkdownItems(tag.description || '', item, options), + ...this.getOperationsItems(parser, undefined, tag, item.depth + 1, options), + ]; res.push(...items); continue; } + item.items = [ + ...MenuBuilder.addMarkdownItems(tag.description || '', item, options), + ...this.getOperationsItems(parser, item, tag, item.depth + 1, options), + ]; + res.push(item); } return res; diff --git a/src/services/models/Group.model.ts b/src/services/models/Group.model.ts index c1aaa246..bb82e18a 100644 --- a/src/services/models/Group.model.ts +++ b/src/services/models/Group.model.ts @@ -40,7 +40,14 @@ export class GroupModel implements IMenuItem { this.type = type; this.name = tagOrGroup['x-displayName'] || tagOrGroup.name; this.level = (tagOrGroup as MarkdownHeading).level || 1; + + // remove sections from markdown, same as in ApiInfo this.description = tagOrGroup.description || ''; + const firstHeadingLinePos = this.description.search(/^##?\s+/m); + if (firstHeadingLinePos > -1) { + this.description = this.description.substring(0, firstHeadingLinePos); + } + this.parent = parent; this.externalDocs = (tagOrGroup as OpenAPITag).externalDocs; diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts index 294c8f32..4d09252f 100644 --- a/src/utils/openapi.ts +++ b/src/utils/openapi.ts @@ -495,6 +495,7 @@ export function normalizeServers( }); } +export const OBJECT_DEFINTION_COMPONENT_NAME = 'object-description'; export const SECURITY_DEFINITIONS_COMPONENT_NAME = 'security-definitions'; export let SECURITY_SCHEMES_SECTION_PREFIX = 'section/Authentication/'; export function setSecuritySchemePrefix(prefix: string) {