From 5536916f093e24c3ee8694a473ff023f04df46a0 Mon Sep 17 00:00:00 2001 From: Kael Shipman Date: Fri, 5 Mar 2021 17:47:33 -0600 Subject: [PATCH 1/3] feat: refactored operation tags for hierarchy --- src/core/components/operation-tag.jsx | 128 ++++++++++++++++++--- src/core/components/operations.jsx | 159 +++++++++++++++----------- 2 files changed, 203 insertions(+), 84 deletions(-) diff --git a/src/core/components/operation-tag.jsx b/src/core/components/operation-tag.jsx index 7eb89de7c52..34630d5b5d2 100644 --- a/src/core/components/operation-tag.jsx +++ b/src/core/components/operation-tag.jsx @@ -6,6 +6,12 @@ import { createDeepLinkPath, escapeDeepLinkPath, sanitizeUrl } from "core/utils" import { buildUrl } from "core/utils/url" import { isFunc } from "core/utils" +const SWAGGER2_OPERATION_METHODS = [ + "get", "put", "post", "delete", "options", "head", "patch" +] + +const OAS3_OPERATION_METHODS = SWAGGER2_OPERATION_METHODS.concat(["trace"]) + export default class OperationTag extends React.Component { static defaultProps = { @@ -14,8 +20,10 @@ export default class OperationTag extends React.Component { } static propTypes = { - tagObj: ImPropTypes.map.isRequired, - tag: PropTypes.string.isRequired, + tagObj: ImPropTypes.map, + tag: PropTypes.string, + childTags: ImPropTypes.map.isRequired, + isRoot: PropTypes.bool, oas3Selectors: PropTypes.func.isRequired, layoutSelectors: PropTypes.object.isRequired, @@ -25,15 +33,26 @@ export default class OperationTag extends React.Component { getComponent: PropTypes.func.isRequired, specUrl: PropTypes.string.isRequired, + } - children: PropTypes.element, + constructor(props) { + super(props); + this.render = this.render.bind(this); + this.renderChildTags = this.renderChildTags.bind(this); } render() { + // If this is the root element, just render the child tags + if (this.props.isRoot) { + return this.renderChildTags(); + } + + // Otherwise, we're rendering the individual elements, so proceed with full render + + // Get the necessary props const { tagObj, tag, - children, oas3Selectors, layoutSelectors, layoutActions, @@ -42,31 +61,33 @@ export default class OperationTag extends React.Component { specUrl, } = this.props + // Get the necessary configs let { docExpansion, deepLinking, } = getConfigs() - const isDeepLinkingEnabled = deepLinking && deepLinking !== "false" - + // Get the necessary components const Collapse = getComponent("Collapse") const Markdown = getComponent("Markdown", true) const DeepLink = getComponent("DeepLink") const Link = getComponent("Link") - let tagDescription = tagObj.getIn(["tagDetails", "description"], null) - let tagExternalDocsDescription = tagObj.getIn(["tagDetails", "externalDocs", "description"]) - let rawTagExternalDocsUrl = tagObj.getIn(["tagDetails", "externalDocs", "url"]) - let tagExternalDocsUrl - if (isFunc(oas3Selectors) && isFunc(oas3Selectors.selectedServer)) { - tagExternalDocsUrl = buildUrl( rawTagExternalDocsUrl, specUrl, { selectedServer: oas3Selectors.selectedServer() } ) - } else { - tagExternalDocsUrl = rawTagExternalDocsUrl - } + // Set up some helpers + const isDeepLinkingEnabled = deepLinking && deepLinking !== "false" - let isShownKey = ["operations-tag", tag] - let showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list") + const tagDescription = tagObj ? tagObj.getIn(["tagDetails", "description"], null) : null; + const tagExternalDocsDescription = tagObj ? tagObj.getIn(["tagDetails", "externalDocs", "description"]) : null; + const rawTagExternalDocsUrl = tagObj ? tagObj.getIn(["tagDetails", "externalDocs", "url"]) : null; + const tagExternalDocsUrl = (isFunc(oas3Selectors) && isFunc(oas3Selectors.selectedServer)) + ? buildUrl(rawTagExternalDocsUrl, specUrl, { selectedServer: oas3Selectors.selectedServer() }) + : rawTagExternalDocsUrl; + const operations = tagObj ? tagObj.get("operations") : Im.fromJS({}); + const isShownKey = ["operations-tag", tag] + const showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list") + + // Finally, render return (
@@ -116,9 +137,80 @@ export default class OperationTag extends React.Component { - {children} +
+ { + operations.map(op => { + const path = op.get("path") + 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) { + return null + } + + return + }).toArray() + } +
+ + { this.renderChildTags() }
) } + + renderChildTags() { + const { childTags } = this.props; + if (!childTags || childTags.size === 0) { + return null; + } + + const { + oas3Selectors, + layoutSelectors, + layoutActions, + getConfigs, + getComponent, + specSelectors, + isRoot, + } = this.props; + + return ( +
+ { + childTags.map((tag, tagName) => { + return + }) + } +
+ ) + } } diff --git a/src/core/components/operations.jsx b/src/core/components/operations.jsx index b50e2afb4cf..ed44199b29c 100644 --- a/src/core/components/operations.jsx +++ b/src/core/components/operations.jsx @@ -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 = { @@ -36,84 +29,118 @@ export default class Operations extends React.Component { fn } = this.props + // Get pertinent options + let { + maxDisplayedTags, + hierarchicalTags, + tagSplitterChar, + } = getConfigs(); + + // Set default tagSplitterChar if necessary + tagSplitterChar = tagSplitterChar || /[:|]/; + + // Get a flat map of tag names to tag info and operations. Note that this will always return a + // flat list, even if the `hierarchicalTags` option is set to `true`. let taggedOps = specSelectors.taggedOperations() const OperationContainer = getComponent("OperationContainer", true) const OperationTag = getComponent("OperationTag") - let { - maxDisplayedTags, - } = getConfigs() - + // Filter, if requested let filter = layoutSelectors.currentFilter() - if (filter) { if (filter !== true && filter !== "true" && filter !== "false") { taggedOps = fn.opsFilter(taggedOps, filter) } } + // Limit to [max] items, if specified if (maxDisplayedTags && !isNaN(maxDisplayedTags) && maxDisplayedTags >= 0) { taggedOps = taggedOps.slice(0, maxDisplayedTags) } - return ( -
- { - taggedOps.map( (tagObj, tag) => { - const operations = tagObj.get("operations") - return ( - - { - operations.map( op => { - const path = op.get("path") - 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) { - return null - } - - return - }).toArray() - } - - - - ) - }).toArray() + // Convert flat object to hierarchy. We're using a "raw" object for cleanliness here, but later + // we'll convert that into an immutable map. Here are the types we're dealing with: + // + // type operationTagsRaw = TagMap; + // type TagMap = { [TagName: string]: TagData }; + // type TagData = { + // canonicalName: string; + // data: TagInfoAndOperations | null; + // childTags: TagMap; + // } + // TODO: Explicitly define TagInfoAndOperations + const operationTagsRaw = {}; + if (hierarchicalTags) { + // If the `hierarchicalTags` option is set, we want to break down the tags into a deep + // hierarchy + + // For each raw tag.... + for (const tagName in taggedOps) { + // Split the raw tag name into parts + const parts = tagName.split(tagSplitterChar); + + // Set a pointer for use in traversing the hierarchy + let current = operationTagsRaw; + + // Iterate through the parts defined by this tag + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + + // If there's no object defined for the current part, define one with just childTags as an + // empty set + if (current[part] === undefined) { + // Compose canonical name from parts up to this point + const canonicalName = parts.reduce( + (name, p, j) => ((j > i) ? name : name.concat([p])), + [] + ).join("|"); + current[part] = { + canonicalName, + data: null, + childTags: {} + } } - { taggedOps.size < 1 ?

No operations defined in spec!

: null } -
- ) - } + // If this is the last part, set data on this object + if (i === parts.length - 1) { + current[part].data = taggedOps.get(tagName); + } + // Move to the next level of the hierarchy before looping around + current = current[part].childTags; + } + } + } else { + // If the `hierarchicalTags` option is not set, we just want to convert our flat tag map into + // the right format + for (const tagName in taggedOps) { + operationTagsRaw[tagName] = { + canonicalName: tagName, + data: taggedOps.get(tagName), + childTags: {} + } + } + } + + // Convert to immutable map + const operationTags = Im.fromJs(operationTagsRaw); + + // Return the render + return operationTags.size === 0 + ?

