Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple examples #4092

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 48 additions & 5 deletions src/core/components/model-example.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,23 @@ export default class ModelExample extends React.Component {
specSelectors: PropTypes.object.isRequired,
schema: PropTypes.object.isRequired,
example: PropTypes.any.isRequired,
examples: ImPropTypes.orderedMap,
isExecute: PropTypes.bool,
getConfigs: PropTypes.func.isRequired,
specPath: ImPropTypes.list.isRequired,
}

constructor(props, context) {
super(props, context)
let { getConfigs } = this.props

let { getConfigs, examples } = this.props
let { defaultModelRendering } = getConfigs()
if (defaultModelRendering !== "example" && defaultModelRendering !== "model") {
defaultModelRendering = "example"
}
if (defaultModelRendering === "example" && examples) {
defaultModelRendering = `example_${ examples.keySeq().first() }`
}
this.state = {
activeTab: defaultModelRendering
}
Expand All @@ -33,16 +38,38 @@ export default class ModelExample extends React.Component {
})
}

formatValue = (value) => {
if(typeof(value) === "string") {
return value
} else {
return JSON.stringify(value, null, 2)
}
}

render() {
let { getComponent, specSelectors, schema, example, isExecute, getConfigs, specPath } = this.props
let { getComponent, specSelectors, schema, example, examples, isExecute, getConfigs, specPath } = this.props
let { defaultModelExpandDepth } = getConfigs()
const ModelWrapper = getComponent("ModelWrapper")
const HighlightCode = getComponent("highlightCode")
const Markdown = getComponent("Markdown")
const ExternalValue = getComponent("ExternalValue")

// TODO fetch externalValue and display it on demand
return <div>
<ul className="tab">
<li className={ "tabitem" + ( isExecute || this.state.activeTab === "example" ? " active" : "") }>
<a className="tablinks" data-name="example" onClick={ this.activeTab }>Example Value</a>
</li>
{ !examples && example &&
<li className={"tabitem" + (isExecute || this.state.activeTab === "example" ? " active" : "")}>
<a className="tablinks" data-name="example" onClick={this.activeTab}>Example Value</a>
</li>
}

{ examples && examples.map( (item, key) => {
return ( !isExecute && <li key={"examples_key_" + key} className={"tabitem" + ( isExecute || this.state.activeTab === "example_" + key ? " active" : "") }>
<a className="tablinks" data-name={"example_" + key} onClick={ this.activeTab }>Example: {key}</a>
</li> )
} ).toArray()
}

{ schema ? <li className={ "tabitem" + ( !isExecute && this.state.activeTab === "model" ? " active" : "") }>
<a className={ "tablinks" + ( isExecute ? " inactive" : "" )} data-name="model" onClick={ this.activeTab }>Model</a>
</li> : null }
Expand All @@ -51,6 +78,22 @@ export default class ModelExample extends React.Component {
{
(isExecute || this.state.activeTab === "example") && example
}
{
examples && examples.map( (item, key) => {
return ((!isExecute && this.state.activeTab === "example_" + key) && (
<div key={"example_div_key_" + key} className="example-wrapper">
{ item.has("summary") && <h6 className="example-summary">{ item.get("summary") }</h6> }
{
item.has("description") && <div className="example-description">
<Markdown source={ item.get("description") } />
</div>
}
{ item.has("value") && <HighlightCode value={ this.formatValue(item.get("value")) } /> }
{ item.has("externalValue") && <ExternalValue location={ item.get("externalValue") } getComponent={ getComponent } /> }
</div>
))
} ).toArray()
}
{
!isExecute && this.state.activeTab === "model" && <ModelWrapper schema={ schema }
getComponent={ getComponent }
Expand Down
2 changes: 1 addition & 1 deletion src/core/components/model-wrapper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default class ModelWrapper extends Component {
specSelectors: PropTypes.object.isRequired,
expandDepth: PropTypes.number,
layoutActions: PropTypes.object,
layoutSelectors: PropTypes.object.isRequired
layoutSelectors: PropTypes.object
}

onToggle = (name,isShown) => {
Expand Down
33 changes: 28 additions & 5 deletions src/core/components/parameter-row.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Map } from "immutable"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import win from "core/window"
import { formatParamValue } from "core/plugins/oas3/utils"
import { getExtensions, getCommonExtensions } from "core/utils"

export default class ParameterRow extends Component {
Expand Down Expand Up @@ -43,6 +44,7 @@ export default class ParameterRow extends Component {
let { isOAS3 } = specSelectors

let example = param.get("example")
let examples = param.get("examples")
let defaultValue = param.get("default")
let parameter = specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in"))
let enumValue
Expand All @@ -61,6 +63,8 @@ export default class ParameterRow extends Component {
value = paramValue
} else if ( example !== undefined ) {
value = example
} else if ( examples !== undefined && examples.size > 0 ) {
value = examples.first().value
} else if ( defaultValue !== undefined) {
value = defaultValue
} else if ( param.get("required") && enumValue && enumValue.size ) {
Expand Down Expand Up @@ -122,6 +126,7 @@ export default class ParameterRow extends Component {
let paramEnum // undefined
let paramDefaultValue // undefined
let paramExample // undefined
let paramExamples // undefined
let isDisplayParamEnum = false

if ( param !== undefined ) {
Expand All @@ -142,9 +147,13 @@ export default class ParameterRow extends Component {
// Default and Example Value for readonly doc
if ( param !== undefined ) {
paramDefaultValue = schema.get("default")
paramExample = param.get("example")
if (paramExample === undefined) {
paramExample = param.get("x-example")
paramExamples = isOAS3 && isOAS3() && param.get("examples")

if(isOAS3 && isOAS3() && !paramExamples){
paramExample = param.get("example")
if (paramExample === undefined) {
paramExample = param.get("x-example")
}
}
}

Expand Down Expand Up @@ -189,7 +198,7 @@ export default class ParameterRow extends Component {
{ bodyParam || !isExecute ? null
: <JsonSchemaForm fn={fn}
getComponent={getComponent}
value={ value }
value={ formatParamValue(value) }
required={ required }
description={param.get("description") ? `${param.get("name")} - ${param.get("description")}` : `${param.get("name")}`}
onChange={ this.onChangeWrapper }
Expand All @@ -205,10 +214,24 @@ export default class ParameterRow extends Component {
isExecute={ isExecute }
specSelectors={ specSelectors }
schema={ param.get("schema") }
example={ bodyParam }/>
example={ bodyParam }
examples={ paramExamples }/>
: null
}

{/* for non-body params with example(s) */}
{
(!bodyParam && paramExamples) && <ModelExample
getComponent={ getComponent }
example={ paramExample }
examples={ paramExamples }
getConfigs={ getConfigs }
isExecute={ isExecute }
specSelectors={ specSelectors }
schema={ schema } />
}


</td>

</tr>
Expand Down
17 changes: 15 additions & 2 deletions src/core/components/response.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import cx from "classnames"
import { fromJS, Seq, Iterable, List } from "immutable"
import { fromJS, OrderedMap, Seq, Iterable, List } from "immutable"
import { getSampleSchema, fromJSOrdered } from "core/utils"

const getExampleComponent = ( sampleResponse, examples, HighlightCode ) => {
Expand Down Expand Up @@ -106,6 +106,17 @@ export default class Response extends React.Component {
includeReadOnly: true
}) : null
schema = oas3SchemaForContentType ? inferSchema(oas3SchemaForContentType.toJS()) : null
examples = response.getIn(["content", this.state.responseContentType, "examples"])

if(!examples) {
// The example object is mutually exclusive of the examples object. Prefer `examples` here.
let example = response.getIn(["content", this.state.responseContentType, "example"])

if(example) {
examples = OrderedMap({"Example": example})
}
}

specPathWithPossibleSchema = oas3SchemaForContentType ? schemaPath : specPath
} else {
schema = inferSchema(response.toJS()) // TODO: don't convert back and forth. Lets just stick with immutable for inferSchema
Expand Down Expand Up @@ -156,7 +167,9 @@ export default class Response extends React.Component {
getConfigs={ getConfigs }
specSelectors={ specSelectors }
schema={ fromJSOrdered(schema) }
example={ example }/>
example={ example }
examples={ examples }
/>
) : null}

{ headers ? (
Expand Down
33 changes: 33 additions & 0 deletions src/core/plugins/oas3/components/external-value.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from "react"
import PropTypes from "prop-types"

/**
* ExternalValue is a <HighlightCode> with content set to an external location.
* usage: <ExternalValue location="http://example.com/path/to/file" />
*/
export default class ExternalValue extends React.Component {
static propTypes = {
getComponent: PropTypes.func.isRequired,
location: PropTypes.string.isRequired
}

constructor(props, context) {
super(props, context)
}

render() {
let { location } = this.props
// const Markdown = getComponent("Markdown")

// TODO should externalValue be fetched and displayed inline?

return (
<div>
<a href={location} target="_blank" title={location} >Open ExternalValue</a>
</div>
)
}
}



3 changes: 3 additions & 0 deletions src/core/plugins/oas3/components/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

import Callbacks from "./callbacks"
import RequestBody from "./request-body"
import OperationLink from "./operation-link.jsx"
import ExternalValue from "./external-value.jsx"
import Servers from "./servers"
import RequestBodyEditor from "./request-body-editor"
import HttpAuth from "./http-auth"
Expand All @@ -10,6 +12,7 @@ export default {
Callbacks,
HttpAuth,
RequestBody,
ExternalValue,
Servers,
RequestBodyEditor,
OperationServers,
Expand Down
4 changes: 4 additions & 0 deletions src/core/plugins/oas3/components/request-body.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { OrderedMap } from "immutable"
import { memoizedGetExamples } from "core/plugins/oas3/utils"

const RequestBody = ({
requestBody,
Expand All @@ -23,6 +24,8 @@ const RequestBody = ({

const mediaTypeValue = requestBodyContent.get(contentType)

const examples = memoizedGetExamples(mediaTypeValue.get("examples"))

if(!mediaTypeValue) {
return null
}
Expand All @@ -47,6 +50,7 @@ const RequestBody = ({
isExecute={isExecute}
specSelectors={specSelectors}
/>}
examples={examples}
/>
</div>
}
Expand Down
26 changes: 26 additions & 0 deletions src/core/plugins/oas3/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import memoizee from "memoizee"
import { fromJS } from "immutable"

/**
* Convert OAS3 "Examples" (in requestBody) section and convert to Map
* @param examplesInSpec {object} "examples" in OAS3 spec
* @returns {Map} Map {name: {summary, description, value, externalValue}}
*/
export const getExamples = (examplesInSpec) => {
return fromJS(examplesInSpec)
}

export const memoizedGetExamples = memoizee(getExamples)

/**
* Convert 'value' to serialized format, so can be handled correctly in <input>
* @param value value
*/
export const formatParamValue = (value) => {
if (typeof ( value ) === "string") {
return value
} else {
// TODO apply 'style', 'explode', 'allowReserved' in OAS3
return JSON.stringify(value, null, 2)
}
}
44 changes: 27 additions & 17 deletions src/style/_layout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -396,33 +396,29 @@
{
font-size: 12px;

min-width: 100px;
min-width: 90px;
padding: 0;

cursor: pointer;

@include text_headline();

&:first-of-type
{
position: relative;
position: relative;

padding-left: 0;
padding-left: 0;
padding-right: 5px;
margin-right: 5px;

&:after
{
position: absolute;
top: 0;
right: 6px;

width: 1px;
height: 100%;
&:nth-last-child(n+2):after
{
position: absolute;
top: 0;
right: 0;

content: '';
width: 1px;
height: 100%;

background: rgba($tab-list-item-first-background-color,.2);
}
content: '';
background: rgba($tab-list-item-first-background-color,.2);
}

&.active
Expand Down Expand Up @@ -796,3 +792,17 @@ a.nostyle {
cursor: pointer;
}
}

/* OAS3 responseBody examples start */
.example-description {
margin: 0 0 5px 0;
padding: 5px 0;
}

/* .example-summary is the summary line of parameter examples */
.example-summary {
margin: 0;
/* use the same font as .opblock-summary-description */
@include text_body();
}
/* OAS3 responseBody examples end */