From 9fd292bf681a33f29ae603b0b1b987017a786039 Mon Sep 17 00:00:00 2001 From: Harjeet Singh Date: Wed, 18 Apr 2018 13:16:27 -0700 Subject: [PATCH 01/14] added try it checkbox --- .gitignore | 3 ++ demo/.gitignore | 1 + demo/webpack.config.ts | 2 ++ src/common-elements/index.ts | 2 ++ src/common-elements/input.tsx | 8 +++++ src/common-elements/toggle.tsx | 10 ++++++ src/components/Fields/Field.tsx | 17 +++++---- src/components/Operation/Operation.tsx | 48 +++++++++++++++++++++++--- 8 files changed, 80 insertions(+), 11 deletions(-) create mode 100644 demo/.gitignore create mode 100644 src/common-elements/input.tsx create mode 100644 src/common-elements/toggle.tsx diff --git a/.gitignore b/.gitignore index 215b9a50..6c666a97 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ cli/index.js .ghpages-tmp stats.json /package-lock.json + +//npmrc for local npm +.npmrc \ No newline at end of file diff --git a/demo/.gitignore b/demo/.gitignore new file mode 100644 index 00000000..3e44267f --- /dev/null +++ b/demo/.gitignore @@ -0,0 +1 @@ +intent.json \ No newline at end of file diff --git a/demo/webpack.config.ts b/demo/webpack.config.ts index 864e8c8d..d5fbe807 100644 --- a/demo/webpack.config.ts +++ b/demo/webpack.config.ts @@ -68,6 +68,8 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) = stats: 'minimal', }, + devtool: 'source-map', + resolve: { extensions: ['.ts', '.tsx', '.js', '.json'], }, diff --git a/src/common-elements/index.ts b/src/common-elements/index.ts index 5c9ec542..cd940d34 100644 --- a/src/common-elements/index.ts +++ b/src/common-elements/index.ts @@ -9,3 +9,5 @@ export * from './mixins'; export * from './tabs'; export * from './samples'; export * from './perfect-scrollbar'; +export * from './toggle'; +export * from './input'; diff --git a/src/common-elements/input.tsx b/src/common-elements/input.tsx new file mode 100644 index 00000000..c7284b2f --- /dev/null +++ b/src/common-elements/input.tsx @@ -0,0 +1,8 @@ +import styled, { css } from '../styled-components'; + +export const TextField = styled.input` + padding: 0.5em; + margin: 0.5em; + border: 1px solid rgba(38,50,56,0.5); + border-radius: 3px; +`; \ No newline at end of file diff --git a/src/common-elements/toggle.tsx b/src/common-elements/toggle.tsx new file mode 100644 index 00000000..cada9073 --- /dev/null +++ b/src/common-elements/toggle.tsx @@ -0,0 +1,10 @@ +import styled, { css } from '../styled-components'; + +export const Toggle = styled.input` + padding: 0.5em; + margin: 0.5em; + color: palevioletred; + background: papayawhip; + border: none; + border-radius: 3px; +`; \ No newline at end of file diff --git a/src/components/Fields/Field.tsx b/src/components/Fields/Field.tsx index 102f98d8..c43f7d6c 100644 --- a/src/components/Fields/Field.tsx +++ b/src/components/Fields/Field.tsx @@ -11,7 +11,7 @@ import { PropertyNameCell, } from '../../common-elements/fields-layout'; -import { ShelfIcon } from '../../common-elements/'; +import { ShelfIcon, TextField } from '../../common-elements/'; import { FieldModel } from '../../services/models'; import { Schema, SchemaOptions } from '../Schema/Schema'; @@ -43,12 +43,12 @@ export class Field extends React.PureComponent { {required && required } ) : ( - - - {name} - {required && required } - - ); + + + {name} + {required && required } + + ); return ( <> @@ -56,6 +56,9 @@ export class Field extends React.PureComponent { + {field && field.in === 'path' && + + } {field.expanded && withSubSchema && ( diff --git a/src/components/Operation/Operation.tsx b/src/components/Operation/Operation.tsx index 75c8fd92..5e3d8fe4 100644 --- a/src/components/Operation/Operation.tsx +++ b/src/components/Operation/Operation.tsx @@ -4,7 +4,7 @@ import { SecurityRequirements } from '../SecurityRequirement/SecuirityRequiremen import { observer } from 'mobx-react'; -import { Badge, DarkRightPanel, H2, MiddlePanel, Row } from '../../common-elements'; +import { Badge, DarkRightPanel, H2, MiddlePanel, Row, Toggle } from '../../common-elements'; import { OptionsContext } from '../OptionsProvider'; @@ -39,12 +39,43 @@ interface OperationProps { operation: OperationType; } +export interface OperationState { + executeMode: boolean; +} + @observer -export class Operation extends React.Component { +export class Operation extends React.Component { + + constructor(props) { + super(props); + this.state = { + executeMode: false, + }; + } + + onTry(e) { + this.setState({ + executeMode: e.target.checked + }); + console.log(e.target.checked + ' ' + this.props.operation); + } + + /* + activate = (item: IMenuItem) => { + this.props.menu.activateAndScroll(item, true); + setTimeout(() => { + if (this._updateScroll) { + this._updateScroll(); + } + }); + }; + */ + render() { const { operation } = this.props; const { name: summary, description, deprecated } = operation; + const { executeMode } = this.state; return ( {options => ( @@ -54,6 +85,8 @@ export class Operation extends React.Component { {summary} {deprecated && Deprecated } + + Try it out! {options.pathInMiddlePanel && } {description !== undefined && } @@ -62,8 +95,15 @@ export class Operation extends React.Component { {!options.pathInMiddlePanel && } - - + {executeMode && +
Execute Mode
+ } + {!executeMode && + + } + {!executeMode && + + }
)} From 63847ff3b6e2fa14ec4376c35fe075433e723dd5 Mon Sep 17 00:00:00 2001 From: Harjeet Singh Date: Thu, 19 Apr 2018 16:34:32 -0700 Subject: [PATCH 02/14] added editor with sample --- demo/playground/hmr-playground.tsx | 4 +- package.json | 4 +- src/components/Console/ConsoleEditor.tsx | 70 ++++++++++++++++++++++++ src/components/Console/ConsoleViewer.tsx | 36 ++++++++++++ src/components/Operation/Operation.tsx | 5 +- src/utils/loadAndBundleSpec.ts | 17 ++++-- tsconfig.json | 2 +- yarn.lock | 28 +++++++++- 8 files changed, 157 insertions(+), 9 deletions(-) create mode 100644 src/components/Console/ConsoleEditor.tsx create mode 100644 src/components/Console/ConsoleViewer.tsx diff --git a/demo/playground/hmr-playground.tsx b/demo/playground/hmr-playground.tsx index 66d9389b..664e60b2 100644 --- a/demo/playground/hmr-playground.tsx +++ b/demo/playground/hmr-playground.tsx @@ -19,7 +19,9 @@ const renderRoot = (props: RedocProps) => const big = window.location.search.indexOf('big') > -1; const swagger = window.location.search.indexOf('swagger') > -1; // compatibility mode ? -const specUrl = swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml'; +let specUrl = swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml'; +specUrl = 'intent.json'; +//specUrl = 'swagger.yaml'; let store; const options: RedocRawOptions = { nativeScrollbars: false }; diff --git a/package.json b/package.json index 11c3ff97..9ce0bd36 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "babel-loader": "8.0.0-beta.2", "babel-plugin-styled-components": "^1.5.1", "beautify-benchmark": "^0.2.4", + "brace": "^0.11.1", "bundlesize": "^0.17.0", "conventional-changelog-cli": "^1.3.17", "copy-webpack-plugin": "^4.5.1", @@ -88,12 +89,13 @@ "fork-ts-checker-webpack-plugin": "^0.4.1", "html-webpack-plugin": "^3.1.0", "jest": "^22.4.3", - "lodash": "^4.17.4", + "lodash": "^4.17.5", "prettier": "^1.5.3", "prettier-eslint": "^8.8.1", "puppeteer": "^1.2.0", "raf": "^3.4.0", "react": "^16.3.0-alpha.2", + "react-ace": "^6.0.0", "react-dom": "^16.3.0-alpha.2", "rimraf": "^2.6.2", "shelljs": "^0.8.1", diff --git a/src/components/Console/ConsoleEditor.tsx b/src/components/Console/ConsoleEditor.tsx new file mode 100644 index 00000000..598af352 --- /dev/null +++ b/src/components/Console/ConsoleEditor.tsx @@ -0,0 +1,70 @@ +import { observer } from 'mobx-react'; +import * as React from 'react'; + +import AceEditor from 'react-ace'; + +import 'brace/mode/json'; +import 'brace/mode/curly'; +import 'brace/theme/github'; +import 'brace/theme/monokai'; + + +import { MediaTypeModel } from '../../services/models'; +import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch'; + +import { MediaTypeSamples } from '../PayloadSamples/MediaTypeSamples'; + +import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel'; +import { InvertedSimpleDropdown, MimeLabel } from '../PayloadSamples/styled.elements'; + +export interface ConsoleEditorProps { + mediaTypes: MediaTypeModel[] +} + +@observer +export class ConsoleEditor extends React.Component { + render() { + const { mediaTypes } = this.props; + + if (!mediaTypes.length) { + return null; + } + let sample = {}; + for (let mediaType of mediaTypes) { + if (mediaType.name.indexOf('json') > -1) { + if (mediaType.examples) { + sample = mediaType.examples && mediaType.examples.default; + } + break; + } + } + + + /* + let body = {}; + if(mimeContent.mediaTypes && mimeContent.mediaTypes.length>0){ + body = mimeContent.mediaTypes[0]; + if(body.examples && body.examples.default) { + + } + } + */ + return ( +
+

ConsoleEditor

