Skip to content

Commit

Permalink
feat: implement x-tagGroup feature (#737)
Browse files Browse the repository at this point in the history
* impement redoc's x-tagGroup feature

* merge main

* update documentation

* fix filtered tags by group

* remove tracked .idea files

* add  option to readme

* merge main

* fix lint

* revert yarn.lock and linter
  • Loading branch information
bencagri committed Mar 15, 2024
1 parent 026074f commit 5b1cdae
Show file tree
Hide file tree
Showing 16 changed files with 211 additions and 15 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,4 @@ demo/**/*.schema.mdx
demo/**/sidebar.js
demo/**/versions.json

.idea
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ The `docusaurus-plugin-openapi-docs` plugin can be configured with the following

| Name | Type | Default | Description |
| -------------------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `groupPathsBy` | `string` | `null` | Organize and group sidebar slice by specified option. Note: Currently, `groupPathsBy` only contains support for grouping by `tag`. |
| `groupPathsBy` | `string` | `null` | Organize and group sidebar slice by specified option. Note: Currently, `groupPathsBy` only contains support for grouping by `tag` and `tagGroup`. |
| `categoryLinkSource` | `string` | `null` | Defines what source to use for rendering category link pages when grouping paths by tag. <br/><br/>The supported options are as follows: <br/><br/> `tag`: Sets the category link config type to `generated-index` and uses the tag description as the link config description. <br/><br/>`info`: Sets the category link config type to `doc` and renders the `info` section as the category link (recommended only for multi/micro-spec scenarios). <br/><br/>`none`: Does not create pages for categories, only groups that can be expanded/collapsed. |
| `sidebarCollapsible` | `boolean` | `true` | Whether sidebar categories are collapsible by default. |
| `sidebarCollapsed` | `boolean` | `true` | Whether sidebar categories are collapsed by default. |
Expand Down
6 changes: 6 additions & 0 deletions demo/docs/sidebars.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ For greater control, you also have the option of constructing the sidebar object

The OpenAPI docs plugin provides out-of-the-box support for grouping paths by "tag". What this means is that the plugin will automatically generate a sidebar slice using the first path tag as the "group by" value and the path itself as one of the `items` under that category.

### Grouping by TagGroup

The OpenAPI docs plugin provides out-of-the-box support for grouping paths by "tagGroup".
What this means is that the plugin will automatically generate a sidebar slice using the first path group as the "group by" value and the path itself as one of the `tags` under that category.
Use `x-tagGroups` to group tags in the [Reference](https://redocly.com/docs/api-reference-docs/specification-extensions/x-tag-groups/) docs navigation sidebar. Add it to the root OpenAPI object.

### Category Links

Docusaurus now supports the ability to designate or customize what page gets displayed when a category is clicked.
Expand Down
7 changes: 7 additions & 0 deletions demo/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,13 @@ const config = {
specPath: "examples/food/yogurtstore/openapi.yaml",
outputDir: "docs/food/yogurtstore",
},
restaurant: {
specPath: "examples/food/restaurant/openapi.yaml",
outputDir: "docs/restaurant",
sidebarOptions: {
groupPathsBy: "tagGroup",
},
},
},
},
],
Expand Down
4 changes: 4 additions & 0 deletions demo/examples/food/restaurant/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"label": "Restaurant",
"collapsed": true
}
70 changes: 70 additions & 0 deletions demo/examples/food/restaurant/openapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
openapi: 3.0.3
info:
title: Restaurant Example
version: 1.0.0
description: Sample description.
paths:
/menu:
get:
tags:
- tag1
summary: Get Menu
description: Froyo's the best!
responses:
200:
description: OK
/products:
get:
tags:
- tag1
- tag2
summary: List All Products
description: Froyo's the best!
responses:
200:
description: OK
/drinks:
get:
tags:
- tag1
- tag2
summary: List All Drinks
description: Froyo's the best!
responses:
200:
description: OK
/pay:
post:
tags:
- tag3
summary: Make Payment
description: Froyo's the best!
responses:
200:
description: OK

tags:
- name: tag1
description: Everything about your restaurant
x-displayName: Tag 1
- name: tag2
description: Tag 2 description
x-displayName: Tag 2
- name: tag3
description: Tag 3 description
x-displayName: Tag 3

