Added callbacks support

This commit is contained in:
Makashov Nurbol 2018-12-26 16:56:22 +06:00
parent cfb6f0fde0
commit f1eb03fd66
14 changed files with 244 additions and 69 deletions

View File

@ -44,22 +44,20 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
null; null;
const website = const website =
(info.contact && (info.contact && info.contact.url && (
info.contact.url && ( <InfoSpan>
<InfoSpan> URL: <a href={info.contact.url}>{info.contact.url}</a>
URL: <a href={info.contact.url}>{info.contact.url}</a> </InfoSpan>
</InfoSpan> )) ||
)) ||
null; null;
const email = const email =
(info.contact && (info.contact && info.contact.email && (
info.contact.email && ( <InfoSpan>
<InfoSpan> {info.contact.name || 'E-mail'}:{' '}
{info.contact.name || 'E-mail'}:{' '} <a href={'mailto:' + info.contact.email}>{info.contact.email}</a>
<a href={'mailto:' + info.contact.email}>{info.contact.email}</a> </InfoSpan>
</InfoSpan> )) ||
)) ||
null; null;
const terms = const terms =

View File

@ -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 (
<div>
<StyledCallbackTitle onClick={this.toggle} name={name} opened={expanded} />
{expanded && (
<CallbackDetailsWrap>
<span>{name}</span>
</CallbackDetailsWrap>
)}
</div>
);
}
}

View File

@ -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<CallbackTitleProps> {
render() {
const { name, opened, className, onClick } = this.props;
return (
<div className={className} onClick={onClick || undefined}>
<ShelfIcon size={'1.5em'} direction={opened ? 'up' : 'down'} float={'left'} />
<strong>{name} </strong>
</div>
);
}
}

View File

@ -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<CallbacksListProps> {
render() {
const { callbacks } = this.props;
if (!callbacks || callbacks.length === 0) {
return null;
}
return (
<div>
<CallbacksHeader> Callbacks </CallbacksHeader>
{callbacks.map(callback => {
return <CallbackView key={callback.name} callback={callback} />;
})}
</div>
);
}
}

View File

