Skip to content

Commit c790584

Browse files
committed
fix(dashboard): move fuzzy suggestions into human formatter
The fuzzy suggestions for no-match filters were in the hint (footer) while the formatter independently showed 'No dashboards found.' — producing duplicate messaging. Now the formatter receives titleFilter and allTitles in the result, and shows the appropriate message: - With filter, has suggestions: 'No dashboards matching X. Did you mean:' - With filter, no suggestions: 'No dashboards matching X.' - Without filter: 'No dashboards found.'
1 parent b32e480 commit c790584

File tree

2 files changed

+35
-9
lines changed

2 files changed

+35
-9
lines changed

src/commands/dashboard/list.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ type DashboardListResult = {
5454
orgSlug: string;
5555
hasMore: boolean;
5656
nextCursor?: string;
57+
/** The title filter used (for empty-state messaging) */
58+
titleFilter?: string;
59+
/** All titles seen during fetch (for fuzzy suggestions on empty filter results) */
60+
allTitles?: string[];
5761
};
5862

5963
// ---------------------------------------------------------------------------
@@ -118,6 +122,15 @@ export function decodeCursor(cursor: string): {
118122
*/
119123
function formatDashboardListHuman(result: DashboardListResult): string {
120124
if (result.dashboards.length === 0) {
125+
if (result.titleFilter && result.allTitles && result.allTitles.length > 0) {
126+
const similar = fuzzyMatch(result.titleFilter, result.allTitles, {
127+
maxResults: 5,
128+
});
129+
if (similar.length > 0) {
130+
return `No dashboards matching '${result.titleFilter}'. Did you mean:\n${similar.map((t) => ` • ${t}`).join("\n")}`;
131+
}
132+
return `No dashboards matching '${result.titleFilter}'.`;
133+
}
121134
return "No dashboards found.";
122135
}
123136

@@ -422,17 +435,13 @@ export const listCommand = buildListCommand("dashboard", {
422435
orgSlug,
423436
hasMore,
424437
nextCursor: cursorToStore,
438+
titleFilter,
439+
allTitles,
425440
} satisfies DashboardListResult);
426441

427442
// Build footer hint
428443
let hint: string | undefined;
429-
if (results.length === 0 && titleFilter && allTitles.length > 0) {
430-
// Filter matched nothing — suggest similar titles via fuzzy matching
431-
const similar = fuzzyMatch(titleFilter, allTitles, { maxResults: 5 });
432-
if (similar.length > 0) {
433-
hint = `No dashboards matching '${titleFilter}'. Did you mean:\n${similar.map((t) => ` • ${t}`).join("\n")}`;
434-
}
435-
} else if (results.length === 0) {
444+
if (results.length === 0) {
436445
hint = undefined;
437446
} else if (hasMore) {
438447
hint = `Showing ${results.length} dashboard(s). Next page: sentry dashboard list ${orgSlug}/ -c last\nDashboards: ${url}`;

test/commands/dashboard/list.test.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ describe("dashboard list command", () => {
396396
expect(parsed.data[0].title).toBe("Errors Overview");
397397
});
398398

399-
test("glob filter with no matches shows empty state", async () => {
399+
test("glob filter with no matches shows filter-aware message", async () => {
400400
resolveOrgSpy.mockResolvedValue({ org: "test-org" });
401401
listDashboardsPaginatedSpy.mockResolvedValue({
402402
data: [DASHBOARD_A, DASHBOARD_B, DASHBOARD_C],
@@ -408,7 +408,24 @@ describe("dashboard list command", () => {
408408
await func.call(context, defaultFlags(), "NoMatch*");
409409

410410
const output = stdoutWrite.mock.calls.map((c) => c[0]).join("");
411-
expect(output).toContain("No dashboards found.");
411+
expect(output).toContain("No dashboards matching 'NoMatch*'.");
412+
});
413+
414+
test("glob filter with no matches shows fuzzy suggestions for close input", async () => {
415+
resolveOrgSpy.mockResolvedValue({ org: "test-org" });
416+
listDashboardsPaginatedSpy.mockResolvedValue({
417+
data: [DASHBOARD_A, DASHBOARD_B, DASHBOARD_C],
418+
nextCursor: undefined,
419+
});
420+
421+
const { context, stdoutWrite } = createMockContext();
422+
const func = await listCommand.loader();
423+
// "Perf" is a prefix of "Performance" → fuzzy match finds it
424+
await func.call(context, defaultFlags(), "Perf");
425+
426+
const output = stdoutWrite.mock.calls.map((c) => c[0]).join("");
427+
expect(output).toContain("Did you mean:");
428+
expect(output).toContain("Performance");
412429
});
413430

414431
// -------------------------------------------------------------------------

0 commit comments

Comments
 (0)