Merge branch 'master' into bug/2903-wrong-font-for-error

This commit is contained in:
Kyle
2017-08-04 16:37:17 -07:00
committed by GitHub
60 changed files with 1281 additions and 165 deletions

View File

@@ -16,7 +16,7 @@ deploy:
email: apiteam@swagger.io email: apiteam@swagger.io
skip_cleanup: true skip_cleanup: true
api_key: api_key:
secure: "IJkLaACa+rfERf1O5nwlqOyuo9sbul3FBhBt4Un9P+DvEet3AoDPV9NQVLd8SkmQYKGbGQWF4BIdjrO5nqFD6Te+JTeUX5Uo/DFS/fu9qw1xv0dQpvbJFuoYnnFlbzGTEs4CFa8lbu3ZromFHQGOQxRobjsG1Kf0dWFSSzmND3g=" secure: "YKk5L1BL4oAixvLjWp+i85fNFXK85HKOlUt6QypkZkt23My5aywuYsv5VCLjjOtuWc72zbmOzP82DTBsuRswCRViXWCiNYhl42QTdvadHu0uIlM/FL6aNlvPpzXIws4bMvz1aYOTzFTnSnNuvCTzF1daW0+2ClOo3r0nLEdDfFg="
on: on:
tags: true tags: true
repo: swagger-api/swagger-ui repo: swagger-api/swagger-ui

View File

