fix(Models): use specPath for isShownKey to toggle models (#6200)
Co-authored-by: Tim Lai <timothy.lai@gmail.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import React, { Component } from "react"
|
import React, { Component } from "react"
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types"
|
||||||
|
import ImPropTypes from "react-immutable-proptypes"
|
||||||
import Im from "immutable"
|
import Im from "immutable"
|
||||||
|
|
||||||
export default class ModelCollapse extends Component {
|
export default class ModelCollapse extends Component {
|
||||||
@@ -13,7 +14,8 @@ export default class ModelCollapse extends Component {
|
|||||||
onToggle: PropTypes.func,
|
onToggle: PropTypes.func,
|
||||||
hideSelfOnExpand: PropTypes.bool,
|
hideSelfOnExpand: PropTypes.bool,
|
||||||
layoutActions: PropTypes.object,
|
layoutActions: PropTypes.object,
|
||||||
layoutSelectors: PropTypes.object.isRequired
|
layoutSelectors: PropTypes.object.isRequired,
|
||||||
|
specPath: ImPropTypes.list.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@@ -21,7 +23,8 @@ export default class ModelCollapse extends Component {
|
|||||||
expanded: false,
|
expanded: false,
|
||||||
title: null,
|
title: null,
|
||||||
onToggle: () => {},
|
onToggle: () => {},
|
||||||
hideSelfOnExpand: false
|
hideSelfOnExpand: false,
|
||||||
|
specPath: Im.List([]),
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
@@ -63,11 +66,10 @@ export default class ModelCollapse extends Component {
|
|||||||
|
|
||||||
onLoad = (ref) => {
|
onLoad = (ref) => {
|
||||||
if(ref) {
|
if(ref) {
|
||||||
const name = this.props.modelName
|
|
||||||
const scrollToKey = this.props.layoutSelectors.getScrollToKey()
|
const scrollToKey = this.props.layoutSelectors.getScrollToKey()
|
||||||
|
|
||||||
if( Im.is(scrollToKey, Im.fromJS(["models", name])) ) this.toggleCollapsed()
|
if( Im.is(scrollToKey, this.props.specPath) ) this.toggleCollapsed()
|
||||||
this.props.layoutActions.readyToScroll(["models", name], ref.parentElement)
|
this.props.layoutActions.readyToScroll(this.props.specPath, ref.parentElement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +85,7 @@ export default class ModelCollapse extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={classes || ""}>
|
<span className={classes || ""} ref={this.onLoad}>
|
||||||
{ title && <span onClick={this.toggleCollapsed} className="pointer">{title}</span> }
|
{ title && <span onClick={this.toggleCollapsed} className="pointer">{title}</span> }
|
||||||
<span onClick={ this.toggleCollapsed } className="pointer">
|
<span onClick={ this.toggleCollapsed } className="pointer">
|
||||||
<span className={ "model-toggle" + ( this.state.expanded ? "" : " collapsed" ) }></span>
|
<span className={ "model-toggle" + ( this.state.expanded ? "" : " collapsed" ) }></span>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import React, { Component, } from "react"
|
import React, { Component, } from "react"
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types"
|
||||||
//import layoutActions from "actions/layout"
|
import ImPropTypes from "react-immutable-proptypes"
|
||||||
|
|
||||||
|
|
||||||
export default class ModelWrapper extends Component {
|
export default class ModelWrapper extends Component {
|
||||||
|
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
schema: PropTypes.object.isRequired,
|
schema: PropTypes.object.isRequired,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
displayName: PropTypes.string,
|
displayName: PropTypes.string,
|
||||||
|
fullPath: PropTypes.array.isRequired,
|
||||||
|
specPath: ImPropTypes.list.isRequired,
|
||||||
getComponent: PropTypes.func.isRequired,
|
getComponent: PropTypes.func.isRequired,
|
||||||
getConfigs: PropTypes.func.isRequired,
|
getConfigs: PropTypes.func.isRequired,
|
||||||
specSelectors: PropTypes.object.isRequired,
|
specSelectors: PropTypes.object.isRequired,
|
||||||
@@ -20,15 +20,10 @@ export default class ModelWrapper extends Component {
|
|||||||
includeWriteOnly: PropTypes.bool,
|
includeWriteOnly: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
getSchemaBasePath = () => {
|
|
||||||
const isOAS3 = this.props.specSelectors.isOAS3()
|
|
||||||
return isOAS3 ? ["components", "schemas"] : ["definitions"]
|
|
||||||
}
|
|
||||||
|
|
||||||
onToggle = (name,isShown) => {
|
onToggle = (name,isShown) => {
|
||||||
// If this prop is present, we'll have deepLinking for it
|
// If this prop is present, we'll have deepLinking for it
|
||||||
if(this.props.layoutActions) {
|
if(this.props.layoutActions) {
|
||||||
this.props.layoutActions.show([...this.getSchemaBasePath(), name],isShown)
|
this.props.layoutActions.show(this.props.fullPath, isShown)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +34,7 @@ export default class ModelWrapper extends Component {
|
|||||||
let expanded
|
let expanded
|
||||||
if(this.props.layoutSelectors) {
|
if(this.props.layoutSelectors) {
|
||||||
// If this is prop is present, we'll have deepLinking for it
|
// If this is prop is present, we'll have deepLinking for it
|
||||||
expanded = this.props.layoutSelectors.isShown(["models",this.props.name])
|
expanded = this.props.layoutSelectors.isShown(this.props.fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className="model-box">
|
return <div className="model-box">
|
||||||
|
|||||||
@@ -23,16 +23,22 @@ export default class Models extends Component {
|
|||||||
|
|
||||||
handleToggle = (name, isExpanded) => {
|
handleToggle = (name, isExpanded) => {
|
||||||
const { layoutActions } = this.props
|
const { layoutActions } = this.props
|
||||||
layoutActions.show(["models", name], isExpanded)
|
layoutActions.show([...this.getSchemaBasePath(), name], isExpanded)
|
||||||
if(isExpanded) {
|
if(isExpanded) {
|
||||||
this.props.specActions.requestResolvedSubtree([...this.getSchemaBasePath(), name])
|
this.props.specActions.requestResolvedSubtree([...this.getSchemaBasePath(), name])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad = (ref) => {
|
onLoadModels = (ref) => {
|
||||||
|
if (ref) {
|
||||||
|
this.props.layoutActions.readyToScroll(this.getSchemaBasePath(), ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoadModel = (ref) => {
|
||||||
if (ref) {
|
if (ref) {
|
||||||
const name = ref.getAttribute("data-name")
|
const name = ref.getAttribute("data-name")
|
||||||
this.props.layoutActions.readyToScroll(["models", name], ref)
|
this.props.layoutActions.readyToScroll([...this.getSchemaBasePath(), name], ref)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,8 +48,8 @@ export default class Models extends Component {
|
|||||||
let { docExpansion, defaultModelsExpandDepth } = getConfigs()
|
let { docExpansion, defaultModelsExpandDepth } = getConfigs()
|
||||||
if (!definitions.size || defaultModelsExpandDepth < 0) return null
|
if (!definitions.size || defaultModelsExpandDepth < 0) return null
|
||||||
|
|
||||||
let showModels = layoutSelectors.isShown("models", defaultModelsExpandDepth > 0 && docExpansion !== "none")
|
|
||||||
const specPathBase = this.getSchemaBasePath()
|
const specPathBase = this.getSchemaBasePath()
|
||||||
|
let showModels = layoutSelectors.isShown(specPathBase, defaultModelsExpandDepth > 0 && docExpansion !== "none")
|
||||||
const isOAS3 = specSelectors.isOAS3()
|
const isOAS3 = specSelectors.isOAS3()
|
||||||
|
|
||||||
const ModelWrapper = getComponent("ModelWrapper")
|
const ModelWrapper = getComponent("ModelWrapper")
|
||||||
@@ -51,8 +57,8 @@ export default class Models extends Component {
|
|||||||
const ModelCollapse = getComponent("ModelCollapse")
|
const ModelCollapse = getComponent("ModelCollapse")
|
||||||
const JumpToPath = getComponent("JumpToPath")
|
const JumpToPath = getComponent("JumpToPath")
|
||||||
|
|
||||||
return <section className={ showModels ? "models is-open" : "models"}>
|
return <section className={ showModels ? "models is-open" : "models"} ref={this.onLoadModels}>
|
||||||
<h4 onClick={() => layoutActions.show("models", !showModels)}>
|
<h4 onClick={() => layoutActions.show(specPathBase, !showModels)}>
|
||||||
<span>{isOAS3 ? "Schemas" : "Models" }</span>
|
<span>{isOAS3 ? "Schemas" : "Models" }</span>
|
||||||
<svg width="20" height="20">
|
<svg width="20" height="20">
|
||||||
<use xlinkHref={showModels ? "#large-arrow-down" : "#large-arrow"} />
|
<use xlinkHref={showModels ? "#large-arrow-down" : "#large-arrow"} />
|
||||||
@@ -63,6 +69,7 @@ export default class Models extends Component {
|
|||||||
definitions.entrySeq().map(([name])=>{
|
definitions.entrySeq().map(([name])=>{
|
||||||
|
|
||||||
const fullPath = [...specPathBase, name]
|
const fullPath = [...specPathBase, name]
|
||||||
|
const specPath = Im.List(fullPath)
|
||||||
|
|
||||||
const schemaValue = specSelectors.specResolvedSubtree(fullPath)
|
const schemaValue = specSelectors.specResolvedSubtree(fullPath)
|
||||||
const rawSchemaValue = specSelectors.specJson().getIn(fullPath)
|
const rawSchemaValue = specSelectors.specJson().getIn(fullPath)
|
||||||
@@ -71,20 +78,19 @@ export default class Models extends Component {
|
|||||||
const rawSchema = Map.isMap(rawSchemaValue) ? rawSchemaValue : Im.Map()
|
const rawSchema = Map.isMap(rawSchemaValue) ? rawSchemaValue : Im.Map()
|
||||||
|
|
||||||
const displayName = schema.get("title") || rawSchema.get("title") || name
|
const displayName = schema.get("title") || rawSchema.get("title") || name
|
||||||
const isShown = layoutSelectors.isShown( ["models", name], false )
|
const isShown = layoutSelectors.isShown(fullPath, false)
|
||||||
|
|
||||||
if( isShown && (schema.size === 0 && rawSchema.size > 0) ) {
|
if( isShown && (schema.size === 0 && rawSchema.size > 0) ) {
|
||||||
// Firing an action in a container render is not great,
|
// Firing an action in a container render is not great,
|
||||||
// but it works for now.
|
// but it works for now.
|
||||||
this.props.specActions.requestResolvedSubtree([...this.getSchemaBasePath(), name])
|
this.props.specActions.requestResolvedSubtree(fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
const specPath = Im.List([...specPathBase, name])
|
|
||||||
|
|
||||||
const content = <ModelWrapper name={ name }
|
const content = <ModelWrapper name={ name }
|
||||||
expandDepth={ defaultModelsExpandDepth }
|
expandDepth={ defaultModelsExpandDepth }
|
||||||
schema={ schema || Im.Map() }
|
schema={ schema || Im.Map() }
|
||||||
displayName={displayName}
|
displayName={displayName}
|
||||||
|
fullPath={fullPath}
|
||||||
specPath={specPath}
|
specPath={specPath}
|
||||||
getComponent={ getComponent }
|
getComponent={ getComponent }
|
||||||
specSelectors={ specSelectors }
|
specSelectors={ specSelectors }
|
||||||
@@ -101,7 +107,7 @@ export default class Models extends Component {
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
return <div id={ `model-${name}` } className="model-container" key={ `models-section-${name}` }
|
return <div id={ `model-${name}` } className="model-container" key={ `models-section-${name}` }
|
||||||
data-name={name} ref={this.onLoad} >
|
data-name={name} ref={this.onLoadModel} >
|
||||||
<span className="models-jump-to-path"><JumpToPath specPath={specPath} /></span>
|
<span className="models-jump-to-path"><JumpToPath specPath={specPath} /></span>
|
||||||
<ModelCollapse
|
<ModelCollapse
|
||||||
classes="model-box"
|
classes="model-box"
|
||||||
@@ -110,6 +116,9 @@ export default class Models extends Component {
|
|||||||
title={title}
|
title={title}
|
||||||
displayName={displayName}
|
displayName={displayName}
|
||||||
modelName={name}
|
modelName={name}
|
||||||
|
specPath={specPath}
|
||||||
|
layoutSelectors={layoutSelectors}
|
||||||
|
layoutActions={layoutActions}
|
||||||
hideSelfOnExpand={true}
|
hideSelfOnExpand={true}
|
||||||
expanded={ defaultModelsExpandDepth > 0 && isShown }
|
expanded={ defaultModelsExpandDepth > 0 && isShown }
|
||||||
>{content}</ModelCollapse>
|
>{content}</ModelCollapse>
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
openapi: "3.0.0"
|
||||||
|
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Order:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
petId:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
quantity:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
shipDate:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
description: Order Status
|
||||||
|
enum:
|
||||||
|
- placed
|
||||||
|
- approved
|
||||||
|
- delivered
|
||||||
|
complete:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
xml:
|
||||||
|
name: Order
|
||||||
|
User:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
firstName:
|
||||||
|
type: string
|
||||||
|
lastName:
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
phone:
|
||||||
|
type: string
|
||||||
|
userStatus:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
description: User Status
|
||||||
|
xml:
|
||||||
|
name: User
|
||||||
|
Pet:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- photoUrls
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
category:
|
||||||
|
$ref: '#/components/schemas/Category'
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: doggie
|
||||||
|
photoUrls:
|
||||||
|
type: array
|
||||||
|
xml:
|
||||||
|
name: photoUrl
|
||||||
|
wrapped: true
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
tags:
|
||||||
|
type: array
|
||||||
|
xml:
|
||||||
|
name: tag
|
||||||
|
wrapped: true
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Tag'
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
description: pet status in the store
|
||||||
|
enum:
|
||||||
|
- available
|
||||||
|
- pending
|
||||||
|
- sold
|
||||||
|
xml:
|
||||||
|
name: Pet
|
||||||
|
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
swagger: "2.0"
|
||||||
|
|
||||||
|
definitions:
|
||||||
|
Order:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
petId:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
quantity:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
shipDate:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
description: Order Status
|
||||||
|
enum:
|
||||||
|
- placed
|
||||||
|
- approved
|
||||||
|
- delivered
|
||||||
|
complete:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
xml:
|
||||||
|
name: Order
|
||||||
|
User:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
firstName:
|
||||||
|
type: string
|
||||||
|
lastName:
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
phone:
|
||||||
|
type: string
|
||||||
|
userStatus:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
description: User Status
|
||||||
|
xml:
|
||||||
|
name: User
|
||||||
|
Pet:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- photoUrls
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
category:
|
||||||
|
$ref: '#/definitions/Category'
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: doggie
|
||||||
|
photoUrls:
|
||||||
|
type: array
|
||||||
|
xml:
|
||||||
|
name: photoUrl
|
||||||
|
wrapped: true
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
tags:
|
||||||
|
type: array
|
||||||
|
xml:
|
||||||
|
name: tag
|
||||||
|
wrapped: true
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Tag'
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
description: pet status in the store
|
||||||
|
enum:
|
||||||
|
- available
|
||||||
|
- pending
|
||||||
|
- sold
|
||||||
|
xml:
|
||||||
|
name: Pet
|
||||||
|
|
||||||
62
test/e2e-cypress/tests/features/model-collapse.js
Normal file
62
test/e2e-cypress/tests/features/model-collapse.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
describe("Model collapse/expand feature", () => {
|
||||||
|
describe("in Swagger 2", () => {
|
||||||
|
const swagger2BaseUrl = "/?deepLinking=true&url=/documents/features/models.swagger.yaml"
|
||||||
|
const urlFragment = "#/definitions/Pet"
|
||||||
|
ModelCollapseTest(swagger2BaseUrl, urlFragment)
|
||||||
|
})
|
||||||
|
describe("in OpenAPI 3", () => {
|
||||||
|
const openAPI3BaseUrl = "/?deepLinking=true&url=/documents/features/models.openapi.yaml"
|
||||||
|
ModelCollapseTest(openAPI3BaseUrl)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function ModelCollapseTest(baseUrl, urlFragment) {
|
||||||
|
it("Models section should be expanded on load", () => {
|
||||||
|
cy.visit(baseUrl)
|
||||||
|
.get(".models")
|
||||||
|
.should("have.class", "is-open")
|
||||||
|
.get("#model-Pet")
|
||||||
|
.should("exist")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Models section should collapse and expand when toggled", () => {
|
||||||
|
cy.visit(baseUrl)
|
||||||
|
.get(".models h4")
|
||||||
|
.click()
|
||||||
|
.get(".models")
|
||||||
|
.should("not.have.class", "is-open")
|
||||||
|
.get("#model-Order")
|
||||||
|
.should("not.exist")
|
||||||
|
.get(".models h4")
|
||||||
|
.click()
|
||||||
|
.get(".models")
|
||||||
|
.should("have.class", "is-open")
|
||||||
|
.get("#model-Order")
|
||||||
|
.should("exist")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Model should collapse and expand when toggled clicking title", () => {
|
||||||
|
cy.visit(baseUrl)
|
||||||
|
.get("#model-User .model-box .pointer:nth-child(1)")
|
||||||
|
.click()
|
||||||
|
.get("#model-User .model-box .model .inner-object")
|
||||||
|
.should("exist")
|
||||||
|
.get("#model-User .model-box .pointer:nth-child(1)")
|
||||||
|
.click()
|
||||||
|
.get("#model-User .model-box .model .inner-object")
|
||||||
|
.should("not.exist")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Model should collapse and expand when toggled clicking arrow", () => {
|
||||||
|
cy.visit(baseUrl)
|
||||||
|
.get("#model-User .model-box .pointer:nth-child(2)")
|
||||||
|
.click()
|
||||||
|
.get("#model-User .model-box .model .inner-object")
|
||||||
|
.should("exist")
|
||||||
|
.get("#model-User .model-box .pointer:nth-child(2)")
|
||||||
|
.click()
|
||||||
|
.get("#model-User .model-box .model .inner-object")
|
||||||
|
.should("not.exist")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user