Try our conversational search powered by Generative AI!

Kane Made It
Nov 17, 2020
  3679
(4 votes)

Integrating Swagger UI on Episerver World with customized components

Documentation section stays for a long time on World, and a new Swagger UI panel has been implemented (available soon), for better usage a long with old Swagger UI pages:

Swagger.IO provided developer with source code and detailed documentation on setting up developing environment, creating custom plugins and Layout. Therefore, investigating and modifying components is fun.

to set up developing environment, simply open powershell/command prompts at parent directory and run command npm run dev, npm will auto rebuilt after each change on source. I personally use Visual studio code for development.

Swagger UI provided with documentation here to support user uses custom layout but material on deeper interaction with the components seems missing. As a newbie to React and Swagger, I tried several methods including duplicate components from core and import it directly to the layout.jsx file within the Standalone layout folder. It seemed to work with several components when some doesn't, as the component has it own initial setup behind the scene. To setup component with initialization we must import those components in layout.jsx and pass it as parameter in return section, which is obstructive.

For secondary approach, I came up with an idea of creating a custom preset called WorldPreset and imported to apis.js

import BasePreset from "./base"
import WorldPreset from "./world"
import OAS3Plugin from "../plugins/oas3"

// Just the base, for now.

export default function PresetApis() {

  return [
    BasePreset,
    WorldPreset,
    OAS3Plugin
  ]
}

Overriding components are declared in world.js file:

import Parameters from "../epiworld/parameters"
import ParameterRow from "../epiworld/parameter-row"
import Responses from "../epiworld/responses"
import Response from "../epiworld/response"
import ModelExample from "../epiworld/model-example"
import Operations from "../epiworld/operations"
import OperationTag from "../epiworld/operation-tag"

export default function() {

  let coreComponents = {
    components: {
      parameters: Parameters,
      parameterRow: ParameterRow,
      responses: Responses,
      response: Response,
      modelExample: ModelExample,
      operations: Operations,
      OperationTag
    }
  }

  return [
    coreComponents
  ]
}

With this method only particular components will be overriden, whereas the rest remains the same. Custom components are placed under the folder "epiworldat the same level of other "core" one. This is to ensure the import command of original source code run well without any modification of directory. For example, customized component parameter-row.jsx import getParameterSchema from "../../helpers/get-parameter-schema.js" runs out-of-the-box.

import React, { Component } from "react"
import { Map, List } from "immutable"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import win from "core/window"
import { getSampleSchema, getExtensions, getCommonExtensions, numberToString, stringify, isEmptyValue } from "core/utils"
import getParameterSchema from "../../helpers/get-parameter-schema.js"

export default class ParameterRow extends Component {
...

However, this method also affected the core bundle. You can see Swagger UI separates code in to 2 files:

<script src="http://localhost:3200/swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="http://localhost:3200/swagger-ui-standalone-preset.js" charset="UTF-8"> </script>

In case you use swagger-ui in different locations and purposes, I recommend pack all you custom code in a swagger plugin instead, it will be built to swagger-ui-standalone-preset.js

So that, I mimicked the TopBar plugin by create a new one. It works nicely, and you can override the default comparison method the same as above:

import StandaloneLayout from "./layout"
// Our custom plugin declaration
import CustomPlugin from "plugins/custom"
import ConfigsPlugin from "corePlugins/configs"

// the Standalone preset

export default [
  CustomPlugin,
  ConfigsPlugin,
  () => {
    return {
      components: { StandaloneLayout }
    }
  }
]

File: /src/standalone/index.js

The components' directories need to be modified as we are on the different location now:

import Parameters from "../../core/epiworld/parameters"
import ParameterRow from "../../core/epiworld/parameter-row"
import Responses from "../../core/epiworld/responses"
import Response from "../../core/epiworld/response"
import ModelExample from "../../core/epiworld/model-example"
import Operations from "../../core/epiworld/operations"
import OperationTag from "../../core/epiworld/operation-tag"

export default function() {
  // Only overriding components declared here
  let coreComponents = {
    components: {
      parameters: Parameters,
      parameterRow: ParameterRow,
      responses: Responses,
      response: Response,
      modelExample: ModelExample,
      operations: Operations,
      OperationTag
    },
    // override default filter function
    fn: {
      opsFilter: (taggedOps, phrase) => {
        const phrases = phrase.split(", ")
        return taggedOps.filter((val, key) => {
          return phrases.some(item => key == item)
        })
      }
    }
  }

  return [
    coreComponents
  ]
}

About the custom opsFilter: We would like to override the default filter as it's the LIKE comparision instead of EQUAL one.

By default, we use Standalone Layout without BaseLayout Component, so we would like to use Operations component. This can be done by getComponent function:

import React from "react"
import PropTypes from "prop-types"

export default class StandaloneLayout extends React.Component {

