feat: ConsoleViewer can send the proper request customized by user now

This commit is contained in:
Arian Rahimi 2020-02-09 18:04:30 +03:30
parent e80fe1d47c
commit 2a927eb27c
7 changed files with 68 additions and 80 deletions

View File

@ -1,8 +1,8 @@
openapi: 3.0.0 openapi: 3.0.0
servers: servers:
- url: //petstore.swagger.io/v2 - url: https://petstore.swagger.io/v2
description: Default server description: Default server
- url: //petstore.swagger.io/sandbox - url: https://petstore.swagger.io/sandbox
description: Sandbox server description: Sandbox server
info: info:
description: | description: |
@ -101,7 +101,7 @@ paths:
'405': '405':
description: Invalid input description: Invalid input
security: security:
- petstore_auth: - authorization:
- 'write:pets' - 'write:pets'
- 'read:pets' - 'read:pets'
x-code-samples: x-code-samples:
@ -149,7 +149,7 @@ paths:
'405': '405':
description: Validation exception description: Validation exception
security: security:
- petstore_auth: - authorization:
- 'write:pets' - 'write:pets'
- 'read:pets' - 'read:pets'
x-code-samples: x-code-samples:
@ -217,7 +217,7 @@ paths:
'405': '405':
description: Invalid input description: Invalid input
security: security:
- petstore_auth: - authorization:
- 'write:pets' - 'write:pets'
- 'read:pets' - 'read:pets'
requestBody: requestBody:
@ -256,7 +256,7 @@ paths:
'400': '400':
description: Invalid pet value description: Invalid pet value
security: security:
- petstore_auth: - authorization:
- 'write:pets' - 'write:pets'
- 'read:pets' - 'read:pets'
'/pet/{petId}/uploadImage': '/pet/{petId}/uploadImage':
@ -282,7 +282,7 @@ paths:
schema: schema:
$ref: '#/components/schemas/ApiResponse' $ref: '#/components/schemas/ApiResponse'
security: security:
- petstore_auth: - authorization:
- 'write:pets' - 'write:pets'
- 'read:pets' - 'read:pets'
requestBody: requestBody:
@ -332,7 +332,7 @@ paths:
'400': '400':
description: Invalid status value description: Invalid status value
security: security:
- petstore_auth: - authorization:
- 'write:pets' - 'write:pets'
- 'read:pets' - 'read:pets'
/pet/findByTags: /pet/findByTags:
@ -372,7 +372,7 @@ paths:
'400': '400':
description: Invalid tag value description: Invalid tag value
security: security:
- petstore_auth: - authorization:
- 'write:pets' - 'write:pets'
- 'read:pets' - 'read:pets'
/store/inventory: /store/inventory:
@ -923,7 +923,7 @@ components:
description: List of user object description: List of user object
required: true required: true
securitySchemes: securitySchemes:
petstore_auth: authorization:
description: | description: |
Get access to data while protecting your account credentials. Get access to data while protecting your account credentials.
OAuth2 is also a safer and more secure way to give you access. OAuth2 is also a safer and more secure way to give you access.

View File

