feat: group / sort parameters by location (#6745)

This commit is contained in:
Mahtis Michel
2021-01-14 20:39:01 +01:00
committed by GitHub
parent a2f7917661
commit ddaee4ec42
3 changed files with 164 additions and 61 deletions

View File

@@ -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>
} }

View File

@@ -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

View 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)
}
})
})
})