Skip to content

Commit

Permalink
refactor(oas31): simplify Webhooks component by utilizing selectors
Browse files Browse the repository at this point in the history
Refs #8474
  • Loading branch information
char0n committed Mar 17, 2023
1 parent 865d98d commit df06a70
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 74 deletions.
2 changes: 2 additions & 0 deletions src/core/components/layouts/base.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,15 @@ export default class BaseLayout extends React.Component {
<Operations />
</Col>
</Row>

{isOAS31 && (
<Row className="webhooks-container">
<Col mobile={12} desktop={12}>
<Webhooks />
</Col>
</Row>
)}

<Row>
<Col mobile={12} desktop={12}>
<Models />
Expand Down
19 changes: 2 additions & 17 deletions src/core/components/operations.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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")
Expand All @@ -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
}

Expand Down
2 changes: 2 additions & 0 deletions src/core/containers/OperationContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export default class OperationContainer extends PureComponent {
props.specSelectors.allowTryItOutFor(props.path, props.method) : props.allowTryItOut)
const security = op.getIn(["operation", "security"]) || props.specSelectors.security()

console.dir(layoutSelectors.isShown(isShownKey, docExpansion === "full" ))

return {
operationId,
isDeepLinkingEnabled,
Expand Down
13 changes: 13 additions & 0 deletions src/core/plugins/oas3/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -279,3 +281,14 @@ export const validateShallowRequired = (
})
return missingRequiredKeys
}

export const validOperationMethods = createSelector(() => [
"get",
"put",
"post",
"delete",
"options",
"head",
"patch",
"trace",
])
12 changes: 11 additions & 1 deletion src/core/plugins/oas3/spec-extensions/wrap-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down
66 changes: 26 additions & 40 deletions src/core/plugins/oas31/components/webhooks.jsx
Original file line number Diff line number Diff line change
@@ -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
29 changes: 25 additions & 4 deletions src/core/plugins/oas31/helpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* @prettier
*/

export const isOAS31 = (jsSpec) => {
const oasVersion = jsSpec.get("openapi")

Expand All @@ -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)
}
}
6 changes: 4 additions & 2 deletions src/core/plugins/oas31/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ import {
selectInfoSummaryField,
selectInfoDescriptionField,
selectInfoTermsOfServiceField,
makeSelectInfoTermsOfServiceUrl as makeSelectTosUrl,
makeSelectInfoTermsOfServiceUrl,
selectExternalDocsDescriptionField,
selectExternalDocsUrlField,
makeSelectExternalDocsUrl,
makeSelectWebhooksOperations,
} from "./spec-extensions/selectors"
import {
isOAS3 as isOAS3Wrapper,
Expand All @@ -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)
},
Expand Down
35 changes: 34 additions & 1 deletion src/core/plugins/oas31/spec-extensions/selectors.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* @prettier
*/
import { Map } from "immutable"
import { List, Map } from "immutable"
import { createSelector } from "reselect"

import { safeBuildUrl } from "core/utils/url"
Expand All @@ -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)
}
Expand Down
14 changes: 5 additions & 9 deletions src/core/plugins/oas31/spec-extensions/wrap-selectors.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
/**
* @prettier
*/
import { onlyOAS31Wrap } from "../helpers"

export const isOAS3 =
(oriSelector, system) =>
(state, ...args) => {
const isOAS31 = system.specSelectors.isOAS31()
return isOAS31 || oriSelector(...args)
}

export const selectLicenseUrl =
(oriSelector, system) =>
(state, ...args) => {
if (system.specSelectors.isOAS31()) {
return system.oas31Selectors.selectLicenseUrl()
}

return oriSelector(...args)
}
export const selectLicenseUrl = onlyOAS31Wrap(() => (system) => {
return system.oas31Selectors.selectLicenseUrl()
})
2 changes: 2 additions & 0 deletions src/core/plugins/spec/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down

0 comments on commit df06a70

Please sign in to comment.