swagger-api / swagger-ui

Swagger UI is a collection of HTML, JavaScript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.
https://swagger.io
Apache License 2.0
26.51k stars 8.96k forks source link

Feature: whole word filter option #6648

Open MastersoftGroup opened 3 years ago

MastersoftGroup commented 3 years ago

Q&A (please complete the following information)

Content & configuration

Using SwaggerUIBundle with filter, which is using tags. I've noticed if I have a tag that the value contains in other tags (case-sensitive), e.g. tag "street" and tag "streetNumber", when I use filter: "street", it will display all operations for "street" tag as well as "streetNumber" tag. I want to only display operations for "street" tag.

Currently my workaround is adding an extra space: "street " tag, but this is not ideal because that means everytime we update or add new tags, we need to check all other tags to make sure no tag value contains other tag value.

To reproduce...

Steps to reproduce the behavior:

  1. Go to Rest Controller, create 2 methods, and add below @ApiOperation with tags on method1 and method2:
    for method1: @ApiOperation(value = "Display street", tags = "street")
    for method2: @ApiOperation(value = "Display street number", tags = "streetNumber")
  2. Get swagger-ui-dist and add SwaggerUIBundle with filter: "street" to a html page
  3. Scroll down to where you add to display the operations
  4. You will see both method1 ("street" tag) and method2 ("streetNumber") tag are displayed

Expected behavior

My expectation is only operations for that exact tag value will be displayed, e.g. using the above example, only method1 should be displayed, not both method1 and method2.

tim-lai commented 3 years ago

Hi, thanks for creating a ticket. The feature you are looking for is a whole word match. I'm updating the ticket title to reflect this. That said, we're open to any and all PR feature requests. ;)

mathis-m commented 3 years ago

@tim-lai @MastersoftGroup Just implemented the feature have a look at the preview.

rcollette commented 1 year ago

The following is my implementation of an exact match filter. Having accomplished this I have a couple observations about what makes this more difficult than is otherwise apparent when looking at the solution.

  1. The documentation for plugins doesn't show the specification of plugins within the context of the configuration object. Hence I am showing my solution here with the configuration context.
  2. I'm not sure what the motivation is for using immutable collections to represent the objects in this system, but it makes dealing with them programmatically more difficult (less intuitive) than dealing with a typed POJO. The configuration type definitions don't go so far as to define the types of the parameters of opsFilter. Hence I have included some type information for those here as well, but once you dive down into the Map of an operation, defining types becomes untenable. If the motivation is simply immutability, there are more efficient options that preserve type information and allow for simpler use of POJOs. Typescript Playground Immutability Example
import SwaggerUI from 'swagger-ui';
import { List, Map, OrderedMap } from 'immutable';
import { OpenAPI, OpenAPIV2, OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';

export type Operations = List<OpenAPI.Operation>; // But not really it is yet another Map :(

export type TagDetails = Map<'name' | 'description', string>;

export type TaggedOperations = OrderedMap<string, TaggedOperationCollection>;

export type TaggedOperationCollection = Map<'tagDetails', TagDetails> &
  Map<'operations', Operations>;

...
  private _renderSwaggerUI() {
    SwaggerUI({
      domNode: this.swaggerContainer?.nativeElement,
      spec: this._options?.openApiDocument,
      deepLinking: false,
      withCredentials: false,
      tryItOutEnabled: true,
      filter: this._options?.tag?.name,
      plugins: [
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        (_system: unknown) => {
          return {
            fn: {
              // The default filter function does a contains match on the filter expression.
              // This may be useful when filtering based on a search input but is not useful
              // when driving the filter from a tag selection in a navigation tree.  We need
              // an exact match on the tag name in this case.
              opsFilter: (
                taggedOps: TaggedOperations,
                filterExpression: string
              ) => {
                // The variable assignment and logging are solely for demonstrating that property access 
                // requires use of "magic strings" rather than providing typed access.  Remove these in 
                // production code.
                return taggedOps.filter((val, key) => {
                  const tagDetails: TagDetails | undefined =
                    val.get('tagDetails');
                  const operations: Operations | undefined =
                    val.get('operations');
                  console.log('tagDetails', tagDetails);
                  console.log('operations', operations);
                  return key === filterExpression;
                });
              },
            },
          };
        },
      ]
    });
  }
...