  static propTypes = {
    errSelectors: PropTypes.object.isRequired,
    errActions: PropTypes.object.isRequired,
    specActions: PropTypes.object.isRequired,
    specSelectors: PropTypes.object.isRequired,
    layoutSelectors: PropTypes.object.isRequired,
    layoutActions: PropTypes.object.isRequired,
    getComponent: PropTypes.func.isRequired
  }

  render() {
    let { getComponent } = this.props
    let Operations = getComponent("operations", true)

    return (
      <Operations />
    )
  }

}

The second parameter (a boolean with value equal true) of the function stated that we don't have to penetrade it with parameters (this is mentioned in the instruction file).

With the help of custom components, we can duplicate components from original and put it under "src/core/components" folder. Modification works are done here. Sometimes it's complicated when alternating current layout - by changing position of something as they belong to another one. It means that we must modify current behavior and javascript handler. Rendered DOMs are also an obstacle for styling with responsive issues, peculiarly when you reuse default stylesheet of swagger UI for table display.

During the development process, the address runs at address localhost:3200, I tried to link direct script but it seems not to work, I added CORS header but no error shows on Console.

To resolve this problem, I create a batch command to build and copy built files to my project's location:

call npm run build
call copy C:\code\swagger-ui\dist\swagger-ui-standalone-preset.js C:\code\DevOps\src\Scripts\Custom\SwaggerPanel\
call copy C:\code\swagger-ui\dist\swagger-ui-standalone-preset.js.map C:\code\DevOps\src\Scripts\Custom\SwaggerPanel\
call copy C:\code\swagger-ui\dist\swagger-ui-bundle.js C:\code\DevOps\src\Scripts\Custom\SwaggerPanel\
call copy C:\code\swagger-ui\dist\swagger-ui-bundle.js.map C:\code\DevOps\src\Scripts\Custom\SwaggerPanel\

Call this script by typing in powershell:

./doit.bat

That's it for the modification works, I hope it helps when you have to deal with Swagger UI. The source code was pushed to my personal repository so in case you would like to find a reference. 

Peace out.

Nov 17, 2020

Comments

Antti Alasvuo
Antti Alasvuo Nov 17, 2020 11:16 AM

Nice! Pst, there is a typo "WolrdPreset" in the apis.js code snip on this page.

Kane Made It
Kane Made It Nov 18, 2020 01:47 AM

Thanks Antti, it's fixed :).

Please login to comment.
Latest blogs
New Series: Building a .NET Core headless site on Optimizely Graph and SaaS CMS

Welcome to this new multi-post series where you can follow along as I indulge in yet another crazy experiment: Can we make our beloved Alloy site r...

Allan Thraen | Jun 14, 2024 | Syndicated blog

Inspect In Index is finally back

EPiCode.InspectInIndex was released 9 years ago . The Search and Navigation addon is now finally upgraded to support Optimizely CMS 12....

Haakon Peder Haugsten | Jun 14, 2024

Change the IP HTTP Header used for geo-lookup in Application Insights

.

Johan Kronberg | Jun 10, 2024 | Syndicated blog

Copying property values

In this article I’d like to show simple Edit Mode extension for copying property values to other language versions. In one of my previous blogposts...

Grzegorz Wiecheć | Jun 8, 2024 | Syndicated blog

Auto-translate with OpenAI GPT-4o in Optimizely CMS

Improvements for Episerver.Labs.LanguageManager! It's now possible to auto-translate both a page and its children at the same time! Additionally, m...

Tomas Hensrud Gulla | Jun 7, 2024 | Syndicated blog

Upgrade To Optimizely CMS 12 Issue: List item fields have become Required

There are many funny details to be aware of when upgrading from Episerver CMS 11 to Optimizely CMS 12. One of them that might feel a bit confusing ...

Allan Thraen | Jun 7, 2024 | Syndicated blog