Skip to content

Commit 131003b

Browse files
authored
fix(dashboard): resolve dashboard by ID/slug in addition to title (#559)
Fixes CLI-MK `resolveDashboardId()` only matched non-numeric references against dashboard titles. Sentry's built-in dashboard has `id: "default-overview"` but `title: "General"`, so `sentry dashboard view default-overview` threw a `ValidationError` despite listing the dashboard as available in the error. ### Changes - **ID-first matching**: `resolveDashboardId()` now tries `d.id` before falling back to `d.title` (both case-insensitive). ID match takes priority when a value collides with a different dashboard's title. - **Clearer error message**: Says "matching" instead of "with title" and labels the available list columns as `(ID Title)`. - **Tests**: 5 new cases covering slug match, case-insensitivity, ID vs title priority, title fallback, and improved error wording.
1 parent ce3cc32 commit 131003b

File tree

2 files changed

+47
-4
lines changed

2 files changed

+47
-4
lines changed

src/commands/dashboard/resolve.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,10 @@ export async function resolveDashboardId(
126126

127127
const dashboards = await listDashboards(orgSlug);
128128
const lowerRef = ref.toLowerCase();
129-
const match = dashboards.find((d) => d.title.toLowerCase() === lowerRef);
129+
// Match by ID/slug first (e.g. "default-overview"), then fall back to title
130+
const match =
131+
dashboards.find((d) => d.id.toLowerCase() === lowerRef) ??
132+
dashboards.find((d) => d.title.toLowerCase() === lowerRef);
130133

131134
if (!match) {
132135
const available = dashboards
@@ -136,8 +139,8 @@ export async function resolveDashboardId(
136139
const suffix =
137140
dashboards.length > 5 ? `\n ... and ${dashboards.length - 5} more` : "";
138141
throw new ValidationError(
139-
`No dashboard with title '${ref}' found in '${orgSlug}'.\n\n` +
140-
`Available dashboards:\n${available}${suffix}`
142+
`No dashboard matching '${ref}' found in '${orgSlug}'.\n\n` +
143+
`Available dashboards (ID Title):\n${available}${suffix}`
141144
);
142145
}
143146

test/commands/dashboard/resolve.test.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,45 @@ describe("resolveDashboardId", () => {
9292
expect(id).toBe("10");
9393
});
9494

95-
test("no match throws ValidationError with available dashboards", async () => {
95+
test("ID/slug match returns matching dashboard ID", async () => {
96+
listDashboardsSpy.mockResolvedValue([
97+
{ id: "default-overview", title: "General" },
98+
{ id: "20", title: "Performance" },
99+
]);
100+
101+
const id = await resolveDashboardId("test-org", "default-overview");
102+
expect(id).toBe("default-overview");
103+
});
104+
105+
test("ID match is case-insensitive", async () => {
106+
listDashboardsSpy.mockResolvedValue([
107+
{ id: "default-overview", title: "General" },
108+
]);
109+
110+
const id = await resolveDashboardId("test-org", "Default-Overview");
111+
expect(id).toBe("default-overview");
112+
});
113+
114+
test("ID match takes priority over title match", async () => {
115+
listDashboardsSpy.mockResolvedValue([
116+
{ id: "perf", title: "Performance Dashboard" },
117+
{ id: "30", title: "perf" },
118+
]);
119+
120+
const id = await resolveDashboardId("test-org", "perf");
121+
expect(id).toBe("perf");
122+
});
123+
124+
test("title match still works when no ID matches", async () => {
125+
listDashboardsSpy.mockResolvedValue([
126+
{ id: "default-overview", title: "General" },
127+
]);
128+
129+
const id = await resolveDashboardId("test-org", "General");
130+
expect(id).toBe("default-overview");
131+
});
132+
133+
test("no match throws ValidationError with improved error message", async () => {
96134
listDashboardsSpy.mockResolvedValue([
97135
{ id: "10", title: "Errors Overview" },
98136
{ id: "20", title: "Performance" },
@@ -105,6 +143,8 @@ describe("resolveDashboardId", () => {
105143
expect(error).toBeInstanceOf(ValidationError);
106144
const message = (error as ValidationError).message;
107145
expect(message).toContain("Missing Dashboard");
146+
expect(message).toContain("matching");
147+
expect(message).toContain("(ID Title)");
108148
expect(message).toContain("Errors Overview");
109149
expect(message).toContain("Performance");
110150
}

0 commit comments

Comments
 (0)