feat: Multiple Examples for OpenAPI 3 Parameters, Request Bodies, and Responses (via #5427)
* add opt-in Prettier config * remove legacy `examples` implementation * create ExamplesSelect * support `Response.examples` in OpenAPI 3 * create response controls group * prettier reformat * prepare to break up Parameters * reunify Parameters and OAS3 Parameters * Parameter Examples * Example component * handle parameter value stringification correctly * FOR REVIEW: add prop for controlling Select * use regular header for param examples in Try-It-Out * manage active examples member via Redux * Request Body Try-It-Out examples * remove special Response description styling * omit Example value display in Try-It-Out * support disabled text inputs in JsonSchemaForm * Example.omitValue => Example.showValue * ExamplesSelectValueRetainer * styling for disabled inputs * remove console.log * support "Modified Values" in ExamplesSelect * remove Examples component (wasn't used anywhere) * use ParameterRow.getParamKey for active examples member keying * split-rendering of examples in ParameterRow * send disabled prop to JsonSchemaForm * use content type to key request body active examples members * remove debugger * rewire RequestBodyEditor to be a controlled component REVIEW: does this have perf implications? * trigger synthetic onSelect events in ExamplesSelect * prettier updates * remove outdated Examples usage in RequestBody * don't handle examples changes in ESVR * make RequestBodyEditor semi-controlled * don't default to an empty Map for request bodies * add namespaceKey to ESVR for state mgmt * don't key RequestBody activeExampleKeys on media type * tweak ESVR isModifiedValueSelected calculation * add trace class to ExamplesSelect * remove usage of ESVR.currentNamespace * reset to first example if currentExampleKey is invalid * add default values to RequestBody rendering * stringify things in ESVR * avoid null select value (silences React warning) * detect user inputs that match any examples member's value * add trace class for json-schema-array * shallowly convert namespace state, to preserve Immutable stucts in state * stringify RBE values; don't trim JSON in editor * match user input to an example when non-primitives are expressed in state as strings * update Cypress * don't apply sample values in JsonSchema_Object * support disabling all JsonSchemaForm subcomponents * Core tests * style changes to accomodate Examples * fix version-checking error in Response * disable SCU for Responses * don't stringify Select values * ModelExample: default to Model tab if no example is available; provide a default no example message * don't trim JSON ParamBody inputs * read directly from 2.0 Response.schema instead of inferring a value * show current Example information in RequestBody * show label for Examples dropdown by default * rework Response content ordering * style disabled textareas like other read-only blocks * meta: fix sourcemaps * refactor ESVR setNameForNamespace * protect second half of ternary expession * cypress: `select.examples-select` => `.examples-select > select` * clarify ModelExample.componentWillReceiveProps * add gates/defaults to prevent issues in very bare-boned documents * fix test block organization problem * simplify RequestBodyEditor interface * linter fixes * prettier updates * use plugin system for new components * move ME Cypress helpers to other file
This commit is contained in:
42
src/core/components/example.jsx
Normal file
42
src/core/components/example.jsx
Normal file
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import { stringify } from "core/utils"
|
||||
|
||||
export default function Example(props) {
|
||||
const { example, showValue, getComponent } = props
|
||||
|
||||
const Markdown = getComponent("Markdown")
|
||||
const HighlightCode = getComponent("highlightCode")
|
||||
|
||||
if(!example) return null
|
||||
|
||||
return (
|
||||
<div className="example">
|
||||
{example.get("description") ? (
|
||||
<section className="example__section">
|
||||
<div className="example__section-header">Example Description</div>
|
||||
<p>
|
||||
<Markdown source={example.get("description")} />
|
||||
</p>
|
||||
</section>
|
||||
) : null}
|
||||
{showValue && example.has("value") ? (
|
||||
<section className="example__section">
|
||||
<div className="example__section-header">Example Value</div>
|
||||
<HighlightCode value={stringify(example.get("value"))} />
|
||||
</section>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Example.propTypes = {
|
||||
example: ImPropTypes.map.isRequired,
|
||||
showValue: PropTypes.bool,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
}
|
||||
222
src/core/components/examples-select-value-retainer.jsx
Normal file
222
src/core/components/examples-select-value-retainer.jsx
Normal file
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import React from "react"
|
||||
import { Map, List } from "immutable"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
|
||||
import { stringify } from "core/utils"
|
||||
|
||||
// This stateful component lets us avoid writing competing values (user
|
||||
// modifications vs example values) into global state, and the mess that comes
|
||||
// with that: tracking which of the two values are currently used for
|
||||
// Try-It-Out, which example a modified value came from, etc...
|
||||
//
|
||||
// The solution here is to retain the last user-modified value in
|
||||
// ExamplesSelectValueRetainer's component state, so that our global state can stay
|
||||
// clean, always simply being the source of truth for what value should be both
|
||||
// displayed to the user and used as a value during request execution.
|
||||
//
|
||||
// This approach/tradeoff was chosen in order to encapsulate the particular
|
||||
// logic of Examples within the Examples component tree, and to avoid
|
||||
// regressions within our current implementation elsewhere (non-Examples
|
||||
// definitions, OpenAPI 2.0, etc). A future refactor to global state might make
|
||||
// this component unnecessary.
|
||||
//
|
||||
// TL;DR: this is not our usual approach, but the choice was made consciously.
|
||||
|
||||
// Note that `currentNamespace` isn't currently used anywhere!
|
||||
|
||||
const stringifyUnlessList = input =>
|
||||
List.isList(input) ? input : stringify(input)
|
||||
|
||||
export default class ExamplesSelectValueRetainer extends React.PureComponent {
|
||||
static propTypes = {
|
||||
examples: ImPropTypes.map,
|
||||
onSelect: PropTypes.func,
|
||||
updateValue: PropTypes.func, // mechanism to update upstream value
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
currentUserInputValue: PropTypes.any,
|
||||
currentKey: PropTypes.string,
|
||||
currentNamespace: PropTypes.string,
|
||||
// (also proxies props for Examples)
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
examples: Map({}),
|
||||
currentNamespace: "__DEFAULT__NAMESPACE__",
|
||||
onSelect: (...args) =>
|
||||
console.log( // eslint-disable-line no-console
|
||||
"ExamplesSelectValueRetainer: no `onSelect` function was provided",
|
||||
...args
|
||||
),
|
||||
updateValue: (...args) =>
|
||||
console.log( // eslint-disable-line no-console
|
||||
"ExamplesSelectValueRetainer: no `updateValue` function was provided",
|
||||
...args
|
||||
),
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
const valueFromExample = this._getCurrentExampleValue()
|
||||
|
||||
this.state = {
|
||||
// user edited: last value that came from the world around us, and didn't
|
||||
// match the current example's value
|
||||
// internal: last value that came from user selecting an Example
|
||||
[props.currentNamespace]: Map({
|
||||
lastUserEditedValue: this.props.currentUserInputValue,
|
||||
lastDownstreamValue: valueFromExample,
|
||||
isModifiedValueSelected:
|
||||
// valueFromExample !== undefined &&
|
||||
this.props.currentUserInputValue !== valueFromExample,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
_getStateForCurrentNamespace = () => {
|
||||
const { currentNamespace } = this.props
|
||||
|
||||
return (this.state[currentNamespace] || Map()).toObject()
|
||||
}
|
||||
|
||||
_setStateForCurrentNamespace = obj => {
|
||||
const { currentNamespace } = this.props
|
||||
|
||||
return this._setStateForNamespace(currentNamespace, obj)
|
||||
}
|
||||
|
||||
_setStateForNamespace = (namespace, obj) => {
|
||||
const oldStateForNamespace = this.state[namespace] || Map()
|
||||
const newStateForNamespace = oldStateForNamespace.mergeDeep(obj)
|
||||
return this.setState({
|
||||
[namespace]: newStateForNamespace,
|
||||
})
|
||||
}
|
||||
|
||||
_isCurrentUserInputSameAsExampleValue = () => {
|
||||
const { currentUserInputValue } = this.props
|
||||
|
||||
const valueFromExample = this._getCurrentExampleValue()
|
||||
|
||||
return valueFromExample === currentUserInputValue
|
||||
}
|
||||
|
||||
_getValueForExample = (exampleKey, props) => {
|
||||
// props are accepted so that this can be used in componentWillReceiveProps,
|
||||
// which has access to `nextProps`
|
||||
const { examples } = props || this.props
|
||||
return stringifyUnlessList(
|
||||
(examples || Map({})).getIn([exampleKey, "value"])
|
||||
)
|
||||
}
|
||||
|
||||
_getCurrentExampleValue = props => {
|
||||
// props are accepted so that this can be used in componentWillReceiveProps,
|
||||
// which has access to `nextProps`
|
||||
const { currentKey } = props || this.props
|
||||
return this._getValueForExample(currentKey, props || this.props)
|
||||
}
|
||||
|
||||
_onExamplesSelect = (key, { isSyntheticChange } = {}, ...otherArgs) => {
|
||||
const { onSelect, updateValue, currentUserInputValue } = this.props
|
||||
const { lastUserEditedValue } = this._getStateForCurrentNamespace()
|
||||
|
||||
const valueFromExample = this._getValueForExample(key)
|
||||
|
||||
if (key === "__MODIFIED__VALUE__") {
|
||||
updateValue(stringifyUnlessList(lastUserEditedValue))
|
||||
return this._setStateForCurrentNamespace({
|
||||
isModifiedValueSelected: true,
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof onSelect === "function") {
|
||||
onSelect(key, { isSyntheticChange }, ...otherArgs)
|
||||
}
|
||||
|
||||
this._setStateForCurrentNamespace({
|
||||
lastDownstreamValue: valueFromExample,
|
||||
isModifiedValueSelected:
|
||||
isSyntheticChange &&
|
||||
!!currentUserInputValue &&
|
||||
currentUserInputValue !== valueFromExample,
|
||||
})
|
||||
|
||||
// we never want to send up value updates from synthetic changes
|
||||
if (isSyntheticChange) return
|
||||
|
||||
if (typeof updateValue === "function") {
|
||||
updateValue(stringifyUnlessList(valueFromExample))
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// update `lastUserEditedValue` as new currentUserInput values come in
|
||||
|
||||
const { currentUserInputValue: newValue, examples, onSelect } = nextProps
|
||||
|
||||
const {
|
||||
lastUserEditedValue,
|
||||
lastDownstreamValue,
|
||||
} = this._getStateForCurrentNamespace()
|
||||
|
||||
const valueFromCurrentExample = this._getValueForExample(
|
||||
nextProps.currentKey,
|
||||
nextProps
|
||||
)
|
||||
|
||||
const exampleMatchingNewValue = examples.find(
|
||||
example =>
|
||||
example.get("value") === newValue ||
|
||||
// sometimes data is stored as a string (e.g. in Request Bodies), so
|
||||
// let's check against a stringified version of our example too
|
||||
stringify(example.get("value")) === newValue
|
||||
)
|
||||
|
||||
if (exampleMatchingNewValue) {
|
||||
onSelect(examples.keyOf(exampleMatchingNewValue), {
|
||||
isSyntheticChange: true,
|
||||
})
|
||||
} else if (
|
||||
newValue !== this.props.currentUserInputValue && // value has changed
|
||||
newValue !== lastUserEditedValue && // value isn't already tracked
|
||||
newValue !== lastDownstreamValue // value isn't what we've seen on the other side
|
||||
) {
|
||||
this._setStateForNamespace(nextProps.currentNamespace, {
|
||||
lastUserEditedValue: nextProps.currentUserInputValue,
|
||||
isModifiedValueSelected: newValue !== valueFromCurrentExample,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { currentUserInputValue, examples, currentKey, getComponent } = this.props
|
||||
const {
|
||||
lastDownstreamValue,
|
||||
lastUserEditedValue,
|
||||
isModifiedValueSelected,
|
||||
} = this._getStateForCurrentNamespace()
|
||||
|
||||
const ExamplesSelect = getComponent("ExamplesSelect")
|
||||
|
||||
return (
|
||||
<ExamplesSelect
|
||||
examples={examples}
|
||||
currentExampleKey={currentKey}
|
||||
onSelect={this._onExamplesSelect}
|
||||
isModifiedValueAvailable={
|
||||
!!lastUserEditedValue && lastUserEditedValue !== lastDownstreamValue
|
||||
}
|
||||
isValueModified={
|
||||
currentUserInputValue !== undefined &&
|
||||
isModifiedValueSelected &&
|
||||
currentUserInputValue !== this._getCurrentExampleValue()
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
138
src/core/components/examples-select.jsx
Normal file
138
src/core/components/examples-select.jsx
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
import React from "react"
|
||||
import Im from "immutable"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
|
||||
export default class ExamplesSelect extends React.PureComponent {
|
||||
static propTypes = {
|
||||
examples: ImPropTypes.map.isRequired,
|
||||
onSelect: PropTypes.func,
|
||||
currentExampleKey: PropTypes.string,
|
||||
isModifiedValueAvailable: PropTypes.bool,
|
||||
isValueModified: PropTypes.bool,
|
||||
showLabels: PropTypes.bool,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
examples: Im.Map({}),
|
||||
onSelect: (...args) =>
|
||||
console.log( // eslint-disable-line no-console
|
||||
// FIXME: remove before merging to master...
|
||||
`DEBUG: ExamplesSelect was not given an onSelect callback`,
|
||||
...args
|
||||
),
|
||||
currentExampleKey: null,
|
||||
showLabels: true,
|
||||
}
|
||||
|
||||
_onSelect = (key, { isSyntheticChange = false } = {}) => {
|
||||
if (typeof this.props.onSelect === "function") {
|
||||
this.props.onSelect(key, {
|
||||
isSyntheticChange,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_onDomSelect = e => {
|
||||
if (typeof this.props.onSelect === "function") {
|
||||
const element = e.target.selectedOptions[0]
|
||||
const key = element.getAttribute("value")
|
||||
|
||||
this._onSelect(key, {
|
||||
isSyntheticChange: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentExample = () => {
|
||||
const { examples, currentExampleKey } = this.props
|
||||
|
||||
const currentExamplePerProps = examples.get(currentExampleKey)
|
||||
|
||||
const firstExamplesKey = examples.keySeq().first()
|
||||
const firstExample = examples.get(firstExamplesKey)
|
||||
|
||||
return currentExamplePerProps || firstExample || Map({})
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// this is the not-so-great part of ExamplesSelect... here we're
|
||||
// artificially kicking off an onSelect event in order to set a default
|
||||
// value in state. the consumer has the option to avoid this by checking
|
||||
// `isSyntheticEvent`, but we should really be doing this in a selector.
|
||||
// TODO: clean this up
|
||||
// FIXME: should this only trigger if `currentExamplesKey` is nullish?
|
||||
const { onSelect, examples } = this.props
|
||||
|
||||
if (typeof onSelect === "function") {
|
||||
const firstExample = examples.first()
|
||||
const firstExampleKey = examples.keyOf(firstExample)
|
||||
|
||||
this._onSelect(firstExampleKey, {
|
||||
isSyntheticChange: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const { currentExampleKey, examples } = nextProps
|
||||
if (examples !== this.props.examples && !examples.has(currentExampleKey)) {
|
||||
// examples have changed from under us, and the currentExampleKey is no longer
|
||||
// valid.
|
||||
const firstExample = examples.first()
|
||||
const firstExampleKey = examples.keyOf(firstExample)
|
||||
|
||||
this._onSelect(firstExampleKey, {
|
||||
isSyntheticChange: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
examples,
|
||||
currentExampleKey,
|
||||
isValueModified,
|
||||
isModifiedValueAvailable,
|
||||
showLabels,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className="examples-select">
|
||||
{
|
||||
showLabels ? (
|
||||
<span className="examples-select__section-label">Examples: </span>
|
||||
) : null
|
||||
}
|
||||
<select
|
||||
onChange={this._onDomSelect}
|
||||
value={
|
||||
isModifiedValueAvailable && isValueModified
|
||||
? "__MODIFIED__VALUE__"
|
||||
: (currentExampleKey || "")
|
||||
}
|
||||
>
|
||||
{isModifiedValueAvailable ? (
|
||||
<option value="__MODIFIED__VALUE__">[Modified value]</option>
|
||||
) : null}
|
||||
{examples
|
||||
.map((example, exampleName) => {
|
||||
return (
|
||||
<option
|
||||
key={exampleName} // for React
|
||||
value={exampleName} // for matching to select's `value`
|
||||
>
|
||||
{example.get("summary") || exampleName}
|
||||
</option>
|
||||
)
|
||||
})
|
||||
.valueSeq()}
|
||||
</select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,8 @@ export class Select extends React.Component {
|
||||
onChange: PropTypes.func,
|
||||
multiple: PropTypes.bool,
|
||||
allowEmptyValue: PropTypes.bool,
|
||||
className: PropTypes.string
|
||||
className: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
@@ -176,12 +177,19 @@ export class Select extends React.Component {
|
||||
onChange && onChange(value)
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// TODO: this puts us in a weird area btwn un/controlled selection... review
|
||||
if(nextProps.value !== this.props.value) {
|
||||
this.setState({ value: nextProps.value })
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
let { allowedValues, multiple, allowEmptyValue } = this.props
|
||||
let { allowedValues, multiple, allowEmptyValue, disabled } = this.props
|
||||
let value = this.state.value.toJS ? this.state.value.toJS() : this.state.value
|
||||
|
||||
return (
|
||||
<select className={this.props.className} multiple={ multiple } value={ value } onChange={ this.onChange } >
|
||||
<select className={this.props.className} multiple={ multiple } value={value} onChange={ this.onChange } disabled={disabled} >
|
||||
{ allowEmptyValue ? <option value="">--</option> : null }
|
||||
{
|
||||
allowedValues.map(function (item, key) {
|
||||
|
||||
@@ -17,11 +17,19 @@ export default class ModelExample extends React.Component {
|
||||
super(props, context)
|
||||
let { getConfigs, isExecute } = this.props
|
||||
let { defaultModelRendering } = getConfigs()
|
||||
|
||||
let activeTab = defaultModelRendering
|
||||
|
||||
if (defaultModelRendering !== "example" && defaultModelRendering !== "model") {
|
||||
defaultModelRendering = "example"
|
||||
activeTab = "example"
|
||||
}
|
||||
|
||||
if(isExecute) {
|
||||
activeTab = "example"
|
||||
}
|
||||
|
||||
this.state = {
|
||||
activeTab: isExecute ? "example" : defaultModelRendering
|
||||
activeTab: activeTab
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +41,12 @@ export default class ModelExample extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props) {
|
||||
if (props.isExecute && props.isExecute !== this.props.isExecute) {
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (
|
||||
nextProps.isExecute &&
|
||||
!this.props.isExecute &&
|
||||
this.props.example
|
||||
) {
|
||||
this.setState({ activeTab: "example" })
|
||||
}
|
||||
}
|
||||
@@ -43,10 +55,11 @@ export default class ModelExample extends React.Component {
|
||||
let { getComponent, specSelectors, schema, example, isExecute, getConfigs, specPath } = this.props
|
||||
let { defaultModelExpandDepth } = getConfigs()
|
||||
const ModelWrapper = getComponent("ModelWrapper")
|
||||
const HighlightCode = getComponent("highlightCode")
|
||||
|
||||
let isOAS3 = specSelectors.isOAS3()
|
||||
|
||||
return <div>
|
||||
return <div className="model-example">
|
||||
<ul className="tab">
|
||||
<li className={ "tabitem" + ( this.state.activeTab === "example" ? " active" : "") }>
|
||||
<a className="tablinks" data-name="example" onClick={ this.activeTab }>{isExecute ? "Edit Value" : "Example Value"}</a>
|
||||
@@ -59,7 +72,11 @@ export default class ModelExample extends React.Component {
|
||||
</ul>
|
||||
<div>
|
||||
{
|
||||
this.state.activeTab === "example" && example
|
||||
this.state.activeTab === "example" ? (
|
||||
example ? example : (
|
||||
<HighlightCode value="(no example available)" />
|
||||
)
|
||||
) : null
|
||||
}
|
||||
{
|
||||
this.state.activeTab === "model" && <ModelWrapper schema={ schema }
|
||||
|
||||
@@ -156,6 +156,8 @@ export default class Operation extends PureComponent {
|
||||
specSelectors={ specSelectors }
|
||||
pathMethod={ [path, method] }
|
||||
getConfigs={ getConfigs }
|
||||
oas3Actions={ oas3Actions }
|
||||
oas3Selectors={ oas3Selectors }
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -214,6 +216,7 @@ export default class Operation extends PureComponent {
|
||||
getConfigs={ getConfigs }
|
||||
specSelectors={ specSelectors }
|
||||
oas3Actions={oas3Actions}
|
||||
oas3Selectors={oas3Selectors}
|
||||
specActions={ specActions }
|
||||
produces={specSelectors.producesOptionsFor([path, method]) }
|
||||
producesValue={ specSelectors.currentProducesFor([path, method]) }
|
||||
|
||||
@@ -82,9 +82,8 @@ export default class ParamBody extends PureComponent {
|
||||
|
||||
handleOnChange = e => {
|
||||
const {consumesValue} = this.props
|
||||
const isJson = /json/i.test(consumesValue)
|
||||
const isXml = /xml/i.test(consumesValue)
|
||||
const inputValue = isJson ? e.target.value.trim() : e.target.value
|
||||
const inputValue = e.target.value
|
||||
this.onChange(inputValue, {isXml})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { Component } from "react"
|
||||
import { Map } from "immutable"
|
||||
import { Map, List } from "immutable"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import win from "core/window"
|
||||
import { getExtensions, getCommonExtensions, numberToString } from "core/utils"
|
||||
import { getExtensions, getCommonExtensions, numberToString, stringify } from "core/utils"
|
||||
|
||||
export default class ParameterRow extends Component {
|
||||
static propTypes = {
|
||||
@@ -18,7 +18,9 @@ export default class ParameterRow extends Component {
|
||||
specActions: PropTypes.object.isRequired,
|
||||
pathMethod: PropTypes.array.isRequired,
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
oas3Actions: PropTypes.object.isRequired,
|
||||
oas3Selectors: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
@@ -29,7 +31,7 @@ export default class ParameterRow extends Component {
|
||||
|
||||
componentWillReceiveProps(props) {
|
||||
let { specSelectors, pathMethod, rawParam } = props
|
||||
let { isOAS3 } = specSelectors
|
||||
let isOAS3 = specSelectors.isOAS3()
|
||||
|
||||
let parameterWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || new Map()
|
||||
// fallback, if the meta lookup fails
|
||||
@@ -37,7 +39,7 @@ export default class ParameterRow extends Component {
|
||||
|
||||
let enumValue
|
||||
|
||||
if(isOAS3()) {
|
||||
if(isOAS3) {
|
||||
let schema = parameterWithMeta.get("schema") || Map()
|
||||
enumValue = schema.get("enum")
|
||||
} else {
|
||||
@@ -74,6 +76,15 @@ export default class ParameterRow extends Component {
|
||||
return onChange(rawParam, valueForUpstream, isXml)
|
||||
}
|
||||
|
||||
_onExampleSelect = (key, /* { isSyntheticChange } = {} */) => {
|
||||
this.props.oas3Actions.setActiveExamplesMember({
|
||||
name: key,
|
||||
pathMethod: this.props.pathMethod,
|
||||
contextType: "parameters",
|
||||
contextName: this.getParamKey()
|
||||
})
|
||||
}
|
||||
|
||||
onChangeIncludeEmpty = (newValue) => {
|
||||
let { specActions, param, pathMethod } = this.props
|
||||
const paramName = param.get("name")
|
||||
@@ -82,10 +93,9 @@ export default class ParameterRow extends Component {
|
||||
}
|
||||
|
||||
setDefaultValue = () => {
|
||||
let { specSelectors, pathMethod, rawParam } = this.props
|
||||
|
||||
let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam)
|
||||
let { specSelectors, pathMethod, rawParam, oas3Selectors } = this.props
|
||||
|
||||
let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()
|
||||
|
||||
if (!paramWithMeta || paramWithMeta.get("value") !== undefined) {
|
||||
return
|
||||
@@ -100,20 +110,32 @@ export default class ParameterRow extends Component {
|
||||
|| paramWithMeta.getIn(["schema", "example"])
|
||||
|| paramWithMeta.getIn(["schema", "default"])
|
||||
} else if (specSelectors.isOAS3()) {
|
||||
newValue = paramWithMeta.get("example")
|
||||
const currentExampleKey = oas3Selectors.activeExamplesMember(...pathMethod, "parameters", this.getParamKey())
|
||||
newValue = paramWithMeta.getIn(["examples", currentExampleKey, "value"])
|
||||
|| paramWithMeta.get("example")
|
||||
|| paramWithMeta.getIn(["schema", "example"])
|
||||
|| paramWithMeta.getIn(["schema", "default"])
|
||||
}
|
||||
if(newValue !== undefined) {
|
||||
this.onChangeWrapper(numberToString(newValue))
|
||||
this.onChangeWrapper(
|
||||
List.isList(newValue) ? newValue : stringify(newValue)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let {param, rawParam, getComponent, getConfigs, isExecute, fn, onChangeConsumes, specSelectors, pathMethod, specPath} = this.props
|
||||
getParamKey() {
|
||||
const { param } = this.props
|
||||
|
||||
if(!param) return null
|
||||
|
||||
let { isOAS3 } = specSelectors
|
||||
return `${param.get("name")}-${param.get("in")}`
|
||||
}
|
||||
|
||||
render() {
|
||||
let {param, rawParam, getComponent, getConfigs, isExecute, fn, onChangeConsumes, specSelectors, pathMethod, specPath, oas3Selectors} = this.props
|
||||
|
||||
let isOAS3 = specSelectors.isOAS3()
|
||||
|
||||
const { showExtensions, showCommonExtensions } = getConfigs()
|
||||
|
||||
@@ -121,6 +143,8 @@ export default class ParameterRow extends Component {
|
||||
param = rawParam
|
||||
}
|
||||
|
||||
if(!rawParam) return null
|
||||
|
||||
// const onChangeWrapper = (value) => onChange(param, value)
|
||||
const JsonSchemaForm = getComponent("JsonSchemaForm")
|
||||
const ParamBody = getComponent("ParamBody")
|
||||
@@ -142,10 +166,12 @@ export default class ParameterRow extends Component {
|
||||
const Markdown = getComponent("Markdown")
|
||||
const ParameterExt = getComponent("ParameterExt")
|
||||
const ParameterIncludeEmpty = getComponent("ParameterIncludeEmpty")
|
||||
const ExamplesSelectValueRetainer = getComponent("ExamplesSelectValueRetainer")
|
||||
const Example = getComponent("Example")
|
||||
|
||||
let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam)
|
||||
let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()
|
||||
let format = param.get("format")
|
||||
let schema = isOAS3 && isOAS3() ? param.get("schema") : param
|
||||
let schema = isOAS3 ? param.get("schema") : param
|
||||
let type = schema.get("type")
|
||||
let isFormData = inType === "formData"
|
||||
let isFormDataSupported = "FormData" in win
|
||||
@@ -199,7 +225,7 @@ export default class ParameterRow extends Component {
|
||||
{ format && <span className="prop-format">(${format})</span>}
|
||||
</div>
|
||||
<div className="parameter__deprecated">
|
||||
{ isOAS3 && isOAS3() && param.get("deprecated") ? "deprecated": null }
|
||||
{ isOAS3 && param.get("deprecated") ? "deprecated": null }
|
||||
</div>
|
||||
<div className="parameter__in">({ param.get("in") })</div>
|
||||
{ !showCommonExtensions || !commonExt.size ? null : commonExt.map((v, key) => <ParameterExt key={`${key}-${v}`} xKey={key} xVal={v} /> )}
|
||||
@@ -224,11 +250,28 @@ export default class ParameterRow extends Component {
|
||||
|
||||
{(isFormData && !isFormDataSupported) && <div>Error: your browser does not support FormData</div>}
|
||||
|
||||
{ bodyParam || !isExecute ? null
|
||||
{
|
||||
isOAS3 && param.get("examples") ? (
|
||||
<section className="parameter-controls">
|
||||
<ExamplesSelectValueRetainer
|
||||
examples={param.get("examples")}
|
||||
onSelect={this._onExampleSelect}
|
||||
updateValue={this.onChangeWrapper}
|
||||
getComponent={getComponent}
|
||||
defaultToFirstExample={true}
|
||||
currentKey={oas3Selectors.activeExamplesMember(...pathMethod, "parameters", this.getParamKey())}
|
||||
currentUserInputValue={value}
|
||||
/>
|
||||
</section>
|
||||
) : null
|
||||
}
|
||||
|
||||
{ bodyParam ? null
|
||||
: <JsonSchemaForm fn={fn}
|
||||
getComponent={getComponent}
|
||||
value={ value }
|
||||
required={ required }
|
||||
disabled={!isExecute}
|
||||
description={param.get("description") ? `${param.get("name")} - ${param.get("description")}` : `${param.get("name")}`}
|
||||
onChange={ this.onChangeWrapper }
|
||||
errors={ paramWithMeta.get("errors") }
|
||||
@@ -257,6 +300,18 @@ export default class ParameterRow extends Component {
|
||||
: null
|
||||
}
|
||||
|
||||
{
|
||||
isOAS3 && param.get("examples") ? (
|
||||
<Example
|
||||
example={param.getIn([
|
||||
"examples",
|
||||
oas3Selectors.activeExamplesMember(...pathMethod, "parameters", this.getParamKey())
|
||||
])}
|
||||
getComponent={getComponent}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
@@ -265,3 +320,4 @@ export default class ParameterRow extends Component {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
import React, { Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import Im from "immutable"
|
||||
|
||||
// More readable, just iterate over maps, only
|
||||
const eachMap = (iterable, fn) => iterable.valueSeq().filter(Im.Map.isMap).map(fn)
|
||||
|
||||
export default class Parameters extends Component {
|
||||
|
||||
static propTypes = {
|
||||
parameters: ImPropTypes.list.isRequired,
|
||||
specActions: 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,
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
}
|
||||
|
||||
|
||||
static defaultProps = {
|
||||
onTryoutClick: Function.prototype,
|
||||
onCancelClick: Function.prototype,
|
||||
tryItOutEnabled: false,
|
||||
allowTryItOut: true,
|
||||
onChangeKey: [],
|
||||
specPath: [],
|
||||
}
|
||||
|
||||
onChange = ( param, value, isXml ) => {
|
||||
let {
|
||||
specActions: { changeParamByIdentity },
|
||||
onChangeKey,
|
||||
} = this.props
|
||||
|
||||
changeParamByIdentity(onChangeKey, param, value, isXml)
|
||||
}
|
||||
|
||||
onChangeConsumesWrapper = ( val ) => {
|
||||
let {
|
||||
specActions: { changeConsumesValue },
|
||||
onChangeKey
|
||||
} = this.props
|
||||
|
||||
changeConsumesValue(onChangeKey, val)
|
||||
}
|
||||
|
||||
render(){
|
||||
|
||||
let {
|
||||
onTryoutClick,
|
||||
onCancelClick,
|
||||
parameters,
|
||||
allowTryItOut,
|
||||
tryItOutEnabled,
|
||||
specPath,
|
||||
|
||||
fn,
|
||||
getComponent,
|
||||
getConfigs,
|
||||
specSelectors,
|
||||
specActions,
|
||||
pathMethod
|
||||
} = this.props
|
||||
|
||||
const ParameterRow = getComponent("parameterRow")
|
||||
const TryItOutButton = getComponent("TryItOutButton")
|
||||
|
||||
const isExecute = tryItOutEnabled && allowTryItOut
|
||||
|
||||
return (
|
||||
<div className="opblock-section">
|
||||
<div className="opblock-section-header">
|
||||
<div className="tab-header">
|
||||
<h4 className="opblock-title">Parameters</h4>
|
||||
</div>
|
||||
{ allowTryItOut ? (
|
||||
<TryItOutButton enabled={ tryItOutEnabled } onCancelClick={ onCancelClick } onTryoutClick={ onTryoutClick } />
|
||||
) : null }
|
||||
</div>
|
||||
{ !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, i) => (
|
||||
<ParameterRow
|
||||
fn={ fn }
|
||||
specPath={specPath.push(i.toString())}
|
||||
getComponent={ getComponent }
|
||||
getConfigs={ getConfigs }
|
||||
rawParam={ parameter }
|
||||
param={ specSelectors.parameterWithMetaByIdentity(pathMethod, parameter) }
|
||||
key={ `${parameter.get( "in" )}.${parameter.get("name")}` }
|
||||
onChange={ this.onChange }
|
||||
onChangeConsumes={this.onChangeConsumesWrapper}
|
||||
specSelectors={ specSelectors }
|
||||
specActions={specActions}
|
||||
pathMethod={ pathMethod }
|
||||
isExecute={ isExecute }/>
|
||||
)).toArray()
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
1
src/core/components/parameters/index.js
Normal file
1
src/core/components/parameters/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Parameters } from "./parameters"
|
||||
232
src/core/components/parameters/parameters.jsx
Normal file
232
src/core/components/parameters/parameters.jsx
Normal file
@@ -0,0 +1,232 @@
|
||||
import React, { Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import Im, { Map, List } from "immutable"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
|
||||
// More readable, just iterate over maps, only
|
||||
const eachMap = (iterable, fn) => iterable.valueSeq().filter(Im.Map.isMap).map(fn)
|
||||
|
||||
export default class Parameters extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
callbackVisible: false,
|
||||
parametersVisible: true
|
||||
}
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
parameters: ImPropTypes.list.isRequired,
|
||||
operation: PropTypes.object.isRequired,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
oas3Actions: PropTypes.object.isRequired,
|
||||
oas3Selectors: 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,
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
}
|
||||
|
||||
|
||||
static defaultProps = {
|
||||
onTryoutClick: Function.prototype,
|
||||
onCancelClick: Function.prototype,
|
||||
tryItOutEnabled: false,
|
||||
allowTryItOut: true,
|
||||
onChangeKey: [],
|
||||
specPath: [],
|
||||
}
|
||||
|
||||
onChange = ( param, value, isXml ) => {
|
||||
let {
|
||||
specActions: { changeParamByIdentity },
|
||||
onChangeKey,
|
||||
} = this.props
|
||||
|
||||
changeParamByIdentity(onChangeKey, param, 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,
|
||||
specPath,
|
||||
|
||||
fn,
|
||||
getComponent,
|
||||
getConfigs,
|
||||
specSelectors,
|
||||
specActions,
|
||||
pathMethod,
|
||||
oas3Actions,
|
||||
oas3Selectors,
|
||||
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.isOAS3()
|
||||
|
||||
const requestBody = operation.get("requestBody")
|
||||
return (
|
||||
<div className="opblock-section">
|
||||
<div className="opblock-section-header">
|
||||
{ isOAS3 ? (
|
||||
<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>
|
||||
) : (
|
||||
<div className="tab-header">
|
||||
<h4 className="opblock-title">Parameters</h4>
|
||||
</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, i) => (
|
||||
<ParameterRow
|
||||
fn={ fn }
|
||||
specPath={specPath.push(i.toString())}
|
||||
getComponent={ getComponent }
|
||||
getConfigs={ getConfigs }
|
||||
rawParam={ parameter }
|
||||
param={ specSelectors.parameterWithMetaByIdentity(pathMethod, parameter) }
|
||||
key={ `${parameter.get( "in" )}.${parameter.get("name")}` }
|
||||
onChange={ this.onChange }
|
||||
onChangeConsumes={this.onChangeConsumesWrapper}
|
||||
specSelectors={ specSelectors }
|
||||
specActions={specActions}
|
||||
oas3Actions={oas3Actions}
|
||||
oas3Selectors={oas3Selectors}
|
||||
pathMethod={ pathMethod }
|
||||
isExecute={ isExecute }/>
|
||||
)).toArray()
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div> : null }
|
||||
|
||||
{this.state.callbackVisible ? <div className="callbacks-container opblock-description-wrapper">
|
||||
<Callbacks
|
||||
callbacks={Map(operation.get("callbacks"))}
|
||||
specPath={specPath.slice(0, -1).push("callbacks")}
|
||||
/>
|
||||
</div> : null }
|
||||
{
|
||||
isOAS3 && requestBody && this.state.parametersVisible &&
|
||||
<div className="opblock-section opblock-section-request-body">
|
||||
<div className="opblock-section-header">
|
||||
<h4 className={`opblock-title parameter__name ${requestBody.get("required") && "required"}`}>Request body</h4>
|
||||
<label>
|
||||
<ContentType
|
||||
value={oas3Selectors.requestContentType(...pathMethod)}
|
||||
contentTypes={ requestBody.get("content", List()).keySeq() }
|
||||
onChange={(value) => {
|
||||
oas3Actions.setRequestContentType({ value, pathMethod })
|
||||
}}
|
||||
className="body-param-content-type" />
|
||||
</label>
|
||||
</div>
|
||||
<div className="opblock-description-wrapper">
|
||||
<RequestBody
|
||||
specPath={specPath.slice(0, -1).push("requestBody")}
|
||||
requestBody={requestBody}
|
||||
requestBodyValue={oas3Selectors.requestBodyValue(...pathMethod)}
|
||||
isExecute={isExecute}
|
||||
activeExamplesKey={oas3Selectors.activeExamplesMember(
|
||||
...pathMethod,
|
||||
"requestBody",
|
||||
"requestBody" // RBs are currently not stored per-mediaType
|
||||
)}
|
||||
updateActiveExamplesKey={key => {
|
||||
this.props.oas3Actions.setActiveExamplesMember({
|
||||
name: key,
|
||||
pathMethod: this.props.pathMethod,
|
||||
contextType: "requestBody",
|
||||
contextName: "requestBody" // RBs are currently not stored per-mediaType
|
||||
})
|
||||
}
|
||||
}
|
||||
onChange={(value, path) => {
|
||||
if(path) {
|
||||
const lastValue = oas3Selectors.requestBodyValue(...pathMethod)
|
||||
const usableValue = Map.isMap(lastValue) ? lastValue : Map()
|
||||
return oas3Actions.setRequestBodyValue({
|
||||
pathMethod,
|
||||
value: usableValue.setIn(path, value)
|
||||
})
|
||||
}
|
||||
oas3Actions.setRequestBodyValue({ value, pathMethod })
|
||||
}}
|
||||
contentType={oas3Selectors.requestContentType(...pathMethod)}/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,11 @@ import cx from "classnames"
|
||||
import { fromJS, Seq, Iterable, List, Map } from "immutable"
|
||||
import { getSampleSchema, fromJSOrdered, stringify } from "core/utils"
|
||||
|
||||
const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => {
|
||||
if ( examples && examples.size ) {
|
||||
return examples.entrySeq().map( ([ key, example ]) => {
|
||||
let exampleValue = stringify(example)
|
||||
|
||||
return (<div key={ key }>
|
||||
<h5>{ key }</h5>
|
||||
<HighlightCode className="example" value={ exampleValue } />
|
||||
</div>)
|
||||
}).toArray()
|
||||
}
|
||||
|
||||
if ( sampleResponse ) { return <div>
|
||||
const getExampleComponent = ( sampleResponse, HighlightCode ) => {
|
||||
if (
|
||||
sampleResponse !== undefined &&
|
||||
sampleResponse !== null
|
||||
) { return <div>
|
||||
<HighlightCode className="example" value={ sampleResponse } />
|
||||
</div>
|
||||
}
|
||||
@@ -29,20 +21,24 @@ export default class Response extends React.Component {
|
||||
super(props, context)
|
||||
|
||||
this.state = {
|
||||
responseContentType: ""
|
||||
responseContentType: "",
|
||||
}
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
path: PropTypes.string.isRequired,
|
||||
method: PropTypes.string.isRequired,
|
||||
code: PropTypes.string.isRequired,
|
||||
response: PropTypes.instanceOf(Iterable),
|
||||
className: PropTypes.string,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
oas3Actions: PropTypes.object.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
fn: PropTypes.object.isRequired,
|
||||
contentType: PropTypes.string,
|
||||
activeExamplesKey: PropTypes.string,
|
||||
controlsAcceptHeader: PropTypes.bool,
|
||||
onContentTypeChange: PropTypes.func
|
||||
}
|
||||
@@ -61,8 +57,21 @@ export default class Response extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
getTargetExamplesKey = () => {
|
||||
const { response, contentType, activeExamplesKey } = this.props
|
||||
|
||||
const activeContentType = this.state.responseContentType || contentType
|
||||
const activeMediaType = response.getIn(["content", activeContentType], Map({}))
|
||||
const examplesForMediaType = activeMediaType.get("examples", null)
|
||||
|
||||
const firstExamplesKey = examplesForMediaType.keySeq().first()
|
||||
return activeExamplesKey || firstExamplesKey
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
path,
|
||||
method,
|
||||
code,
|
||||
response,
|
||||
className,
|
||||
@@ -72,14 +81,14 @@ export default class Response extends React.Component {
|
||||
getConfigs,
|
||||
specSelectors,
|
||||
contentType,
|
||||
controlsAcceptHeader
|
||||
controlsAcceptHeader,
|
||||
oas3Actions,
|
||||
} = this.props
|
||||
|
||||
let { inferSchema } = fn
|
||||
let { isOAS3 } = specSelectors
|
||||
let isOAS3 = specSelectors.isOAS3()
|
||||
|
||||
let headers = response.get("headers")
|
||||
let examples = response.get("examples")
|
||||
let links = response.get("links")
|
||||
const Headers = getComponent("headers")
|
||||
const HighlightCode = getComponent("highlightCode")
|
||||
@@ -87,44 +96,53 @@ export default class Response extends React.Component {
|
||||
const Markdown = getComponent( "Markdown" )
|
||||
const OperationLink = getComponent("operationLink")
|
||||
const ContentType = getComponent("contentType")
|
||||
const ExamplesSelect = getComponent("ExamplesSelect")
|
||||
const Example = getComponent("Example")
|
||||
|
||||
|
||||
var sampleResponse
|
||||
var sampleSchema
|
||||
var schema, specPathWithPossibleSchema
|
||||
|
||||
const activeContentType = this.state.responseContentType || contentType
|
||||
const activeMediaType = response.getIn(["content", activeContentType], Map({}))
|
||||
const examplesForMediaType = activeMediaType.get("examples", null)
|
||||
|
||||
if(isOAS3()) {
|
||||
const mediaType = response.getIn(["content", activeContentType], Map({}))
|
||||
const oas3SchemaForContentType = mediaType.get("schema", Map({}))
|
||||
// Goal: find a schema value for `schema`
|
||||
if(isOAS3) {
|
||||
const oas3SchemaForContentType = activeMediaType.get("schema", Map({}))
|
||||
|
||||
if(mediaType.get("example") !== undefined) {
|
||||
sampleSchema = stringify(mediaType.get("example"))
|
||||
} else {
|
||||
sampleSchema = getSampleSchema(oas3SchemaForContentType.toJS(), this.state.responseContentType, {
|
||||
includeReadOnly: true
|
||||
})
|
||||
}
|
||||
sampleResponse = oas3SchemaForContentType ? sampleSchema : null
|
||||
schema = oas3SchemaForContentType ? inferSchema(oas3SchemaForContentType.toJS()) : null
|
||||
specPathWithPossibleSchema = oas3SchemaForContentType ? List(["content", this.state.responseContentType, "schema"]) : specPath
|
||||
} else {
|
||||
schema = inferSchema(response.toJS()) // TODO: don't convert back and forth. Lets just stick with immutable for inferSchema
|
||||
schema = response.get("schema")
|
||||
specPathWithPossibleSchema = response.has("schema") ? specPath.push("schema") : specPath
|
||||
sampleResponse = schema ? getSampleSchema(schema, activeContentType, {
|
||||
}
|
||||
|
||||
// Goal: find an example value for `sampleResponse`
|
||||
if(isOAS3) {
|
||||
const oas3SchemaForContentType = activeMediaType.get("schema", Map({}))
|
||||
|
||||
if(examplesForMediaType) {
|
||||
const targetExamplesKey = this.getTargetExamplesKey()
|
||||
const targetExample = examplesForMediaType.get(targetExamplesKey, Map({}))
|
||||
sampleResponse = stringify(targetExample.get("value"))
|
||||
} else if(activeMediaType.get("example") !== undefined) {
|
||||
// use the example key's value
|
||||
sampleResponse = stringify(activeMediaType.get("example"))
|
||||
} else {
|
||||
// use an example value generated based on the schema
|
||||
sampleResponse = getSampleSchema(oas3SchemaForContentType.toJS(), this.state.responseContentType, {
|
||||
includeReadOnly: true
|
||||
})
|
||||
}
|
||||
} else {
|
||||
sampleResponse = schema ? getSampleSchema(schema.toJS(), activeContentType, {
|
||||
includeReadOnly: true,
|
||||
includeWriteOnly: true // writeOnly has no filtering effect in swagger 2.0
|
||||
}) : null
|
||||
}
|
||||
|
||||
if(examples) {
|
||||
examples = examples.map(example => {
|
||||
// Remove unwanted properties from examples
|
||||
return example.set ? example.set("$$ref", undefined) : example
|
||||
})
|
||||
}
|
||||
|
||||
let example = getExampleComponent( sampleResponse, examples, HighlightCode )
|
||||
let example = getExampleComponent( sampleResponse, HighlightCode )
|
||||
|
||||
return (
|
||||
<tr className={ "response " + ( className || "") } data-code={code}>
|
||||
@@ -137,20 +155,55 @@ export default class Response extends React.Component {
|
||||
<Markdown source={ response.get( "description" ) } />
|
||||
</div>
|
||||
|
||||
{ isOAS3 ?
|
||||
<div className={cx("response-content-type", {
|
||||
"controls-accept-header": controlsAcceptHeader
|
||||
})}>
|
||||
<ContentType
|
||||
{isOAS3 && response.get("content") ? (
|
||||
<section className="response-controls">
|
||||
<div
|
||||
className={cx("response-control-media-type", {
|
||||
"response-control-media-type--accept-controller": controlsAcceptHeader
|
||||
})}
|
||||
>
|
||||
<small className="response-control-media-type__title">
|
||||
Media type
|
||||
</small>
|
||||
<ContentType
|
||||
value={this.state.responseContentType}
|
||||
contentTypes={ response.get("content") ? response.get("content").keySeq() : Seq() }
|
||||
contentTypes={
|
||||
response.get("content")
|
||||
? response.get("content").keySeq()
|
||||
: Seq()
|
||||
}
|
||||
onChange={this._onContentTypeChange}
|
||||
/>
|
||||
{controlsAcceptHeader ? (
|
||||
<small className="response-control-media-type__accept-message">
|
||||
Controls <code>Accept</code> header.
|
||||
</small>
|
||||
) : null}
|
||||
</div>
|
||||
{examplesForMediaType ? (
|
||||
<div className="response-control-examples">
|
||||
<small className="response-control-examples__title">
|
||||
Examples
|
||||
</small>
|
||||
<ExamplesSelect
|
||||
examples={examplesForMediaType}
|
||||
currentExampleKey={this.getTargetExamplesKey()}
|
||||
onSelect={key =>
|
||||
oas3Actions.setActiveExamplesMember({
|
||||
name: key,
|
||||
pathMethod: [path, method],
|
||||
contextType: "responses",
|
||||
contextName: code
|
||||
})
|
||||
}
|
||||
showLabels={false}
|
||||
/>
|
||||
{ controlsAcceptHeader ? <small>Controls <code>Accept</code> header.</small> : null }
|
||||
</div>
|
||||
: null }
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{ example ? (
|
||||
{ example || schema ? (
|
||||
<ModelExample
|
||||
specPath={specPathWithPossibleSchema}
|
||||
getComponent={ getComponent }
|
||||
@@ -158,6 +211,14 @@ export default class Response extends React.Component {
|
||||
specSelectors={ specSelectors }
|
||||
schema={ fromJSOrdered(schema) }
|
||||
example={ example }/>
|
||||
) : null }
|
||||
|
||||
{ isOAS3 && examplesForMediaType ? (
|
||||
<Example
|
||||
example={examplesForMediaType.get(this.getTargetExamplesKey(), Map({}))}
|
||||
getComponent={getComponent}
|
||||
omitValue={true}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{ headers ? (
|
||||
@@ -167,9 +228,8 @@ export default class Response extends React.Component {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
|
||||
</td>
|
||||
{specSelectors.isOAS3() ? <td className="col response-col_links">
|
||||
{isOAS3 ? <td className="col response-col_links">
|
||||
{ links ?
|
||||
links.toSeq().map((link, key) => {
|
||||
return <OperationLink key={key} name={key} link={ link } getComponent={getComponent}/>
|
||||
|
||||
@@ -18,6 +18,7 @@ export default class Responses extends React.Component {
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
oas3Actions: PropTypes.object.isRequired,
|
||||
oas3Selectors: PropTypes.object.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
fn: PropTypes.object.isRequired
|
||||
}
|
||||
@@ -28,17 +29,20 @@ export default class Responses extends React.Component {
|
||||
displayRequestDuration: false
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
// These performance-enhancing checks were disabled as part of Multiple Examples
|
||||
// because they were causing data-consistency issues
|
||||
//
|
||||
// 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)
|
||||
|
||||
@@ -64,6 +68,10 @@ export default class Responses extends React.Component {
|
||||
producesValue,
|
||||
displayRequestDuration,
|
||||
specPath,
|
||||
path,
|
||||
method,
|
||||
oas3Selectors,
|
||||
oas3Actions,
|
||||
} = this.props
|
||||
let defaultCode = defaultStatusCode( responses )
|
||||
|
||||
@@ -121,6 +129,8 @@ export default class Responses extends React.Component {
|
||||
let className = tryItOutResponse && tryItOutResponse.get("status") == code ? "response_current" : ""
|
||||
return (
|
||||
<Response key={ code }
|
||||
path={path}
|
||||
method={method}
|
||||
specPath={specPath.push(code)}
|
||||
isDefault={defaultCode === code}
|
||||
fn={fn}
|
||||
@@ -132,6 +142,13 @@ export default class Responses extends React.Component {
|
||||
onContentTypeChange={this.onResponseContentTypeChange}
|
||||
contentType={ producesValue }
|
||||
getConfigs={ getConfigs }
|
||||
activeExamplesKey={oas3Selectors.activeExamplesMember(
|
||||
path,
|
||||
method,
|
||||
"responses",
|
||||
code
|
||||
)}
|
||||
oas3Actions={oas3Actions}
|
||||
getComponent={ getComponent }/>
|
||||
)
|
||||
}).toArray()
|
||||
|
||||
Reference in New Issue
Block a user