feat: lazy resolver (#4249)

* default to empty `ImmutableMap` when grabbing op metadata
* pass `errors` into JsonSchema components
* Account for Immutable data structure in JavaScriptonSchema...
    ...and create empty Lists instead of Maps by default.
* Pass ImmutableList through to JsonSchema child components
* Add lazy resolving spec state extensions
* TEMPORARY: disable conventional resolved spec
* WIP
* Use resolveSubtree in Operation display
* Freebie: short-circuit Markdown component if it is given plaintext
* NEW DEFAULT BEHAVIOR: `defaultModelsExpandDepth: 1` does not expand individual models
* Render faked Model expander to trigger resolution
* Baseline support for Editor lifecycles
* Display operation summaries before the operation is resolved
* Test migrations
* WIP
* Swagger2 TIO Body params
* a bit of cleanup
* Debounce string param inputs
* Reach into unresolved operation for deprecated flag, if available
* Fire subtree request outside of render
* Remove debugging flags
* Fix logical errors in spec statePlugins
* TODOs become TODONEs!
* Migrate deeplinking feature to non-resolved spec action
* ESLint fixes
This commit is contained in:
kyle
2018-02-23 01:12:53 -08:00
committed by GitHub
parent 54ed39d69f
commit ecf688171f
23 changed files with 359 additions and 97 deletions

View File

@@ -8,14 +8,17 @@ export default class ModelCollapse extends Component {
children: PropTypes.any,
title: PropTypes.element,
modelName: PropTypes.string,
onToggle: PropTypes.func
classes: PropTypes.string,
onToggle: PropTypes.func,
hideSelfOnExpand: PropTypes.bool,
}
static defaultProps = {
collapsedContent: "{...}",
expanded: false,
title: null,
onToggle: () => {}
onToggle: () => {},
hideSelfOnExpand: false
}
constructor(props, context) {
@@ -29,17 +32,23 @@ export default class ModelCollapse extends Component {
}
}
componentWillReceiveProps(nextProps){
componentDidMount() {
const { hideSelfOnExpand, expanded, modelName } = this.props
if(hideSelfOnExpand && expanded) {
// We just mounted pre-expanded, and we won't be going back..
// So let's give our parent an `onToggle` call..
// Since otherwise it will never be called.
this.props.onToggle(modelName, expanded)
}
}
if(this.props.expanded!= nextProps.expanded){
componentWillReceiveProps(nextProps){
if(this.props.expanded !== nextProps.expanded){
this.setState({expanded: nextProps.expanded})
}
}
toggleCollapsed=()=>{
if(this.props.onToggle){
this.props.onToggle(this.props.modelName,!this.state.expanded)
}
@@ -50,9 +59,18 @@ export default class ModelCollapse extends Component {
}
render () {
const {title} = this.props
const { title, classes } = this.props
if(this.state.expanded ) {
if(this.props.hideSelfOnExpand) {
return <span className={classes || ""}>
{this.props.children}
</span>
}
}
return (
<span>
<span className={classes || ""}>
{ title && <span onClick={this.toggleCollapsed} style={{ "cursor": "pointer" }}>{title}</span> }
<span onClick={ this.toggleCollapsed } style={{ "cursor": "pointer" }}>
<span className={ "model-toggle" + ( this.state.expanded ? "" : " collapsed" ) }></span>

View File

@@ -6,11 +6,29 @@ export default class Models extends Component {
static propTypes = {
getComponent: PropTypes.func,
specSelectors: PropTypes.object,
specActions: PropTypes.object.isRequired,
layoutSelectors: PropTypes.object,
layoutActions: PropTypes.object,
getConfigs: PropTypes.func.isRequired
}
getSchemaBasePath = () => {
const isOAS3 = this.props.specSelectors.isOAS3()
return isOAS3 ? ["components", "schemas"] : ["definitions"]
}
getCollapsedContent = () => {
return " "
}
handleToggle = (name, isExpanded) => {
const { layoutActions } = this.props
layoutActions.show(["models", name], isExpanded)
if(isExpanded) {
this.props.specActions.requestResolvedSubtree([...this.getSchemaBasePath(), name])
}
}
render(){
let { specSelectors, getComponent, layoutSelectors, layoutActions, getConfigs } = this.props
let definitions = specSelectors.definitions()
@@ -18,10 +36,11 @@ export default class Models extends Component {
if (!definitions.size || defaultModelsExpandDepth < 0) return null
let showModels = layoutSelectors.isShown("models", defaultModelsExpandDepth > 0 && docExpansion !== "none")
const specPathBase = specSelectors.isOAS3() ? ["components", "schemas"] : ["definitions"]
const specPathBase = this.getSchemaBasePath()
const ModelWrapper = getComponent("ModelWrapper")
const Collapse = getComponent("Collapse")
const ModelCollapse = getComponent("ModelCollapse")
return <section className={ showModels ? "models is-open" : "models"}>
<h4 onClick={() => layoutActions.show("models", !showModels)}>
@@ -32,18 +51,40 @@ export default class Models extends Component {
</h4>
<Collapse isOpened={showModels}>
{
definitions.entrySeq().map( ( [ name, model ])=>{
definitions.entrySeq().map( ( [ name ])=>{
const schema = specSelectors.specResolvedSubtree([...specPathBase, name])
if(layoutSelectors.isShown(["models", name], false) && schema === undefined) {
// Firing an action in a container render is not great,
// but it works for now.
this.props.specActions.requestResolvedSubtree([...this.getSchemaBasePath(), name])
}
const content = <ModelWrapper name={ name }
expandDepth={ defaultModelsExpandDepth }
schema={ schema }
specPath={Im.List([...specPathBase, name])}
getComponent={ getComponent }
specSelectors={ specSelectors }
getConfigs = {getConfigs}
layoutSelectors = {layoutSelectors}
layoutActions = {layoutActions}/>
const title = <span className="model-box">
<span className="model model-title">{name}</span>
</span>
return <div id={ `model-${name}` } className="model-container" key={ `models-section-${name}` }>
<ModelWrapper name={ name }
expandDepth={ defaultModelsExpandDepth }
schema={ model }
specPath={Im.List([...specPathBase, name])}
getComponent={ getComponent }
specSelectors={ specSelectors }
getConfigs = {getConfigs}
layoutSelectors = {layoutSelectors}
layoutActions = {layoutActions}/>
<ModelCollapse
classes="model-box"
collapsedContent={this.getCollapsedContent(name)}
onToggle={this.handleToggle}
title={title}
modelName={name}
hideSelfOnExpand={true}
expanded={defaultModelsExpandDepth > 1}
>{content}</ModelCollapse>
</div>
}).toArray()
}

View File

@@ -9,6 +9,7 @@ export default class Operation extends PureComponent {
static propTypes = {
specPath: ImPropTypes.list.isRequired,
operation: PropTypes.instanceOf(Iterable).isRequired,
summary: PropTypes.string,
response: PropTypes.instanceOf(Iterable),
request: PropTypes.instanceOf(Iterable),
@@ -34,7 +35,8 @@ export default class Operation extends PureComponent {
operation: null,
response: null,
request: null,
specPath: List()
specPath: List(),
summary: ""
}
render() {
@@ -59,6 +61,8 @@ export default class Operation extends PureComponent {
let operationProps = this.props.operation
let {
summary,
deprecated,
isShown,
isAuthorized,
path,
@@ -76,14 +80,13 @@ export default class Operation extends PureComponent {
} = operationProps.toJS()
let {
summary,
summary: resolvedSummary,
description,
deprecated,
externalDocs,
schemes
} = op.operation
} = op
let operation = operationProps.getIn(["op", "operation"])
let operation = operationProps.getIn(["op"])
let security = operationProps.get("security")
let responses = operation.get("responses")
let produces = operation.get("produces")
@@ -132,7 +135,7 @@ export default class Operation extends PureComponent {
{ !showSummary ? null :
<div className="opblock-summary-description">
{ summary }
{ resolvedSummary || summary }
</div>
}

View File

@@ -47,7 +47,7 @@ export default class ParamBody extends PureComponent {
updateValues = (props) => {
let { specSelectors, pathMethod, param, isExecute, consumesValue="" } = props
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name"), param.get("in")) : fromJS({})
let parameter = specSelectors ? specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in")) : fromJS({})
let isXml = /xml/i.test(consumesValue)
let isJson = /json/i.test(consumesValue)
let paramValue = isXml ? parameter.get("value_xml") : parameter.get("value")
@@ -107,7 +107,7 @@ export default class ParamBody extends PureComponent {
const HighlightCode = getComponent("highlightCode")
const ContentType = getComponent("contentType")
// for domains where specSelectors not passed
let parameter = specSelectors ? specSelectors.getParameter(pathMethod, param.get("name"), param.get("in")) : param
let parameter = specSelectors ? specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in")) : param
let errors = parameter.get("errors", List())
let consumesValue = specSelectors.contentTypeValues(pathMethod).get("requestContentType")
let consumes = this.props.consumes && this.props.consumes.size ? this.props.consumes : ParamBody.defaultProp.consumes

View File

@@ -24,7 +24,7 @@ export default class ParameterRow extends Component {
let { specSelectors, pathMethod, param } = props
let defaultValue = param.get("default")
let parameter = specSelectors.getParameter(pathMethod, param.get("name"), param.get("in"))
let parameter = specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in"))
let value = parameter ? parameter.get("value") : ""
if ( defaultValue !== undefined && value === undefined ) {
this.onChangeWrapper(defaultValue)
@@ -37,7 +37,7 @@ export default class ParameterRow extends Component {
let example = param.get("example")
let defaultValue = param.get("default")
let parameter = specSelectors.getParameter(pathMethod, param.get("name"), param.get("in"))
let parameter = specSelectors.parameterWithMeta(pathMethod, param.get("name"), param.get("in"))
let enumValue
if(isOAS3()) {
@@ -104,8 +104,7 @@ export default class ParameterRow extends Component {
let isFormDataSupported = "FormData" in win
let required = param.get("required")
let itemType = param.getIn(isOAS3 && isOAS3() ? ["schema", "items", "type"] : ["items", "type"])
let parameter = specSelectors.getParameter(pathMethod, param.get("name"), param.get("in"))
let value = parameter ? parameter.get("value") : ""
let value = param ? param.get("value") : ""
let extensions = getExtensions(param)

View File

@@ -101,7 +101,7 @@ export default class Parameters extends Component {
specPath={specPath.push(i.toString())}
getComponent={ getComponent }
getConfigs={ getConfigs }
param={ parameter }
param={ specSelectors.parameterWithMeta(pathMethod, parameter.get("name"), parameter.get("in")) }
key={ `${parameter.get( "in" )}.${parameter.get("name")}` }
onChange={ this.onChange }
onChangeConsumes={this.onChangeConsumesWrapper}

View File

@@ -3,7 +3,17 @@ import PropTypes from "prop-types"
import Remarkable from "remarkable"
import sanitize from "sanitize-html"
// eslint-disable-next-line no-useless-escape
const isPlainText = (str) => /^[A-Z\s0-9!?\.]+$/gi.test(str)
function Markdown({ source }) {
if(isPlainText(source)) {
// If the source text is not Markdown,
// let's save some time and just render it.
return <div className="markdown">
{source}
</div>
}
const html = new Remarkable({
html: true,
typographer: true,