Merge branch 'master' into feature/validation_tooltips

This commit is contained in:
Helder Sepulveda
2017-11-09 15:41:15 -05:00
committed by GitHub
16 changed files with 249 additions and 37 deletions

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@ node_modules
.idea .idea
.deps_check .deps_check
.DS_Store .DS_Store
.nyc_output
npm-debug.log* npm-debug.log*
.eslintcache .eslintcache
package-lock.json package-lock.json

View File

@@ -4,7 +4,7 @@ We love contributions from our community of users! This document explains our gu
#### Environment setup #### Environment setup
0. Install Node.js (4 or newer) and npm (3 or newer). 0. Install Node.js (6 or newer) and npm (3 or newer).
1. Make a fork of Swagger-UI on GitHub, then clone your fork to your machine. 1. Make a fork of Swagger-UI on GitHub, then clone your fork to your machine.
2. Run `npm install` in your Swagger-UI directory. 2. Run `npm install` in your Swagger-UI directory.
3. Run `npm run dev`. `localhost:3200` should open automatically. 3. Run `npm run dev`. `localhost:3200` should open automatically.

View File

@@ -33,6 +33,7 @@
"test-in-node": "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": "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 --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", "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", "e2e-initial-render": "nightwatch test/e2e/scenarios/ --config test/e2e/nightwatch.json --group initial-render",
"mock-api": "json-server --watch test/e2e/db.json --port 3204", "mock-api": "json-server --watch test/e2e/db.json --port 3204",
@@ -60,13 +61,13 @@
"react-addons-perf": "^15.4.0", "react-addons-perf": "^15.4.0",
"react-addons-shallow-compare": "0.14.8", "react-addons-shallow-compare": "0.14.8",
"react-addons-test-utils": "^15.6.2", "react-addons-test-utils": "^15.6.2",
"react-collapse": "2.3.1", "react-collapse": "^4.0.3",
"react-dom": "^15.6.2", "react-dom": "^15.6.2",
"react-height": "^2.0.0", "react-height": "^2.0.0",
"react-hot-loader": "1.3.1", "react-hot-loader": "1.3.1",
"react-immutable-proptypes": "2.1.0", "react-immutable-proptypes": "2.1.0",
"react-markdown": "^2.5.0", "react-markdown": "^2.5.0",
"react-motion": "0.4.4", "react-motion": "^0.5.2",
"react-object-inspector": "0.2.1", "react-object-inspector": "0.2.1",
"react-redux": "^4.x.x", "react-redux": "^4.x.x",
"react-split-pane": "0.1.57", "react-split-pane": "0.1.57",
@@ -127,6 +128,7 @@
"node-sass": "^4.5.0", "node-sass": "^4.5.0",
"npm-run-all": "4.0.2", "npm-run-all": "4.0.2",
"null-loader": "0.1.1", "null-loader": "0.1.1",
"nyc": "^11.3.0",
"open": "0.0.5", "open": "0.0.5",
"postcss-loader": "2.0.6", "postcss-loader": "2.0.6",
"raw-loader": "0.5.1", "raw-loader": "0.5.1",
@@ -152,5 +154,11 @@
], ],
"optionalDependencies": { "optionalDependencies": {
"webpack-dev-server": "2.5.0" "webpack-dev-server": "2.5.0"
},
"nyc": {
"all": true,
"include": [
"**/src/core/plugins/**.js"
]
} }
} }

View File

