From 350595fdf2d734e0d373225191d1fc6f696c3304 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 18 Jan 2018 18:41:05 -0600 Subject: [PATCH 1/9] Remove produces/consumes setter from OperationContainer --- src/core/containers/OperationContainer.jsx | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/core/containers/OperationContainer.jsx b/src/core/containers/OperationContainer.jsx index dae2b735fae..6364365af7a 100644 --- a/src/core/containers/OperationContainer.jsx +++ b/src/core/containers/OperationContainer.jsx @@ -82,27 +82,9 @@ export default class OperationContainer extends PureComponent { } componentWillReceiveProps(nextProps) { - const defaultContentType = "application/json" - let { specActions, path, method, op } = nextProps - let operation = op.get("operation") - let producesValue = operation.get("produces_value") - let produces = operation.get("produces") - let consumes = operation.get("consumes") - let consumesValue = operation.get("consumes_value") - if(nextProps.response !== this.props.response) { this.setState({ executeInProgress: false }) } - - if (producesValue === undefined) { - producesValue = produces && produces.size ? produces.first() : defaultContentType - specActions.changeProducesValue([path, method], producesValue) - } - - if (consumesValue === undefined) { - consumesValue = consumes && consumes.size ? consumes.first() : defaultContentType - specActions.changeConsumesValue([path, method], consumesValue) - } } toggleShown =() => { From 2940e1890f8cd030bf394167fd7382fbda3fda18 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 18 Jan 2018 19:05:09 -0600 Subject: [PATCH 2/9] Store consumes/produces information in `meta` key --- src/core/plugins/spec/actions.js | 6 +++--- src/core/plugins/spec/reducers.js | 10 ++++------ src/core/plugins/spec/selectors.js | 5 +++-- test/core/plugins/spec-reducer.js | 4 ++-- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/core/plugins/spec/actions.js b/src/core/plugins/spec/actions.js index a0cf3588308..fcfdc585392 100644 --- a/src/core/plugins/spec/actions.js +++ b/src/core/plugins/spec/actions.js @@ -19,7 +19,7 @@ export const LOG_REQUEST = "spec_log_request" export const CLEAR_RESPONSE = "spec_clear_response" export const CLEAR_REQUEST = "spec_clear_request" export const CLEAR_VALIDATE_PARAMS = "spec_clear_validate_param" -export const UPDATE_OPERATION_VALUE = "spec_update_operation_value" +export const UPDATE_OPERATION_META_VALUE = "spec_UPDATE_OPERATION_META_VALUE" export const UPDATE_RESOLVED = "spec_update_resolved" export const SET_SCHEME = "set_scheme" @@ -150,14 +150,14 @@ export function clearValidateParams( payload ){ export function changeConsumesValue(path, value) { return { - type: UPDATE_OPERATION_VALUE, + type: UPDATE_OPERATION_META_VALUE, payload:{ path, value, key: "consumes_value" } } } export function changeProducesValue(path, value) { return { - type: UPDATE_OPERATION_VALUE, + type: UPDATE_OPERATION_META_VALUE, payload:{ path, value, key: "produces_value" } } } diff --git a/src/core/plugins/spec/reducers.js b/src/core/plugins/spec/reducers.js index 0f8020bf0ef..7d57e38b5b6 100644 --- a/src/core/plugins/spec/reducers.js +++ b/src/core/plugins/spec/reducers.js @@ -12,7 +12,7 @@ import { SET_REQUEST, SET_MUTATED_REQUEST, UPDATE_RESOLVED, - UPDATE_OPERATION_VALUE, + UPDATE_OPERATION_META_VALUE, CLEAR_RESPONSE, CLEAR_REQUEST, CLEAR_VALIDATE_PARAMS, @@ -107,11 +107,9 @@ export default { return state.setIn( [ "mutatedRequests", path, method ], fromJSOrdered(req)) }, - [UPDATE_OPERATION_VALUE]: (state, { payload: { path, value, key } }) => { - let operationPath = ["resolved", "paths", ...path] - if(!state.getIn(operationPath)) { - return state - } + [UPDATE_OPERATION_META_VALUE]: (state, { payload: { path, value, key } }) => { + let operationPath = ["meta", "paths", ...path] + return state.setIn([...operationPath, key], fromJS(value)) }, diff --git a/src/core/plugins/spec/selectors.js b/src/core/plugins/spec/selectors.js index aee730c9fff..ea858e0f36f 100644 --- a/src/core/plugins/spec/selectors.js +++ b/src/core/plugins/spec/selectors.js @@ -306,10 +306,11 @@ export function parametersIncludeType(parameters, typeValue="") { export function contentTypeValues(state, pathMethod) { pathMethod = pathMethod || [] let op = spec(state).getIn(["paths", ...pathMethod], fromJS({})) + let meta = spec(state).getIn(["paths", ...pathMethod], fromJS({})) const parameters = op.get("parameters") || new List() const requestContentType = ( - op.get("consumes_value") ? op.get("consumes_value") + meta.get("consumes_value") ? meta.get("consumes_value") : parametersIncludeType(parameters, "file") ? "multipart/form-data" : parametersIncludeType(parameters, "formData") ? "application/x-www-form-urlencoded" : undefined @@ -317,7 +318,7 @@ export function contentTypeValues(state, pathMethod) { return fromJS({ requestContentType, - responseContentType: op.get("produces_value") + responseContentType: meta.get("produces_value") }) } diff --git a/test/core/plugins/spec-reducer.js b/test/core/plugins/spec-reducer.js index 09e73b7c900..6bfc2defe89 100644 --- a/test/core/plugins/spec-reducer.js +++ b/test/core/plugins/spec-reducer.js @@ -7,7 +7,7 @@ describe("spec plugin - reducer", function(){ describe("update operation value", function() { it("should update the operation at the specified key", () => { - const updateOperationValue = reducer["spec_update_operation_value"] + const updateOperationValue = reducer["spec_UPDATE_OPERATION_META_VALUE"] const state = fromJS({ resolved: { @@ -46,7 +46,7 @@ describe("spec plugin - reducer", function(){ }) it("shouldn't throw an error if we try to update the consumes_value of a null operation", () => { - const updateOperationValue = reducer["spec_update_operation_value"] + const updateOperationValue = reducer["spec_UPDATE_OPERATION_META_VALUE"] const state = fromJS({ resolved: { From 99e37352403713264fca609ae81c39dece2bea5f Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 18 Jan 2018 19:26:14 -0600 Subject: [PATCH 3/9] Migrate produces value state usage to `meta` key --- src/core/components/operation.jsx | 2 +- src/core/plugins/spec/selectors.js | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/core/components/operation.jsx b/src/core/components/operation.jsx index c7a6b194ff8..14f2122758a 100644 --- a/src/core/components/operation.jsx +++ b/src/core/components/operation.jsx @@ -248,7 +248,7 @@ export default class Operation extends PureComponent { oas3Actions={oas3Actions} specActions={ specActions } produces={ produces } - producesValue={ operation.get("produces_value") } + producesValue={ specSelectors.currentProducesFor([path, method]) } specPath={specPath.push("responses")} path={ path } method={ method } diff --git a/src/core/plugins/spec/selectors.js b/src/core/plugins/spec/selectors.js index ea858e0f36f..747c399029e 100644 --- a/src/core/plugins/spec/selectors.js +++ b/src/core/plugins/spec/selectors.js @@ -306,7 +306,8 @@ export function parametersIncludeType(parameters, typeValue="") { export function contentTypeValues(state, pathMethod) { pathMethod = pathMethod || [] let op = spec(state).getIn(["paths", ...pathMethod], fromJS({})) - let meta = spec(state).getIn(["paths", ...pathMethod], fromJS({})) + let meta = state.getIn(["meta", "paths", ...pathMethod], fromJS({})) + const parameters = op.get("parameters") || new List() const requestContentType = ( @@ -328,6 +329,12 @@ export function operationConsumes(state, pathMethod) { return spec(state).getIn(["paths", ...pathMethod, "consumes"], fromJS({})) } +// Get the currently selected produces value for an operation +export function currentProducesFor(state, pathMethod) { + pathMethod = pathMethod || [] + return state.getIn(["meta", "paths", ...pathMethod, "produces_value"], "") +} + export const operationScheme = ( state, path, method ) => { let url = state.get("url") let matchResult = url.match(/^([a-z][a-z0-9+\-.]*):/) From 3be737a904ae1c111205785d67707854f40a8239 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 18 Jan 2018 20:00:18 -0600 Subject: [PATCH 4/9] use meta consumes data for isXml check --- src/core/plugins/spec/reducers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/plugins/spec/reducers.js b/src/core/plugins/spec/reducers.js index 7d57e38b5b6..4207bf318b8 100644 --- a/src/core/plugins/spec/reducers.js +++ b/src/core/plugins/spec/reducers.js @@ -52,8 +52,8 @@ export default { }, [VALIDATE_PARAMS]: ( state, { payload: { pathMethod, isOAS3 } } ) => { - let operation = state.getIn( [ "resolved", "paths", ...pathMethod ] ) - let isXml = /xml/i.test(operation.get("consumes_value")) + let meta = state.getIn( [ "meta", "paths", ...pathMethod ] ) + let isXml = /xml/i.test(meta.get("consumes_value")) return state.updateIn( [ "resolved", "paths", ...pathMethod, "parameters" ], fromJS([]), parameters => { return parameters.withMutations( parameters => { From 2950e0f0d8c3078e84e3c12f0e5db1ff71d0a839 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 18 Jan 2018 20:10:53 -0600 Subject: [PATCH 5/9] Fix failing tests --- src/core/plugins/spec/reducers.js | 11 +++++++-- .../{spec-reducer.js => spec/reducer.js} | 14 ++++++++--- test/core/plugins/spec/selectors.js | 23 +++++++++++++++---- 3 files changed, 39 insertions(+), 9 deletions(-) rename test/core/plugins/{spec-reducer.js => spec/reducer.js} (90%) diff --git a/src/core/plugins/spec/reducers.js b/src/core/plugins/spec/reducers.js index 4207bf318b8..bf8dadf5e33 100644 --- a/src/core/plugins/spec/reducers.js +++ b/src/core/plugins/spec/reducers.js @@ -108,9 +108,16 @@ export default { }, [UPDATE_OPERATION_META_VALUE]: (state, { payload: { path, value, key } }) => { - let operationPath = ["meta", "paths", ...path] + // path is a pathMethod tuple... can't change the name now. + let operationPath = ["resolved", "paths", ...path] + let metaPath = ["meta", "paths", ...path] - return state.setIn([...operationPath, key], fromJS(value)) + if(!state.getIn(operationPath)) { + // do nothing if the operation does not exist + return state + } + + return state.setIn([...metaPath, key], fromJS(value)) }, [CLEAR_RESPONSE]: (state, { payload: { path, method } } ) =>{ diff --git a/test/core/plugins/spec-reducer.js b/test/core/plugins/spec/reducer.js similarity index 90% rename from test/core/plugins/spec-reducer.js rename to test/core/plugins/spec/reducer.js index 6bfc2defe89..00920ecc4fe 100644 --- a/test/core/plugins/spec-reducer.js +++ b/test/core/plugins/spec/reducer.js @@ -5,8 +5,8 @@ import reducer from "corePlugins/spec/reducers" describe("spec plugin - reducer", function(){ - describe("update operation value", function() { - it("should update the operation at the specified key", () => { + describe("update operation meta value", function() { + it("should update the operation metadata at the specified key", () => { const updateOperationValue = reducer["spec_UPDATE_OPERATION_META_VALUE"] const state = fromJS({ @@ -34,7 +34,15 @@ describe("spec plugin - reducer", function(){ "paths": { "/pet": { "post": { - "description": "my operation", + "description": "my operation" + } + } + } + }, + meta: { + paths: { + "/pet": { + post: { "consumes_value": "application/json" } } diff --git a/test/core/plugins/spec/selectors.js b/test/core/plugins/spec/selectors.js index df6b1c219b0..85e053beda5 100644 --- a/test/core/plugins/spec/selectors.js +++ b/test/core/plugins/spec/selectors.js @@ -56,6 +56,13 @@ describe("spec plugin - selectors", function(){ // Given let state = fromJS({ resolved: { + paths: { + "/one": { + get: {} + } + } + }, + meta: { paths: { "/one": { get: { @@ -83,13 +90,21 @@ describe("spec plugin - selectors", function(){ paths: { "/one": { get: { - "consumes_value": "one", - "parameters": [{ + "parameters": [{ "type": "file" }], } } } + }, + meta: { + paths: { + "/one": { + get: { + "consumes_value": "one", + } + } + } } }) @@ -106,7 +121,7 @@ describe("spec plugin - selectors", function(){ paths: { "/one": { get: { - "parameters": [{ + "parameters": [{ "type": "file" }], } @@ -128,7 +143,7 @@ describe("spec plugin - selectors", function(){ paths: { "/one": { get: { - "parameters": [{ + "parameters": [{ "type": "formData" }], } From 58eda41f278d0ef9be593fe527f3b3691193a279 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 18 Jan 2018 20:13:38 -0600 Subject: [PATCH 6/9] normalize action name casing --- src/core/plugins/spec/actions.js | 2 +- test/core/plugins/spec/reducer.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/plugins/spec/actions.js b/src/core/plugins/spec/actions.js index fcfdc585392..0d0d853a0e3 100644 --- a/src/core/plugins/spec/actions.js +++ b/src/core/plugins/spec/actions.js @@ -19,7 +19,7 @@ export const LOG_REQUEST = "spec_log_request" export const CLEAR_RESPONSE = "spec_clear_response" export const CLEAR_REQUEST = "spec_clear_request" export const CLEAR_VALIDATE_PARAMS = "spec_clear_validate_param" -export const UPDATE_OPERATION_META_VALUE = "spec_UPDATE_OPERATION_META_VALUE" +export const UPDATE_OPERATION_META_VALUE = "spec_update_operation_meta_value" export const UPDATE_RESOLVED = "spec_update_resolved" export const SET_SCHEME = "set_scheme" diff --git a/test/core/plugins/spec/reducer.js b/test/core/plugins/spec/reducer.js index 00920ecc4fe..1c91e054add 100644 --- a/test/core/plugins/spec/reducer.js +++ b/test/core/plugins/spec/reducer.js @@ -7,7 +7,7 @@ describe("spec plugin - reducer", function(){ describe("update operation meta value", function() { it("should update the operation metadata at the specified key", () => { - const updateOperationValue = reducer["spec_UPDATE_OPERATION_META_VALUE"] + const updateOperationValue = reducer["spec_update_operation_meta_value"] const state = fromJS({ resolved: { @@ -54,7 +54,7 @@ describe("spec plugin - reducer", function(){ }) it("shouldn't throw an error if we try to update the consumes_value of a null operation", () => { - const updateOperationValue = reducer["spec_UPDATE_OPERATION_META_VALUE"] + const updateOperationValue = reducer["spec_update_operation_meta_value"] const state = fromJS({ resolved: { From 862a811619581938cafd8af2c7d42b1c028bf7c1 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 18 Jan 2018 20:24:03 -0600 Subject: [PATCH 7/9] restore correct produces fallback value logic --- src/core/plugins/spec/selectors.js | 17 +++++++- test/core/plugins/spec/selectors.js | 67 ++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/src/core/plugins/spec/selectors.js b/src/core/plugins/spec/selectors.js index 747c399029e..e19c590c31b 100644 --- a/src/core/plugins/spec/selectors.js +++ b/src/core/plugins/spec/selectors.js @@ -307,6 +307,7 @@ export function contentTypeValues(state, pathMethod) { pathMethod = pathMethod || [] let op = spec(state).getIn(["paths", ...pathMethod], fromJS({})) let meta = state.getIn(["meta", "paths", ...pathMethod], fromJS({})) + let producesValue = currentProducesFor(state, pathMethod) const parameters = op.get("parameters") || new List() @@ -319,7 +320,7 @@ export function contentTypeValues(state, pathMethod) { return fromJS({ requestContentType, - responseContentType: meta.get("produces_value") + responseContentType: producesValue }) } @@ -332,7 +333,19 @@ export function operationConsumes(state, pathMethod) { // Get the currently selected produces value for an operation export function currentProducesFor(state, pathMethod) { pathMethod = pathMethod || [] - return state.getIn(["meta", "paths", ...pathMethod, "produces_value"], "") + + const operation = spec(state).getIn(["paths", ...pathMethod], null) + + if(operation === null) { + // return nothing if the operation does not exist + return + } + + const currentProducesValue = state.getIn(["meta", "paths", ...pathMethod, "produces_value"], null) + const firstProducesArrayItem = operation.getIn(["produces", 0], null) + + return currentProducesValue || firstProducesArrayItem || "application/json" + } export const operationScheme = ( state, path, method ) => { diff --git a/test/core/plugins/spec/selectors.js b/test/core/plugins/spec/selectors.js index 85e053beda5..824464899f9 100644 --- a/test/core/plugins/spec/selectors.js +++ b/test/core/plugins/spec/selectors.js @@ -83,6 +83,71 @@ describe("spec plugin - selectors", function(){ }) }) + it("should default to the first `produces` array value if current is not set", function(){ + // Given + let state = fromJS({ + resolved: { + paths: { + "/one": { + get: { + produces: [ + "application/xml", + "application/whatever" + ] + } + } + } + }, + meta: { + paths: { + "/one": { + get: { + "consumes_value": "one" + } + } + } + } + }) + + // When + let contentTypes = contentTypeValues(state, [ "/one", "get" ]) + // Then + expect(contentTypes.toJS()).toEqual({ + requestContentType: "one", + responseContentType: "application/xml" + }) + }) + + it("should default to `application/json` if a default produces value is not available", function(){ + // Given + let state = fromJS({ + resolved: { + paths: { + "/one": { + get: {} + } + } + }, + meta: { + paths: { + "/one": { + get: { + "consumes_value": "one" + } + } + } + } + }) + + // When + let contentTypes = contentTypeValues(state, [ "/one", "get" ]) + // Then + expect(contentTypes.toJS()).toEqual({ + requestContentType: "one", + responseContentType: "application/json" + }) + }) + it("should prioritize consumes value first from an operation", function(){ // Given let state = fromJS({ @@ -158,7 +223,7 @@ describe("spec plugin - selectors", function(){ expect(contentTypes.toJS().requestContentType).toEqual("application/x-www-form-urlencoded") }) - it("should be ok, if no operation found", function(){ + it("should return nothing, if the operation does not exist", function(){ // Given let state = fromJS({ }) From d56b238ce45a4bfd83e7868ccfc20732c513981c Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Fri, 19 Jan 2018 21:13:08 -0600 Subject: [PATCH 8/9] default to first server if current server is invalid --- src/core/plugins/oas3/components/servers.jsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/core/plugins/oas3/components/servers.jsx b/src/core/plugins/oas3/components/servers.jsx index d6ed4a63b3a..6e57f9b9a11 100644 --- a/src/core/plugins/oas3/components/servers.jsx +++ b/src/core/plugins/oas3/components/servers.jsx @@ -35,7 +35,11 @@ export default class Servers extends React.Component { if(this.props.currentServer !== nextProps.currentServer) { // Server has changed, we may need to set default values let currentServerDefinition = servers - .find(v => v.get("url") === nextProps.currentServer) || OrderedMap() + .find(v => v.get("url") === nextProps.currentServer) + + if(!currentServerDefinition) { + return this.setServer(servers.first().get("url")) + } let currentServerVariableDefs = currentServerDefinition.get("variables") || OrderedMap() @@ -90,6 +94,7 @@ export default class Servers extends React.Component { getEffectiveServerValue } = this.props + let currentServerDefinition = servers.find(v => v.get("url") === currentServer) || OrderedMap() let currentServerVariableDefs = currentServerDefinition.get("variables") || OrderedMap() From 2d593cf1090e8b73ee7d2cbd11d3c92858fb2a77 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Fri, 19 Jan 2018 22:30:13 -0600 Subject: [PATCH 9/9] improve(response-rendering): give Content-Disposition render priority --- src/core/components/response-body.jsx | 71 +++++++++++++-------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/src/core/components/response-body.jsx b/src/core/components/response-body.jsx index 2c1eb92f64a..863d5c70568 100644 --- a/src/core/components/response-body.jsx +++ b/src/core/components/response-body.jsx @@ -20,8 +20,41 @@ export default class ResponseBody extends React.Component { let body, bodyEl url = url || "" - // JSON - if (/json/i.test(contentType)) { + if ( + /^application\/octet-stream/i.test(contentType) || + (headers["Content-Disposition"] && (/attachment/i).test(headers["Content-Disposition"])) || + (headers["content-disposition"] && (/attachment/i).test(headers["content-disposition"])) || + (headers["Content-Description"] && (/File Transfer/i).test(headers["Content-Description"])) || + (headers["content-description"] && (/File Transfer/i).test(headers["content-description"]))) { + // Download + + const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) + + if (!isSafari && "Blob" in window) { + let type = contentType || "text/html" + let blob = (content instanceof Blob) ? content : new Blob([content], {type: type}) + let href = window.URL.createObjectURL(blob) + let fileName = url.substr(url.lastIndexOf("/") + 1) + let download = [type, fileName, href].join(":") + + // Use filename from response header, + // First check if filename is quoted (e.g. contains space), if no, fallback to not quoted check + let disposition = headers["content-disposition"] || headers["Content-Disposition"] + if (typeof disposition !== "undefined") { + let responseFilename = extractFileNameFromContentDispositionHeader(disposition) + if (responseFilename !== null) { + download = responseFilename + } + } + + bodyEl = + } else { + bodyEl =
Download headers detected but your browser does not support downloading binary via XHR (Blob).
+ } + + // Anything else (CORS) + } else if (/json/i.test(contentType)) { + // JSON try { body = JSON.stringify(JSON.parse(content), null, " ") } catch (error) { @@ -53,40 +86,6 @@ export default class ResponseBody extends React.Component { // Audio } else if (/^audio\//i.test(contentType)) { bodyEl =
- - // Download - } else if ( - /^application\/octet-stream/i.test(contentType) || - (headers["Content-Disposition"] && (/attachment/i).test(headers["Content-Disposition"])) || - (headers["content-disposition"] && (/attachment/i).test(headers["content-disposition"])) || - (headers["Content-Description"] && (/File Transfer/i).test(headers["Content-Description"])) || - (headers["content-description"] && (/File Transfer/i).test(headers["content-description"]))) { - - const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) - - if (!isSafari && "Blob" in window) { - let type = contentType || "text/html" - let blob = (content instanceof Blob) ? content : new Blob([content], {type: type}) - let href = window.URL.createObjectURL(blob) - let fileName = url.substr(url.lastIndexOf("/") + 1) - let download = [type, fileName, href].join(":") - - // Use filename from response header, - // First check if filename is quoted (e.g. contains space), if no, fallback to not quoted check - let disposition = headers["content-disposition"] || headers["Content-Disposition"] - if (typeof disposition !== "undefined") { - let responseFilename = extractFileNameFromContentDispositionHeader(disposition) - if (responseFilename !== null) { - download = responseFilename - } - } - - bodyEl = - } else { - bodyEl =
Download headers detected but your browser does not support downloading binary via XHR (Blob).
- } - - // Anything else (CORS) } else if (typeof content === "string") { bodyEl = } else if ( content.size > 0 ) {