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"
|
||||
@@ -2,12 +2,11 @@ import React, { Component } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import Im, { Map, List } from "immutable"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import { OAS3ComponentWrapFactory } from "../helpers"
|
||||
|
||||
// More readable, just iterate over maps, only
|
||||
const eachMap = (iterable, fn) => iterable.valueSeq().filter(Im.Map.isMap).map(fn)
|
||||
|
||||
class Parameters extends Component {
|
||||
export default class Parameters extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
@@ -19,21 +18,21 @@ class Parameters extends Component {
|
||||
|
||||
static propTypes = {
|
||||
parameters: ImPropTypes.list.isRequired,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
operation: PropTypes.object.isRequired,
|
||||
specActions: PropTypes.object.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
getConfigs: 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,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
onTryoutClick: PropTypes.func,
|
||||
onCancelClick: PropTypes.func,
|
||||
onChangeKey: PropTypes.array,
|
||||
pathMethod: PropTypes.array.isRequired
|
||||
pathMethod: PropTypes.array.isRequired,
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
specPath: ImPropTypes.list.isRequired,
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +42,7 @@ class Parameters extends Component {
|
||||
tryItOutEnabled: false,
|
||||
allowTryItOut: true,
|
||||
onChangeKey: [],
|
||||
specPath: [],
|
||||
}
|
||||
|
||||
onChange = ( param, value, isXml ) => {
|
||||
@@ -51,7 +51,7 @@ class Parameters extends Component {
|
||||
onChangeKey,
|
||||
} = this.props
|
||||
|
||||
changeParamByIdentity( onChangeKey, param, value, isXml)
|
||||
changeParamByIdentity(onChangeKey, param, value, isXml)
|
||||
}
|
||||
|
||||
onChangeConsumesWrapper = ( val ) => {
|
||||
@@ -85,16 +85,16 @@ class Parameters extends Component {
|
||||
parameters,
|
||||
allowTryItOut,
|
||||
tryItOutEnabled,
|
||||
specPath,
|
||||
|
||||
fn,
|
||||
getComponent,
|
||||
getConfigs,
|
||||
specSelectors,
|
||||
specActions,
|
||||
pathMethod,
|
||||
oas3Actions,
|
||||
oas3Selectors,
|
||||
pathMethod,
|
||||
specPath,
|
||||
operation
|
||||
} = this.props
|
||||
|
||||
@@ -105,72 +105,79 @@ class Parameters extends Component {
|
||||
const RequestBody = getComponent("RequestBody", true)
|
||||
|
||||
const isExecute = tryItOutEnabled && allowTryItOut
|
||||
const { isOAS3 } = specSelectors
|
||||
const isOAS3 = specSelectors.isOAS3()
|
||||
|
||||
const requestBody = operation.get("requestBody")
|
||||
const requestBodySpecPath = specPath.slice(0, -1).push("requestBody") // remove the "parameters" part
|
||||
|
||||
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 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>
|
||||
{ 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 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 }
|
||||
getComponent={ getComponent }
|
||||
specPath={specPath.push(i)}
|
||||
getConfigs={ getConfigs }
|
||||
rawParam={ parameter }
|
||||
param={ specSelectors.parameterWithMetaByIdentity(pathMethod, parameter) }
|
||||
key={ parameter.get( "name" ) }
|
||||
onChange={ this.onChange }
|
||||
onChangeConsumes={this.onChangeConsumesWrapper}
|
||||
specSelectors={ specSelectors }
|
||||
specActions={ specActions }
|
||||
pathMethod={ pathMethod }
|
||||
isExecute={ isExecute }/>
|
||||
)).toArray()
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</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}
|
||||
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> : "" }
|
||||
</div> : null }
|
||||
{
|
||||
isOAS3() && requestBody && this.state.parametersVisible &&
|
||||
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>
|
||||
@@ -186,10 +193,24 @@ class Parameters extends Component {
|
||||
</div>
|
||||
<div className="opblock-description-wrapper">
|
||||
<RequestBody
|
||||
specPath={requestBodySpecPath}
|
||||
specPath={specPath.slice(0, -1).push("requestBody")}
|
||||
requestBody={requestBody}
|
||||
requestBodyValue={oas3Selectors.requestBodyValue(...pathMethod) || Map()}
|
||||
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)
|
||||
@@ -209,6 +230,3 @@ class Parameters extends Component {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default OAS3ComponentWrapFactory(Parameters)
|
||||
@@ -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()
|
||||
|
||||
@@ -4,7 +4,6 @@ import { List, fromJS } from "immutable"
|
||||
import cx from "classnames"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
import DebounceInput from "react-debounce-input"
|
||||
import { getSampleSchema } from "core/utils"
|
||||
//import "less/json-schema-form"
|
||||
|
||||
const noop = ()=> {}
|
||||
@@ -18,7 +17,8 @@ const JsonSchemaPropShape = {
|
||||
errors: ImPropTypes.list,
|
||||
required: PropTypes.bool,
|
||||
dispatchInitialValue: PropTypes.bool,
|
||||
description: PropTypes.any
|
||||
description: PropTypes.any,
|
||||
disabled: PropTypes.bool,
|
||||
}
|
||||
|
||||
const JsonSchemaDefaultProps = {
|
||||
@@ -43,7 +43,7 @@ export class JsonSchemaForm extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let { schema, errors, value, onChange, getComponent, fn } = this.props
|
||||
let { schema, errors, value, onChange, getComponent, fn, disabled } = this.props
|
||||
|
||||
if(schema.toJS)
|
||||
schema = schema.toJS()
|
||||
@@ -51,7 +51,7 @@ export class JsonSchemaForm extends Component {
|
||||
let { type, format="" } = schema
|
||||
|
||||
let Comp = (format ? getComponent(`JsonSchema_${type}_${format}`) : getComponent(`JsonSchema_${type}`)) || getComponent("JsonSchema_string")
|
||||
return <Comp { ...this.props } errors={errors} fn={fn} getComponent={getComponent} value={value} onChange={onChange} schema={schema}/>
|
||||
return <Comp { ...this.props } errors={errors} fn={fn} getComponent={getComponent} value={value} onChange={onChange} schema={schema} disabled={disabled}/>
|
||||
}
|
||||
|
||||
}
|
||||
@@ -65,7 +65,7 @@ export class JsonSchema_string extends Component {
|
||||
}
|
||||
onEnumChange = (val) => this.props.onChange(val)
|
||||
render() {
|
||||
let { getComponent, value, schema, errors, required, description } = this.props
|
||||
let { getComponent, value, schema, errors, required, description, disabled } = this.props
|
||||
let enumValue = schema["enum"]
|
||||
|
||||
errors = errors.toJS ? errors.toJS() : []
|
||||
@@ -80,7 +80,7 @@ export class JsonSchema_string extends Component {
|
||||
onChange={ this.onEnumChange }/>)
|
||||
}
|
||||
|
||||
const isDisabled = schema["in"] === "formData" && !("FormData" in window)
|
||||
const isDisabled = disabled || (schema["in"] === "formData" && !("FormData" in window))
|
||||
const Input = getComponent("Input")
|
||||
if (schema["type"] === "file") {
|
||||
return (<Input type="file"
|
||||
@@ -149,7 +149,7 @@ export class JsonSchema_array extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
let { getComponent, required, schema, errors, fn } = this.props
|
||||
let { getComponent, required, schema, errors, fn, disabled } = this.props
|
||||
|
||||
errors = errors.toJS ? errors.toJS() : []
|
||||
|
||||
@@ -167,13 +167,14 @@ export class JsonSchema_array extends PureComponent {
|
||||
title={ errors.length ? errors : ""}
|
||||
multiple={ true }
|
||||
value={ value }
|
||||
disabled={disabled}
|
||||
allowedValues={ enumValue }
|
||||
allowEmptyValue={ !required }
|
||||
onChange={ this.onEnumChange }/>)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="json-schema-array">
|
||||
{ !value || !value.count || value.count() < 1 ? null :
|
||||
value.map( (item,i) => {
|
||||
let schema = Object.assign({}, itemSchema)
|
||||
@@ -183,13 +184,32 @@ export class JsonSchema_array extends PureComponent {
|
||||
}
|
||||
return (
|
||||
<div key={i} className="json-schema-form-item">
|
||||
<JsonSchemaForm fn={fn} getComponent={getComponent} value={item} onChange={(val) => this.onItemChange(val, i)} schema={schema} />
|
||||
<Button className="btn btn-sm json-schema-form-item-remove" onClick={()=> this.removeItem(i)} > - </Button>
|
||||
<JsonSchemaForm
|
||||
fn={fn}
|
||||
getComponent={getComponent}
|
||||
value={item}
|
||||
onChange={(val) => this.onItemChange(val, i)}
|
||||
schema={schema}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{ !disabled ? (
|
||||
<Button
|
||||
className="btn btn-sm json-schema-form-item-remove"
|
||||
onClick={()=> this.removeItem(i)}
|
||||
> - </Button>
|
||||
) : null }
|
||||
</div>
|
||||
)
|
||||
}).toArray()
|
||||
}
|
||||
<Button className={`btn btn-sm json-schema-form-item-add ${errors.length ? "invalid" : null}`} onClick={this.addItem}> Add item </Button>
|
||||
{ !disabled ? (
|
||||
<Button
|
||||
className={`btn btn-sm json-schema-form-item-add ${errors.length ? "invalid" : null}`}
|
||||
onClick={this.addItem}
|
||||
>
|
||||
Add item
|
||||
</Button>
|
||||
) : null }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -201,7 +221,7 @@ export class JsonSchema_boolean extends Component {
|
||||
|
||||
onEnumChange = (val) => this.props.onChange(val)
|
||||
render() {
|
||||
let { getComponent, value, errors, schema, required } = this.props
|
||||
let { getComponent, value, errors, schema, required, disabled } = this.props
|
||||
errors = errors.toJS ? errors.toJS() : []
|
||||
|
||||
const Select = getComponent("Select")
|
||||
@@ -209,6 +229,7 @@ export class JsonSchema_boolean extends Component {
|
||||
return (<Select className={ errors.length ? "invalid" : ""}
|
||||
title={ errors.length ? errors : ""}
|
||||
value={ String(value) }
|
||||
disabled={disabled}
|
||||
allowedValues={ fromJS(schema.enum || ["true", "false"]) }
|
||||
allowEmptyValue={ !schema.enum || !required }
|
||||
onChange={ this.onEnumChange }/>)
|
||||
@@ -223,16 +244,6 @@ export class JsonSchema_object extends PureComponent {
|
||||
static propTypes = JsonSchemaPropShape
|
||||
static defaultProps = JsonSchemaDefaultProps
|
||||
|
||||
componentDidMount() {
|
||||
if(!this.props.value && this.props.schema) {
|
||||
this.resetValueToSample()
|
||||
}
|
||||
}
|
||||
|
||||
resetValueToSample = () => {
|
||||
this.onChange(getSampleSchema(this.props.schema) )
|
||||
}
|
||||
|
||||
onChange = (value) => {
|
||||
this.props.onChange(value)
|
||||
}
|
||||
@@ -247,7 +258,8 @@ export class JsonSchema_object extends PureComponent {
|
||||
let {
|
||||
getComponent,
|
||||
value,
|
||||
errors
|
||||
errors,
|
||||
disabled
|
||||
} = this.props
|
||||
|
||||
const TextArea = getComponent("TextArea")
|
||||
@@ -258,6 +270,7 @@ export class JsonSchema_object extends PureComponent {
|
||||
className={cx({ invalid: errors.size })}
|
||||
title={ errors.size ? errors.join(", ") : ""}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
onChange={ this.handleOnChange }/>
|
||||
</div>
|
||||
)
|
||||
@@ -267,4 +280,4 @@ export class JsonSchema_object extends PureComponent {
|
||||
|
||||
function valueOrEmptyList(value) {
|
||||
return List.isList(value) ? value : List()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
export const UPDATE_SELECTED_SERVER = "oas3_set_servers"
|
||||
export const UPDATE_REQUEST_BODY_VALUE = "oas3_set_request_body_value"
|
||||
export const UPDATE_ACTIVE_EXAMPLES_MEMBER = "oas3_set_active_examples_member"
|
||||
export const UPDATE_REQUEST_CONTENT_TYPE = "oas3_set_request_content_type"
|
||||
export const UPDATE_RESPONSE_CONTENT_TYPE = "oas3_set_response_content_type"
|
||||
export const UPDATE_SERVER_VARIABLE_VALUE = "oas3_set_server_variable_value"
|
||||
@@ -21,6 +22,13 @@ export function setRequestBodyValue ({ value, pathMethod }) {
|
||||
}
|
||||
}
|
||||
|
||||
export function setActiveExamplesMember ({ name, pathMethod, contextType, contextName }) {
|
||||
return {
|
||||
type: UPDATE_ACTIVE_EXAMPLES_MEMBER,
|
||||
payload: { name, pathMethod, contextType, contextName }
|
||||
}
|
||||
}
|
||||
|
||||
export function setRequestContentType ({ value, pathMethod }) {
|
||||
return {
|
||||
type: UPDATE_REQUEST_CONTENT_TYPE,
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
import React, { PureComponent } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { fromJS } from "immutable"
|
||||
import { getSampleSchema, stringify } from "core/utils"
|
||||
import { stringify } from "core/utils"
|
||||
|
||||
const NOOP = Function.prototype
|
||||
|
||||
export default class RequestBodyEditor extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
requestBody: PropTypes.object.isRequired,
|
||||
mediaType: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
isExecute: PropTypes.bool,
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
value: PropTypes.string,
|
||||
defaultValue: PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
mediaType: "application/json",
|
||||
requestBody: fromJS({}),
|
||||
onChange: NOOP,
|
||||
};
|
||||
|
||||
@@ -26,108 +21,75 @@ export default class RequestBodyEditor extends PureComponent {
|
||||
super(props, context)
|
||||
|
||||
this.state = {
|
||||
isEditBox: false,
|
||||
userDidModify: false,
|
||||
value: ""
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setValueToSample.call(this)
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(this.props.mediaType !== nextProps.mediaType) {
|
||||
// media type was changed
|
||||
this.setValueToSample(nextProps.mediaType)
|
||||
value: stringify(props.value) || props.defaultValue
|
||||
}
|
||||
|
||||
if(!this.props.isExecute && nextProps.isExecute) {
|
||||
// we just entered execute mode,
|
||||
// so enable editing for convenience
|
||||
this.setState({ isEditBox: true })
|
||||
}
|
||||
// this is the glue that makes sure our initial value gets set as the
|
||||
// current request body value
|
||||
// TODO: achieve this in a selector instead
|
||||
props.onChange(props.value)
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if(this.props.requestBody !== prevProps.requestBody) {
|
||||
// force recalc of value if the request body definition has changed
|
||||
this.setValueToSample(this.props.mediaType)
|
||||
}
|
||||
}
|
||||
applyDefaultValue = (nextProps) => {
|
||||
const { onChange, defaultValue } = (nextProps ? nextProps : this.props)
|
||||
|
||||
setValueToSample = (explicitMediaType) => {
|
||||
this.onChange(this.sample(explicitMediaType))
|
||||
}
|
||||
|
||||
resetValueToSample = (explicitMediaType) => {
|
||||
this.setState({ userDidModify: false })
|
||||
this.setValueToSample(explicitMediaType)
|
||||
}
|
||||
|
||||
sample = (explicitMediaType) => {
|
||||
let { requestBody, mediaType } = this.props
|
||||
let mediaTypeValue = requestBody.getIn(["content", explicitMediaType || mediaType])
|
||||
let schema = mediaTypeValue.get("schema").toJS()
|
||||
let mediaTypeExample = mediaTypeValue.get("example") !== undefined ? stringify(mediaTypeValue.get("example")) : null
|
||||
|
||||
return mediaTypeExample || getSampleSchema(schema, explicitMediaType || mediaType, {
|
||||
includeWriteOnly: true
|
||||
this.setState({
|
||||
value: defaultValue
|
||||
})
|
||||
|
||||
return onChange(defaultValue)
|
||||
}
|
||||
|
||||
onChange = (value) => {
|
||||
this.setState({value})
|
||||
this.props.onChange(value)
|
||||
this.props.onChange(stringify(value))
|
||||
}
|
||||
|
||||
handleOnChange = e => {
|
||||
const { mediaType } = this.props
|
||||
const isJson = /json/i.test(mediaType)
|
||||
const inputValue = isJson ? e.target.value.trim() : e.target.value
|
||||
onDomChange = e => {
|
||||
const inputValue = e.target.value
|
||||
|
||||
this.setState({ userDidModify: true })
|
||||
this.onChange(inputValue)
|
||||
this.setState({
|
||||
value: inputValue,
|
||||
}, () => this.onChange(inputValue))
|
||||
}
|
||||
|
||||
toggleIsEditBox = () => this.setState( state => ({isEditBox: !state.isEditBox}))
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(
|
||||
this.props.value !== nextProps.value &&
|
||||
nextProps.value !== this.state.value
|
||||
) {
|
||||
|
||||
this.setState({
|
||||
value: stringify(nextProps.value)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(!nextProps.value && nextProps.defaultValue && !!this.state.value) {
|
||||
// if new value is falsy, we have a default, AND the falsy value didn't
|
||||
// come from us originally
|
||||
this.applyDefaultValue(nextProps)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let {
|
||||
isExecute,
|
||||
getComponent,
|
||||
mediaType,
|
||||
getComponent
|
||||
} = this.props
|
||||
|
||||
const Button = getComponent("Button")
|
||||
const TextArea = getComponent("TextArea")
|
||||
const HighlightCode = getComponent("highlightCode")
|
||||
let {
|
||||
value
|
||||
} = this.state
|
||||
|
||||
let { value, isEditBox, userDidModify } = this.state
|
||||
const TextArea = getComponent("TextArea")
|
||||
|
||||
return (
|
||||
<div className="body-param">
|
||||
{
|
||||
isEditBox && isExecute
|
||||
? <TextArea className={"body-param__text"} value={value} onChange={ this.handleOnChange }/>
|
||||
: (value && <HighlightCode className="body-param__example"
|
||||
value={ value }/>)
|
||||
}
|
||||
<div className="body-param-options">
|
||||
<div className="body-param-edit">
|
||||
{
|
||||
!isExecute ? null
|
||||
: <Button className={isEditBox ? "btn cancel body-param__example-edit" : "btn edit body-param__example-edit"}
|
||||
onClick={this.toggleIsEditBox}>{ isEditBox ? "Cancel" : "Edit"}
|
||||
</Button>
|
||||
|
||||
}
|
||||
{ userDidModify &&
|
||||
<Button className="btn ml3" onClick={() => { this.resetValueToSample(mediaType) }}>Reset</Button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TextArea
|
||||
className={"body-param__text"}
|
||||
value={value}
|
||||
onChange={ this.onDomChange }
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
@@ -4,6 +4,36 @@ import ImPropTypes from "react-immutable-proptypes"
|
||||
import { Map, OrderedMap, List } from "immutable"
|
||||
import { getCommonExtensions, getSampleSchema, stringify } from "core/utils"
|
||||
|
||||
function getDefaultRequestBodyValue(requestBody, mediaType, activeExamplesKey) {
|
||||
let mediaTypeValue = requestBody.getIn(["content", mediaType])
|
||||
let schema = mediaTypeValue.get("schema").toJS()
|
||||
let example =
|
||||
mediaTypeValue.get("example") !== undefined
|
||||
? stringify(mediaTypeValue.get("example"))
|
||||
: null
|
||||
let currentExamplesValue = mediaTypeValue.getIn([
|
||||
"examples",
|
||||
activeExamplesKey,
|
||||
"value"
|
||||
])
|
||||
|
||||
if (mediaTypeValue.get("examples")) {
|
||||
// the media type DOES have examples
|
||||
return stringify(currentExamplesValue) || ""
|
||||
} else {
|
||||
// the media type DOES NOT have examples
|
||||
return stringify(
|
||||
example ||
|
||||
getSampleSchema(schema, mediaType, {
|
||||
includeWriteOnly: true
|
||||
}) ||
|
||||
""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const RequestBody = ({
|
||||
requestBody,
|
||||
requestBodyValue,
|
||||
@@ -14,7 +44,9 @@ const RequestBody = ({
|
||||
contentType,
|
||||
isExecute,
|
||||
specPath,
|
||||
onChange
|
||||
onChange,
|
||||
activeExamplesKey,
|
||||
updateActiveExamplesKey,
|
||||
}) => {
|
||||
const handleFile = (e) => {
|
||||
onChange(e.target.files[0])
|
||||
@@ -23,6 +55,9 @@ const RequestBody = ({
|
||||
const Markdown = getComponent("Markdown")
|
||||
const ModelExample = getComponent("modelExample")
|
||||
const RequestBodyEditor = getComponent("RequestBodyEditor")
|
||||
const HighlightCode = getComponent("highlightCode")
|
||||
const ExamplesSelectValueRetainer = getComponent("ExamplesSelectValueRetainer")
|
||||
const Example = getComponent("Example")
|
||||
|
||||
const { showCommonExtensions } = getConfigs()
|
||||
|
||||
@@ -32,6 +67,11 @@ const RequestBody = ({
|
||||
|
||||
const mediaTypeValue = requestBodyContent.get(contentType, OrderedMap())
|
||||
const schemaForMediaType = mediaTypeValue.get("schema", OrderedMap())
|
||||
const examplesForMediaType = mediaTypeValue.get("examples", OrderedMap())
|
||||
|
||||
const handleExamplesSelect = (key /*, { isSyntheticChange } */) => {
|
||||
updateActiveExamplesKey(key)
|
||||
}
|
||||
|
||||
if(!mediaTypeValue.size) {
|
||||
return null
|
||||
@@ -83,7 +123,7 @@ const RequestBody = ({
|
||||
const format = prop.get("format")
|
||||
const description = prop.get("description")
|
||||
const currentValue = requestBodyValue.get(key)
|
||||
|
||||
|
||||
let initialValue = prop.get("default") || prop.get("example") || ""
|
||||
|
||||
if (initialValue === "" && type === "object") {
|
||||
@@ -139,23 +179,63 @@ const RequestBody = ({
|
||||
{ requestBodyDescription &&
|
||||
<Markdown source={requestBodyDescription} />
|
||||
}
|
||||
<ModelExample
|
||||
getComponent={ getComponent }
|
||||
getConfigs={ getConfigs }
|
||||
specSelectors={ specSelectors }
|
||||
expandDepth={1}
|
||||
isExecute={isExecute}
|
||||
schema={mediaTypeValue.get("schema")}
|
||||
specPath={specPath.push("content", contentType)}
|
||||
example={<RequestBodyEditor
|
||||
requestBody={requestBody}
|
||||
onChange={onChange}
|
||||
mediaType={contentType}
|
||||
getComponent={getComponent}
|
||||
isExecute={isExecute}
|
||||
specSelectors={specSelectors}
|
||||
/>}
|
||||
/>
|
||||
{
|
||||
examplesForMediaType ? (
|
||||
<ExamplesSelectValueRetainer
|
||||
examples={examplesForMediaType}
|
||||
currentKey={activeExamplesKey}
|
||||
currentUserInputValue={requestBodyValue}
|
||||
onSelect={handleExamplesSelect}
|
||||
updateValue={onChange}
|
||||
defaultToFirstExample={true}
|
||||
getComponent={getComponent}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
{
|
||||
isExecute ? (
|
||||
<div>
|
||||
<RequestBodyEditor
|
||||
value={requestBodyValue}
|
||||
defaultValue={getDefaultRequestBodyValue(
|
||||
requestBody,
|
||||
contentType,
|
||||
activeExamplesKey,
|
||||
)}
|
||||
onChange={onChange}
|
||||
getComponent={getComponent}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<ModelExample
|
||||
getComponent={ getComponent }
|
||||
getConfigs={ getConfigs }
|
||||
specSelectors={ specSelectors }
|
||||
expandDepth={1}
|
||||
isExecute={isExecute}
|
||||
schema={mediaTypeValue.get("schema")}
|
||||
specPath={specPath.push("content", contentType)}
|
||||
example={
|
||||
<HighlightCode
|
||||
className="body-param__example"
|
||||
value={stringify(requestBodyValue) || getDefaultRequestBodyValue(
|
||||
requestBody,
|
||||
contentType,
|
||||
activeExamplesKey,
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
examplesForMediaType ? (
|
||||
<Example
|
||||
example={examplesForMediaType.get(activeExamplesKey)}
|
||||
getComponent={getComponent}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -169,7 +249,9 @@ RequestBody.propTypes = {
|
||||
contentType: PropTypes.string,
|
||||
isExecute: PropTypes.bool.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
specPath: PropTypes.array.isRequired
|
||||
specPath: PropTypes.array.isRequired,
|
||||
activeExamplesKey: PropTypes.string,
|
||||
updateActiveExamplesKey: PropTypes.func,
|
||||
}
|
||||
|
||||
export default RequestBody
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
UPDATE_SELECTED_SERVER,
|
||||
UPDATE_REQUEST_BODY_VALUE,
|
||||
UPDATE_ACTIVE_EXAMPLES_MEMBER,
|
||||
UPDATE_REQUEST_CONTENT_TYPE,
|
||||
UPDATE_SERVER_VARIABLE_VALUE,
|
||||
UPDATE_RESPONSE_CONTENT_TYPE
|
||||
@@ -15,6 +16,10 @@ export default {
|
||||
let [path, method] = pathMethod
|
||||
return state.setIn( [ "requestData", path, method, "bodyValue" ], value)
|
||||
},
|
||||
[UPDATE_ACTIVE_EXAMPLES_MEMBER]: (state, { payload: { name, pathMethod, contextType, contextName } } ) =>{
|
||||
let [path, method] = pathMethod
|
||||
return state.setIn( [ "examples", path, method, contextType, contextName, "activeExample" ], name)
|
||||
},
|
||||
[UPDATE_REQUEST_CONTENT_TYPE]: (state, { payload: { value, pathMethod } } ) =>{
|
||||
let [path, method] = pathMethod
|
||||
return state.setIn( [ "requestData", path, method, "requestContentType" ], value)
|
||||
|
||||
@@ -26,6 +26,11 @@ export const requestBodyValue = onlyOAS3((state, path, method) => {
|
||||
}
|
||||
)
|
||||
|
||||
export const activeExamplesMember = onlyOAS3((state, path, method, type, name) => {
|
||||
return state.getIn(["examples", path, method, type, name, "activeExample"]) || null
|
||||
}
|
||||
)
|
||||
|
||||
export const requestContentType = onlyOAS3((state, path, method) => {
|
||||
return state.getIn(["requestData", path, method, "requestContentType"]) || null
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Markdown from "./markdown"
|
||||
import AuthItem from "./auth-item"
|
||||
import parameters from "./parameters"
|
||||
import VersionStamp from "./version-stamp"
|
||||
import OnlineValidatorBadge from "./online-validator-badge"
|
||||
import Model from "./model"
|
||||
@@ -9,7 +8,6 @@ import JsonSchema_string from "./json-schema-string"
|
||||
export default {
|
||||
Markdown,
|
||||
AuthItem,
|
||||
parameters,
|
||||
JsonSchema_string,
|
||||
VersionStamp,
|
||||
model: Model,
|
||||
|
||||
@@ -25,6 +25,9 @@ import AuthItem from "core/components/auth/auth-item"
|
||||
import AuthError from "core/components/auth/error"
|
||||
import ApiKeyAuth from "core/components/auth/api-key-auth"
|
||||
import BasicAuth from "core/components/auth/basic-auth"
|
||||
import Example from "core/components/example"
|
||||
import ExamplesSelect from "core/components/examples-select"
|
||||
import ExamplesSelectValueRetainer from "core/components/examples-select-value-retainer"
|
||||
import Oauth2 from "core/components/auth/oauth2"
|
||||
import Clear from "core/components/clear"
|
||||
import LiveResponse from "core/components/live-response"
|
||||
@@ -41,7 +44,7 @@ import HighlightCode from "core/components/highlight-code"
|
||||
import Responses from "core/components/responses"
|
||||
import Response from "core/components/response"
|
||||
import ResponseBody from "core/components/response-body"
|
||||
import Parameters from "core/components/parameters"
|
||||
import { Parameters } from "core/components/parameters"
|
||||
import ParameterExt from "core/components/parameter-extension"
|
||||
import ParameterIncludeEmpty from "core/components/parameter-include-empty"
|
||||
import ParameterRow from "core/components/parameter-row"
|
||||
@@ -152,7 +155,10 @@ export default function() {
|
||||
DeepLink,
|
||||
InfoUrl,
|
||||
InfoBasePath,
|
||||
SvgAssets
|
||||
SvgAssets,
|
||||
Example,
|
||||
ExamplesSelect,
|
||||
ExamplesSelectValueRetainer,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -780,7 +780,7 @@ export function stringify(thing) {
|
||||
return thing
|
||||
}
|
||||
|
||||
if (thing.toJS) {
|
||||
if (thing && thing.toJS) {
|
||||
thing = thing.toJS()
|
||||
}
|
||||
|
||||
@@ -793,6 +793,10 @@ export function stringify(thing) {
|
||||
}
|
||||
}
|
||||
|
||||
if(thing === null || thing === undefined) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return thing.toString()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user