@ -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;
`;

View File

@ -4,10 +4,19 @@ import * as React from 'react';
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation'; import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
import { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown'; import { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown';
import { Operation } from '..';
import { H1, H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements'; 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 { 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 @observer
export class ContentItems extends React.Component<{ export class ContentItems extends React.Component<{
@ -18,7 +27,22 @@ export class ContentItems extends React.Component<{
if (items.length === 0) { if (items.length === 0) {
return null; return null;
} }
return items.map(item => <ContentItem item={item} key={item.id} />);
return items.map(item => {
if (item.type === 'operation' && item.callbacks.length > 0) {
return (
<React.Fragment key={item.id}>
<ContentItem item={item} />
<CallbacksHeader>Callbacks:</CallbacksHeader>
{item.callbacks.map((callbackIndex, idx) => {
return <ContentItems key={idx} items={callbackIndex.operations} />;
})}
</React.Fragment>
);
}
return <ContentItem key={item.id} item={item} />;
});
} }
} }

View File

@ -65,21 +65,20 @@ export class Field extends React.Component<FieldProps> {
<FieldDetails {...this.props} /> <FieldDetails {...this.props} />
</PropertyDetailsCell> </PropertyDetailsCell>
</tr> </tr>
{field.expanded && {field.expanded && withSubSchema && (
withSubSchema && ( <tr key={field.name + 'inner'}>
<tr key={field.name + 'inner'}> <PropertyCellWithInner colSpan={2}>
<PropertyCellWithInner colSpan={2}> <InnerPropertiesWrap>
<InnerPropertiesWrap> <Schema
<Schema schema={field.schema}
schema={field.schema} skipReadOnly={this.props.skipReadOnly}
skipReadOnly={this.props.skipReadOnly} skipWriteOnly={this.props.skipWriteOnly}
skipWriteOnly={this.props.skipWriteOnly} showTitle={this.props.showTitle}
showTitle={this.props.showTitle} />
/> </InnerPropertiesWrap>
</InnerPropertiesWrap> </PropertyCellWithInner>
</PropertyCellWithInner> </tr>
</tr> )}
)}
</> </>
); );
} }

View File

@ -28,12 +28,11 @@ export class ResponseView extends React.Component<{ response: ResponseModel }> {
code={code} code={code}
opened={expanded} opened={expanded}
/> />
{expanded && {expanded && !empty && (
!empty && ( <ResponseDetailsWrap>
<ResponseDetailsWrap> <ResponseDetails response={this.props.response} />
<ResponseDetails response={this.props.response} /> </ResponseDetailsWrap>
</ResponseDetailsWrap> )}
)}
</div> </div>
); );
} }

View File

@ -93,13 +93,12 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
<th> HTTP Authorization Scheme </th> <th> HTTP Authorization Scheme </th>
<td> {scheme.http.scheme} </td> <td> {scheme.http.scheme} </td>
</tr>, </tr>,
scheme.http.scheme === 'bearer' && scheme.http.scheme === 'bearer' && scheme.http.bearerFormat && (
scheme.http.bearerFormat && ( <tr key="bearer">
<tr key="bearer"> <th> Bearer format </th>
<th> Bearer format </th> <td> "{scheme.http.bearerFormat}" </td>
<td> "{scheme.http.bearerFormat}" </td> </tr>
</tr> ),
),
] ]
) : scheme.openId ? ( ) : scheme.openId ? (
<tr> <tr>

View File

@ -57,22 +57,19 @@ export class MenuItem extends React.Component<MenuItemProps> {
{item.name} {item.name}
{this.props.children} {this.props.children}
</MenuItemTitle> </MenuItemTitle>
{(item.depth > 0 && {(item.depth > 0 && item.items.length > 0 && (
item.items.length > 0 && ( <ShelfIcon float={'right'} direction={item.expanded ? 'down' : 'right'} />
<ShelfIcon float={'right'} direction={item.expanded ? 'down' : 'right'} /> )) ||
)) ||
null} null}
</MenuItemLabel> </MenuItemLabel>
)} )}
{!withoutChildren && {!withoutChildren && item.items && item.items.length > 0 && (
item.items && <MenuItems
item.items.length > 0 && ( expanded={item.expanded}
<MenuItems items={item.items}
expanded={item.expanded} onActivate={this.props.onActivate}
items={item.items} />
onActivate={this.props.onActivate} )}
/>
)}
</MenuItemLi> </MenuItemLi>
); );
} }

View File

@ -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<OpenAPICallback>;
operations: OperationModel[] = [];
constructor(
parser: OpenAPIParser,
name: string,
infoOrRef: Referenced<OpenAPICallback>,
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;
}
}

View File

@ -49,11 +49,7 @@ export class MediaTypeModel {
if (this.schema && this.schema.oneOf) { if (this.schema && this.schema.oneOf) {
this.examples = {}; this.examples = {};
for (const subSchema of this.schema.oneOf) { for (const subSchema of this.schema.oneOf) {
const sample = Sampler.sample( const sample = Sampler.sample(subSchema.rawSchema, samplerOptions, parser.spec);
subSchema.rawSchema,
samplerOptions,
parser.spec,
);
if (this.schema.discriminatorProp && typeof sample === 'object' && sample) { if (this.schema.discriminatorProp && typeof sample === 'object' && sample) {
sample[this.schema.discriminatorProp] = subSchema.title; sample[this.schema.discriminatorProp] = subSchema.title;
@ -66,11 +62,7 @@ export class MediaTypeModel {
} else if (this.schema) { } else if (this.schema) {
this.examples = { this.examples = {
default: new ExampleModel(parser, { default: new ExampleModel(parser, {
value: Sampler.sample( value: Sampler.sample(info.schema, samplerOptions, parser.spec),
info.schema,
samplerOptions,
parser.spec,
),
}), }),
}; };
} }

View File

@ -26,6 +26,7 @@ import {
import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder'; import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder';
import { OpenAPIParser } from '../OpenAPIParser'; import { OpenAPIParser } from '../OpenAPIParser';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { CallbackModel } from './Callback';
import { FieldModel } from './Field'; import { FieldModel } from './Field';
import { RequestBodyModel } from './RequestBody'; import { RequestBodyModel } from './RequestBody';
import { ResponseModel } from './Response'; import { ResponseModel } from './Response';
@ -77,8 +78,8 @@ export class OperationModel implements IMenuItem {
operationSpec.operationId !== undefined operationSpec.operationId !== undefined
? 'operation/' + operationSpec.operationId ? 'operation/' + operationSpec.operationId
: parent !== undefined : parent !== undefined
? parent.id + this.pointer ? parent.id + this.pointer
: this.pointer; : this.pointer;
this.name = getOperationSummary(operationSpec); this.name = getOperationSummary(operationSpec);
this.description = operationSpec.description; 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,
);
});
}
} }

View File

@ -1,3 +1,4 @@
export * from './Callback';
export * from '../SpecStore'; export * from '../SpecStore';
export * from './Group.model'; export * from './Group.model';
export * from './Operation'; export * from './Operation';