From 086aedb5719b1e4c23d947dd310eab3e78ac4cbc Mon Sep 17 00:00:00 2001 From: betegon Date: Fri, 20 Mar 2026 20:48:28 +0100 Subject: [PATCH 1/9] feat(dashboard): add `widget types` command and layout guidance for agents Agents creating dashboards don't know about the 6-column grid or available display types. This adds a `sentry dashboard widget types` command that outputs display types with default sizes, datasets, aggregate functions, and aliases. Also surfaces layout info in `widget --help` and expands the `--display` flag brief to show more types. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/commands/dashboard/index.ts | 2 +- src/commands/dashboard/widget/add.ts | 3 +- src/commands/dashboard/widget/edit.ts | 3 +- src/commands/dashboard/widget/index.ts | 8 +- src/commands/dashboard/widget/types.ts | 116 +++++++++++++++++++++++++ src/lib/formatters/human.ts | 64 ++++++++++++++ src/types/dashboard.ts | 6 +- 7 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 src/commands/dashboard/widget/types.ts diff --git a/src/commands/dashboard/index.ts b/src/commands/dashboard/index.ts index a47d1f6c1..2c06b18db 100644 --- a/src/commands/dashboard/index.ts +++ b/src/commands/dashboard/index.ts @@ -19,7 +19,7 @@ export const dashboardRoute = buildRouteMap({ " list List dashboards\n" + " view View a dashboard\n" + " create Create a dashboard\n" + - " widget Manage dashboard widgets (add, edit, delete)", + " widget Manage dashboard widgets (add, edit, delete, types)", hideRoute: {}, }, }); diff --git a/src/commands/dashboard/widget/add.ts b/src/commands/dashboard/widget/add.ts index d62ff2b07..4ad5431bb 100644 --- a/src/commands/dashboard/widget/add.ts +++ b/src/commands/dashboard/widget/add.ts @@ -105,7 +105,8 @@ export const addCommand = buildCommand({ display: { kind: "parsed", parse: String, - brief: "Display type (line, bar, table, big_number, ...)", + brief: + "Display type (big_number, line, area, bar, table, stacked_area, top_n, text, ...)", }, dataset: { kind: "parsed", diff --git a/src/commands/dashboard/widget/edit.ts b/src/commands/dashboard/widget/edit.ts index 54342b3f7..5d2fda646 100644 --- a/src/commands/dashboard/widget/edit.ts +++ b/src/commands/dashboard/widget/edit.ts @@ -156,7 +156,8 @@ export const editCommand = buildCommand({ display: { kind: "parsed", parse: String, - brief: "Display type (line, bar, table, big_number, ...)", + brief: + "Display type (big_number, line, area, bar, table, stacked_area, top_n, text, ...)", optional: true, }, dataset: { diff --git a/src/commands/dashboard/widget/index.ts b/src/commands/dashboard/widget/index.ts index a3e4144cd..a91c7bffc 100644 --- a/src/commands/dashboard/widget/index.ts +++ b/src/commands/dashboard/widget/index.ts @@ -2,21 +2,27 @@ import { buildRouteMap } from "@stricli/core"; import { addCommand } from "./add.js"; import { deleteCommand } from "./delete.js"; import { editCommand } from "./edit.js"; +import { typesCommand } from "./types.js"; export const widgetRoute = buildRouteMap({ routes: { add: addCommand, edit: editCommand, delete: deleteCommand, + types: typesCommand, }, docs: { brief: "Manage dashboard widgets", fullDescription: "Add, edit, or delete widgets in a Sentry dashboard.\n\n" + + "Dashboards use a 6-column grid. Widget widths should sum to 6 per row.\n" + + "Common display types: big_number (2 cols), line/area/bar (3 cols), table (6 cols).\n" + + "Default dataset: spans. Run 'sentry dashboard widget types' for the full list.\n\n" + "Commands:\n" + " add Add a widget to a dashboard\n" + " edit Edit a widget in a dashboard\n" + - " delete Delete a widget from a dashboard", + " delete Delete a widget from a dashboard\n" + + " types Show available display types and layout info", hideRoute: {}, }, }); diff --git a/src/commands/dashboard/widget/types.ts b/src/commands/dashboard/widget/types.ts new file mode 100644 index 000000000..d08e52e51 --- /dev/null +++ b/src/commands/dashboard/widget/types.ts @@ -0,0 +1,116 @@ +/** + * sentry dashboard widget types + * + * Show available widget display types, default sizes, datasets, + * and aggregate functions. Purely local — no API calls or auth needed. + */ + +import type { SentryContext } from "../../../context.js"; +import { buildCommand } from "../../../lib/command.js"; +import { formatWidgetTypes } from "../../../lib/formatters/human.js"; +import { CommandOutput } from "../../../lib/formatters/output.js"; +import { + AGGREGATE_ALIASES, + DEFAULT_WIDGET_SIZE, + DEFAULT_WIDGET_TYPE, + DISCOVER_AGGREGATE_FUNCTIONS, + DISPLAY_TYPES, + type DisplayType, + FALLBACK_SIZE, + GRID_COLUMNS, + SPAN_AGGREGATE_FUNCTIONS, + WIDGET_TYPES, +} from "../../../types/dashboard.js"; + +/** Category classification for display types */ +const DISPLAY_TYPE_CATEGORY: Record< + string, + "common" | "specialized" | "internal" +> = { + big_number: "common", + line: "common", + area: "common", + bar: "common", + table: "common", + stacked_area: "specialized", + top_n: "specialized", + categorical_bar: "specialized", + text: "specialized", + details: "internal", + wheel: "internal", + rage_and_dead_clicks: "internal", + server_tree: "internal", + agents_traces_table: "internal", +}; + +export type WidgetTypesResult = { + grid: { columns: number }; + displayTypes: Array<{ + name: string; + defaultWidth: number; + defaultHeight: number; + category: "common" | "specialized" | "internal"; + }>; + datasets: Array<{ name: string; isDefault: boolean }>; + aggregateFunctions: { + spans: readonly string[]; + discover: readonly string[]; + }; + aggregateAliases: Record; +}; + +export const typesCommand = buildCommand({ + docs: { + brief: "Show available widget display types and layout info", + fullDescription: + "Show available widget display types with default grid sizes, datasets, " + + "and aggregate functions.\n\n" + + "Sentry dashboards use a 6-column grid. When adding widgets, aim to fill " + + "complete rows (widths should sum to 6).\n\n" + + "Display types are categorized as:\n" + + " common — general-purpose (big_number, line, area, bar, table)\n" + + " specialized — for specific use cases (stacked_area, top_n, categorical_bar, text)\n" + + " internal — Sentry-internal, rarely used directly\n\n" + + "Examples:\n" + + " sentry dashboard widget types\n" + + " sentry dashboard widget types --json", + }, + output: { + human: formatWidgetTypes, + }, + parameters: { + positional: { kind: "array", parameter: { brief: "", parse: String } }, + flags: {}, + aliases: {}, + }, + // biome-ignore lint/suspicious/useAwait: buildCommand requires async generator + async *func(this: SentryContext, _flags: { readonly json: boolean }) { + const displayTypes = DISPLAY_TYPES.map((name) => { + const size = DEFAULT_WIDGET_SIZE[name as DisplayType] ?? FALLBACK_SIZE; + return { + name, + defaultWidth: size.w, + defaultHeight: size.h, + category: DISPLAY_TYPE_CATEGORY[name] ?? ("internal" as const), + }; + }); + + const datasets = WIDGET_TYPES.map((name) => ({ + name, + isDefault: name === DEFAULT_WIDGET_TYPE, + })); + + const result: WidgetTypesResult = { + grid: { columns: GRID_COLUMNS }, + displayTypes, + datasets, + aggregateFunctions: { + spans: SPAN_AGGREGATE_FUNCTIONS, + discover: DISCOVER_AGGREGATE_FUNCTIONS, + }, + aggregateAliases: { ...AGGREGATE_ALIASES }, + }; + + yield new CommandOutput(result); + }, +}); diff --git a/src/lib/formatters/human.ts b/src/lib/formatters/human.ts index 8f4ffaa60..91b20fd73 100644 --- a/src/lib/formatters/human.ts +++ b/src/lib/formatters/human.ts @@ -10,6 +10,7 @@ // biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import import * as Sentry from "@sentry/node-core/light"; import prettyMs from "pretty-ms"; +import type { WidgetTypesResult } from "../../commands/dashboard/widget/types.js"; import type { DashboardDetail, DashboardWidget, @@ -2276,3 +2277,66 @@ export function formatWidgetEdited(result: { ]; return renderMarkdown(lines.join("\n")); } + +/** + * Format widget types info for human-readable output. + */ +export function formatWidgetTypes(result: WidgetTypesResult): string { + const parts: string[] = []; + const buffer: Writer = { write: (s) => parts.push(s) }; + + // Grid header + parts.push(renderMarkdown(`**Grid:** ${result.grid.columns} columns`)); + parts.push("\n"); + + // Display types table + type DisplayRow = WidgetTypesResult["displayTypes"][number]; + const dtColumns: Column[] = [ + { header: "DISPLAY TYPE", value: (r) => r.name }, + { header: "WIDTH:", value: (r) => String(r.defaultWidth), align: "right" }, + { + header: "HEIGHT:", + value: (r) => String(r.defaultHeight), + align: "right", + }, + { header: "CATEGORY", value: (r) => r.category }, + ]; + writeTable(buffer, result.displayTypes, dtColumns); + + parts.push("\n"); + + // Datasets table + type DatasetRow = WidgetTypesResult["datasets"][number]; + const dsColumns: Column[] = [ + { header: "DATASET", value: (r) => r.name }, + { header: "DEFAULT", value: (r) => (r.isDefault ? "✓" : "") }, + ]; + writeTable(buffer, result.datasets, dsColumns); + + parts.push("\n"); + + // Aggregate functions + const aggLines: string[] = []; + aggLines.push( + `**Aggregates (spans):** ${result.aggregateFunctions.spans.join(", ")}` + ); + + const spanSet = new Set(result.aggregateFunctions.spans); + const discoverOnly = result.aggregateFunctions.discover.filter( + (f) => !spanSet.has(f) + ); + if (discoverOnly.length > 0) { + aggLines.push(`**Aggregates (discover):** + ${discoverOnly.join(", ")}`); + } + + const aliasEntries = Object.entries(result.aggregateAliases); + if (aliasEntries.length > 0) { + aggLines.push( + `**Aliases:** ${aliasEntries.map(([k, v]) => `${k} → ${v}`).join(", ")}` + ); + } + + parts.push(renderMarkdown(aggLines.join("\n"))); + + return parts.join("").trimEnd(); +} diff --git a/src/types/dashboard.ts b/src/types/dashboard.ts index 241c585fc..e8cf66b50 100644 --- a/src/types/dashboard.ts +++ b/src/types/dashboard.ts @@ -479,10 +479,10 @@ export function prepareWidgetQueries(widget: DashboardWidget): DashboardWidget { // --------------------------------------------------------------------------- /** Sentry dashboard grid column count */ -const GRID_COLUMNS = 6; +export const GRID_COLUMNS = 6; /** Default widget dimensions by displayType */ -const DEFAULT_WIDGET_SIZE: Partial< +export const DEFAULT_WIDGET_SIZE: Partial< Record > = { big_number: { w: 2, h: 1, minH: 1 }, @@ -491,7 +491,7 @@ const DEFAULT_WIDGET_SIZE: Partial< bar: { w: 3, h: 2, minH: 2 }, table: { w: 6, h: 2, minH: 2 }, }; -const FALLBACK_SIZE = { w: 3, h: 2, minH: 2 }; +export const FALLBACK_SIZE = { w: 3, h: 2, minH: 2 }; /** Build a set of occupied grid cells and the max bottom edge from existing layouts. */ function buildOccupiedGrid(widgets: DashboardWidget[]): { From 6942cf9723ed18dbe7d6c448b3fb1a6abdb63bf6 Mon Sep 17 00:00:00 2001 From: betegon Date: Fri, 20 Mar 2026 20:48:33 +0100 Subject: [PATCH 2/9] docs(dashboard): add layout guidance and widget types command docs Adds a "Dashboard Layout" section to agent-guidance.md explaining the 6-column grid, display type categories, default sizes, and row-filling patterns. Also documents the new `widget types` command. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/src/content/docs/agent-guidance.md | 42 +++++++++++++++++++++ docs/src/content/docs/commands/dashboard.md | 22 +++++++++++ 2 files changed, 64 insertions(+) diff --git a/docs/src/content/docs/agent-guidance.md b/docs/src/content/docs/agent-guidance.md index 5a31bfd89..e32bc919d 100644 --- a/docs/src/content/docs/agent-guidance.md +++ b/docs/src/content/docs/agent-guidance.md @@ -102,6 +102,48 @@ sentry api /api/0/organizations/my-org/ sentry api /api/0/organizations/my-org/projects/ --method POST --data '{"name":"new-project","platform":"python"}' ``` +## Dashboard Layout + +Sentry dashboards use a **6-column grid**. When adding widgets, aim to fill complete rows (widths should sum to 6). + +Default widget sizes by display type: + +| Display Type | Width | Height | Notes | +|---|---|---|---| +| `big_number` | 2 | 1 | Compact KPI — place 3 per row (2+2+2=6) | +| `line` | 3 | 2 | Half-width chart — place 2 per row (3+3=6) | +| `area` | 3 | 2 | Half-width chart — place 2 per row | +| `bar` | 3 | 2 | Half-width chart — place 2 per row | +| `table` | 6 | 2 | Full-width — always takes its own row | + +Common display types for general dashboards: `big_number`, `line`, `area`, `bar`, `table`. + +Specialized types (only use when specifically requested): `stacked_area`, `top_n`, `categorical_bar`, `text`. + +Internal types (avoid unless user explicitly asks): `details`, `wheel`, `rage_and_dead_clicks`, `server_tree`, `agents_traces_table`. + +Available datasets: `spans` (default, covers most use cases), `discover`, `issue`, `error-events`, `transaction-like`, `metrics`, `logs`. + +Run `sentry dashboard widget types --json` for the full machine-readable list including aggregate functions. + +**Row-filling examples:** + +```bash +# 3 KPIs filling one row (2+2+2 = 6) +sentry dashboard widget add "Error Count" --display big_number --query count +sentry dashboard widget add "P95 Duration" --display big_number --query p95:span.duration +sentry dashboard widget add "Throughput" --display big_number --query epm + +# 2 charts filling one row (3+3 = 6) +sentry dashboard widget add "Errors Over Time" --display line --query count +sentry dashboard widget add "Latency Over Time" --display line --query p95:span.duration + +# Full-width table (6 = 6) +sentry dashboard widget add "Top Endpoints" --display table \ + --query count --query p95:span.duration \ + --group-by transaction --sort -count --limit 10 +``` + ## Common Mistakes - **Wrong issue ID format**: Use `PROJECT-123` (short ID), not the numeric ID `123456789`. The short ID includes the project prefix. diff --git a/docs/src/content/docs/commands/dashboard.md b/docs/src/content/docs/commands/dashboard.md index 0b27e2575..6224587b6 100644 --- a/docs/src/content/docs/commands/dashboard.md +++ b/docs/src/content/docs/commands/dashboard.md @@ -323,3 +323,25 @@ sentry dashboard widget delete 'My Dashboard' --title 'Error Count' # Delete by index sentry dashboard widget delete 12345 --index 2 ``` + +### `sentry dashboard widget types` + +Show available widget display types with default grid sizes, datasets, and aggregate functions. + +Sentry dashboards use a 6-column grid. This command helps you understand the available options and how widgets fit into the layout. + +```bash +# Human-readable table +sentry dashboard widget types + +# Machine-readable JSON (recommended for agents) +sentry dashboard widget types --json +``` + +Display types are categorized as: + +| Category | Types | When to use | +|----------|-------|------------| +| common | `big_number`, `line`, `area`, `bar`, `table` | General-purpose dashboards | +| specialized | `stacked_area`, `top_n`, `categorical_bar`, `text` | Specific use cases | +| internal | `details`, `wheel`, `rage_and_dead_clicks`, `server_tree`, `agents_traces_table` | Sentry-internal, rarely used directly | From 0eb23c54ffa5ae40d2805acc9439398165dabf19 Mon Sep 17 00:00:00 2001 From: betegon Date: Fri, 20 Mar 2026 20:48:38 +0100 Subject: [PATCH 3/9] test(dashboard): add tests for widget types command Covers grid columns, display type sizes and categories, datasets, aggregate functions, aliases, and human output rendering. Co-Authored-By: Claude Opus 4.6 (1M context) --- test/commands/dashboard/widget/types.test.ts | 161 +++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 test/commands/dashboard/widget/types.test.ts diff --git a/test/commands/dashboard/widget/types.test.ts b/test/commands/dashboard/widget/types.test.ts new file mode 100644 index 000000000..5e2f40f25 --- /dev/null +++ b/test/commands/dashboard/widget/types.test.ts @@ -0,0 +1,161 @@ +/** + * Dashboard Widget Types Command Tests + * + * Tests for the widget types command in src/commands/dashboard/widget/types.ts. + * This is a purely local command (no API calls), so no mocking is needed. + */ + +import { describe, expect, mock, test } from "bun:test"; + +import { + typesCommand, + type WidgetTypesResult, +} from "../../../../src/commands/dashboard/widget/types.js"; +import { + DISCOVER_AGGREGATE_FUNCTIONS, + DISPLAY_TYPES, + SPAN_AGGREGATE_FUNCTIONS, + WIDGET_TYPES, +} from "../../../../src/types/dashboard.js"; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function createMockContext() { + const stdoutWrite = mock(() => true); + return { + context: { + stdout: { write: stdoutWrite }, + stderr: { write: mock(() => true) }, + cwd: "/tmp", + setContext: mock(() => { + // no-op for test + }), + }, + stdoutWrite, + }; +} + +async function runTypesCommand( + json = true +): Promise<{ output: string; result: WidgetTypesResult }> { + const { context, stdoutWrite } = createMockContext(); + const func = await typesCommand.loader(); + await func.call(context as never, { json } as never); + + const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); + const result = JSON.parse(output) as WidgetTypesResult; + return { output, result }; +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe("sentry dashboard widget types", () => { + test("grid reports 6 columns", async () => { + const { result } = await runTypesCommand(); + expect(result.grid.columns).toBe(6); + }); + + test("includes all display types", async () => { + const { result } = await runTypesCommand(); + const names = result.displayTypes.map((dt) => dt.name); + for (const dt of DISPLAY_TYPES) { + expect(names).toContain(dt); + } + expect(result.displayTypes).toHaveLength(DISPLAY_TYPES.length); + }); + + test("big_number has correct default size", async () => { + const { result } = await runTypesCommand(); + const bigNumber = result.displayTypes.find( + (dt) => dt.name === "big_number" + ); + expect(bigNumber).toBeDefined(); + expect(bigNumber!.defaultWidth).toBe(2); + expect(bigNumber!.defaultHeight).toBe(1); + expect(bigNumber!.category).toBe("common"); + }); + + test("table is full-width", async () => { + const { result } = await runTypesCommand(); + const table = result.displayTypes.find((dt) => dt.name === "table"); + expect(table).toBeDefined(); + expect(table!.defaultWidth).toBe(6); + expect(table!.category).toBe("common"); + }); + + test("common types are categorized correctly", async () => { + const { result } = await runTypesCommand(); + const common = result.displayTypes + .filter((dt) => dt.category === "common") + .map((dt) => dt.name); + expect(common).toContain("big_number"); + expect(common).toContain("line"); + expect(common).toContain("area"); + expect(common).toContain("bar"); + expect(common).toContain("table"); + }); + + test("includes all datasets", async () => { + const { result } = await runTypesCommand(); + const names = result.datasets.map((ds) => ds.name); + for (const wt of WIDGET_TYPES) { + expect(names).toContain(wt); + } + expect(result.datasets).toHaveLength(WIDGET_TYPES.length); + }); + + test("spans is the default dataset", async () => { + const { result } = await runTypesCommand(); + const defaults = result.datasets.filter((ds) => ds.isDefault); + expect(defaults).toHaveLength(1); + expect(defaults[0]!.name).toBe("spans"); + }); + + test("span aggregate functions are included", async () => { + const { result } = await runTypesCommand(); + const fns = result.aggregateFunctions.spans; + expect(fns).toContain("count"); + expect(fns).toContain("p95"); + expect(fns).toContain("avg"); + expect(fns).toContain("sum"); + expect(fns.length).toBe(SPAN_AGGREGATE_FUNCTIONS.length); + }); + + test("discover aggregates are a superset of span aggregates", async () => { + const { result } = await runTypesCommand(); + const spanSet = new Set(result.aggregateFunctions.spans); + const discoverSet = new Set(result.aggregateFunctions.discover); + for (const fn of spanSet) { + expect(discoverSet.has(fn)).toBe(true); + } + expect(discoverSet.size).toBeGreaterThan(spanSet.size); + expect(result.aggregateFunctions.discover.length).toBe( + DISCOVER_AGGREGATE_FUNCTIONS.length + ); + }); + + test("aggregate aliases are included", async () => { + const { result } = await runTypesCommand(); + expect(result.aggregateAliases.spm).toBe("epm"); + expect(result.aggregateAliases.tpm).toBe("epm"); + expect(result.aggregateAliases.sps).toBe("eps"); + expect(result.aggregateAliases.tps).toBe("eps"); + }); + + test("human output contains grid info and display types", async () => { + const { context, stdoutWrite } = createMockContext(); + const func = await typesCommand.loader(); + await func.call(context as never, { json: false } as never); + + const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); + expect(output).toContain("6 columns"); + expect(output).toContain("big_number"); + expect(output).toContain("table"); + expect(output).toContain("spans"); + expect(output).toContain("count"); + }); +}); From 748081f6d4e287f787db43a12c0c42a81b2fe951 Mon Sep 17 00:00:00 2001 From: betegon Date: Fri, 20 Mar 2026 20:48:43 +0100 Subject: [PATCH 4/9] chore(skill): regenerate skill files with widget types and layout info Co-Authored-By: Claude Opus 4.6 (1M context) --- plugins/sentry-cli/skills/sentry-cli/SKILL.md | 43 +++++++++++++++++++ .../sentry-cli/references/dashboards.md | 8 +++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/plugins/sentry-cli/skills/sentry-cli/SKILL.md b/plugins/sentry-cli/skills/sentry-cli/SKILL.md index 09ec0e10b..1d1ba96cb 100644 --- a/plugins/sentry-cli/skills/sentry-cli/SKILL.md +++ b/plugins/sentry-cli/skills/sentry-cli/SKILL.md @@ -112,6 +112,48 @@ sentry api /api/0/organizations/my-org/ sentry api /api/0/organizations/my-org/projects/ --method POST --data '{"name":"new-project","platform":"python"}' ``` +### Dashboard Layout + +Sentry dashboards use a **6-column grid**. When adding widgets, aim to fill complete rows (widths should sum to 6). + +Default widget sizes by display type: + +| Display Type | Width | Height | Notes | +|---|---|---|---| +| `big_number` | 2 | 1 | Compact KPI — place 3 per row (2+2+2=6) | +| `line` | 3 | 2 | Half-width chart — place 2 per row (3+3=6) | +| `area` | 3 | 2 | Half-width chart — place 2 per row | +| `bar` | 3 | 2 | Half-width chart — place 2 per row | +| `table` | 6 | 2 | Full-width — always takes its own row | + +Common display types for general dashboards: `big_number`, `line`, `area`, `bar`, `table`. + +Specialized types (only use when specifically requested): `stacked_area`, `top_n`, `categorical_bar`, `text`. + +Internal types (avoid unless user explicitly asks): `details`, `wheel`, `rage_and_dead_clicks`, `server_tree`, `agents_traces_table`. + +Available datasets: `spans` (default, covers most use cases), `discover`, `issue`, `error-events`, `transaction-like`, `metrics`, `logs`. + +Run `sentry dashboard widget types --json` for the full machine-readable list including aggregate functions. + +**Row-filling examples:** + +```bash +# 3 KPIs filling one row (2+2+2 = 6) +sentry dashboard widget add "Error Count" --display big_number --query count +sentry dashboard widget add "P95 Duration" --display big_number --query p95:span.duration +sentry dashboard widget add "Throughput" --display big_number --query epm + +# 2 charts filling one row (3+3 = 6) +sentry dashboard widget add "Errors Over Time" --display line --query count +sentry dashboard widget add "Latency Over Time" --display line --query p95:span.duration + +# Full-width table (6 = 6) +sentry dashboard widget add "Top Endpoints" --display table \ + --query count --query p95:span.duration \ + --group-by transaction --sort -count --limit 10 +``` + ### Common Mistakes - **Wrong issue ID format**: Use `PROJECT-123` (short ID), not the numeric ID `123456789`. The short ID includes the project prefix. @@ -229,6 +271,7 @@ Manage Sentry dashboards - `sentry dashboard widget add ` — Add a widget to a dashboard - `sentry dashboard widget edit ` — Edit a widget in a dashboard - `sentry dashboard widget delete ` — Delete a widget from a dashboard +- `sentry dashboard widget types ` — Show available widget display types and layout info → Full flags and examples: `references/dashboards.md` diff --git a/plugins/sentry-cli/skills/sentry-cli/references/dashboards.md b/plugins/sentry-cli/skills/sentry-cli/references/dashboards.md index c53562b74..561d6e97a 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/dashboards.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/dashboards.md @@ -90,7 +90,7 @@ sentry dashboard widget add 'Frontend Performance' "Error Count" --display big_n Add a widget to a dashboard **Flags:** -- `-d, --display - Display type (line, bar, table, big_number, ...)` +- `-d, --display - Display type (big_number, line, area, bar, table, stacked_area, top_n, text, ...)` - `--dataset - Widget dataset (default: spans)` - `-q, --query ... - Aggregate expression (e.g. count, p95:span.duration)` - `-w, --where - Search conditions filter (e.g. is:unresolved)` @@ -106,7 +106,7 @@ Edit a widget in a dashboard - `-i, --index - Widget index (0-based)` - `-t, --title - Widget title to match` - `--new-title - New widget title` -- `-d, --display - Display type (line, bar, table, big_number, ...)` +- `-d, --display - Display type (big_number, line, area, bar, table, stacked_area, top_n, text, ...)` - `--dataset - Widget dataset (default: spans)` - `-q, --query ... - Aggregate expression (e.g. count, p95:span.duration)` - `-w, --where - Search conditions filter (e.g. is:unresolved)` @@ -122,4 +122,8 @@ Delete a widget from a dashboard - `-i, --index - Widget index (0-based)` - `-t, --title - Widget title to match` +### `sentry dashboard widget types ` + +Show available widget display types and layout info + All commands also support `--json`, `--fields`, `--help`, `--log-level`, and `--verbose` flags. From 24966dbf759f9c2eb35995ec1556415f9d336e1c Mon Sep 17 00:00:00 2001 From: betegon Date: Fri, 20 Mar 2026 20:52:53 +0100 Subject: [PATCH 5/9] docs(dashboard): list all 14 display types in agent guidance table Previously only 5 common types were in the table with the rest mentioned in text. Now all display types are listed with their sizes and categories. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/src/content/docs/agent-guidance.md | 35 +++++++++++-------- plugins/sentry-cli/skills/sentry-cli/SKILL.md | 35 +++++++++++-------- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/docs/src/content/docs/agent-guidance.md b/docs/src/content/docs/agent-guidance.md index e32bc919d..68b9f5801 100644 --- a/docs/src/content/docs/agent-guidance.md +++ b/docs/src/content/docs/agent-guidance.md @@ -106,21 +106,26 @@ sentry api /api/0/organizations/my-org/projects/ --method POST --data '{"name":" Sentry dashboards use a **6-column grid**. When adding widgets, aim to fill complete rows (widths should sum to 6). -Default widget sizes by display type: - -| Display Type | Width | Height | Notes | -|---|---|---|---| -| `big_number` | 2 | 1 | Compact KPI — place 3 per row (2+2+2=6) | -| `line` | 3 | 2 | Half-width chart — place 2 per row (3+3=6) | -| `area` | 3 | 2 | Half-width chart — place 2 per row | -| `bar` | 3 | 2 | Half-width chart — place 2 per row | -| `table` | 6 | 2 | Full-width — always takes its own row | - -Common display types for general dashboards: `big_number`, `line`, `area`, `bar`, `table`. - -Specialized types (only use when specifically requested): `stacked_area`, `top_n`, `categorical_bar`, `text`. - -Internal types (avoid unless user explicitly asks): `details`, `wheel`, `rage_and_dead_clicks`, `server_tree`, `agents_traces_table`. +Display types with default sizes: + +| Display Type | Width | Height | Category | Notes | +|---|---|---|---|---| +| `big_number` | 2 | 1 | common | Compact KPI — place 3 per row (2+2+2=6) | +| `line` | 3 | 2 | common | Half-width chart — place 2 per row (3+3=6) | +| `area` | 3 | 2 | common | Half-width chart — place 2 per row | +| `bar` | 3 | 2 | common | Half-width chart — place 2 per row | +| `table` | 6 | 2 | common | Full-width — always takes its own row | +| `stacked_area` | 3 | 2 | specialized | Stacked area chart | +| `top_n` | 3 | 2 | specialized | Top N ranked list | +| `categorical_bar` | 3 | 2 | specialized | Categorical bar chart | +| `text` | 3 | 2 | specialized | Static text/markdown widget | +| `details` | 3 | 2 | internal | Detail view | +| `wheel` | 3 | 2 | internal | Pie/wheel chart | +| `rage_and_dead_clicks` | 3 | 2 | internal | Rage/dead click visualization | +| `server_tree` | 3 | 2 | internal | Hierarchical tree display | +| `agents_traces_table` | 3 | 2 | internal | Agents traces table | + +Use **common** types for general dashboards. Use **specialized** only when specifically requested. Avoid **internal** types unless the user explicitly asks. Available datasets: `spans` (default, covers most use cases), `discover`, `issue`, `error-events`, `transaction-like`, `metrics`, `logs`. diff --git a/plugins/sentry-cli/skills/sentry-cli/SKILL.md b/plugins/sentry-cli/skills/sentry-cli/SKILL.md index 1d1ba96cb..95e06a9fd 100644 --- a/plugins/sentry-cli/skills/sentry-cli/SKILL.md +++ b/plugins/sentry-cli/skills/sentry-cli/SKILL.md @@ -116,21 +116,26 @@ sentry api /api/0/organizations/my-org/projects/ --method POST --data '{"name":" Sentry dashboards use a **6-column grid**. When adding widgets, aim to fill complete rows (widths should sum to 6). -Default widget sizes by display type: - -| Display Type | Width | Height | Notes | -|---|---|---|---| -| `big_number` | 2 | 1 | Compact KPI — place 3 per row (2+2+2=6) | -| `line` | 3 | 2 | Half-width chart — place 2 per row (3+3=6) | -| `area` | 3 | 2 | Half-width chart — place 2 per row | -| `bar` | 3 | 2 | Half-width chart — place 2 per row | -| `table` | 6 | 2 | Full-width — always takes its own row | - -Common display types for general dashboards: `big_number`, `line`, `area`, `bar`, `table`. - -Specialized types (only use when specifically requested): `stacked_area`, `top_n`, `categorical_bar`, `text`. - -Internal types (avoid unless user explicitly asks): `details`, `wheel`, `rage_and_dead_clicks`, `server_tree`, `agents_traces_table`. +Display types with default sizes: + +| Display Type | Width | Height | Category | Notes | +|---|---|---|---|---| +| `big_number` | 2 | 1 | common | Compact KPI — place 3 per row (2+2+2=6) | +| `line` | 3 | 2 | common | Half-width chart — place 2 per row (3+3=6) | +| `area` | 3 | 2 | common | Half-width chart — place 2 per row | +| `bar` | 3 | 2 | common | Half-width chart — place 2 per row | +| `table` | 6 | 2 | common | Full-width — always takes its own row | +| `stacked_area` | 3 | 2 | specialized | Stacked area chart | +| `top_n` | 3 | 2 | specialized | Top N ranked list | +| `categorical_bar` | 3 | 2 | specialized | Categorical bar chart | +| `text` | 3 | 2 | specialized | Static text/markdown widget | +| `details` | 3 | 2 | internal | Detail view | +| `wheel` | 3 | 2 | internal | Pie/wheel chart | +| `rage_and_dead_clicks` | 3 | 2 | internal | Rage/dead click visualization | +| `server_tree` | 3 | 2 | internal | Hierarchical tree display | +| `agents_traces_table` | 3 | 2 | internal | Agents traces table | + +Use **common** types for general dashboards. Use **specialized** only when specifically requested. Avoid **internal** types unless the user explicitly asks. Available datasets: `spans` (default, covers most use cases), `discover`, `issue`, `error-events`, `transaction-like`, `metrics`, `logs`. From 694aa140cefb334e917b424bc1421802ec3e559f Mon Sep 17 00:00:00 2001 From: betegon Date: Fri, 20 Mar 2026 20:54:09 +0100 Subject: [PATCH 6/9] docs(dashboard): list all display types with sizes in widget help and types command The widget --help and widget types --help now show all 14 display types with their default grid dimensions, matching the agent guidance table. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/commands/dashboard/widget/index.ts | 7 +++++-- src/commands/dashboard/widget/types.ts | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/commands/dashboard/widget/index.ts b/src/commands/dashboard/widget/index.ts index a91c7bffc..595459bcf 100644 --- a/src/commands/dashboard/widget/index.ts +++ b/src/commands/dashboard/widget/index.ts @@ -15,8 +15,11 @@ export const widgetRoute = buildRouteMap({ brief: "Manage dashboard widgets", fullDescription: "Add, edit, or delete widgets in a Sentry dashboard.\n\n" + - "Dashboards use a 6-column grid. Widget widths should sum to 6 per row.\n" + - "Common display types: big_number (2 cols), line/area/bar (3 cols), table (6 cols).\n" + + "Dashboards use a 6-column grid. Widget widths should sum to 6 per row.\n\n" + + "Display types (width × height):\n" + + " common: big_number (2×1), line (3×2), area (3×2), bar (3×2), table (6×2)\n" + + " specialized: stacked_area (3×2), top_n (3×2), categorical_bar (3×2), text (3×2)\n" + + " internal: details, wheel, rage_and_dead_clicks, server_tree, agents_traces_table (3×2)\n\n" + "Default dataset: spans. Run 'sentry dashboard widget types' for the full list.\n\n" + "Commands:\n" + " add Add a widget to a dashboard\n" + diff --git a/src/commands/dashboard/widget/types.ts b/src/commands/dashboard/widget/types.ts index d08e52e51..453718c89 100644 --- a/src/commands/dashboard/widget/types.ts +++ b/src/commands/dashboard/widget/types.ts @@ -67,10 +67,10 @@ export const typesCommand = buildCommand({ "and aggregate functions.\n\n" + "Sentry dashboards use a 6-column grid. When adding widgets, aim to fill " + "complete rows (widths should sum to 6).\n\n" + - "Display types are categorized as:\n" + - " common — general-purpose (big_number, line, area, bar, table)\n" + - " specialized — for specific use cases (stacked_area, top_n, categorical_bar, text)\n" + - " internal — Sentry-internal, rarely used directly\n\n" + + "Display types (width × height):\n" + + " common: big_number (2×1), line (3×2), area (3×2), bar (3×2), table (6×2)\n" + + " specialized: stacked_area (3×2), top_n (3×2), categorical_bar (3×2), text (3×2)\n" + + " internal: details, wheel, rage_and_dead_clicks, server_tree, agents_traces_table (3×2)\n\n" + "Examples:\n" + " sentry dashboard widget types\n" + " sentry dashboard widget types --json", From 4278a1fdd3f447e73b6ccad93f34d08cc5897cd8 Mon Sep 17 00:00:00 2001 From: betegon Date: Fri, 20 Mar 2026 20:59:15 +0100 Subject: [PATCH 7/9] refactor(dashboard): co-locate widget types formatter with its command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move formatWidgetTypes from human.ts into the types.ts command file, matching the pattern used by dashboard list. Removes the unusual command→formatter cross-import from human.ts. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/commands/dashboard/widget/types.ts | 72 +++++++++++++++++++++++++- src/lib/formatters/human.ts | 64 ----------------------- 2 files changed, 70 insertions(+), 66 deletions(-) diff --git a/src/commands/dashboard/widget/types.ts b/src/commands/dashboard/widget/types.ts index 453718c89..39af756b7 100644 --- a/src/commands/dashboard/widget/types.ts +++ b/src/commands/dashboard/widget/types.ts @@ -7,8 +7,9 @@ import type { SentryContext } from "../../../context.js"; import { buildCommand } from "../../../lib/command.js"; -import { formatWidgetTypes } from "../../../lib/formatters/human.js"; +import { renderMarkdown } from "../../../lib/formatters/markdown.js"; import { CommandOutput } from "../../../lib/formatters/output.js"; +import { type Column, writeTable } from "../../../lib/formatters/table.js"; import { AGGREGATE_ALIASES, DEFAULT_WIDGET_SIZE, @@ -21,6 +22,7 @@ import { SPAN_AGGREGATE_FUNCTIONS, WIDGET_TYPES, } from "../../../types/dashboard.js"; +import type { Writer } from "../../../types/index.js"; /** Category classification for display types */ const DISPLAY_TYPE_CATEGORY: Record< @@ -59,6 +61,72 @@ export type WidgetTypesResult = { aggregateAliases: Record; }; +/** + * Format widget types info for human-readable terminal output. + * + * Renders grid info, a display types table, a datasets table, and + * aggregate function summaries. + */ +function formatWidgetTypesHuman(result: WidgetTypesResult): string { + const parts: string[] = []; + const buffer: Writer = { write: (s) => parts.push(s) }; + + // Grid header + parts.push(renderMarkdown(`**Grid:** ${result.grid.columns} columns`)); + parts.push("\n"); + + // Display types table + type DisplayRow = WidgetTypesResult["displayTypes"][number]; + const dtColumns: Column[] = [ + { header: "DISPLAY TYPE", value: (r) => r.name }, + { header: "WIDTH:", value: (r) => String(r.defaultWidth), align: "right" }, + { + header: "HEIGHT:", + value: (r) => String(r.defaultHeight), + align: "right", + }, + { header: "CATEGORY", value: (r) => r.category }, + ]; + writeTable(buffer, result.displayTypes, dtColumns); + + parts.push("\n"); + + // Datasets table + type DatasetRow = WidgetTypesResult["datasets"][number]; + const dsColumns: Column[] = [ + { header: "DATASET", value: (r) => r.name }, + { header: "DEFAULT", value: (r) => (r.isDefault ? "✓" : "") }, + ]; + writeTable(buffer, result.datasets, dsColumns); + + parts.push("\n"); + + // Aggregate functions + const aggLines: string[] = []; + aggLines.push( + `**Aggregates (spans):** ${result.aggregateFunctions.spans.join(", ")}` + ); + + const spanSet = new Set(result.aggregateFunctions.spans); + const discoverOnly = result.aggregateFunctions.discover.filter( + (f) => !spanSet.has(f) + ); + if (discoverOnly.length > 0) { + aggLines.push(`**Aggregates (discover):** + ${discoverOnly.join(", ")}`); + } + + const aliasEntries = Object.entries(result.aggregateAliases); + if (aliasEntries.length > 0) { + aggLines.push( + `**Aliases:** ${aliasEntries.map(([k, v]) => `${k} → ${v}`).join(", ")}` + ); + } + + parts.push(renderMarkdown(aggLines.join("\n"))); + + return parts.join("").trimEnd(); +} + export const typesCommand = buildCommand({ docs: { brief: "Show available widget display types and layout info", @@ -76,7 +144,7 @@ export const typesCommand = buildCommand({ " sentry dashboard widget types --json", }, output: { - human: formatWidgetTypes, + human: formatWidgetTypesHuman, }, parameters: { positional: { kind: "array", parameter: { brief: "", parse: String } }, diff --git a/src/lib/formatters/human.ts b/src/lib/formatters/human.ts index 91b20fd73..8f4ffaa60 100644 --- a/src/lib/formatters/human.ts +++ b/src/lib/formatters/human.ts @@ -10,7 +10,6 @@ // biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import import * as Sentry from "@sentry/node-core/light"; import prettyMs from "pretty-ms"; -import type { WidgetTypesResult } from "../../commands/dashboard/widget/types.js"; import type { DashboardDetail, DashboardWidget, @@ -2277,66 +2276,3 @@ export function formatWidgetEdited(result: { ]; return renderMarkdown(lines.join("\n")); } - -/** - * Format widget types info for human-readable output. - */ -export function formatWidgetTypes(result: WidgetTypesResult): string { - const parts: string[] = []; - const buffer: Writer = { write: (s) => parts.push(s) }; - - // Grid header - parts.push(renderMarkdown(`**Grid:** ${result.grid.columns} columns`)); - parts.push("\n"); - - // Display types table - type DisplayRow = WidgetTypesResult["displayTypes"][number]; - const dtColumns: Column[] = [ - { header: "DISPLAY TYPE", value: (r) => r.name }, - { header: "WIDTH:", value: (r) => String(r.defaultWidth), align: "right" }, - { - header: "HEIGHT:", - value: (r) => String(r.defaultHeight), - align: "right", - }, - { header: "CATEGORY", value: (r) => r.category }, - ]; - writeTable(buffer, result.displayTypes, dtColumns); - - parts.push("\n"); - - // Datasets table - type DatasetRow = WidgetTypesResult["datasets"][number]; - const dsColumns: Column[] = [ - { header: "DATASET", value: (r) => r.name }, - { header: "DEFAULT", value: (r) => (r.isDefault ? "✓" : "") }, - ]; - writeTable(buffer, result.datasets, dsColumns); - - parts.push("\n"); - - // Aggregate functions - const aggLines: string[] = []; - aggLines.push( - `**Aggregates (spans):** ${result.aggregateFunctions.spans.join(", ")}` - ); - - const spanSet = new Set(result.aggregateFunctions.spans); - const discoverOnly = result.aggregateFunctions.discover.filter( - (f) => !spanSet.has(f) - ); - if (discoverOnly.length > 0) { - aggLines.push(`**Aggregates (discover):** + ${discoverOnly.join(", ")}`); - } - - const aliasEntries = Object.entries(result.aggregateAliases); - if (aliasEntries.length > 0) { - aggLines.push( - `**Aliases:** ${aliasEntries.map(([k, v]) => `${k} → ${v}`).join(", ")}` - ); - } - - parts.push(renderMarkdown(aggLines.join("\n"))); - - return parts.join("").trimEnd(); -} From a0198f4fedc38d76a621136a0966e0c021710161 Mon Sep 17 00:00:00 2001 From: betegon Date: Fri, 20 Mar 2026 21:06:01 +0100 Subject: [PATCH 8/9] refactor(dashboard): remove widget types command, put all info in widget --help Removes the standalone `sentry dashboard widget types` command. All the info (display types, sizes, datasets, aggregates, aliases, row-filling examples) now lives directly in `sentry dashboard widget --help` so agents get it immediately without running a separate command. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/src/content/docs/agent-guidance.md | 2 +- docs/src/content/docs/commands/dashboard.md | 22 --- plugins/sentry-cli/skills/sentry-cli/SKILL.md | 3 +- .../sentry-cli/references/dashboards.md | 4 - src/commands/dashboard/index.ts | 2 +- src/commands/dashboard/widget/index.ts | 30 ++- src/commands/dashboard/widget/types.ts | 184 ------------------ src/types/dashboard.ts | 6 +- test/commands/dashboard/widget/types.test.ts | 161 --------------- 9 files changed, 30 insertions(+), 384 deletions(-) delete mode 100644 src/commands/dashboard/widget/types.ts delete mode 100644 test/commands/dashboard/widget/types.test.ts diff --git a/docs/src/content/docs/agent-guidance.md b/docs/src/content/docs/agent-guidance.md index 68b9f5801..12cc3e60b 100644 --- a/docs/src/content/docs/agent-guidance.md +++ b/docs/src/content/docs/agent-guidance.md @@ -129,7 +129,7 @@ Use **common** types for general dashboards. Use **specialized** only when speci Available datasets: `spans` (default, covers most use cases), `discover`, `issue`, `error-events`, `transaction-like`, `metrics`, `logs`. -Run `sentry dashboard widget types --json` for the full machine-readable list including aggregate functions. +Run `sentry dashboard widget --help` for the full list including aggregate functions. **Row-filling examples:** diff --git a/docs/src/content/docs/commands/dashboard.md b/docs/src/content/docs/commands/dashboard.md index 6224587b6..0b27e2575 100644 --- a/docs/src/content/docs/commands/dashboard.md +++ b/docs/src/content/docs/commands/dashboard.md @@ -323,25 +323,3 @@ sentry dashboard widget delete 'My Dashboard' --title 'Error Count' # Delete by index sentry dashboard widget delete 12345 --index 2 ``` - -### `sentry dashboard widget types` - -Show available widget display types with default grid sizes, datasets, and aggregate functions. - -Sentry dashboards use a 6-column grid. This command helps you understand the available options and how widgets fit into the layout. - -```bash -# Human-readable table -sentry dashboard widget types - -# Machine-readable JSON (recommended for agents) -sentry dashboard widget types --json -``` - -Display types are categorized as: - -| Category | Types | When to use | -|----------|-------|------------| -| common | `big_number`, `line`, `area`, `bar`, `table` | General-purpose dashboards | -| specialized | `stacked_area`, `top_n`, `categorical_bar`, `text` | Specific use cases | -| internal | `details`, `wheel`, `rage_and_dead_clicks`, `server_tree`, `agents_traces_table` | Sentry-internal, rarely used directly | diff --git a/plugins/sentry-cli/skills/sentry-cli/SKILL.md b/plugins/sentry-cli/skills/sentry-cli/SKILL.md index 95e06a9fd..337dd3a61 100644 --- a/plugins/sentry-cli/skills/sentry-cli/SKILL.md +++ b/plugins/sentry-cli/skills/sentry-cli/SKILL.md @@ -139,7 +139,7 @@ Use **common** types for general dashboards. Use **specialized** only when speci Available datasets: `spans` (default, covers most use cases), `discover`, `issue`, `error-events`, `transaction-like`, `metrics`, `logs`. -Run `sentry dashboard widget types --json` for the full machine-readable list including aggregate functions. +Run `sentry dashboard widget --help` for the full list including aggregate functions. **Row-filling examples:** @@ -276,7 +276,6 @@ Manage Sentry dashboards - `sentry dashboard widget add ` — Add a widget to a dashboard - `sentry dashboard widget edit ` — Edit a widget in a dashboard - `sentry dashboard widget delete ` — Delete a widget from a dashboard -- `sentry dashboard widget types ` — Show available widget display types and layout info → Full flags and examples: `references/dashboards.md` diff --git a/plugins/sentry-cli/skills/sentry-cli/references/dashboards.md b/plugins/sentry-cli/skills/sentry-cli/references/dashboards.md index 561d6e97a..1d4698847 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/dashboards.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/dashboards.md @@ -122,8 +122,4 @@ Delete a widget from a dashboard - `-i, --index - Widget index (0-based)` - `-t, --title - Widget title to match` -### `sentry dashboard widget types ` - -Show available widget display types and layout info - All commands also support `--json`, `--fields`, `--help`, `--log-level`, and `--verbose` flags. diff --git a/src/commands/dashboard/index.ts b/src/commands/dashboard/index.ts index 2c06b18db..a47d1f6c1 100644 --- a/src/commands/dashboard/index.ts +++ b/src/commands/dashboard/index.ts @@ -19,7 +19,7 @@ export const dashboardRoute = buildRouteMap({ " list List dashboards\n" + " view View a dashboard\n" + " create Create a dashboard\n" + - " widget Manage dashboard widgets (add, edit, delete, types)", + " widget Manage dashboard widgets (add, edit, delete)", hideRoute: {}, }, }); diff --git a/src/commands/dashboard/widget/index.ts b/src/commands/dashboard/widget/index.ts index 595459bcf..facdc39b4 100644 --- a/src/commands/dashboard/widget/index.ts +++ b/src/commands/dashboard/widget/index.ts @@ -2,14 +2,12 @@ import { buildRouteMap } from "@stricli/core"; import { addCommand } from "./add.js"; import { deleteCommand } from "./delete.js"; import { editCommand } from "./edit.js"; -import { typesCommand } from "./types.js"; export const widgetRoute = buildRouteMap({ routes: { add: addCommand, edit: editCommand, delete: deleteCommand, - types: typesCommand, }, docs: { brief: "Manage dashboard widgets", @@ -19,13 +17,33 @@ export const widgetRoute = buildRouteMap({ "Display types (width × height):\n" + " common: big_number (2×1), line (3×2), area (3×2), bar (3×2), table (6×2)\n" + " specialized: stacked_area (3×2), top_n (3×2), categorical_bar (3×2), text (3×2)\n" + - " internal: details, wheel, rage_and_dead_clicks, server_tree, agents_traces_table (3×2)\n\n" + - "Default dataset: spans. Run 'sentry dashboard widget types' for the full list.\n\n" + + " internal: details (3×2), wheel (3×2), rage_and_dead_clicks (3×2),\n" + + " server_tree (3×2), agents_traces_table (3×2)\n\n" + + "Datasets: spans (default), discover, issue, error-events, transaction-like,\n" + + " metrics, logs, tracemetrics, preprod-app-size\n\n" + + "Aggregates (spans): count, count_unique, sum, avg, percentile, p50, p75,\n" + + " p90, p95, p99, p100, eps, epm, any, min, max\n" + + "Aggregates (discover adds): failure_count, failure_rate, apdex,\n" + + " count_miserable, user_misery, count_web_vitals, count_if, count_at_least,\n" + + " last_seen, latest_event, var, stddev, cov, corr, performance_score,\n" + + " opportunity_score, count_scores\n" + + "Aliases: spm → epm, sps → eps, tpm → epm, tps → eps\n\n" + + "Row-filling examples:\n" + + " # 3 KPIs (2+2+2 = 6)\n" + + ' sentry dashboard widget add "Error Count" --display big_number --query count\n' + + ' sentry dashboard widget add "P95" --display big_number --query p95:span.duration\n' + + ' sentry dashboard widget add "Throughput" --display big_number --query epm\n\n' + + " # 2 charts (3+3 = 6)\n" + + ' sentry dashboard widget add "Errors" --display line --query count\n' + + ' sentry dashboard widget add "Latency" --display line --query p95:span.duration\n\n' + + " # Full-width table (6 = 6)\n" + + ' sentry dashboard widget add "Top Endpoints" --display table \\\n' + + " --query count --query p95:span.duration --group-by transaction \\\n" + + " --sort -count --limit 10\n\n" + "Commands:\n" + " add Add a widget to a dashboard\n" + " edit Edit a widget in a dashboard\n" + - " delete Delete a widget from a dashboard\n" + - " types Show available display types and layout info", + " delete Delete a widget from a dashboard", hideRoute: {}, }, }); diff --git a/src/commands/dashboard/widget/types.ts b/src/commands/dashboard/widget/types.ts deleted file mode 100644 index 39af756b7..000000000 --- a/src/commands/dashboard/widget/types.ts +++ /dev/null @@ -1,184 +0,0 @@ -/** - * sentry dashboard widget types - * - * Show available widget display types, default sizes, datasets, - * and aggregate functions. Purely local — no API calls or auth needed. - */ - -import type { SentryContext } from "../../../context.js"; -import { buildCommand } from "../../../lib/command.js"; -import { renderMarkdown } from "../../../lib/formatters/markdown.js"; -import { CommandOutput } from "../../../lib/formatters/output.js"; -import { type Column, writeTable } from "../../../lib/formatters/table.js"; -import { - AGGREGATE_ALIASES, - DEFAULT_WIDGET_SIZE, - DEFAULT_WIDGET_TYPE, - DISCOVER_AGGREGATE_FUNCTIONS, - DISPLAY_TYPES, - type DisplayType, - FALLBACK_SIZE, - GRID_COLUMNS, - SPAN_AGGREGATE_FUNCTIONS, - WIDGET_TYPES, -} from "../../../types/dashboard.js"; -import type { Writer } from "../../../types/index.js"; - -/** Category classification for display types */ -const DISPLAY_TYPE_CATEGORY: Record< - string, - "common" | "specialized" | "internal" -> = { - big_number: "common", - line: "common", - area: "common", - bar: "common", - table: "common", - stacked_area: "specialized", - top_n: "specialized", - categorical_bar: "specialized", - text: "specialized", - details: "internal", - wheel: "internal", - rage_and_dead_clicks: "internal", - server_tree: "internal", - agents_traces_table: "internal", -}; - -export type WidgetTypesResult = { - grid: { columns: number }; - displayTypes: Array<{ - name: string; - defaultWidth: number; - defaultHeight: number; - category: "common" | "specialized" | "internal"; - }>; - datasets: Array<{ name: string; isDefault: boolean }>; - aggregateFunctions: { - spans: readonly string[]; - discover: readonly string[]; - }; - aggregateAliases: Record; -}; - -/** - * Format widget types info for human-readable terminal output. - * - * Renders grid info, a display types table, a datasets table, and - * aggregate function summaries. - */ -function formatWidgetTypesHuman(result: WidgetTypesResult): string { - const parts: string[] = []; - const buffer: Writer = { write: (s) => parts.push(s) }; - - // Grid header - parts.push(renderMarkdown(`**Grid:** ${result.grid.columns} columns`)); - parts.push("\n"); - - // Display types table - type DisplayRow = WidgetTypesResult["displayTypes"][number]; - const dtColumns: Column[] = [ - { header: "DISPLAY TYPE", value: (r) => r.name }, - { header: "WIDTH:", value: (r) => String(r.defaultWidth), align: "right" }, - { - header: "HEIGHT:", - value: (r) => String(r.defaultHeight), - align: "right", - }, - { header: "CATEGORY", value: (r) => r.category }, - ]; - writeTable(buffer, result.displayTypes, dtColumns); - - parts.push("\n"); - - // Datasets table - type DatasetRow = WidgetTypesResult["datasets"][number]; - const dsColumns: Column[] = [ - { header: "DATASET", value: (r) => r.name }, - { header: "DEFAULT", value: (r) => (r.isDefault ? "✓" : "") }, - ]; - writeTable(buffer, result.datasets, dsColumns); - - parts.push("\n"); - - // Aggregate functions - const aggLines: string[] = []; - aggLines.push( - `**Aggregates (spans):** ${result.aggregateFunctions.spans.join(", ")}` - ); - - const spanSet = new Set(result.aggregateFunctions.spans); - const discoverOnly = result.aggregateFunctions.discover.filter( - (f) => !spanSet.has(f) - ); - if (discoverOnly.length > 0) { - aggLines.push(`**Aggregates (discover):** + ${discoverOnly.join(", ")}`); - } - - const aliasEntries = Object.entries(result.aggregateAliases); - if (aliasEntries.length > 0) { - aggLines.push( - `**Aliases:** ${aliasEntries.map(([k, v]) => `${k} → ${v}`).join(", ")}` - ); - } - - parts.push(renderMarkdown(aggLines.join("\n"))); - - return parts.join("").trimEnd(); -} - -export const typesCommand = buildCommand({ - docs: { - brief: "Show available widget display types and layout info", - fullDescription: - "Show available widget display types with default grid sizes, datasets, " + - "and aggregate functions.\n\n" + - "Sentry dashboards use a 6-column grid. When adding widgets, aim to fill " + - "complete rows (widths should sum to 6).\n\n" + - "Display types (width × height):\n" + - " common: big_number (2×1), line (3×2), area (3×2), bar (3×2), table (6×2)\n" + - " specialized: stacked_area (3×2), top_n (3×2), categorical_bar (3×2), text (3×2)\n" + - " internal: details, wheel, rage_and_dead_clicks, server_tree, agents_traces_table (3×2)\n\n" + - "Examples:\n" + - " sentry dashboard widget types\n" + - " sentry dashboard widget types --json", - }, - output: { - human: formatWidgetTypesHuman, - }, - parameters: { - positional: { kind: "array", parameter: { brief: "", parse: String } }, - flags: {}, - aliases: {}, - }, - // biome-ignore lint/suspicious/useAwait: buildCommand requires async generator - async *func(this: SentryContext, _flags: { readonly json: boolean }) { - const displayTypes = DISPLAY_TYPES.map((name) => { - const size = DEFAULT_WIDGET_SIZE[name as DisplayType] ?? FALLBACK_SIZE; - return { - name, - defaultWidth: size.w, - defaultHeight: size.h, - category: DISPLAY_TYPE_CATEGORY[name] ?? ("internal" as const), - }; - }); - - const datasets = WIDGET_TYPES.map((name) => ({ - name, - isDefault: name === DEFAULT_WIDGET_TYPE, - })); - - const result: WidgetTypesResult = { - grid: { columns: GRID_COLUMNS }, - displayTypes, - datasets, - aggregateFunctions: { - spans: SPAN_AGGREGATE_FUNCTIONS, - discover: DISCOVER_AGGREGATE_FUNCTIONS, - }, - aggregateAliases: { ...AGGREGATE_ALIASES }, - }; - - yield new CommandOutput(result); - }, -}); diff --git a/src/types/dashboard.ts b/src/types/dashboard.ts index e8cf66b50..241c585fc 100644 --- a/src/types/dashboard.ts +++ b/src/types/dashboard.ts @@ -479,10 +479,10 @@ export function prepareWidgetQueries(widget: DashboardWidget): DashboardWidget { // --------------------------------------------------------------------------- /** Sentry dashboard grid column count */ -export const GRID_COLUMNS = 6; +const GRID_COLUMNS = 6; /** Default widget dimensions by displayType */ -export const DEFAULT_WIDGET_SIZE: Partial< +const DEFAULT_WIDGET_SIZE: Partial< Record > = { big_number: { w: 2, h: 1, minH: 1 }, @@ -491,7 +491,7 @@ export const DEFAULT_WIDGET_SIZE: Partial< bar: { w: 3, h: 2, minH: 2 }, table: { w: 6, h: 2, minH: 2 }, }; -export const FALLBACK_SIZE = { w: 3, h: 2, minH: 2 }; +const FALLBACK_SIZE = { w: 3, h: 2, minH: 2 }; /** Build a set of occupied grid cells and the max bottom edge from existing layouts. */ function buildOccupiedGrid(widgets: DashboardWidget[]): { diff --git a/test/commands/dashboard/widget/types.test.ts b/test/commands/dashboard/widget/types.test.ts deleted file mode 100644 index 5e2f40f25..000000000 --- a/test/commands/dashboard/widget/types.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -/** - * Dashboard Widget Types Command Tests - * - * Tests for the widget types command in src/commands/dashboard/widget/types.ts. - * This is a purely local command (no API calls), so no mocking is needed. - */ - -import { describe, expect, mock, test } from "bun:test"; - -import { - typesCommand, - type WidgetTypesResult, -} from "../../../../src/commands/dashboard/widget/types.js"; -import { - DISCOVER_AGGREGATE_FUNCTIONS, - DISPLAY_TYPES, - SPAN_AGGREGATE_FUNCTIONS, - WIDGET_TYPES, -} from "../../../../src/types/dashboard.js"; - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -function createMockContext() { - const stdoutWrite = mock(() => true); - return { - context: { - stdout: { write: stdoutWrite }, - stderr: { write: mock(() => true) }, - cwd: "/tmp", - setContext: mock(() => { - // no-op for test - }), - }, - stdoutWrite, - }; -} - -async function runTypesCommand( - json = true -): Promise<{ output: string; result: WidgetTypesResult }> { - const { context, stdoutWrite } = createMockContext(); - const func = await typesCommand.loader(); - await func.call(context as never, { json } as never); - - const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); - const result = JSON.parse(output) as WidgetTypesResult; - return { output, result }; -} - -// --------------------------------------------------------------------------- -// Tests -// --------------------------------------------------------------------------- - -describe("sentry dashboard widget types", () => { - test("grid reports 6 columns", async () => { - const { result } = await runTypesCommand(); - expect(result.grid.columns).toBe(6); - }); - - test("includes all display types", async () => { - const { result } = await runTypesCommand(); - const names = result.displayTypes.map((dt) => dt.name); - for (const dt of DISPLAY_TYPES) { - expect(names).toContain(dt); - } - expect(result.displayTypes).toHaveLength(DISPLAY_TYPES.length); - }); - - test("big_number has correct default size", async () => { - const { result } = await runTypesCommand(); - const bigNumber = result.displayTypes.find( - (dt) => dt.name === "big_number" - ); - expect(bigNumber).toBeDefined(); - expect(bigNumber!.defaultWidth).toBe(2); - expect(bigNumber!.defaultHeight).toBe(1); - expect(bigNumber!.category).toBe("common"); - }); - - test("table is full-width", async () => { - const { result } = await runTypesCommand(); - const table = result.displayTypes.find((dt) => dt.name === "table"); - expect(table).toBeDefined(); - expect(table!.defaultWidth).toBe(6); - expect(table!.category).toBe("common"); - }); - - test("common types are categorized correctly", async () => { - const { result } = await runTypesCommand(); - const common = result.displayTypes - .filter((dt) => dt.category === "common") - .map((dt) => dt.name); - expect(common).toContain("big_number"); - expect(common).toContain("line"); - expect(common).toContain("area"); - expect(common).toContain("bar"); - expect(common).toContain("table"); - }); - - test("includes all datasets", async () => { - const { result } = await runTypesCommand(); - const names = result.datasets.map((ds) => ds.name); - for (const wt of WIDGET_TYPES) { - expect(names).toContain(wt); - } - expect(result.datasets).toHaveLength(WIDGET_TYPES.length); - }); - - test("spans is the default dataset", async () => { - const { result } = await runTypesCommand(); - const defaults = result.datasets.filter((ds) => ds.isDefault); - expect(defaults).toHaveLength(1); - expect(defaults[0]!.name).toBe("spans"); - }); - - test("span aggregate functions are included", async () => { - const { result } = await runTypesCommand(); - const fns = result.aggregateFunctions.spans; - expect(fns).toContain("count"); - expect(fns).toContain("p95"); - expect(fns).toContain("avg"); - expect(fns).toContain("sum"); - expect(fns.length).toBe(SPAN_AGGREGATE_FUNCTIONS.length); - }); - - test("discover aggregates are a superset of span aggregates", async () => { - const { result } = await runTypesCommand(); - const spanSet = new Set(result.aggregateFunctions.spans); - const discoverSet = new Set(result.aggregateFunctions.discover); - for (const fn of spanSet) { - expect(discoverSet.has(fn)).toBe(true); - } - expect(discoverSet.size).toBeGreaterThan(spanSet.size); - expect(result.aggregateFunctions.discover.length).toBe( - DISCOVER_AGGREGATE_FUNCTIONS.length - ); - }); - - test("aggregate aliases are included", async () => { - const { result } = await runTypesCommand(); - expect(result.aggregateAliases.spm).toBe("epm"); - expect(result.aggregateAliases.tpm).toBe("epm"); - expect(result.aggregateAliases.sps).toBe("eps"); - expect(result.aggregateAliases.tps).toBe("eps"); - }); - - test("human output contains grid info and display types", async () => { - const { context, stdoutWrite } = createMockContext(); - const func = await typesCommand.loader(); - await func.call(context as never, { json: false } as never); - - const output = stdoutWrite.mock.calls.map((c) => c[0]).join(""); - expect(output).toContain("6 columns"); - expect(output).toContain("big_number"); - expect(output).toContain("table"); - expect(output).toContain("spans"); - expect(output).toContain("count"); - }); -}); From 6b6dc9574b4e7ca9d41a639a5ad9bd217ea94e5d Mon Sep 17 00:00:00 2001 From: betegon Date: Fri, 20 Mar 2026 21:07:29 +0100 Subject: [PATCH 9/9] fix(dashboard): list all display types in --display flag brief, no ellipsis Co-Authored-By: Claude Opus 4.6 (1M context) --- plugins/sentry-cli/skills/sentry-cli/references/dashboards.md | 4 ++-- src/commands/dashboard/widget/add.ts | 2 +- src/commands/dashboard/widget/edit.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/sentry-cli/skills/sentry-cli/references/dashboards.md b/plugins/sentry-cli/skills/sentry-cli/references/dashboards.md index 1d4698847..bfa565345 100644 --- a/plugins/sentry-cli/skills/sentry-cli/references/dashboards.md +++ b/plugins/sentry-cli/skills/sentry-cli/references/dashboards.md @@ -90,7 +90,7 @@ sentry dashboard widget add 'Frontend Performance' "Error Count" --display big_n Add a widget to a dashboard **Flags:** -- `-d, --display - Display type (big_number, line, area, bar, table, stacked_area, top_n, text, ...)` +- `-d, --display - Display type (big_number, line, area, bar, table, stacked_area, top_n, text, categorical_bar, details, wheel, rage_and_dead_clicks, server_tree, agents_traces_table)` - `--dataset - Widget dataset (default: spans)` - `-q, --query ... - Aggregate expression (e.g. count, p95:span.duration)` - `-w, --where - Search conditions filter (e.g. is:unresolved)` @@ -106,7 +106,7 @@ Edit a widget in a dashboard - `-i, --index - Widget index (0-based)` - `-t, --title - Widget title to match` - `--new-title - New widget title` -- `-d, --display - Display type (big_number, line, area, bar, table, stacked_area, top_n, text, ...)` +- `-d, --display - Display type (big_number, line, area, bar, table, stacked_area, top_n, text, categorical_bar, details, wheel, rage_and_dead_clicks, server_tree, agents_traces_table)` - `--dataset - Widget dataset (default: spans)` - `-q, --query ... - Aggregate expression (e.g. count, p95:span.duration)` - `-w, --where - Search conditions filter (e.g. is:unresolved)` diff --git a/src/commands/dashboard/widget/add.ts b/src/commands/dashboard/widget/add.ts index 4ad5431bb..8938d3cb4 100644 --- a/src/commands/dashboard/widget/add.ts +++ b/src/commands/dashboard/widget/add.ts @@ -106,7 +106,7 @@ export const addCommand = buildCommand({ kind: "parsed", parse: String, brief: - "Display type (big_number, line, area, bar, table, stacked_area, top_n, text, ...)", + "Display type (big_number, line, area, bar, table, stacked_area, top_n, text, categorical_bar, details, wheel, rage_and_dead_clicks, server_tree, agents_traces_table)", }, dataset: { kind: "parsed", diff --git a/src/commands/dashboard/widget/edit.ts b/src/commands/dashboard/widget/edit.ts index 5d2fda646..a3b1d157f 100644 --- a/src/commands/dashboard/widget/edit.ts +++ b/src/commands/dashboard/widget/edit.ts @@ -157,7 +157,7 @@ export const editCommand = buildCommand({ kind: "parsed", parse: String, brief: - "Display type (big_number, line, area, bar, table, stacked_area, top_n, text, ...)", + "Display type (big_number, line, area, bar, table, stacked_area, top_n, text, categorical_bar, details, wheel, rage_and_dead_clicks, server_tree, agents_traces_table)", optional: true, }, dataset: {