Compare commits

..
12 Commits
Author SHA1 Message Date
kyle 9e3a801750 v3.14.2 (#4548)
* v3.14.2

* rebuild dist

* constrict `eslint-plugin-react` version

https://github.com/yannickcr/eslint-plugin-react/issues/1789
2018-05-12 00:05:49 -07:00
kyle 9155eb946e fix: always display locally-available title property as a model's name (#4542)
* fix: always display locally-available `title` property as a model's name
* fix failing unit test
* add e2e test case
2018-05-10 22:15:36 -07:00
maryscar 66e9bd735a fix: model property color contrast (#4540) 2018-05-10 16:19:59 -07:00
kyle ae849c31ea fix: remove outdated Safari check for Blob downloads (#4541) 2018-05-10 16:07:29 -07:00
Helder Sepulveda 1a95b9e9c9 New fixes for 1 vulnerable dependency (deep-extend) (#4507)
* fix: package.json to reduce vulnerabilities

The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/npm:lodash:20180130

Latest report for heldersepu/swagger-ui:
https://snyk.io/test/github/heldersepu/swagger-ui

* fix: package.json to reduce vulnerabilities

The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/npm:deep-extend:20180409

Latest report for heldersepu/swagger-ui:
https://snyk.io/test/github/heldersepu/swagger-ui
2018-05-07 23:00:00 -07:00
Brett Randall 073b1df730 Updated 'urls' (An array of API definition objects) description. (#4525)
* Updated 'urls' (An array of API definition objects) description.

Updated 'urls' (An array of API definition objects) description to show an actual object array containing two (2) urls, clarifying this configuration-option.

* urls is an array, not a string
2018-05-07 21:56:08 -07:00
kyle 11c586309f v3.14.1 (#4521)
* v3.14.1

* transform `const` & `let` to `var` in node_modules, for IE11

* rebuild dist
2018-05-04 19:25:20 -07:00
kyle a37d637061 meta: multiple issue templates (#4519)
* MOAR issue templates

* ...LESS issue templates
2018-05-04 12:58:08 -07:00
kyle 75747424cf improvement: sanitization via DOMPurify (#4513)
* swap `sanitize-html` for `dompurify`

* set up node enzyme tests with jsdom

dompurify, as the name suggests, needs a DOM or it won't work!

* reconcile tests and sanitizer settings

* remove obsolete sanitizeOptions

* add `jsdom` dependency
2018-05-04 10:06:44 -07:00
Helder Sepulveda 8055129dd2 Improve downloadable HighlightCode filename (#4508)
* Update highlight-code.jsx
* improve filename no more response.txt
* use new `fileName` prop for file names
* use template strings for `fileName` prop values
* fall back to old "response.txt" file name if none is provided
2018-05-03 17:33:21 -07:00
kyle 7049de6201 v3.14.0 (#4492)
* v3.14.0

* fix: simplify config fetch interceptor implementation

* add `loadRemoteConfig` flag to requests

* v3.14.0
2018-04-27 23:20:13 -07:00
kyle 8cef3adfe6 fix: path-item $ref operation metadata storage (#4489) 2018-04-27 10:29:33 -07:00
36 changed files with 379 additions and 344 deletions
+1
View File
@@ -6,6 +6,7 @@
],
"plugins": [
"transform-runtime",
"babel-plugin-transform-es2015-constants",
[
"module-alias",
[
+72
View File
@@ -0,0 +1,72 @@
---
name: Bug report
about: Report an issue you're experiencing
---
<!---
Thanks for filing a bug report 😄 ! Before you submit, please read the following:
If you're here to report a security issue, please STOP writing an issue and
contact us at security@swagger.io instead!
Search open/closed issues before submitting!
Issues on GitHub are only related to problems of Swagger-UI itself. We'll try
to offer support here for your use case, but we can't offer help with projects
that use Swagger-UI indirectly, like Springfox or swagger-node.
Likewise, we can't accept bugs in the Swagger/OpenAPIspecifications themselves,
or anything that violates the specifications.
-->
## Q&A (please complete the following information)
- OS: [e.g. macOS]
- Browser: [e.g. chrome, safari]
- Version: [e.g. 22]
- Method of installation: [e.g. npm, dist assets]
- Swagger-UI version: [e.g. 3.10.0]
- Swagger/OpenAPI version: [e.g. Swagger 2.0, OpenAPI 3.0]
### Content & configuration
<!--
Provide us with a way to see what you're seeing,
so that we can fix your issue.
-->
Example Swagger/OpenAPI definition:
```yaml
# your YAML here
```
Swagger-UI configuration options:
```js
SwaggerUI({
// your config options here
})
```
```
?yourQueryStringConfig
```
### Describe the bug you're encountering
<!-- A clear and concise description of what the bug is. -->
### To reproduce...
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
### Expected behavior
<!-- A clear and concise description of what you expected to happen. -->
### Screenshots
<!-- If applicable, add screenshots to help explain your problem. -->
### Additional context or thoughts
<!-- Add any other context about the problem here. -->
+50
View File
@@ -0,0 +1,50 @@
---
name: Feature request
about: Suggest an new feature or enhancement for this project
---
## Q&A (please complete the following information)
- OS: [e.g. macOS]
- Browser: [e.g. chrome, safari]
- Version: [e.g. 22]
- Method of installation: [e.g. npm, dist assets]
- Swagger-UI version: [e.g. 3.10.0]
- Swagger/OpenAPI version: [e.g. Swagger 2.0, OpenAPI 3.0]
### Content & configuration
Swagger/OpenAPI definition:
```yaml
# your YAML here
```
Swagger-UI configuration options:
```js
SwaggerUI({
// your config options here
})
```
```
?yourQueryStringConfig
```
### Is your feature request related to a problem?
<!--
Please provide a clear and concise description of what the problem is.
"I'm always frustrated when..."
-->
### Describe the solution you'd like
<!-- A clear and concise description of what you want to happen. -->
### Describe alternatives you've considered
<!--
A clear and concise description of any alternative solutions or features
you've considered.
-->
### Additional context
<!-- Add any other context or screenshots about the feature request here. -->
+46
View File
@@ -0,0 +1,46 @@
---
name: Support
about: Ask a question or request help with your implementation.
---
<!--
We can only offer support for Swagger-UI itself.
If you're having a problem with a library that uses Swagger-UI
(for example, Springfox or swagger-node), please open an issue
in that project's repository instead.
-->
### Q&A (please complete the following information)
- OS: [e.g. macOS]
- Browser: [e.g. chrome, safari]
- Version: [e.g. 22]
- Method of installation: [e.g. npm, dist assets]
- Swagger-UI version: [e.g. 3.10.0]
- Swagger/OpenAPI version: [e.g. Swagger 2.0, OpenAPI 3.0]
### Content & configuration
<!-- Provide us with a way to see what you're seeing, so that we can help. -->
Swagger/OpenAPI definition:
```yaml
# your YAML here
```
Swagger-UI configuration options:
```js
SwaggerUI({
// your config options here
})
```
```
?yourQueryStringConfig
```
### Screenshots
<!-- If applicable, add screenshots to help give context to your problem. -->
### How can we help?
<!-- Your question or problem goes here! -->
-70
View File
@@ -1,70 +0,0 @@
<!---
Thanks for filing an issue 😄 ! Before you submit, please read the following:
If you're here to report a security issue, please STOP writing an issue and contact us
at security@swagger.io instead!
Search open/closed issues before submitting since someone might have asked the same thing before!
Issues on GitHub are only related to problems of Swagger-UI itself. We'll try to offer support
here for your use case, but we can't offer help with projects that use Swagger-UI indirectly,
like Springfox or swagger-node.
Likewise, we can't accept features or bugs in the Swagger/OpenAPI specifications themselves,
or anything that violates the specifications.
-->
<!--- Provide a general summary of the issue in the title above -->
<!---
If you aren't sure what Swagger-UI version, see this guide: https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/version-detection.md
--->
| Q | A
| ------------------------------- | -------
| Bug or feature request? |
| Which Swagger/OpenAPI version? |
| Which Swagger-UI version? |
| How did you install Swagger-UI? |
| Which browser & version? |
| Which operating system? |
### Demonstration API definition
<!--- If you're describing a bug, please provide an API definition that reproduces your problem -->
<!--- If you have link to a demo repo please link that! -->
<!--- If your spec is large, please put it into a Gist (https://gist.github.com) instead of pasting it here. -->
```yaml
your: "API definition goes here"
```
### Configuration (browser query string, constructor, config.yaml)
<!--- If describing a bug, tell us what your configuration looks like -->
```js
{
"your": { "constructorConfig": "here" }
}
```
`?yourQueryStringConfig=here`
### Expected Behavior
<!--- If you're describing a bug, tell us what should happen -->
<!--- If you're suggesting a change/improvement, tell us how it should work -->
### Current Behavior
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
### Possible Solution
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
<!--- or ideas how to implement the addition or change -->
### Context
<!--- How has this issue affected you? What are you trying to accomplish? -->
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
+1 -1
View File
@@ -22,7 +22,7 @@ The OpenAPI Specification has undergone 5 revisions since initial creation in 20
Swagger UI Version | Release Date | OpenAPI Spec compatibility | Notes
------------------ | ------------ | -------------------------- | -----
3.13.5 | 2018-04-20 | 2.0, 3.0 | [tag v3.13.5](https://github.com/swagger-api/swagger-ui/tree/v3.13.5)
3.14.2 | 2018-05-11 | 2.0, 3.0 | [tag v3.14.2](https://github.com/swagger-api/swagger-ui/tree/v3.14.2)
3.0.21 | 2017-07-26 | 2.0 | [tag v3.0.21](https://github.com/swagger-api/swagger-ui/tree/v3.0.21)
2.2.10 | 2017-01-04 | 1.1, 1.2, 2.0 | [tag v2.2.10](https://github.com/swagger-api/swagger-ui/tree/v2.2.10)
2.1.5 | 2016-07-20 | 1.1, 1.2, 2.0 | [tag v2.1.5](https://github.com/swagger-api/swagger-ui/tree/v2.1.5)
-81
View File
@@ -1,81 +0,0 @@
openapi: "3.0.0"
info:
version: 1.0.0
title: Brentertainment OAuth2 Test Server
description: >
The server does not support CORS (yet?), so you need to take the strange step of disabling Same-Origin Policy in your browser.
[Here](https://www.thepolyglotdeveloper.com/2014/08/bypass-cors-errors-testing-apis-locally/) are some instructions.
license:
name: MIT
servers:
- url: http://brentertainment.com/oauth2/lockdin/
paths:
/resource:
get:
description: Protected resource
security:
- oauth2AuthorizationCode: ["resource"]
- oauth2Implicit: ["resource"]
- oauth2Password: ["resource"]
- oauth2ClientCredentials: ["resource"]
- oauth2Multiflow: ["resource"]
responses:
200:
description: the only response
content:
application/json:
schema:
type: array
items:
type: string
components:
securitySchemes:
oauth2AuthorizationCode:
type: oauth2
flows:
authorizationCode:
authorizationUrl: "http://brentertainment.com/oauth2/lockdin/authorize"
tokenUrl: "http://brentertainment.com/oauth2/lockdin/token"
scopes:
resource: our only scope
oauth2Implicit:
type: oauth2
flows:
implicit:
authorizationUrl: "http://brentertainment.com/oauth2/lockdin/authorize"
scopes:
resource: our only scope
oauth2Password:
type: oauth2
flows:
password:
tokenUrl: "http://brentertainment.com/oauth2/lockdin/token"
scopes:
resource: our only scope
oauth2ClientCredentials:
type: oauth2
flows:
clientCredentials:
tokenUrl: "http://brentertainment.com/oauth2/lockdin/token"
scopes:
resource: our only scope
oauth2Multiflow:
type: oauth2
flows:
clientCredentials:
tokenUrl: "http://brentertainment.com/oauth2/lockdin/token"
scopes:
resource: our only scope
password:
tokenUrl: "http://brentertainment.com/oauth2/lockdin/token"
scopes:
resource: our only scope
implicit:
authorizationUrl: "http://brentertainment.com/oauth2/lockdin/authorize"
scopes:
resource: our only scope
authorizationCode:
authorizationUrl: "http://brentertainment.com/oauth2/lockdin/authorize"
tokenUrl: "http://brentertainment.com/oauth2/lockdin/token"
scopes:
resource: our only scope
+34 -23
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3 -2
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -30,7 +30,7 @@ Parameter Name | Description
`domNode` | `Element`, **REQUIRED** if `dom_id` is not provided. The HTML DOM element inside which SwaggerUi will put the user interface for swagger. Overrides `dom_id`.
`spec` | `Object={}`. A JS object describing the OpenAPI Specification. When used, the `url` parameter will not be parsed. This is useful for testing manually-generated specifications without hosting them.
`url` | `String`. The url pointing to API definition (normally `swagger.json` or `swagger.yaml`). Will be ignored if `urls` or `spec` is used.
`urls` | `String`. An array of API definition objects (`{url: "<url>", name: "<name>"}`) used by Topbar plugin. When used and Topbar plugin is enabled, the `url` parameter will not be parsed. Names and URLs must be unique among all items in this array, since they're used as identifiers.
`urls` | `Array`. An array of API definition objects (`[{url: "<url1>", name: "<name1>"},{url: "<url2>", name: "<name2>"}]`) used by Topbar plugin. When used and Topbar plugin is enabled, the `url` parameter will not be parsed. Names and URLs must be unique among all items in this array, since they're used as identifiers.
`urls.primaryName` | `String`. When using `urls`, you can use this subparameter. If the value matches the name of a spec provided in `urls`, that spec will be displayed when Swagger-UI loads, instead of defaulting to the first spec in `urls`.
##### Plugin system
+14 -10
View File
@@ -1,10 +1,11 @@
var path = require("path")
var webpack = require('webpack')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
var deepExtend = require('deep-extend')
const {gitDescribeSync} = require('git-describe')
var webpack = require("webpack")
var ExtractTextPlugin = require("extract-text-webpack-plugin")
const CopyWebpackPlugin = require("copy-webpack-plugin")
const UglifyJsPlugin = require("uglifyjs-webpack-plugin")
var deepExtend = require("deep-extend")
const {gitDescribeSync} = require("git-describe")
const os = require("os")
var pkg = require("./package.json")
@@ -28,7 +29,10 @@ var commonRules = [
retainLines: true
}
}],
include: [ path.join(__dirname, "src") ]
include: [
path.join(__dirname, "src"),
path.join(__dirname, "node_modules", "object-assign-deep"),
]
},
{ test: /\.(txt|yaml)(\?.*)?$/,
loader: "raw-loader" },
@@ -61,10 +65,10 @@ module.exports = function(rules, options) {
}))
}
if( specialOptions.minimize ) { // production mode
if( specialOptions.minimize ) { // production mode
plugins.push(
new webpack.optimize.UglifyJsPlugin({
new UglifyJsPlugin({
sourceMap: true,
}),
new webpack.LoaderOptionsPlugin({
@@ -76,8 +80,8 @@ module.exports = function(rules, options) {
plugins.push( new webpack.NoEmitOnErrorsPlugin())
} else { // development mode
plugins.push(new CopyWebpackPlugin([ { from: 'test/e2e/specs', to: 'test-specs' } ]))
} else { // development mode
plugins.push(new CopyWebpackPlugin([ { from: "test/e2e/specs", to: "test-specs" } ]))
}
plugins.push(
+9 -6
View File
@@ -1,6 +1,6 @@
{
"name": "swagger-ui",
"version": "3.13.6",
"version": "3.14.2",
"main": "dist/swagger-ui.js",
"repository": "git@github.com:swagger-api/swagger-ui.git",
"contributors": [
@@ -31,7 +31,7 @@
"test": "npm run lint-errors && npm run just-test-in-node",
"test-in-node": "npm run lint-errors && npm run just-test-in-node",
"just-test": "karma start --config karma.conf.js",
"just-test-in-node": "mocha --recursive --compilers js:babel-core/register test/core test/components test/bugs test/swagger-ui-dist-package test/xss",
"just-test-in-node": "mocha --require test/setup.js --recursive --compilers js:babel-core/register test/core test/components test/bugs test/swagger-ui-dist-package test/xss",
"just-check-coverage": "nyc npm run just-test-in-node",
"test-e2e": "sleep 3 && nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json",
"e2e-initial-render": "nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json --group initial-render",
@@ -47,7 +47,8 @@
"commonmark": "^0.28.1",
"core-js": "^2.5.1",
"css.escape": "1.5.1",
"deep-extend": "0.4.1",
"deep-extend": "0.5.1",
"dompurify": "^1.0.4",
"expect": "1.20.2",
"getbase": "^2.8.2",
"ieee754": "^1.1.8",
@@ -80,11 +81,10 @@
"redux-logger": "*",
"remarkable": "^1.7.1",
"reselect": "2.5.3",
"sanitize-html": "^1.14.1",
"scroll-to-element": "^2.0.0",
"serialize-error": "2.0.0",
"shallowequal": "0.2.2",
"swagger-client": "^3.7.2",
"swagger-client": "^3.8.1",
"url-parse": "^1.1.8",
"whatwg-fetch": "0.11.1",
"worker-loader": "^0.7.1",
@@ -99,6 +99,7 @@
"babel-eslint": "^7.1.1",
"babel-loader": "^7.1.0",
"babel-plugin-module-alias": "^1.6.0",
"babel-plugin-transform-es2015-constants": "^6.1.4",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-es2015": "^6.22.0",
"babel-preset-es2015-ie": "^6.6.2",
@@ -114,11 +115,12 @@
"eslint": "^4.1.1",
"eslint-plugin-import": "^2.6.0",
"eslint-plugin-mocha": "^4.11.0",
"eslint-plugin-react": "^7.1.0",
"eslint-plugin-react": "~7.7.0",
"extract-text-webpack-plugin": "^2.1.2",
"file-loader": "0.11.2",
"git-describe": "^4.0.1",
"imports-loader": "0.7.1",
"jsdom": "^11.10.0",
"json-loader": "0.5.4",
"json-server": "^0.11.0",
"karma": "^1.7.0",
@@ -146,6 +148,7 @@
"standard-loader": "^6.0.1",
"style-loader": "0.18.2",
"tachyons-sass": "^4.9.2",
"uglifyjs-webpack-plugin": "^1.2.5",
"url-loader": "0.5.9",
"webpack": "^2.6.1",
"webpack-bundle-size-analyzer": "^2.5.0"
+3 -2
View File
@@ -11,6 +11,7 @@ export default class ArrayModel extends Component {
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
displayName: PropTypes.string,
required: PropTypes.bool,
expandDepth: PropTypes.number,
specPath: ImPropTypes.list.isRequired,
@@ -18,10 +19,10 @@ export default class ArrayModel extends Component {
}
render(){
let { getComponent, getConfigs, schema, depth, expandDepth, name, specPath } = this.props
let { getComponent, getConfigs, schema, depth, expandDepth, name, displayName, specPath } = this.props
let description = schema.get("description")
let items = schema.get("items")
let title = schema.get("title") || name
let title = schema.get("title") || displayName || name
let properties = schema.filter( ( v, key) => ["type", "items", "description", "$$ref"].indexOf(key) === -1 )
const Markdown = getComponent("Markdown")
+2 -5
View File
@@ -99,13 +99,10 @@ export default class Auths extends React.Component {
{
definitions.filter( schema => schema.get("type") === "oauth2")
.map( (schema, name) =>{
const { flow } = schema
const key = [name, flow].join("__").slice(0, -2)
return (<div key={ key }>
return (<div key={ name }>
<Oauth2 authorized={ authorized }
schema={ schema }
authId={ key }
name={ key || schema.get("name") || name } />
name={ name } />
</div>)
}
).toArray()
+8 -8
View File
@@ -47,11 +47,11 @@ export default class Oauth2 extends React.Component {
}
authorize =() => {
let { authActions, errActions, getConfigs, authSelectors, authId } = this.props
let { authActions, errActions, getConfigs, authSelectors } = this.props
let configs = getConfigs()
let authConfigs = authSelectors.getConfigs()
errActions.clear({authId: authId ,type: "auth", source: "auth"})
errActions.clear({authId: name,type: "auth", source: "auth"})
oauth2Authorize({auth: this.state, authActions, errActions, configs, authConfigs })
}
@@ -79,15 +79,15 @@ export default class Oauth2 extends React.Component {
logout =(e) => {
e.preventDefault()
let { authActions, errActions, authId } = this.props
let { authActions, errActions, name } = this.props
errActions.clear({authId: authId, type: "auth", source: "auth"})
authActions.logout([ authId ])
errActions.clear({authId: name, type: "auth", source: "auth"})
authActions.logout([ name ])
}
render() {
let {
schema, getComponent, authSelectors, errSelectors, name, authId, specSelectors
schema, getComponent, authSelectors, errSelectors, name, specSelectors
} = this.props
const Input = getComponent("Input")
const Row = getComponent("Row")
@@ -107,9 +107,9 @@ export default class Oauth2 extends React.Component {
let flow = schema.get("flow")
let scopes = schema.get("allowedScopes") || schema.get("scopes")
let authorizedAuth = authSelectors.authorized().get(authId)
let authorizedAuth = authSelectors.authorized().get(name)
let isAuthorized = !!authorizedAuth
let errors = errSelectors.allErrors().filter( err => err.get("authId") === authId)
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
let isValid = !errors.filter( err => err.get("source") === "validation").size
let description = schema.get("description")
+3 -2
View File
@@ -7,7 +7,8 @@ export default class HighlightCode extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
className: PropTypes.string,
downloadable: PropTypes.bool
downloadable: PropTypes.bool,
fileName: PropTypes.string
}
componentDidMount() {
@@ -23,7 +24,7 @@ export default class HighlightCode extends Component {
}
downloadText = () => {
saveAs(this.props.value, "response.txt")
saveAs(this.props.value, this.props.fileName || "response.txt")
}
preventYScrollingBeyondElement = (e) => {
+1
View File
@@ -9,6 +9,7 @@ export default class ModelWrapper extends Component {
static propTypes = {
schema: PropTypes.object.isRequired,
name: PropTypes.string,
displayName: PropTypes.string,
getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
+3 -2
View File
@@ -10,6 +10,7 @@ export default class Model extends ImmutablePureComponent {
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
displayName: PropTypes.string,
isRef: PropTypes.bool,
required: PropTypes.bool,
expandDepth: PropTypes.number,
@@ -33,7 +34,7 @@ export default class Model extends ImmutablePureComponent {
}
render () {
let { getComponent, getConfigs, specSelectors, schema, required, name, isRef, specPath } = this.props
let { getComponent, getConfigs, specSelectors, schema, required, name, isRef, specPath, displayName } = this.props
const ObjectModel = getComponent("ObjectModel")
const ArrayModel = getComponent("ArrayModel")
const PrimitiveModel = getComponent("PrimitiveModel")
@@ -51,7 +52,7 @@ export default class Model extends ImmutablePureComponent {
if(!schema) {
return <span className="model model-title">
<span className="model-title__text">{ name }</span>
<span className="model-title__text">{ displayName || name }</span>
<img src={require("core/../img/rolling-load.svg")} height={"20px"} width={"20px"} style={{
marginLeft: "1em",
position: "relative",
+11 -4
View File
@@ -51,9 +51,12 @@ export default class Models extends Component {
</h4>
<Collapse isOpened={showModels}>
{
definitions.entrySeq().map( ( [ name ])=>{
definitions.entrySeq().map(([name])=>{
const schema = specSelectors.specResolvedSubtree([...specPathBase, name])
const fullPath = [...specPathBase, name]
const schema = specSelectors.specResolvedSubtree(fullPath)|| Im.Map()
const rawSchema = specSelectors.specJson().getIn(fullPath, Im.Map())
const displayName = schema.get("title") || rawSchema.get("title") || name
if(layoutSelectors.isShown(["models", name], false) && schema === undefined) {
// Firing an action in a container render is not great,
@@ -63,7 +66,8 @@ export default class Models extends Component {
const content = <ModelWrapper name={ name }
expandDepth={ defaultModelsExpandDepth }
schema={ schema || Im.Map() }
schema={ schema || Im.Map() }
displayName={displayName}
specPath={Im.List([...specPathBase, name])}
getComponent={ getComponent }
specSelectors={ specSelectors }
@@ -72,7 +76,9 @@ export default class Models extends Component {
layoutActions = {layoutActions}/>
const title = <span className="model-box">
<span className="model model-title">{name}</span>
<span className="model model-title">
{displayName}
</span>
</span>
return <div id={ `model-${name}` } className="model-container" key={ `models-section-${name}` }>
@@ -81,6 +87,7 @@ export default class Models extends Component {
collapsedContent={this.getCollapsedContent(name)}
onToggle={this.handleToggle}
title={title}
displayName={displayName}
modelName={name}
hideSelfOnExpand={true}
expanded={defaultModelsExpandDepth > 1}
+4 -3
View File
@@ -15,6 +15,7 @@ export default class ObjectModel extends Component {
onToggle: PropTypes.func,
specSelectors: PropTypes.object.isRequired,
name: PropTypes.string,
displayName: PropTypes.string,
isRef: PropTypes.bool,
expandDepth: PropTypes.number,
depth: PropTypes.number,
@@ -22,7 +23,7 @@ export default class ObjectModel extends Component {
}
render(){
let { schema, name, isRef, getComponent, getConfigs, depth, onToggle, expanded, specPath, ...otherProps } = this.props
let { schema, name, displayName, isRef, getComponent, getConfigs, depth, onToggle, expanded, specPath, ...otherProps } = this.props
let { specSelectors,expandDepth } = otherProps
const { isOAS3 } = specSelectors
@@ -35,7 +36,7 @@ export default class ObjectModel extends Component {
let description = schema.get("description")
let properties = schema.get("properties")
let additionalProperties = schema.get("additionalProperties")
let title = schema.get("title") || name
let title = schema.get("title") || displayName || name
let requiredProperties = schema.get("required")
const JumpToPath = getComponent("JumpToPath", true)
@@ -78,7 +79,7 @@ export default class ObjectModel extends Component {
{
<table className="model"><tbody>
{
!description ? null : <tr style={{ color: "#999", fontStyle: "italic" }}>
!description ? null : <tr style={{ color: "#666", fontStyle: "italic" }}>
<td>description:</td>
<td>
<Markdown source={ description } />
+3 -2
View File
@@ -10,11 +10,12 @@ export default class Primitive extends Component {
getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
name: PropTypes.string,
displayName: PropTypes.string,
depth: PropTypes.number
}
render(){
let { schema, getComponent, getConfigs, name, depth } = this.props
let { schema, getComponent, getConfigs, name, displayName, depth } = this.props
const { showExtensions } = getConfigs()
@@ -27,7 +28,7 @@ export default class Primitive extends Component {
let format = schema.get("format")
let xml = schema.get("xml")
let enumArray = schema.get("enum")
let title = schema.get("title") || name
let title = schema.get("title") || displayName || name
let description = schema.get("description")
let extensions = getExtensions(schema)
let properties = schema
+4 -16
View File
@@ -1,7 +1,7 @@
import React from "react"
import PropTypes from "prop-types"
import Remarkable from "remarkable"
import sanitize from "sanitize-html"
import DomPurify from "dompurify"
import cx from "classnames"
// eslint-disable-next-line no-useless-escape
@@ -40,20 +40,8 @@ Markdown.propTypes = {
export default Markdown
const sanitizeOptions = {
allowedTags: sanitize.defaults.allowedTags.concat([ "h1", "h2", "img", "span" ]),
allowedAttributes: {
...sanitize.defaults.allowedAttributes,
"img": sanitize.defaults.allowedAttributes.img.concat(["title"]),
"td": [ "colspan" ],
"*": [ "class" ]
},
allowedSchemesByTag: { img: [ "http", "https", "data" ] },
textFilter: function(text) {
return text.replace(/&quot;/g, "\"")
}
}
export function sanitizer(str) {
return sanitize(str, sanitizeOptions)
return DomPurify.sanitize(str, {
ADD_ATTR: ["target"]
})
}
+7 -8
View File
@@ -52,6 +52,7 @@ export default class ResponseBody extends React.PureComponent {
let { content, contentType, url, headers={}, getComponent } = this.props
const { parsedContent } = this.state
const HighlightCode = getComponent("highlightCode")
const downloadName = "response_" + new Date().getTime()
let body, bodyEl
url = url || ""
@@ -63,9 +64,7 @@ export default class ResponseBody extends React.PureComponent {
(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) {
if ("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)
@@ -100,7 +99,7 @@ export default class ResponseBody extends React.PureComponent {
body = "can't parse JSON. Raw result:\n\n" + content
}
bodyEl = <HighlightCode downloadable value={ body } />
bodyEl = <HighlightCode downloadable fileName={`${downloadName}.json`} value={ body } />
// XML
} else if (/xml/i.test(contentType)) {
@@ -108,11 +107,11 @@ export default class ResponseBody extends React.PureComponent {
textNodesOnSameLine: true,
indentor: " "
})
bodyEl = <HighlightCode downloadable value={ body } />
bodyEl = <HighlightCode downloadable fileName={`${downloadName}.xml`} value={ body } />
// HTML or Plain Text
} else if (lowerCase(contentType) === "text/html" || /text\/plain/.test(contentType)) {
bodyEl = <HighlightCode downloadable value={ content } />
bodyEl = <HighlightCode downloadable fileName={`${downloadName}.html`} value={ content } />
// Image
} else if (/^image\//i.test(contentType)) {
@@ -126,7 +125,7 @@ export default class ResponseBody extends React.PureComponent {
} else if (/^audio\//i.test(contentType)) {
bodyEl = <pre><audio controls><source src={ url } type={ contentType } /></audio></pre>
} else if (typeof content === "string") {
bodyEl = <HighlightCode downloadable value={ content } />
bodyEl = <HighlightCode downloadable fileName={`${downloadName}.txt`} value={ content } />
} else if ( content.size > 0 ) {
// We don't know the contentType, but there was some content returned
if(parsedContent) {
@@ -136,7 +135,7 @@ export default class ResponseBody extends React.PureComponent {
<p className="i">
Unrecognized response type; displaying content as text.
</p>
<HighlightCode downloadable value={ parsedContent } />
<HighlightCode downloadable fileName={`${downloadName}.txt`} value={ parsedContent } />
</div>
} else {
+6 -1
View File
@@ -173,7 +173,12 @@ module.exports = function SwaggerUI(opts) {
let configUrl = queryConfig.config || constructorConfig.configUrl
if (!configUrl || !system.specActions.getConfigByUrl || system.specActions.getConfigByUrl && !system.specActions.getConfigByUrl(configUrl, downloadSpec)) {
if (!configUrl || !system.specActions.getConfigByUrl || system.specActions.getConfigByUrl && !system.specActions.getConfigByUrl({
url: configUrl,
loadRemoteConfig: true,
requestInterceptor: constructorConfig.requestInterceptor,
responseInterceptor: constructorConfig.responseInterceptor,
}, downloadSpec)) {
return downloadSpec()
}
+7 -16
View File
@@ -1,23 +1,14 @@
import { parseYamlConfig } from "./helpers"
export const downloadConfig = (url) => ({fn: { fetch }, getConfigs}) => {
const { requestInterceptor, responseInterceptor } = getConfigs()
let req = { url }
if(requestInterceptor) {
req = requestInterceptor(req)
}
export const downloadConfig = (req) => (system) => {
const {fn: { fetch }} = system
return fetch(req)
.then(res => {
if(res) {
return responseInterceptor(res)
}
return res
})
}
export const getConfigByUrl = (configUrl, cb)=> ({ specActions }) => {
if (configUrl) {
return specActions.downloadConfig(configUrl).then(next, next)
export const getConfigByUrl = (req, cb)=> ({ specActions }) => {
if (req) {
return specActions.downloadConfig(req).then(next, next)
}
function next(res) {
@@ -25,7 +16,7 @@ export const getConfigByUrl = (configUrl, cb)=> ({ specActions }) => {
specActions.updateLoadingStatus("failedConfig")
specActions.updateLoadingStatus("failedConfig")
specActions.updateUrl("")
console.error(res.statusText + " " + configUrl)
console.error(res.statusText + " " + req.url)
cb(null)
} else {
cb(parseYamlConfig(res.text))
@@ -33,7 +33,6 @@ export const definitionsToAuthorize = onlyOAS3(createSelector(
definition.get("flows").entrySeq().forEach(([flowKey, flowVal]) => {
let translatedDef = fromJS({
flow: flowKey,
name: defName,
authorizationUrl: flowVal.get("authorizationUrl"),
tokenUrl: flowVal.get("tokenUrl"),
scopes: flowVal.get("scopes"),
@@ -41,7 +40,7 @@ export const definitionsToAuthorize = onlyOAS3(createSelector(
})
list = list.push(new Map({
[`${defName}__${flowKey}`]: translatedDef.filter((v) => {
[defName]: translatedDef.filter((v) => {
// filter out unset values, sometimes `authorizationUrl`
// and `tokenUrl` come out as `undefined` in the data
return v !== undefined
+7 -7
View File
@@ -16,19 +16,19 @@ describe("Markdown component", function() {
it("allows td elements with colspan attrib", function() {
const str = `<table><tr><td>ABC</td></tr></table>`
const el = render(<Markdown source={str} />)
expect(el.html()).toEqual(`<div class="markdown"><table><tr><td>ABC</td></tr></table></div>`)
expect(el.html()).toEqual(`<div class="markdown"><table><tbody><tr><td>ABC</td></tr></tbody></table></div>`)
})
it("allows image elements", function() {
const str = `![Image alt text](http://image.source "Image title")`
const el = render(<Markdown source={str} />)
expect(el.html()).toEqual(`<div class="markdown"><p><img src="http://image.source" title="Image title"></p>\n</div>`)
expect(el.html()).toEqual(`<div class="markdown"><p><img title="Image title" alt="Image alt text" src="http://image.source"></p>\n</div>`)
})
it("allows image elements with https scheme", function() {
const str = `![Image alt text](https://image.source "Image title")`
const el = render(<Markdown source={str} />)
expect(el.html()).toEqual(`<div class="markdown"><p><img src="https://image.source" title="Image title"></p>\n</div>`)
expect(el.html()).toEqual(`<div class="markdown"><p><img title="Image title" alt="Image alt text" src="https://image.source"></p>\n</div>`)
})
it("allows image elements with data scheme", function() {
@@ -52,7 +52,7 @@ describe("Markdown component", function() {
it("allows links", function() {
const str = `[Link](https://example.com/)`
const el = render(<Markdown source={str} />)
expect(el.html()).toEqual(`<div class="markdown"><p><a href="https://example.com/" target="_blank">Link</a></p>\n</div>`)
expect(el.html()).toEqual(`<div class="markdown"><p><a target="_blank" href="https://example.com/">Link</a></p>\n</div>`)
})
})
@@ -60,13 +60,13 @@ describe("Markdown component", function() {
it("allows image elements", function() {
const str = `![Image alt text](http://image.source "Image title")`
const el = render(<OAS3Markdown source={str} />)
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><p><img src="http://image.source" title="Image title"></p></div></div>`)
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><p><img title="Image title" alt="Image alt text" src="http://image.source"></p></div></div>`)
})
it("allows image elements with https scheme", function() {
const str = `![Image alt text](https://image.source "Image title")`
const el = render(<OAS3Markdown source={str} />)
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><p><img src="https://image.source" title="Image title"></p></div></div>`)
expect(el.html()).toEqual(`<div class="renderedMarkdown"><div><p><img title="Image title" alt="Image alt text" src="https://image.source"></p></div></div>`)
})
it("allows image elements with data scheme", function() {
+2 -1
View File
@@ -2,7 +2,7 @@
import React from "react"
import expect, { createSpy } from "expect"
import { shallow } from "enzyme"
import { fromJS } from "immutable"
import { fromJS, Map } from "immutable"
import Models from "components/models"
import ModelCollpase from "components/model-collapse"
import ModelComponent from "components/model-wrapper"
@@ -19,6 +19,7 @@ describe("<Models/>", function(){
},
specSelectors: {
isOAS3: () => false,
specJson: () => Map(),
definitions: function() {
return fromJS({
def1: {},
+7 -64
View File
@@ -5,80 +5,23 @@ import { downloadConfig } from "corePlugins/configs/spec-actions"
describe("configs plugin - actions", () => {
describe("downloadConfig", () => {
it("should call the system fetch helper with a provided url", () => {
it("should call the system fetch helper with a provided request", () => {
const fetchSpy = createSpy(async () => {}).andCallThrough()
const system = {
fn: {
fetch: fetchSpy
},
getConfigs() {
return {}
}
}
const url = "http://swagger.io/one"
downloadConfig(url)(system)
expect(fetchSpy).toHaveBeenCalledWith({
url: url
})
})
it("should allow the globally configured requestInterceptor to modify the request", () => {
const fetchSpy = createSpy(async () => {}).andCallThrough()
const requestInterceptorSpy = createSpy((req) => {
req.url = "http://swagger.io/two"
return req
}).andCallThrough()
const system = {
fn: {
fetch: fetchSpy
},
getConfigs() {
return {
requestInterceptor: requestInterceptorSpy
}
}
const req = {
url: "http://swagger.io/one",
requestInterceptor: a => a,
responseInterceptor: a => a,
}
const url = "http://swagger.io/one"
downloadConfig(req)(system)
downloadConfig(url)(system)
expect(fetchSpy).toHaveBeenCalledWith({
url: "http://swagger.io/two"
})
})
it("should allow the globally configured responseInterceptor to modify the response", async () => {
const fetchSpy = createSpy(async (req) => {
return {
url: req.url,
ok: true
}
}).andCallThrough()
const responseInterceptorSpy = createSpy((res) => {
res.url = "http://swagger.io/two"
return res
}).andCallThrough()
const system = {
fn: {
fetch: fetchSpy
},
getConfigs() {
return {
responseInterceptor: responseInterceptorSpy
}
}
}
const url = "http://swagger.io/one"
const res = await downloadConfig(url)(system)
expect(res).toEqual({
url: "http://swagger.io/two",
ok: true
})
expect(fetchSpy).toHaveBeenCalledWith(req)
})
})
})
+29
View File
@@ -0,0 +1,29 @@
describe("bug #4536: model name consistency", function () {
let mainPage
beforeEach(function (client, done) {
mainPage = client
.url("localhost:3230")
.page.main()
client.waitForElementVisible(".download-url-input", 5000)
.pause(2000)
.clearValue(".download-url-input")
.setValue(".download-url-input", "http://localhost:3230/test-specs/bugs/4536.yaml")
.click("button.download-url-button")
.pause(1000)
done()
})
afterEach(function (client, done) {
done()
})
it("consistently displays a model's name regardless of expansion state", function (client) {
client.waitForElementVisible("span.model.model-title", 10000)
.assert.containsText("span.model.model-title", "TitleName")
.click("span.model.model-title")
.pause(500)
.assert.containsText("span.model-title__text", "TitleName")
client.end()
})
})
+9
View File
@@ -0,0 +1,9 @@
swagger: '2.0'
info:
version: 0.0.0
title: test
paths: {}
definitions:
ModelName:
title: TitleName
+23
View File
@@ -0,0 +1,23 @@
const { JSDOM } = require("jsdom")
const win = require("core/window")
const jsdom = new JSDOM("<!doctype html><html><body></body></html>")
const { window } = jsdom
function copyProps(src, target) {
const props = Object.getOwnPropertyNames(src)
.filter(prop => typeof target[prop] === "undefined")
.reduce((result, prop) => ({
...result,
[prop]: Object.getOwnPropertyDescriptor(src, prop),
}), {})
Object.defineProperties(target, props)
}
global.window = window
global.document = window.document
global.navigator = {
userAgent: "node.js",
}
copyProps(win, window) // use UI's built-in window wrapper
copyProps(window, global)