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:
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user