Browse Source

feature: `swagger-ui-react` module (via #5207)

* swagger-ui-react alpha.0

* alpha.1

* alpha.2

* alpha.3

* begin updating README

* alpha.4

* WIP: `displayOperationId` support

* move loading error readouts to BaseLayout

* add `url` prop

* export React component as default

* add interceptor support

* modify docs markup

* add `onComplete` prop

* add `spec` prop

* Update README.md

* alpha.6

* remove independent manifest; build releasable exclusively from template

* ensure dist is present; drop config field in manifest

* drop alpha field

this script is now able to release to npm!

* remove unused selector references

* Update README.md
bubble
kyle 5 years ago
committed by GitHub
parent
commit
7025773fbf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 973 additions and 385 deletions
  1. +2
    -0
      .gitignore
  2. +2
    -2
      README.md
  3. +79
    -0
      flavors/swagger-ui-react/README.md
  4. +83
    -0
      flavors/swagger-ui-react/index.js
  5. +5
    -0
      flavors/swagger-ui-react/release/create-manifest.js
  6. +28
    -0
      flavors/swagger-ui-react/release/run.sh
  7. +45
    -0
      flavors/swagger-ui-react/release/template.json
  8. +687
    -344
      package-lock.json
  9. +2
    -0
      package.json
  10. +37
    -9
      src/core/components/layouts/base.jsx
  11. +3
    -30
      src/standalone/layout.jsx

+ 2
- 0
.gitignore View File

@@ -11,6 +11,8 @@ selenium-debug.log
test/e2e/db.json
docs/_book

flavors/**/dist/*

# Cypress
test/e2e-cypress/screenshots
test/e2e-cypress/videos

+ 2
- 2
README.md View File

@@ -7,10 +7,11 @@
**🕰️ Looking for the older version of Swagger UI?** Refer to the [*2.x* branch](https://github.com/swagger-api/swagger-ui/tree/2.x).


This repository publishes to two different NPM modules:
This repository publishes to three different NPM modules:

* [swagger-ui](https://www.npmjs.com/package/swagger-ui) is a traditional npm module intended for use in single-page applications that are capable of resolving dependencies (via Webpack, Browserify, etc).
* [swagger-ui-dist](https://www.npmjs.com/package/swagger-ui-dist) is a dependency-free module that includes everything you need to serve Swagger UI in a server-side project, or a single-page application that can't resolve npm module dependencies.
* [swagger-ui-react](https://www.npmjs.com/package/swagger-ui-react) is Swagger UI packaged as a React component for use in React applciations.

We strongly suggest that you use `swagger-ui` instead of `swagger-ui-dist` if you're building a single-page application, since `swagger-ui-dist` is significantly larger.

@@ -67,7 +68,6 @@ To help with the migration, here are the currently known issues with 3.X. This l
- Support for `collectionFormat` is partial.
- l10n (translations) is not implemented.
- Relative path support for external files is not implemented.
- There are compatibility [issues](https://github.com/swagger-api/swagger-ui/labels/epic%3A%20usage%20in%20react%4016) with React 16.x.

## Security contact



+ 79
- 0
flavors/swagger-ui-react/README.md View File

@@ -0,0 +1,79 @@
# `swagger-ui-react`

[![NPM version](https://badge.fury.io/js/swagger-ui-react.svg)](http://badge.fury.io/js/swagger-ui-react)

`swagger-ui-react` is a flavor of Swagger UI suitable for use in React applications.

It has a few differences from the main version of Swagger UI:
* Declares `react` and `react-dom` as peerDependencies instead of production dependencies
* Exports a component instead of a constructor function

Versions of this module mirror the version of Swagger UI included in the distribution.

## Quick start

Install `swagger-ui-react`:

```
$ npm i --save swagger-ui-react
```

Use it in your React application:

```js
import SwaggerUI from "swagger-ui-react"
import "swagger-ui-react/swagger-ui.css"

export default App = () => <SwaggerUI url="https://petstore.swagger.io/v2/swagger.json" />
```

## Props

These props map to [Swagger UI configuration options](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md) of the same name.

#### `spec`: PropTypes.object

An OpenAPI document respresented as a JavaScript object, JSON string, or YAML string for Swagger UI to display.

⚠️ Don't use this in conjunction with `url` - unpredictable behavior may occur.

#### `url`: PropTypes.string

Remote URL to an OpenAPI document that Swagger UI will fetch, parse, and display.

⚠️ Don't use this in conjunction with `spec` - unpredictable behavior may occur.

#### `onComplete`: PropTypes.func

A callback function that is triggered when Swagger-UI finishes rendering an OpenAPI document.

#### `requestInterceptor`: PropTypes.func

> `req => req` or `req => Promise<req>`.

A function that accepts a request object, and returns either a request object
or a Promise that resolves to a request object.

#### `responseInterceptor`: PropTypes.func

> `res => res` or `res => Promise<res>`.

A function that accepts a response object, and returns either a response object
or a Promise that resolves to a response object.

## Limitations

* Not all configuration bindings are available.
* OAuth redirection handling is not supported.
* Topbar/Standalone mode is not supported.
* Custom plugins are not supported.

We intend to address these limitations based on user demand, so please open an issue or pull request if you have a specific request.

## Notes

* The `package.json` in the same folder as this README is _not_ the manifest that should be used for releases - another manifest is generated at build-time and can be found in `./dist/`.

---

For anything else, check the [Swagger-UI](https://github.com/swagger-api/swagger-ui) repository.

+ 83
- 0
flavors/swagger-ui-react/index.js View File

@@ -0,0 +1,83 @@
import React from "react"
import PropTypes from "prop-types"
import swaggerUIConstructor from "./swagger-ui"

export default class SwaggerUI extends React.Component {
constructor (props) {
super(props)
this.SwaggerUIComponent = null
this.system = null
}
componentDidMount() {
const ui = swaggerUIConstructor({
spec: this.props.spec,
url: this.props.url,
requestInterceptor: this.requestInterceptor,
responseInterceptor: this.responseInterceptor,
onComplete: this.onComplete,
})

this.system = ui
this.SwaggerUIComponent = ui.getComponent("App", "root")

this.forceUpdate()
}
render() {
return this.SwaggerUIComponent ? <this.SwaggerUIComponent /> : null
}

componentDidUpdate(prevProps) {
if(this.props.url !== prevProps.url) {
// flush current content
this.system.specActions.updateSpec("")

if(this.props.url) {
// update the internal URL
this.system.specActions.updateUrl(this.props.url)
// trigger remote definition fetch
this.system.specActions.download(this.props.url)
}
}

if(this.props.spec !== prevProps.spec && this.props.spec) {
if(typeof this.props.spec === "object") {
this.system.specActions.updateSpec(JSON.stringify(this.props.spec))
} else {
this.system.specActions.updateSpec(this.props.spec)
}
}
}

requestInterceptor = (req) => {
if (typeof this.props.requestInterceptor === "function") {
return this.props.requestInterceptor(req)
}
return req
}

responseInterceptor = (res) => {
if (typeof this.props.responseInterceptor === "function") {
return this.props.responseInterceptor(res)
}
return res
}

onComplete = () => {
if (typeof this.props.onComplete === "function") {
return this.props.onComplete()
}
}
}

SwaggerUI.propTypes = {
spec: PropTypes.oneOf([
PropTypes.string,
PropTypes.object,
]),
url: PropTypes.string,
requestInterceptor: PropTypes.func,
responseInterceptor: PropTypes.func,
onComplete: PropTypes.func,
}

+ 5
- 0
flavors/swagger-ui-react/release/create-manifest.js View File

@@ -0,0 +1,5 @@
var jsonMerger = require("json-merger")
var fs = require("fs")
var result = jsonMerger.mergeFiles(["../../../package.json", "template.json"])

process.stdout.write(JSON.stringify(result, null, 2))

+ 28
- 0
flavors/swagger-ui-react/release/run.sh View File

@@ -0,0 +1,28 @@
# Deploy `swagger-ui-react` to npm.

# Parameter Expansion: http://stackoverflow.com/questions/6393551/what-is-the-meaning-of-0-in-a-bash-script
cd "${0%/*}"

mkdir ../dist

# Copy UI's dist files to our directory
cp ../../../dist/swagger-ui.js ../dist
cp ../../../dist/swagger-ui.css ../dist

# Create a releasable package manifest
node create-manifest.js > ../dist/package.json

# Transpile our top-level component
../../../node_modules/.bin/babel ../index.js > ../dist/index.js

# Copy our README into the dist folder for npm
cp ../README.md ../dist

# Run the release from the dist folder
cd ../dist

if [ "$PUBLISH_FLAVOR_REACT" = "true" ] ; then
npm publish .
else
npm pack .
fi

+ 45
- 0
flavors/swagger-ui-react/release/template.json View File

@@ -0,0 +1,45 @@
{
"dependencies": {
"react": {
"$remove": true
},
"react-dom": {
"$remove": true
}
},
"scripts": {
"$remove": true
},
"devDependencies": {
"$remove": true
},
"bundlesize": {
"$remove": true
},
"nyc": {
"$remove": true
},
"browserslist": {
"$remove": true
},
"config": {
"$remove": true
},
"name": "swagger-ui-react",
"main": "index.js",
"repository": "git@github.com:swagger-api/swagger-ui.git",
"contributors": [
"(in alphabetical order)",
"Anna Bodnia <anna.bodnia@gmail.com>",
"Buu Nguyen <buunguyen@gmail.com>",
"Josh Ponelat <jponelat@gmail.com>",
"Kyle Shockey <kyleshockey1@gmail.com>",
"Robert Barnwell <robert@robertismy.name>",
"Sahar Jafari <shr.jafari@gmail.com>"
],
"license": "Apache-2.0",
"peerDependencies": {
"react": ">=15.6.2",
"react-dom": ">=15.6.2"
}
}

+ 687
- 344
package-lock.json
File diff suppressed because it is too large
View File


+ 2
- 0
package.json View File

@@ -83,6 +83,7 @@
},
"devDependencies": {
"autoprefixer": "^8.4.1",
"babel-cli": "^6.26.0",
"babel-core": "^6.23.1",
"babel-eslint": "^7.1.1",
"babel-loader": "^7.1.0",
@@ -118,6 +119,7 @@
"imports-loader": "^0.8.0",
"jsdom": "^11.10.0",
"json-loader": "^0.5.7",
"json-merger": "^1.1.0",
"json-server": "^0.12.2",
"less": "^3.0.2",
"license-checker": "^19.0.0",


+ 37
- 9
src/core/components/layouts/base.jsx View File

@@ -13,7 +13,7 @@ export default class BaseLayout extends React.Component {
}

render() {
let {specSelectors, getComponent} = this.props
let {errSelectors, specSelectors, getComponent} = this.props

let SvgAssets = getComponent("SvgAssets")
let InfoContainer = getComponent("InfoContainer", true)
@@ -33,15 +33,43 @@ export default class BaseLayout extends React.Component {

const isSpecEmpty = !specSelectors.specStr()

if(isSpecEmpty) {
let loadingMessage
let isLoading = specSelectors.loadingStatus() === "loading"
if(isLoading) {
loadingMessage = <div className="loading"></div>
} else {
loadingMessage = <h4>No API definition provided.</h4>
}
const loadingStatus = specSelectors.loadingStatus()

let loadingMessage = null
if(loadingStatus === "loading") {
loadingMessage = <div className="info">
<div className="loading-container">
<div className="loading"></div>
</div>
</div>
}

if(loadingStatus === "failed") {
loadingMessage = <div className="info">
<div className="loading-container">
<h4 className="title">Failed to load API definition.</h4>
<Errors />
</div>
</div>
}

if (loadingStatus === "failedConfig") {
const lastErr = errSelectors.lastError()
const lastErrMsg = lastErr ? lastErr.get("message") : ""
loadingMessage = <div className="info" style={{ maxWidth: "880px", marginLeft: "auto", marginRight: "auto", textAlign: "center" }}>
<div className="loading-container">
<h4 className="title">Failed to load remote configuration.</h4>
<p>{lastErrMsg}</p>
</div>
</div>
}

if(!loadingMessage && isSpecEmpty) {
loadingMessage = <h4>No API definition provided.</h4>
}

if(loadingMessage) {
return <div className="swagger-ui">
<div className="loading-container">
{loadingMessage}


+ 3
- 30
src/standalone/layout.jsx View File

@@ -16,49 +16,22 @@ export default class StandaloneLayout extends React.Component {
}

render() {
let { getComponent, specSelectors, errSelectors } = this.props
let { getComponent } = this.props

let Container = getComponent("Container")
let Row = getComponent("Row")
let Col = getComponent("Col")
let Errors = getComponent("errors", true)

const Topbar = getComponent("Topbar", true)
const BaseLayout = getComponent("BaseLayout", true)
const OnlineValidatorBadge = getComponent("onlineValidatorBadge", true)

const loadingStatus = specSelectors.loadingStatus()
const lastErr = errSelectors.lastError()
const lastErrMsg = lastErr ? lastErr.get("message") : ""

return (

<Container className='swagger-ui'>
{ Topbar ? <Topbar /> : null }
{ loadingStatus === "loading" &&
<div className="info">
<div className="loading-container">
<div className="loading"></div>
</div>
</div>
}
{ loadingStatus === "failed" &&
<div className="info">
<div className="loading-container">
<h4 className="title">Failed to load API definition.</h4>
<Errors/>
</div>
</div>
}
{ loadingStatus === "failedConfig" &&
<div className="info" style={{ maxWidth: "880px", marginLeft: "auto", marginRight: "auto", textAlign: "center" }}>
<div className="loading-container">
<h4 className="title">Failed to load remote configuration.</h4>
<p>{lastErrMsg}</p>
</div>
</div>
}
{ !loadingStatus || loadingStatus === "success" && <BaseLayout /> }
{Topbar ? <Topbar /> : null}
<BaseLayout />
<Row>
<Col>
<OnlineValidatorBadge />


Loading…
Cancel
Save