@@ -18,16 +18,17 @@ This repo publishes to two different NPM packages:
For the older version of swagger-ui, refer to the [*2.x branch*](https://github.com/swagger-api/swagger-ui/tree/2.x). For the older version of swagger-ui, refer to the [*2.x branch*](https://github.com/swagger-api/swagger-ui/tree/2.x).
## Compatibility ## Compatibility
The OpenAPI Specification has undergone 4 revisions since initial creation in 2010. Compatibility between swagger-ui and the OpenAPI Specification is as follows: The OpenAPI Specification has undergone 5 revisions since initial creation in 2010. Compatibility between swagger-ui and the OpenAPI Specification is as follows:
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes | Status Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes
------------------ | ------------ | -------------------------- | ----- | ------ ------------------ | ------------ | -------------------------- | -----
3.0.19 | 2017-07-14 | 2.0 | [tag v3.0.19](https://github.com/swagger-api/swagger-ui/tree/v3.0.19) | 3.1.2 | 2017-07-31 | 2.0, 3.0 | [tag v3.1.2](https://github.com/swagger-api/swagger-ui/tree/v3.1.2)
2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10) | 3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21)
2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5) | 2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10)
2.0.24 | 2014-09-12 | 1.1, 1.2 | [tag v2.0.24](https://github.com/swagger-api/swagger-ui/tree/v2.0.24) | 2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5)
1.0.13 | 2013-03-08 | 1.1, 1.2 | [tag v1.0.13](https://github.com/swagger-api/swagger-ui/tree/v1.0.13) | 2.0.24 | 2014-09-12 | 1.1, 1.2 | [tag v2.0.24](https://github.com/swagger-api/swagger-ui/tree/v2.0.24)
1.0.1 | 2011-10-11 | 1.0, 1.1 | [tag v1.0.1](https://github.com/swagger-api/swagger-ui/tree/v1.0.1) | 1.0.13 | 2013-03-08 | 1.1, 1.2 | [tag v1.0.13](https://github.com/swagger-api/swagger-ui/tree/v1.0.13)
1.0.1 | 2011-10-11 | 1.0, 1.1 | [tag v1.0.1](https://github.com/swagger-api/swagger-ui/tree/v1.0.1)
### How to run ### How to run
@@ -96,7 +97,8 @@ To use swagger-ui's bundles, you should take a look at the [source of swagger-ui
#### OAuth2 configuration #### OAuth2 configuration
You can configure OAuth2 authorization by calling `initOAuth` method with passed configs under the instance of `SwaggerUIBundle` You can configure OAuth2 authorization by calling `initOAuth` method with passed configs under the instance of `SwaggerUIBundle`
default `client_id` and `client_secret`, `realm`, an application name `appName`, `scopeSeparator`, `additionalQueryStringParams`. default `client_id` and `client_secret`, `realm`, an application name `appName`, `scopeSeparator`, `additionalQueryStringParams`,
`useBasicAuthenticationWithAccessCodeGrant`.
Config Name | Description Config Name | Description
--- | --- --- | ---
@@ -106,6 +108,7 @@ realm | realm query parameter (for oauth1) added to `authorizationUrl` and `toke
appName | application name, displayed in authorization popup. MUST be a string appName | application name, displayed in authorization popup. MUST be a string
scopeSeparator | scope separator for passing scopes, encoded before calling, default value is a space (encoded value `%20`). MUST be a string scopeSeparator | scope separator for passing scopes, encoded before calling, default value is a space (encoded value `%20`). MUST be a string
additionalQueryStringParams | Additional query parameters added to `authorizationUrl` and `tokenUrl`. MUST be an object additionalQueryStringParams | Additional query parameters added to `authorizationUrl` and `tokenUrl`. MUST be an object
useBasicAuthenticationWithAccessCodeGrant | Only activated for the `accessCode` flow. During the `authorization_code` request to the `tokenUrl`, pass the [Client Password](https://tools.ietf.org/html/rfc6749#section-2.3.1) using the HTTP Basic Authentication scheme (`Authorization` header with `Basic base64encoded[client_id:client_secret]`). The default is `false`
``` ```
const ui = SwaggerUIBundle({...}) const ui = SwaggerUIBundle({...})
@@ -135,6 +138,7 @@ urls.primaryName | When using `urls`, you can use this subparameter. If the valu
spec | A JSON object describing the OpenAPI Specification. When used, the `url` parameter will not be parsed. This is useful for testing manually-generated specifications without hosting them. spec | A JSON object describing the OpenAPI Specification. When used, the `url` parameter will not be parsed. This is useful for testing manually-generated specifications without hosting them.
validatorUrl | By default, Swagger-UI attempts to validate specs against swagger.io's online validator. You can use this parameter to set a different validator URL, for example for locally deployed validators ([Validator Badge](https://github.com/swagger-api/validator-badge)). Setting it to `null` will disable validation. validatorUrl | By default, Swagger-UI attempts to validate specs against swagger.io's online validator. You can use this parameter to set a different validator URL, for example for locally deployed validators ([Validator Badge](https://github.com/swagger-api/validator-badge)). Setting it to `null` will disable validation.
dom_id | The id of a dom element inside which SwaggerUi will put the user interface for swagger. dom_id | The id of a dom element inside which SwaggerUi will put the user interface for swagger.
domNode | The HTML DOM element inside which SwaggerUi will put the user interface for swagger. Overrides `dom_id`.
oauth2RedirectUrl | OAuth redirect URL oauth2RedirectUrl | OAuth redirect URL
tagsSorter | Apply a sort to the tag list of each API. It can be 'alpha' (sort by paths alphanumerically) or a function (see [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) to learn how to write a sort function). Two tag name strings are passed to the sorter for each pass. Default is the order determined by Swagger-UI. tagsSorter | Apply a sort to the tag list of each API. It can be 'alpha' (sort by paths alphanumerically) or a function (see [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) to learn how to write a sort function). Two tag name strings are passed to the sorter for each pass. Default is the order determined by Swagger-UI.
operationsSorter | Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically), 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works). Default is the order returned by the server unchanged. operationsSorter | Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically), 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works). Default is the order returned by the server unchanged.

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAsyKA;;;;;;AAktEA;;;;;;;;;;;;;;;;;;;;;;;;;;AAkqTA;;;;;;;;;;;;;;AAs8JA;;;;;;;;;AA4tnBA;;;;;AAmpQA;;;;;;AA+mXA","sourceRoot":""} {"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAoyKA;;;;;;AAy+EA;;;;;;;;;;;;;;;;;;;;;;;;;;AAw1TA;;;;;;;;;;;;;;AAs8JA;;;;;;;;;AAy6oBA;;;;;AAqqQA;AAm4DA;;;;;;AAo4YA;;;;;;AA8jaA;AAumvBA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AA40CA;;;;;;AAqlFA","sourceRoot":""} {"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AA00CA;;;;;;AAmlFA","sourceRoot":""}

5
dist/swagger-ui.css vendored

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"swagger-ui.css","sources":[],"mappings":";;;","sourceRoot":""} {"version":3,"file":"swagger-ui.css","sources":[],"mappings":"","sourceRoot":""}

4
dist/swagger-ui.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AAk/aA","sourceRoot":""} {"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AAyvcA","sourceRoot":""}

View File

@@ -1,6 +1,6 @@
{ {
"name": "swagger-ui", "name": "swagger-ui",
"version": "3.0.19", "version": "3.1.2",
"main": "dist/swagger-ui.js", "main": "dist/swagger-ui.js",
"repository": "git@github.com:swagger-api/swagger-ui.git", "repository": "git@github.com:swagger-api/swagger-ui.git",
"contributors": [ "contributors": [
@@ -43,7 +43,6 @@
"ieee754": "^1.1.8", "ieee754": "^1.1.8",
"immutable": "^3.x.x", "immutable": "^3.x.x",
"js-yaml": "^3.5.5", "js-yaml": "^3.5.5",
"less": "2.7.1",
"lodash": "4.17.2", "lodash": "4.17.2",
"matcher": "^0.1.2", "matcher": "^0.1.2",
"memoizee": "0.4.1", "memoizee": "0.4.1",
@@ -58,6 +57,7 @@
"react-height": "^2.0.0", "react-height": "^2.0.0",
"react-hot-loader": "1.3.1", "react-hot-loader": "1.3.1",
"react-immutable-proptypes": "2.1.0", "react-immutable-proptypes": "2.1.0",
"react-markdown": "^2.5.0",
"react-motion": "0.4.4", "react-motion": "0.4.4",
"react-object-inspector": "0.2.1", "react-object-inspector": "0.2.1",
"react-redux": "^4.x.x", "react-redux": "^4.x.x",
@@ -71,7 +71,7 @@
"scroll-to-element": "^2.0.0", "scroll-to-element": "^2.0.0",
"serialize-error": "2.0.0", "serialize-error": "2.0.0",
"shallowequal": "0.2.2", "shallowequal": "0.2.2",
"swagger-client": "3.0.18", "swagger-client": "3.0.19",
"url-parse": "^1.1.8", "url-parse": "^1.1.8",
"whatwg-fetch": "0.11.1", "whatwg-fetch": "0.11.1",
"worker-loader": "^0.7.1", "worker-loader": "^0.7.1",

View File

@@ -15,7 +15,7 @@ export default class ArrayModel extends Component {
} }
render(){ render(){
let { getComponent, required, schema, depth, expandDepth, name } = this.props let { getComponent, schema, depth, expandDepth, name } = this.props
let items = schema.get("items") let items = schema.get("items")
let title = schema.get("title") || name let title = schema.get("title") || name
let properties = schema.filter( ( v, key) => ["type", "items", "$$ref"].indexOf(key) === -1 ) let properties = schema.filter( ( v, key) => ["type", "items", "$$ref"].indexOf(key) === -1 )
@@ -31,7 +31,7 @@ export default class ArrayModel extends Component {
return <span className="model"> return <span className="model">
<ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent="[...]"> <ModelCollapse title={titleEl} collapsed={ depth > expandDepth } collapsedContent="[...]">
[ [
<span><Model { ...this.props } schema={ items } required={ false }/></span> <span><Model { ...this.props } schema={ items } required={ false } depth={ depth + 1 } /></span>
] ]
{ {
properties.size ? <span> properties.size ? <span>
@@ -41,7 +41,6 @@ export default class ArrayModel extends Component {
: null : null
} }
</ModelCollapse> </ModelCollapse>
{ required && <span style={{ color: "red" }}>*</span>}
</span> </span>
} }
} }

View File

@@ -8,7 +8,7 @@ const noop = ()=>{}
export default class ContentType extends React.Component { export default class ContentType extends React.Component {
static propTypes = { static propTypes = {
contentTypes: PropTypes.oneOfType([ImPropTypes.list, ImPropTypes.set]), contentTypes: PropTypes.oneOfType([ImPropTypes.list, ImPropTypes.set, ImPropTypes.seq]),
value: PropTypes.string, value: PropTypes.string,
onChange: PropTypes.func, onChange: PropTypes.func,
className: PropTypes.string className: PropTypes.string
@@ -22,8 +22,10 @@ export default class ContentType extends React.Component {
componentDidMount() { componentDidMount() {
// Needed to populate the form, initially // Needed to populate the form, initially
if(this.props.contentTypes) {
this.props.onChange(this.props.contentTypes.first()) this.props.onChange(this.props.contentTypes.first())
} }
}
onChangeWrapper = e => this.props.onChange(e.target.value) onChangeWrapper = e => this.props.onChange(e.target.value)

View File

@@ -88,12 +88,13 @@ export default class Info extends React.Component {
const { url:externalDocsUrl, description:externalDocsDescription } = (externalDocs || fromJS({})).toJS() const { url:externalDocsUrl, description:externalDocsDescription } = (externalDocs || fromJS({})).toJS()
const Markdown = getComponent("Markdown") const Markdown = getComponent("Markdown")
const VersionStamp = getComponent("VersionStamp")
return ( return (
<div className="info"> <div className="info">
<hgroup className="main"> <hgroup className="main">
<h2 className="title" >{ title } <h2 className="title" >{ title }
{ version && <small><pre className="version"> { version } </pre></small> } { version && <VersionStamp version={version}></VersionStamp> }
</h2> </h2>
{ host || basePath ? <Path host={ host } basePath={ basePath } /> : null } { host || basePath ? <Path host={ host } basePath={ basePath } /> : null }
{ url && <a target="_blank" href={ url }><span className="url"> { url } </span></a> } { url && <a target="_blank" href={ url }><span className="url"> { url } </span></a> }

View File

@@ -35,9 +35,9 @@ export default class ModelExample extends React.Component {
<li className={ "tabitem" + ( isExecute || this.state.activeTab === "example" ? " active" : "") }> <li className={ "tabitem" + ( isExecute || this.state.activeTab === "example" ? " active" : "") }>
<a className="tablinks" data-name="example" onClick={ this.activeTab }>Example Value</a> <a className="tablinks" data-name="example" onClick={ this.activeTab }>Example Value</a>
</li> </li>
<li className={ "tabitem" + ( !isExecute && this.state.activeTab === "model" ? " active" : "") }> { schema ? <li className={ "tabitem" + ( !isExecute && this.state.activeTab === "model" ? " active" : "") }>
<a className={ "tablinks" + ( isExecute ? " inactive" : "" )} data-name="model" onClick={ this.activeTab }>Model</a> <a className={ "tablinks" + ( isExecute ? " inactive" : "" )} data-name="model" onClick={ this.activeTab }>Model</a>
</li> </li> : null }
</ul> </ul>
<div> <div>
{ {

View File

@@ -17,6 +17,9 @@ export default class Model extends Component {
if ( ref.indexOf("#/definitions/") !== -1 ) { if ( ref.indexOf("#/definitions/") !== -1 ) {
return ref.replace(/^.*#\/definitions\//, "") return ref.replace(/^.*#\/definitions\//, "")
} }
if ( ref.indexOf("#/components/schemas/") !== -1 ) {
return ref.replace("#/components/schemas/", "")
}
} }
getRefSchema =( model )=> { getRefSchema =( model )=> {
@@ -26,7 +29,7 @@ export default class Model extends Component {
} }
render () { render () {
let { schema, getComponent, required, name, isRef } = this.props let { getComponent, specSelectors, schema, required, name, isRef } = this.props
let ObjectModel = getComponent("ObjectModel") let ObjectModel = getComponent("ObjectModel")
let ArrayModel = getComponent("ArrayModel") let ArrayModel = getComponent("ArrayModel")
let PrimitiveModel = getComponent("PrimitiveModel") let PrimitiveModel = getComponent("PrimitiveModel")
@@ -34,6 +37,8 @@ export default class Model extends Component {
let modelName = $$ref && this.getModelName( $$ref ) let modelName = $$ref && this.getModelName( $$ref )
let modelSchema, type let modelSchema, type
const deprecated = specSelectors.isOAS3() && schema.get("deprecated")
if ( schema && (schema.get("type") || schema.get("properties")) ) { if ( schema && (schema.get("type") || schema.get("properties")) ) {
modelSchema = schema modelSchema = schema
} else if ( $$ref ) { } else if ( $$ref ) {
@@ -47,17 +52,30 @@ export default class Model extends Component {
switch(type) { switch(type) {
case "object": case "object":
return <ObjectModel className="object" { ...this.props } schema={ modelSchema } return <ObjectModel
name={ name || modelName } required={ required } className="object" { ...this.props }
schema={ modelSchema }
name={ modelName || name }
deprecated={deprecated}
isRef={ isRef!== undefined ? isRef : !!$$ref } /> isRef={ isRef!== undefined ? isRef : !!$$ref } />
case "array": case "array":
return <ArrayModel className="array" { ...this.props } schema={ modelSchema } required={ required } /> return <ArrayModel
className="array" { ...this.props }
schema={ modelSchema }
name={ modelName || name }
deprecated={deprecated}
required={ required } />
case "string": case "string":
case "number": case "number":
case "integer": case "integer":
case "boolean": case "boolean":
default: default:
return <PrimitiveModel getComponent={ getComponent } schema={ modelSchema } required={ required }/> return <PrimitiveModel
} { ...this.props }
getComponent={ getComponent }
schema={ modelSchema }
name={ modelName || name }
deprecated={deprecated}
required={ required }/> }
} }
} }

View File

@@ -17,8 +17,9 @@ export default class ObjectModel extends Component {
} }
render(){ render(){
let { schema, name, isRef, getComponent, depth, ...props } = this.props let { schema, name, isRef, getComponent, depth, expandDepth, specSelectors, ...props } = this.props
let { expandDepth } = this.props let { isOAS3 } = specSelectors
let description = schema.get("description") let description = schema.get("description")
let properties = schema.get("properties") let properties = schema.get("properties")
let additionalProperties = schema.get("additionalProperties") let additionalProperties = schema.get("additionalProperties")
@@ -30,7 +31,10 @@ export default class ObjectModel extends Component {
const Model = getComponent("Model") const Model = getComponent("Model")
const ModelCollapse = getComponent("ModelCollapse") const ModelCollapse = getComponent("ModelCollapse")
const JumpToPathSection = ({ name }) => <span className="model-jump-to-path"><JumpToPath path={`definitions.${name}`} /></span> const JumpToPathSection = ({ name }) => {
const path = isOAS3 && isOAS3() ? `components.schemas.${name}` : `definitions.${name}`
return <span className="model-jump-to-path"><JumpToPath path={path} /></span>
}
const collapsedContent = (<span> const collapsedContent = (<span>
<span>{ braceOpen }</span>...<span>{ braceClose }</span> <span>{ braceOpen }</span>...<span>{ braceClose }</span>
{ {
@@ -38,6 +42,10 @@ export default class ObjectModel extends Component {
} }
</span>) </span>)
const anyOf = specSelectors.isOAS3() ? schema.get("anyOf") : null
const oneOf = specSelectors.isOAS3() ? schema.get("oneOf") : null
const not = specSelectors.isOAS3() ? schema.get("not") : null
const titleEl = title && <span className="model-title"> const titleEl = title && <span className="model-title">
{ isRef && schema.get("$$ref") && <span className="model-hint">{ schema.get("$$ref") }</span> } { isRef && schema.get("$$ref") && <span className="model-hint">{ schema.get("$$ref") }</span> }
<span className="model-title__text">{ title }</span> <span className="model-title__text">{ title }</span>
@@ -51,7 +59,7 @@ export default class ObjectModel extends Component {
} }
<span className="inner-object"> <span className="inner-object">
{ {
<table className="model" style={{ marginLeft: "2em" }}><tbody> <table className="model"><tbody>
{ {
!description ? null : <tr style={{ color: "#999", fontStyle: "italic" }}> !description ? null : <tr style={{ color: "#999", fontStyle: "italic" }}>
<td>description:</td> <td>description:</td>
@@ -95,6 +103,48 @@ export default class ObjectModel extends Component {
</td> </td>
</tr> </tr>
} }
{
!anyOf ? null
: <tr>
<td>{ "anyOf ->" }</td>
<td>
{anyOf.map((schema, k) => {
return <div key={k}><Model { ...props } required={ false }
getComponent={ getComponent }
schema={ schema }
depth={ depth + 1 } /></div>
})}
</td>
</tr>
}
{
!oneOf ? null
: <tr>
<td>{ "oneOf ->" }</td>
<td>
{oneOf.map((schema, k) => {
return <div key={k}><Model { ...props } required={ false }
getComponent={ getComponent }
schema={ schema }
depth={ depth + 1 } /></div>
})}
</td>
</tr>
}
{
!not ? null
: <tr>
<td>{ "not ->" }</td>
<td>
{not.map((schema, k) => {
return <div key={k}><Model { ...props } required={ false }
getComponent={ getComponent }
schema={ schema }
depth={ depth + 1 } /></div>
})}
</td>
</tr>
}
</tbody></table> </tbody></table>
} }
</span> </span>

View File

@@ -186,7 +186,7 @@ export default class Operation extends PureComponent {
} }
</div> </div>
<Collapse isOpened={shown} animated> <Collapse isOpened={shown}>
<div className="opblock-body"> <div className="opblock-body">
{ deprecated && <h4 className="opblock-title_normal"> Warning: Deprecated</h4>} { deprecated && <h4 className="opblock-title_normal"> Warning: Deprecated</h4>}
{ description && { description &&
@@ -210,6 +210,7 @@ export default class Operation extends PureComponent {
} }
<Parameters <Parameters
parameters={parameters} parameters={parameters}
operation={operation}
onChangeKey={onChangeKey} onChangeKey={onChangeKey}
onTryoutClick = { this.onTryoutClick } onTryoutClick = { this.onTryoutClick }
onCancelClick = { this.onCancelClick } onCancelClick = { this.onCancelClick }

View File

@@ -66,6 +66,8 @@ export default class Operations extends React.Component {
taggedOps.map( (tagObj, tag) => { taggedOps.map( (tagObj, tag) => {
let operations = tagObj.get("operations") let operations = tagObj.get("operations")
let tagDescription = tagObj.getIn(["tagDetails", "description"], null) let tagDescription = tagObj.getIn(["tagDetails", "description"], null)
let tagExternalDocsDescription = tagObj.getIn(["tagDetails", "externalDocs", "description"])
let tagExternalDocsUrl = tagObj.getIn(["tagDetails", "externalDocs", "url"])
let isShownKey = ["operations-tag", tag] let isShownKey = ["operations-tag", tag]
let showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list") let showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list")
@@ -89,6 +91,22 @@ export default class Operations extends React.Component {
</small> </small>
} }
<div>
{ !tagExternalDocsDescription ? null :
<small>
{ tagExternalDocsDescription }
{ tagExternalDocsUrl ? ": " : null }
{ tagExternalDocsUrl ?
<a
href={tagExternalDocsUrl}
onClick={(e) => e.stopPropagation()}
target={"_blank"}
>{tagExternalDocsUrl}</a> : null
}
</small>
}
</div>
<button className="expand-operation" title="Expand operation" onClick={() => layoutActions.show(isShownKey, !showTag)}> <button className="expand-operation" title="Expand operation" onClick={() => layoutActions.show(isShownKey, !showTag)}>
<svg className="arrow" width="20" height="20"> <svg className="arrow" width="20" height="20">
<use xlinkHref={showTag ? "#large-arrow-down" : "#large-arrow"} /> <use xlinkHref={showTag ? "#large-arrow-down" : "#large-arrow"} />

View File

@@ -58,6 +58,8 @@ export default class ParameterRow extends Component {
render() { render() {
let {param, onChange, getComponent, isExecute, fn, onChangeConsumes, specSelectors, pathMethod} = this.props let {param, onChange, getComponent, isExecute, fn, onChangeConsumes, specSelectors, pathMethod} = this.props
let { isOAS3 } = specSelectors
// const onChangeWrapper = (value) => onChange(param, value) // const onChangeWrapper = (value) => onChange(param, value)
const JsonSchemaForm = getComponent("JsonSchemaForm") const JsonSchemaForm = getComponent("JsonSchemaForm")
const ParamBody = getComponent("ParamBody") const ParamBody = getComponent("ParamBody")
@@ -95,6 +97,9 @@ export default class ParameterRow extends Component {
{ !required ? null : <span style={{color: "red"}}>&nbsp;*</span> } { !required ? null : <span style={{color: "red"}}>&nbsp;*</span> }
</div> </div>
<div className="parameter__type">{ param.get("type") } { itemType && `[${itemType}]` }</div> <div className="parameter__type">{ param.get("type") } { itemType && `[${itemType}]` }</div>
<div className="parameter__deprecated">
{ isOAS3 && isOAS3() && param.get("deprecated") ? "deprecated": null }
</div>
<div className="parameter__in">({ param.get("in") })</div> <div className="parameter__in">({ param.get("in") })</div>
</td> </td>

View File

@@ -72,7 +72,9 @@ export default class Parameters extends Component {
return ( return (
<div className="opblock-section"> <div className="opblock-section">
<div className="opblock-section-header"> <div className="opblock-section-header">
<div className="tab-header">
<h4 className="opblock-title">Parameters</h4> <h4 className="opblock-title">Parameters</h4>
</div>
{ allowTryItOut ? ( { allowTryItOut ? (
<TryItOutButton enabled={ tryItOutEnabled } onCancelClick={ onCancelClick } onTryoutClick={ onTryoutClick } /> <TryItOutButton enabled={ tryItOutEnabled } onCancelClick={ onCancelClick } onTryoutClick={ onTryoutClick } />
) : null } ) : null }

View File

@@ -7,11 +7,12 @@ export default class Primitive extends Component {
static propTypes = { static propTypes = {
schema: PropTypes.object.isRequired, schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
required: PropTypes.bool name: PropTypes.string,
depth: PropTypes.number
} }
render(){ render(){
let { schema, getComponent, required } = this.props let { schema, getComponent, name, depth } = this.props
if(!schema || !schema.get) { if(!schema || !schema.get) {
// don't render if schema isn't correctly formed // don't render if schema isn't correctly formed
@@ -24,12 +25,13 @@ export default class Primitive extends Component {
let enumArray = schema.get("enum") let enumArray = schema.get("enum")
let description = schema.get("description") let description = schema.get("description")
let properties = schema.filter( ( v, key) => ["enum", "type", "format", "description", "$$ref"].indexOf(key) === -1 ) let properties = schema.filter( ( v, key) => ["enum", "type", "format", "description", "$$ref"].indexOf(key) === -1 )
let style = required ? { fontWeight: "bold" } : {}
const Markdown = getComponent("Markdown") const Markdown = getComponent("Markdown")
const EnumModel = getComponent("EnumModel") const EnumModel = getComponent("EnumModel")
return <span className="prop"> return <span className="model">
<span className="prop-type" style={ style }>{ type }</span> { required && <span style={{ color: "red" }}>*</span>} <span className="prop">
{ name && <span className={`${depth === 1 && "model-title"} prop-name`}>{ name }</span> }
<span className="prop-type">{ type }</span>
{ format && <span className="prop-format">(${format})</span>} { format && <span className="prop-format">(${format})</span>}
{ {
properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }> properties.size ? properties.entrySeq().map( ( [ key, v ] ) => <span key={`${key}-${v}`} style={ propStyle }>
@@ -51,5 +53,6 @@ export default class Primitive extends Component {
enumArray && <EnumModel value={ enumArray } getComponent={ getComponent } /> enumArray && <EnumModel value={ enumArray } getComponent={ getComponent } />
} }
</span> </span>
</span>
} }
} }

View File

@@ -3,15 +3,8 @@ import PropTypes from "prop-types"
import Remarkable from "react-remarkable" import Remarkable from "react-remarkable"
import sanitize from "sanitize-html" import sanitize from "sanitize-html"
const sanitizeOptions = {
textFilter: function(text) {
return text
.replace(/&quot;/g, "\"")
}
}
function Markdown({ source }) { function Markdown({ source }) {
const sanitized = sanitize(source, sanitizeOptions) const sanitized = sanitizer(source)
// sometimes the sanitizer returns "undefined" as a string // sometimes the sanitizer returns "undefined" as a string
if(!source || !sanitized || sanitized === "undefined") { if(!source || !sanitized || sanitized === "undefined") {
@@ -31,3 +24,14 @@ Markdown.propTypes = {
} }
export default Markdown export default Markdown
const sanitizeOptions = {
textFilter: function(text) {
return text
.replace(/&quot;/g, "\"")
}
}
export function sanitizer(str) {
return sanitize(str, sanitizeOptions)
}

View File

@@ -1,6 +1,6 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { fromJS } from "immutable" import { fromJS, Seq } from "immutable"
import { getSampleSchema } from "core/utils" import { getSampleSchema } from "core/utils"
const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => { const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => {
@@ -31,6 +31,13 @@ const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => {
} }
export default class Response extends React.Component { export default class Response extends React.Component {
constructor(props, context) {
super(props, context)
this.state = {
responseContentType: ""
}
}
static propTypes = { static propTypes = {
code: PropTypes.string.isRequired, code: PropTypes.string.isRequired,
@@ -59,16 +66,29 @@ export default class Response extends React.Component {
} = this.props } = this.props
let { inferSchema } = fn let { inferSchema } = fn
let { isOAS3 } = specSelectors
let schema = inferSchema(response.toJS())
let headers = response.get("headers") let headers = response.get("headers")
let examples = response.get("examples") let examples = response.get("examples")
let links = response.get("links")
const Headers = getComponent("headers") const Headers = getComponent("headers")
const HighlightCode = getComponent("highlightCode") const HighlightCode = getComponent("highlightCode")
const ModelExample = getComponent("modelExample") const ModelExample = getComponent("modelExample")
const Markdown = getComponent( "Markdown" ) const Markdown = getComponent( "Markdown" )
const OperationLink = getComponent("operationLink")
const ContentType = getComponent("contentType")
let sampleResponse = schema ? getSampleSchema(schema, contentType, { includeReadOnly: true }) : null var sampleResponse
var schema
if(isOAS3()) {
let oas3SchemaForContentType = response.getIn(["content", this.state.responseContentType, "schema"])
sampleResponse = oas3SchemaForContentType ? getSampleSchema(oas3SchemaForContentType.toJS(), this.state.responseContentType, { includeReadOnly: true }) : null
schema = oas3SchemaForContentType ? inferSchema(oas3SchemaForContentType.toJS()) : null
} else {
schema = inferSchema(response.toJS())
sampleResponse = schema ? getSampleSchema(schema, contentType, { includeReadOnly: true }) : null
}
let example = getExampleComponent( sampleResponse, examples, HighlightCode ) let example = getExampleComponent( sampleResponse, examples, HighlightCode )
return ( return (
@@ -82,6 +102,12 @@ export default class Response extends React.Component {
<Markdown source={ response.get( "description" ) } /> <Markdown source={ response.get( "description" ) } />
</div> </div>
{ isOAS3 ? <ContentType
value={this.state.responseContentType}
contentTypes={ response.get("content") ? response.get("content").keySeq() : Seq() }
onChange={(val) => this.setState({ responseContentType: val })}
className="response-content-type" /> : null }
{ example ? ( { example ? (
<ModelExample <ModelExample
getComponent={ getComponent } getComponent={ getComponent }
@@ -94,8 +120,15 @@ export default class Response extends React.Component {
<Headers headers={ headers }/> <Headers headers={ headers }/>
) : null} ) : null}
</td>
</td>
{specSelectors.isOAS3() ? <td>
{ links ?
links.toSeq().map((link, key) => {
return <OperationLink key={key} name={key} link={ link }/>
})
: <i>No links</i>}
</td> : null}
</tr> </tr>
) )
} }

View File

@@ -42,13 +42,13 @@ export default class Responses extends React.Component {
<div className="responses-wrapper"> <div className="responses-wrapper">
<div className="opblock-section-header"> <div className="opblock-section-header">
<h4>Responses</h4> <h4>Responses</h4>
<label> { specSelectors.isOAS3() ? null : <label>
<span>Response content type</span> <span>Response content type</span>
<ContentType value={producesValue} <ContentType value={producesValue}
onChange={this.onChangeProducesWrapper} onChange={this.onChangeProducesWrapper}
contentTypes={produces} contentTypes={produces}
className="execute-content-type"/> className="execute-content-type"/>
</label> </label> }
</div> </div>
<div className="responses-inner"> <div className="responses-inner">
{ {
@@ -68,6 +68,7 @@ export default class Responses extends React.Component {
<tr className="responses-header"> <tr className="responses-header">
<td className="col col_header response-col_status">Code</td> <td className="col col_header response-col_status">Code</td>
<td className="col col_header response-col_description">Description</td> <td className="col col_header response-col_description">Description</td>
{ specSelectors.isOAS3() ? <td className="col col_header response-col_description">Links</td> : null }
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@@ -0,0 +1,12 @@
import React from "react"
import PropTypes from "prop-types"
const VersionStamp = ({ version }) => {
return <small><pre className="version"> { version } </pre></small>
}
VersionStamp.propTypes = {
version: PropTypes.string.isRequired
}
export default VersionStamp

View File

@@ -4,34 +4,7 @@ import System from "core/system"
import win from "core/window" import win from "core/window"
import ApisPreset from "core/presets/apis" import ApisPreset from "core/presets/apis"
import * as AllPlugins from "core/plugins/all" import * as AllPlugins from "core/plugins/all"
import { parseSearch, filterConfigs } from "core/utils" import { parseSearch } from "core/utils"
const CONFIGS = [
"url",
"urls",
"urls.primaryName",
"spec",
"validatorUrl",
"onComplete",
"onFailure",
"authorizations",
"docExpansion",
"tagsSorter",
"maxDisplayedTags",
"filter",
"operationsSorter",
"supportedSubmitMethods",
"dom_id",
"defaultModelRendering",
"oauth2RedirectUrl",
"showRequestHeaders",
"custom",
"modelPropertyMacro",
"parameterMacro",
"displayOperationId",
"displayRequestDuration",
"deepLinking",
]
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
const { GIT_DIRTY, GIT_COMMIT, PACKAGE_VERSION, HOSTNAME, BUILD_TIME } = buildInfo const { GIT_DIRTY, GIT_COMMIT, PACKAGE_VERSION, HOSTNAME, BUILD_TIME } = buildInfo
@@ -50,6 +23,7 @@ module.exports = function SwaggerUI(opts) {
const defaults = { const defaults = {
// Some general settings, that we floated to the top // Some general settings, that we floated to the top
dom_id: null, dom_id: null,
domNode: null,
spec: {}, spec: {},
url: "", url: "",
urls: null, urls: null,
@@ -126,7 +100,13 @@ module.exports = function SwaggerUI(opts) {
let localConfig = system.specSelectors.getLocalConfig ? system.specSelectors.getLocalConfig() : {} let localConfig = system.specSelectors.getLocalConfig ? system.specSelectors.getLocalConfig() : {}
let mergedConfig = deepExtend({}, localConfig, constructorConfig, fetchedConfig || {}, queryConfig) let mergedConfig = deepExtend({}, localConfig, constructorConfig, fetchedConfig || {}, queryConfig)
store.setConfigs(filterConfigs(mergedConfig, CONFIGS))
// deep extend mangles domNode, we need to set it manually
if(opts.domNode) {
mergedConfig.domNode = opts.domNode
}
store.setConfigs(mergedConfig)
if (fetchedConfig !== null) { if (fetchedConfig !== null) {
if (!queryConfig.url && typeof mergedConfig.spec === "object" && Object.keys(mergedConfig.spec).length) { if (!queryConfig.url && typeof mergedConfig.spec === "object" && Object.keys(mergedConfig.spec).length) {
@@ -139,10 +119,13 @@ module.exports = function SwaggerUI(opts) {
} }
} }
if(mergedConfig.dom_id) { if(mergedConfig.domNode) {
system.render(mergedConfig.dom_id, "App") system.render(mergedConfig.domNode, "App")
} else if(mergedConfig.dom_id) {
let domNode = document.querySelector(mergedConfig.dom_id)
system.render(domNode, "App")
} else { } else {
console.error("Skipped rendering: no `dom_id` was specified") console.error("Skipped rendering: no `dom_id` or `domNode` was specified")
} }
return system return system

View File

@@ -68,11 +68,21 @@ export default function authorize ( { auth, authActions, errActions, configs, au
// pass action authorizeOauth2 and authentication data through window // pass action authorizeOauth2 and authentication data through window
// to authorize with oauth2 // to authorize with oauth2
let callback
if (flow === "implicit") {
callback = authActions.preAuthorizeImplicit
} else if (authConfigs.useBasicAuthenticationWithAccessCodeGrant) {
callback = authActions.authorizeAccessCodeWithBasicAuthentication
} else {
callback = authActions.authorizeAccessCodeWithFormParams
}
win.swaggerUIRedirectOauth2 = { win.swaggerUIRedirectOauth2 = {
auth: auth, auth: auth,
state: state, state: state,
redirectUrl: redirectUrl, redirectUrl: redirectUrl,
callback: flow === "implicit" ? authActions.preAuthorizeImplicit : authActions.authorizeAccessCode, callback: callback,
errCb: errActions.newAuthErr errCb: errActions.newAuthErr
} }

View File

@@ -111,7 +111,7 @@ export const authorizeApplication = ( auth ) => ( { authActions } ) => {
return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth, headers }) return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth, headers })
} }
export const authorizeAccessCode = ( { auth, redirectUrl } ) => ( { authActions } ) => { export const authorizeAccessCodeWithFormParams = ( { auth, redirectUrl } ) => ( { authActions } ) => {
let { schema, name, clientId, clientSecret } = auth let { schema, name, clientId, clientSecret } = auth
let form = { let form = {
grant_type: "authorization_code", grant_type: "authorization_code",
@@ -124,6 +124,21 @@ export const authorizeAccessCode = ( { auth, redirectUrl } ) => ( { authActions
return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth}) return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth})
} }
export const authorizeAccessCodeWithBasicAuthentication = ( { auth, redirectUrl } ) => ( { authActions } ) => {
let { schema, name, clientId, clientSecret } = auth
let headers = {
Authorization: "Basic " + btoa(clientId + ":" + clientSecret)
}
let form = {
grant_type: "authorization_code",
code: auth.code,
client_id: clientId,
redirect_uri: redirectUrl
}
return authActions.authorizeRequest({body: buildFormData(form), name, url: schema.get("tokenUrl"), auth, headers})
}
export const authorizeRequest = ( data ) => ( { fn, authActions, errActions, authSelectors } ) => { export const authorizeRequest = ( data ) => ( { fn, authActions, errActions, authSelectors } ) => {
let { body, query={}, headers={}, name, url, auth } = data let { body, query={}, headers={}, name, url, auth } = data
let { additionalQueryStringParams } = authSelectors.getConfigs() || {} let { additionalQueryStringParams } = authSelectors.getConfigs() || {}
@@ -135,7 +150,6 @@ export const authorizeRequest = ( data ) => ( { fn, authActions, errActions, aut
let _headers = Object.assign({ let _headers = Object.assign({
"Accept":"application/json, text/plain, */*", "Accept":"application/json, text/plain, */*",
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/x-www-form-urlencoded" "Content-Type": "application/x-www-form-urlencoded"
}, headers) }, headers)

View File

@@ -0,0 +1,49 @@
import React from "react"
import PropTypes from "prop-types"
const Callbacks = (props) => {
let { callbacks, getComponent } = props
// const Markdown = getComponent("Markdown")
const Operation = getComponent("operation", true)
if(!callbacks) {
return <span>No callbacks</span>
}
let callbackElements = callbacks.map((callback, callbackName) => {
return <div key={callbackName}>
<h2>{callbackName}</h2>
{ callback.map((pathItem, pathItemName) => {
return <div key={pathItemName}>
{ pathItem.map((operation, method) => {
return <Operation
operation={operation}
key={method}
method={method}
isShownKey={["callbacks", operation.get("id"), callbackName]}
path={pathItemName}
allowTryItOut={false}
{...props}></Operation>
// return <pre>{JSON.stringify(operation)}</pre>
}) }
</div>
}) }
</div>
// return <div>
// <h2>{name}</h2>
// {callback.description && <Markdown source={callback.description}/>}
// <pre>{JSON.stringify(callback)}</pre>
// </div>
})
return <div>
{callbackElements}
</div>
}
Callbacks.propTypes = {
getComponent: PropTypes.func.isRequired,
callbacks: PropTypes.array.isRequired
}
export default Callbacks

View File

@@ -0,0 +1,9 @@
import Callbacks from "./callbacks"
import RequestBody from "./request-body"
import OperationLink from "./operation-link.jsx"
export default {
Callbacks,
RequestBody,
operationLink: OperationLink
}

View File

@@ -0,0 +1,37 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
class OperationLink extends Component {
render() {
const { link, name } = this.props
let targetOp = link.get("operationId") || link.get("operationRef")
let parameters = link.get("parameters") && link.get("parameters").toJS()
let description = link.get("description")
return <span>
<div style={{ padding: "5px 2px" }}>{name}{description ? `: ${description}` : ""}</div>
<pre>
Operation `{targetOp}`<br /><br />
Parameters {padString(0, JSON.stringify(parameters, null, 2)) || "{}"}<br />
</pre>
</span>
}
}
function padString(n, string) {
if(typeof string !== "string") { return "" }
return string
.split("\n")
.map((line, i) => i > 0 ? Array(n + 1).join(" ") + line : line)
.join("\n")
}
OperationLink.propTypes = {
link: ImPropTypes.orderedMap.isRequired,
name: PropTypes.String
}
export default OperationLink

View File

@@ -0,0 +1,42 @@
import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { OrderedMap } from "immutable"
import { getSampleSchema } from "core/utils"
const RequestBody = ({ requestBody, getComponent, specSelectors, contentType }) => {
const Markdown = getComponent("Markdown")
const ModelExample = getComponent("modelExample")
const HighlightCode = getComponent("highlightCode")
const requestBodyDescription = (requestBody && requestBody.get("description")) || null
const requestBodyContent = (requestBody && requestBody.get("content")) || new OrderedMap()
contentType = contentType || requestBodyContent.keySeq().first()
const mediaTypeValue = requestBodyContent.get(contentType)
const sampleSchema = getSampleSchema(mediaTypeValue.get("schema").toJS(), contentType)
return <div>
{ requestBodyDescription &&
<Markdown source={requestBodyDescription} />
}
<ModelExample
getComponent={ getComponent }
specSelectors={ specSelectors }
expandDepth={1}
schema={mediaTypeValue.get("schema")}
example={<HighlightCode value={sampleSchema} />}
/>
</div>
}
RequestBody.propTypes = {
requestBody: ImPropTypes.orderedMap.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
contentType: PropTypes.string.isRequired
}
export default RequestBody

View File

@@ -0,0 +1,36 @@
import React from "react"
export function isOAS3(jsSpec) {
const oasVersion = jsSpec.get("openapi")
if(!oasVersion) {
return false
}
return oasVersion.startsWith("3.0.0")
}
export function isSwagger2(jsSpec) {
const swaggerVersion = jsSpec.get("swagger")
if(!swaggerVersion) {
return false
}
return swaggerVersion.startsWith("2")
}
export function OAS3ComponentWrapFactory(Component) {
return (Ori, system) => (props) => {
if(system && system.specSelectors && system.specSelectors.specJson) {
const spec = system.specSelectors.specJson()
if(isOAS3(spec)) {
return <Component {...props} {...system} Ori={Ori}></Component>
} else {
return <Ori {...props}></Ori>
}
} else {
console.warn("OAS3 wrapper: couldn't get spec")
return null
}
}
}

View File

@@ -0,0 +1,17 @@
// import reducers from "./reducers"
// import * as actions from "./actions"
import * as wrapSelectors from "./wrap-selectors"
import components from "./components"
import wrapComponents from "./wrap-components"
export default function() {
return {
components,
wrapComponents,
statePlugins: {
spec: {
wrapSelectors
}
}
}
}

View File

@@ -0,0 +1,15 @@
import Markdown from "./markdown"
import parameters from "./parameters"
import VersionStamp from "./version-stamp"
import OnlineValidatorBadge from "./online-validator-badge"
import Model from "./model"
import TryItOutButton from "./try-it-out-button"
export default {
Markdown,
parameters,
VersionStamp,
model: Model,
onlineValidatorBadge: OnlineValidatorBadge,
TryItOutButton
}

View File

@@ -0,0 +1,11 @@
import React from "react"
import ReactMarkdown from "react-markdown"
import { OAS3ComponentWrapFactory } from "../helpers"
import { sanitizer } from "core/components/providers/markdown"
export default OAS3ComponentWrapFactory(({ source }) => { return source ? (
<ReactMarkdown
source={sanitizer(source)}
className={"renderedMarkdown"}
/>
) : null})

View File

@@ -0,0 +1,36 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
import { OAS3ComponentWrapFactory } from "../helpers"
import { Model } from "core/components/model"
class ModelComponent extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
name: PropTypes.string,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
expandDepth: PropTypes.number
}
render(){
let { schema } = this.props
let classes = ["model-box"]
let isDeprecated = schema.get("deprecated") === true
let message = null
if(isDeprecated) {
classes.push("deprecated")
message = <span className="model-deprecated-warning">Deprecated:</span>
}
return <div className={classes.join(" ")}>
{message}
<Model { ...this.props }
depth={ 1 }
expandDepth={ this.props.expandDepth || 0 }
/>
</div>
}
}
export default OAS3ComponentWrapFactory(ModelComponent)

View File

@@ -0,0 +1,5 @@
import { OAS3ComponentWrapFactory } from "../helpers"
// We're disabling the Online Validator Badge until the online validator
// can handle OAS3 specs.
export default OAS3ComponentWrapFactory(() => null)

View File

@@ -0,0 +1,181 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
import Im, { Map } from "immutable"
import ImPropTypes from "react-immutable-proptypes"
import { OAS3ComponentWrapFactory } from "../helpers"
// More readable, just iterate over maps, only
const eachMap = (iterable, fn) => iterable.valueSeq().filter(Im.Map.isMap).map(fn)
class Parameters extends Component {
constructor(props) {
super(props)
this.state = {
callbackVisible: false,
parametersVisible: true,
requestBodyContentType: ""
}
}
static propTypes = {
parameters: ImPropTypes.list.isRequired,
specActions: PropTypes.object.isRequired,
operation: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired,
tryItOutEnabled: PropTypes.bool,
allowTryItOut: PropTypes.bool,
onTryoutClick: PropTypes.func,
onCancelClick: PropTypes.func,
onChangeKey: PropTypes.array,
pathMethod: PropTypes.array.isRequired
}
static defaultProps = {
onTryoutClick: Function.prototype,
onCancelClick: Function.prototype,
tryItOutEnabled: false,
allowTryItOut: true,
onChangeKey: [],
}
onChange = ( param, value, isXml ) => {
let {
specActions: { changeParam },
onChangeKey,
} = this.props
changeParam( onChangeKey, param.get("name"), value, isXml)
}
onChangeConsumesWrapper = ( val ) => {
let {
specActions: { changeConsumesValue },
onChangeKey
} = this.props
changeConsumesValue(onChangeKey, val)
}
toggleTab = (tab) => {
if(tab === "parameters"){
return this.setState({
parametersVisible: true,
callbackVisible: false
})
}else if(tab === "callbacks"){
return this.setState({
callbackVisible: true,
parametersVisible: false
})
}
}
render(){
let {
onTryoutClick,
onCancelClick,
parameters,
allowTryItOut,
tryItOutEnabled,
fn,
getComponent,
specSelectors,
pathMethod,
operation
} = this.props
const ParameterRow = getComponent("parameterRow")
const TryItOutButton = getComponent("TryItOutButton")
const ContentType = getComponent("contentType")
const Callbacks = getComponent("Callbacks", true)
const RequestBody = getComponent("RequestBody", true)
const isExecute = tryItOutEnabled && allowTryItOut
const { isOAS3 } = specSelectors
const requestBody = operation.get("requestBody")
return (
<div className="opblock-section">
<div className="opblock-section-header">
<div className="tab-header">
<div onClick={() => this.toggleTab("parameters")} className={`tab-item ${this.state.parametersVisible && "active"}`}>
<h4 className="opblock-title"><span>Parameters</span></h4>
</div>
{ operation.get("callbacks") ?
(
<div onClick={() => this.toggleTab("callbacks")} className={`tab-item ${this.state.callbackVisible && "active"}`}>
<h4 className="opblock-title"><span>Callbacks</span></h4>
</div>
) : null
}
</div>
{ allowTryItOut ? (
<TryItOutButton enabled={ tryItOutEnabled } onCancelClick={ onCancelClick } onTryoutClick={ onTryoutClick } />
) : null }
</div>
{this.state.parametersVisible ? <div className="parameters-container">
{ !parameters.count() ? <div className="opblock-description-wrapper"><p>No parameters</p></div> :
<div className="table-container">
<table className="parameters">
<thead>
<tr>
<th className="col col_header parameters-col_name">Name</th>
<th className="col col_header parameters-col_description">Description</th>
</tr>
</thead>
<tbody>
{
eachMap(parameters, (parameter) => (
<ParameterRow fn={ fn }
getComponent={ getComponent }
param={ parameter }
key={ parameter.get( "name" ) }
onChange={ this.onChange }
onChangeConsumes={this.onChangeConsumesWrapper}
specSelectors={ specSelectors }
pathMethod={ pathMethod }
isExecute={ isExecute }/>
)).toArray()
}
</tbody>
</table>
</div>
}
</div> : "" }
{this.state.callbackVisible ? <div className="callbacks-container opblock-description-wrapper">
<Callbacks callbacks={Map(operation.get("callbacks"))} />
</div> : "" }
{
isOAS3() && requestBody && this.state.parametersVisible &&
<div className="opblock-section">
<div className="opblock-section-header">
<h4 className={`opblock-title parameter__name ${requestBody.get("required") && "required"}`}>Request body</h4>
<label>
<ContentType
value={this.state.requestBodyContentType}
contentTypes={ requestBody.get("content").keySeq() }
onChange={(val) => this.setState({ requestBodyContentType: val })}
className="body-param-content-type" />
</label>
</div>
<div className="opblock-description-wrapper">
<RequestBody
requestBody={requestBody}
contentType={this.state.requestBodyContentType}/>
</div>
</div>
}
</div>
)
}
}
export default OAS3ComponentWrapFactory(Parameters)

View File

@@ -0,0 +1,5 @@
import { OAS3ComponentWrapFactory } from "../helpers"
export default OAS3ComponentWrapFactory(() => {
return null
})

View File

@@ -0,0 +1,13 @@
import React from "react"
import { OAS3ComponentWrapFactory } from "../helpers"
export default OAS3ComponentWrapFactory((props) => {
const { Ori } = props
return <span>
<Ori {...props} />
<small style={{ backgroundColor: "#89bf04" }}>
<pre className="version">OAS3</pre>
</small>
</span>
})

View File

@@ -0,0 +1,67 @@
import { createSelector } from "reselect"
import { Map } from "immutable"
import { isOAS3 as isOAS3Helper, isSwagger2 as isSwagger2Helper } from "./helpers"
// Helpers
function onlyOAS3(selector) {
return (ori, system) => (...args) => {
const spec = system.getSystem().specSelectors.specJson()
if(isOAS3Helper(spec)) {
return selector(...args)
} else {
return ori(...args)
}
}
}
const state = state => {
return state || Map()
}
const nullSelector = createSelector(() => null)
const OAS3NullSelector = onlyOAS3(nullSelector)
const specJson = createSelector(
state,
spec => spec.get("json", Map())
)
const specResolved = createSelector(
state,
spec => spec.get("resolved", Map())
)
const spec = state => {
let res = specResolved(state)
if(res.count() < 1)
res = specJson(state)
return res
}
// Wrappers
export const definitions = onlyOAS3(createSelector(
spec,
spec => spec.getIn(["components", "schemas"]) || Map()
))
export const host = OAS3NullSelector
export const basePath = OAS3NullSelector
export const consumes = OAS3NullSelector
export const produces = OAS3NullSelector
export const schemes = OAS3NullSelector
// New selectors
export const isOAS3 = (ori, system) => () => {
const spec = system.getSystem().specSelectors.specJson()
return isOAS3Helper(spec)
}
export const isSwagger2 = (ori, system) => () => {
const spec = system.getSystem().specSelectors.specJson()
return isSwagger2Helper(spec)
}

View File

@@ -46,6 +46,15 @@ export const spec = state => {
return res return res
} }
export const isOAS3 = createSelector(
// isOAS3 is stubbed out here to work around an issue with injecting more selectors
// in the OAS3 plugin, and to ensure that the function is always available.
// It's not perfect, but our hybrid (core+plugin code) implementation for OAS3
// needs this. //KS
spec,
() => false
)
export const info = createSelector( export const info = createSelector(
spec, spec,
spec => returnSelfOrNewMap(spec && spec.get("info")) spec => returnSelfOrNewMap(spec && spec.get("info"))

View File

@@ -58,8 +58,7 @@ export const makeMappedContainer = (getSystem, getStore, memGetComponent, getCom
} }
export const render = (getSystem, getStore, getComponent, getComponents, dom) => { export const render = (getSystem, getStore, getComponent, getComponents, domNode) => {
let domNode = document.querySelector(dom)
let App = (getComponent(getSystem, getStore, getComponents, "App", "root")) let App = (getComponent(getSystem, getStore, getComponents, "App", "root"))
ReactDOM.render(( <App/> ), domNode) ReactDOM.render(( <App/> ), domNode)
} }

View File

@@ -1,4 +1,5 @@
import BasePreset from "./base" import BasePreset from "./base"
import OAS3Plugin from "../plugins/oas3"
// Just the base, for now. // Just the base, for now.
@@ -6,5 +7,6 @@ export default function PresetApis() {
return [ return [
BasePreset, BasePreset,
OAS3Plugin
] ]
} }

View File

@@ -52,6 +52,7 @@ import ObjectModel from "core/components/object-model"
import ArrayModel from "core/components/array-model" import ArrayModel from "core/components/array-model"
import PrimitiveModel from "core/components/primitive-model" import PrimitiveModel from "core/components/primitive-model"
import TryItOutButton from "core/components/try-it-out-button" import TryItOutButton from "core/components/try-it-out-button"
import VersionStamp from "core/components/version-stamp"
import Markdown from "core/components/providers/markdown" import Markdown from "core/components/providers/markdown"
@@ -105,7 +106,8 @@ export default function() {
PrimitiveModel, PrimitiveModel,
TryItOutButton, TryItOutButton,
Markdown, Markdown,
BaseLayout BaseLayout,
VersionStamp
} }
} }

View File

@@ -76,7 +76,7 @@ export default class Store {
this.boundSystem = Object.assign({}, this.boundSystem = Object.assign({},
this.getRootInjects(), this.getRootInjects(),
this.getWrappedAndBoundActions(dispatch), this.getWrappedAndBoundActions(dispatch),
this.getBoundSelectors(getState, this.getSystem), this.getWrappedAndBoundSelectors(getState, this.getSystem),
this.getStateThunks(getState), this.getStateThunks(getState),
this.getFn(), this.getFn(),
this.getConfigs() this.getConfigs()
@@ -176,6 +176,36 @@ export default class Store {
}) })
} }
getWrappedAndBoundSelectors(getState, getSystem) {
let selectorGroups = this.getBoundSelectors(getState, getSystem)
return objMap(selectorGroups, (selectors, selectorGroupName) => {
let stateName = [selectorGroupName.slice(0, -9)] // selectors = 9 chars
let wrappers = this.system.statePlugins[stateName].wrapSelectors
if(wrappers) {
return objMap(selectors, (selector, selectorName) => {
let wrap = wrappers[selectorName]
if(!wrap) {
return selector
}
if(!Array.isArray(wrap)) {
wrap = [wrap]
}
return wrap.reduce((acc, fn) => {
let wrappedSelector = (...args) => {
return fn(acc, this.getSystem())(getState().getIn(stateName), ...args)
}
if(!isFn(wrappedSelector)) {
throw new TypeError("wrapSelector needs to return a function that returns a new function (ie the wrapped action)")
}
return wrappedSelector
}, selector || Function.prototype)
})
}
return selectors
})
}
getStates(state) { getStates(state) {
return Object.keys(this.system.statePlugins).reduce((obj, key) => { return Object.keys(this.system.statePlugins).reduce((obj, key) => {
obj[key] = state.get(key) obj[key] = state.get(key)
@@ -197,8 +227,17 @@ export default class Store {
} }
getComponents(component) { getComponents(component) {
if(typeof component !== "undefined") const res = this.system.components[component]
if(Array.isArray(res)) {
return res.reduce((ori, wrapper) => {
return wrapper(ori, this.getSystem())
})
}
if(typeof component !== "undefined") {
return this.system.components[component] return this.system.components[component]
}
return this.system.components return this.system.components
} }
@@ -291,6 +330,24 @@ function systemExtend(dest={}, src={}) {
return dest return dest
} }
// Wrap components
// Parses existing components in the system, and prepares them for wrapping via getComponents
if(src.wrapComponents) {
objMap(src.wrapComponents, (wrapperFn, key) => {
const ori = dest.components[key]
if(ori && Array.isArray(ori)) {
dest.components[key] = ori.concat([wrapperFn])
} else if(ori) {
dest.components[key] = [ori, wrapperFn]
} else {
dest.components[key] = null
}
})
delete src.wrapComponents
}
// Account for wrapActions, make it an array and append to it // Account for wrapActions, make it an array and append to it
// Modifies `src` // Modifies `src`
// 80% of this code is just safe traversal. We need to address that ( ie: use a lib ) // 80% of this code is just safe traversal. We need to address that ( ie: use a lib )

View File

@@ -487,9 +487,12 @@ export const validateParam = (param, isXml) => {
let required = param.get("required") let required = param.get("required")
let type = param.get("type") let type = param.get("type")
// If the parameter is required OR the parameter has a value (meaning optional, but filled in) /*
// then we should do our validation routine If the parameter is required OR the parameter has a value (meaning optional, but filled in)
if ( required || value ) { then we should do our validation routine.
Only bother validating the parameter if the type was specified.
*/
if ( type && (required || value) ) {
// These checks should evaluate to true if the parameter's value is valid // These checks should evaluate to true if the parameter's value is valid
let stringCheck = type === "string" && value && !validateString(value) let stringCheck = type === "string" && value && !validateString(value)
let arrayCheck = type === "array" && Array.isArray(value) && value.length let arrayCheck = type === "array" && Array.isArray(value) && value.length
@@ -622,18 +625,6 @@ export const buildFormData = (data) => {
return formArr.join("&") return formArr.join("&")
} }
export const filterConfigs = (configs, allowed) => {
let i, filteredConfigs = {}
for (i in configs) {
if (allowed.indexOf(i) !== -1) {
filteredConfigs[i] = configs[i]
}
}
return filteredConfigs
}
// Is this really required as a helper? Perhaps. TODO: expose the system of presets.apis in docs, so we know what is supported // Is this really required as a helper? Perhaps. TODO: expose the system of presets.apis in docs, so we know what is supported
export const shallowEqualKeys = (a,b, keys) => { export const shallowEqualKeys = (a,b, keys) => {
return !!find(keys, (key) => { return !!find(keys, (key) => {

View File

@@ -14,7 +14,8 @@
@include text_headline(); @include text_headline();
&.btn-sm { &.btn-sm
{
font-size: 12px; font-size: 12px;
padding: 4px 23px; padding: 4px 23px;
} }
@@ -172,7 +173,8 @@ button
cursor: pointer; cursor: pointer;
outline: none; outline: none;
&.invalid { &.invalid
{
@include invalidFormElement(); @include invalidFormElement();
} }
} }

View File

@@ -57,7 +57,8 @@ input[type=file]
border-radius: 4px; border-radius: 4px;
background: #fff; background: #fff;
&.invalid { &.invalid
{
@include invalidFormElement(); @include invalidFormElement();
} }
} }

View File

@@ -56,6 +56,11 @@
{ {
border-color: $color; border-color: $color;
} }
.tab-header .tab-item.active h4 span:after
{
background: $color;
}
} }
@@ -126,6 +131,51 @@
border-radius: 4px; border-radius: 4px;
box-shadow: 0 0 3px rgba(#000,.19); box-shadow: 0 0 3px rgba(#000,.19);
.tab-header
{
display: flex;
flex: 1;
.tab-item
{
padding: 0 40px;
cursor: pointer;
&:first-of-type
{
padding: 0 40px 0 0;
}
&.active
{
h4
{
span
{
position: relative;
&:after
{
position: absolute;
bottom: -15px;
left: 50%;
width: 120%;
height: 4px;
content: '';
transform: translateX(-50%);
background: #888;
}
}
}
}
}
}
&.is-open &.is-open
{ {
@@ -142,6 +192,8 @@
padding: 8px 20px; padding: 8px 20px;
min-height: 50px;
background: rgba(#fff,.8); background: rgba(#fff,.8);
box-shadow: 0 1px 2px rgba(#000,.1); box-shadow: 0 1px 2px rgba(#000,.1);
@@ -154,6 +206,7 @@
align-items: center; align-items: center;
margin: 0; margin: 0;
margin-left: auto;
@include text_headline(); @include text_headline();
@@ -625,6 +678,17 @@
} }
} }
.renderedMarkdown {
p {
@include text_body();
margin-top: 0px;
margin-bottom: 0px;
}
}
.response-content-type {
padding-top: 1em;
}
@keyframes blinker @keyframes blinker
{ {

View File

@@ -3,6 +3,13 @@
font-size: 12px; font-size: 12px;
font-weight: 300; font-weight: 300;
.deprecated
{
span, td {
color: #aaa !important;
}
}
@include text_code(); @include text_code();
&-toggle &-toggle
{ {
@@ -192,6 +199,11 @@ section.models
position: relative; position: relative;
top: 4px; top: 4px;
} }
&.deprecated
{
opacity: .5;
}
} }
@@ -202,13 +214,19 @@ section.models
@include text_headline(#555); @include text_headline(#555);
} }
.model-deprecated-warning
{
font-size: 16px;
font-weight: 600;
margin-right: 1em;
@include text_headline($_color-delete);
}
span span
{ {
> span.model > span.model
{ {
padding: 0 0 0 10px;
.brace-close .brace-close
{ {
padding: 0 0 0 10px; padding: 0 0 0 10px;
@@ -216,6 +234,12 @@ span
} }
} }
.prop-name
{
display: inline-block;
width: 100px;
}
.prop-type .prop-type
{ {
color: #55a; color: #55a;

View File

@@ -19,8 +19,8 @@ table
&:first-of-type &:first-of-type
{ {
width: 100px; width: 124px;
padding: 0; padding: 0 0 0 2em;
} }
} }
} }
@@ -138,6 +138,14 @@ table
@include text_code(#888); @include text_code(#888);
} }
.parameter__deprecated
{
font-size: 12px;
font-style: italic;
@include text_code(#f00);
}
.table-container .table-container
{ {

View File

@@ -1,2 +1,5 @@
env: env:
mocha: true mocha: true
rules:
"react/prop-types": 1 # bah humbug
"no-unused-vars": 1 # unused vars in tests can be useful for indicating a full signature

View File

@@ -326,6 +326,122 @@ describe("bound system", function(){
}) })
describe("wrapSelectors", () => {
it("should wrap a selector and provide a reference to the original", function(){
// Given
const system = new System({
plugins: [
{
statePlugins: {
doge: {
selectors: {
wow: () => (system) => {
return "original"
}
}
}
}
},
{
statePlugins: {
doge: {
wrapSelectors: {
wow: (ori) => (system) => {
// Then
return ori() + " wrapper"
}
}
}
}
}
]
})
// When
var res = system.getSystem().dogeSelectors.wow(1)
expect(res).toEqual("original wrapper")
})
it("should provide a live reference to the system to a wrapper", function(done){
// Given
const mySystem = new System({
plugins: [
{
statePlugins: {
doge: {
selectors: {
wow: () => (system) => {
return "original"
}
}
}
}
},
{
statePlugins: {
doge: {
wrapSelectors: {
wow: (ori, system) => () => {
// Then
expect(mySystem.getSystem()).toEqual(system.getSystem())
done()
return ori() + " wrapper"
}
}
}
}
}
]
})
mySystem.getSystem().dogeSelectors.wow(1)
})
it("should provide the state as the first argument to the inner function", function(done){
// Given
const mySystem = new System({
state: {
doge: {
abc: "123"
}
},
plugins: [
{
statePlugins: {
doge: {
selectors: {
wow: () => (system) => {
return "original"
}
}
}
}
},
{
statePlugins: {
doge: {
wrapSelectors: {
wow: (ori, system) => (dogeState) => {
// Then
expect(dogeState.toJS().abc).toEqual("123")
done()
return ori() + " wrapper"
}
}
}
}
}
]
})
mySystem.getSystem().dogeSelectors.wow(1)
})
})
}) })
}) })

View File

@@ -0,0 +1,139 @@
import React from "react"
import expect from "expect"
import { render } from "enzyme"
import System from "core/system"
describe("wrapComponents", () => {
describe("should wrap a component and provide a reference to the original", () => {
it("with stateless components", function(){
// Given
const system = new System({
plugins: [
{
components: {
wow: ({ name }) => <div>{name} component</div>
}
},
{
wrapComponents: {
wow: (OriginalComponent) => (props) => {
return <container>
<OriginalComponent {...props}></OriginalComponent>
<OriginalComponent name="Wrapped"></OriginalComponent>
</container>
}
}
}
]
})
// When
var Component = system.getSystem().getComponents("wow")
const wrapper = render(<Component name="Normal" />)
const container = wrapper.children().first()
expect(container[0].name).toEqual("container")
const children = container.children()
expect(children.length).toEqual(2)
expect(children.eq(0).text()).toEqual("Normal component")
expect(children.eq(1).text()).toEqual("Wrapped component")
})
it("with React classes", function(){
class MyComponent extends React.Component {
render() {
return <div>{this.props.name} component</div>
}
}
// Given
const system = new System({
plugins: [
{
components: {
wow: MyComponent
}
},
{
wrapComponents: {
wow: (OriginalComponent) => {
return class WrapperComponent extends React.Component {
render() {
return <container>
<OriginalComponent {...this.props}></OriginalComponent>
<OriginalComponent name="Wrapped"></OriginalComponent>
</container>
}
}
}
}
}
]
})
// When
var Component = system.getSystem().getComponents("wow")
const wrapper = render(<Component name="Normal" />)
const container = wrapper.children().first()
expect(container[0].name).toEqual("container")
const children = container.children()
expect(children.length).toEqual(2)
expect(children.eq(0).text()).toEqual("Normal component")
expect(children.eq(1).text()).toEqual("Wrapped component")
})
})
it("should provide a reference to the system to the wrapper", function(){
// Given
const mySystem = new System({
plugins: [
{
// Make a selector
statePlugins: {
doge: {
selectors: {
wow: () => () => {
return "WOW much data"
}
}
}
}
},
{
// Create a component
components: {
wow: () => <div>Original component</div>
}
},
{
// Wrap the component and use the system
wrapComponents: {
wow: (OriginalComponent, system) => (props) => {
return <container>
<OriginalComponent {...props}></OriginalComponent>
<div>{system.dogeSelectors.wow()}</div>
</container>
}
}
}
]
})
// Then
var Component = mySystem.getSystem().getComponents("wow")
const wrapper = render(<Component name="Normal" />)
const container = wrapper.children().first()
expect(container[0].name).toEqual("container")
const children = container.children()
expect(children.length).toEqual(2)
expect(children.eq(0).text()).toEqual("Original component")
expect(children.eq(1).text()).toEqual("WOW much data")
})
})

View File

@@ -175,6 +175,17 @@ describe("utils", function() {
let param = null let param = null
let result = null let result = null
it("skips validation when `type` is not specified", function() {
// invalid type
param = fromJS({
required: false,
type: undefined,
value: ""
})
result = validateParam( param, false )
expect( result ).toEqual( [] )
})
it("validates required strings", function() { it("validates required strings", function() {
// invalid string // invalid string
param = fromJS({ param = fromJS({
@@ -534,7 +545,7 @@ describe("utils", function() {
result = validateParam( param, false ) result = validateParam( param, false )
expect( result ).toEqual( [] ) expect( result ).toEqual( [] )
// valid number // integers
param = fromJS({ param = fromJS({
required: false, required: false,
type: "integer", type: "integer",