Skip to content

Commit 45a2922

Browse files
fix(plugin): generate unique sidebar keys for tagGroup items
Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
1 parent 7a5535e commit 45a2922

2 files changed

Lines changed: 120 additions & 7 deletions

File tree

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/* ============================================================================
2+
* Copyright (c) Palo Alto Networks
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
* ========================================================================== */
7+
8+
import type { TagGroupObject, TagObject } from "../openapi/types";
9+
import type { ApiMetadata } from "../types";
10+
import generateSidebarSlice from "./index";
11+
12+
describe("generateSidebarSlice", () => {
13+
describe("tagGroup with overlapping tags", () => {
14+
const mockApiItems: ApiMetadata[] = [
15+
{
16+
type: "api",
17+
id: "get-books",
18+
unversionedId: "get-books",
19+
title: "Get Books",
20+
description: "",
21+
source: "",
22+
sourceDirName: "",
23+
permalink: "/get-books",
24+
frontMatter: {},
25+
api: {
26+
method: "get",
27+
path: "/books",
28+
tags: ["Books", "Deprecated"],
29+
jsonRequestBodyExample: "",
30+
info: { title: "Test API", version: "1.0.0" },
31+
},
32+
} as ApiMetadata,
33+
];
34+
35+
const mockTags: TagObject[][] = [
36+
[
37+
{ name: "Books", description: "Book operations" },
38+
{ name: "Deprecated", description: "Deprecated endpoints" },
39+
],
40+
];
41+
42+
const mockTagGroups: TagGroupObject[] = [
43+
{ name: "Library", tags: ["Books"] },
44+
{ name: "Deprecation", tags: ["Deprecated"] },
45+
];
46+
47+
function collectKeys(obj: unknown): string[] {
48+
const keys: string[] = [];
49+
JSON.stringify(obj, (k, v) => {
50+
if (k === "key" && typeof v === "string") {
51+
keys.push(v);
52+
}
53+
return v;
54+
});
55+
return keys;
56+
}
57+
58+
it("should generate unique keys for items appearing in multiple tagGroups", () => {
59+
const result = generateSidebarSlice(
60+
{ groupPathsBy: "tagGroup" },
61+
{ outputDir: "docs/test", specPath: "" },
62+
mockApiItems,
63+
mockTags,
64+
"",
65+
mockTagGroups
66+
);
67+
68+
const keys = collectKeys(result);
69+
70+
expect(keys.length).toBeGreaterThan(0);
71+
expect(new Set(keys).size).toBe(keys.length);
72+
});
73+
74+
it("should include tagGroup name in keys to differentiate same items", () => {
75+
const result = generateSidebarSlice(
76+
{ groupPathsBy: "tagGroup" },
77+
{ outputDir: "docs/test", specPath: "" },
78+
mockApiItems,
79+
mockTags,
80+
"",
81+
mockTagGroups
82+
);
83+
84+
const keys = collectKeys(result);
85+
86+
expect(keys.filter((k) => k.includes("library")).length).toBeGreaterThan(
87+
0
88+
);
89+
expect(
90+
keys.filter((k) => k.includes("deprecation")).length
91+
).toBeGreaterThan(0);
92+
});
93+
});
94+
});

packages/docusaurus-plugin-openapi-docs/src/sidebars/index.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ function groupByTags(
7777
sidebarOptions: SidebarOptions,
7878
options: APIOptions,
7979
tags: TagObject[][],
80-
docPath: string
80+
docPath: string,
81+
tagGroupKey?: string
8182
): ProcessedSidebar {
8283
let { outputDir, label, showSchemas } = options;
8384

@@ -152,9 +153,11 @@ function groupByTags(
152153
if (infoItems.length === 1) {
153154
const infoItem = infoItems[0];
154155
const id = infoItem.id;
156+
const docId = basePath === "" || undefined ? `${id}` : `${basePath}/${id}`;
155157
rootIntroDoc = {
156158
type: "doc" as const,
157-
id: basePath === "" || undefined ? `${id}` : `${basePath}/${id}`,
159+
id: docId,
160+
...(tagGroupKey && { key: kebabCase(`${tagGroupKey}-${docId}`) }),
158161
};
159162
}
160163

@@ -219,15 +222,28 @@ function groupByTags(
219222
(item) => !!item.schema["x-tags"]?.includes(tag)
220223
);
221224

225+
const categoryLabel = tagObject?.["x-displayName"] ?? tag;
226+
const categoryKey = tagGroupKey
227+
? kebabCase(`${tagGroupKey}-${categoryLabel}`)
228+
: undefined;
229+
222230
return {
223231
type: "category" as const,
224-
label: tagObject?.["x-displayName"] ?? tag,
232+
label: categoryLabel,
233+
...(categoryKey && { key: categoryKey }),
225234
link: linkConfig,
226235
collapsible: sidebarCollapsible,
227236
collapsed: sidebarCollapsed,
228-
items: [...taggedSchemaItems, ...taggedApiItems].map((item) =>
229-
createDocItemFn(item, createDocItemFnContext)
230-
),
237+
items: [...taggedSchemaItems, ...taggedApiItems].map((item) => {
238+
const docItem = createDocItemFn(item, createDocItemFnContext);
239+
if (tagGroupKey && docItem.type === "doc") {
240+
return {
241+
...docItem,
242+
key: kebabCase(`${tagGroupKey}-${tag}-${docItem.id}`),
243+
};
244+
}
245+
return docItem;
246+
}),
231247
};
232248
})
233249
.filter((item) => item.items.length > 0); // Filter out any categories with no items.
@@ -296,6 +312,8 @@ export default function generateSidebarSlice(
296312
}
297313
});
298314

315+
const tagGroupKey = kebabCase(tagGroup.name);
316+
299317
const groupCategory = {
300318
type: "category" as const,
301319
label: tagGroup.name,
@@ -306,7 +324,8 @@ export default function generateSidebarSlice(
306324
sidebarOptions,
307325
options,
308326
[filteredTags],
309-
docPath
327+
docPath,
328+
tagGroupKey
310329
),
311330
};
312331

0 commit comments

Comments
 (0)