Merge branch 'master' into ft/3052-extensions

This commit is contained in:
Greg Thompson
2017-11-20 09:48:33 -06:00
30 changed files with 680 additions and 286 deletions

View File

@@ -22,7 +22,7 @@ The OpenAPI Specification has undergone 5 revisions since initial creation in 20
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes
------------------ | ------------ | -------------------------- | ----- ------------------ | ------------ | -------------------------- | -----
3.4.4 | 2017-11-03 | 2.0, 3.0 | [tag v3.4.4](https://github.com/swagger-api/swagger-ui/tree/v3.4.4) 3.4.5 | 2017-11-03 | 2.0, 3.0 | [tag v3.4.5](https://github.com/swagger-api/swagger-ui/tree/v3.4.5)
3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21) 3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21)
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.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.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.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)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/swagger-ui.css vendored

File diff suppressed because one or more lines are too long

4
dist/swagger-ui.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{ {
"name": "swagger-ui", "name": "swagger-ui",
"version": "3.4.4", "version": "3.4.5",
"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": [
@@ -45,6 +45,7 @@
"brace": "0.7.0", "brace": "0.7.0",
"classnames": "^2.2.5", "classnames": "^2.2.5",
"commonmark": "^0.28.1", "commonmark": "^0.28.1",
"core-js": "^2.5.1",
"css.escape": "1.5.1", "css.escape": "1.5.1",
"deep-extend": "0.4.1", "deep-extend": "0.4.1",
"expect": "1.20.2", "expect": "1.20.2",
@@ -80,7 +81,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.3.3", "swagger-client": "^3.3.4",
"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

@@ -60,6 +60,9 @@ export default class ApiKeyAuth extends React.Component {
<Row> <Row>
<Markdown source={ schema.get("description") } /> <Markdown source={ schema.get("description") } />
</Row> </Row>
<Row>
<p>Name: <code>{ schema.get("name") }</code></p>
</Row>
<Row> <Row>
<p>In: <code>{ schema.get("in") }</code></p> <p>In: <code>{ schema.get("in") }</code></p>
</Row> </Row>

View File

@@ -27,6 +27,16 @@ export default class ContentType extends React.Component {
} }
} }
componentWillReceiveProps(nextProps) {
if(!nextProps.contentTypes || !nextProps.contentTypes.size) {
return
}
if(!nextProps.contentTypes.includes(nextProps.value)) {
nextProps.onChange(nextProps.contentTypes.first())
}
}
onChangeWrapper = e => this.props.onChange(e.target.value) onChangeWrapper = e => this.props.onChange(e.target.value)
render() { render() {

View File

@@ -8,7 +8,6 @@ export default class Execute extends Component {
specActions: PropTypes.object.isRequired, specActions: PropTypes.object.isRequired,
operation: PropTypes.object.isRequired, operation: PropTypes.object.isRequired,
path: PropTypes.string.isRequired, path: PropTypes.string.isRequired,
getComponent: PropTypes.func.isRequired,
method: PropTypes.string.isRequired, method: PropTypes.string.isRequired,
onExecute: PropTypes.func onExecute: PropTypes.func
} }

View File

@@ -1,6 +1,7 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes" import ImPropTypes from "react-immutable-proptypes"
import { Iterable } from "immutable"
const Headers = ( { headers } )=>{ const Headers = ( { headers } )=>{
return ( return (
@@ -28,19 +29,29 @@ Duration.propTypes = {
export default class LiveResponse extends React.Component { export default class LiveResponse extends React.Component {
static propTypes = { static propTypes = {
response: PropTypes.object.isRequired, response: PropTypes.instanceOf(Iterable).isRequired,
specSelectors: PropTypes.object.isRequired, path: PropTypes.string.isRequired,
pathMethod: PropTypes.object.isRequired, method: PropTypes.string.isRequired,
getComponent: PropTypes.func.isRequired,
displayRequestDuration: PropTypes.bool.isRequired, displayRequestDuration: PropTypes.bool.isRequired,
specSelectors: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired getConfigs: PropTypes.func.isRequired
} }
shouldComponentUpdate(nextProps) {
// BUG: props.response is always coming back as a new Immutable instance
// same issue as responses.jsx (tryItOutResponse)
return this.props.response !== nextProps.response
|| this.props.path !== nextProps.path
|| this.props.method !== nextProps.method
|| this.props.displayRequestDuration !== nextProps.displayRequestDuration
}
render() { render() {
const { response, getComponent, getConfigs, displayRequestDuration, specSelectors, pathMethod } = this.props const { response, getComponent, getConfigs, displayRequestDuration, specSelectors, path, method } = this.props
const { showMutatedRequest } = getConfigs() const { showMutatedRequest } = getConfigs()
const curlRequest = showMutatedRequest ? specSelectors.mutatedRequestFor(pathMethod[0], pathMethod[1]) : specSelectors.requestFor(pathMethod[0], pathMethod[1]) const curlRequest = showMutatedRequest ? specSelectors.mutatedRequestFor(path, method) : specSelectors.requestFor(path, method)
const status = response.get("status") const status = response.get("status")
const url = response.get("url") const url = response.get("url")
const headers = response.get("headers").toJS() const headers = response.get("headers").toJS()
@@ -118,7 +129,6 @@ export default class LiveResponse extends React.Component {
static propTypes = { static propTypes = {
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
request: ImPropTypes.map,
response: ImPropTypes.map response: ImPropTypes.map
} }
} }

View File

@@ -1,32 +1,22 @@
import React, { PureComponent } from "react" import React, { PureComponent } from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { getList } from "core/utils" import { getList } from "core/utils"
import * as CustomPropTypes from "core/proptypes"
import { getExtensions, sanitizeUrl } from "core/utils" import { getExtensions, sanitizeUrl } from "core/utils"
import { Iterable } from "immutable"
//import "less/opblock"
export default class Operation extends PureComponent { export default class Operation extends PureComponent {
static propTypes = { static propTypes = {
path: PropTypes.string.isRequired, operation: PropTypes.instanceOf(Iterable).isRequired,
method: PropTypes.string.isRequired, response: PropTypes.instanceOf(Iterable),
operation: PropTypes.object.isRequired, request: PropTypes.instanceOf(Iterable),
showSummary: PropTypes.bool,
isShown: PropTypes.bool.isRequired,
tagKey: PropTypes.string, toggleShown: PropTypes.func.isRequired,
operationKey: PropTypes.string, onTryoutClick: PropTypes.func.isRequired,
jumpToKey: CustomPropTypes.arrayOrString.isRequired, onCancelClick: PropTypes.func.isRequired,
onExecute: PropTypes.func.isRequired,
allowTryItOut: PropTypes.bool,
displayOperationId: PropTypes.bool,
displayRequestDuration: PropTypes.bool,
response: PropTypes.object,
request: PropTypes.object,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
authActions: PropTypes.object, authActions: PropTypes.object,
authSelectors: PropTypes.object, authSelectors: PropTypes.object,
specActions: PropTypes.object.isRequired, specActions: PropTypes.object.isRequired,
@@ -34,89 +24,68 @@ export default class Operation extends PureComponent {
oas3Actions: PropTypes.object.isRequired, oas3Actions: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired, layoutActions: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired, layoutSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired, fn: PropTypes.object.isRequired
getConfigs: PropTypes.func.isRequired
} }
static defaultProps = { static defaultProps = {
showSummary: true, operation: null,
response: null, response: null,
allowTryItOut: true, request: null
displayOperationId: false,
displayRequestDuration: false
}
constructor(props, context) {
super(props, context)
this.state = {
tryItOutEnabled: false
}
}
componentWillReceiveProps(nextProps) {
if(nextProps.response !== this.props.response) {
this.setState({ executeInProgress: false })
}
}
toggleShown =() => {
let { layoutActions, tagKey, operationKey, isShown } = this.props
const isShownKey = ["operations", tagKey, operationKey]
layoutActions.show(isShownKey, !isShown)
}
onTryoutClick =() => {
this.setState({tryItOutEnabled: !this.state.tryItOutEnabled})
}
onCancelClick =() => {
let { specActions, path, method } = this.props
this.setState({tryItOutEnabled: !this.state.tryItOutEnabled})
specActions.clearValidateParams([path, method])
}
onExecute = () => {
this.setState({ executeInProgress: true })
} }
render() { render() {
let { let {
operationKey,
tagKey,
isShown,
jumpToKey,
path,
method,
operation,
showSummary,
response, response,
request, request,
allowTryItOut, toggleShown,
displayOperationId, onTryoutClick,
displayRequestDuration, onCancelClick,
onExecute,
fn, fn,
getComponent, getComponent,
getConfigs,
specActions, specActions,
specSelectors, specSelectors,
authActions, authActions,
authSelectors, authSelectors,
getConfigs,
oas3Actions oas3Actions
} = this.props } = this.props
let operationProps = this.props.operation
let summary = operation.get("summary") let {
let description = operation.get("description") isShown,
let deprecated = operation.get("deprecated") isAuthorized,
let extensions = getExtensions(operation) jumpToKey,
let externalDocs = operation.get("externalDocs") path,
method,
op,
tag,
showSummary,
operationId,
allowTryItOut,
displayOperationId,
displayRequestDuration,
isDeepLinkingEnabled,
tryItOutEnabled,
executeInProgress
} = operationProps.toJS()
let {
summary,
description,
deprecated,
externalDocs,
schemes
} = op.operation
let operation = operationProps.getIn(["op", "operation"])
let security = operationProps.get("security")
let responses = operation.get("responses") let responses = operation.get("responses")
let security = operation.get("security") || specSelectors.security()
let produces = operation.get("produces") let produces = operation.get("produces")
let schemes = operation.get("schemes")
let parameters = getList(operation, ["parameters"]) let parameters = getList(operation, ["parameters"])
let operationId = operation.get("__originalOperationId")
let operationScheme = specSelectors.operationScheme(path, method) let operationScheme = specSelectors.operationScheme(path, method)
let isShownKey = ["operations", tag, operationId]
let extensions = getExtensions(operation)
const Responses = getComponent("responses") const Responses = getComponent("responses")
const Parameters = getComponent( "parameters" ) const Parameters = getComponent( "parameters" )
@@ -129,9 +98,7 @@ export default class Operation extends PureComponent {
const Schemes = getComponent( "schemes" ) const Schemes = getComponent( "schemes" )
const OperationExt = getComponent( "OperationExt" ) const OperationExt = getComponent( "OperationExt" )
const { deepLinking, showExtensions } = getConfigs() const { showExtensions } = getConfigs()
const isDeepLinkingEnabled = deepLinking && deepLinking !== "false"
// Merge in Live Response // Merge in Live Response
if(responses && response && response.size > 0) { if(responses && response && response.size > 0) {
@@ -139,18 +106,17 @@ export default class Operation extends PureComponent {
response = response.set("notDocumented", notDocumented) response = response.set("notDocumented", notDocumented)
} }
let { tryItOutEnabled } = this.state
let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method ) let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method )
return ( return (
<div className={deprecated ? "opblock opblock-deprecated" : isShown ? `opblock opblock-${method} is-open` : `opblock opblock-${method}`} id={`operations-${tagKey}-${operationKey}`} > <div className={deprecated ? "opblock opblock-deprecated" : isShown ? `opblock opblock-${method} is-open` : `opblock opblock-${method}`} id={isShownKey.join("-")} >
<div className={`opblock-summary opblock-summary-${method}`} onClick={this.toggleShown} > <div className={`opblock-summary opblock-summary-${method}`} onClick={toggleShown} >
<span className="opblock-summary-method">{method.toUpperCase()}</span> <span className="opblock-summary-method">{method.toUpperCase()}</span>
<span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } > <span className={ deprecated ? "opblock-summary-path__deprecated" : "opblock-summary-path" } >
<a <a
className="nostyle" className="nostyle"
onClick={isDeepLinkingEnabled ? (e) => e.preventDefault() : null} onClick={isDeepLinkingEnabled ? (e) => e.preventDefault() : null}
href={isDeepLinkingEnabled ? `#/${tagKey}/${operationKey}` : null}> href={isDeepLinkingEnabled ? `#/${isShownKey.join("/")}` : null}>
<span>{path}</span> <span>{path}</span>
</a> </a>
<JumpToPath path={jumpToKey} /> <JumpToPath path={jumpToKey} />
@@ -167,7 +133,7 @@ export default class Operation extends PureComponent {
{ {
(!security || !security.count()) ? null : (!security || !security.count()) ? null :
<AuthorizeOperationBtn <AuthorizeOperationBtn
isAuthorized={ authSelectors.isAuthorized(security) } isAuthorized={ isAuthorized }
onClick={() => { onClick={() => {
const applicableDefinitions = authSelectors.definitionsForRequirements(security) const applicableDefinitions = authSelectors.definitionsForRequirements(security)
authActions.showDefinitions(applicableDefinitions) authActions.showDefinitions(applicableDefinitions)
@@ -203,8 +169,8 @@ export default class Operation extends PureComponent {
parameters={parameters} parameters={parameters}
operation={operation} operation={operation}
onChangeKey={onChangeKey} onChangeKey={onChangeKey}
onTryoutClick = { this.onTryoutClick } onTryoutClick = { onTryoutClick }
onCancelClick = { this.onCancelClick } onCancelClick = { onCancelClick }
tryItOutEnabled = { tryItOutEnabled } tryItOutEnabled = { tryItOutEnabled }
allowTryItOut={allowTryItOut} allowTryItOut={allowTryItOut}
@@ -229,25 +195,23 @@ export default class Operation extends PureComponent {
{ !tryItOutEnabled || !allowTryItOut ? null : { !tryItOutEnabled || !allowTryItOut ? null :
<Execute <Execute
getComponent={getComponent}
operation={ operation } operation={ operation }
specActions={ specActions } specActions={ specActions }
specSelectors={ specSelectors } specSelectors={ specSelectors }
path={ path } path={ path }
method={ method } method={ method }
onExecute={ this.onExecute } /> onExecute={ onExecute } />
} }
{ (!tryItOutEnabled || !response || !allowTryItOut) ? null : { (!tryItOutEnabled || !response || !allowTryItOut) ? null :
<Clear <Clear
onClick={ this.onClearClick }
specActions={ specActions } specActions={ specActions }
path={ path } path={ path }
method={ method }/> method={ method }/>
} }
</div> </div>
{this.state.executeInProgress ? <div className="loading-container"><div className="loading"></div></div> : null} {executeInProgress ? <div className="loading-container"><div className="loading"></div></div> : null}
{ !responses ? null : { !responses ? null :
<Responses <Responses
@@ -261,7 +225,8 @@ export default class Operation extends PureComponent {
specActions={ specActions } specActions={ specActions }
produces={ produces } produces={ produces }
producesValue={ operation.get("produces_value") } producesValue={ operation.get("produces_value") }
pathMethod={ [path, method] } path={ path }
method={ method }
displayRequestDuration={ displayRequestDuration } displayRequestDuration={ displayRequestDuration }
fn={fn} /> fn={fn} />
} }

View File

@@ -1,8 +1,6 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { helpers } from "swagger-client"
import { createDeepLinkPath, sanitizeUrl } from "core/utils" import { createDeepLinkPath, sanitizeUrl } from "core/utils"
const { opId } = helpers
export default class Operations extends React.Component { export default class Operations extends React.Component {
@@ -21,28 +19,20 @@ export default class Operations extends React.Component {
render() { render() {
let { let {
specSelectors, specSelectors,
specActions,
oas3Actions,
getComponent, getComponent,
layoutSelectors, layoutSelectors,
layoutActions, layoutActions,
authActions, getConfigs
authSelectors,
getConfigs,
fn
} = this.props } = this.props
let taggedOps = specSelectors.taggedOperations() let taggedOps = specSelectors.taggedOperations()
const Operation = getComponent("operation") const OperationContainer = getComponent("OperationContainer", true)
const Collapse = getComponent("Collapse") const Collapse = getComponent("Collapse")
const Markdown = getComponent("Markdown") const Markdown = getComponent("Markdown")
let showSummary = layoutSelectors.showSummary()
let { let {
docExpansion, docExpansion,
displayOperationId,
displayRequestDuration,
maxDisplayedTags, maxDisplayedTags,
deepLinking deepLinking
} = getConfigs() } = getConfigs()
@@ -120,49 +110,15 @@ export default class Operations extends React.Component {
<Collapse isOpened={showTag}> <Collapse isOpened={showTag}>
{ {
operations.map( op => { operations.map( op => {
const path = op.get("path")
const method = op.get("method")
const path = op.get("path", "") return <OperationContainer
const method = op.get("method", "") key={`${path}-${method}`}
const jumpToKey = `paths.${path}.${method}` op={op}
path={path}
const operationId = method={method}
op.getIn(["operation", "operationId"]) || op.getIn(["operation", "__originalOperationId"]) || opId(op.get("operation"), path, method) || op.get("id") tag={tag}
const tagKey = createDeepLinkPath(tag)
const operationKey = createDeepLinkPath(operationId)
const allowTryItOut = specSelectors.allowTryItOutFor(op.get("path"), op.get("method"))
const response = specSelectors.responseFor(op.get("path"), op.get("method"))
const request = specSelectors.requestFor(op.get("path"), op.get("method"))
return <Operation
{...op.toObject()}
tagKey={tagKey}
operationKey={operationKey}
isShown={layoutSelectors.isShown(["operations", tagKey, operationKey], docExpansion === "full")}
jumpToKey={jumpToKey}
showSummary={showSummary}
key={tagKey + operationKey}
response={ response }
request={ request }
allowTryItOut={allowTryItOut}
displayOperationId={displayOperationId}
displayRequestDuration={displayRequestDuration}
specActions={ specActions }
specSelectors={ specSelectors }
oas3Actions={oas3Actions}
layoutActions={ layoutActions }
layoutSelectors={ layoutSelectors }
authActions={ authActions }
authSelectors={ authSelectors }
getComponent={ getComponent }
fn={fn}
getConfigs={ getConfigs }
/> />
}).toArray() }).toArray()
} }

View File

@@ -1,7 +1,7 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import cx from "classnames" import cx from "classnames"
import { fromJS, Seq } from "immutable" import { fromJS, Seq, Iterable } from "immutable"
import { getSampleSchema, fromJSOrdered } from "core/utils" import { getSampleSchema, fromJSOrdered } from "core/utils"
const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => { const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => {
@@ -42,7 +42,7 @@ export default class Response extends React.Component {
static propTypes = { static propTypes = {
code: PropTypes.string.isRequired, code: PropTypes.string.isRequired,
response: PropTypes.object, response: PropTypes.instanceOf(Iterable),
className: PropTypes.string, className: PropTypes.string,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired, getConfigs: PropTypes.func.isRequired,

View File

@@ -1,41 +1,52 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { fromJS } from "immutable" import { fromJS, Iterable } from "immutable"
import { defaultStatusCode, getAcceptControllingResponse } from "core/utils" import { defaultStatusCode, getAcceptControllingResponse } from "core/utils"
export default class Responses extends React.Component { export default class Responses extends React.Component {
static propTypes = { static propTypes = {
request: PropTypes.object, tryItOutResponse: PropTypes.instanceOf(Iterable),
tryItOutResponse: PropTypes.object, responses: PropTypes.instanceOf(Iterable).isRequired,
responses: PropTypes.object.isRequired, produces: PropTypes.instanceOf(Iterable),
produces: PropTypes.object,
producesValue: PropTypes.any, producesValue: PropTypes.any,
displayRequestDuration: PropTypes.bool.isRequired,
path: PropTypes.string.isRequired,
method: PropTypes.string.isRequired,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired, getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired, specActions: PropTypes.object.isRequired,
oas3Actions: PropTypes.object.isRequired, oas3Actions: PropTypes.object.isRequired,
pathMethod: PropTypes.array.isRequired,
displayRequestDuration: PropTypes.bool.isRequired,
fn: PropTypes.object.isRequired fn: PropTypes.object.isRequired
} }
static defaultProps = { static defaultProps = {
request: null,
tryItOutResponse: null, tryItOutResponse: null,
produces: fromJS(["application/json"]), produces: fromJS(["application/json"]),
displayRequestDuration: false displayRequestDuration: false
} }
onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue(this.props.pathMethod, val) shouldComponentUpdate(nextProps) {
// BUG: props.tryItOutResponse is always coming back as a new Immutable instance
let render = this.props.tryItOutResponse !== nextProps.tryItOutResponse
|| this.props.responses !== nextProps.responses
|| this.props.produces !== nextProps.produces
|| this.props.producesValue !== nextProps.producesValue
|| this.props.displayRequestDuration !== nextProps.displayRequestDuration
|| this.props.path !== nextProps.path
|| this.props.method !== nextProps.method
return render
}
onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue([this.props.path, this.props.method], val)
onResponseContentTypeChange = ({ controlsAcceptHeader, value }) => { onResponseContentTypeChange = ({ controlsAcceptHeader, value }) => {
const { oas3Actions, pathMethod } = this.props const { oas3Actions, path, method } = this.props
if(controlsAcceptHeader) { if(controlsAcceptHeader) {
oas3Actions.setResponseContentType({ oas3Actions.setResponseContentType({
value, value,
pathMethod path,
method
}) })
} }
} }
@@ -43,7 +54,6 @@ export default class Responses extends React.Component {
render() { render() {
let { let {
responses, responses,
request,
tryItOutResponse, tryItOutResponse,
getComponent, getComponent,
getConfigs, getConfigs,
@@ -81,12 +91,12 @@ export default class Responses extends React.Component {
{ {
!tryItOutResponse ? null !tryItOutResponse ? null
: <div> : <div>
<LiveResponse request={ request } <LiveResponse response={ tryItOutResponse }
response={ tryItOutResponse }
getComponent={ getComponent } getComponent={ getComponent }
getConfigs={ getConfigs } getConfigs={ getConfigs }
specSelectors={ specSelectors } specSelectors={ specSelectors }
pathMethod={ this.props.pathMethod } path={ this.props.path }
method={ this.props.method }
displayRequestDuration={ displayRequestDuration } /> displayRequestDuration={ displayRequestDuration } />
<h4>Responses</h4> <h4>Responses</h4>
</div> </div>

View File

@@ -0,0 +1,203 @@
import React, { PureComponent } from "react"
import PropTypes from "prop-types"
import { helpers } from "swagger-client"
import { Iterable, fromJS } from "immutable"
const { opId } = helpers
export default class OperationContainer extends PureComponent {
constructor(props, context) {
super(props, context)
this.state = {
tryItOutEnabled: false,
executeInProgress: false
}
}
static propTypes = {
op: PropTypes.instanceOf(Iterable).isRequired,
tag: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
method: PropTypes.string.isRequired,
operationId: PropTypes.string.isRequired,
showSummary: PropTypes.bool.isRequired,
isShown: PropTypes.bool.isRequired,
jumpToKey: PropTypes.string.isRequired,
allowTryItOut: PropTypes.bool,
displayOperationId: PropTypes.bool,
isAuthorized: PropTypes.bool,
displayRequestDuration: PropTypes.bool,
response: PropTypes.instanceOf(Iterable),
request: PropTypes.instanceOf(Iterable),
security: PropTypes.instanceOf(Iterable),
isDeepLinkingEnabled: PropTypes.bool.isRequired,
getComponent: PropTypes.func.isRequired,
authActions: PropTypes.object,
oas3Actions: PropTypes.object,
authSelectors: PropTypes.object,
specActions: PropTypes.object.isRequired,
specSelectors: PropTypes.object.isRequired,
layoutActions: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object.isRequired,
fn: PropTypes.object.isRequired,
getConfigs: PropTypes.func.isRequired
}
static defaultProps = {
showSummary: true,
response: null,
allowTryItOut: true,
displayOperationId: false,
displayRequestDuration: false
}
mapStateToProps(nextState, props) {
const { op, layoutSelectors, getConfigs } = props
const { docExpansion, deepLinking, displayOperationId, displayRequestDuration } = getConfigs()
const showSummary = layoutSelectors.showSummary()
const operationId = op.getIn(["operation", "operationId"]) || op.getIn(["operation", "__originalOperationId"]) || opId(op.get("operation"), props.path, props.method) || op.get("id")
const isShownKey = ["operations", props.tag, operationId]
const isDeepLinkingEnabled = deepLinking && deepLinking !== "false"
const allowTryItOut = typeof props.allowTryItOut === "undefined" ?
props.specSelectors.allowTryItOutFor(props.path, props.method) : props.allowTryItOut
const security = op.getIn(["operation", "security"]) || props.specSelectors.security()
return {
operationId,
isDeepLinkingEnabled,
showSummary,
displayOperationId,
displayRequestDuration,
allowTryItOut,
security,
isAuthorized: props.authSelectors.isAuthorized(security),
isShown: layoutSelectors.isShown(isShownKey, docExpansion === "full" ),
jumpToKey: `paths.${props.path}.${props.method}`,
response: props.specSelectors.responseFor(props.path, props.method),
request: props.specSelectors.requestFor(props.path, props.method)
}
}
componentWillReceiveProps(nextProps) {
const defaultContentType = "application/json"
let { specActions, path, method, op } = nextProps
let operation = op.get("operation")
let producesValue = operation.get("produces_value")
let produces = operation.get("produces")
let consumes = operation.get("consumes")
let consumesValue = operation.get("consumes_value")
if(nextProps.response !== this.props.response) {
this.setState({ executeInProgress: false })
}
if (producesValue === undefined) {
producesValue = produces && produces.size ? produces.first() : defaultContentType
specActions.changeProducesValue([path, method], producesValue)
}
if (consumesValue === undefined) {
consumesValue = consumes && consumes.size ? consumes.first() : defaultContentType
specActions.changeConsumesValue([path, method], consumesValue)
}
}
toggleShown =() => {
let { layoutActions, tag, operationId, isShown } = this.props
layoutActions.show(["operations", tag, operationId], !isShown)
}
onTryoutClick =() => {
this.setState({tryItOutEnabled: !this.state.tryItOutEnabled})
}
onCancelClick =() => {
let { specActions, path, method } = this.props
this.setState({tryItOutEnabled: !this.state.tryItOutEnabled})
specActions.clearValidateParams([path, method])
}
onExecute = () => {
this.setState({ executeInProgress: true })
}
render() {
let {
op,
tag,
path,
method,
security,
isAuthorized,
operationId,
showSummary,
isShown,
jumpToKey,
allowTryItOut,
response,
request,
displayOperationId,
displayRequestDuration,
isDeepLinkingEnabled,
specSelectors,
specActions,
getComponent,
getConfigs,
layoutSelectors,
layoutActions,
authActions,
authSelectors,
oas3Actions,
fn
} = this.props
const Operation = getComponent( "operation" )
const operationProps = fromJS({
op,
tag,
path,
method,
security,
isAuthorized,
operationId,
showSummary,
isShown,
jumpToKey,
allowTryItOut,
request,
displayOperationId,
displayRequestDuration,
isDeepLinkingEnabled,
executeInProgress: this.state.executeInProgress,
tryItOutEnabled: this.state.tryItOutEnabled
})
return (
<Operation
operation={operationProps}
response={response}
request={request}
isShown={isShown}
toggleShown={this.toggleShown}
onTryoutClick={this.onTryoutClick}
onCancelClick={this.onCancelClick}
onExecute={this.onExecute}
specActions={ specActions }
specSelectors={ specSelectors }
oas3Actions={oas3Actions}
layoutActions={ layoutActions }
layoutSelectors={ layoutSelectors }
authActions={ authActions }
authSelectors={ authSelectors }
getComponent={ getComponent }
getConfigs={ getConfigs }
fn={fn}
/>
)
}
}

View File

@@ -7,8 +7,7 @@ import * as AllPlugins from "core/plugins/all"
import { parseSearch } from "core/utils" import { parseSearch } from "core/utils"
if (process.env.NODE_ENV !== "production") { if (process.env.NODE_ENV !== "production") {
const Perf = require("react-addons-perf") window.Perf = require("react-addons-perf")
window.Perf = Perf
} }
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef

View File

@@ -73,7 +73,7 @@ export const authorizePassword = ( auth ) => ( { authActions } ) => {
let { schema, name, username, password, passwordType, clientId, clientSecret } = auth let { schema, name, username, password, passwordType, clientId, clientSecret } = auth
let form = { let form = {
grant_type: "password", grant_type: "password",
scope: encodeURIComponent(auth.scopes.join(scopeSeparator)) scope: auth.scopes.join(scopeSeparator)
} }
let query = {} let query = {}
let headers = {} let headers = {}

View File

@@ -28,10 +28,10 @@ export function setRequestContentType ({ value, pathMethod }) {
} }
} }
export function setResponseContentType ({ value, pathMethod }) { export function setResponseContentType ({ value, path, method }) {
return { return {
type: UPDATE_RESPONSE_CONTENT_TYPE, type: UPDATE_RESPONSE_CONTENT_TYPE,
payload: { value, pathMethod } payload: { value, path, method }
} }
} }

View File

@@ -1,10 +1,12 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { fromJS } from "immutable"
const Callbacks = (props) => { const Callbacks = (props) => {
let { callbacks, getComponent } = props let { callbacks, getComponent } = props
// const Markdown = getComponent("Markdown") // const Markdown = getComponent("Markdown")
const Operation = getComponent("operation", true) const OperationContainer = getComponent("OperationContainer", true)
if(!callbacks) { if(!callbacks) {
return <span>No callbacks</span> return <span>No callbacks</span>
@@ -16,24 +18,22 @@ const Callbacks = (props) => {
{ callback.map((pathItem, pathItemName) => { { callback.map((pathItem, pathItemName) => {
return <div key={pathItemName}> return <div key={pathItemName}>
{ pathItem.map((operation, method) => { { pathItem.map((operation, method) => {
return <Operation let op = fromJS({
operation={operation} operation
})
return <OperationContainer
{...props}
op={op}
key={method} key={method}
tag={""}
method={method} method={method}
isShownKey={["callbacks", operation.get("id"), callbackName]}
path={pathItemName} path={pathItemName}
allowTryItOut={false} allowTryItOut={false}
{...props}></Operation> />
// return <pre>{JSON.stringify(operation)}</pre>
}) } }) }
</div> </div>
}) } }) }
</div> </div>
// return <div>
// <h2>{name}</h2>
// {callback.description && <Markdown source={callback.description}/>}
// <pre>{JSON.stringify(callback)}</pre>
// </div>
}) })
return <div> return <div>
{callbackElements} {callbackElements}
@@ -42,7 +42,7 @@ const Callbacks = (props) => {
Callbacks.propTypes = { Callbacks.propTypes = {
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
callbacks: PropTypes.array.isRequired callbacks: ImPropTypes.iterable.isRequired
} }

View File

@@ -48,6 +48,13 @@ export default class RequestBodyEditor extends PureComponent {
} }
} }
componentDidUpdate(prevProps) {
if(this.props.requestBody !== prevProps.requestBody) {
// force recalc of value if the request body definition has changed
this.setValueToSample(this.props.mediaType)
}
}
setValueToSample = (explicitMediaType) => { setValueToSample = (explicitMediaType) => {
this.onChange(this.sample(explicitMediaType)) this.onChange(this.sample(explicitMediaType))
} }

View File

@@ -22,6 +22,10 @@ const RequestBody = ({
const mediaTypeValue = requestBodyContent.get(contentType) const mediaTypeValue = requestBodyContent.get(contentType)
if(!mediaTypeValue) {
return null
}
return <div> return <div>
{ requestBodyDescription && { requestBodyDescription &&
<Markdown source={requestBodyDescription} /> <Markdown source={requestBodyDescription} />

View File

@@ -18,8 +18,7 @@ export default {
let [path, method] = pathMethod let [path, method] = pathMethod
return state.setIn( [ "requestData", path, method, "requestContentType" ], value) return state.setIn( [ "requestData", path, method, "requestContentType" ], value)
}, },
[UPDATE_RESPONSE_CONTENT_TYPE]: (state, { payload: { value, pathMethod } } ) =>{ [UPDATE_RESPONSE_CONTENT_TYPE]: (state, { payload: { value, path, method } } ) =>{
let [path, method] = pathMethod
return state.setIn( [ "requestData", path, method, "responseContentType" ], value) return state.setIn( [ "requestData", path, method, "responseContentType" ], value)
}, },
[UPDATE_SERVER_VARIABLE_VALUE]: (state, { payload: { server, key, val } } ) =>{ [UPDATE_SERVER_VARIABLE_VALUE]: (state, { payload: { server, key, val } } ) =>{

View File

@@ -20,8 +20,14 @@ const RootWrapper = (reduxStore, ComponentToWrap) => class extends Component {
} }
const makeContainer = (getSystem, component, reduxStore) => { const makeContainer = (getSystem, component, reduxStore) => {
const mapStateToProps = function(state, ownProps) {
const propsForContainerComponent = Object.assign({}, ownProps, getSystem())
const ori = component.prototype.mapStateToProps || (state => { return {state} })
return ori(state, propsForContainerComponent)
}
let wrappedWithSystem = SystemWrapper(getSystem, component, reduxStore) let wrappedWithSystem = SystemWrapper(getSystem, component, reduxStore)
let connected = connect(state => ({state}))(wrappedWithSystem) let connected = connect( mapStateToProps )(wrappedWithSystem)
if(reduxStore) if(reduxStore)
return RootWrapper(reduxStore, connected) return RootWrapper(reduxStore, connected)
return connected return connected
@@ -114,5 +120,5 @@ export const getComponent = (getSystem, getStore, getComponents, componentName,
return makeContainer(getSystem, component, getStore()) return makeContainer(getSystem, component, getStore())
// container == truthy // container == truthy
return makeContainer(getSystem, component) return makeContainer(getSystem, wrapRender(component))
} }

View File

@@ -13,6 +13,8 @@ import downloadUrlPlugin from "core/plugins/download-url"
import configsPlugin from "plugins/configs" import configsPlugin from "plugins/configs"
import deepLinkingPlugin from "core/plugins/deep-linking" import deepLinkingPlugin from "core/plugins/deep-linking"
import OperationContainer from "core/containers/OperationContainer"
import App from "core/components/app" import App from "core/components/app"
import AuthorizationPopup from "core/components/auth/authorization-popup" import AuthorizationPopup from "core/components/auth/authorization-popup"
import AuthorizeBtn from "core/components/auth/authorize-btn" import AuthorizeBtn from "core/components/auth/authorize-btn"
@@ -118,7 +120,8 @@ export default function() {
VersionStamp, VersionStamp,
OperationExt, OperationExt,
OperationExtRow, OperationExtRow,
ParameterExt ParameterExt,
OperationContainer
} }
} }

View File

@@ -289,8 +289,7 @@ export default class Store {
getMapStateToProps() { getMapStateToProps() {
return () => { return () => {
let obj = Object.assign({}, this.getSystem()) return Object.assign({}, this.getSystem())
return obj
} }
} }

View File

@@ -565,6 +565,7 @@
p p
{ {
margin: 0; margin: 0;
@include text_code($response-col-description-inner-markdown-font-color);
} }
a a
@@ -575,6 +576,12 @@
color: $response-col-description-inner-markdown-link-font-color-hover; color: $response-col-description-inner-markdown-link-font-color-hover;
} }
} }
th
{
@include text_code($response-col-description-inner-markdown-font-color);
border-bottom: 1px solid $response-col-description-inner-markdown-font-color;
}
} }
} }

View File

@@ -1,7 +1,11 @@
/* eslint-env mocha */ /* eslint-env mocha */
import React, { PureComponent } from "react"
import expect from "expect" import expect from "expect"
import System from "core/system" import System from "core/system"
import { fromJS } from "immutable" import { fromJS } from "immutable"
import { render } from "enzyme"
import ViewPlugin from "core/plugins/view/index.js"
import { connect, Provider } from "react-redux"
describe("bound system", function(){ describe("bound system", function(){
@@ -444,4 +448,239 @@ describe("bound system", function(){
}) })
describe("getComponent", function() {
it("returns a component from the system", function() {
const system = new System({
plugins: [
ViewPlugin,
{
components: {
test: ({ name }) => <div>{name} component</div>
}
}
]
})
// When
var Component = system.getSystem().getComponent("test")
const renderedComponent = render(<Component name="Test" />)
expect(renderedComponent.text()).toEqual("Test component")
})
it("allows container components to provide their own `mapStateToProps` function", function() {
// Given
class ContainerComponent extends PureComponent {
mapStateToProps(nextState, props) {
return {
"fromMapState": "This came from mapStateToProps"
}
}
static defaultProps = {
"fromMapState" : ""
}
render() {
const { exampleSelectors, fromMapState, fromOwnProps } = this.props
return (
<div>{ fromMapState } {exampleSelectors.foo()} {fromOwnProps}</div>
)
}
}
const system = new System({
plugins: [
ViewPlugin,
{
components: {
ContainerComponent
}
},
{
statePlugins: {
example: {
selectors: {
foo() { return "and this came from the system" }
}
}
}
}
]
})
// When
var Component = system.getSystem().getComponent("ContainerComponent", true)
const renderedComponent = render(
<Provider store={system.getStore()}>
<Component fromOwnProps="and this came from my own props" />
</Provider>
)
// Then
expect(renderedComponent.text()).toEqual("This came from mapStateToProps and this came from the system and this came from my own props")
})
it("gives the system and own props as props to a container's `mapStateToProps` function", function() {
// Given
class ContainerComponent extends PureComponent {
mapStateToProps(nextState, props) {
const { exampleSelectors, fromMapState, fromOwnProps } = props
return {
"fromMapState": `This came from mapStateToProps ${exampleSelectors.foo()} ${fromOwnProps}`
}
}
static defaultProps = {
"fromMapState" : ""
}
render() {
const { fromMapState } = this.props
return (
<div>{ fromMapState }</div>
)
}
}
const system = new System({
plugins: [
ViewPlugin,
{
components: {
ContainerComponent
}
},
{
statePlugins: {
example: {
selectors: {
foo() { return "and this came from the system" }
}
}
}
}
]
})
// When
var Component = system.getSystem().getComponent("ContainerComponent", true)
const renderedComponent = render(
<Provider store={system.getStore()}>
<Component fromOwnProps="and this came from my own props" />
</Provider>
)
// Then
expect(renderedComponent.text()).toEqual("This came from mapStateToProps and this came from the system and this came from my own props")
})
it("should catch errors thrown inside of React Component Class render methods", function() {
// Given
// eslint-disable-next-line react/require-render-return
class BrokenComponent extends React.Component {
render() {
throw new Error("This component is broken")
}
}
const system = new System({
plugins: [
ViewPlugin,
{
components: {
BrokenComponent
}
}
]
})
// When
var Component = system.getSystem().getComponent("BrokenComponent")
const renderedComponent = render(<Component />)
// Then
expect(renderedComponent.text()).toEqual("😱 Could not render BrokenComponent, see the console.")
})
it("should catch errors thrown inside of pure component render methods", function() {
// Given
// eslint-disable-next-line react/require-render-return
class BrokenComponent extends PureComponent {
render() {
throw new Error("This component is broken")
}
}
const system = new System({
plugins: [
ViewPlugin,
{
components: {
BrokenComponent
}
}
]
})
// When
var Component = system.getSystem().getComponent("BrokenComponent")
const renderedComponent = render(<Component />)
// Then
expect(renderedComponent.text()).toEqual("😱 Could not render BrokenComponent, see the console.")
})
it("should catch errors thrown inside of stateless component functions", function() {
// Given
// eslint-disable-next-line react/require-render-return
let BrokenComponent = function BrokenComponent() { throw new Error("This component is broken") }
const system = new System({
plugins: [
ViewPlugin,
{
components: {
BrokenComponent
}
}
]
})
// When
var Component = system.getSystem().getComponent("BrokenComponent")
const renderedComponent = render(<Component />)
// Then
expect(renderedComponent.text().startsWith("😱 Could not render")).toEqual(true)
})
it("should catch errors thrown inside of container components", function() {
// Given
// eslint-disable-next-line react/require-render-return
class BrokenComponent extends React.Component {
render() {
throw new Error("This component is broken")
}
}
const system = new System({
plugins: [
ViewPlugin,
{
components: {
BrokenComponent
}
}
]
})
// When
var Component = system.getSystem().getComponent("BrokenComponent", true)
const renderedComponent = render(
<Provider store={system.getStore()}>
<Component />
</Provider>
)
// Then
expect(renderedComponent.text()).toEqual("😱 Could not render BrokenComponent, see the console.")
})
})
}) })