From f1eb03fd665f626bfd2e5b4248eef5554d4f3cec Mon Sep 17 00:00:00 2001 From: Makashov Nurbol Date: Wed, 26 Dec 2018 16:56:22 +0600 Subject: [PATCH] Added callbacks support --- src/components/ApiInfo/ApiInfo.tsx | 24 ++++----- src/components/Callbacks/Callback.tsx | 26 +++++++++ src/components/Callbacks/CallbackTitle.tsx | 22 ++++++++ src/components/Callbacks/CallbacksList.tsx | 35 ++++++++++++ src/components/Callbacks/styled.elements.ts | 17 ++++++ src/components/ContentItems/ContentItems.tsx | 30 +++++++++-- src/components/Fields/Field.tsx | 29 +++++----- src/components/Responses/Response.tsx | 11 ++-- .../SecuritySchemes/SecuritySchemes.tsx | 13 +++-- src/components/SideMenu/MenuItem.tsx | 23 ++++---- src/services/models/Callback.ts | 53 +++++++++++++++++++ src/services/models/MediaType.ts | 12 +---- src/services/models/Operation.ts | 17 +++++- src/services/models/index.ts | 1 + 14 files changed, 244 insertions(+), 69 deletions(-) create mode 100644 src/components/Callbacks/Callback.tsx create mode 100644 src/components/Callbacks/CallbackTitle.tsx create mode 100644 src/components/Callbacks/CallbacksList.tsx create mode 100644 src/components/Callbacks/styled.elements.ts create mode 100644 src/services/models/Callback.ts diff --git a/src/components/ApiInfo/ApiInfo.tsx b/src/components/ApiInfo/ApiInfo.tsx index e1fcaaea..198fc541 100644 --- a/src/components/ApiInfo/ApiInfo.tsx +++ b/src/components/ApiInfo/ApiInfo.tsx @@ -44,22 +44,20 @@ export class ApiInfo extends React.Component { null; const website = - (info.contact && - info.contact.url && ( - - URL: {info.contact.url} - - )) || + (info.contact && info.contact.url && ( + + URL: {info.contact.url} + + )) || null; const email = - (info.contact && - info.contact.email && ( - - {info.contact.name || 'E-mail'}:{' '} - {info.contact.email} - - )) || + (info.contact && info.contact.email && ( + + {info.contact.name || 'E-mail'}:{' '} + {info.contact.email} + + )) || null; const terms = diff --git a/src/components/Callbacks/Callback.tsx b/src/components/Callbacks/Callback.tsx new file mode 100644 index 00000000..ffec7f6c --- /dev/null +++ b/src/components/Callbacks/Callback.tsx @@ -0,0 +1,26 @@ +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { CallbackModel } from '../../services/models'; +import { CallbackDetailsWrap, StyledCallbackTitle } from '../Callbacks/styled.elements'; + +@observer +export class CallbackView extends React.Component<{ callback: CallbackModel }> { + toggle = () => { + this.props.callback.toggle(); + }; + + render() { + const { name, expanded } = this.props.callback; + + return ( +
+ + {expanded && ( + + {name} + + )} +
+ ); + } +} diff --git a/src/components/Callbacks/CallbackTitle.tsx b/src/components/Callbacks/CallbackTitle.tsx new file mode 100644 index 00000000..f594238e --- /dev/null +++ b/src/components/Callbacks/CallbackTitle.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; + +import { ShelfIcon } from '../../common-elements'; + +export interface CallbackTitleProps { + name: string; + opened?: boolean; + className?: string; + onClick?: () => void; +} + +export class CallbackTitle extends React.PureComponent { + render() { + const { name, opened, className, onClick } = this.props; + return ( +
+ + {name} +
+ ); + } +} diff --git a/src/components/Callbacks/CallbacksList.tsx b/src/components/Callbacks/CallbacksList.tsx new file mode 100644 index 00000000..826cdb2a --- /dev/null +++ b/src/components/Callbacks/CallbacksList.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import { CallbackModel } from '../../services/models'; +import styled from '../../styled-components'; +import { CallbackView } from './Callback'; + +const CallbacksHeader = styled.h3` + font-size: 18px; + padding: 0.2em 0; + margin: 3em 0 1.1em; + color: #253137; + font-weight: normal; +`; + +export interface CallbacksListProps { + callbacks: CallbackModel[]; +} + +export class CallbacksList extends React.PureComponent { + render() { + const { callbacks } = this.props; + + if (!callbacks || callbacks.length === 0) { + return null; + } + + return ( +
+ Callbacks + {callbacks.map(callback => { + return ; + })} +
+ ); + } +} diff --git a/src/components/Callbacks/styled.elements.ts b/src/components/Callbacks/styled.elements.ts new file mode 100644 index 00000000..505af0b6 --- /dev/null +++ b/src/components/Callbacks/styled.elements.ts @@ -0,0 +1,17 @@ +// import { transparentize } from 'polished'; + +import styled from '../../styled-components'; +import { CallbackTitle } from './CallbackTitle'; + +export const StyledCallbackTitle = styled(CallbackTitle)` + padding: 10px; + border-radius: 2px; + margin-bottom: 4px; + line-height: 1.5em; + background-color: #f2f2f2; + cursor: pointer; +`; + +export const CallbackDetailsWrap = styled.div` + padding: 10px; +`; diff --git a/src/components/ContentItems/ContentItems.tsx b/src/components/ContentItems/ContentItems.tsx index 3b638896..bccafcf6 100644 --- a/src/components/ContentItems/ContentItems.tsx +++ b/src/components/ContentItems/ContentItems.tsx @@ -4,10 +4,19 @@ import * as React from 'react'; import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation'; import { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown'; +import { Operation } from '..'; import { H1, H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements'; -import { ContentItemModel } from '../../services/MenuBuilder'; +import { ContentItemModel } from '../../services'; import { GroupModel, OperationModel } from '../../services/models'; -import { Operation } from '../Operation/Operation'; +import styled from '../../styled-components'; + +const CallbacksHeader = styled.h3` + font-size: 18px; + padding: 0 40px; + margin: 3em 0 1.1em; + color: #253137; + font-weight: normal; +`; @observer export class ContentItems extends React.Component<{ @@ -18,7 +27,22 @@ export class ContentItems extends React.Component<{ if (items.length === 0) { return null; } - return items.map(item => ); + + return items.map(item => { + if (item.type === 'operation' && item.callbacks.length > 0) { + return ( + + + Callbacks: + {item.callbacks.map((callbackIndex, idx) => { + return ; + })} + + ); + } + + return ; + }); } } diff --git a/src/components/Fields/Field.tsx b/src/components/Fields/Field.tsx index dae79e96..ef969b2d 100644 --- a/src/components/Fields/Field.tsx +++ b/src/components/Fields/Field.tsx @@ -65,21 +65,20 @@ export class Field extends React.Component { - {field.expanded && - withSubSchema && ( - - - - - - - - )} + {field.expanded && withSubSchema && ( + + + + + + + + )} ); } diff --git a/src/components/Responses/Response.tsx b/src/components/Responses/Response.tsx index 7c46a659..58e0ae32 100644 --- a/src/components/Responses/Response.tsx +++ b/src/components/Responses/Response.tsx @@ -28,12 +28,11 @@ export class ResponseView extends React.Component<{ response: ResponseModel }> { code={code} opened={expanded} /> - {expanded && - !empty && ( - - - - )} + {expanded && !empty && ( + + + + )} ); } diff --git a/src/components/SecuritySchemes/SecuritySchemes.tsx b/src/components/SecuritySchemes/SecuritySchemes.tsx index 08ca01fd..ff076d45 100644 --- a/src/components/SecuritySchemes/SecuritySchemes.tsx +++ b/src/components/SecuritySchemes/SecuritySchemes.tsx @@ -93,13 +93,12 @@ export class SecurityDefs extends React.PureComponent { HTTP Authorization Scheme {scheme.http.scheme} , - scheme.http.scheme === 'bearer' && - scheme.http.bearerFormat && ( - - Bearer format - "{scheme.http.bearerFormat}" - - ), + scheme.http.scheme === 'bearer' && scheme.http.bearerFormat && ( + + Bearer format + "{scheme.http.bearerFormat}" + + ), ] ) : scheme.openId ? ( diff --git a/src/components/SideMenu/MenuItem.tsx b/src/components/SideMenu/MenuItem.tsx index 9b66cb53..486062d9 100644 --- a/src/components/SideMenu/MenuItem.tsx +++ b/src/components/SideMenu/MenuItem.tsx @@ -57,22 +57,19 @@ export class MenuItem extends React.Component { {item.name} {this.props.children} - {(item.depth > 0 && - item.items.length > 0 && ( - - )) || + {(item.depth > 0 && item.items.length > 0 && ( + + )) || null} )} - {!withoutChildren && - item.items && - item.items.length > 0 && ( - - )} + {!withoutChildren && item.items && item.items.length > 0 && ( + + )} ); } diff --git a/src/services/models/Callback.ts b/src/services/models/Callback.ts new file mode 100644 index 00000000..efe87425 --- /dev/null +++ b/src/services/models/Callback.ts @@ -0,0 +1,53 @@ +import { action, observable } from 'mobx'; +import { OpenAPICallback, Referenced } from '../../types'; +import { isOperationName } from '../../utils'; +import { OpenAPIParser } from '../OpenAPIParser'; +import { OperationModel } from './Operation'; + +export class CallbackModel { + @observable + expanded: boolean; + name: string; + paths: Referenced; + operations: OperationModel[] = []; + + constructor( + parser: OpenAPIParser, + name: string, + infoOrRef: Referenced, + options, + ) { + this.name = name; + this.paths = parser.deref(infoOrRef); + parser.exitRef(infoOrRef); + + for (const pathName of Object.keys(this.paths)) { + const path = this.paths[pathName]; + const operations = Object.keys(path).filter(isOperationName); + for (const operationName of operations) { + const operationInfo = path[operationName]; + + const operation = new OperationModel( + parser, + { + ...operationInfo, + pathName, + httpVerb: operationName, + pathParameters: path.parameters || [], + }, + undefined, + options, + ); + + this.operations.push(operation); + } + } + + console.log(this.operations); + } + + @action + toggle() { + this.expanded = !this.expanded; + } +} diff --git a/src/services/models/MediaType.ts b/src/services/models/MediaType.ts index 941a35fb..0050ab44 100644 --- a/src/services/models/MediaType.ts +++ b/src/services/models/MediaType.ts @@ -49,11 +49,7 @@ export class MediaTypeModel { if (this.schema && this.schema.oneOf) { this.examples = {}; for (const subSchema of this.schema.oneOf) { - const sample = Sampler.sample( - subSchema.rawSchema, - samplerOptions, - parser.spec, - ); + const sample = Sampler.sample(subSchema.rawSchema, samplerOptions, parser.spec); if (this.schema.discriminatorProp && typeof sample === 'object' && sample) { sample[this.schema.discriminatorProp] = subSchema.title; @@ -66,11 +62,7 @@ export class MediaTypeModel { } else if (this.schema) { this.examples = { default: new ExampleModel(parser, { - value: Sampler.sample( - info.schema, - samplerOptions, - parser.spec, - ), + value: Sampler.sample(info.schema, samplerOptions, parser.spec), }), }; } diff --git a/src/services/models/Operation.ts b/src/services/models/Operation.ts index 3e31a367..24af510f 100644 --- a/src/services/models/Operation.ts +++ b/src/services/models/Operation.ts @@ -26,6 +26,7 @@ import { import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder'; import { OpenAPIParser } from '../OpenAPIParser'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; +import { CallbackModel } from './Callback'; import { FieldModel } from './Field'; import { RequestBodyModel } from './RequestBody'; import { ResponseModel } from './Response'; @@ -77,8 +78,8 @@ export class OperationModel implements IMenuItem { operationSpec.operationId !== undefined ? 'operation/' + operationSpec.operationId : parent !== undefined - ? parent.id + this.pointer - : this.pointer; + ? parent.id + this.pointer + : this.pointer; this.name = getOperationSummary(operationSpec); this.description = operationSpec.description; @@ -187,4 +188,16 @@ export class OperationModel implements IMenuItem { ); }); } + + @memoize + get callbacks() { + return Object.keys(this.operationSpec.callbacks || []).map(callbackName => { + return new CallbackModel( + this.parser, + callbackName, + this.operationSpec.callbacks![callbackName], + this.options, + ); + }); + } } diff --git a/src/services/models/index.ts b/src/services/models/index.ts index 65006e79..b373b392 100644 --- a/src/services/models/index.ts +++ b/src/services/models/index.ts @@ -1,3 +1,4 @@ +export * from './Callback'; export * from '../SpecStore'; export * from './Group.model'; export * from './Operation';