x-tagGroups:
- name: Tag 1 & 2
tags:
- tag1
- tag2
- name: Trinity
tags:
- tag1
- tag2
- tag3
- name: Last Two
tags:
- tag2
- tag3
10 changes: 10 additions & 0 deletions demo/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,16 @@ const sidebars = {
},
],
},
{
type: "category",
label: "Restaurant",
link: {
type: "generated-index",
title: "Restaurant API",
slug: "/category/restaurant-api",
},
items: require("./docs/restaurant/sidebar.js"),
},
],
"petstore-2.0.0": [
{
Expand Down
5 changes: 3 additions & 2 deletions packages/docusaurus-plugin-openapi-docs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export default function pluginOpenAPIDocs(

try {
const openapiFiles = await readOpenapiFiles(contentPath);
const [loadedApi, tags] = await processOpenapiFiles(
const [loadedApi, tags, tagGroups] = await processOpenapiFiles(
openapiFiles,
options,
sidebarOptions!
Expand All @@ -155,7 +155,8 @@ export default function pluginOpenAPIDocs(
options,
loadedApi,
tags,
docPath
docPath,
tagGroups
);

const sidebarSliceTemplate = `module.exports = {{{slice}}};`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,32 @@ paths:
responses:
200:
description: OK

tags:
- name: tag1
description: Everything about your Pets
x-displayName: Tag 1
- name: tag2
description: Tag 2 description
x-displayName: Tag 2
- name: tag3
description: Tag 3 description
x-displayName: Tag 3
- name: tag4
description: Tag 4 description
x-displayName: Tag 4

x-tagGroups:
- name: Tag 1 & 2
tags:
- tag1
- tag2
- name: Trinity
tags:
- tag1
- tag2
- tag3
- name: Last Two
tags:
- tag3
- tag4
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ describe("openapi", () => {
const yaml = results.find((x) => x.source.endsWith("openapi.yaml"));
expect(yaml).toBeTruthy();
expect(yaml?.sourceDirName).toBe(".");

expect(yaml?.data.tags).toBeDefined();
expect(yaml?.data["x-tagGroups"]).toBeDefined();
});
});
});
35 changes: 29 additions & 6 deletions packages/docusaurus-plugin-openapi-docs/src/openapi/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
TagPageMetadata,
} from "../types";
import { sampleRequestFromSchema } from "./createRequestExample";
import { OpenApiObject, TagObject } from "./types";
import { OpenApiObject, TagGroupObject, TagObject } from "./types";
import { loadAndResolveSpec } from "./utils/loadAndResolveSpec";

/**
Expand Down Expand Up @@ -579,7 +579,7 @@ export async function processOpenapiFiles(
files: OpenApiFiles[],
options: APIOptions,
sidebarOptions: SidebarOptions
): Promise<[ApiMetadata[], TagObject[][]]> {
): Promise<[ApiMetadata[], TagObject[][], TagGroupObject[]]> {
const promises = files.map(async (file) => {
if (file.data !== undefined) {
const processedFile = await processOpenapiFile(
Expand All @@ -591,7 +591,8 @@ export async function processOpenapiFiles(
...item,
}));
const tags = processedFile[1];
return [itemsObjectsArray, tags];
const tagGroups = processedFile[2];
return [itemsObjectsArray, tags, tagGroups];
}
console.warn(
chalk.yellow(
Expand All @@ -610,6 +611,7 @@ export async function processOpenapiFiles(
// Remove undefined items due to transient parsing errors
return x !== undefined;
});

const tags = metadata
.map(function (x) {
return x[1];
Expand All @@ -618,14 +620,29 @@ export async function processOpenapiFiles(
// Remove undefined tags due to transient parsing errors
return x !== undefined;
});
return [items as ApiMetadata[], tags as TagObject[][]];

const tagGroups = metadata
.map(function (x) {
return x[2];
})
.flat()
.filter(function (x) {
// Remove undefined tags due to transient parsing errors
return x !== undefined;
});

return [
items as ApiMetadata[],
tags as TagObject[][],
tagGroups as TagGroupObject[],
];
}

export async function processOpenapiFile(
openapiData: OpenApiObject,
options: APIOptions,
sidebarOptions: SidebarOptions
): Promise<[ApiMetadata[], TagObject[]]> {
): Promise<[ApiMetadata[], TagObject[], TagGroupObject[]]> {
const postmanCollection = await createPostmanCollection(openapiData);
const items = createItems(openapiData, options, sidebarOptions);

Expand All @@ -635,7 +652,13 @@ export async function processOpenapiFile(
if (openapiData.tags !== undefined) {
tags = openapiData.tags;
}
return [items, tags];

let tagGroups: TagGroupObject[] = [];
if (openapiData["x-tagGroups"] !== undefined) {
tagGroups = openapiData["x-tagGroups"];
}

return [items, tags, tagGroups];
}

// order for picking items as a display name of tags
Expand Down
6 changes: 6 additions & 0 deletions packages/docusaurus-plugin-openapi-docs/src/openapi/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface OpenApiObject {
externalDocs?: ExternalDocumentationObject;
swagger?: string;
"x-webhooks"?: PathsObject;
"x-tagGroups"?: TagGroupObject[];
}

export interface OpenApiObjectWithRef {
Expand Down Expand Up @@ -311,6 +312,11 @@ export interface TagObject {
"x-displayName"?: string;
}

export interface TagGroupObject {
name: string;
tags: string[];
}

export interface ReferenceObject {
$ref: string;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/docusaurus-plugin-openapi-docs/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { Joi } from "@docusaurus/utils-validation";

const sidebarOptions = Joi.object({
groupPathsBy: Joi.string().valid("tag"),
groupPathsBy: Joi.string().valid("tag", "tagGroup"),
categoryLinkSource: Joi.string().valid("tag", "info", "auto"),
customProps: Joi.object(),
sidebarCollapsible: Joi.boolean(),
Expand Down
37 changes: 33 additions & 4 deletions packages/docusaurus-plugin-openapi-docs/src/sidebars/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import path from "path";

import { ProcessedSidebarItem } from "@docusaurus/plugin-content-docs/lib/sidebars/types";
import {
ProcessedSidebar,
SidebarItemCategory,
Expand All @@ -18,7 +19,7 @@ import clsx from "clsx";
import { kebabCase } from "lodash";
import uniq from "lodash/uniq";

import { TagObject } from "../openapi/types";
import { TagGroupObject, TagObject } from "../openapi/types";
import type {
SidebarOptions,
APIOptions,
Expand Down Expand Up @@ -86,7 +87,7 @@ function groupByTags(
apiTags.push(tag.name!);
}
});
apiTags = uniq(apiTags.concat(operationTags));
// apiTags = uniq(apiTags.concat(operationTags));

const basePath = docPath
? outputDir.split(docPath!)[1].replace(/^\/+/g, "")
Expand Down Expand Up @@ -241,11 +242,38 @@ export default function generateSidebarSlice(
options: APIOptions,
api: ApiMetadata[],
tags: TagObject[][],
docPath: string
docPath: string,
tagGroups?: TagGroupObject[]
) {
let sidebarSlice: ProcessedSidebar = [];

if (sidebarOptions.groupPathsBy === "tag") {
if (sidebarOptions.groupPathsBy === "tagGroup") {
tagGroups?.forEach((tagGroup) => {
//filter tags only included in group
const filteredTags: TagObject[] = [];
tags[0].forEach((tag) => {
if (tagGroup.tags.includes(tag.name as string)) {
filteredTags.push(tag);
}
});

const groupCategory = {
type: "category" as const,
label: tagGroup.name,
collapsible: true,
collapsed: true,
items: groupByTags(
api as ApiPageMetadata[],
sidebarOptions,
options,
[filteredTags],
docPath
),
} as ProcessedSidebarItem;

sidebarSlice.push(groupCategory);
});
} else if (sidebarOptions.groupPathsBy === "tag") {
sidebarSlice = groupByTags(
api as ApiPageMetadata[],
sidebarOptions,
Expand All @@ -254,5 +282,6 @@ export default function generateSidebarSlice(
docPath
);
}

return sidebarSlice;
}
7 changes: 7 additions & 0 deletions packages/docusaurus-plugin-openapi-docs/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@ export interface SchemaPageMetadata extends ApiMetadataBase {
markdown?: string;
}

export interface TagGroupPageMetadata extends ApiMetadataBase {
type: "tagGroup";
name: string;
tags: TagObject[];
markdown?: string;
}

export type ApiInfo = InfoObject;

export interface ApiNavLink {
Expand Down
Loading

0 comments on commit 5b1cdae

Please sign in to comment.