diff --git a/package.json b/package.json index e01e60e0..5de5597b 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "brace": "0.7.0", "classnames": "^2.2.5", "commonmark": "^0.28.1", + "core-js": "^2.5.1", "css.escape": "1.5.1", "deep-extend": "0.4.1", "expect": "1.20.2", @@ -70,7 +71,7 @@ "react-motion": "^0.5.2", "react-object-inspector": "0.2.1", "react-redux": "^4.x.x", - "react-split-pane": "0.1.57", + "react-split-pane": "0.1.70", "redux": "^3.x.x", "redux-immutable": "3.0.8", "redux-logger": "*", diff --git a/src/core/components/auth/api-key-auth.jsx b/src/core/components/auth/api-key-auth.jsx index 2d8a5f52..e9c926b5 100644 --- a/src/core/components/auth/api-key-auth.jsx +++ b/src/core/components/auth/api-key-auth.jsx @@ -60,6 +60,9 @@ export default class ApiKeyAuth extends React.Component { + +

Name: { schema.get("name") }

+

In: { schema.get("in") }

diff --git a/src/core/components/content-type.jsx b/src/core/components/content-type.jsx index 6f81f671..19d22188 100644 --- a/src/core/components/content-type.jsx +++ b/src/core/components/content-type.jsx @@ -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) render() { diff --git a/src/core/components/execute.jsx b/src/core/components/execute.jsx index 8d114b14..23546ad6 100644 --- a/src/core/components/execute.jsx +++ b/src/core/components/execute.jsx @@ -8,7 +8,6 @@ export default class Execute extends Component { specActions: PropTypes.object.isRequired, operation: PropTypes.object.isRequired, path: PropTypes.string.isRequired, - getComponent: PropTypes.func.isRequired, method: PropTypes.string.isRequired, onExecute: PropTypes.func } diff --git a/src/core/components/live-response.jsx b/src/core/components/live-response.jsx index cf9cf63b..67dd19c7 100644 --- a/src/core/components/live-response.jsx +++ b/src/core/components/live-response.jsx @@ -1,6 +1,7 @@ import React from "react" import PropTypes from "prop-types" import ImPropTypes from "react-immutable-proptypes" +import { Iterable } from "immutable" const Headers = ( { headers } )=>{ return ( @@ -28,19 +29,29 @@ Duration.propTypes = { export default class LiveResponse extends React.Component { static propTypes = { - response: PropTypes.object.isRequired, - specSelectors: PropTypes.object.isRequired, - pathMethod: PropTypes.object.isRequired, - getComponent: PropTypes.func.isRequired, + response: PropTypes.instanceOf(Iterable).isRequired, + path: PropTypes.string.isRequired, + method: PropTypes.string.isRequired, displayRequestDuration: PropTypes.bool.isRequired, + specSelectors: PropTypes.object.isRequired, + getComponent: 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() { - const { response, getComponent, getConfigs, displayRequestDuration, specSelectors, pathMethod } = this.props + const { response, getComponent, getConfigs, displayRequestDuration, specSelectors, path, method } = this.props 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 url = response.get("url") const headers = response.get("headers").toJS() @@ -118,7 +129,6 @@ export default class LiveResponse extends React.Component { static propTypes = { getComponent: PropTypes.func.isRequired, - request: ImPropTypes.map, response: ImPropTypes.map } } diff --git a/src/core/components/object-model.jsx b/src/core/components/object-model.jsx index db8adfd5..8746c7e4 100644 --- a/src/core/components/object-model.jsx +++ b/src/core/components/object-model.jsx @@ -76,13 +76,14 @@ export default class ObjectModel extends Component { { !(properties && properties.size) ? null : properties.entrySeq().map( ([key, value]) => { + let isDeprecated = isOAS3() && value.get("deprecated") let isRequired = List.isList(requiredProperties) && requiredProperties.contains(key) let propertyStyle = { verticalAlign: "top", paddingRight: "0.2em" } if ( isRequired ) { propertyStyle.fontWeight = "bold" } - return ( + return ( { key }{ isRequired && * } diff --git a/src/core/components/operation.jsx b/src/core/components/operation.jsx index 457ee863..7f3c8b9f 100644 --- a/src/core/components/operation.jsx +++ b/src/core/components/operation.jsx @@ -1,32 +1,22 @@ import React, { PureComponent } from "react" import PropTypes from "prop-types" import { getList } from "core/utils" -import * as CustomPropTypes from "core/proptypes" import { sanitizeUrl } from "core/utils" - -//import "less/opblock" +import { Iterable } from "immutable" export default class Operation extends PureComponent { static propTypes = { - path: PropTypes.string.isRequired, - method: PropTypes.string.isRequired, - operation: PropTypes.object.isRequired, - showSummary: PropTypes.bool, - isShown: PropTypes.bool.isRequired, + operation: PropTypes.instanceOf(Iterable).isRequired, + response: PropTypes.instanceOf(Iterable), + request: PropTypes.instanceOf(Iterable), - tagKey: PropTypes.string, - operationKey: PropTypes.string, - jumpToKey: CustomPropTypes.arrayOrString.isRequired, - - allowTryItOut: PropTypes.bool, - - displayOperationId: PropTypes.bool, - displayRequestDuration: PropTypes.bool, - - response: PropTypes.object, - request: PropTypes.object, + toggleShown: PropTypes.func.isRequired, + onTryoutClick: PropTypes.func.isRequired, + onCancelClick: PropTypes.func.isRequired, + onExecute: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired, + getConfigs: PropTypes.func.isRequired, authActions: PropTypes.object, authSelectors: PropTypes.object, specActions: PropTypes.object.isRequired, @@ -34,88 +24,66 @@ export default class Operation extends PureComponent { oas3Actions: PropTypes.object.isRequired, layoutActions: PropTypes.object.isRequired, layoutSelectors: PropTypes.object.isRequired, - fn: PropTypes.object.isRequired, - getConfigs: PropTypes.func.isRequired + fn: PropTypes.object.isRequired } static defaultProps = { - showSummary: true, + operation: null, response: null, - allowTryItOut: true, - 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 }) + request: null } render() { let { - operationKey, - tagKey, - isShown, - jumpToKey, - path, - method, - operation, - showSummary, response, request, - allowTryItOut, - displayOperationId, - displayRequestDuration, + toggleShown, + onTryoutClick, + onCancelClick, + onExecute, fn, getComponent, + getConfigs, specActions, specSelectors, authActions, authSelectors, - getConfigs, oas3Actions } = this.props + let operationProps = this.props.operation - let summary = operation.get("summary") - let description = operation.get("description") - let deprecated = operation.get("deprecated") - let externalDocs = operation.get("externalDocs") + let { + isShown, + jumpToKey, + 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 responses = operation.get("responses") - let security = operation.get("security") || specSelectors.security() let produces = operation.get("produces") - let schemes = operation.get("schemes") + let security = operation.get("security") || specSelectors.security() let parameters = getList(operation, ["parameters"]) - let operationId = operation.get("__originalOperationId") let operationScheme = specSelectors.operationScheme(path, method) + let isShownKey = ["operations", tag, operationId] const Responses = getComponent("responses") const Parameters = getComponent( "parameters" ) @@ -127,28 +95,23 @@ export default class Operation extends PureComponent { const Markdown = getComponent( "Markdown" ) const Schemes = getComponent( "schemes" ) - const { deepLinking } = getConfigs() - - const isDeepLinkingEnabled = deepLinking && deepLinking !== "false" - // Merge in Live Response if(responses && response && response.size > 0) { let notDocumented = !responses.get(String(response.get("status"))) 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 ) return ( -
-
+
+
{method.toUpperCase()} e.preventDefault() : null} - href={isDeepLinkingEnabled ? `#/${tagKey}/${operationKey}` : null}> + href={isDeepLinkingEnabled ? `#/${isShownKey.join("/")}` : null}> {path} @@ -200,8 +163,8 @@ export default class Operation extends PureComponent { parameters={parameters} operation={operation} onChangeKey={onChangeKey} - onTryoutClick = { this.onTryoutClick } - onCancelClick = { this.onCancelClick } + onTryoutClick = { onTryoutClick } + onCancelClick = { onCancelClick } tryItOutEnabled = { tryItOutEnabled } allowTryItOut={allowTryItOut} @@ -226,25 +189,23 @@ export default class Operation extends PureComponent { { !tryItOutEnabled || !allowTryItOut ? null : + onExecute={ onExecute } /> } { (!tryItOutEnabled || !response || !allowTryItOut) ? null : }
- {this.state.executeInProgress ?
: null} + {executeInProgress ?
: null} { !responses ? null : } diff --git a/src/core/components/operations.jsx b/src/core/components/operations.jsx index 1784fd8e..32b15437 100644 --- a/src/core/components/operations.jsx +++ b/src/core/components/operations.jsx @@ -1,8 +1,6 @@ import React from "react" import PropTypes from "prop-types" -import { helpers } from "swagger-client" import { createDeepLinkPath, sanitizeUrl } from "core/utils" -const { opId } = helpers export default class Operations extends React.Component { @@ -21,28 +19,20 @@ export default class Operations extends React.Component { render() { let { specSelectors, - specActions, - oas3Actions, getComponent, layoutSelectors, layoutActions, - authActions, - authSelectors, - getConfigs, - fn + getConfigs } = this.props let taggedOps = specSelectors.taggedOperations() - const Operation = getComponent("operation") + const OperationContainer = getComponent("OperationContainer", true) const Collapse = getComponent("Collapse") const Markdown = getComponent("Markdown") - let showSummary = layoutSelectors.showSummary() let { docExpansion, - displayOperationId, - displayRequestDuration, maxDisplayedTags, deepLinking } = getConfigs() @@ -120,49 +110,15 @@ export default class Operations extends React.Component { { operations.map( op => { + const path = op.get("path") + const method = op.get("method") - const path = op.get("path", "") - const method = op.get("method", "") - const jumpToKey = `paths.${path}.${method}` - - const operationId = - op.getIn(["operation", "operationId"]) || op.getIn(["operation", "__originalOperationId"]) || opId(op.get("operation"), path, method) || op.get("id") - 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 }).toArray() } diff --git a/src/core/components/response.jsx b/src/core/components/response.jsx index 4272b851..e839d405 100644 --- a/src/core/components/response.jsx +++ b/src/core/components/response.jsx @@ -1,7 +1,7 @@ import React from "react" import PropTypes from "prop-types" import cx from "classnames" -import { fromJS, Seq } from "immutable" +import { fromJS, Seq, Iterable } from "immutable" import { getSampleSchema, fromJSOrdered } from "core/utils" const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => { @@ -42,7 +42,7 @@ export default class Response extends React.Component { static propTypes = { code: PropTypes.string.isRequired, - response: PropTypes.object, + response: PropTypes.instanceOf(Iterable), className: PropTypes.string, getComponent: PropTypes.func.isRequired, getConfigs: PropTypes.func.isRequired, diff --git a/src/core/components/responses.jsx b/src/core/components/responses.jsx index ebde3425..ea5ee486 100644 --- a/src/core/components/responses.jsx +++ b/src/core/components/responses.jsx @@ -1,41 +1,52 @@ import React from "react" import PropTypes from "prop-types" -import { fromJS } from "immutable" +import { fromJS, Iterable } from "immutable" import { defaultStatusCode, getAcceptControllingResponse } from "core/utils" export default class Responses extends React.Component { - static propTypes = { - request: PropTypes.object, - tryItOutResponse: PropTypes.object, - responses: PropTypes.object.isRequired, - produces: PropTypes.object, + tryItOutResponse: PropTypes.instanceOf(Iterable), + responses: PropTypes.instanceOf(Iterable).isRequired, + produces: PropTypes.instanceOf(Iterable), producesValue: PropTypes.any, + displayRequestDuration: PropTypes.bool.isRequired, + path: PropTypes.string.isRequired, + method: PropTypes.string.isRequired, getComponent: PropTypes.func.isRequired, getConfigs: PropTypes.func.isRequired, specSelectors: PropTypes.object.isRequired, specActions: PropTypes.object.isRequired, oas3Actions: PropTypes.object.isRequired, - pathMethod: PropTypes.array.isRequired, - displayRequestDuration: PropTypes.bool.isRequired, fn: PropTypes.object.isRequired } static defaultProps = { - request: null, tryItOutResponse: null, produces: fromJS(["application/json"]), 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 }) => { - const { oas3Actions, pathMethod } = this.props + const { oas3Actions, path, method } = this.props if(controlsAcceptHeader) { oas3Actions.setResponseContentType({ value, - pathMethod + path, + method }) } } @@ -43,7 +54,6 @@ export default class Responses extends React.Component { render() { let { responses, - request, tryItOutResponse, getComponent, getConfigs, @@ -81,12 +91,12 @@ export default class Responses extends React.Component { { !tryItOutResponse ? null :
-

Responses

diff --git a/src/core/containers/OperationContainer.jsx b/src/core/containers/OperationContainer.jsx new file mode 100644 index 00000000..4f224855 --- /dev/null +++ b/src/core/containers/OperationContainer.jsx @@ -0,0 +1,194 @@ +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, + displayRequestDuration: PropTypes.bool, + response: PropTypes.instanceOf(Iterable), + request: 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 + + return { + operationId, + isDeepLinkingEnabled, + showSummary, + displayOperationId, + displayRequestDuration, + allowTryItOut, + 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, + 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, + operationId, + showSummary, + isShown, + jumpToKey, + allowTryItOut, + request, + displayOperationId, + displayRequestDuration, + isDeepLinkingEnabled, + executeInProgress: this.state.executeInProgress, + tryItOutEnabled: this.state.tryItOutEnabled + }) + + return ( + + ) + } + +} diff --git a/src/core/index.js b/src/core/index.js index 76a0d705..7298043a 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -7,8 +7,7 @@ import * as AllPlugins from "core/plugins/all" import { parseSearch } from "core/utils" if (process.env.NODE_ENV !== "production") { - const Perf = require("react-addons-perf") - window.Perf = Perf + window.Perf = require("react-addons-perf") } // eslint-disable-next-line no-undef diff --git a/src/core/plugins/oas3/actions.js b/src/core/plugins/oas3/actions.js index ad81f3e7..c9abc855 100644 --- a/src/core/plugins/oas3/actions.js +++ b/src/core/plugins/oas3/actions.js @@ -28,10 +28,10 @@ export function setRequestContentType ({ value, pathMethod }) { } } -export function setResponseContentType ({ value, pathMethod }) { +export function setResponseContentType ({ value, path, method }) { return { type: UPDATE_RESPONSE_CONTENT_TYPE, - payload: { value, pathMethod } + payload: { value, path, method } } } diff --git a/src/core/plugins/oas3/components/callbacks.jsx b/src/core/plugins/oas3/components/callbacks.jsx index 9290c6fe..43437375 100644 --- a/src/core/plugins/oas3/components/callbacks.jsx +++ b/src/core/plugins/oas3/components/callbacks.jsx @@ -1,10 +1,12 @@ import React from "react" import PropTypes from "prop-types" +import ImPropTypes from "react-immutable-proptypes" +import { fromJS } from "immutable" const Callbacks = (props) => { let { callbacks, getComponent } = props // const Markdown = getComponent("Markdown") - const Operation = getComponent("operation", true) + const OperationContainer = getComponent("OperationContainer", true) if(!callbacks) { return No callbacks @@ -16,24 +18,22 @@ const Callbacks = (props) => { { callback.map((pathItem, pathItemName) => { return
{ pathItem.map((operation, method) => { - return - // return
{JSON.stringify(operation)}
+ /> }) }
}) }
- // return
- //

{name}

- // {callback.description && } - //
{JSON.stringify(callback)}
- //
}) return
{callbackElements} @@ -42,7 +42,7 @@ const Callbacks = (props) => { Callbacks.propTypes = { getComponent: PropTypes.func.isRequired, - callbacks: PropTypes.array.isRequired + callbacks: ImPropTypes.iterable.isRequired } diff --git a/src/core/plugins/oas3/components/request-body-editor.jsx b/src/core/plugins/oas3/components/request-body-editor.jsx index 62f91a52..ff4c4dae 100644 --- a/src/core/plugins/oas3/components/request-body-editor.jsx +++ b/src/core/plugins/oas3/components/request-body-editor.jsx @@ -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) => { this.onChange(this.sample(explicitMediaType)) } diff --git a/src/core/plugins/oas3/components/request-body.jsx b/src/core/plugins/oas3/components/request-body.jsx index 5b20a50c..4c13f1d6 100644 --- a/src/core/plugins/oas3/components/request-body.jsx +++ b/src/core/plugins/oas3/components/request-body.jsx @@ -22,6 +22,10 @@ const RequestBody = ({ const mediaTypeValue = requestBodyContent.get(contentType) + if(!mediaTypeValue) { + return null + } + return
{ requestBodyDescription && diff --git a/src/core/plugins/oas3/reducers.js b/src/core/plugins/oas3/reducers.js index 149f55e3..8590284d 100644 --- a/src/core/plugins/oas3/reducers.js +++ b/src/core/plugins/oas3/reducers.js @@ -18,8 +18,7 @@ export default { let [path, method] = pathMethod return state.setIn( [ "requestData", path, method, "requestContentType" ], value) }, - [UPDATE_RESPONSE_CONTENT_TYPE]: (state, { payload: { value, pathMethod } } ) =>{ - let [path, method] = pathMethod + [UPDATE_RESPONSE_CONTENT_TYPE]: (state, { payload: { value, path, method } } ) =>{ return state.setIn( [ "requestData", path, method, "responseContentType" ], value) }, [UPDATE_SERVER_VARIABLE_VALUE]: (state, { payload: { server, key, val } } ) =>{ diff --git a/src/core/plugins/view/root-injects.js b/src/core/plugins/view/root-injects.js index 1d996102..d977400d 100644 --- a/src/core/plugins/view/root-injects.js +++ b/src/core/plugins/view/root-injects.js @@ -20,8 +20,14 @@ const RootWrapper = (reduxStore, ComponentToWrap) => class extends Component { } 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 connected = connect(state => ({state}))(wrappedWithSystem) + let connected = connect( mapStateToProps )(wrappedWithSystem) if(reduxStore) return RootWrapper(reduxStore, connected) return connected @@ -114,5 +120,5 @@ export const getComponent = (getSystem, getStore, getComponents, componentName, return makeContainer(getSystem, component, getStore()) // container == truthy - return makeContainer(getSystem, component) + return makeContainer(getSystem, wrapRender(component)) } diff --git a/src/core/presets/base.js b/src/core/presets/base.js index d173336e..d608b544 100644 --- a/src/core/presets/base.js +++ b/src/core/presets/base.js @@ -13,6 +13,8 @@ import downloadUrlPlugin from "core/plugins/download-url" import configsPlugin from "plugins/configs" import deepLinkingPlugin from "core/plugins/deep-linking" +import OperationContainer from "core/containers/OperationContainer" + import App from "core/components/app" import AuthorizationPopup from "core/components/auth/authorization-popup" import AuthorizeBtn from "core/components/auth/authorize-btn" @@ -112,7 +114,8 @@ export default function() { TryItOutButton, Markdown, BaseLayout, - VersionStamp + VersionStamp, + OperationContainer } } diff --git a/src/core/system.js b/src/core/system.js index e4eee5b8..06071bc9 100644 --- a/src/core/system.js +++ b/src/core/system.js @@ -289,8 +289,7 @@ export default class Store { getMapStateToProps() { return () => { - let obj = Object.assign({}, this.getSystem()) - return obj + return Object.assign({}, this.getSystem()) } } diff --git a/src/style/_layout.scss b/src/style/_layout.scss index 327c7633..786b9f80 100644 --- a/src/style/_layout.scss +++ b/src/style/_layout.scss @@ -575,6 +575,12 @@ 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; + } } } diff --git a/src/style/_models.scss b/src/style/_models.scss index feb46a87..5b69b3fd 100644 --- a/src/style/_models.scss +++ b/src/style/_models.scss @@ -12,6 +12,10 @@ { color: $model-deprecated-font-color !important; } + + > td:first-of-type { + text-decoration: line-through; + } } &-toggle { diff --git a/test/core/system/system.js b/test/core/system/system.js index ada3a93b..073390fa 100644 --- a/test/core/system/system.js +++ b/test/core/system/system.js @@ -1,7 +1,11 @@ /* eslint-env mocha */ +import React, { PureComponent } from "react" import expect from "expect" import System from "core/system" 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(){ @@ -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 }) =>
{name} component
+ } + } + ] + }) + + // When + var Component = system.getSystem().getComponent("test") + const renderedComponent = render() + 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 ( +
{ fromMapState } {exampleSelectors.foo()} {fromOwnProps}
+ ) + } + } + 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( + + + + ) + + // 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 ( +
{ fromMapState }
+ ) + } + } + 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( + + + + ) + + // 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() + + // 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() + + // 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() + + // 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( + + + + ) + + // Then + expect(renderedComponent.text()).toEqual("😱 Could not render BrokenComponent, see the console.") + }) + }) + })