Compare commits

...
Author SHA1 Message Date
Kyle Shockey f23be7d8f5 use Object.assign to always ensure setState receives a new value object 2018-04-20 15:59:46 -07:00
Kyle Shockey 1be0a6df60 tests: add failing e2e test case 2018-04-20 15:59:32 -07:00
Helder Sepulveda 8a8aeeb119 Merge branch 'master' into patch-7 2018-04-15 17:28:08 -04:00
kyle 5f855e56bb v3.13.4 (#4438)
* v3.13.4

* rebuild dist
2018-04-13 18:38:21 -07:00
Ville Skyttä 3f6e21e3b1 improve: HTTP auth display logic and messaging (#4430)
* http-auth: Include unsupported scheme name in error message

* http-auth: Treat authentication scheme case insensitively

The spec refers to RFC 7235 for scheme names, and the RFC says names
are matched case insensitively.

* linter fixes
2018-04-13 17:02:36 -07:00
Aleksey Ganzha 26d0ad7684 fix(regression): model rendering for Swagger 2.0 body parameters (#4424)
* Fix model rendering for parameters

* tests(e2e): add test confirming that Pet model is shown in operation
2018-04-13 16:57:32 -07:00
kyle 9a121ea445 fix: attempt to display Blob response bodies as text (#4437)
* fix: attempt to display Blob response bodies as text

... if nothing else works.

* correctly pass `prevProps.content` to updateParsedContent

* remove debugger statement

* caniuse `Blob` && `FileReader`? yes.

Blob: https://caniuse.com/#search=Blob
FileReader: https://caniuse.com/#search=filereader
2018-04-13 16:16:54 -07:00
kyle c55f22575a fix: make Schemes <select> a fully state-controlled input (#4436) 2018-04-13 13:52:01 -07:00
Giancarlo Klemm Camilo 2795518340 enhance: scrollable + downloadable HighlightCode (#4397)
* Auto hidding content that is longer than 600 characters long.

* Added basic downloading

Slightly broken

* Better downloading

now downloads file on button click

* Fix the angry linter

* Fix dist

* Removed collapsing, added scrolling.

* Code clean up.

* CSS fix.

* prevent HighlightCode from scrolling entire document

* center "Download" text in button

* `this.downloadJSON` -> `this.downloadText`

we're always saving as `.txt`, so JSON is a misnomer

* hide Download button behind option `downloadable` prop

* `file-saver` -> `js-file-download`
2018-04-12 18:18:49 -07:00
Helder Sepulveda abe17f92ef Set the value if set is available 2018-03-31 23:38:14 -04:00
18 changed files with 305 additions and 50 deletions
+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.3 | 2018-03-23 | 2.0, 3.0 | [tag v3.13.3](https://github.com/swagger-api/swagger-ui/tree/v3.13.3)
3.13.4 | 2018-04-15 | 2.0, 3.0 | [tag v3.13.4](https://github.com/swagger-api/swagger-ui/tree/v3.13.4)
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)
+14 -14
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
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -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
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "swagger-ui",
"version": "3.13.3",
"version": "3.13.4",
"main": "dist/swagger-ui.js",
"repository": "git@github.com:swagger-api/swagger-ui.git",
"contributors": [
@@ -52,6 +52,7 @@
"getbase": "^2.8.2",
"ieee754": "^1.1.8",
"immutable": "^3.x.x",
"js-file-download": "^0.4.1",
"js-yaml": "^3.5.5",
"lodash": "4.17.5",
"matcher": "^0.1.2",
@@ -83,7 +84,7 @@
"scroll-to-element": "^2.0.0",
"serialize-error": "2.0.0",
"shallowequal": "0.2.2",
"swagger-client": "^3.6.0",
"swagger-client": "^3.7.0",
"url-parse": "^1.1.8",
"whatwg-fetch": "0.11.1",
"worker-loader": "^0.7.1",
+41 -3
View File
@@ -1,11 +1,13 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
import { highlight } from "core/utils"
import saveAs from "js-file-download"
export default class HighlightCode extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
className: PropTypes.string
className: PropTypes.string,
downloadable: PropTypes.bool
}
componentDidMount() {
@@ -20,10 +22,46 @@ export default class HighlightCode extends Component {
this.el = c
}
downloadText = () => {
saveAs(this.props.value, "response.txt")
}
preventYScrollingBeyondElement = (e) => {
const target = e.target
var deltaY = e.nativeEvent.deltaY
var contentHeight = target.scrollHeight
var visibleHeight = target.offsetHeight
var scrollTop = target.scrollTop
const scrollOffset = visibleHeight + scrollTop
const isScrollingPastTop = scrollTop === 0 && deltaY < 0
const isScrollingPastBottom = scrollOffset >= contentHeight && deltaY > 0
if (isScrollingPastTop || isScrollingPastBottom) {
e.preventDefault()
}
}
render () {
let { value, className } = this.props
let { value, className, downloadable } = this.props
className = className || ""
return <pre ref={this.initializeComponent} className={className + " microlight"}>{ value }</pre>
return (
<div className="highlight-code">
{ !downloadable ? null :
<div className="download-contents" onClick={this.downloadText}>
Download
</div>
}
<pre
ref={this.initializeComponent}
onWheel={this.preventYScrollingBeyondElement}
className={className + " microlight"}>
{value}
</pre>
</div>
)
}
}
+1 -1
View File
@@ -197,7 +197,7 @@ export default class ParameterRow extends Component {
getConfigs={ getConfigs }
isExecute={ isExecute }
specSelectors={ specSelectors }
schema={ schema }
schema={ param.get("schema") }
example={ bodyParam }/>
: null
}
+55 -6
View File
@@ -5,7 +5,10 @@ import lowerCase from "lodash/lowerCase"
import { extractFileNameFromContentDispositionHeader } from "core/utils"
import win from "core/window"
export default class ResponseBody extends React.Component {
export default class ResponseBody extends React.PureComponent {
state = {
parsedContent: null
}
static propTypes = {
content: PropTypes.any.isRequired,
@@ -15,8 +18,39 @@ export default class ResponseBody extends React.Component {
url: PropTypes.string
}
updateParsedContent = (prevContent) => {
const { content } = this.props
if(prevContent === content) {
return
}
if(content && content instanceof Blob) {
var reader = new FileReader()
reader.onload = () => {
this.setState({
parsedContent: reader.result
})
}
reader.readAsText(content)
} else {
this.setState({
parsedContent: content.toString()
})
}
}
componentDidMount() {
this.updateParsedContent(null)
}
componentDidUpdate(prevProps) {
this.updateParsedContent(prevProps.content)
}
render() {
let { content, contentType, url, headers={}, getComponent } = this.props
const { parsedContent } = this.state
const HighlightCode = getComponent("highlightCode")
let body, bodyEl
url = url || ""
@@ -66,7 +100,7 @@ export default class ResponseBody extends React.Component {
body = "can't parse JSON. Raw result:\n\n" + content
}
bodyEl = <HighlightCode value={ body } />
bodyEl = <HighlightCode downloadable value={ body } />
// XML
} else if (/xml/i.test(contentType)) {
@@ -74,11 +108,11 @@ export default class ResponseBody extends React.Component {
textNodesOnSameLine: true,
indentor: " "
})
bodyEl = <HighlightCode value={ body } />
bodyEl = <HighlightCode downloadable value={ body } />
// HTML or Plain Text
} else if (lowerCase(contentType) === "text/html" || /text\/plain/.test(contentType)) {
bodyEl = <HighlightCode value={ content } />
bodyEl = <HighlightCode downloadable value={ content } />
// Image
} else if (/^image\//i.test(contentType)) {
@@ -92,10 +126,25 @@ export default class ResponseBody extends React.Component {
} else if (/^audio\//i.test(contentType)) {
bodyEl = <pre><audio controls><source src={ url } type={ contentType } /></audio></pre>
} else if (typeof content === "string") {
bodyEl = <HighlightCode value={ content } />
bodyEl = <HighlightCode downloadable value={ content } />
} else if ( content.size > 0 ) {
// We don't know the contentType, but there was some content returned
bodyEl = <div>Unknown response type</div>
if(parsedContent) {
// We were able to squeeze something out of content
// in `updateParsedContent`, so let's display it
bodyEl = <div>
<p className="i">
Unrecognized response type; displaying content as text.
</p>
<HighlightCode downloadable value={ parsedContent } />
</div>
} else {
// Give up
bodyEl = <p className="i">
Unrecognized response type; unable to display.
</p>
}
} else {
// We don't know the contentType and there was no content returned
bodyEl = null
+2 -2
View File
@@ -37,12 +37,12 @@ export default class Schemes extends React.Component {
}
render() {
let { schemes } = this.props
let { schemes, currentScheme } = this.props
return (
<label htmlFor="schemes">
<span className="schemes-title">Schemes</span>
<select onChange={ this.onChange }>
<select onChange={ this.onChange } value={currentScheme}>
{ schemes.valueSeq().map(
( scheme ) => <option value={ scheme } key={ scheme }>{ scheme }</option>
).toArray()}
@@ -33,7 +33,8 @@ export default class HttpAuth extends React.Component {
let { onChange } = this.props
let { value, name } = e.target
let newValue = this.state.value || {}
let newValue = Object.assign({}, this.state.value)
if(name) {
newValue[name] = value
} else {
@@ -53,7 +54,7 @@ export default class HttpAuth extends React.Component {
const Markdown = getComponent( "Markdown" )
const JumpToPath = getComponent("JumpToPath", true)
const scheme = schema.get("scheme")
const scheme = (schema.get("scheme") || "").toLowerCase()
let value = this.getValue()
let errors = errSelectors.allErrors().filter( err => err.get("authId") === name)
@@ -125,7 +126,7 @@ export default class HttpAuth extends React.Component {
)
}
return <div>
<em><b>{name}</b> HTTP authentication: unsupported or missing scheme</em>
<em><b>{name}</b> HTTP authentication: unsupported scheme {`'${scheme}'`}</em>
</div>
}
}
+27
View File
@@ -626,6 +626,33 @@
}
}
.highlight-code {
position: relative;
> .microlight {
overflow-y: auto;
max-height: 400px;
min-height: 6em;
}
}
.download-contents {
position: absolute;
bottom: 10px;
right: 10px;
cursor: pointer;
background: #7d8293;
text-align: center;
padding: 5px;
border-radius: 4px;
font-family: 'Titillium Web', sans-serif;
font-weight: 600;
color: white;
font-size: 14px;
height: 30px;
width: 75px;
}
.scheme-container
{
margin: 0 0 20px 0;
+44
View File
@@ -0,0 +1,44 @@
describe("bug #4196: HTTP basic auth credential retention", function () {
let mainPage
beforeEach(function (client, done) {
mainPage = client
.url("localhost:3230")
.page.main()
client.waitForElementVisible(".download-url-input", 5000)
.pause(80)
.clearValue(".download-url-input")
.setValue(".download-url-input", "http://localhost:3230/test-specs/bugs/4196.yaml")
.click("button.download-url-button")
.pause(1000)
done()
})
afterEach(function (client, done) {
done()
})
it("should display the most recent auth data across modal close/opens", function (client) {
client.waitForElementVisible(".opblock-tag-section", 10000)
.click("button.btn.authorize") // Open modal
.waitForElementVisible("section>input", 5000)
.setValue("section>input", "aaa") // Set user
.waitForElementVisible(`section>input[type="password"]`, 5000)
.setValue(`section>input[type="password"]`, "aaa") // Set password
.click(".auth-btn-wrapper button:nth-child(1)") // Click Authorize
.assert.containsText("div.wrapper:nth-child(4)>code", "aaa")
.click(".auth-btn-wrapper button:nth-child(2)") // Close modal
.pause(50)
.click("button.btn.authorize") // Open modal
.pause(50)
.click(".auth-btn-wrapper button:nth-child(1)") // Logout
.waitForElementVisible("section>input", 5000)
.setValue("section>input", "bbb") // Set user
.waitForElementVisible(`section>input[type="password"]`, 5000)
.setValue(`section>input[type="password"]`, "bbb") // Set password
.click(".auth-btn-wrapper button:nth-child(1)") // Click Authorize
.pause(5000)
.assert.containsText("div.wrapper:nth-child(4)>code", "bbb")
client.end()
})
})
+19 -9
View File
@@ -71,6 +71,16 @@ describe("render pet api container", function () {
client.end()
})
it("Testing post /pet api model values", function (client) {
apiWrapper.waitForElementVisible("@petOperationPostContainer", 5000)
.click("@petOperationPostCollpase")
.waitForElementVisible("@petOperationPostCollapseContainer", 5000)
.click("a.tablinks[data-name=model]")
.assert.containsText("span.model-title > .model-title__text", `Pet`)
client.end()
})
it("render put /pet api container", function (client) {
apiWrapper.waitForElementVisible("@petOperationPutContainer", 5000)
.assert.containsText("@petOperationPutTitle", "/pet")
@@ -81,10 +91,10 @@ describe("render pet api container", function () {
.waitForElementVisible("@petOperationPutExecuteBtn", 1000)
.click("@petOperationPutTryBtn")
.assert.cssClassNotPresent("@petOperationPutTryBtn", "cancel")
client.end()
})
it("Testing put /pet api Mock data", function (client) {
apiWrapper.waitForElementVisible("@petOperationPutContainer", 5000)
.click("@petOperationPutCollpase")
@@ -115,7 +125,7 @@ describe("render pet api container", function () {
.waitForElementVisible("@petOperationGetByTagExecuteBtn", 1000)
.click("@petOperationGetByTagTryBtn")
.assert.cssClassNotPresent("@petOperationGetByTagTryBtn", "cancel")
client.end()
})
@@ -138,7 +148,7 @@ describe("render pet api container", function () {
client.end()
})
it("render get by ID /pet/{petId} api container", function (client) {
apiWrapper.waitForElementVisible("@petOperationGetByIdContainer", 5000)
.assert.containsText("@petOperationGetByIdTitle", "/pet/{petId}")
@@ -148,7 +158,7 @@ describe("render pet api container", function () {
.waitForElementVisible("@petOperationGetByTagExecuteBtn", 1000)
.click("@petOperationGetByTagTryBtn")
.assert.cssClassNotPresent("@petOperationGetByTagTryBtn", "cancel")
client.end()
})
@@ -167,7 +177,7 @@ describe("render pet api container", function () {
.assert.attributeEquals("@petOperationGetByIdParameter", "title", "Value must be an integer")
.click("@petOperationGetByTagTryBtn")
.assert.cssClassNotPresent("@petOperationGetByTagTryBtn", "cancel")
client.end()
})
@@ -180,10 +190,10 @@ describe("render pet api container", function () {
.waitForElementVisible("@petOperationDeleteExecuteBtn", 1000)
.click("@petOperationDeleteTryBtn")
.assert.cssClassNotPresent("@petOperationDeleteTryBtn", "cancel")
client.end()
})
it("Testing delete /pet api Mock data", function (client) {
apiWrapper.waitForElementVisible("@petOperationDeleteContainer", 3000)
.click("@petOperationDeleteCollpase")
@@ -203,4 +213,4 @@ describe("render pet api container", function () {
client.end()
})
})
})
+85
View File
@@ -0,0 +1,85 @@
openapi: 3.0.0
info:
title: Demo API
description: First test
termsOfService: 'http://demo.io/terms-of-service/'
contact:
name: Demo Support
email: support@demo.io
version: 1.0.0
servers:
- url: '{server}/v1'
variables:
server:
default: https://api.demo.io
description: the API endpoint
paths:
/session:
put:
summary: Returns a new authentication token
tags:
- session
security:
- basicAuth: []
responses:
'201':
description: A session object
content:
application/json:
schema:
allOf:
- type: object
properties:
user_id:
type: string
format: uuid
readOnly: true
example: 110e8400-e29b-11d4-a716-446655440000
- $ref: '#/components/schemas/Session'
'401':
$ref: '#/components/responses/Unauthorized'
components:
securitySchemes:
basicAuth:
type: http
scheme: basic
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
Session:
required:
- token
properties:
token:
type: string
readOnly: true
example: >-
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.EkN-DOsnsuRjRO6BxXemmJDm3HbxrbRzXglbN2S4sOkopdU4IsDxTI8jO19W_A4K8ZPJijNLis4EZsHeY559a4DFOd50_OqgHGuERTqYZyuhtF39yxJPAjUESwxk2J5k_4zM3O-vtd1Ghyo4IbqKKSy6J9mTniYJPenn5-HIirE
Error:
required:
- message
properties:
message:
description: a human readable message explaining the error
type: string
reason:
description: a functionnal key about the error
type: string
responses:
Unauthorized:
description: Not authenticated
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'