@ -25,9 +25,7 @@ const specUrl =
(userUrl && userUrl[1]) || (swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml'); (userUrl && userUrl[1]) || (swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml');
let store; let store;
const headers = { const headers = {};
'x-nutanix-client': 'ui',
};
const options: RedocRawOptions = { nativeScrollbars: false, enableConsole: true, providedByName: 'Intent ApiDocs by Nutanix', providedByUri: 'http://www.nutanix.com', additionalHeaders: headers }; const options: RedocRawOptions = { nativeScrollbars: false, enableConsole: true, providedByName: 'Intent ApiDocs by Nutanix', providedByUri: 'http://www.nutanix.com', additionalHeaders: headers };
async function init() { async function init() {

View File

@ -1,9 +1,9 @@
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react'; import * as React from 'react';
import { SecuritySchemeModel } from '../../../typings/services/models';
import { SubmitButton } from '../../common-elements/buttons'; import { SubmitButton } from '../../common-elements/buttons';
import { FlexLayoutReverse } from '../../common-elements/panels'; import { FlexLayoutReverse } from '../../common-elements/panels';
import { FieldModel, OperationModel } from '../../services/models'; import { FieldModel, OperationModel, SecuritySchemesModel } from '../../services/models';
import { OpenAPISchema } from '../../types';
import { SourceCodeWithCopy } from '../SourceCode/SourceCode'; import { SourceCodeWithCopy } from '../SourceCode/SourceCode';
import { ConsoleEditor } from './ConsoleEditor'; import { ConsoleEditor } from './ConsoleEditor';
@ -14,6 +14,7 @@ export interface ConsoleViewerProps {
additionalHeaders?: object; additionalHeaders?: object;
queryParamPrefix?: string; queryParamPrefix?: string;
queryParamSuffix?: string; queryParamSuffix?: string;
securitySchemes: SecuritySchemesModel;
} }
export interface ConsoleViewerState { export interface ConsoleViewerState {
@ -22,7 +23,6 @@ export interface ConsoleViewerState {
export interface Schema { export interface Schema {
_$ref?: any; _$ref?: any;
rawSchema?: OpenAPISchema;
} }
@observer @observer
@ -40,7 +40,8 @@ export class ConsoleViewer extends React.Component<ConsoleViewerProps, ConsoleVi
} }
onClickSend = async () => { onClickSend = async () => {
const ace = this.consoleEditor && this.consoleEditor.editor; const ace = this.consoleEditor && this.consoleEditor.editor;
const { operation, additionalHeaders = {} } = this.props; const { operation, securitySchemes: {schemes}, additionalHeaders = {} } = this.props;
let value = ace && ace.editor.getValue(); let value = ace && ace.editor.getValue();
const content = operation.requestBody && operation.requestBody.content; const content = operation.requestBody && operation.requestBody.content;
@ -54,7 +55,23 @@ export class ConsoleViewer extends React.Component<ConsoleViewerProps, ConsoleVi
} }
const contentType = mediaType && mediaType.name || 'application/json'; const contentType = mediaType && mediaType.name || 'application/json';
const contentTypeHeader = { 'Content-Type': contentType }; const contentTypeHeader = { 'Content-Type': contentType };
const headers = { ...additionalHeaders, ...contentTypeHeader };
const schemeMapper: Map<string, SecuritySchemeModel> = new Map<string, SecuritySchemeModel>();
schemes.forEach(scheme => {
schemeMapper.set(scheme.id, scheme);
});
const securityHeaders: Dict<string | undefined> = {};
operation.security.forEach(({schemes: [{ id }]}) => {
if (schemeMapper.has(id)) {
// this part of code needs a ts-ignore because typescript couldn't detect that schemeMapper.get(id) -
// has been checked to avoid token of undefined.
// @ts-ignore
securityHeaders[id] = schemeMapper.get(id).token;
}
});
const headers = { ...additionalHeaders, ...contentTypeHeader, ...securityHeaders };
let result; let result;
try { try {
result = await this.invoke(endpoint, value, headers); result = await this.invoke(endpoint, value, headers);
@ -76,8 +93,6 @@ export class ConsoleViewer extends React.Component<ConsoleViewerProps, ConsoleVi
const queryParamSuffix = '}'; const queryParamSuffix = '}';
for (const fieldModel of params) { for (const fieldModel of params) {
console.log(fieldModel.name + ' ' + url);
console.log(fieldModel.$value);
if (url.indexOf(`${queryParamPrefix}${fieldModel.name}${queryParamSuffix}`) > -1 && fieldModel.$value.length > 0) { if (url.indexOf(`${queryParamPrefix}${fieldModel.name}${queryParamSuffix}`) > -1 && fieldModel.$value.length > 0) {
url = url.replace(`${queryParamPrefix}${fieldModel.name}${queryParamSuffix}`, fieldModel.$value); url = url.replace(`${queryParamPrefix}${fieldModel.name}${queryParamSuffix}`, fieldModel.$value);
} }
@ -106,7 +121,6 @@ export class ConsoleViewer extends React.Component<ConsoleViewerProps, ConsoleVi
const request = new Request(url, { const request = new Request(url, {
method: endpoint.method, method: endpoint.method,
credentials: 'include',
redirect: 'manual', redirect: 'manual',
headers: myHeaders, headers: myHeaders,
body: (body) ? JSON.stringify(body) : undefined, body: (body) ? JSON.stringify(body) : undefined,
@ -173,29 +187,4 @@ export class ConsoleViewer extends React.Component<ConsoleViewerProps, ConsoleVi
</div> </div>
); );
} }
getSchema() {
const { operation } = this.props;
const requestBodyContent = operation.requestBody && operation.requestBody.content && operation.requestBody.content;
const mediaTypes = (requestBodyContent && requestBodyContent.mediaTypes) ? requestBodyContent.mediaTypes : [];
if (!mediaTypes.length) {
return null;
}
const schema: Schema = {
};
for (const mediaType of mediaTypes) {
if (mediaType.name.indexOf('json') > -1) {
if (mediaType.schema) {
schema.rawSchema = mediaType.schema && mediaType.schema.rawSchema;
console.log('rawSchema : ' + JSON.stringify(schema));
console.log('schema : ' + JSON.stringify(mediaType.schema.schema));
}
break;
}
}
return schema;
}
} }

View File

@ -1,35 +1,40 @@
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react'; import * as React from 'react';
import { AppStore } from '../../services';
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation'; import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
import { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown'; import { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown';
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/MenuBuilder';
import { GroupModel, OperationModel } from '../../services/models'; import { GroupModel } from '../../services/models';
import { Operation } from '../Operation/Operation'; import { Operation } from '../Operation/Operation';
@observer export interface ContentItemsProps {
export class ContentItems extends React.Component<{
items: ContentItemModel[]; items: ContentItemModel[];
}> { store: AppStore;
}
@observer
export class ContentItems extends React.Component<ContentItemsProps> {
render() { render() {
const items = this.props.items; const { items, store } = this.props;
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 => <ContentItem store={store} item={item} key={item.id} />);
} }
} }
export interface ContentItemProps { export interface ContentItemProps {
item: ContentItemModel; item: ContentItemModel;
store: AppStore;
} }
@observer @observer
export class ContentItem extends React.Component<ContentItemProps> { export class ContentItem extends React.Component<ContentItemProps> {
render() { render() {
const item = this.props.item; const { item, store } = this.props;
let content; let content;
const { type } = item; const { type } = item;
switch (type) { switch (type) {
@ -41,7 +46,7 @@ export class ContentItem extends React.Component<ContentItemProps> {
content = <SectionItem {...this.props} />; content = <SectionItem {...this.props} />;
break; break;
case 'operation': case 'operation':
content = <OperationItem item={item as any} />; content = <Operation securitySchemes={store.spec.securitySchemes} operation={item as any} />;
break; break;
default: default:
content = <SectionItem {...this.props} />; content = <SectionItem {...this.props} />;
@ -54,7 +59,7 @@ export class ContentItem extends React.Component<ContentItemProps> {
{content} {content}
</Section> </Section>
)} )}
{item.items && <ContentItems items={item.items} />} {item.items && <ContentItems store={store} items={item.items} />}
</> </>
); );
} }
@ -90,12 +95,3 @@ export class SectionItem extends React.Component<ContentItemProps> {
); );
} }
} }
@observer
export class OperationItem extends React.Component<{
item: OperationModel;
}> {
render() {
return <Operation operation={this.props.item} />;
}
}

