feat: group / sort parameters by location (#6745)
This commit is contained in:
@@ -1,18 +1,15 @@
|
|||||||
import React, { Component } from "react"
|
import React, { Component } from "react"
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types"
|
||||||
import Im, { Map, List } from "immutable"
|
import { Map, List } from "immutable"
|
||||||
import ImPropTypes from "react-immutable-proptypes"
|
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 {
|
export default class Parameters extends Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
callbackVisible: false,
|
callbackVisible: false,
|
||||||
parametersVisible: true
|
parametersVisible: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +42,7 @@ export default class Parameters extends Component {
|
|||||||
specPath: [],
|
specPath: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = ( param, value, isXml ) => {
|
onChange = (param, value, isXml) => {
|
||||||
let {
|
let {
|
||||||
specActions: { changeParamByIdentity },
|
specActions: { changeParamByIdentity },
|
||||||
onChangeKey,
|
onChangeKey,
|
||||||
@@ -54,30 +51,30 @@ export default class Parameters extends Component {
|
|||||||
changeParamByIdentity(onChangeKey, param, value, isXml)
|
changeParamByIdentity(onChangeKey, param, value, isXml)
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeConsumesWrapper = ( val ) => {
|
onChangeConsumesWrapper = (val) => {
|
||||||
let {
|
let {
|
||||||
specActions: { changeConsumesValue },
|
specActions: { changeConsumesValue },
|
||||||
onChangeKey
|
onChangeKey,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
changeConsumesValue(onChangeKey, val)
|
changeConsumesValue(onChangeKey, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleTab = (tab) => {
|
toggleTab = (tab) => {
|
||||||
if(tab === "parameters"){
|
if (tab === "parameters") {
|
||||||
return this.setState({
|
return this.setState({
|
||||||
parametersVisible: true,
|
parametersVisible: true,
|
||||||
callbackVisible: false
|
callbackVisible: false,
|
||||||
})
|
})
|
||||||
}else if(tab === "callbacks"){
|
} else if (tab === "callbacks") {
|
||||||
return this.setState({
|
return this.setState({
|
||||||
callbackVisible: true,
|
callbackVisible: true,
|
||||||
parametersVisible: false
|
parametersVisible: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeMediaType = ( { value, pathMethod } ) => {
|
onChangeMediaType = ({ value, pathMethod }) => {
|
||||||
let { specSelectors, specActions, oas3Selectors, oas3Actions } = this.props
|
let { specSelectors, specActions, oas3Selectors, oas3Actions } = this.props
|
||||||
let targetMediaType = value
|
let targetMediaType = value
|
||||||
let currentMediaType = oas3Selectors.requestContentType(...pathMethod)
|
let currentMediaType = oas3Selectors.requestContentType(...pathMethod)
|
||||||
@@ -92,7 +89,7 @@ export default class Parameters extends Component {
|
|||||||
oas3Actions.initRequestBodyValidateError({ pathMethod })
|
oas3Actions.initRequestBodyValidateError({ pathMethod })
|
||||||
}
|
}
|
||||||
|
|
||||||
render(){
|
render() {
|
||||||
|
|
||||||
let {
|
let {
|
||||||
onTryoutClick,
|
onTryoutClick,
|
||||||
@@ -110,7 +107,7 @@ export default class Parameters extends Component {
|
|||||||
pathMethod,
|
pathMethod,
|
||||||
oas3Actions,
|
oas3Actions,
|
||||||
oas3Selectors,
|
oas3Selectors,
|
||||||
operation
|
operation,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
const ParameterRow = getComponent("parameterRow")
|
const ParameterRow = getComponent("parameterRow")
|
||||||
@@ -124,17 +121,29 @@ export default class Parameters extends Component {
|
|||||||
|
|
||||||
|
|
||||||
const requestBody = operation.get("requestBody")
|
const requestBody = operation.get("requestBody")
|
||||||
|
|
||||||
|
const groupedParametersArr = Object.values(parameters
|
||||||
|
.reduce((acc, x) => {
|
||||||
|
const key = x.get("in")
|
||||||
|
acc[key] ??= []
|
||||||
|
acc[key].push(x)
|
||||||
|
return acc
|
||||||
|
}, {}))
|
||||||
|
.reduce((acc, x) => acc.concat(x), [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="opblock-section">
|
<div className="opblock-section">
|
||||||
<div className="opblock-section-header">
|
<div className="opblock-section-header">
|
||||||
{ isOAS3 ? (
|
{isOAS3 ? (
|
||||||
<div className="tab-header">
|
<div className="tab-header">
|
||||||
<div onClick={() => this.toggleTab("parameters")} className={`tab-item ${this.state.parametersVisible && "active"}`}>
|
<div onClick={() => this.toggleTab("parameters")}
|
||||||
|
className={`tab-item ${this.state.parametersVisible && "active"}`}>
|
||||||
<h4 className="opblock-title"><span>Parameters</span></h4>
|
<h4 className="opblock-title"><span>Parameters</span></h4>
|
||||||
</div>
|
</div>
|
||||||
{ operation.get("callbacks") ?
|
{operation.get("callbacks") ?
|
||||||
(
|
(
|
||||||
<div onClick={() => this.toggleTab("callbacks")} className={`tab-item ${this.state.callbackVisible && "active"}`}>
|
<div onClick={() => this.toggleTab("callbacks")}
|
||||||
|
className={`tab-item ${this.state.callbackVisible && "active"}`}>
|
||||||
<h4 className="opblock-title"><span>Callbacks</span></h4>
|
<h4 className="opblock-title"><span>Callbacks</span></h4>
|
||||||
</div>
|
</div>
|
||||||
) : null
|
) : null
|
||||||
@@ -145,12 +154,12 @@ export default class Parameters extends Component {
|
|||||||
<h4 className="opblock-title">Parameters</h4>
|
<h4 className="opblock-title">Parameters</h4>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{ allowTryItOut ? (
|
{allowTryItOut ? (
|
||||||
<TryItOutButton enabled={ tryItOutEnabled } onCancelClick={ onCancelClick } onTryoutClick={ onTryoutClick } />
|
<TryItOutButton enabled={tryItOutEnabled} onCancelClick={onCancelClick} onTryoutClick={onTryoutClick} />
|
||||||
) : null }
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{this.state.parametersVisible ? <div className="parameters-container">
|
{this.state.parametersVisible ? <div className="parameters-container">
|
||||||
{ !parameters.count() ? <div className="opblock-description-wrapper"><p>No parameters</p></div> :
|
{!groupedParametersArr.length ? <div className="opblock-description-wrapper"><p>No parameters</p></div> :
|
||||||
<div className="table-container">
|
<div className="table-container">
|
||||||
<table className="parameters">
|
<table className="parameters">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -161,46 +170,47 @@ export default class Parameters extends Component {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{
|
{
|
||||||
eachMap(parameters, (parameter, i) => (
|
groupedParametersArr.map((parameter, i) => (
|
||||||
<ParameterRow
|
<ParameterRow
|
||||||
fn={ fn }
|
fn={fn}
|
||||||
specPath={specPath.push(i.toString())}
|
specPath={specPath.push(i.toString())}
|
||||||
getComponent={ getComponent }
|
getComponent={getComponent}
|
||||||
getConfigs={ getConfigs }
|
getConfigs={getConfigs}
|
||||||
rawParam={ parameter }
|
rawParam={parameter}
|
||||||
param={ specSelectors.parameterWithMetaByIdentity(pathMethod, parameter) }
|
param={specSelectors.parameterWithMetaByIdentity(pathMethod, parameter)}
|
||||||
key={ `${parameter.get( "in" )}.${parameter.get("name")}` }
|
key={`${parameter.get("in")}.${parameter.get("name")}`}
|
||||||
onChange={ this.onChange }
|
onChange={this.onChange}
|
||||||
onChangeConsumes={this.onChangeConsumesWrapper}
|
onChangeConsumes={this.onChangeConsumesWrapper}
|
||||||
specSelectors={ specSelectors }
|
specSelectors={specSelectors}
|
||||||
specActions={specActions}
|
specActions={specActions}
|
||||||
oas3Actions={oas3Actions}
|
oas3Actions={oas3Actions}
|
||||||
oas3Selectors={oas3Selectors}
|
oas3Selectors={oas3Selectors}
|
||||||
pathMethod={ pathMethod }
|
pathMethod={pathMethod}
|
||||||
isExecute={ isExecute }/>
|
isExecute={isExecute} />
|
||||||
)).toArray()
|
))
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div> : null }
|
</div> : null}
|
||||||
|
|
||||||
{this.state.callbackVisible ? <div className="callbacks-container opblock-description-wrapper">
|
{this.state.callbackVisible ? <div className="callbacks-container opblock-description-wrapper">
|
||||||
<Callbacks
|
<Callbacks
|
||||||
callbacks={Map(operation.get("callbacks"))}
|
callbacks={Map(operation.get("callbacks"))}
|
||||||
specPath={specPath.slice(0, -1).push("callbacks")}
|
specPath={specPath.slice(0, -1).push("callbacks")}
|
||||||
/>
|
/>
|
||||||
</div> : null }
|
</div> : null}
|
||||||
{
|
{
|
||||||
isOAS3 && requestBody && this.state.parametersVisible &&
|
isOAS3 && requestBody && this.state.parametersVisible &&
|
||||||
<div className="opblock-section opblock-section-request-body">
|
<div className="opblock-section opblock-section-request-body">
|
||||||
<div className="opblock-section-header">
|
<div className="opblock-section-header">
|
||||||
<h4 className={`opblock-title parameter__name ${requestBody.get("required") && "required"}`}>Request body</h4>
|
<h4 className={`opblock-title parameter__name ${requestBody.get("required") && "required"}`}>Request
|
||||||
|
body</h4>
|
||||||
<label>
|
<label>
|
||||||
<ContentType
|
<ContentType
|
||||||
value={oas3Selectors.requestContentType(...pathMethod)}
|
value={oas3Selectors.requestContentType(...pathMethod)}
|
||||||
contentTypes={ requestBody.get("content", List()).keySeq() }
|
contentTypes={requestBody.get("content", List()).keySeq()}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
this.onChangeMediaType({ value, pathMethod })
|
this.onChangeMediaType({ value, pathMethod })
|
||||||
}}
|
}}
|
||||||
@@ -219,24 +229,24 @@ export default class Parameters extends Component {
|
|||||||
activeExamplesKey={oas3Selectors.activeExamplesMember(
|
activeExamplesKey={oas3Selectors.activeExamplesMember(
|
||||||
...pathMethod,
|
...pathMethod,
|
||||||
"requestBody",
|
"requestBody",
|
||||||
"requestBody" // RBs are currently not stored per-mediaType
|
"requestBody", // RBs are currently not stored per-mediaType
|
||||||
)}
|
)}
|
||||||
updateActiveExamplesKey={key => {
|
updateActiveExamplesKey={key => {
|
||||||
this.props.oas3Actions.setActiveExamplesMember({
|
this.props.oas3Actions.setActiveExamplesMember({
|
||||||
name: key,
|
name: key,
|
||||||
pathMethod: this.props.pathMethod,
|
pathMethod: this.props.pathMethod,
|
||||||
contextType: "requestBody",
|
contextType: "requestBody",
|
||||||
contextName: "requestBody" // RBs are currently not stored per-mediaType
|
contextName: "requestBody", // RBs are currently not stored per-mediaType
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onChange={(value, path) => {
|
onChange={(value, path) => {
|
||||||
if(path) {
|
if (path) {
|
||||||
const lastValue = oas3Selectors.requestBodyValue(...pathMethod)
|
const lastValue = oas3Selectors.requestBodyValue(...pathMethod)
|
||||||
const usableValue = Map.isMap(lastValue) ? lastValue : Map()
|
const usableValue = Map.isMap(lastValue) ? lastValue : Map()
|
||||||
return oas3Actions.setRequestBodyValue({
|
return oas3Actions.setRequestBodyValue({
|
||||||
pathMethod,
|
pathMethod,
|
||||||
value: usableValue.setIn(path, value)
|
value: usableValue.setIn(path, value),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
oas3Actions.setRequestBodyValue({ value, pathMethod })
|
oas3Actions.setRequestBodyValue({ value, pathMethod })
|
||||||
@@ -248,7 +258,7 @@ export default class Parameters extends Component {
|
|||||||
name,
|
name,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
contentType={oas3Selectors.requestContentType(...pathMethod)}/>
|
contentType={oas3Selectors.requestContentType(...pathMethod)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
openapi: 3.0.1
|
||||||
|
info:
|
||||||
|
title: Example Swagger
|
||||||
|
version: '1.0'
|
||||||
|
servers:
|
||||||
|
- url: /api/v1
|
||||||
|
paths:
|
||||||
|
/test/{id}/related/{relatedId}:
|
||||||
|
post:
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: relatedId
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
default: 1
|
||||||
|
description: The related ID
|
||||||
|
- in: header
|
||||||
|
name: TRACE-ID
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
default: 1
|
||||||
|
description: The trace ID
|
||||||
|
- in: cookie
|
||||||
|
name: debug
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
enum:
|
||||||
|
- 0
|
||||||
|
- 1
|
||||||
|
description: debug flag
|
||||||
|
- in: header
|
||||||
|
name: USER-ID
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
default: 1
|
||||||
|
description: The user ID
|
||||||
|
- in: query
|
||||||
|
name: asc
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
description: sort asc
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
default: 1
|
||||||
|
description: The ID
|
||||||
|
requestBody:
|
||||||
|
description: Some
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Some
|
||||||
30
test/e2e-cypress/tests/features/parameter-order.js
Normal file
30
test/e2e-cypress/tests/features/parameter-order.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
describe("Parameter order", () => {
|
||||||
|
|
||||||
|
it("should be well ordered", () => {
|
||||||
|
cy.visit("/?url=/documents/features/parameter-order.yaml")
|
||||||
|
.get("#operations-default-post_test__id__related__relatedId_")
|
||||||
|
.click()
|
||||||
|
.get(".parameters > tbody")
|
||||||
|
.children()
|
||||||
|
.each((tr, i, arr) => {
|
||||||
|
const parameterTableRows = Array.from(arr)
|
||||||
|
expect(tr).to.have.attr("data-param-in")
|
||||||
|
if (i === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const inValue = tr[0].getAttribute("data-param-in")
|
||||||
|
if (!inValue) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const beforeInValue = parameterTableRows[i - 1].getAttribute("data-param-in")
|
||||||
|
const sameAsBefore = beforeInValue === inValue
|
||||||
|
if (sameAsBefore) {
|
||||||
|
expect(parameterTableRows[i - 1]).to.have.attr("data-param-in", inValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (let x = i + 1; x < parameterTableRows.length; x++) {
|
||||||
|
expect(parameterTableRows[x]).to.not.have.attr("data-param-in", beforeInValue)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user