+ +
+ ); + } + +} diff --git a/src/components/Console/ConsoleViewer.tsx b/src/components/Console/ConsoleViewer.tsx new file mode 100644 index 00000000..e50a2bf3 --- /dev/null +++ b/src/components/Console/ConsoleViewer.tsx @@ -0,0 +1,36 @@ +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { OperationModel } from '../../services/models'; +import { PayloadSamples } from '../PayloadSamples/PayloadSamples'; +import { SourceCodeWithCopy } from '../SourceCode/SourceCode'; +import { ConsoleEditor } from './ConsoleEditor'; + +export interface ConsoleViewerProps { + operation: OperationModel; +} + +@observer +export class ConsoleViewer extends React.Component { + operation: OperationModel; + visited = new Set(); + + render() { + const { operation } = this.props; + const requestBodyContent = operation.requestBody && operation.requestBody.content && operation.requestBody.content; + const hasBodySample = requestBodyContent && requestBodyContent.hasSample; + const samples = operation.codeSamples; + const mediaTypes = (requestBodyContent && requestBodyContent.mediaTypes) ? requestBodyContent.mediaTypes : []; + + return ( +
+

Console

+ {hasBodySample && ( + + )} + {samples.map(sample => ( + + ))} +
+ ); + } +} diff --git a/src/components/Operation/Operation.tsx b/src/components/Operation/Operation.tsx index 5e3d8fe4..480f0790 100644 --- a/src/components/Operation/Operation.tsx +++ b/src/components/Operation/Operation.tsx @@ -15,6 +15,7 @@ import { Parameters } from '../Parameters/Parameters'; import { RequestSamples } from '../RequestSamples/RequestSamples'; import { ResponsesList } from '../Responses/ResponsesList'; import { ResponseSamples } from '../ResponseSamples/ResponseSamples'; +import { ConsoleViewer } from '../Console/ConsoleViewer'; import { OperationModel as OperationType } from '../../services/models'; @@ -96,7 +97,9 @@ export class Operation extends React.Component { {!options.pathInMiddlePanel && } {executeMode && -
Execute Mode
+
+ +
} {!executeMode && diff --git a/src/utils/loadAndBundleSpec.ts b/src/utils/loadAndBundleSpec.ts index 66b7fdde..a88f4470 100644 --- a/src/utils/loadAndBundleSpec.ts +++ b/src/utils/loadAndBundleSpec.ts @@ -1,18 +1,27 @@ import * as JsonSchemaRefParser from 'json-schema-ref-parser'; import { convertObj } from 'swagger2openapi'; import { OpenAPISpec } from '../types'; +import { cloneDeep } from 'lodash'; export async function loadAndBundleSpec(specUrlOrObject: object | string): Promise { const parser = new JsonSchemaRefParser(); - const spec = await parser.bundle(specUrlOrObject, { + let spec = await parser.bundle(specUrlOrObject, { resolve: { http: { withCredentials: false } }, } as object); + let v2Specs = spec; if (spec.swagger !== undefined) { - return convertSwagger2OpenAPI(spec); - } else { - return spec; + v2Specs = await convertSwagger2OpenAPI(spec); } + + // we can derefrence the schema here for future use. + // const derefrencedSpec = await parser.dereference(cloneDeep(spec)); + const derefed = await parser.dereference(v2Specs, { + resolve: { http: { withCredentials: false } }, + } as object); + + return derefed; + } export function convertSwagger2OpenAPI(spec: any): Promise { diff --git a/tsconfig.json b/tsconfig.json index 04950bbc..d58c8994 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -39,4 +39,4 @@ "./src/standalone.tsx", "demo/*.tsx" ] -} +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 3a37ff43..d9c0cffc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1589,6 +1589,10 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace@^0.11.0, brace@^0.11.1: + version "0.11.1" + resolved "https://registry.yarnpkg.com/brace/-/brace-0.11.1.tgz#4896fcc9d544eef45f4bb7660db320d3b379fe58" + braces@^1.8.2: version "1.8.5" resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" @@ -2970,6 +2974,10 @@ detect-node@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127" +diff-match-patch@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.0.tgz#1cc3c83a490d67f95d91e39f6ad1f2e086b63048" + diff@^3.1.0, diff@^3.2.0, diff@^3.3.1, diff@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -5947,6 +5955,14 @@ lodash.flattendeep@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + +lodash.isequal@^4.1.1: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + lodash.isfunction@^3.0.8: version "3.0.9" resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" @@ -7553,7 +7569,7 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@^15.5.0, prop-types@^15.5.4, prop-types@^15.6.0: +prop-types@^15.5.0, prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0: version "15.6.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca" dependencies: @@ -7747,6 +7763,16 @@ rc@^1.1.6, rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-ace@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-6.0.0.tgz#c211c21825f27343a7392f102493dc3ae099886d" + dependencies: + brace "^0.11.0" + diff-match-patch "^1.0.0" + lodash.get "^4.4.2" + lodash.isequal "^4.1.1" + prop-types "^15.5.8" + react-dom@^16.3.0-alpha.2: version "16.3.0-alpha.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.3.0-alpha.2.tgz#a970b6185684941e89a568c09321d22643457cb6" From 2a7ce2b64e1cdb0aaef8f0d1f6c2e8008a771584 Mon Sep 17 00:00:00 2001 From: Harjeet Singh Date: Fri, 20 Apr 2018 15:55:09 -0700 Subject: [PATCH 03/14] added send button --- src/common-elements/buttons.ts | 14 ++++++++++++++ src/common-elements/panels.ts | 14 ++++++++++---- src/components/Console/ConsoleEditor.tsx | 5 ++--- src/components/Console/ConsoleViewer.tsx | 5 +++++ 4 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 src/common-elements/buttons.ts diff --git a/src/common-elements/buttons.ts b/src/common-elements/buttons.ts new file mode 100644 index 00000000..2e94dff9 --- /dev/null +++ b/src/common-elements/buttons.ts @@ -0,0 +1,14 @@ +import styled, { StyledComponentClass, withProps } from '../styled-components'; + + +export const Button = styled.button` + background: #248fb2; + border-radius: 0px; + border: none; + color: white; + font-size: 0.929em; +`; + +export const SendButton = Button.extend` + background: #B0045E; +`; \ No newline at end of file diff --git a/src/common-elements/panels.ts b/src/common-elements/panels.ts index 91ac574e..bd1c6084 100644 --- a/src/common-elements/panels.ts +++ b/src/common-elements/panels.ts @@ -4,7 +4,7 @@ export const MiddlePanel = styled.div` width: calc(100% - ${props => props.theme.rightPanel.width}); padding: ${props => props.theme.spacingUnit * 2}px; - ${media.lessThan('medium')` + ${media.lessThan('medium') ` width: 100%; `}; `; @@ -15,7 +15,7 @@ export const RightPanel = styled.div` background-color: ${props => props.theme.rightPanel.backgroundColor}; padding: ${props => props.theme.spacingUnit * 2}px; - ${media.lessThan('medium')` + ${media.lessThan('medium') ` width: 100%; `}; `; @@ -25,7 +25,7 @@ export const DarkRightPanel = RightPanel.extend` `; export const EmptyDarkRightPanel = DarkRightPanel.extend` - ${media.lessThan('medium')` + ${media.lessThan('medium') ` padding: 0 `}; `; @@ -34,7 +34,13 @@ export const Row = styled.div` display: flex; width: 100%; - ${media.lessThan('medium')` + ${media.lessThan('medium') ` flex-direction: column; `}; `; + +export const FlexLayout = styled.div` + align-items: flex-end; + display: flex; + width: 100%; +`; diff --git a/src/components/Console/ConsoleEditor.tsx b/src/components/Console/ConsoleEditor.tsx index 598af352..8492cd40 100644 --- a/src/components/Console/ConsoleEditor.tsx +++ b/src/components/Console/ConsoleEditor.tsx @@ -33,7 +33,7 @@ export class ConsoleEditor extends React.Component { for (let mediaType of mediaTypes) { if (mediaType.name.indexOf('json') > -1) { if (mediaType.examples) { - sample = mediaType.examples && mediaType.examples.default; + sample = mediaType.examples && mediaType.examples.default && mediaType.examples.default.value; } break; } @@ -51,12 +51,11 @@ export class ConsoleEditor extends React.Component { */ return (
-

ConsoleEditor

{ {samples.map(sample => ( ))} + + Send Request +
); } From e3b9afad92877d0100deb923e957ada9a24c6ca4 Mon Sep 17 00:00:00 2001 From: Harjeet Singh Date: Mon, 23 Apr 2018 16:36:06 -0700 Subject: [PATCH 04/14] added ajv validator --- demo/petstore.json | 993 +++++++++++++++++++++++ demo/playground/hmr-playground.tsx | 12 + package.json | 2 + src/common-elements/buttons.ts | 1 + src/common-elements/panels.ts | 5 + src/components/Console/ConsoleEditor.tsx | 19 + src/components/Console/ConsoleViewer.tsx | 63 +- src/components/Operation/Operation.tsx | 6 +- src/utils/loadAndBundleSpec.ts | 8 +- yarn.lock | 19 + 10 files changed, 1115 insertions(+), 13 deletions(-) create mode 100644 demo/petstore.json diff --git a/demo/petstore.json b/demo/petstore.json new file mode 100644 index 00000000..77a25e0d --- /dev/null +++ b/demo/petstore.json @@ -0,0 +1,993 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.", + "version": "1.0.0", + "title": "Swagger Petstore", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "host": "petstore.swagger.io", + "basePath": "/v2", + "tags": [{ + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders" + }, + { + "name": "user", + "description": "Operations about user", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + } + ], + "schemes": [ + "http" + ], + "paths": { + "/pet": { + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "", + "operationId": "addPet", + "consumes": [ + "application/json", + "application/xml" + ], + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [{ + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": true, + "schema": { + "$ref": "#/definitions/Pet" + } + }], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [{ + "petstore_auth": [ + "write:pets", + "read:pets" + ] + }] + }, + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "", + "operationId": "updatePet", + "consumes": [ + "application/json", + "application/xml" + ], + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [{ + "in": "body", + "name": "body", + "description": "Pet object that needs to be added to the store", + "required": true, + "schema": { + "$ref": "#/definitions/Pet" + } + }], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [{ + "petstore_auth": [ + "write:pets", + "read:pets" + ] + }] + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "operationId": "findPetsByStatus", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [{ + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": true, + "type": "array", + "items": { + "type": "string", + "enum": [ + "available", + "pending", + "sold" + ], + "default": "available" + }, + "collectionFormat": "multi" + }], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Pet" + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": [{ + "petstore_auth": [ + "write:pets", + "read:pets" + ] + }] + } + }, + "/pet/findByTags": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by tags", + "description": "Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [{ + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi" + }], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Pet" + } + } + }, + "400": { + "description": "Invalid tag value" + } + }, + "security": [{ + "petstore_auth": [ + "write:pets", + "read:pets" + ] + }], + "deprecated": true + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pet" + ], + "summary": "Find pet by ID", + "description": "Returns a single pet", + "operationId": "getPetById", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [{ + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "type": "integer", + "format": "int64" + }], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Pet" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [{ + "api_key": [ + + ] + }] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Updates a pet in the store with form data", + "description": "", + "operationId": "updatePetWithForm", + "consumes": [ + "application/x-www-form-urlencoded" + ], + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [{ + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "type": "integer", + "format": "int64" + }, + { + "name": "name", + "in": "formData", + "description": "Updated name of the pet", + "required": false, + "type": "string" + }, + { + "name": "status", + "in": "formData", + "description": "Updated status of the pet", + "required": false, + "type": "string" + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [{ + "petstore_auth": [ + "write:pets", + "read:pets" + ] + }] + }, + "delete": { + "tags": [ + "pet" + ], + "summary": "Deletes a pet", + "description": "", + "operationId": "deletePet", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [{ + "name": "api_key", + "in": "header", + "required": false, + "type": "string" + }, + { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [{ + "petstore_auth": [ + "write:pets", + "read:pets" + ] + }] + } + }, + "/pet/{petId}/uploadImage": { + "post": { + "tags": [ + "pet" + ], + "summary": "uploads an image", + "description": "", + "operationId": "uploadFile", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "parameters": [{ + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "type": "integer", + "format": "int64" + }, + { + "name": "additionalMetadata", + "in": "formData", + "description": "Additional data to pass to server", + "required": false, + "type": "string" + }, + { + "name": "file", + "in": "formData", + "description": "file to upload", + "required": false, + "type": "file" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/ApiResponse" + } + } + }, + "security": [{ + "petstore_auth": [ + "write:pets", + "read:pets" + ] + }] + } + }, + "/store/inventory": { + "get": { + "tags": [ + "store" + ], + "summary": "Returns pet inventories by status", + "description": "Returns a map of status codes to quantities", + "operationId": "getInventory", + "produces": [ + "application/json" + ], + "parameters": [ + + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + }, + "security": [{ + "api_key": [ + + ] + }] + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet", + "description": "", + "operationId": "placeOrder", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [{ + "in": "body", + "name": "body", + "description": "order placed for purchasing the pet", + "required": true, + "schema": { + "$ref": "#/definitions/Order" + } + }], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Order" + } + }, + "400": { + "description": "Invalid Order" + } + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": [ + "store" + ], + "summary": "Find purchase order by ID", + "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions", + "operationId": "getOrderById", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [{ + "name": "orderId", + "in": "path", + "description": "ID of pet that needs to be fetched", + "required": true, + "type": "integer", + "maximum": 10.0, + "minimum": 1.0, + "format": "int64" + }], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/Order" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + }, + "delete": { + "tags": [ + "store" + ], + "summary": "Delete purchase order by ID", + "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors", + "operationId": "deleteOrder", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [{ + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "type": "integer", + "minimum": 1.0, + "format": "int64" + }], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + } + }, + "/user": { + "post": { + "tags": [ + "user" + ], + "summary": "Create user", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [{ + "in": "body", + "name": "body", + "description": "Created user object", + "required": true, + "schema": { + "$ref": "#/definitions/User" + } + }], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/createWithArray": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithArrayInput", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [{ + "in": "body", + "name": "body", + "description": "List of user object", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + } + }], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "", + "operationId": "createUsersWithListInput", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [{ + "in": "body", + "name": "body", + "description": "List of user object", + "required": true, + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/User" + } + } + }], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system", + "description": "", + "operationId": "loginUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [{ + "name": "username", + "in": "query", + "description": "The user name for login", + "required": true, + "type": "string" + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "string" + }, + "headers": { + "X-Rate-Limit": { + "type": "integer", + "format": "int32", + "description": "calls per hour allowed by the user" + }, + "X-Expires-After": { + "type": "string", + "format": "date-time", + "description": "date in UTC when token expires" + } + } + }, + "400": { + "description": "Invalid username/password supplied" + } + } + } + }, + "/user/logout": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs out current logged in user session", + "description": "", + "operationId": "logoutUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [ + + ], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/{username}": { + "get": { + "tags": [ + "user" + ], + "summary": "Get user by user name", + "description": "", + "operationId": "getUserByName", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [{ + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing. ", + "required": true, + "type": "string" + }], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/definitions/User" + } + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "put": { + "tags": [ + "user" + ], + "summary": "Updated user", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [{ + "name": "username", + "in": "path", + "description": "name that need to be updated", + "required": true, + "type": "string" + }, + { + "in": "body", + "name": "body", + "description": "Updated user object", + "required": true, + "schema": { + "$ref": "#/definitions/User" + } + } + ], + "responses": { + "400": { + "description": "Invalid user supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "delete": { + "tags": [ + "user" + ], + "summary": "Delete user", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "produces": [ + "application/xml", + "application/json" + ], + "parameters": [{ + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "type": "string" + }], + "responses": { + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + } + } + }, + "securityDefinitions": { + "petstore_auth": { + "type": "oauth2", + "authorizationUrl": "http://petstore.swagger.io/oauth/dialog", + "flow": "implicit", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + }, + "definitions": { + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "petId": { + "type": "integer", + "format": "int64" + }, + "quantity": { + "type": "integer", + "format": "int32" + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean", + "default": false + } + }, + "xml": { + "name": "Order" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "username": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status" + } + }, + "xml": { + "name": "User" + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Category" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "Tag" + } + }, + "Pet": { + "type": "object", + "required": [ + "name", + "photoUrls" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "category": { + "$ref": "#/definitions/Category" + }, + "name": { + "type": "string", + "example": "doggie" + }, + "photoUrls": { + "type": "array", + "xml": { + "name": "photoUrl", + "wrapped": true + }, + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "xml": { + "name": "tag", + "wrapped": true + }, + "items": { + "$ref": "#/definitions/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "Pet" + } + }, + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + } + } + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + } +} diff --git a/demo/playground/hmr-playground.tsx b/demo/playground/hmr-playground.tsx index 664e60b2..45510186 100644 --- a/demo/playground/hmr-playground.tsx +++ b/demo/playground/hmr-playground.tsx @@ -8,6 +8,8 @@ import { AppStore } from '../../src/services/AppStore'; import { RedocRawOptions } from '../../src/services/RedocNormalizedOptions'; import { loadAndBundleSpec } from '../../src/utils/loadAndBundleSpec'; +const Ajv = require('ajv'); + const renderRoot = (props: RedocProps) => render( @@ -22,6 +24,7 @@ const swagger = window.location.search.indexOf('swagger') > -1; // compatibility let specUrl = swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml'; specUrl = 'intent.json'; //specUrl = 'swagger.yaml'; +//specUrl = 'petstore.json'; let store; const options: RedocRawOptions = { nativeScrollbars: false }; @@ -29,6 +32,15 @@ const options: RedocRawOptions = { nativeScrollbars: false }; async function init() { const spec = await loadAndBundleSpec(specUrl); store = new AppStore(spec, specUrl, options); + + const ajv = new Ajv({ allErrors: true, unknownFormats: ['int32', 'UUID', 'int64'] }); + ajv.addSchema(spec, 'specs.json') + + const ajvError = require('ajv-errors')(ajv); + + window.ajv = ajv; + window.ajvError = ajvError; + renderRoot({ store }); } diff --git a/package.json b/package.json index 9ce0bd36..e9bdad09 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,8 @@ "@types/webpack": "^4.1.2", "@types/webpack-env": "^1.13.0", "@types/yargs": "^11.0.0", + "ajv": "^6.4.0", + "ajv-errors": "^1.0.0", "babel-loader": "8.0.0-beta.2", "babel-plugin-styled-components": "^1.5.1", "beautify-benchmark": "^0.2.4", diff --git a/src/common-elements/buttons.ts b/src/common-elements/buttons.ts index 2e94dff9..860a0e3f 100644 --- a/src/common-elements/buttons.ts +++ b/src/common-elements/buttons.ts @@ -7,6 +7,7 @@ export const Button = styled.button` border: none; color: white; font-size: 0.929em; + padding: 5px; `; export const SendButton = Button.extend` diff --git a/src/common-elements/panels.ts b/src/common-elements/panels.ts index bd1c6084..ed7be94f 100644 --- a/src/common-elements/panels.ts +++ b/src/common-elements/panels.ts @@ -44,3 +44,8 @@ export const FlexLayout = styled.div` display: flex; width: 100%; `; + +export const ConsoleActionsRow = FlexLayout.extend` + padding: 5px 0px; +`; + diff --git a/src/components/Console/ConsoleEditor.tsx b/src/components/Console/ConsoleEditor.tsx index 8492cd40..e12ded51 100644 --- a/src/components/Console/ConsoleEditor.tsx +++ b/src/components/Console/ConsoleEditor.tsx @@ -23,6 +23,24 @@ export interface ConsoleEditorProps { @observer export class ConsoleEditor extends React.Component { + + public editor: any; + + /* + get aceEditor(): AceEditor { + return this._aceEditor; + } + + set aceEditor(aceEditor: AceEditor) { + if (aceEditor) { + this.aceEditor = this.aceEditor + } + else { + console.log("Error: Undefined ace editor!"); + } + } + */ + render() { const { mediaTypes } = this.props; @@ -59,6 +77,7 @@ export class ConsoleEditor extends React.Component { name="request-builder-editor" editorProps={{ $blockScrolling: true }} value={JSON.stringify(sample, null, 2)} + ref={(ace: AceEditor) => (this.editor = ace)} width="100%" height="400px" /> diff --git a/src/components/Console/ConsoleViewer.tsx b/src/components/Console/ConsoleViewer.tsx index 41ea2e66..ed7fe73f 100644 --- a/src/components/Console/ConsoleViewer.tsx +++ b/src/components/Console/ConsoleViewer.tsx @@ -4,7 +4,7 @@ import { OperationModel } from '../../services/models'; import { PayloadSamples } from '../PayloadSamples/PayloadSamples'; import { SourceCodeWithCopy } from '../SourceCode/SourceCode'; import { SendButton } from '../../common-elements/buttons'; -import { FlexLayout } from '../../common-elements/panels'; +import { ConsoleActionsRow } from '../../common-elements/panels'; import { ConsoleEditor } from './ConsoleEditor'; export interface ConsoleViewerProps { @@ -13,9 +13,32 @@ export interface ConsoleViewerProps { @observer export class ConsoleViewer extends React.Component { + private consoleEditor: ConsoleEditor; operation: OperationModel; visited = new Set(); + onClickSend = () => { + const ace = this.consoleEditor && this.consoleEditor.editor; + //const value = ace && ace.editor && + const schema = this.getSchema(); + + //console.log('Schema: ' + JSON.stringify(schema, null, 2)); + const value = ace && ace.editor.getValue(); + + const ref = schema && schema['_$ref']; + + var valid = window && window.ajv.validate({ $ref: `specs.json${ref}` }, value); + + console.log(JSON.stringify(window.ajv.errors)); + + if (!valid) { + console.warn('INVALID REQUEST!'); + } + + + console.log('Value: ' + value); + }; + render() { const { operation } = this.props; const requestBodyContent = operation.requestBody && operation.requestBody.content && operation.requestBody.content; @@ -27,15 +50,43 @@ export class ConsoleViewer extends React.Component {

Console

{hasBodySample && ( - + (this.consoleEditor = editor)} /> )} - {samples.map(sample => ( + {false && samples.map(sample => ( ))} - - Send Request - + + Send Request +
); } + + 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; + } + let schema = {}; + for (let mediaType of mediaTypes) { + if (mediaType.name.indexOf('json') > -1) { + if (mediaType.schema) { + //schema = mediaType.schema; + schema = mediaType.schema && mediaType.schema.rawSchema; + console.log('rawSchema : ' + JSON.stringify(schema)); + console.log('schema : ' + JSON.stringify(mediaType.schema.schema)); + schema['_$ref'] = mediaType.schema && mediaType.schema['_$ref'] + } + break; + } + } + + return schema; + + } } diff --git a/src/components/Operation/Operation.tsx b/src/components/Operation/Operation.tsx index 480f0790..63091345 100644 --- a/src/components/Operation/Operation.tsx +++ b/src/components/Operation/Operation.tsx @@ -54,12 +54,12 @@ export class Operation extends React.Component { }; } - onTry(e) { + onTry = (e) => { this.setState({ executeMode: e.target.checked }); console.log(e.target.checked + ' ' + this.props.operation); - } + }; /* activate = (item: IMenuItem) => { @@ -86,7 +86,7 @@ export class Operation extends React.Component { {summary} {deprecated && Deprecated } - + Try it out! {options.pathInMiddlePanel && } {description !== undefined && } diff --git a/src/utils/loadAndBundleSpec.ts b/src/utils/loadAndBundleSpec.ts index a88f4470..8b50124a 100644 --- a/src/utils/loadAndBundleSpec.ts +++ b/src/utils/loadAndBundleSpec.ts @@ -16,11 +16,11 @@ export async function loadAndBundleSpec(specUrlOrObject: object | string): Promi // we can derefrence the schema here for future use. // const derefrencedSpec = await parser.dereference(cloneDeep(spec)); - const derefed = await parser.dereference(v2Specs, { - resolve: { http: { withCredentials: false } }, - } as object); + //const derefed = await parser.dereference(v2Specs, { + // resolve: { http: { withCredentials: false } }, + //} as object); - return derefed; + return v2Specs; } diff --git a/yarn.lock b/yarn.lock index d9c0cffc..3d99af36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -391,6 +391,10 @@ agent-base@^4.1.0: dependencies: es6-promisify "^5.0.0" +ajv-errors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.0.tgz#ecf021fa108fd17dfb5e6b383f2dd233e31ffc59" + ajv-keywords@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" @@ -423,6 +427,15 @@ ajv@^6.1.0: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" +ajv@^6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.4.0.tgz#d3aff78e9277549771daf0164cff48482b754fc6" + dependencies: + fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + uri-js "^3.0.2" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -9480,6 +9493,12 @@ upper-case@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" +uri-js@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-3.0.2.tgz#f90b858507f81dea4dcfbb3c4c3dbfa2b557faaa" + dependencies: + punycode "^2.1.0" + urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" From 270d4909057262bbe981157482694d4bce25e2c6 Mon Sep 17 00:00:00 2001 From: Harjeet Singh Date: Tue, 24 Apr 2018 15:46:28 -0700 Subject: [PATCH 05/14] added fetch api --- demo/webpack.config.ts | 22 ++++++++ package.json | 4 +- src/components/Console/ConsoleViewer.tsx | 66 ++++++++++++++++++++++-- src/utils/fetch.ts | 0 yarn.lock | 8 +++ 5 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 src/utils/fetch.ts diff --git a/demo/webpack.config.ts b/demo/webpack.config.ts index d5fbe807..a85fa226 100644 --- a/demo/webpack.config.ts +++ b/demo/webpack.config.ts @@ -45,6 +45,26 @@ const babelLoader = mode => ({ }, }); +let proxy = {}; +let https = false; +//we are using our own proxy here +if (process.env.PROXY && process.env.USERNAME && process.env.PASSWORD) { + proxy = { + "/api": { + auth: `${process.env.USERNAME}:${process.env.PASSWORD}`, + target: process.env.PROXY, + "secure": false, + changeOrigin: true, + ws: true, + xfwd: true + } + } + https = true; + console.log('Using proxy configuration provided with command line, https in use.\n'); +} else { + console.log('Using proxy from PC/PE local server\n'); +} + export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) => ({ entry: [ root('../src/polyfills.ts'), @@ -66,6 +86,8 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) = port: 9090, disableHostCheck: true, stats: 'minimal', + https, + proxy, }, devtool: 'source-map', diff --git a/package.json b/package.json index e9bdad09..592757f4 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "@types/marked": "^0.3.0", "@types/prismjs": "^1.6.4", "@types/prop-types": "^15.5.2", + "@types/qs": "^6.5.1", "@types/react": "^16.0.41", "@types/react-dom": "^16.0.0", "@types/react-hot-loader": "^3.0.3", @@ -143,7 +144,8 @@ "slugify": "^1.2.1", "stickyfill": "^1.1.1", "styled-components": "^3.2.3", - "swagger2openapi": "^2.11.0" + "swagger2openapi": "^2.11.0", + "whatwg-fetch": "^2.0.4" }, "resolutions": { "@types/chai": "4.0.8", diff --git a/src/components/Console/ConsoleViewer.tsx b/src/components/Console/ConsoleViewer.tsx index ed7fe73f..f09b1d0f 100644 --- a/src/components/Console/ConsoleViewer.tsx +++ b/src/components/Console/ConsoleViewer.tsx @@ -1,5 +1,6 @@ import { observer } from 'mobx-react'; import * as React from 'react'; +import qs = require('qs'); import { OperationModel } from '../../services/models'; import { PayloadSamples } from '../PayloadSamples/PayloadSamples'; import { SourceCodeWithCopy } from '../SourceCode/SourceCode'; @@ -17,10 +18,11 @@ export class ConsoleViewer extends React.Component { operation: OperationModel; visited = new Set(); - onClickSend = () => { + onClickSend = async () => { const ace = this.consoleEditor && this.consoleEditor.editor; //const value = ace && ace.editor && const schema = this.getSchema(); + const { operation } = this.props; //console.log('Schema: ' + JSON.stringify(schema, null, 2)); const value = ace && ace.editor.getValue(); @@ -28,17 +30,75 @@ export class ConsoleViewer extends React.Component { const ref = schema && schema['_$ref']; var valid = window && window.ajv.validate({ $ref: `specs.json${ref}` }, value); - console.log(JSON.stringify(window.ajv.errors)); - if (!valid) { console.warn('INVALID REQUEST!'); } + const endpoint = { + method: operation.httpVerb, + path: operation.servers[0].url + operation.path + } console.log('Value: ' + value); + const result = await this.invoke(endpoint, { 'Content-Type': 'application/*' }, JSON.parse(value)); + console.log('Result: ' + JSON.stringify(result)); + }; + async invoke(endpoint, headers, body) { + + const fetchArgs = { + method: endpoint.method, + headers, + redirect: 'manual', + credentials: 'include' + } + + let url = endpoint.path; + if (endpoint.method.toLocaleLowerCase() === 'get') { + url = url + '?' + qs.stringify(body); + } else { + fetchArgs['body'] = (body) ? JSON.stringify(body) : null; + } + + const result = await fetch(url, fetchArgs); + + const contentType = result.headers.get("content-type"); + if (contentType && contentType.indexOf("application/json") !== -1) { + // successful cross-domain connect/ability + const resp = await result.json(); + + return { json: resp, statusCode: result.status, _fetchRes: result }; + } + else if (result.status === 200 && contentType && contentType.indexOf("text/plain") !== -1) { + const resp = await result.text(); + return { resp, _fetchRes: result }; + } + else { + if (result && result.type && result.type === 'opaqueredirect') { + return { + json: { + endpoint, + error_code: "RECEIVED_LOGIN_REDIRECT", + details: "Your session expired. Please refresh the page.", + severity: "error" + } + } + } + + return { + json: { + endpoint, + error_code: "INVALID_SERVER_RESPONSE", + details: "Either server not authenticated or error on server", + severity: "error" + } + }; + } + } + + render() { const { operation } = this.props; const requestBodyContent = operation.requestBody && operation.requestBody.content && operation.requestBody.content; diff --git a/src/utils/fetch.ts b/src/utils/fetch.ts new file mode 100644 index 00000000..e69de29b diff --git a/yarn.lock b/yarn.lock index 3d99af36..7ff44556 100644 --- a/yarn.lock +++ b/yarn.lock @@ -264,6 +264,10 @@ version "15.5.2" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.2.tgz#3c6b8dceb2906cc87fe4358e809f9d20c8d59be1" +"@types/qs@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.5.1.tgz#a38f69c62528d56ba7bd1f91335a8004988d72f7" + "@types/react-dom@^16.0.0": version "16.0.4" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.0.4.tgz#2e8fd45f5443780ed49bf2cdd9809e6091177a7d" @@ -9839,6 +9843,10 @@ whatwg-fetch@>=0.10.0: version "2.0.3" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" +whatwg-fetch@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" + whatwg-url@^6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.0.tgz#08fdf2b9e872783a7a1f6216260a1d66cc722e08" From 5b219b48d1ec39c6e1e65ab53634492467e2c9d5 Mon Sep 17 00:00:00 2001 From: Harjeet Singh Date: Wed, 25 Apr 2018 13:48:49 -0700 Subject: [PATCH 06/14] hooked with server --- demo/playground/hmr-playground.tsx | 4 +- src/components/Console/ConsoleViewer.tsx | 123 ++++++++++++++--------- src/polyfills.ts | 2 + src/utils/fetch.ts | 37 +++++++ 4 files changed, 116 insertions(+), 50 deletions(-) diff --git a/demo/playground/hmr-playground.tsx b/demo/playground/hmr-playground.tsx index 45510186..e0f8ddf0 100644 --- a/demo/playground/hmr-playground.tsx +++ b/demo/playground/hmr-playground.tsx @@ -32,7 +32,7 @@ const options: RedocRawOptions = { nativeScrollbars: false }; async function init() { const spec = await loadAndBundleSpec(specUrl); store = new AppStore(spec, specUrl, options); - + /* const ajv = new Ajv({ allErrors: true, unknownFormats: ['int32', 'UUID', 'int64'] }); ajv.addSchema(spec, 'specs.json') @@ -40,7 +40,7 @@ async function init() { window.ajv = ajv; window.ajvError = ajvError; - + */ renderRoot({ store }); } diff --git a/src/components/Console/ConsoleViewer.tsx b/src/components/Console/ConsoleViewer.tsx index f09b1d0f..afba7dc0 100644 --- a/src/components/Console/ConsoleViewer.tsx +++ b/src/components/Console/ConsoleViewer.tsx @@ -1,6 +1,5 @@ import { observer } from 'mobx-react'; import * as React from 'react'; -import qs = require('qs'); import { OperationModel } from '../../services/models'; import { PayloadSamples } from '../PayloadSamples/PayloadSamples'; import { SourceCodeWithCopy } from '../SourceCode/SourceCode'; @@ -8,16 +7,29 @@ import { SendButton } from '../../common-elements/buttons'; import { ConsoleActionsRow } from '../../common-elements/panels'; import { ConsoleEditor } from './ConsoleEditor'; +const qs = require('qs'); + + export interface ConsoleViewerProps { operation: OperationModel; } +export interface ConsoleViewerState { + result: any; +} + @observer -export class ConsoleViewer extends React.Component { +export class ConsoleViewer extends React.Component { private consoleEditor: ConsoleEditor; operation: OperationModel; visited = new Set(); + constructor(props) { + super(props); + this.state = { + result: null + }; + } onClickSend = async () => { const ace = this.consoleEditor && this.consoleEditor.editor; //const value = ace && ace.editor && @@ -25,77 +37,89 @@ export class ConsoleViewer extends React.Component { const { operation } = this.props; //console.log('Schema: ' + JSON.stringify(schema, null, 2)); - const value = ace && ace.editor.getValue(); + let value = ace && ace.editor.getValue(); const ref = schema && schema['_$ref']; - var valid = window && window.ajv.validate({ $ref: `specs.json${ref}` }, value); - console.log(JSON.stringify(window.ajv.errors)); - if (!valid) { - console.warn('INVALID REQUEST!'); - } + //var valid = window && window.ajv.validate({ $ref: `specs.json${ref}` }, value); + //console.log(JSON.stringify(window.ajv.errors)); + //if (!valid) { + // console.warn('INVALID REQUEST!'); + //} const endpoint = { method: operation.httpVerb, path: operation.servers[0].url + operation.path } - console.log('Value: ' + value); - const result = await this.invoke(endpoint, { 'Content-Type': 'application/*' }, JSON.parse(value)); + if (value) { + value = JSON.parse(value); + } + const result = await this.invoke(endpoint, { 'Content-Type': 'application/*' }, value); console.log('Result: ' + JSON.stringify(result)); + this.setState({ + result + }); }; async invoke(endpoint, headers, body) { - const fetchArgs = { - method: endpoint.method, - headers, - redirect: 'manual', - credentials: 'include' - } + try { + let url = endpoint.path; + if (endpoint.method.toLocaleLowerCase() === 'get') { + url = url + '?' + qs.stringify(body || ''); + } - let url = endpoint.path; - if (endpoint.method.toLocaleLowerCase() === 'get') { - url = url + '?' + qs.stringify(body); - } else { - fetchArgs['body'] = (body) ? JSON.stringify(body) : null; - } + var myHeaders = new Headers(); + myHeaders.append('Content-Type', 'application/*'); - const result = await fetch(url, fetchArgs); + const request = new Request(url, { + method: endpoint.method, + credentials: 'include', + redirect: 'manual', + headers: myHeaders, + body: (body) ? JSON.stringify(body) : null + }); - const contentType = result.headers.get("content-type"); - if (contentType && contentType.indexOf("application/json") !== -1) { - // successful cross-domain connect/ability - const resp = await result.json(); + const result = await fetch(request); + + const contentType = result.headers.get("content-type"); + if (contentType && contentType.indexOf("application/json") !== -1) { + // successful cross-domain connect/ability + const resp = await result.json(); + + return { json: resp, statusCode: result.status, _fetchRes: result }; + } + else if (result.status === 200 && contentType && contentType.indexOf("text/plain") !== -1) { + const resp = await result.text(); + return { resp, _fetchRes: result }; + } + else { + if (result && result.type && result.type === 'opaqueredirect') { + return { + json: { + endpoint, + error_code: "RECEIVED_LOGIN_REDIRECT", + details: "Your session expired. Please refresh the page.", + severity: "error" + } + } + } - return { json: resp, statusCode: result.status, _fetchRes: result }; - } - else if (result.status === 200 && contentType && contentType.indexOf("text/plain") !== -1) { - const resp = await result.text(); - return { resp, _fetchRes: result }; - } - else { - if (result && result.type && result.type === 'opaqueredirect') { return { json: { endpoint, - error_code: "RECEIVED_LOGIN_REDIRECT", - details: "Your session expired. Please refresh the page.", + error_code: "INVALID_SERVER_RESPONSE", + details: "Either server not authenticated or error on server", severity: "error" } - } + }; } - - return { - json: { - endpoint, - error_code: "INVALID_SERVER_RESPONSE", - details: "Either server not authenticated or error on server", - severity: "error" - } - }; + } catch (error) { + console.error(error); } + } @@ -105,7 +129,7 @@ export class ConsoleViewer extends React.Component { const hasBodySample = requestBodyContent && requestBodyContent.hasSample; const samples = operation.codeSamples; const mediaTypes = (requestBodyContent && requestBodyContent.mediaTypes) ? requestBodyContent.mediaTypes : []; - + const { result } = this.state; return (

Console

@@ -120,6 +144,9 @@ export class ConsoleViewer extends React.Component { Send Request + {result && + + }
); } diff --git a/src/polyfills.ts b/src/polyfills.ts index b2432991..d854a460 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -7,3 +7,5 @@ import 'core-js/fn/array/find'; import 'core-js/es6/map'; import 'core-js/es6/set'; import 'core-js/es6/symbol'; + +import 'whatwg-fetch'; diff --git a/src/utils/fetch.ts b/src/utils/fetch.ts index e69de29b..590f8e44 100644 --- a/src/utils/fetch.ts +++ b/src/utils/fetch.ts @@ -0,0 +1,37 @@ +import { Promise } from "core-js"; + +export class Fetch { + + + execute(): Promise> { + return fetch('https://api.github.com/orgs/lemoncode/members') + .then((response) => this.checkStatus(response)) + .then((response) => this.parseJSON(response) + .then((response) => { return Promise.resolve(this._parse(response)) }) + .catch((error) => this.throwError(error)) + ); + } + + private checkStatus(response: Response): Promise { + if (response.status >= 200 && response.status < 300) { + return Promise.resolve(response); + } else { + let error = new Error(response.statusText); + throw error; + } + } + + private parseJSON(response: Response): Promise { + return response.json(); + } + + private _parse(data) { + return data; + } + + private throwError(error) { + document.write("

Ops! something wrong! We are so embarrased..

"); + console.log(error); + return Promise.reject(error); + } +} \ No newline at end of file From 30b741e732b0d9171bbee3d5b2309de0019bbda2 Mon Sep 17 00:00:00 2001 From: Harjeet Singh Date: Tue, 8 May 2018 14:28:55 -0700 Subject: [PATCH 07/14] working with backend apis , content type was not set right, also added max height to response panel as some responses can be lengthy --- package.json | 12 ++++---- src/components/Console/ConsoleViewer.tsx | 9 +++--- src/components/SourceCode/SourceCode.tsx | 4 +-- src/theme.ts | 6 ++++ src/utils/fetch.ts | 38 +----------------------- 5 files changed, 20 insertions(+), 49 deletions(-) diff --git a/package.json b/package.json index 592757f4..3a1022fc 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "@types/mark.js": "^8.11.1", "@types/marked": "^0.3.0", "@types/prismjs": "^1.6.4", + "@types/promise": "^7.1.30", "@types/prop-types": "^15.5.2", "@types/qs": "^6.5.1", "@types/react": "^16.0.41", @@ -71,6 +72,7 @@ "@types/react-tabs": "^1.0.2", "@types/webpack": "^4.1.2", "@types/webpack-env": "^1.13.0", + "@types/whatwg-fetch": "^0.0.33", "@types/yargs": "^11.0.0", "ajv": "^6.4.0", "ajv-errors": "^1.0.0", @@ -151,12 +153,10 @@ "@types/chai": "4.0.8", "@types/tapable": "1.0.0" }, - "bundlesize": [ - { - "path": "./bundles/redoc.standalone.js", - "maxSize": "300 kB" - } - ], + "bundlesize": [{ + "path": "./bundles/redoc.standalone.js", + "maxSize": "300 kB" + }], "jest": { "transform": { "^.+\\.tsx?$": "/node_modules/ts-jest/preprocessor.js" diff --git a/src/components/Console/ConsoleViewer.tsx b/src/components/Console/ConsoleViewer.tsx index afba7dc0..9e2a0127 100644 --- a/src/components/Console/ConsoleViewer.tsx +++ b/src/components/Console/ConsoleViewer.tsx @@ -55,7 +55,8 @@ export class ConsoleViewer extends React.Component props.theme.responsePanel}; const StyledPre = styled.pre` font-family: ${props => props.theme.code.fontFamily}; font-size: ${props => props.theme.code.fontSize}; overflow-x: auto; margin: 0; - + max-height: ${props => props.theme.styledPre.maxHeight}; word-break: break-all; word-wrap: break-word; white-space: pre-wrap; diff --git a/src/theme.ts b/src/theme.ts index ae0b53f9..4711efe5 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -65,6 +65,9 @@ const defaultTheme: ThemeInterface = { backgroundColor: '#263238', width: '40%', }, + styledPre: { + maxHeight: '500px' + }, }; export default defaultTheme; @@ -168,6 +171,9 @@ export interface ResolvedThemeInterface { backgroundColor: string; width: string; }; + styledPre: { + maxHeight: string; + } } export type primitive = string | number | boolean | undefined | null; diff --git a/src/utils/fetch.ts b/src/utils/fetch.ts index 590f8e44..835e785a 100644 --- a/src/utils/fetch.ts +++ b/src/utils/fetch.ts @@ -1,37 +1 @@ -import { Promise } from "core-js"; - -export class Fetch { - - - execute(): Promise> { - return fetch('https://api.github.com/orgs/lemoncode/members') - .then((response) => this.checkStatus(response)) - .then((response) => this.parseJSON(response) - .then((response) => { return Promise.resolve(this._parse(response)) }) - .catch((error) => this.throwError(error)) - ); - } - - private checkStatus(response: Response): Promise { - if (response.status >= 200 && response.status < 300) { - return Promise.resolve(response); - } else { - let error = new Error(response.statusText); - throw error; - } - } - - private parseJSON(response: Response): Promise { - return response.json(); - } - - private _parse(data) { - return data; - } - - private throwError(error) { - document.write("

Ops! something wrong! We are so embarrased..

"); - console.log(error); - return Promise.reject(error); - } -} \ No newline at end of file +parseparseFetchFetch \ No newline at end of file From fa7e39015b4e93c54a4226adb8807f3df872b210 Mon Sep 17 00:00:00 2001 From: Harjeet Singh Date: Tue, 29 May 2018 13:31:54 -0700 Subject: [PATCH 08/14] ts warning and errors with null --- src/components/Console/ConsoleViewer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Console/ConsoleViewer.tsx b/src/components/Console/ConsoleViewer.tsx index 9e2a0127..771b598d 100644 --- a/src/components/Console/ConsoleViewer.tsx +++ b/src/components/Console/ConsoleViewer.tsx @@ -80,7 +80,7 @@ export class ConsoleViewer extends React.Component Date: Tue, 19 Jun 2018 16:17:46 -0700 Subject: [PATCH 09/14] pulled latest from master and fixed errors --- demo/playground/hmr-playground.tsx | 4 +- src/common-elements/buttons.ts | 3 +- src/common-elements/input.tsx | 3 +- src/common-elements/toggle.tsx | 3 +- yarn.lock | 68 ++++++++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 5 deletions(-) diff --git a/demo/playground/hmr-playground.tsx b/demo/playground/hmr-playground.tsx index e0f8ddf0..25384bfc 100644 --- a/demo/playground/hmr-playground.tsx +++ b/demo/playground/hmr-playground.tsx @@ -22,9 +22,9 @@ const big = window.location.search.indexOf('big') > -1; const swagger = window.location.search.indexOf('swagger') > -1; // compatibility mode ? let specUrl = swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml'; -specUrl = 'intent.json'; +//specUrl = 'intent.json'; //specUrl = 'swagger.yaml'; -//specUrl = 'petstore.json'; +specUrl = 'petstore.json'; let store; const options: RedocRawOptions = { nativeScrollbars: false }; diff --git a/src/common-elements/buttons.ts b/src/common-elements/buttons.ts index 860a0e3f..1fdf7bed 100644 --- a/src/common-elements/buttons.ts +++ b/src/common-elements/buttons.ts @@ -1,4 +1,5 @@ -import styled, { StyledComponentClass, withProps } from '../styled-components'; +import * as React from 'react'; +import styled, { ResolvedThemeInterface, StyledComponentClass } from '../styled-components'; export const Button = styled.button` diff --git a/src/common-elements/input.tsx b/src/common-elements/input.tsx index c7284b2f..0633d7c9 100644 --- a/src/common-elements/input.tsx +++ b/src/common-elements/input.tsx @@ -1,4 +1,5 @@ -import styled, { css } from '../styled-components'; +import * as React from 'react'; +import styled, { css, ResolvedThemeInterface, StyledComponentClass } from '../styled-components'; export const TextField = styled.input` padding: 0.5em; diff --git a/src/common-elements/toggle.tsx b/src/common-elements/toggle.tsx index cada9073..cd09b4a4 100644 --- a/src/common-elements/toggle.tsx +++ b/src/common-elements/toggle.tsx @@ -1,4 +1,5 @@ -import styled, { css } from '../styled-components'; +import * as React from 'react'; +import styled, { css, ResolvedThemeInterface, StyledComponentClass } from '../styled-components'; export const Toggle = styled.input` padding: 0.5em; diff --git a/yarn.lock b/yarn.lock index 5288d77e..d4c80a7b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -299,10 +299,18 @@ version "1.9.0" resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.9.0.tgz#38af9491e2f105079a443703ee675fb27371ec94" +"@types/promise@^7.1.30": + version "7.1.30" + resolved "https://registry.yarnpkg.com/@types/promise/-/promise-7.1.30.tgz#1b6714b321fdfc54d1527e7a17116a0e1f2ab810" + "@types/prop-types@^15.5.3": version "15.5.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.3.tgz#bef071852dca2a2dbb65fecdb7bfb30cedae2de2" +"@types/qs@^6.5.1": + version "6.5.1" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.5.1.tgz#a38f69c62528d56ba7bd1f91335a8004988d72f7" + "@types/react-dom@^16.0.0": version "16.0.5" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.0.5.tgz#a757457662e3819409229e8f86795ff37b371f96" @@ -371,6 +379,16 @@ "@types/uglify-js" "*" source-map "^0.6.0" +"@types/whatwg-fetch@^0.0.33": + version "0.0.33" + resolved "https://registry.yarnpkg.com/@types/whatwg-fetch/-/whatwg-fetch-0.0.33.tgz#19c0d2863c8cb2380f21a1c736b79cbf7895bb13" + dependencies: + "@types/whatwg-streams" "*" + +"@types/whatwg-streams@*": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@types/whatwg-streams/-/whatwg-streams-0.0.6.tgz#5062c67efb695c886fe3dbb9618df35aac418503" + "@types/yargs@^11.0.0": version "11.0.0" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-11.0.0.tgz#124b9ed9c65b7091cc36da59ae12cbd47d8745ea" @@ -676,6 +694,10 @@ agent-base@^4.1.0: dependencies: es6-promisify "^5.0.0" +ajv-errors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.0.tgz#ecf021fa108fd17dfb5e6b383f2dd233e31ffc59" + ajv-keywords@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" @@ -709,6 +731,15 @@ ajv@^6.1.0: json-schema-traverse "^0.3.0" uri-js "^4.2.1" +ajv@^6.4.0: + version "6.5.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.1.tgz#88ebc1263c7133937d108b80c5572e64e1d9322d" + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.1" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -1897,6 +1928,10 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace@^0.11.0, brace@^0.11.1: + version "0.11.1" + resolved "https://registry.yarnpkg.com/brace/-/brace-0.11.1.tgz#4896fcc9d544eef45f4bb7660db320d3b379fe58" + braces@^1.8.2: version "1.8.5" resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" @@ -3327,6 +3362,10 @@ dezalgo@^1.0.0: asap "^2.0.0" wrappy "1" +diff-match-patch@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.1.tgz#d5f880213d82fbc124d2b95111fb3c033dbad7fa" + diff@^3.1.0, diff@^3.2.0, diff@^3.3.1, diff@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -6064,6 +6103,10 @@ json-schema-traverse@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -6354,6 +6397,14 @@ lodash.flattendeep@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + +lodash.isequal@^4.1.1: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + lodash.isfunction@^3.0.8: version "3.0.9" resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" @@ -7991,6 +8042,13 @@ prop-types@^15.5.0, prop-types@^15.5.4, prop-types@^15.6.0, prop-types@^15.6.1: loose-envify "^1.3.1" object-assign "^4.1.1" +prop-types@^15.5.8: + version "15.6.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" + dependencies: + loose-envify "^1.3.1" + object-assign "^4.1.1" + proxy-addr@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" @@ -8182,6 +8240,16 @@ rc@^1.1.6, rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-ace@^6.0.0: + version "6.1.1" + resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-6.1.1.tgz#c8339b4f0a27401bff53f477f125d3d7eac2a090" + dependencies: + brace "^0.11.0" + diff-match-patch "^1.0.0" + lodash.get "^4.4.2" + lodash.isequal "^4.1.1" + prop-types "^15.5.8" + react-dom@^16.4.0: version "16.4.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.0.tgz#099f067dd5827ce36a29eaf9a6cdc7cbf6216b1e" From 002de3c8f822aaa535c64577a8f84d08cc4a0c12 Mon Sep 17 00:00:00 2001 From: Harjeet Singh Date: Wed, 20 Jun 2018 13:16:35 -0700 Subject: [PATCH 10/14] fixed tslint errors --- demo/playground/hmr-playground.tsx | 9 +-- package.json | 13 ++-- src/common-elements/buttons.ts | 3 +- src/common-elements/input.tsx | 2 +- src/common-elements/panels.ts | 9 ++- src/common-elements/toggle.tsx | 2 +- src/components/Console/ConsoleEditor.tsx | 12 ++-- src/components/Console/ConsoleViewer.tsx | 86 +++++++++++++----------- src/components/Operation/Operation.tsx | 6 +- src/components/SourceCode/SourceCode.tsx | 2 +- src/theme.ts | 4 +- src/utils/loadAndBundleSpec.ts | 8 +-- tslint.json | 2 +- yarn.lock | 8 +-- 14 files changed, 84 insertions(+), 82 deletions(-) diff --git a/demo/playground/hmr-playground.tsx b/demo/playground/hmr-playground.tsx index 25384bfc..40398cde 100644 --- a/demo/playground/hmr-playground.tsx +++ b/demo/playground/hmr-playground.tsx @@ -8,7 +8,7 @@ import { AppStore } from '../../src/services/AppStore'; import { RedocRawOptions } from '../../src/services/RedocNormalizedOptions'; import { loadAndBundleSpec } from '../../src/utils/loadAndBundleSpec'; -const Ajv = require('ajv'); +// const Ajv = require('ajv'); const renderRoot = (props: RedocProps) => render( @@ -22,9 +22,10 @@ const big = window.location.search.indexOf('big') > -1; const swagger = window.location.search.indexOf('swagger') > -1; // compatibility mode ? let specUrl = swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml'; -//specUrl = 'intent.json'; -//specUrl = 'swagger.yaml'; -specUrl = 'petstore.json'; +specUrl = 'intent.json'; + +// specUrl = 'swagger.yaml'; +// specUrl = 'petstore.json'; let store; const options: RedocRawOptions = { nativeScrollbars: false }; diff --git a/package.json b/package.json index 7bf96ebe..87b88e87 100644 --- a/package.json +++ b/package.json @@ -67,8 +67,8 @@ "@types/marked": "^0.3.0", "@types/prismjs": "^1.6.4", "@types/promise": "^7.1.30", - "@types/qs": "^6.5.1", "@types/prop-types": "^15.5.3", + "@types/qs": "^6.5.1", "@types/react": "^16.0.41", "@types/react-dom": "^16.0.0", "@types/react-hot-loader": "^4.1.0", @@ -77,12 +77,9 @@ "@types/webpack-env": "^1.13.0", "@types/whatwg-fetch": "^0.0.33", "@types/yargs": "^11.0.0", - "ajv": "^6.4.0", - "ajv-errors": "^1.0.0", "babel-loader": "8.0.0-beta.2", "babel-plugin-styled-components": "^1.5.1", "beautify-benchmark": "^0.2.4", - "brace": "^0.11.1", "bundlesize": "^0.17.0", "conventional-changelog-cli": "^2.0.0", "copy-webpack-plugin": "^4.5.1", @@ -96,7 +93,6 @@ "enzyme-to-json": "^3.3.4", "fork-ts-checker-webpack-plugin": "^0.4.1", "html-webpack-plugin": "^3.1.0", - "react-ace": "^6.0.0", "jest": "^23.0.1", "license-checker": "^20.0.0", "lodash": "^4.17.10", @@ -130,6 +126,9 @@ "dependencies": { "@types/chai": "4.1.3", "@types/tapable": "1.0.2", + "ajv": "^6.4.0", + "ajv-errors": "^1.0.0", + "brace": "^0.11.1", "classnames": "^2.2.5", "decko": "^1.2.0", "dompurify": "^1.0.2", @@ -146,6 +145,8 @@ "polished": "^1.9.2", "prismjs": "^1.12.2", "prop-types": "^15.6.0", + "qs": "^6.5.2", + "react-ace": "^6.0.0", "react-dropdown": "^1.3.0", "react-hot-loader": "^4.0.0", "react-tabs": "^2.0.0", @@ -161,7 +162,7 @@ }, "bundlesize": [{ "path": "./bundles/redoc.standalone.js", - "maxSize": "300 kB" + "maxSize": "750 kB" }], "jest": { "transform": { diff --git a/src/common-elements/buttons.ts b/src/common-elements/buttons.ts index 1fdf7bed..c6a7650a 100644 --- a/src/common-elements/buttons.ts +++ b/src/common-elements/buttons.ts @@ -1,7 +1,6 @@ import * as React from 'react'; import styled, { ResolvedThemeInterface, StyledComponentClass } from '../styled-components'; - export const Button = styled.button` background: #248fb2; border-radius: 0px; @@ -13,4 +12,4 @@ export const Button = styled.button` export const SendButton = Button.extend` background: #B0045E; -`; \ No newline at end of file +`; diff --git a/src/common-elements/input.tsx b/src/common-elements/input.tsx index 0633d7c9..088307e1 100644 --- a/src/common-elements/input.tsx +++ b/src/common-elements/input.tsx @@ -6,4 +6,4 @@ export const TextField = styled.input` margin: 0.5em; border: 1px solid rgba(38,50,56,0.5); border-radius: 3px; -`; \ No newline at end of file +`; diff --git a/src/common-elements/panels.ts b/src/common-elements/panels.ts index e73b459e..02783d2a 100644 --- a/src/common-elements/panels.ts +++ b/src/common-elements/panels.ts @@ -5,7 +5,7 @@ export const MiddlePanel = styled.div` width: calc(100% - ${props => props.theme.rightPanel.width}); padding: ${props => props.theme.spacingUnit * 2}px; - ${media.lessThan('medium') ` + ${media.lessThan('medium')` width: 100%; `}; `; @@ -16,7 +16,7 @@ export const RightPanel = styled.div` background-color: ${props => props.theme.rightPanel.backgroundColor}; padding: ${props => props.theme.spacingUnit * 2}px; - ${media.lessThan('medium') ` + ${media.lessThan('medium')` width: 100%; `}; `; @@ -26,7 +26,7 @@ export const DarkRightPanel = RightPanel.extend` `; export const EmptyDarkRightPanel = DarkRightPanel.extend` - ${media.lessThan('medium') ` + ${media.lessThan('medium')` padding: 0 `}; `; @@ -35,7 +35,7 @@ export const Row = styled.div` display: flex; width: 100%; - ${media.lessThan('medium') ` + ${media.lessThan('medium')` flex-direction: column; `}; `; @@ -49,4 +49,3 @@ export const FlexLayout = styled.div` export const ConsoleActionsRow = FlexLayout.extend` padding: 5px 0px; `; - diff --git a/src/common-elements/toggle.tsx b/src/common-elements/toggle.tsx index cd09b4a4..4332f5a1 100644 --- a/src/common-elements/toggle.tsx +++ b/src/common-elements/toggle.tsx @@ -8,4 +8,4 @@ export const Toggle = styled.input` background: papayawhip; border: none; border-radius: 3px; -`; \ No newline at end of file +`; diff --git a/src/components/Console/ConsoleEditor.tsx b/src/components/Console/ConsoleEditor.tsx index e12ded51..fb801eb2 100644 --- a/src/components/Console/ConsoleEditor.tsx +++ b/src/components/Console/ConsoleEditor.tsx @@ -3,12 +3,11 @@ import * as React from 'react'; import AceEditor from 'react-ace'; -import 'brace/mode/json'; import 'brace/mode/curly'; +import 'brace/mode/json'; import 'brace/theme/github'; import 'brace/theme/monokai'; - import { MediaTypeModel } from '../../services/models'; import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch'; @@ -18,19 +17,19 @@ import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel'; import { InvertedSimpleDropdown, MimeLabel } from '../PayloadSamples/styled.elements'; export interface ConsoleEditorProps { - mediaTypes: MediaTypeModel[] + mediaTypes: MediaTypeModel[]; } @observer export class ConsoleEditor extends React.Component { - public editor: any; + editor: any; /* get aceEditor(): AceEditor { return this._aceEditor; } - + set aceEditor(aceEditor: AceEditor) { if (aceEditor) { this.aceEditor = this.aceEditor @@ -48,7 +47,7 @@ export class ConsoleEditor extends React.Component { return null; } let sample = {}; - for (let mediaType of mediaTypes) { + for (const mediaType of mediaTypes) { if (mediaType.name.indexOf('json') > -1) { if (mediaType.examples) { sample = mediaType.examples && mediaType.examples.default && mediaType.examples.default.value; @@ -57,7 +56,6 @@ export class ConsoleEditor extends React.Component { } } - /* let body = {}; if(mimeContent.mediaTypes && mimeContent.mediaTypes.length>0){ diff --git a/src/components/Console/ConsoleViewer.tsx b/src/components/Console/ConsoleViewer.tsx index 771b598d..161d3a4d 100644 --- a/src/components/Console/ConsoleViewer.tsx +++ b/src/components/Console/ConsoleViewer.tsx @@ -1,15 +1,15 @@ import { observer } from 'mobx-react'; import * as React from 'react'; -import { OperationModel } from '../../services/models'; -import { PayloadSamples } from '../PayloadSamples/PayloadSamples'; -import { SourceCodeWithCopy } from '../SourceCode/SourceCode'; import { SendButton } from '../../common-elements/buttons'; import { ConsoleActionsRow } from '../../common-elements/panels'; +import { OperationModel } from '../../services/models'; +import { OpenAPISchema } from '../../types'; +import { PayloadSamples } from '../PayloadSamples/PayloadSamples'; +import { SourceCodeWithCopy } from '../SourceCode/SourceCode'; import { ConsoleEditor } from './ConsoleEditor'; const qs = require('qs'); - export interface ConsoleViewerProps { operation: OperationModel; } @@ -18,39 +18,44 @@ export interface ConsoleViewerState { result: any; } +export interface Schema { + _$ref?: any; + rawSchema?: OpenAPISchema; +} + @observer export class ConsoleViewer extends React.Component { - private consoleEditor: ConsoleEditor; operation: OperationModel; visited = new Set(); + private consoleEditor: ConsoleEditor; constructor(props) { super(props); this.state = { - result: null + result: null, }; } onClickSend = async () => { const ace = this.consoleEditor && this.consoleEditor.editor; - //const value = ace && ace.editor && + // const value = ace && ace.editor && const schema = this.getSchema(); const { operation } = this.props; - //console.log('Schema: ' + JSON.stringify(schema, null, 2)); + // console.log('Schema: ' + JSON.stringify(schema, null, 2)); let value = ace && ace.editor.getValue(); - const ref = schema && schema['_$ref']; + const ref = schema && schema._$ref; - //var valid = window && window.ajv.validate({ $ref: `specs.json${ref}` }, value); - //console.log(JSON.stringify(window.ajv.errors)); - //if (!valid) { + // var valid = window && window.ajv.validate({ $ref: `specs.json${ref}` }, value); + // console.log(JSON.stringify(window.ajv.errors)); + // if (!valid) { // console.warn('INVALID REQUEST!'); - //} + // } const endpoint = { method: operation.httpVerb, - path: operation.servers[0].url + operation.path - } + path: operation.servers[0].url + operation.path, + }; console.log('Value: ' + value); if (value) { value = JSON.parse(value); @@ -59,7 +64,7 @@ export class ConsoleViewer extends React.Component (this.consoleEditor = editor)} /> + ref={(editor: ConsoleEditor) => (this.consoleEditor = editor)} + /> )} {false && samples.map(sample => ( @@ -146,7 +149,7 @@ export class ConsoleViewer extends React.ComponentSend Request {result && - + } ); @@ -160,15 +163,16 @@ export class ConsoleViewer extends React.Component -1) { if (mediaType.schema) { - //schema = mediaType.schema; - schema = mediaType.schema && mediaType.schema.rawSchema; + // schema = mediaType.schema; + schema.rawSchema = mediaType.schema && mediaType.schema.rawSchema; console.log('rawSchema : ' + JSON.stringify(schema)); console.log('schema : ' + JSON.stringify(mediaType.schema.schema)); - schema['_$ref'] = mediaType.schema && mediaType.schema['_$ref'] + schema._$ref = mediaType.schema && mediaType.schema._$ref; } break; } diff --git a/src/components/Operation/Operation.tsx b/src/components/Operation/Operation.tsx index 47c5ba00..3cd96b1c 100644 --- a/src/components/Operation/Operation.tsx +++ b/src/components/Operation/Operation.tsx @@ -9,13 +9,13 @@ import { Badge, DarkRightPanel, H2, MiddlePanel, Row, Toggle } from '../../commo import { OptionsContext } from '../OptionsProvider'; import { ShareLink } from '../../common-elements/linkify'; +import { ConsoleViewer } from '../Console/ConsoleViewer'; import { Endpoint } from '../Endpoint/Endpoint'; import { Markdown } from '../Markdown/Markdown'; import { Parameters } from '../Parameters/Parameters'; import { RequestSamples } from '../RequestSamples/RequestSamples'; import { ResponsesList } from '../Responses/ResponsesList'; import { ResponseSamples } from '../ResponseSamples/ResponseSamples'; -import { ConsoleViewer } from '../Console/ConsoleViewer'; import { OperationModel as OperationType } from '../../services/models'; @@ -54,9 +54,9 @@ export class Operation extends React.Component { }; } - onTry = (e) => { + onTry = e => { this.setState({ - executeMode: e.target.checked + executeMode: e.target.checked, }); console.log(e.target.checked + ' ' + this.props.operation); }; diff --git a/src/components/SourceCode/SourceCode.tsx b/src/components/SourceCode/SourceCode.tsx index 7f226152..3d4e1e30 100644 --- a/src/components/SourceCode/SourceCode.tsx +++ b/src/components/SourceCode/SourceCode.tsx @@ -4,7 +4,7 @@ import { highlight } from '../../utils'; import { SampleControls, SampleControlsWrap } from '../../common-elements'; import { CopyButtonWrapper } from '../../common-elements/CopyButtonWrapper'; -//${props => props.theme.responsePanel}; +// ${props => props.theme.responsePanel}; const StyledPre = styled.pre` font-family: ${props => props.theme.code.fontFamily}; font-size: ${props => props.theme.code.fontSize}; diff --git a/src/theme.ts b/src/theme.ts index 6785370e..913999fc 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -70,7 +70,7 @@ const defaultTheme: ThemeInterface = { width: '40%', }, styledPre: { - maxHeight: '500px' + maxHeight: '500px', }, }; @@ -181,7 +181,7 @@ export interface ResolvedThemeInterface { }; styledPre: { maxHeight: string; - } + }; } export type primitive = string | number | boolean | undefined | null; diff --git a/src/utils/loadAndBundleSpec.ts b/src/utils/loadAndBundleSpec.ts index 8b50124a..cbecbb7f 100644 --- a/src/utils/loadAndBundleSpec.ts +++ b/src/utils/loadAndBundleSpec.ts @@ -1,11 +1,10 @@ import * as JsonSchemaRefParser from 'json-schema-ref-parser'; import { convertObj } from 'swagger2openapi'; import { OpenAPISpec } from '../types'; -import { cloneDeep } from 'lodash'; export async function loadAndBundleSpec(specUrlOrObject: object | string): Promise { const parser = new JsonSchemaRefParser(); - let spec = await parser.bundle(specUrlOrObject, { + const spec = await parser.bundle(specUrlOrObject, { resolve: { http: { withCredentials: false } }, } as object); @@ -15,10 +14,11 @@ export async function loadAndBundleSpec(specUrlOrObject: object | string): Promi } // we can derefrence the schema here for future use. + // import { cloneDeep } from 'lodash'; // const derefrencedSpec = await parser.dereference(cloneDeep(spec)); - //const derefed = await parser.dereference(v2Specs, { + // const derefed = await parser.dereference(v2Specs, { // resolve: { http: { withCredentials: false } }, - //} as object); + // } as object); return v2Specs; diff --git a/tslint.json b/tslint.json index 9c980dcb..feec7e40 100644 --- a/tslint.json +++ b/tslint.json @@ -17,7 +17,7 @@ "quotemark": [true, "single", "avoid-template", "jsx-double"], "variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case"], "arrow-parens": [true, "ban-single-arg-parens"], - "no-submodule-imports": [true, "prismjs", "perfect-scrollbar", "react-dom", "core-js"], + "no-submodule-imports": [true, "prismjs", "perfect-scrollbar", "react-dom", "core-js", "brace"], "object-literal-key-quotes": [true, "as-needed"], "no-unused-expression": [true, "allow-tagged-template"], "semicolon": [true, "always", "ignore-bound-class-methods"], diff --git a/yarn.lock b/yarn.lock index d4c80a7b..7c15df74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8133,14 +8133,14 @@ qs@6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" +qs@^6.5.2, qs@~6.5.1: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" -qs@~6.5.1: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - query-string@^4.1.0: version "4.3.4" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" From ee8bd4fe17b8904757f397bf9c8367b411c46b76 Mon Sep 17 00:00:00 2001 From: Harjeet Singh Date: Wed, 27 Jun 2018 12:10:01 -0700 Subject: [PATCH 11/14] added more to options to enable/disable console. Also introduced some constants to make it more configurable --- demo/playground/hmr-playground.tsx | 15 +++------------ src/common-elements/buttons.ts | 6 ++++++ src/common-elements/index.ts | 1 + src/common-elements/panels.ts | 4 ++++ src/components/Console/ConsoleEditor.tsx | 24 ------------------------ src/components/Console/index.ts | 2 ++ src/components/Operation/Operation.tsx | 18 ++++++++++-------- src/components/SideMenu/SideMenu.tsx | 18 +++++++++--------- src/components/index.ts | 2 +- src/services/RedocNormalizedOptions.ts | 10 ++++++++++ src/services/SearchStore.ts | 2 +- 11 files changed, 47 insertions(+), 55 deletions(-) create mode 100644 src/components/Console/index.ts diff --git a/demo/playground/hmr-playground.tsx b/demo/playground/hmr-playground.tsx index 40398cde..786b8ab3 100644 --- a/demo/playground/hmr-playground.tsx +++ b/demo/playground/hmr-playground.tsx @@ -22,26 +22,17 @@ const big = window.location.search.indexOf('big') > -1; const swagger = window.location.search.indexOf('swagger') > -1; // compatibility mode ? let specUrl = swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml'; -specUrl = 'intent.json'; +// specUrl = 'intent.json'; // specUrl = 'swagger.yaml'; -// specUrl = 'petstore.json'; +specUrl = 'petstore.json'; let store; -const options: RedocRawOptions = { nativeScrollbars: false }; +const options: RedocRawOptions = { nativeScrollbars: false, enableConsole: true, providedByName: 'Intent ApiDocs by Nutanix', providedByUri: 'http://www.nutanix.com' }; async function init() { const spec = await loadAndBundleSpec(specUrl); store = new AppStore(spec, specUrl, options); - /* - const ajv = new Ajv({ allErrors: true, unknownFormats: ['int32', 'UUID', 'int64'] }); - ajv.addSchema(spec, 'specs.json') - - const ajvError = require('ajv-errors')(ajv); - - window.ajv = ajv; - window.ajvError = ajvError; - */ renderRoot({ store }); } diff --git a/src/common-elements/buttons.ts b/src/common-elements/buttons.ts index c6a7650a..14be3ba8 100644 --- a/src/common-elements/buttons.ts +++ b/src/common-elements/buttons.ts @@ -13,3 +13,9 @@ export const Button = styled.button` export const SendButton = Button.extend` background: #B0045E; `; + +export const ConsoleButton = Button.extend` + background: #e2e2e2; + color: black; + float: right; +`; \ No newline at end of file diff --git a/src/common-elements/index.ts b/src/common-elements/index.ts index cd940d34..3ff7e1cc 100644 --- a/src/common-elements/index.ts +++ b/src/common-elements/index.ts @@ -11,3 +11,4 @@ export * from './samples'; export * from './perfect-scrollbar'; export * from './toggle'; export * from './input'; +export * from './buttons'; diff --git a/src/common-elements/panels.ts b/src/common-elements/panels.ts index 02783d2a..05f8fe89 100644 --- a/src/common-elements/panels.ts +++ b/src/common-elements/panels.ts @@ -49,3 +49,7 @@ export const FlexLayout = styled.div` export const ConsoleActionsRow = FlexLayout.extend` padding: 5px 0px; `; + +export const FlexLayoutReverse = FlexLayout.extend` + flex-direction: row-reverse; +`; \ No newline at end of file diff --git a/src/components/Console/ConsoleEditor.tsx b/src/components/Console/ConsoleEditor.tsx index fb801eb2..e9175569 100644 --- a/src/components/Console/ConsoleEditor.tsx +++ b/src/components/Console/ConsoleEditor.tsx @@ -25,21 +25,6 @@ export class ConsoleEditor extends React.Component { editor: any; - /* - get aceEditor(): AceEditor { - return this._aceEditor; - } - - set aceEditor(aceEditor: AceEditor) { - if (aceEditor) { - this.aceEditor = this.aceEditor - } - else { - console.log("Error: Undefined ace editor!"); - } - } - */ - render() { const { mediaTypes } = this.props; @@ -56,15 +41,6 @@ export class ConsoleEditor extends React.Component { } } - /* - let body = {}; - if(mimeContent.mediaTypes && mimeContent.mediaTypes.length>0){ - body = mimeContent.mediaTypes[0]; - if(body.examples && body.examples.default) { - - } - } - */ return (
{ }; } - onTry = e => { + onConsoleClick = () => { this.setState({ - executeMode: e.target.checked, + executeMode: !this.state.executeMode }); - console.log(e.target.checked + ' ' + this.props.operation); - }; - + } /* activate = (item: IMenuItem) => { this.props.menu.activateAndScroll(item, true); @@ -77,6 +75,7 @@ export class Operation extends React.Component { const { name: summary, description, deprecated } = operation; const { executeMode } = this.state; + const consoleButtonLabel = (executeMode) ? 'Hide Console' : 'Show Console'; return ( {options => ( @@ -86,8 +85,11 @@ export class Operation extends React.Component { {summary} {deprecated && Deprecated } - - Try it out! + {options.enableConsole && + + {consoleButtonLabel} + + } {options.pathInMiddlePanel && } {description !== undefined && } diff --git a/src/components/SideMenu/SideMenu.tsx b/src/components/SideMenu/SideMenu.tsx index 9aab7310..d2c8f3bc 100644 --- a/src/components/SideMenu/SideMenu.tsx +++ b/src/components/SideMenu/SideMenu.tsx @@ -28,15 +28,15 @@ export class SideMenu extends React.Component<{ menu: MenuStore }> { root={true} /> ) : ( - - - - - Documentation Powered by ReDoc - - - - ) + + + + + {options.providedByName} + + + + ) } ); diff --git a/src/components/index.ts b/src/components/index.ts index 09dac433..e8cac5e2 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -16,4 +16,4 @@ export * from './StoreProvider'; export * from './OptionsProvider'; export * from './SideMenu/'; export * from './StickySidebar/StickyResponsiveSidebar'; -export * from './SearchBox/SearchBox'; +export * from './SearchBox/SearchBox'; \ No newline at end of file diff --git a/src/services/RedocNormalizedOptions.ts b/src/services/RedocNormalizedOptions.ts index f1a7373c..b1b427ff 100644 --- a/src/services/RedocNormalizedOptions.ts +++ b/src/services/RedocNormalizedOptions.ts @@ -6,6 +6,7 @@ export interface RedocRawOptions { theme?: ThemeInterface; scrollYOffset?: number | string | (() => number); hideHostname?: boolean | string; + enableConsole?: boolean; expandResponses?: string | 'all'; requiredPropsFirst?: boolean | string; noAutoAuth?: boolean | string; @@ -15,6 +16,9 @@ export interface RedocRawOptions { hideLoading?: boolean | string; hideDownloadButton?: boolean | string; + providedByName?: string; + providedByUri?: string; + unstable_ignoreMimeParameters?: boolean; } @@ -93,6 +97,9 @@ export class RedocNormalizedOptions { pathInMiddlePanel: boolean; untrustedSpec: boolean; hideDownloadButton: boolean; + enableConsole: boolean; + providedByName: string; + providedByUri: string; /* tslint:disable-next-line */ unstable_ignoreMimeParameters: boolean; @@ -108,6 +115,9 @@ export class RedocNormalizedOptions { this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel); this.untrustedSpec = argValueToBoolean(raw.untrustedSpec); this.hideDownloadButton = argValueToBoolean(raw.hideDownloadButton); + this.enableConsole = argValueToBoolean(raw.enableConsole); + this.providedByName = raw.providedByName || 'Documentation Powered by ReDoc'; + this.providedByUri = raw.providedByUri || 'https://github.com/Rebilly/ReDoc'; this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters); } diff --git a/src/services/SearchStore.ts b/src/services/SearchStore.ts index ed8a616d..e856580d 100644 --- a/src/services/SearchStore.ts +++ b/src/services/SearchStore.ts @@ -9,7 +9,7 @@ let worker: new () => Worker; if (IS_BROWSER) { try { // tslint:disable-next-line - worker = require('workerize-loader?inline&fallback=false!./SearchWorker.worker'); + worker = require('workerize-loader?fallback=false!./SearchWorker.worker'); } catch (e) { worker = require('./SearchWorker.worker').default; } From 07ad2ccecfab634a7c04e46f8b1390186b152b7b Mon Sep 17 00:00:00 2001 From: Harjeet Singh Date: Wed, 27 Jun 2018 13:21:57 -0700 Subject: [PATCH 12/14] fixed lint issues --- src/common-elements/buttons.ts | 2 +- src/common-elements/panels.ts | 2 +- src/components/Operation/Operation.tsx | 4 ++-- src/components/index.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/common-elements/buttons.ts b/src/common-elements/buttons.ts index 14be3ba8..79479a29 100644 --- a/src/common-elements/buttons.ts +++ b/src/common-elements/buttons.ts @@ -18,4 +18,4 @@ export const ConsoleButton = Button.extend` background: #e2e2e2; color: black; float: right; -`; \ No newline at end of file +`; diff --git a/src/common-elements/panels.ts b/src/common-elements/panels.ts index 05f8fe89..97927c7b 100644 --- a/src/common-elements/panels.ts +++ b/src/common-elements/panels.ts @@ -52,4 +52,4 @@ export const ConsoleActionsRow = FlexLayout.extend` export const FlexLayoutReverse = FlexLayout.extend` flex-direction: row-reverse; -`; \ No newline at end of file +`; diff --git a/src/components/Operation/Operation.tsx b/src/components/Operation/Operation.tsx index 33eacc7d..1ee827f1 100644 --- a/src/components/Operation/Operation.tsx +++ b/src/components/Operation/Operation.tsx @@ -4,7 +4,7 @@ import { SecurityRequirements } from '../SecurityRequirement/SecuirityRequiremen import { observer } from 'mobx-react'; -import { ConsoleButton, Badge, FlexLayoutReverse, DarkRightPanel, H2, MiddlePanel, Row, Toggle } from '../../common-elements'; +import { Badge, ConsoleButton, DarkRightPanel, FlexLayoutReverse, H2, MiddlePanel, Row, Toggle } from '../../common-elements'; import { OptionsContext } from '../OptionsProvider'; @@ -56,7 +56,7 @@ export class Operation extends React.Component { onConsoleClick = () => { this.setState({ - executeMode: !this.state.executeMode + executeMode: !this.state.executeMode, }); } /* diff --git a/src/components/index.ts b/src/components/index.ts index e8cac5e2..09dac433 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -16,4 +16,4 @@ export * from './StoreProvider'; export * from './OptionsProvider'; export * from './SideMenu/'; export * from './StickySidebar/StickyResponsiveSidebar'; -export * from './SearchBox/SearchBox'; \ No newline at end of file +export * from './SearchBox/SearchBox'; From b1dc621212c24fe568e605dcbe967f5440056c61 Mon Sep 17 00:00:00 2001 From: Harjeet Singh Date: Fri, 6 Jul 2018 10:59:31 -0700 Subject: [PATCH 13/14] mediatype is properly picked and stuff like uuid in uri is also properly handlled, --- demo/playground/hmr-playground.tsx | 9 ++-- src/components/Console/ConsoleViewer.tsx | 67 ++++++++++++++++++------ src/components/Endpoint/Endpoint.tsx | 3 ++ src/components/Fields/Field.tsx | 8 ++- src/components/Operation/Operation.tsx | 2 +- src/services/RedocNormalizedOptions.ts | 9 ++++ src/services/models/Field.ts | 7 ++- src/utils/fetch.ts | 2 +- 8 files changed, 84 insertions(+), 23 deletions(-) diff --git a/demo/playground/hmr-playground.tsx b/demo/playground/hmr-playground.tsx index 786b8ab3..a89f1755 100644 --- a/demo/playground/hmr-playground.tsx +++ b/demo/playground/hmr-playground.tsx @@ -22,13 +22,16 @@ const big = window.location.search.indexOf('big') > -1; const swagger = window.location.search.indexOf('swagger') > -1; // compatibility mode ? let specUrl = swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml'; -// specUrl = 'intent.json'; +specUrl = 'intent.json'; // specUrl = 'swagger.yaml'; -specUrl = 'petstore.json'; +// specUrl = 'petstore.json'; let store; -const options: RedocRawOptions = { nativeScrollbars: false, enableConsole: true, providedByName: 'Intent ApiDocs by Nutanix', providedByUri: 'http://www.nutanix.com' }; +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 }; async function init() { const spec = await loadAndBundleSpec(specUrl); diff --git a/src/components/Console/ConsoleViewer.tsx b/src/components/Console/ConsoleViewer.tsx index 161d3a4d..2f585ce1 100644 --- a/src/components/Console/ConsoleViewer.tsx +++ b/src/components/Console/ConsoleViewer.tsx @@ -2,7 +2,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { SendButton } from '../../common-elements/buttons'; import { ConsoleActionsRow } from '../../common-elements/panels'; -import { OperationModel } from '../../services/models'; +import { FieldModel, OperationModel } from '../../services/models'; import { OpenAPISchema } from '../../types'; import { PayloadSamples } from '../PayloadSamples/PayloadSamples'; import { SourceCodeWithCopy } from '../SourceCode/SourceCode'; @@ -12,6 +12,9 @@ const qs = require('qs'); export interface ConsoleViewerProps { operation: OperationModel; + additionalHeaders?: object; + queryParamPrefix?: string; + queryParamSuffix?: string; } export interface ConsoleViewerState { @@ -26,6 +29,7 @@ export interface Schema { @observer export class ConsoleViewer extends React.Component { operation: OperationModel; + additionalHeaders: object; visited = new Set(); private consoleEditor: ConsoleEditor; @@ -39,46 +43,77 @@ export class ConsoleViewer extends React.Component) { + const queryParamPrefix = '{'; + const queryParamSuffix = '}'; + + 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) { + url = url.replace(`${queryParamPrefix}${fieldModel.name}${queryParamSuffix}`, fieldModel.$value); + } + } + + if (url.split(queryParamPrefix).length > 1) { + console.error('** we have missing query params ** ', url); + throw Error(`** we have missing query params ** ${url}`); + } + + return url; }; - async invoke(endpoint, body) { - const headers = { 'Content-Type': 'application/json' }; + async invoke(endpoint, body, headers = {}) { try { - let url = endpoint.path; + const { operation } = this.props; + let url = this.addParamsToUrl(endpoint.path, operation.parameters || []); if (endpoint.method.toLocaleLowerCase() === 'get') { url = url + '?' + qs.stringify(body || ''); } - const myHeaders = new Headers(); - myHeaders.append('Content-Type', 'application/json'); + for (const [key, value] of Object.entries(headers)) { + myHeaders.append(key, `${value}`); + } const request = new Request(url, { method: endpoint.method, diff --git a/src/components/Endpoint/Endpoint.tsx b/src/components/Endpoint/Endpoint.tsx index 734fa32b..0615fb47 100644 --- a/src/components/Endpoint/Endpoint.tsx +++ b/src/components/Endpoint/Endpoint.tsx @@ -41,6 +41,9 @@ export class Endpoint extends React.Component { const { operation, inverted, hideHostname } = this.props; const { expanded } = this.state; + if (operation && operation.parameters && operation.parameters.length > 0) { + console.log('USER INPUT VALUE:: ' + operation.parameters[0]['$value']); + } // TODO: highlight server variables, e.g. https://{user}.test.com return ( diff --git a/src/components/Fields/Field.tsx b/src/components/Fields/Field.tsx index e2fc2979..158004d4 100644 --- a/src/components/Fields/Field.tsx +++ b/src/components/Fields/Field.tsx @@ -30,6 +30,12 @@ export class Field extends React.PureComponent { toggle = () => { this.props.field.toggle(); }; + + onFieldChange = (e) => { + console.log('Textfield value is ' + e.target.placeholder + ' - ' + e.target.value); + this.props.field.setValue(e.target.value); + }; + render() { const { className, field, isLast } = this.props; const { name, expanded, deprecated, required, kind } = field; @@ -63,7 +69,7 @@ export class Field extends React.PureComponent { {field && field.in === 'path' && - + } {field.expanded && diff --git a/src/components/Operation/Operation.tsx b/src/components/Operation/Operation.tsx index 1ee827f1..37e413c5 100644 --- a/src/components/Operation/Operation.tsx +++ b/src/components/Operation/Operation.tsx @@ -100,7 +100,7 @@ export class Operation extends React.Component { {!options.pathInMiddlePanel && } {executeMode &&
- +
} {!executeMode && diff --git a/src/services/RedocNormalizedOptions.ts b/src/services/RedocNormalizedOptions.ts index b1b427ff..8d0608a9 100644 --- a/src/services/RedocNormalizedOptions.ts +++ b/src/services/RedocNormalizedOptions.ts @@ -7,6 +7,7 @@ export interface RedocRawOptions { scrollYOffset?: number | string | (() => number); hideHostname?: boolean | string; enableConsole?: boolean; + additionalHeaders?: object; expandResponses?: string | 'all'; requiredPropsFirst?: boolean | string; noAutoAuth?: boolean | string; @@ -18,6 +19,8 @@ export interface RedocRawOptions { providedByName?: string; providedByUri?: string; + queryParamPrefix?: string; + queryParamSuffix?: string; unstable_ignoreMimeParameters?: boolean; } @@ -98,8 +101,11 @@ export class RedocNormalizedOptions { untrustedSpec: boolean; hideDownloadButton: boolean; enableConsole: boolean; + additionalHeaders: object; providedByName: string; providedByUri: string; + queryParamPrefix: string; + queryParamSuffix: string; /* tslint:disable-next-line */ unstable_ignoreMimeParameters: boolean; @@ -116,8 +122,11 @@ export class RedocNormalizedOptions { this.untrustedSpec = argValueToBoolean(raw.untrustedSpec); this.hideDownloadButton = argValueToBoolean(raw.hideDownloadButton); this.enableConsole = argValueToBoolean(raw.enableConsole); + this.additionalHeaders = raw.additionalHeaders || {}; this.providedByName = raw.providedByName || 'Documentation Powered by ReDoc'; this.providedByUri = raw.providedByUri || 'https://github.com/Rebilly/ReDoc'; + this.queryParamPrefix = raw.queryParamPrefix || '{'; + this.queryParamSuffix = raw.queryParamSuffix || '}'; this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters); } diff --git a/src/services/models/Field.ts b/src/services/models/Field.ts index b2405844..2cbf9e6b 100644 --- a/src/services/models/Field.ts +++ b/src/services/models/Field.ts @@ -11,7 +11,7 @@ import { SchemaModel } from './Schema'; */ export class FieldModel { @observable expanded: boolean = false; - + @observable $value: string = ''; schema: SchemaModel; name: string; required: boolean; @@ -46,4 +46,9 @@ export class FieldModel { toggle() { this.expanded = !this.expanded; } + + @action + setValue(value: string) { + this.$value = value; + } } diff --git a/src/utils/fetch.ts b/src/utils/fetch.ts index 835e785a..b332d125 100644 --- a/src/utils/fetch.ts +++ b/src/utils/fetch.ts @@ -1 +1 @@ -parseparseFetchFetch \ No newline at end of file +//parseparseFetchFetch \ No newline at end of file From bc59d13a5ca824bb483fb9e65dff5f3ea3424198 Mon Sep 17 00:00:00 2001 From: Harjeet Singh Date: Fri, 6 Jul 2018 14:43:43 -0700 Subject: [PATCH 14/14] fixed lint issues. --- src/components/Console/ConsoleViewer.tsx | 4 ++-- src/components/Endpoint/Endpoint.tsx | 3 --- src/components/Fields/Field.tsx | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/components/Console/ConsoleViewer.tsx b/src/components/Console/ConsoleViewer.tsx index 2f585ce1..0931a805 100644 --- a/src/components/Console/ConsoleViewer.tsx +++ b/src/components/Console/ConsoleViewer.tsx @@ -82,7 +82,7 @@ export class ConsoleViewer extends React.Component) { + addParamsToUrl(url: string, params: FieldModel[]) { const queryParamPrefix = '{'; const queryParamSuffix = '}'; @@ -101,7 +101,7 @@ export class ConsoleViewer extends React.Component { const { operation, inverted, hideHostname } = this.props; const { expanded } = this.state; - if (operation && operation.parameters && operation.parameters.length > 0) { - console.log('USER INPUT VALUE:: ' + operation.parameters[0]['$value']); - } // TODO: highlight server variables, e.g. https://{user}.test.com return ( diff --git a/src/components/Fields/Field.tsx b/src/components/Fields/Field.tsx index 158004d4..a20deecd 100644 --- a/src/components/Fields/Field.tsx +++ b/src/components/Fields/Field.tsx @@ -31,7 +31,7 @@ export class Field extends React.PureComponent { this.props.field.toggle(); }; - onFieldChange = (e) => { + onFieldChange = e => { console.log('Textfield value is ' + e.target.placeholder + ' - ' + e.target.value); this.props.field.setValue(e.target.value); };