From 0482a8c9ba2531719c3d66487d587f0aefdf27e0 Mon Sep 17 00:00:00 2001 From: PratikMane0112 Date: Mon, 7 Apr 2025 20:05:27 +0530 Subject: [PATCH] Add support for expandable OpenAPI.webhooks grouped by tags --- .../plugins/oas31/components/webhooks.jsx | 73 ++++++++++++----- src/core/plugins/oas31/index.js | 4 + .../oas31/spec-extensions/selectors.js | 81 ++++++++++++++++++- 3 files changed, 135 insertions(+), 23 deletions(-) diff --git a/src/core/plugins/oas31/components/webhooks.jsx b/src/core/plugins/oas31/components/webhooks.jsx index 49ce6b2f338..193f069b4c7 100644 --- a/src/core/plugins/oas31/components/webhooks.jsx +++ b/src/core/plugins/oas31/components/webhooks.jsx @@ -3,44 +3,73 @@ */ import React from "react" import PropTypes from "prop-types" -import { List } from "immutable" +import { List, Map } from "immutable" -const Webhooks = ({ specSelectors, getComponent }) => { - const operationDTOs = specSelectors.selectWebhooksOperations() - const pathItemNames = Object.keys(operationDTOs) +const Webhooks = ({ specSelectors, getComponent, layoutSelectors, layoutActions, getConfigs, oas3Selectors }) => { + const taggedWebhooks = specSelectors.taggedWebhooks() - const OperationContainer = getComponent("OperationContainer", true) + if (taggedWebhooks.size === 0) return null - if (pathItemNames.length === 0) return null + const OperationTag = getComponent("OperationTag") + const OperationContainer = getComponent("OperationContainer", true) return (

Webhooks

- {pathItemNames.map((pathItemName) => ( -
- {operationDTOs[pathItemName].map((operationDTO) => ( - - ))} -
- ))} + {taggedWebhooks.map((tagObj, tag) => { + const operations = tagObj.get("operations") + + return ( + +
+ { + operations.map(op => { + const path = op.get("path") + const method = op.get("method") + const operation = op.get("operation") + const specPath = List(["webhooks", path, method]) + + return ( + + ) + }).toArray() + } +
+
+ ) + }).toArray()}
) } Webhooks.propTypes = { specSelectors: PropTypes.shape({ - selectWebhooksOperations: PropTypes.func.isRequired, + taggedWebhooks: PropTypes.func.isRequired, + url: PropTypes.func.isRequired, }).isRequired, getComponent: PropTypes.func.isRequired, + layoutSelectors: PropTypes.object.isRequired, + layoutActions: PropTypes.object.isRequired, + getConfigs: PropTypes.func.isRequired, + oas3Selectors: PropTypes.func.isRequired } export default Webhooks diff --git a/src/core/plugins/oas31/index.js b/src/core/plugins/oas31/index.js index 739a04fe6f1..bbb43be0148 100644 --- a/src/core/plugins/oas31/index.js +++ b/src/core/plugins/oas31/index.js @@ -28,6 +28,8 @@ import { license as selectLicense, contact as selectContact, webhooks as selectWebhooks, + webhooksWithTags as selectWebhooksWithTags, + taggedWebhooks as selectTaggedWebhooks, selectLicenseNameField, selectLicenseUrlField, selectLicenseIdentifierField, @@ -143,6 +145,8 @@ const OAS31Plugin = ({ fn }) => { webhooks: createOnlyOAS31Selector(selectWebhooks), selectWebhooksOperations: createOnlyOAS31Selector(createSystemSelector(selectWebhooksOperations)), // prettier-ignore + webhooksWithTags: createOnlyOAS31Selector(createSystemSelector(selectWebhooksWithTags)), // prettier-ignore + taggedWebhooks: createOnlyOAS31Selector(createSystemSelector(selectTaggedWebhooks)), // prettier-ignore selectJsonSchemaDialectField, selectJsonSchemaDialectDefault, diff --git a/src/core/plugins/oas31/spec-extensions/selectors.js b/src/core/plugins/oas31/spec-extensions/selectors.js index 0ed33135c78..bdcc508b638 100644 --- a/src/core/plugins/oas31/spec-extensions/selectors.js +++ b/src/core/plugins/oas31/spec-extensions/selectors.js @@ -1,13 +1,15 @@ /** * @prettier */ -import { List, Map } from "immutable" +import { List, Map, Set, OrderedMap } from "immutable" import { createSelector } from "reselect" import { safeBuildUrl } from "core/utils/url" import { isOAS31 as isOAS31Fn } from "../fn" +import { sorters } from "core/utils" const map = Map() +const DEFAULT_TAG = "default" export const isOAS31 = createSelector( (state, system) => system.specSelectors.specJson(), @@ -52,6 +54,83 @@ export const selectWebhooksOperations = createSelector( .toObject() ) +/** + * Selectors for grouping webhooks by tags + */ +export const webhooksWithTags = createSelector( + [ + (state, system) => system.specSelectors.webhooks(), + (state, system) => system.specSelectors.validOperationMethods(), + (state, system) => system.specSelectors.specResolvedSubtree(["webhooks"]), + (state, system) => system.specSelectors.tags(), + ], + (webhooks, validOperationMethods, resolvedWebhooks, tags) => { + return webhooks + .reduce((taggedMap, pathItem, pathItemName) => { + if (!Map.isMap(pathItem)) return taggedMap + + const pathItemOperations = pathItem + .entrySeq() + .filter(([key]) => validOperationMethods.includes(key)) + .map(([method, operation]) => { + return Map({ + operation, + method, + path: pathItemName, + specPath: ["webhooks", pathItemName, method], + operationTags: Set(operation.get("tags", List())) + }) + }) + + return pathItemOperations.reduce((acc, operation) => { + const operationTags = operation.get("operationTags") + + if (operationTags.count() < 1) { + return acc.update(DEFAULT_TAG, List(), ar => ar.push(operation)) + } + + return operationTags.reduce( + (res, tag) => res.update(tag, List(), ar => ar.push(operation)), + acc + ) + }, taggedMap) + }, tags.reduce((taggedMap, tag) => { + return taggedMap.set(tag.get("name"), List()) + }, OrderedMap())) + } +) + +export const taggedWebhooks = createSelector( + [ + (state, system) => system.specSelectors.webhooksWithTags(state), + (state, system) => system.specSelectors.tags(), + (state, system) => system.specSelectors.tagDetails, + ], + (webhooksWithTags, tags, tagDetailsSelector) => ({ getConfigs }) => { + let { tagsSorter, operationsSorter } = getConfigs() + return webhooksWithTags + .sortBy( + (val, key) => key, // get the name of the tag to be passed to the sorter + (tagA, tagB) => { + let sortFn = (typeof tagsSorter === "function" ? tagsSorter : sorters.tagsSorter[tagsSorter]) + return (!sortFn ? null : sortFn(tagA, tagB)) + } + ) + .map((ops, tag) => { + let sortFn = (typeof operationsSorter === "function" ? operationsSorter : sorters.operationsSorter[operationsSorter]) + let operations = (!sortFn ? ops : ops.sort(sortFn)) + + // Find the tag details in the tags array + const tagDetail = tags.find(t => t.get("name") === tag) + + return Map({ + tagDetails: tagDetail || null, + operations: operations + }) + }) + } +) + export const license = () => (system) => { const license = system.specSelectors.info().get("license") return Map.isMap(license) ? license : map