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