View File

@ -5,7 +5,7 @@ import { Badge, DarkRightPanel, H2, MiddlePanel, Row } from '../../common-elemen
import { ShareLink } from '../../common-elements/linkify'; import { ShareLink } from '../../common-elements/linkify';
import { OperationModel as OperationType } from '../../services/models'; import { OperationModel as OperationType, SecuritySchemesModel } from '../../services/models';
import styled from '../../styled-components'; import styled from '../../styled-components';
import { ConsoleViewer } from '../Console/ConsoleViewer'; import { ConsoleViewer } from '../Console/ConsoleViewer';
import { Endpoint } from '../Endpoint/Endpoint'; import { Endpoint } from '../Endpoint/Endpoint';
@ -34,6 +34,7 @@ const Description = styled.div`
export interface OperationProps { export interface OperationProps {
operation: OperationType; operation: OperationType;
securitySchemes: SecuritySchemesModel;
} }
export interface OperationState { export interface OperationState {
@ -57,7 +58,7 @@ export class Operation extends React.Component<OperationProps, OperationState> {
} }
render() { render() {
const { operation } = this.props; const { operation, securitySchemes } = this.props;
const { executeMode } = this.state; const { executeMode } = this.state;
const { name: summary, description, deprecated, externalDocs } = operation; const { name: summary, description, deprecated, externalDocs } = operation;
@ -95,7 +96,13 @@ export class Operation extends React.Component<OperationProps, OperationState> {
{!options.pathInMiddlePanel && <Endpoint operation={operation} />} {!options.pathInMiddlePanel && <Endpoint operation={operation} />}
{executeMode && {executeMode &&
<div> <div>
<ConsoleViewer operation={operation} additionalHeaders={options.additionalHeaders} queryParamPrefix={options.queryParamPrefix} queryParamSuffix={options.queryParamSuffix} /> <ConsoleViewer
securitySchemes={securitySchemes}
operation={operation}
additionalHeaders={options.additionalHeaders}
queryParamPrefix={options.queryParamPrefix}
queryParamSuffix={options.queryParamSuffix}
/>
</div> </div>
} }
{!executeMode && {!executeMode &&

View File

@ -57,7 +57,7 @@ export class Redoc extends React.Component<RedocProps> {
</StickyResponsiveSidebar> </StickyResponsiveSidebar>
<ApiContentWrap className="api-content"> <ApiContentWrap className="api-content">
<ApiInfo store={store} /> <ApiInfo store={store} />
<ContentItems items={menu.items as any} /> <ContentItems store={store} items={menu.items as any} />
</ApiContentWrap> </ApiContentWrap>
<BackgroundStub /> <BackgroundStub />
</RedocWrap> </RedocWrap>

View File

@ -67,18 +67,16 @@ export class SecurityRequirement extends React.PureComponent<SecurityRequirement
const security = this.props.security; const security = this.props.security;
return ( return (
<SecurityRequirementOrWrap> <SecurityRequirementOrWrap>
{security.schemes.map(scheme => { {security.schemes.map(scheme => (
return ( <SecurityRequirementAndWrap key={scheme.id}>
<SecurityRequirementAndWrap key={scheme.id}> <Link to={scheme.sectionId}>{scheme.id}</Link>
<Link to={scheme.sectionId}>{scheme.id}</Link> {scheme.scopes.length > 0 && ' ('}
{scheme.scopes.length > 0 && ' ('} {scheme.scopes.map(scope => (
{scheme.scopes.map(scope => ( <ScopeName key={scope}>{scope}</ScopeName>
<ScopeName key={scope}>{scope}</ScopeName> ))}
))} {scheme.scopes.length > 0 && ') '}
{scheme.scopes.length > 0 && ') '} </SecurityRequirementAndWrap>
</SecurityRequirementAndWrap> ))}
);
})}
</SecurityRequirementOrWrap> </SecurityRequirementOrWrap>
); );
} }