feat(admin): add custom admin route ranking feature (#13946)

*  Add custom admin routes ranking

* 🐛 Fix sorting

* 📝 Update admin ui-routes documentation

*  Add admin menu items spec

* 🔧 Add changeset

* 🐛 Remove redundant undefined initializations

* 🔥 🔥 Move the documentation to a separate PR

* ♻️ Move sorting logic to utils

* 🔧 Update changeset

---------

Co-authored-by: Bastien MONTOIS <bqst@bqst-hqckintosh.home>
This commit is contained in:
Bastien
2025-11-07 13:59:40 -05:00
committed by GitHub
co-authored by Bastien MONTOIS
parent aae92d5447
commit 213c344804
9 changed files with 437 additions and 7 deletions
@@ -70,6 +70,7 @@ const expectedMenuItems = `
icon: RouteConfig0.icon,
path: "/one",
nested: undefined,
rank: undefined,
translationNs: undefined
},
{
@@ -77,6 +78,7 @@ const expectedMenuItems = `
icon: undefined,
path: "/two",
nested: undefined,
rank: undefined,
translationNs: undefined
},
{
@@ -84,6 +86,7 @@ const expectedMenuItems = `
icon: RouteConfig2.icon,
path: "/three",
nested: "/products",
rank: undefined,
translationNs: undefined
}
]
@@ -141,6 +144,82 @@ describe("generateMenuItems", () => {
)
})
it("should include rank property in generated menu items", async () => {
const mockFilesWithRank = [
"Users/user/medusa/src/admin/routes/analytics/page.tsx",
"Users/user/medusa/src/admin/routes/reports/page.tsx",
]
const mockFileContentsWithRank = [
`
import { defineRouteConfig } from "@medusajs/admin-sdk"
const Page = () => {
return <div>Analytics</div>
}
export const config = defineRouteConfig({
label: "Analytics",
icon: "ChartBar",
rank: 1,
})
export default Page
`,
`
import { defineRouteConfig } from "@medusajs/admin-sdk"
const Page = () => {
return <div>Reports</div>
}
export const config = defineRouteConfig({
label: "Reports",
rank: 2,
})
export default Page
`,
]
vi.mocked(utils.crawl).mockResolvedValue(mockFilesWithRank)
vi.mocked(fs.readFile).mockImplementation(async (file) =>
Promise.resolve(
mockFileContentsWithRank[mockFilesWithRank.indexOf(file as string)]
)
)
const result = await generateMenuItems(
new Set(["Users/user/medusa/src/admin"])
)
const expectedMenuItemsWithRank = `
menuItems: [
{
label: RouteConfig0.label,
icon: RouteConfig0.icon,
path: "/analytics",
nested: undefined,
rank: 1,
translationNs: undefined
},
{
label: RouteConfig1.label,
icon: undefined,
path: "/reports",
nested: undefined,
rank: 2,
translationNs: undefined
}
]
`
expect(utils.normalizeString(result.code)).toEqual(
utils.normalizeString(expectedMenuItemsWithRank)
)
})
it("should handle translationNs field", async () => {
const mockFileWithTranslation = `
import { defineRouteConfig } from "@medusajs/admin-sdk"
@@ -176,6 +255,7 @@ describe("generateMenuItems", () => {
icon: undefined,
path: "/custom",
nested: undefined,
rank: undefined,
translationNs: RouteConfig0.translationNs
}
]
@@ -185,4 +265,99 @@ describe("generateMenuItems", () => {
utils.normalizeString(expectedOutput)
)
})
it("should handle mixed ranked and unranked routes", async () => {
const mockMixedFiles = [
"Users/user/medusa/src/admin/routes/first/page.tsx",
"Users/user/medusa/src/admin/routes/second/page.tsx",
"Users/user/medusa/src/admin/routes/third/page.tsx",
]
const mockMixedContents = [
`
import { defineRouteConfig } from "@medusajs/admin-sdk"
const Page = () => {
return <div>First</div>
}
export const config = defineRouteConfig({
label: "First",
rank: 1,
})
export default Page
`,
`
import { defineRouteConfig } from "@medusajs/admin-sdk"
const Page = () => {
return <div>Second</div>
}
export const config = defineRouteConfig({
label: "Second",
})
export default Page
`,
`
import { defineRouteConfig } from "@medusajs/admin-sdk"
const Page = () => {
return <div>Third</div>
}
export const config = defineRouteConfig({
label: "Third",
rank: 0,
})
export default Page
`,
]
vi.mocked(utils.crawl).mockResolvedValue(mockMixedFiles)
vi.mocked(fs.readFile).mockImplementation(async (file) =>
Promise.resolve(mockMixedContents[mockMixedFiles.indexOf(file as string)])
)
const result = await generateMenuItems(
new Set(["Users/user/medusa/src/admin"])
)
const expectedMixedMenuItems = `
menuItems: [
{
label: RouteConfig0.label,
icon: undefined,
path: "/first",
nested: undefined,
rank: 1,
translationNs: undefined
},
{
label: RouteConfig1.label,
icon: undefined,
path: "/second",
nested: undefined,
rank: undefined,
translationNs: undefined
},
{
label: RouteConfig2.label,
icon: undefined,
path: "/third",
nested: undefined,
rank: 0,
translationNs: undefined
}
]
`
expect(utils.normalizeString(result.code)).toEqual(
utils.normalizeString(expectedMixedMenuItems)
)
})
})