Skip to content

Commit c291a74

Browse files
committed
feat(event): add 'sentry event list' command for issue-scoped event listing (#632)
Add a top-level 'event list' command that lists events for a Sentry issue, equivalent to 'issue events' but living in the event route. The 'sentry events' plural alias now points to this list command (Pattern A). Changes: - Extract shared types, formatters, and flag defs into event/shared-events.ts - Create event/list.ts with its own command identity (pagination hints, pagination key, and error messages say 'sentry event list') - Make buildCommandHint and resolveIssue accept optional commandBase param - Update event route to include list alongside view - Switch events plural alias from eventRoute to eventListCommand - Remove stale event/list synonym from command-suggestions.ts - Add 6 func tests verifying output, pagination hints, and command identity
1 parent 0108c1e commit c291a74

File tree

10 files changed

+757
-236
lines changed

10 files changed

+757
-236
lines changed

plugins/sentry-cli/skills/sentry-cli/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,9 +303,10 @@ Manage Sentry issues
303303

304304
### Event
305305

306-
View Sentry events
306+
View and list Sentry events
307307

308308
- `sentry event view <org/project/event-id...>` — View details of a specific event
309+
- `sentry event list <issue>` — List events for an issue
309310

310311
→ Full flags and examples: `references/event.md`
311312

plugins/sentry-cli/skills/sentry-cli/references/event.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
---
22
name: sentry-cli-event
33
version: 0.25.0-dev.0
4-
description: View Sentry events
4+
description: View and list Sentry events
55
requires:
66
bins: ["sentry"]
77
auth: true
88
---
99

1010
# Event Commands
1111

12-
View Sentry events
12+
View and list Sentry events
1313

1414
### `sentry event view <org/project/event-id...>`
1515

@@ -29,4 +29,36 @@ sentry event view abc123def456abc123def456abc12345
2929
sentry event view abc123def456abc123def456abc12345 -w
3030
```
3131

32+
### `sentry event list <issue>`
33+
34+
List events for an issue
35+
36+
**Flags:**
37+
- `-n, --limit <value> - Number of events (1-1000) - (default: "25")`
38+
- `-q, --query <value> - Search query (Sentry search syntax)`
39+
- `--full - Include full event body (stacktraces)`
40+
- `-t, --period <value> - Time period (e.g., "1h", "24h", "7d", "30d") - (default: "7d")`
41+
- `-f, --fresh - Bypass cache, re-detect projects, and fetch fresh data`
42+
- `-c, --cursor <value> - Navigate pages: "next", "prev", "first" (or raw cursor string)`
43+
44+
**JSON Fields** (use `--json --fields` to select specific fields):
45+
46+
| Field | Type | Description |
47+
|-------|------|-------------|
48+
| `id` | string | Internal event ID |
49+
| `event.type` | string | Event type (error, default, transaction) |
50+
| `groupID` | string \| null | Group (issue) ID |
51+
| `eventID` | string | UUID-format event ID |
52+
| `projectID` | string | Project ID |
53+
| `message` | string | Event message |
54+
| `title` | string | Event title |
55+
| `location` | string \| null | Source location (file:line) |
56+
| `culprit` | string \| null | Culprit function/module |
57+
| `user` | object \| null | User context |
58+
| `tags` | array | Event tags |
59+
| `platform` | string \| null | Platform (python, javascript, etc.) |
60+
| `dateCreated` | string | ISO 8601 creation timestamp |
61+
| `crashFile` | string \| null | Crash file URL |
62+
| `metadata` | unknown \| null | Event metadata |
63+
3264
All commands also support `--json`, `--fields`, `--help`, `--log-level`, and `--verbose` flags.

src/app.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { cliRoute } from "./commands/cli/index.js";
1515
import { dashboardRoute } from "./commands/dashboard/index.js";
1616
import { listCommand as dashboardListCommand } from "./commands/dashboard/list.js";
1717
import { eventRoute } from "./commands/event/index.js";
18+
import { listCommand as eventListCommand } from "./commands/event/list.js";
1819
import { helpCommand } from "./commands/help.js";
1920
import { initCommand } from "./commands/init.js";
2021
import { issueRoute } from "./commands/issue/index.js";
@@ -88,7 +89,7 @@ export const routes = buildRouteMap({
8889
team: teamRoute,
8990
issue: issueRoute,
9091
event: eventRoute,
91-
events: eventRoute,
92+
events: eventListCommand,
9293
log: logRoute,
9394
sourcemap: sourcemapRoute,
9495
sourcemaps: sourcemapRoute,

src/commands/event/index.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import { buildRouteMap } from "@stricli/core";
2+
import { listCommand } from "./list.js";
23
import { viewCommand } from "./view.js";
34

45
export const eventRoute = buildRouteMap({
56
routes: {
67
view: viewCommand,
8+
list: listCommand,
79
},
810
defaultCommand: "view",
911
aliases: { show: "view" },
1012
docs: {
11-
brief: "View Sentry events",
13+
brief: "View and list Sentry events",
1214
fullDescription:
13-
"View detailed event data from Sentry. " +
14-
"Use 'sentry event view <event-id>' to view a specific event.",
15+
"View and list event data from Sentry.\n\n" +
16+
"Use 'sentry event view <event-id>' to view a specific event.\n" +
17+
"Use 'sentry event list <issue-id>' to list events for an issue.",
1518
hideRoute: {},
1619
},
1720
});

src/commands/event/list.ts

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/**
2+
* sentry event list
3+
*
4+
* List events for a specific Sentry issue.
5+
* This is the top-level event listing command — functionally equivalent to
6+
* `sentry issue events` but living in the `event` route. The `sentry events`
7+
* plural alias points here.
8+
*/
9+
10+
import type { SentryContext } from "../../context.js";
11+
import { listIssueEvents } from "../../lib/api-client.js";
12+
import {
13+
advancePaginationState,
14+
buildPaginationContextKey,
15+
hasPreviousPage,
16+
resolveCursor,
17+
} from "../../lib/db/pagination.js";
18+
import { ContextError } from "../../lib/errors.js";
19+
import { CommandOutput } from "../../lib/formatters/output.js";
20+
import { buildListCommand, paginationHint } from "../../lib/list-command.js";
21+
import { withProgress } from "../../lib/polling.js";
22+
import { IssueEventSchema } from "../../types/index.js";
23+
import {
24+
buildCommandHint,
25+
issueIdPositional,
26+
resolveIssue,
27+
} from "../issue/utils.js";
28+
import {
29+
appendEventsFlags,
30+
EVENTS_ALIASES,
31+
EVENTS_FLAGS,
32+
type EventsFlags,
33+
formatEventsHuman,
34+
jsonTransformEvents,
35+
} from "./shared-events.js";
36+
37+
// ---------------------------------------------------------------------------
38+
// Constants
39+
// ---------------------------------------------------------------------------
40+
41+
/** Command name used in issue resolution error messages */
42+
const COMMAND_NAME = "list";
43+
44+
/** Base command prefix for error hints */
45+
const COMMAND_BASE = "sentry event";
46+
47+
/** Command key for pagination cursor storage */
48+
const PAGINATION_KEY = "event-list";
49+
50+
// ---------------------------------------------------------------------------
51+
// Pagination hints
52+
// ---------------------------------------------------------------------------
53+
54+
/** Build the CLI hint for fetching the next page, preserving active flags. */
55+
function nextPageHint(
56+
issueArg: string,
57+
flags: Pick<EventsFlags, "query" | "full" | "period">
58+
): string {
59+
return appendEventsFlags(`sentry event list ${issueArg} -c next`, flags);
60+
}
61+
62+
/** Build the CLI hint for fetching the previous page, preserving active flags. */
63+
function prevPageHint(
64+
issueArg: string,
65+
flags: Pick<EventsFlags, "query" | "full" | "period">
66+
): string {
67+
return appendEventsFlags(`sentry event list ${issueArg} -c prev`, flags);
68+
}
69+
70+
// ---------------------------------------------------------------------------
71+
// Command definition
72+
// ---------------------------------------------------------------------------
73+
74+
export const listCommand = buildListCommand("event", {
75+
docs: {
76+
brief: "List events for an issue",
77+
fullDescription:
78+
"List events belonging to a Sentry issue.\n\n" +
79+
"Issue formats:\n" +
80+
" @latest - Most recent unresolved issue\n" +
81+
" @most_frequent - Issue with highest event frequency\n" +
82+
" <org>/ID - Explicit org: sentry/EXTENSION-7\n" +
83+
" <project>-suffix - Project + suffix: cli-G\n" +
84+
" ID - Short ID: CLI-G\n" +
85+
" numeric - Numeric ID: 123456789\n\n" +
86+
"Examples:\n" +
87+
" sentry event list CLI-G\n" +
88+
" sentry event list @latest --limit 50\n" +
89+
" sentry event list 123456789 --full\n" +
90+
' sentry event list CLI-G -q "user.email:foo@bar.com"\n' +
91+
" sentry event list CLI-G --json",
92+
},
93+
output: {
94+
human: formatEventsHuman,
95+
jsonTransform: jsonTransformEvents,
96+
schema: IssueEventSchema,
97+
},
98+
parameters: {
99+
positional: issueIdPositional,
100+
flags: EVENTS_FLAGS,
101+
aliases: EVENTS_ALIASES,
102+
},
103+
async *func(this: SentryContext, flags: EventsFlags, issueArg: string) {
104+
const { cwd } = this;
105+
106+
// Resolve issue using shared resolution logic (supports @latest, short IDs, etc.)
107+
const { org, issue } = await resolveIssue({
108+
issueArg,
109+
cwd,
110+
command: COMMAND_NAME,
111+
commandBase: COMMAND_BASE,
112+
});
113+
114+
// Org is required for region-routed events endpoint
115+
if (!org) {
116+
throw new ContextError(
117+
"Organization",
118+
buildCommandHint(COMMAND_NAME, issueArg, COMMAND_BASE)
119+
);
120+
}
121+
122+
// Build context key for pagination (keyed by issue ID + query-varying params)
123+
const contextKey = buildPaginationContextKey(
124+
"event-list",
125+
`${org}/${issue.id}`,
126+
{ q: flags.query, period: flags.period }
127+
);
128+
const { cursor, direction } = resolveCursor(
129+
flags.cursor,
130+
PAGINATION_KEY,
131+
contextKey
132+
);
133+
134+
const { data: events, nextCursor } = await withProgress(
135+
{
136+
message: `Fetching events for ${issue.shortId} (up to ${flags.limit})...`,
137+
json: flags.json,
138+
},
139+
() =>
140+
listIssueEvents(org, issue.id, {
141+
limit: flags.limit,
142+
query: flags.query,
143+
full: flags.full,
144+
cursor,
145+
statsPeriod: flags.period,
146+
})
147+
);
148+
149+
// Update pagination state (handles both advance and truncation)
150+
advancePaginationState(PAGINATION_KEY, contextKey, direction, nextCursor);
151+
const hasPrev = hasPreviousPage(PAGINATION_KEY, contextKey);
152+
153+
const hasMore = !!nextCursor;
154+
155+
// Build footer hint based on result state
156+
const nav = paginationHint({
157+
hasMore,
158+
hasPrev,
159+
prevHint: prevPageHint(issueArg, flags),
160+
nextHint: nextPageHint(issueArg, flags),
161+
});
162+
let hint: string | undefined;
163+
if (events.length === 0 && nav) {
164+
hint = `No events on this page. ${nav}`;
165+
} else if (events.length > 0) {
166+
const countText = `Showing ${events.length} event${events.length === 1 ? "" : "s"}.`;
167+
hint = nav
168+
? `${countText} ${nav}`
169+
: `${countText} Use 'sentry event view <EVENT_ID>' to view full event details.`;
170+
}
171+
172+
yield new CommandOutput({
173+
events,
174+
hasMore,
175+
hasPrev,
176+
nextCursor,
177+
issueShortId: issue.shortId,
178+
issueId: issue.id,
179+
});
180+
return { hint };
181+
},
182+
});

0 commit comments

Comments
 (0)