Kane Made It
Nov 17, 2020
  3869
(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
keep special characters in URL

When creating a page, the default URL segment validation automatically replaces special characters with their standard equivalents (e.g., "ä" is...

K Khan | Sep 19, 2024

Streamlining Marketing Success: The Benefits for Optimizely One with Perficient

As an Optimizely expert, I eagerly anticipate this time of year due to the exciting Optimizely events happening worldwide. These include Opticon, t...

Alex Harris - Perficient | Sep 17, 2024 | Syndicated blog

Creating an Optimizely Addon - Packaging for NuGet

In   Part One   and   Part Two   of this series; I covered topics from having a great idea, solution structure, extending the menus and adding...

Mark Stott | Sep 16, 2024

Optimizely CMS and weekly updates

Learn how reporting bugs in Optimizely CMS not only helps improve the platform but also benefits you and the entire user community.

Tomas Hensrud Gulla | Sep 12, 2024 | Syndicated blog

Introduce the ablility to select then delete items manually on FIND UI

In FIND 16.3.0 we introduce an ability to select items and delete them manually, it will helps you to delete unexpected items from the UI without a...

Manh Nguyen | Sep 12, 2024

The composable consulting model our industry needs

The architecture of a modern consulting business is ‘composable’. Certainly, we think of ourselves a composable consulting business and have done...

Mark Everard | Sep 12, 2024 | Syndicated blog