Vulnerability in EPiServer.Forms

Try our conversational search powered by Generative AI!

Kane Made It
Nov 17, 2020
  3350
(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
A day in the life of an Optimizely Developer - Optimizely CMS 12: The advantages and considerations when exploring an upgrade

GRAHAM CARR - LEAD .NET DEVELOPER, 28 Nov 2023 In 2022, Optimizely released CMS 12 as part of its ongoing evolution of the platform to help provide...

Graham Carr | Nov 28, 2023

A day in the life of an Optimizely Developer - OptiUKNorth Meetup January 2024

It's time for another UK North Optimizely meet up! After the success of the last one, Ibrar Hussain (26) and Paul Gruffydd (Kin + Carta) will be...

Graham Carr | Nov 28, 2023

Publish content to Optimizely CMS using a custom GPT from OpenAI 🤖

Do you find the traditional editor interface complicated and cluttered? Would you like an editorial AI assistant you can chat with? You can!

Tomas Hensrud Gulla | Nov 28, 2023 | Syndicated blog

Optimizely Graph and Next.js: Building Scalable Headless Solutions

Optimizely Graph harnesses the capabilities of GraphQL, an intuitive and efficient query language to, transform content within an Optimizely CMS in...

Szymon Uryga | Nov 27, 2023

Getting Started with Optimizely SaaS Core and Next.js Integration: Testing Content Updates

The blog post discusses the challenges of content updates on a website using Optimizely CMS, Next.js, and the Apollo Client due to Apollo's local...

Francisco Quintanilla | Nov 27, 2023 | Syndicated blog

Performance optimization – the hardcore series – part 4

Let’s take a break from the memory allocation, and do some optimization on another aspect, yet as important (if not even more important) – database...

Quan Mai | Nov 25, 2023 | Syndicated blog