feat(ux): enhance media-type switching experience in RequestBodyEditor (#6837)
* feat(ux): enhance media-type switching experience in RequestBodyEditor 1. When canceling the try-out mode the request body will be reset to its initial state. 2. When the user switches the media-type in the try-out mode, the experience is as follows: - If the user did edit the request body the body wont be touched and only media type is updated. This is to ensure that user content is NEVER accidentally overwritten with a default value. - If the user did not edit the request body it is safe to be replaced by the default value of the target media-type. Multiple example needed some care in order to allow the retain example value to function properly * fix(test): workaround cypress issue that can't be reproduced manually * test: added new feature to ensure enhanced user editing flow Signed-off-by: mathis-m <mathis.michel@outlook.de>
This commit is contained in:
@@ -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_REQUEST_BODY_VALUE_RETAIN_FLAG = "oas3_set_request_body_retain_flag"
|
||||
export const UPDATE_REQUEST_BODY_INCLUSION = "oas3_set_request_body_inclusion"
|
||||
export const UPDATE_ACTIVE_EXAMPLES_MEMBER = "oas3_set_active_examples_member"
|
||||
export const UPDATE_REQUEST_CONTENT_TYPE = "oas3_set_request_content_type"
|
||||
@@ -26,6 +27,14 @@ export function setRequestBodyValue ({ value, pathMethod }) {
|
||||
}
|
||||
}
|
||||
|
||||
export const setRetainRequestBodyValueFlag = ({ value, pathMethod }) => {
|
||||
return {
|
||||
type: UPDATE_REQUEST_BODY_VALUE_RETAIN_FLAG,
|
||||
payload: { value, pathMethod }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function setRequestBodyInclusion ({ value, pathMethod, name }) {
|
||||
return {
|
||||
type: UPDATE_REQUEST_BODY_INCLUSION,
|
||||
|
||||
@@ -17,6 +17,7 @@ export default class RequestBodyEditor extends PureComponent {
|
||||
|
||||
static defaultProps = {
|
||||
onChange: NOOP,
|
||||
userHasEditedBody: false,
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
@@ -65,7 +66,7 @@ export default class RequestBodyEditor extends PureComponent {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if(!nextProps.value && nextProps.defaultValue && !!this.state.value) {
|
||||
// if new value is falsy, we have a default, AND the falsy value didn't
|
||||
@@ -77,7 +78,7 @@ export default class RequestBodyEditor extends PureComponent {
|
||||
render() {
|
||||
let {
|
||||
getComponent,
|
||||
errors
|
||||
errors,
|
||||
} = this.props
|
||||
|
||||
let {
|
||||
|
||||
@@ -4,7 +4,7 @@ import ImPropTypes from "react-immutable-proptypes"
|
||||
import { Map, OrderedMap, List } from "immutable"
|
||||
import { getCommonExtensions, getSampleSchema, stringify, isEmptyValue } from "core/utils"
|
||||
|
||||
function getDefaultRequestBodyValue(requestBody, mediaType, activeExamplesKey) {
|
||||
export const getDefaultRequestBodyValue = (requestBody, mediaType, activeExamplesKey) => {
|
||||
const mediaTypeValue = requestBody.getIn(["content", mediaType])
|
||||
const schema = mediaTypeValue.get("schema").toJS()
|
||||
|
||||
@@ -32,6 +32,7 @@ function getDefaultRequestBodyValue(requestBody, mediaType, activeExamplesKey) {
|
||||
|
||||
|
||||
const RequestBody = ({
|
||||
userHasEditedBody,
|
||||
requestBody,
|
||||
requestBodyValue,
|
||||
requestBodyInclusionSetting,
|
||||
@@ -47,6 +48,7 @@ const RequestBody = ({
|
||||
onChangeIncludeEmpty,
|
||||
activeExamplesKey,
|
||||
updateActiveExamplesKey,
|
||||
setRetainRequestBodyValueFlag
|
||||
}) => {
|
||||
const handleFile = (e) => {
|
||||
onChange(e.target.files[0])
|
||||
@@ -222,6 +224,7 @@ const RequestBody = ({
|
||||
{
|
||||
examplesForMediaType ? (
|
||||
<ExamplesSelectValueRetainer
|
||||
userHasEditedBody={userHasEditedBody}
|
||||
examples={examplesForMediaType}
|
||||
currentKey={activeExamplesKey}
|
||||
currentUserInputValue={requestBodyValue}
|
||||
@@ -229,6 +232,7 @@ const RequestBody = ({
|
||||
updateValue={onChange}
|
||||
defaultToFirstExample={true}
|
||||
getComponent={getComponent}
|
||||
setRetainRequestBodyValueFlag={setRetainRequestBodyValueFlag}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
@@ -276,6 +280,7 @@ const RequestBody = ({
|
||||
}
|
||||
|
||||
RequestBody.propTypes = {
|
||||
userHasEditedBody: PropTypes.bool.isRequired,
|
||||
requestBody: ImPropTypes.orderedMap.isRequired,
|
||||
requestBodyValue: ImPropTypes.orderedMap.isRequired,
|
||||
requestBodyInclusionSetting: ImPropTypes.Map.isRequired,
|
||||
@@ -291,6 +296,8 @@ RequestBody.propTypes = {
|
||||
specPath: PropTypes.array.isRequired,
|
||||
activeExamplesKey: PropTypes.string,
|
||||
updateActiveExamplesKey: PropTypes.func,
|
||||
setRetainRequestBodyValueFlag: PropTypes.func,
|
||||
oas3Actions: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
export default RequestBody
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
UPDATE_RESPONSE_CONTENT_TYPE,
|
||||
SET_REQUEST_BODY_VALIDATE_ERROR,
|
||||
CLEAR_REQUEST_BODY_VALIDATE_ERROR,
|
||||
CLEAR_REQUEST_BODY_VALUE,
|
||||
CLEAR_REQUEST_BODY_VALUE, UPDATE_REQUEST_BODY_VALUE_RETAIN_FLAG,
|
||||
} from "./actions"
|
||||
|
||||
export default {
|
||||
@@ -42,6 +42,10 @@ export default {
|
||||
})
|
||||
return state.setIn(["requestData", path, method, "bodyValue"], newVal)
|
||||
},
|
||||
[UPDATE_REQUEST_BODY_VALUE_RETAIN_FLAG]: (state, { payload: { value, pathMethod } } ) =>{
|
||||
let [path, method] = pathMethod
|
||||
return state.setIn(["requestData", path, method, "retainBodyValue"], value)
|
||||
},
|
||||
[UPDATE_REQUEST_BODY_INCLUSION]: (state, { payload: { value, pathMethod, name } } ) =>{
|
||||
let [path, method] = pathMethod
|
||||
return state.setIn( [ "requestData", path, method, "bodyInclusion", name ], value)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { OrderedMap, Map } from "immutable"
|
||||
import { OrderedMap, Map, List } from "immutable"
|
||||
import { isOAS3 as isOAS3Helper } from "./helpers"
|
||||
import { getDefaultRequestBodyValue } from "./components/request-body"
|
||||
import { stringify } from "../../utils"
|
||||
|
||||
// Helpers
|
||||
|
||||
@@ -54,6 +56,43 @@ export const requestBodyValue = onlyOAS3((state, path, method) => {
|
||||
}
|
||||
)
|
||||
|
||||
export const shouldRetainRequestBodyValue = onlyOAS3((state, path, method) => {
|
||||
return state.getIn(["requestData", path, method, "retainBodyValue"]) || false
|
||||
}
|
||||
)
|
||||
|
||||
export const hasUserEditedBody = (state, path, method) => (system) => {
|
||||
const {oas3Selectors, specSelectors} = system.getSystem()
|
||||
const spec = specSelectors.specJson()
|
||||
if(isOAS3Helper(spec)) {
|
||||
let userHasEditedBody = false
|
||||
const currentMediaType = oas3Selectors.requestContentType(path, method)
|
||||
let userEditedRequestBody = oas3Selectors.requestBodyValue(path, method)
|
||||
if (Map.isMap(userEditedRequestBody)) {
|
||||
// context is not application/json media-type
|
||||
userEditedRequestBody = stringify(userEditedRequestBody.mapEntries((kv) => Map.isMap(kv[1]) ? [kv[0], kv[1].get("value")] : kv).toJS())
|
||||
}
|
||||
if(List.isList(userEditedRequestBody)) {
|
||||
userEditedRequestBody = stringify(userEditedRequestBody)
|
||||
}
|
||||
if (currentMediaType) {
|
||||
const currentMediaTypeDefaultBodyValue = getDefaultRequestBodyValue(
|
||||
specSelectors.specResolvedSubtree(["paths", path, method, "requestBody"]),
|
||||
currentMediaType,
|
||||
oas3Selectors.activeExamplesMember(
|
||||
path, method,
|
||||
"requestBody",
|
||||
"requestBody",
|
||||
)
|
||||
)
|
||||
userHasEditedBody = !!userEditedRequestBody && userEditedRequestBody !== currentMediaTypeDefaultBodyValue
|
||||
}
|
||||
return userHasEditedBody
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const requestBodyInclusionSetting = onlyOAS3((state, path, method) => {
|
||||
return state.getIn(["requestData", path, method, "bodyInclusion"]) || Map()
|
||||
}
|
||||
|
||||
@@ -525,7 +525,7 @@ export const isMediaTypeSchemaPropertiesEqual = ( state, pathMethod, currentMedi
|
||||
}
|
||||
let currentMediaTypeSchemaProperties = requestBodyContent.getIn([currentMediaType, "schema", "properties"], fromJS([]))
|
||||
let targetMediaTypeSchemaProperties = requestBodyContent.getIn([targetMediaType, "schema", "properties"], fromJS([]))
|
||||
return currentMediaTypeSchemaProperties.equals(targetMediaTypeSchemaProperties) ? true: false
|
||||
return !!currentMediaTypeSchemaProperties.equals(targetMediaTypeSchemaProperties)
|
||||
}
|
||||
|
||||
function returnSelfOrNewMap(obj) {
|
||||
|
||||
Reference in New Issue
Block a user