Skip to content

Commit 8729670

Browse files
authored
feature: allowEmptyValue controls (#4788)
* add baseline tests * coerce empty strings to null when updating parameter values * add ParameterIncludeEmpty * add redux management for empty parameter value inclusion state * use name+in keying for state management instead of hash keying * update new redux method usages to name+in keying * coerce empty Immutable iterables in onChangeWrapper * OAS3 tests & support * add included empty parameters to requests before dispatching to Swagger Client * make empty inclusion interface prettier * add tests for #4587 * linter fixes * check for truthy value before reaching into property
1 parent dd3afdc commit 8729670

File tree

18 files changed

+1363
-4
lines changed

18 files changed

+1363
-4
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from "react"
2+
import cx from "classnames"
3+
import PropTypes from "prop-types"
4+
import ImPropTypes from "react-immutable-proptypes"
5+
6+
export const ParameterIncludeEmpty = ({ param, isIncluded, onChange, isDisabled }) => {
7+
const onCheckboxChange = e => {
8+
onChange(e.target.checked)
9+
}
10+
if(!param.get("allowEmptyValue")) {
11+
return null
12+
}
13+
return <div className={cx("parameter__empty_value_toggle", {
14+
"disabled": isDisabled
15+
})}>
16+
<input type="checkbox" disabled={isDisabled} checked={!isDisabled && isIncluded} onChange={onCheckboxChange} />
17+
Send empty value
18+
</div>
19+
}
20+
ParameterIncludeEmpty.propTypes = {
21+
param: ImPropTypes.map.isRequired,
22+
isIncluded: PropTypes.bool.isRequired,
23+
isDisabled: PropTypes.bool.isRequired,
24+
onChange: PropTypes.func.isRequired,
25+
}
26+
27+
export default ParameterIncludeEmpty

src/core/components/parameter-row.jsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export default class ParameterRow extends Component {
1515
isExecute: PropTypes.bool,
1616
onChangeConsumes: PropTypes.func.isRequired,
1717
specSelectors: PropTypes.object.isRequired,
18+
specActions: PropTypes.object.isRequired,
1819
pathMethod: PropTypes.array.isRequired,
1920
getConfigs: PropTypes.func.isRequired,
2021
specPath: ImPropTypes.list.isRequired
@@ -61,7 +62,23 @@ export default class ParameterRow extends Component {
6162

6263
onChangeWrapper = (value, isXml = false) => {
6364
let { onChange, rawParam } = this.props
64-
return onChange(rawParam, value, isXml)
65+
let valueForUpstream
66+
67+
// Coerce empty strings and empty Immutable objects to null
68+
if(value === "" || (value && value.size === 0)) {
69+
valueForUpstream = null
70+
} else {
71+
valueForUpstream = value
72+
}
73+
74+
return onChange(rawParam, valueForUpstream, isXml)
75+
}
76+
77+
onChangeIncludeEmpty = (newValue) => {
78+
let { specActions, param, pathMethod } = this.props
79+
const paramName = param.get("name")
80+
const paramIn = param.get("in")
81+
return specActions.updateEmptyParamInclusion(pathMethod, paramName, paramIn, newValue)
6582
}
6683

6784
setDefaultValue = () => {
@@ -120,6 +137,7 @@ export default class ParameterRow extends Component {
120137
const ModelExample = getComponent("modelExample")
121138
const Markdown = getComponent("Markdown")
122139
const ParameterExt = getComponent("ParameterExt")
140+
const ParameterIncludeEmpty = getComponent("ParameterIncludeEmpty")
123141

124142
let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam)
125143
let format = param.get("format")
@@ -225,6 +243,16 @@ export default class ParameterRow extends Component {
225243
: null
226244
}
227245

246+
{
247+
!bodyParam && isExecute ?
248+
<ParameterIncludeEmpty
249+
onChange={this.onChangeIncludeEmpty}
250+
isIncluded={specSelectors.parameterInclusionSettingFor(pathMethod, param.get("name"), param.get("in"))}
251+
isDisabled={value && value.size !== 0}
252+
param={param} />
253+
: null
254+
}
255+
228256
</td>
229257

230258
</tr>

src/core/components/parameters.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ export default class Parameters extends Component {
6565
fn,
6666
getComponent,
6767
getConfigs,
68-
specSelectors,
68+
specSelectors,
69+
specActions,
6970
pathMethod
7071
} = this.props
7172

@@ -107,6 +108,7 @@ export default class Parameters extends Component {
107108
onChange={ this.onChange }
108109
onChangeConsumes={this.onChangeConsumesWrapper}
109110
specSelectors={ specSelectors }
111+
specActions={specActions}
110112
pathMethod={ pathMethod }
111113
isExecute={ isExecute }/>
112114
)).toArray()

src/core/plugins/oas3/wrap-components/parameters.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class Parameters extends Component {
9090
getComponent,
9191
getConfigs,
9292
specSelectors,
93+
specActions,
9394
oas3Actions,
9495
oas3Selectors,
9596
pathMethod,
@@ -151,6 +152,7 @@ class Parameters extends Component {
151152
onChange={ this.onChange }
152153
onChangeConsumes={this.onChangeConsumesWrapper}
153154
specSelectors={ specSelectors }
155+
specActions={ specActions }
154156
pathMethod={ pathMethod }
155157
isExecute={ isExecute }/>
156158
)).toArray()

src/core/plugins/spec/actions.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export const UPDATE_SPEC = "spec_update_spec"
1414
export const UPDATE_URL = "spec_update_url"
1515
export const UPDATE_JSON = "spec_update_json"
1616
export const UPDATE_PARAM = "spec_update_param"
17+
export const UPDATE_EMPTY_PARAM_INCLUSION = "spec_update_empty_param_inclusion"
1718
export const VALIDATE_PARAMS = "spec_validate_param"
1819
export const SET_RESPONSE = "spec_set_response"
1920
export const SET_REQUEST = "spec_set_request"
@@ -270,6 +271,18 @@ export const validateParams = ( payload, isOAS3 ) =>{
270271
}
271272
}
272273

274+
export const updateEmptyParamInclusion = ( pathMethod, paramName, paramIn, includeEmptyValue ) =>{
275+
return {
276+
type: UPDATE_EMPTY_PARAM_INCLUSION,
277+
payload:{
278+
pathMethod,
279+
paramName,
280+
paramIn,
281+
includeEmptyValue
282+
}
283+
}
284+
}
285+
273286
export function clearValidateParams( payload ){
274287
return {
275288
type: CLEAR_VALIDATE_PARAMS,
@@ -327,7 +340,28 @@ export const executeRequest = (req) =>
327340
let { pathName, method, operation } = req
328341
let { requestInterceptor, responseInterceptor } = getConfigs()
329342

343+
330344
let op = operation.toJS()
345+
346+
// ensure that explicitly-included params are in the request
347+
348+
if(op && op.parameters && op.parameters.length) {
349+
op.parameters
350+
.filter(param => param && param.allowEmptyValue === true)
351+
.forEach(param => {
352+
if (specSelectors.parameterInclusionSettingFor([pathName, method], param.name, param.in)) {
353+
req.parameters = req.parameters || {}
354+
const paramValue = req.parameters[param.name]
355+
356+
// if the value is falsy or an empty Immutable iterable...
357+
if(!paramValue || (paramValue && paramValue.size === 0)) {
358+
// set it to empty string, so Swagger Client will treat it as
359+
// present but empty.
360+
req.parameters[param.name] = ""
361+
}
362+
}
363+
})
364+
}
331365

332366
// if url is relative, parseUrl makes it absolute by inferring from `window.location`
333367
req.contextUrl = parseUrl(specSelectors.url()).toString()

src/core/plugins/spec/reducers.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
UPDATE_URL,
1313
UPDATE_JSON,
1414
UPDATE_PARAM,
15+
UPDATE_EMPTY_PARAM_INCLUSION,
1516
VALIDATE_PARAMS,
1617
SET_RESPONSE,
1718
SET_REQUEST,
@@ -70,6 +71,22 @@ export default {
7071
)
7172
},
7273

74+
[UPDATE_EMPTY_PARAM_INCLUSION]: ( state, {payload} ) => {
75+
let { pathMethod, paramName, paramIn, includeEmptyValue } = payload
76+
77+
if(!paramName || !paramIn) {
78+
console.warn("Warning: UPDATE_EMPTY_PARAM_INCLUSION could not generate a paramKey.")
79+
return state
80+
}
81+
82+
const paramKey = `${paramName}.${paramIn}`
83+
84+
return state.setIn(
85+
["meta", "paths", ...pathMethod, "parameter_inclusions", paramKey],
86+
includeEmptyValue
87+
)
88+
},
89+
7390
[VALIDATE_PARAMS]: ( state, { payload: { pathMethod, isOAS3 } } ) => {
7491
let meta = state.getIn( [ "meta", "paths", ...pathMethod ], fromJS({}) )
7592
let isXml = /xml/i.test(meta.get("consumes_value"))

src/core/plugins/spec/selectors.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,11 @@ export const parameterWithMetaByIdentity = (state, pathMethod, param) => {
311311
return mergedParams.find(curr => curr.get("in") === param.get("in") && curr.get("name") === param.get("name"), OrderedMap())
312312
}
313313

314+
export const parameterInclusionSettingFor = (state, pathMethod, paramName, paramIn) => {
315+
const paramKey = `${paramName}.${paramIn}`
316+
return state.getIn(["meta", "paths", ...pathMethod, "parameter_inclusions", paramKey], false)
317+
}
318+
314319

315320
export const parameterWithMeta = (state, pathMethod, paramName, paramIn) => {
316321
const opParams = specJsonWithResolvedSubtrees(state).getIn(["paths", ...pathMethod, "parameters"], OrderedMap())

src/core/presets/base.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import Response from "core/components/response"
4242
import ResponseBody from "core/components/response-body"
4343
import Parameters from "core/components/parameters"
4444
import ParameterExt from "core/components/parameter-extension"
45+
import ParameterIncludeEmpty from "core/components/parameter-include-empty"
4546
import ParameterRow from "core/components/parameter-row"
4647
import Execute from "core/components/execute"
4748
import Headers from "core/components/headers"
@@ -143,6 +144,7 @@ export default function() {
143144
OperationExt,
144145
OperationExtRow,
145146
ParameterExt,
147+
ParameterIncludeEmpty,
146148
OperationTag,
147149
OperationContainer,
148150
DeepLink,

src/style/_table.scss

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,20 @@ table
139139
@include text_code($table-parameter-deprecated-font-color);
140140
}
141141

142+
.parameter__empty_value_toggle {
143+
font-size: 13px;
144+
padding-top: 5px;
145+
padding-bottom: 12px;
146+
147+
input {
148+
margin-right: 7px;
149+
}
150+
151+
&.disabled {
152+
opacity: 0.7;
153+
}
154+
}
155+
142156

143157
.table-container
144158
{

test/core/plugins/spec/actions.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-env mocha */
22
import expect, { createSpy } from "expect"
33
import { fromJS } from "immutable"
4-
import { execute, executeRequest, changeParamByIdentity } from "corePlugins/spec/actions"
4+
import { execute, executeRequest, changeParamByIdentity, updateEmptyParamInclusion } from "corePlugins/spec/actions"
55

66
describe("spec plugin - actions", function(){
77

@@ -207,4 +207,22 @@ describe("spec plugin - actions", function(){
207207
})
208208
})
209209
})
210+
211+
describe("updateEmptyParamInclusion", function () {
212+
it("should map its arguments to a payload", function () {
213+
const pathMethod = ["/one", "get"]
214+
215+
const result = updateEmptyParamInclusion(pathMethod, "param", "query", true)
216+
217+
expect(result).toEqual({
218+
type: "spec_update_empty_param_inclusion",
219+
payload: {
220+
pathMethod,
221+
paramName: "param",
222+
paramIn: "query",
223+
includeEmptyValue: true
224+
}
225+
})
226+
})
227+
})
210228
})

0 commit comments

Comments
 (0)