@@ -1,25 +1,23 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
export default class AuthorizeOperationBtn extends React.Component { export default class AuthorizeOperationBtn extends React.Component {
static propTypes = {
isAuthorized: PropTypes.bool.isRequired,
onClick: PropTypes.func
}
onClick =(e) => { onClick =(e) => {
e.stopPropagation() e.stopPropagation()
let { onClick } = this.props
let { security, authActions, authSelectors } = this.props if(onClick) {
let definitions = authSelectors.getDefinitionsByNames(security) onClick()
}
authActions.showDefinitions(definitions)
} }
render() { render() {
let { security, authSelectors } = this.props let { isAuthorized } = this.props
let isAuthorized = authSelectors.isAuthorized(security)
if(isAuthorized === null) {
return null
}
return ( return (
<button className={isAuthorized ? "authorization__btn locked" : "authorization__btn unlocked"} onClick={ this.onClick }> <button className={isAuthorized ? "authorization__btn locked" : "authorization__btn unlocked"} onClick={ this.onClick }>
@@ -30,10 +28,4 @@ export default class AuthorizeOperationBtn extends React.Component {
) )
} }
static propTypes = {
authSelectors: PropTypes.object.isRequired,
authActions: PropTypes.object.isRequired,
security: ImPropTypes.iterable.isRequired
}
} }

View File

@@ -37,7 +37,7 @@ export default class ContentType extends React.Component {
return ( return (
<div className={ "content-type-wrapper " + ( className || "" ) }> <div className={ "content-type-wrapper " + ( className || "" ) }>
<select className="content-type" value={value} onChange={this.onChangeWrapper} > <select className="content-type" value={value || ""} onChange={this.onChangeWrapper} >
{ contentTypes.map( (val) => { { contentTypes.map( (val) => {
return <option key={ val } value={ val }>{ val }</option> return <option key={ val } value={ val }>{ val }</option>
}).toArray()} }).toArray()}

View File

@@ -1,6 +1,6 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import Collapse from "react-collapse" import { Collapse } from "react-collapse"
import { presets } from "react-motion" import { presets } from "react-motion"
import ObjectInspector from "react-object-inspector" import ObjectInspector from "react-object-inspector"
import Perf from "react-addons-perf" import Perf from "react-addons-perf"

View File

@@ -1,7 +1,7 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import { List } from "immutable" import { List } from "immutable"
import Collapse from "react-collapse" import { Collapse } from "react-collapse"
export default class Errors extends React.Component { export default class Errors extends React.Component {

View File

@@ -1,6 +1,6 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import OriCollapse from "react-collapse" import { Collapse as OriCollapse } from "react-collapse"
function xclass(...args) { function xclass(...args) {
return args.filter(a => !!a).join(" ").trim() return args.filter(a => !!a).join(" ").trim()

View File

@@ -1,7 +1,7 @@
import React, { Component } from "react" import React, { PureComponent } from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
export default class Model extends Component { export default class Model extends PureComponent {
static propTypes = { static propTypes = {
schema: PropTypes.object.isRequired, schema: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,

View File

@@ -183,9 +183,13 @@ export default class Operation extends PureComponent {
{ {
(!security || !security.count()) ? null : (!security || !security.count()) ? null :
<AuthorizeOperationBtn authActions={ authActions } <AuthorizeOperationBtn
security={ security } isAuthorized={ authSelectors.isAuthorized(security) }
authSelectors={ authSelectors }/> onClick={() => {
const applicableDefinitions = authSelectors.definitionsForRequirements(security)
authActions.showDefinitions(applicableDefinitions)
}}
/>
} }
</div> </div>

View File

@@ -11,7 +11,7 @@ export const shownDefinitions = createSelector(
export const definitionsToAuthorize = createSelector( export const definitionsToAuthorize = createSelector(
state, state,
() => ( { specSelectors } ) => { () => ( { specSelectors } ) => {
let definitions = specSelectors.securityDefinitions() let definitions = specSelectors.securityDefinitions() || Map({})
let list = List() let list = List()
//todo refactor //todo refactor
@@ -28,6 +28,7 @@ export const definitionsToAuthorize = createSelector(
export const getDefinitionsByNames = ( state, securities ) => ( { specSelectors } ) => { export const getDefinitionsByNames = ( state, securities ) => ( { specSelectors } ) => {
console.warn("WARNING: getDefinitionsByNames is deprecated and will be removed in the next major version.")
let securityDefinitions = specSelectors.securityDefinitions() let securityDefinitions = specSelectors.securityDefinitions()
let result = List() let result = List()
@@ -58,6 +59,13 @@ export const getDefinitionsByNames = ( state, securities ) => ( { specSelectors
return result return result
} }
export const definitionsForRequirements = (state, securities = List()) => ({ authSelectors }) => {
const allDefinitions = authSelectors.definitionsToAuthorize() || List()
return allDefinitions.filter((def) => {
return securities.some(sec => sec.get(def.keySeq().first()))
})
}
export const authorized = createSelector( export const authorized = createSelector(
state, state,
auth => auth.get("authorized") || Map() auth => auth.get("authorized") || Map()

View File

@@ -50,7 +50,7 @@ RequestBody.propTypes = {
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired, getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
contentType: PropTypes.string.isRequired, contentType: PropTypes.string,
isExecute: PropTypes.bool.isRequired, isExecute: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired onChange: PropTypes.func.isRequired
} }

View File

@@ -22,6 +22,7 @@ class Parameters extends Component {
specActions: PropTypes.object.isRequired, specActions: PropTypes.object.isRequired,
operation: PropTypes.object.isRequired, operation: PropTypes.object.isRequired,
getComponent: PropTypes.func.isRequired, getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired, specSelectors: PropTypes.object.isRequired,
oas3Actions: PropTypes.object.isRequired, oas3Actions: PropTypes.object.isRequired,
oas3Selectors: PropTypes.object.isRequired, oas3Selectors: PropTypes.object.isRequired,
@@ -86,6 +87,7 @@ class Parameters extends Component {
fn, fn,
getComponent, getComponent,
getConfigs,
specSelectors, specSelectors,
oas3Actions, oas3Actions,
oas3Selectors, oas3Selectors,
@@ -137,6 +139,7 @@ class Parameters extends Component {
eachMap(parameters, (parameter) => ( eachMap(parameters, (parameter) => (
<ParameterRow fn={ fn } <ParameterRow fn={ fn }
getComponent={ getComponent } getComponent={ getComponent }
getConfigs={ getConfigs }
param={ parameter } param={ parameter }
key={ parameter.get( "name" ) } key={ parameter.get( "name" ) }
onChange={ this.onChange } onChange={ this.onChange }

View File

@@ -1,4 +1,4 @@
import React from "react" import React, { cloneElement } from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
//import "./topbar.less" //import "./topbar.less"
@@ -134,7 +134,7 @@ export default class Topbar extends React.Component {
<span>swagger</span> <span>swagger</span>
</Link> </Link>
<form className="download-url-wrapper" onSubmit={formOnSubmit}> <form className="download-url-wrapper" onSubmit={formOnSubmit}>
{control} {control.map((el, i) => cloneElement(el, { key: i }))}
</form> </form>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,63 @@
import React from "react"
import expect from "expect"
import { shallow } from "enzyme"
import { fromJS } from "immutable"
import ObjectModel from "components/object-model"
import ModelExample from "components/model-example"
import Immutable from "immutable"
import Model from "components/model"
import ModelCollapse from "components/model-collapse"
import { inferSchema } from "corePlugins/samples/fn"
describe("<ObjectModel />", function() {
const dummyComponent = () => null
const components = {
"JumpToPath" : dummyComponent,
"Markdown" : dummyComponent,
"Model" : Model,
"ModelCollapse" : ModelCollapse
}
const props = {
getComponent: c => components[c],
isRef : false,
schema: Immutable.fromJS(
{
"properties": {
// Note reverse order: c, b, a
c: {
type: "integer",
name: "c"
},
b: {
type: "boolean",
name: "b"
},
a: {
type: "string",
name: "a"
}
}
}
),
specSelectors: {
isOAS3(){
return false
}
},
className: "for-test"
}
it("renders a collapsible header", function(){
const wrapper = shallow(<ObjectModel {...props}/>)
const renderedModelCollapse = wrapper.find(ModelCollapse)
expect(renderedModelCollapse.length).toEqual(1)
})
it("renders the object properties in order", function() {
const wrapper = shallow(<ObjectModel {...props}/>)
const renderedModel = wrapper.find(Model)
expect(renderedModel.length).toEqual(3)
expect(renderedModel.get(0).props.schema.get("name")).toEqual("c")
expect(renderedModel.get(1).props.schema.get("name")).toEqual("b")
expect(renderedModel.get(2).props.schema.get("name")).toEqual("a")
})
})

View File

@@ -0,0 +1,133 @@
/* eslint-env mocha */
import expect from "expect"
import { fromJS } from "immutable"
import { definitionsToAuthorize, definitionsForRequirements } from "corePlugins/auth/selectors"
describe("auth plugin - selectors", () => {
describe("definitionsToAuthorize", () => {
it("should return securityDefinitions as a List", () => {
const securityDefinitions = {
"petstore_auth": {
"type": "oauth2",
"authorizationUrl": "http://petstore.swagger.io/oauth/dialog",
"flow": "implicit",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets"
}
},
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "header"
}
}
const system = {
specSelectors: {
securityDefinitions() {
return fromJS(securityDefinitions)
}
}
}
const res = definitionsToAuthorize({})(system)
expect(res.toJS()).toEqual([
{
"petstore_auth": securityDefinitions["petstore_auth"]
},
{
"api_key": securityDefinitions["api_key"]
},
])
})
it("should fail gracefully with bad data", () => {
const securityDefinitions = null
const system = {
specSelectors: {
securityDefinitions() {
return fromJS(securityDefinitions)
}
}
}
const res = definitionsToAuthorize({})(system)
expect(res.toJS()).toEqual([])
})
})
describe("definitionsForRequirements", () => {
it("should return applicable securityDefinitions as a List", () => {
const securityDefinitions = {
"petstore_auth": {
"type": "oauth2",
"authorizationUrl": "http://petstore.swagger.io/oauth/dialog",
"flow": "implicit",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets"
}
},
"api_key": {
"type": "apiKey",
"name": "api_key",
"in": "header"
}
}
const system = {
authSelectors: {
definitionsToAuthorize() {
return fromJS([
{
"petstore_auth": securityDefinitions["petstore_auth"]
},
{
"api_key": securityDefinitions["api_key"]
},
])
}
}
}
const securities = fromJS([
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
])
const res = definitionsForRequirements({}, securities)(system)
expect(res.toJS()).toEqual([
{
"petstore_auth": securityDefinitions["petstore_auth"]
}
])
})
it("should fail gracefully with bad data", () => {
const securityDefinitions = null
const system = {
authSelectors: {
definitionsToAuthorize() {
return null
}
}
}
const securities = null
const res = definitionsForRequirements({}, securities)(system)
expect(res.toJS()).toEqual([])
})
})
})