diff --git a/src/core/components/copy-component.jsx b/src/core/components/copy-component.jsx
new file mode 100644
index 00000000000..de2652cce03
--- /dev/null
+++ b/src/core/components/copy-component.jsx
@@ -0,0 +1,31 @@
+import React from "react"
+import PropTypes from "prop-types"
+
+function copyToClipboard(e, getBaseUrl, path, method) {
+ const baseUrl = document.createElement("input")
+ baseUrl.setAttribute("value", getBaseUrl(path, method))
+ baseUrl.addEventListener("copy", () => e.stopPropagation())
+ document.body.appendChild(baseUrl)
+ baseUrl.select()
+ document.execCommand("copy")
+ document.body.removeChild(baseUrl)
+ }
+
+const CopyComponent = ({ getBaseUrl, path, method }) => {
+
+ return (
+
copyToClipboard(e, getBaseUrl, path, method)}>
+
+
+ )
+}
+
+CopyComponent.propTypes = {
+ getBaseUrl: PropTypes.func.isRequired,
+ path: PropTypes.object.isRequired,
+ method: PropTypes.object.isRequired
+ }
+
+ export default CopyComponent
diff --git a/src/core/components/curl.jsx b/src/core/components/curl.jsx
index ad1a2df4677..bda2d36e084 100644
--- a/src/core/components/curl.jsx
+++ b/src/core/components/curl.jsx
@@ -19,7 +19,7 @@ export default class Curl extends React.Component {
return (
Curl
-
diff --git a/src/core/components/operation-summary.jsx b/src/core/components/operation-summary.jsx
index bed0b3b4dea..16768232274 100644
--- a/src/core/components/operation-summary.jsx
+++ b/src/core/components/operation-summary.jsx
@@ -3,6 +3,7 @@ import PropTypes from "prop-types"
import { Iterable, List } from "immutable"
import ImPropTypes from "react-immutable-proptypes"
import toString from "lodash/toString"
+import CopyComponent from "./copy-component"
export default class OperationSummary extends PureComponent {
@@ -15,6 +16,7 @@ export default class OperationSummary extends PureComponent {
getConfigs: PropTypes.func.isRequired,
authActions: PropTypes.object,
authSelectors: PropTypes.object,
+ getBaseUrl: PropTypes.func.isRequired
}
static defaultProps = {
@@ -32,6 +34,7 @@ export default class OperationSummary extends PureComponent {
authSelectors,
operationProps,
specPath,
+ getBaseUrl
} = this.props
let {
@@ -43,6 +46,7 @@ export default class OperationSummary extends PureComponent {
operationId,
originalOperationId,
displayOperationId,
+ path
} = operationProps.toJS()
let {
@@ -80,9 +84,10 @@ export default class OperationSummary extends PureComponent {
}}
/>
}
+
{/* TODO: use wrapComponents here, swagger-ui doesn't care about jumpToPath */}
)
- }
+ }
}
diff --git a/src/core/components/operation.jsx b/src/core/components/operation.jsx
index f441a5d0fbe..f50632fb994 100644
--- a/src/core/components/operation.jsx
+++ b/src/core/components/operation.jsx
@@ -1,7 +1,7 @@
import React, { PureComponent } from "react"
import PropTypes from "prop-types"
import { getList } from "core/utils"
-import { getExtensions, sanitizeUrl, escapeDeepLinkPath } from "core/utils"
+import { getExtensions, sanitizeUrl, escapeDeepLinkPath, baseUrl } from "core/utils"
import { Iterable, List } from "immutable"
import ImPropTypes from "react-immutable-proptypes"
@@ -110,9 +110,23 @@ export default class Operation extends PureComponent {
let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method )
+ const getBaseUrl = (path, method) => {
+ const obj = {
+ spec: specSelectors.specJson().toJS(),
+ contextUrl: specSelectors.url(),
+ specIsOAS3: specSelectors.isOAS3(specSelectors.specJson().toJS()),
+ server: oas3Selectors.selectedServer(),
+ pathName: path,
+ method,
+ scheme: specSelectors.operationScheme()
+ }
+ return baseUrl(obj)
+ }
+
return (
-
+
{ (operation && operation.size) || operation === null ? null :
@@ -233,4 +247,4 @@ export default class Operation extends PureComponent {
)
}
-}
+}
\ No newline at end of file
diff --git a/src/core/components/svg-assets.jsx b/src/core/components/svg-assets.jsx
index 3b078edda7e..ace497f4bcb 100644
--- a/src/core/components/svg-assets.jsx
+++ b/src/core/components/svg-assets.jsx
@@ -36,6 +36,10 @@ const SvgAssets = () =>
+
+
+
+
diff --git a/src/core/utils.js b/src/core/utils.js
index d3e6104e4e1..5707aa0a30e 100644
--- a/src/core/utils.js
+++ b/src/core/utils.js
@@ -9,6 +9,8 @@ import eq from "lodash/eq"
import { memoizedSampleFromSchema, memoizedCreateXMLExample } from "core/plugins/samples/fn"
import win from "./window"
import cssEscape from "css.escape"
+import url from "url"
+import getIn from "lodash/get"
const DEFAULT_RESPONSE_KEY = "default"
@@ -842,4 +844,109 @@ export function paramToValue(param, paramValues) {
.filter(value => value !== undefined)
return values[0]
+}
+
+const stripNonAlpha = str => (str ? str.replace(/\W/g, "") : null)
+
+const buildOas3UrlWithContext = (ourUrl = "", contextUrl = "") => {
+ const parsedUrl = url.parse(ourUrl)
+ const parsedContextUrl = url.parse(contextUrl)
+
+ const computedScheme = stripNonAlpha(parsedUrl.protocol) || stripNonAlpha(parsedContextUrl.protocol) || ""
+ const computedHost = parsedUrl.host || parsedContextUrl.host
+ const computedPath = parsedUrl.pathname || ""
+ let res
+
+ if (computedScheme && computedHost) {
+ res = `${computedScheme}://${computedHost + computedPath}`
+
+ // If last character is '/', trim it off
+ }
+ else {
+ res = computedPath
+ }
+
+ return res[res.length - 1] === "/" ? res.slice(0, -1) : res
+}
+
+function getVariableTemplateNames(str) {
+ const results = []
+ const re = /{([^}]+)}/g
+ let text
+
+ // eslint-disable-next-line no-cond-assign
+ while (text = re.exec(str)) {
+ results.push(text[1])
+ }
+ return results
+}
+
+const oas3BaseUrl = ({ spec, pathName, method, server, contextUrl, serverVariables = {} }) => {
+ const servers =
+ getIn(spec, ["paths", pathName, (method || "").toLowerCase(), "servers"]) ||
+ getIn(spec, ["paths", pathName, "servers"]) ||
+ getIn(spec, ["servers"])
+
+ let selectedServerUrl = ""
+ let selectedServerObj = null
+
+ if (server && servers && servers.length) {
+ const serverUrls = servers.map(srv => srv.url)
+
+ if (serverUrls.indexOf(server) > -1) {
+ selectedServerUrl = server
+ selectedServerObj = servers[serverUrls.indexOf(server)]
+ }
+ }
+
+ if (!selectedServerUrl && servers && servers.length) {
+ // default to the first server if we don't have one by now
+ selectedServerUrl = servers[0].url
+ selectedServerObj = servers[0]
+ }
+
+ if (selectedServerUrl.indexOf("{") > -1) {
+ // do variable substitution
+ const varNames = getVariableTemplateNames(selectedServerUrl)
+ varNames.forEach((vari) => {
+ if (selectedServerObj.variables && selectedServerObj.variables[vari]) {
+ // variable is defined in server
+ const variableDefinition = selectedServerObj.variables[vari]
+ const variableValue = serverVariables[vari] || variableDefinition.default
+
+ const re = new RegExp(`{${vari}}`, "g")
+ selectedServerUrl = selectedServerUrl.replace(re, variableValue)
+ }
+ })
+ }
+
+ //return selectedServerUrl
+ return buildOas3UrlWithContext(selectedServerUrl, contextUrl)
+}
+
+const swagger2BaseUrl = ({ spec, scheme, contextUrl = "" }) => {
+ const parsedContextUrl = url.parse(contextUrl)
+ const firstSchemeInSpec = Array.isArray(spec.schemes) ? spec.schemes[0] : null
+
+ const computedScheme = scheme || firstSchemeInSpec || stripNonAlpha(parsedContextUrl.protocol) || "http"
+ const computedHost = spec.host || parsedContextUrl.host || ""
+ const computedPath = spec.basePath || ""
+ let res
+
+ if (computedScheme && computedHost) {
+ // we have what we need for an absolute URL
+ res = `${computedScheme}://${computedHost + computedPath}`
+ }
+ else {
+ // if not, a relative URL will have to do
+ res = computedPath
+ }
+
+ // If last character is '/', trim it off
+ return res[res.length - 1] === "/" ? res.slice(0, -1) : res
+}
+
+export const baseUrl = (obj) => {
+ const baseUrl = obj.specIsOAS3 ? oas3BaseUrl(obj) : swagger2BaseUrl(obj)
+ return (`${baseUrl}${obj.pathName}`)
}
\ No newline at end of file