refactor(oas31): simplify Webhooks component by utilizing selectors (#8481)
Refs #8474
This commit is contained in:
@@ -126,6 +126,7 @@ export default class BaseLayout extends React.Component {
|
||||
<Operations />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{isOAS31 && (
|
||||
<Row className="webhooks-container">
|
||||
<Col mobile={12} desktop={12}>
|
||||
@@ -133,6 +134,7 @@ export default class BaseLayout extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
|
||||
<Row>
|
||||
<Col mobile={12} desktop={12}>
|
||||
<Models />
|
||||
|
||||
@@ -2,13 +2,6 @@ import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import Im from "immutable"
|
||||
|
||||
const SWAGGER2_OPERATION_METHODS = [
|
||||
"get", "put", "post", "delete", "options", "head", "patch"
|
||||
]
|
||||
|
||||
const OAS3_OPERATION_METHODS = SWAGGER2_OPERATION_METHODS.concat(["trace"])
|
||||
|
||||
|
||||
export default class Operations extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
@@ -53,6 +46,7 @@ export default class Operations extends React.Component {
|
||||
layoutActions,
|
||||
getConfigs,
|
||||
} = this.props
|
||||
const validOperationMethods = specSelectors.validOperationMethods()
|
||||
const OperationContainer = getComponent("OperationContainer", true)
|
||||
const OperationTag = getComponent("OperationTag")
|
||||
const operations = tagObj.get("operations")
|
||||
@@ -74,16 +68,7 @@ export default class Operations extends React.Component {
|
||||
const method = op.get("method")
|
||||
const specPath = Im.List(["paths", path, method])
|
||||
|
||||
|
||||
// FIXME: (someday) this logic should probably be in a selector,
|
||||
// but doing so would require further opening up
|
||||
// selectors to the plugin system, to allow for dynamic
|
||||
// overriding of low-level selectors that other selectors
|
||||
// rely on. --KS, 12/17
|
||||
const validMethods = specSelectors.isOAS3() ?
|
||||
OAS3_OPERATION_METHODS : SWAGGER2_OPERATION_METHODS
|
||||
|
||||
if (validMethods.indexOf(method) === -1) {
|
||||
if (validOperationMethods.indexOf(method) === -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* @prettier
|
||||
*/
|
||||
import { OrderedMap, Map, List } from "immutable"
|
||||
import { createSelector } from "reselect"
|
||||
|
||||
import { getDefaultRequestBodyValue } from "./components/request-body"
|
||||
import { stringify } from "../../utils"
|
||||
|
||||
@@ -279,3 +281,14 @@ export const validateShallowRequired = (
|
||||
})
|
||||
return missingRequiredKeys
|
||||
}
|
||||
|
||||
export const validOperationMethods = createSelector(() => [
|
||||
"get",
|
||||
"put",
|
||||
"post",
|
||||
"delete",
|
||||
"options",
|
||||
"head",
|
||||
"patch",
|
||||
"trace",
|
||||
])
|
||||
|
||||
@@ -16,7 +16,7 @@ function onlyOAS3(selector) {
|
||||
(...args) => {
|
||||
if (system.getSystem().specSelectors.isOAS3()) {
|
||||
const result = selector(...args)
|
||||
return typeof result === "function" ? result(system, ...args) : result
|
||||
return typeof result === "function" ? result(system) : result
|
||||
} else {
|
||||
return ori(...args)
|
||||
}
|
||||
@@ -49,6 +49,16 @@ export const securityDefinitions = onlyOAS3(
|
||||
)
|
||||
)
|
||||
|
||||
export const validOperationMethods =
|
||||
(oriSelector, system) =>
|
||||
(state, ...args) => {
|
||||
if (system.specSelectors.isOAS3()) {
|
||||
return system.oas3Selectors.validOperationMethods()
|
||||
}
|
||||
|
||||
return oriSelector(...args)
|
||||
}
|
||||
|
||||
export const host = OAS3NullSelector
|
||||
export const basePath = OAS3NullSelector
|
||||
export const consumes = OAS3NullSelector
|
||||
|
||||
@@ -1,59 +1,45 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import React from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import { fromJS } from "immutable"
|
||||
import ImPropTypes from "react-immutable-proptypes"
|
||||
|
||||
// Todo: nice to have: similar to operation-tags, could have an expand/collapse button
|
||||
// to show/hide all webhook items
|
||||
const Webhooks = (props) => {
|
||||
const { specSelectors, getComponent, specPath } = props
|
||||
const Webhooks = ({ specSelectors, getComponent }) => {
|
||||
const operationDTOs = specSelectors.selectWebhooksOperations()
|
||||
const pathItemNames = Object.keys(operationDTOs)
|
||||
|
||||
const webhooksPathItems = specSelectors.webhooks()
|
||||
if (!webhooksPathItems || webhooksPathItems?.size < 1) {
|
||||
return null
|
||||
}
|
||||
const OperationContainer = getComponent("OperationContainer", true)
|
||||
|
||||
const pathItemsElements = webhooksPathItems.entrySeq().map(([pathItemName, pathItem], i) => {
|
||||
const operationsElements = pathItem.entrySeq().map(([operationMethod, operation], j) => {
|
||||
const op = fromJS({
|
||||
operation
|
||||
})
|
||||
// using defaultProps for `specPath`; may want to remove from props
|
||||
// and/or if extract to separate PathItem component, allow for use
|
||||
// with both OAS3.1 "webhooks" and "components.pathItems" features
|
||||
return <OperationContainer
|
||||
{...props}
|
||||
op={op}
|
||||
key={`${pathItemName}--${operationMethod}--${j}`}
|
||||
tag={""}
|
||||
method={operationMethod}
|
||||
path={pathItemName}
|
||||
specPath={specPath.push("webhooks", pathItemName, operationMethod)}
|
||||
allowTryItOut={false}
|
||||
/>
|
||||
})
|
||||
return <div key={`${pathItemName}-${i}`}>
|
||||
{operationsElements}
|
||||
</div>
|
||||
})
|
||||
if (pathItemNames.length === 0) return null
|
||||
|
||||
return (
|
||||
<div className="webhooks">
|
||||
<h2>Webhooks</h2>
|
||||
{pathItemsElements}
|
||||
|
||||
{pathItemNames.map((pathItemName) => (
|
||||
<div key={`${pathItemName}-webhook`}>
|
||||
{operationDTOs[pathItemName].map((operationDTO) => (
|
||||
<OperationContainer
|
||||
key={`${pathItemName}-${operationDTO.method}-webhook`}
|
||||
op={operationDTO.operation}
|
||||
tag=""
|
||||
method={operationDTO.method}
|
||||
path={pathItemName}
|
||||
specPath={operationDTO.specPath}
|
||||
allowTryItOut={false}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Webhooks.propTypes = {
|
||||
specSelectors: PropTypes.object.isRequired,
|
||||
specSelectors: PropTypes.shape({
|
||||
selectWebhooksOperations: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
specPath: ImPropTypes.list,
|
||||
}
|
||||
|
||||
Webhooks.defaultProps = {
|
||||
specPath: fromJS([])
|
||||
}
|
||||
|
||||
export default Webhooks
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
export const isOAS31 = (jsSpec) => {
|
||||
const oasVersion = jsSpec.get("openapi")
|
||||
|
||||
@@ -9,14 +10,34 @@ export const isOAS31 = (jsSpec) => {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector maker the only calls the passed selector
|
||||
* when spec is of OpenAPI 3.1.0 version.
|
||||
*/
|
||||
export const onlyOAS31 =
|
||||
(selector) =>
|
||||
() =>
|
||||
(system, ...args) => {
|
||||
(state, ...args) =>
|
||||
(system) => {
|
||||
if (system.getSystem().specSelectors.isOAS31()) {
|
||||
const result = selector(...args)
|
||||
return typeof result === "function" ? result(system, ...args) : result
|
||||
const result = selector(state, ...args)
|
||||
return typeof result === "function" ? result(system) : result
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector wrapper maker the only wraps the passed selector
|
||||
* when spec is of OpenAPI 3.1.0 version.
|
||||
*/
|
||||
export const onlyOAS31Wrap =
|
||||
(selector) =>
|
||||
(oriSelector, system) =>
|
||||
(state, ...args) => {
|
||||
if (system.getSystem().specSelectors.isOAS31()) {
|
||||
const result = selector(state, ...args)
|
||||
return typeof result === "function" ? result(system) : result
|
||||
} else {
|
||||
return oriSelector(...args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,11 @@ import {
|
||||
selectInfoSummaryField,
|
||||
selectInfoDescriptionField,
|
||||
selectInfoTermsOfServiceField,
|
||||
makeSelectInfoTermsOfServiceUrl as makeSelectTosUrl,
|
||||
makeSelectInfoTermsOfServiceUrl,
|
||||
selectExternalDocsDescriptionField,
|
||||
selectExternalDocsUrlField,
|
||||
makeSelectExternalDocsUrl,
|
||||
makeSelectWebhooksOperations,
|
||||
} from "./spec-extensions/selectors"
|
||||
import {
|
||||
isOAS3 as isOAS3Wrapper,
|
||||
@@ -45,8 +46,9 @@ const OAS31Plugin = () => {
|
||||
specSelectors.isOAS31 = makeIsOAS31(system)
|
||||
specSelectors.selectLicenseUrl = makeSelectLicenseUrl(system)
|
||||
specSelectors.selectContactUrl = makeSelectContactUrl(system)
|
||||
specSelectors.selectInfoTermsOfServiceUrl = makeSelectTosUrl(system)
|
||||
specSelectors.selectInfoTermsOfServiceUrl = makeSelectInfoTermsOfServiceUrl(system) // prettier-ignore
|
||||
specSelectors.selectExternalDocsUrl = makeSelectExternalDocsUrl(system)
|
||||
specSelectors.selectWebhooksOperations = makeSelectWebhooksOperations(system) // prettier-ignore
|
||||
|
||||
oas31Selectors.selectLicenseUrl = makeOAS31SelectLicenseUrl(system)
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import { Map } from "immutable"
|
||||
import { List, Map } from "immutable"
|
||||
import { createSelector } from "reselect"
|
||||
|
||||
import { safeBuildUrl } from "core/utils/url"
|
||||
@@ -16,6 +16,39 @@ export const webhooks = onlyOAS31(() => (system) => {
|
||||
return system.specSelectors.specJson().get("webhooks", map)
|
||||
})
|
||||
|
||||
/**
|
||||
* `specResolvedSubtree` selector is needed as input selector,
|
||||
* so that we regenerate the selected result whenever the lazy
|
||||
* resolution happens.
|
||||
*/
|
||||
export const makeSelectWebhooksOperations = (system) =>
|
||||
onlyOAS31(
|
||||
createSelector(
|
||||
() => system.specSelectors.webhooks(),
|
||||
() => system.specSelectors.validOperationMethods(),
|
||||
() => system.specSelectors.specResolvedSubtree(["webhooks"]),
|
||||
(webhooks, validOperationMethods) => {
|
||||
return webhooks
|
||||
.reduce((allOperations, pathItem, pathItemName) => {
|
||||
const pathItemOperations = pathItem
|
||||
.entrySeq()
|
||||
.filter(([key]) => validOperationMethods.includes(key))
|
||||
.map(([method, operation]) => ({
|
||||
operation: Map({ operation }),
|
||||
method,
|
||||
path: pathItemName,
|
||||
specPath: List(["webhooks", pathItemName, method]),
|
||||
}))
|
||||
|
||||
return allOperations.concat(pathItemOperations)
|
||||
}, List())
|
||||
.groupBy((operation) => operation.path)
|
||||
.map((operations) => operations.toArray())
|
||||
.toObject()
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
export const license = () => (system) => {
|
||||
return system.specSelectors.info().get("license", map)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
import { onlyOAS31Wrap } from "../helpers"
|
||||
|
||||
export const isOAS3 =
|
||||
(oriSelector, system) =>
|
||||
(state, ...args) => {
|
||||
@@ -8,12 +10,6 @@ export const isOAS3 =
|
||||
return isOAS31 || oriSelector(...args)
|
||||
}
|
||||
|
||||
export const selectLicenseUrl =
|
||||
(oriSelector, system) =>
|
||||
(state, ...args) => {
|
||||
if (system.specSelectors.isOAS31()) {
|
||||
export const selectLicenseUrl = onlyOAS31Wrap(() => (system) => {
|
||||
return system.oas31Selectors.selectLicenseUrl()
|
||||
}
|
||||
|
||||
return oriSelector(...args)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -114,6 +114,8 @@ export const paths = createSelector(
|
||||
spec => spec.get("paths")
|
||||
)
|
||||
|
||||
export const validOperationMethods = createSelector(() => ["get", "put", "post", "delete", "options", "head", "patch"])
|
||||
|
||||
export const operations = createSelector(
|
||||
paths,
|
||||
paths => {
|
||||
|
||||
@@ -28,6 +28,7 @@ describe("<Operations/>", function(){
|
||||
specSelectors: {
|
||||
isOAS3() { return false },
|
||||
url() { return "https://petstore.swagger.io/v2/swagger.json" },
|
||||
validOperationMethods() { return ["get", "put", "post", "delete", "options", "head", "patch"] },
|
||||
taggedOperations() {
|
||||
return fromJS({
|
||||
"default": {
|
||||
@@ -83,6 +84,7 @@ describe("<Operations/>", function(){
|
||||
specSelectors: {
|
||||
isOAS3() { return true },
|
||||
url() { return "https://petstore.swagger.io/v2/swagger.json" },
|
||||
validOperationMethods() { return ["get", "put", "post", "delete", "options", "head", "patch", "trace"] },
|
||||
taggedOperations() {
|
||||
return fromJS({
|
||||
"default": {
|
||||
|
||||
Reference in New Issue
Block a user