No operations defined in spec!

+ : + + } } Operations.propTypes = { From 657a9698df361abe67d1456a03bac55b59fc1498 Mon Sep 17 00:00:00 2001 From: Kael Shipman Date: Fri, 5 Mar 2021 19:26:08 -0600 Subject: [PATCH 2/3] fix: cleanup and bugfixes for recent work --- src/core/components/operation-tag.jsx | 8 +++++--- src/core/components/operations.jsx | 15 +++++++-------- src/core/index.js | 2 ++ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/core/components/operation-tag.jsx b/src/core/components/operation-tag.jsx index 34630d5b5d2..b01dc3df9a8 100644 --- a/src/core/components/operation-tag.jsx +++ b/src/core/components/operation-tag.jsx @@ -58,6 +58,7 @@ export default class OperationTag extends React.Component { layoutActions, getConfigs, getComponent, + specSelectors, specUrl, } = this.props @@ -68,6 +69,7 @@ export default class OperationTag extends React.Component { } = getConfigs() // Get the necessary components + const OperationContainer = getComponent("OperationContainer", true) const Collapse = getComponent("Collapse") const Markdown = getComponent("Markdown", true) const DeepLink = getComponent("DeepLink") @@ -137,7 +139,7 @@ export default class OperationTag extends React.Component { -
+
{ operations.map(op => { const path = op.get("path") @@ -195,7 +197,7 @@ export default class OperationTag extends React.Component {
{ childTags.map((tag, tagName) => { - return - }) + }).toArray() }
) diff --git a/src/core/components/operations.jsx b/src/core/components/operations.jsx index ed44199b29c..86327e1092e 100644 --- a/src/core/components/operations.jsx +++ b/src/core/components/operations.jsx @@ -43,7 +43,6 @@ export default class Operations extends React.Component { // flat list, even if the `hierarchicalTags` option is set to `true`. let taggedOps = specSelectors.taggedOperations() - const OperationContainer = getComponent("OperationContainer", true) const OperationTag = getComponent("OperationTag") // Filter, if requested @@ -76,7 +75,7 @@ export default class Operations extends React.Component { // hierarchy // For each raw tag.... - for (const tagName in taggedOps) { + taggedOps.map((tagObj, tagName) => { // Split the raw tag name into parts const parts = tagName.split(tagSplitterChar); @@ -104,27 +103,27 @@ export default class Operations extends React.Component { // If this is the last part, set data on this object if (i === parts.length - 1) { - current[part].data = taggedOps.get(tagName); + current[part].data = tagObj; } // Move to the next level of the hierarchy before looping around current = current[part].childTags; } - } + }); } else { // If the `hierarchicalTags` option is not set, we just want to convert our flat tag map into // the right format - for (const tagName in taggedOps) { + taggedOps.map((tagObj, tagName) => { operationTagsRaw[tagName] = { canonicalName: tagName, - data: taggedOps.get(tagName), + data: tagObj, childTags: {} } - } + }); } // Convert to immutable map - const operationTags = Im.fromJs(operationTagsRaw); + const operationTags = Im.fromJS(operationTagsRaw); // Return the render return operationTags.size === 0 diff --git a/src/core/index.js b/src/core/index.js index 72a32d3ff03..22b4dbe338d 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -53,6 +53,8 @@ export default function SwaggerUI(opts) { showExtensions: false, showCommonExtensions: false, withCredentials: undefined, + hierarchicalTags: false, + tagSplitterChar: /[:|]/, supportedSubmitMethods: [ "get", "put", From b122a7002124d3b8b5f601ce87415a5799dbc6ab Mon Sep 17 00:00:00 2001 From: Kael Shipman Date: Sat, 6 Mar 2021 13:20:36 -0600 Subject: [PATCH 3/3] fix: made changes backward-compatible --- .../components/hierarchical-operation-tag.jsx | 218 ++++++++++++++++++ src/core/components/operation-tag.jsx | 130 ++--------- src/core/components/operations.jsx | 136 +++++++---- src/core/presets/base.js | 2 + 4 files changed, 332 insertions(+), 154 deletions(-) create mode 100644 src/core/components/hierarchical-operation-tag.jsx diff --git a/src/core/components/hierarchical-operation-tag.jsx b/src/core/components/hierarchical-operation-tag.jsx new file mode 100644 index 00000000000..0de093b63e6 --- /dev/null +++ b/src/core/components/hierarchical-operation-tag.jsx @@ -0,0 +1,218 @@ +import React from "react" +import PropTypes from "prop-types" +import ImPropTypes from "react-immutable-proptypes" +import Im from "immutable" +import { createDeepLinkPath, escapeDeepLinkPath, sanitizeUrl } from "core/utils" +import { buildUrl } from "core/utils/url" +import { isFunc } from "core/utils" + +const SWAGGER2_OPERATION_METHODS = [ + "get", "put", "post", "delete", "options", "head", "patch" +] + +const OAS3_OPERATION_METHODS = SWAGGER2_OPERATION_METHODS.concat(["trace"]) + +export default class HierarchicalOperationTag extends React.Component { + + static defaultProps = { + tagObj: Im.fromJS({}), + tag: "", + } + + static propTypes = { + tagObj: ImPropTypes.map, + tag: PropTypes.string, + childTags: ImPropTypes.map.isRequired, + isRoot: PropTypes.bool, + + oas3Selectors: PropTypes.func.isRequired, + layoutSelectors: PropTypes.object.isRequired, + layoutActions: PropTypes.object.isRequired, + + getConfigs: PropTypes.func.isRequired, + getComponent: PropTypes.func.isRequired, + + specUrl: PropTypes.string.isRequired, + } + + constructor(props) { + super(props); + this.render = this.render.bind(this); + this.renderChildTags = this.renderChildTags.bind(this); + } + + render() { + // If this is the root element, just render the child tags + if (this.props.isRoot) { + return this.renderChildTags(); + } + + // Otherwise, we're rendering the individual elements, so proceed with full render + + // Get the necessary props + const { + tagObj, + tag, + oas3Selectors, + layoutSelectors, + layoutActions, + getConfigs, + getComponent, + specSelectors, + specUrl, + } = this.props + + // Get the necessary configs + let { + docExpansion, + deepLinking, + } = getConfigs() + + // Get the necessary components + const OperationContainer = getComponent("OperationContainer", true) + const Collapse = getComponent("Collapse") + const Markdown = getComponent("Markdown", true) + const DeepLink = getComponent("DeepLink") + const Link = getComponent("Link") + + // Set up some helpers + const isDeepLinkingEnabled = deepLinking && deepLinking !== "false" + + const tagDescription = tagObj ? tagObj.getIn(["tagDetails", "description"], null) : null; + const tagExternalDocsDescription = tagObj ? tagObj.getIn(["tagDetails", "externalDocs", "description"]) : null; + const rawTagExternalDocsUrl = tagObj ? tagObj.getIn(["tagDetails", "externalDocs", "url"]) : null; + const tagExternalDocsUrl = (isFunc(oas3Selectors) && isFunc(oas3Selectors.selectedServer)) + ? buildUrl(rawTagExternalDocsUrl, specUrl, { selectedServer: oas3Selectors.selectedServer() }) + : rawTagExternalDocsUrl; + const operations = tagObj ? tagObj.get("operations") : Im.fromJS({}); + + const isShownKey = ["operations-tag", tag] + const showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list") + + // Finally, render + return ( +
+ +

layoutActions.show(isShownKey, !showTag)} + className={!tagDescription ? "opblock-tag no-desc" : "opblock-tag" } + id={isShownKey.map(v => escapeDeepLinkPath(v)).join("-")} + data-tag={tag} + data-is-open={showTag} + > + + { !tagDescription ? : + + + + } + +
+ { !tagExternalDocsDescription ? null : + + { tagExternalDocsDescription } + { tagExternalDocsUrl ? ": " : null } + { tagExternalDocsUrl ? + e.stopPropagation()} + target="_blank" + >{tagExternalDocsUrl} : null + } + + } +
+ + +

+ + +
+ { + operations.map(op => { + const path = op.get("path") + 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) { + return null + } + + return + }).toArray() + } +
+ + { this.renderChildTags() } +
+
+ ) + } + + renderChildTags() { + const { childTags } = this.props; + if (!childTags || childTags.size === 0) { + return null; + } + + const { + oas3Selectors, + layoutSelectors, + layoutActions, + getConfigs, + getComponent, + specSelectors, + isRoot, + } = this.props; + + return ( +
+ { + childTags.map((tag, tagName) => { + return + }).toArray() + } +
+ ) + } +} diff --git a/src/core/components/operation-tag.jsx b/src/core/components/operation-tag.jsx index b01dc3df9a8..7eb89de7c52 100644 --- a/src/core/components/operation-tag.jsx +++ b/src/core/components/operation-tag.jsx @@ -6,12 +6,6 @@ import { createDeepLinkPath, escapeDeepLinkPath, sanitizeUrl } from "core/utils" import { buildUrl } from "core/utils/url" import { isFunc } from "core/utils" -const SWAGGER2_OPERATION_METHODS = [ - "get", "put", "post", "delete", "options", "head", "patch" -] - -const OAS3_OPERATION_METHODS = SWAGGER2_OPERATION_METHODS.concat(["trace"]) - export default class OperationTag extends React.Component { static defaultProps = { @@ -20,10 +14,8 @@ export default class OperationTag extends React.Component { } static propTypes = { - tagObj: ImPropTypes.map, - tag: PropTypes.string, - childTags: ImPropTypes.map.isRequired, - isRoot: PropTypes.bool, + tagObj: ImPropTypes.map.isRequired, + tag: PropTypes.string.isRequired, oas3Selectors: PropTypes.func.isRequired, layoutSelectors: PropTypes.object.isRequired, @@ -33,63 +25,48 @@ export default class OperationTag extends React.Component { getComponent: PropTypes.func.isRequired, specUrl: PropTypes.string.isRequired, - } - constructor(props) { - super(props); - this.render = this.render.bind(this); - this.renderChildTags = this.renderChildTags.bind(this); + children: PropTypes.element, } render() { - // If this is the root element, just render the child tags - if (this.props.isRoot) { - return this.renderChildTags(); - } - - // Otherwise, we're rendering the individual elements, so proceed with full render - - // Get the necessary props const { tagObj, tag, + children, oas3Selectors, layoutSelectors, layoutActions, getConfigs, getComponent, - specSelectors, specUrl, } = this.props - // Get the necessary configs let { docExpansion, deepLinking, } = getConfigs() - // Get the necessary components - const OperationContainer = getComponent("OperationContainer", true) + const isDeepLinkingEnabled = deepLinking && deepLinking !== "false" + const Collapse = getComponent("Collapse") const Markdown = getComponent("Markdown", true) const DeepLink = getComponent("DeepLink") const Link = getComponent("Link") - // Set up some helpers - const isDeepLinkingEnabled = deepLinking && deepLinking !== "false" - - const tagDescription = tagObj ? tagObj.getIn(["tagDetails", "description"], null) : null; - const tagExternalDocsDescription = tagObj ? tagObj.getIn(["tagDetails", "externalDocs", "description"]) : null; - const rawTagExternalDocsUrl = tagObj ? tagObj.getIn(["tagDetails", "externalDocs", "url"]) : null; - const tagExternalDocsUrl = (isFunc(oas3Selectors) && isFunc(oas3Selectors.selectedServer)) - ? buildUrl(rawTagExternalDocsUrl, specUrl, { selectedServer: oas3Selectors.selectedServer() }) - : rawTagExternalDocsUrl; - const operations = tagObj ? tagObj.get("operations") : Im.fromJS({}); + let tagDescription = tagObj.getIn(["tagDetails", "description"], null) + let tagExternalDocsDescription = tagObj.getIn(["tagDetails", "externalDocs", "description"]) + let rawTagExternalDocsUrl = tagObj.getIn(["tagDetails", "externalDocs", "url"]) + let tagExternalDocsUrl + if (isFunc(oas3Selectors) && isFunc(oas3Selectors.selectedServer)) { + tagExternalDocsUrl = buildUrl( rawTagExternalDocsUrl, specUrl, { selectedServer: oas3Selectors.selectedServer() } ) + } else { + tagExternalDocsUrl = rawTagExternalDocsUrl + } - const isShownKey = ["operations-tag", tag] - const showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list") + let isShownKey = ["operations-tag", tag] + let showTag = layoutSelectors.isShown(isShownKey, docExpansion === "full" || docExpansion === "list") - // Finally, render return (
@@ -139,80 +116,9 @@ export default class OperationTag extends React.Component { -
- { - operations.map(op => { - const path = op.get("path") - 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) { - return null - } - - return - }).toArray() - } -
- - { this.renderChildTags() } + {children}
) } - - renderChildTags() { - const { childTags } = this.props; - if (!childTags || childTags.size === 0) { - return null; - } - - const { - oas3Selectors, - layoutSelectors, - layoutActions, - getConfigs, - getComponent, - specSelectors, - isRoot, - } = this.props; - - return ( -
- { - childTags.map((tag, tagName) => { - return - }).toArray() - } -
- ) - } } diff --git a/src/core/components/operations.jsx b/src/core/components/operations.jsx index 86327e1092e..8f32b9694f5 100644 --- a/src/core/components/operations.jsx +++ b/src/core/components/operations.jsx @@ -2,6 +2,12 @@ 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 = { @@ -43,8 +49,6 @@ export default class Operations extends React.Component { // flat list, even if the `hierarchicalTags` option is set to `true`. let taggedOps = specSelectors.taggedOperations() - const OperationTag = getComponent("OperationTag") - // Filter, if requested let filter = layoutSelectors.currentFilter() if (filter) { @@ -58,21 +62,22 @@ export default class Operations extends React.Component { taggedOps = taggedOps.slice(0, maxDisplayedTags) } - // Convert flat object to hierarchy. We're using a "raw" object for cleanliness here, but later - // we'll convert that into an immutable map. Here are the types we're dealing with: - // - // type operationTagsRaw = TagMap; - // type TagMap = { [TagName: string]: TagData }; - // type TagData = { - // canonicalName: string; - // data: TagInfoAndOperations | null; - // childTags: TagMap; - // } - // TODO: Explicitly define TagInfoAndOperations - const operationTagsRaw = {}; + // Render either hierarchical or flat depending on config if (hierarchicalTags) { // If the `hierarchicalTags` option is set, we want to break down the tags into a deep - // hierarchy + // hierarchy. We're using a "raw" object for cleanliness here, but later we'll convert that + // into an immutable map. Here are the types we're dealing with: + // + // const operationTagsRaw: TagMap; + // type TagMap = { [TagName: string]: TagData }; + // type TagData = { + // canonicalName: string; + // data: TagInfoAndOperations | null; + // childTags: TagMap; + // } + // TODO: Explicitly define TagInfoAndOperations + + const operationTagsRaw = {}; // For each raw tag.... taggedOps.map((tagObj, tagName) => { @@ -110,35 +115,82 @@ export default class Operations extends React.Component { current = current[part].childTags; } }); + + // Convert to immutable map + const operationTags = Im.fromJS(operationTagsRaw); + const HierarchicalOperationTag = getComponent("HierarchicalOperationTag") + return operationTags.size === 0 + ?

No operations defined in spec!

+ : + } else { - // If the `hierarchicalTags` option is not set, we just want to convert our flat tag map into - // the right format - taggedOps.map((tagObj, tagName) => { - operationTagsRaw[tagName] = { - canonicalName: tagName, - data: tagObj, - childTags: {} - } - }); - } + const OperationContainer = getComponent("OperationContainer", true) + const OperationTag = getComponent("OperationTag") + return ( +
+ { + taggedOps.map( (tagObj, tag) => { + const operations = tagObj.get("operations") + return ( + + { + operations.map( op => { + const path = op.get("path") + 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) { + return null + } + + return + }).toArray() + } + + + + ) + }).toArray() + } - // Convert to immutable map - const operationTags = Im.fromJS(operationTagsRaw); - - // Return the render - return operationTags.size === 0 - ?

No operations defined in spec!

- : - + { taggedOps.size < 1 ?

No operations defined in spec!

: null } +
+ ) + } } } diff --git a/src/core/presets/base.js b/src/core/presets/base.js index 73565cdf7cd..6f131ef8da5 100644 --- a/src/core/presets/base.js +++ b/src/core/presets/base.js @@ -33,6 +33,7 @@ import Clear from "core/components/clear" import LiveResponse from "core/components/live-response" import OnlineValidatorBadge from "core/components/online-validator-badge" import Operations from "core/components/operations" +import HierarchicalOperationTag from "core/components/hierarchical-operation-tag" import OperationTag from "core/components/operation-tag" import Operation from "core/components/operation" import OperationSummary from "core/components/operation-summary" @@ -154,6 +155,7 @@ export default function() { OperationExtRow, ParameterExt, ParameterIncludeEmpty, + HierarchicalOperationTag, OperationTag, OperationContainer, DeepLink,