fix(Models): use specPath for isShownKey to toggle models (#6200)

Co-authored-by: Tim Lai <timothy.lai@gmail.com>
This commit is contained in:
geraldglynn
2020-08-01 02:40:47 +01:00
committed by GitHub
parent d7d166d0a4
commit 084b236f76
6 changed files with 280 additions and 27 deletions

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View 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")
})
}