Coverage for src / sentry_tool / commands / events.py: 88.89%
81 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-28 19:20 -0500
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-28 19:20 -0500
1"""Event-related commands."""
3from typing import Annotated
5import typer
6from rich.console import Console
8from sentry_tool.output import (
9 Column,
10 OutputFormat,
11 print_event_context,
12 print_exception_entry,
13 render,
14 render_event_basic_info,
15)
16from sentry_tool.services import resolve_issue_to_numeric
17from sentry_tool.utils import api, get_config
19app = typer.Typer(help="Event management commands")
22@app.command("event")
23def show_event(
24 issue_id: Annotated[str, typer.Argument(help="Issue ID (numeric or short ID)")],
25 event_id: Annotated[
26 str | None, typer.Option("--event", "-e", help="Specific event ID (default: latest)")
27 ] = None,
28 format: Annotated[
29 OutputFormat,
30 typer.Option("--format", "-f", help="Output format"),
31 ] = OutputFormat.table,
32 context: Annotated[
33 bool, typer.Option("--context", "-c", help="Show only context/stacktrace")
34 ] = False,
35) -> None:
36 """
37 Show event details for an issue.
39 By default shows the latest event. Use --event to specify a particular event.
41 Examples:
42 sentry-tool event 24 # Latest event for issue 24
43 sentry-tool event OTEL-COLLECTOR-Q # Latest event by short ID
44 sentry-tool event 24 -c # Show just context/stacktrace
45 sentry-tool event 24 --format json # Full JSON output
46 """
47 config = get_config()
49 numeric_id, short_id = resolve_issue_to_numeric(config, issue_id)
51 if event_id:
52 event = api(
53 f"/organizations/{config['org']}/issues/{numeric_id}/events/{event_id}/",
54 token=config["auth_token"],
55 base_url=config["url"],
56 )
57 else:
58 event = api(
59 f"/organizations/{config['org']}/issues/{numeric_id}/events/latest/",
60 token=config["auth_token"],
61 base_url=config["url"],
62 )
64 if format == OutputFormat.json:
65 render([event], format)
66 return
68 console = Console()
70 render_event_basic_info(console, event, short_id)
72 ctx = event.get("context", {})
73 if ctx:
74 print_event_context(console, ctx)
76 entries = event.get("entries", [])
77 for entry in entries:
78 entry_type = entry.get("type", "")
79 data = entry.get("data", {})
81 if entry_type == "message" and not context:
82 formatted = data.get("formatted", "")
83 if formatted:
84 console.print(f"\n[bold]Formatted Message:[/bold]\n {formatted}")
86 elif entry_type == "exception":
87 print_exception_entry(console, data)
89 console.print()
92@app.command("events")
93def list_events(
94 issue_id: Annotated[str, typer.Argument(help="Issue ID (numeric or short ID)")],
95 max_rows: Annotated[int, typer.Option("--max", "-n", help="Maximum events to show")] = 10,
96 format: Annotated[
97 OutputFormat,
98 typer.Option("--format", "-f", help="Output format"),
99 ] = OutputFormat.table,
100) -> None:
101 """
102 List recent events for an issue.
104 Examples:
105 sentry-tool events 24
106 sentry-tool events OTEL-COLLECTOR-Q -n 5
107 sentry-tool events 24 --format json
108 """
109 config = get_config()
111 numeric_id, _short_id = resolve_issue_to_numeric(config, issue_id)
113 events = api(
114 f"/organizations/{config['org']}/issues/{numeric_id}/events/",
115 token=config["auth_token"],
116 base_url=config["url"],
117 )
119 if not events:
120 Console().print("No events found")
121 return
123 events = events[:max_rows]
125 rows = []
126 for evt in events:
127 evt_id = evt.get("eventID", evt.get("id", ""))
128 date = evt.get("dateCreated", "")[:19]
130 server = "-"
131 for tag in evt.get("tags", []):
132 if tag.get("key") == "server_name":
133 server = tag.get("value", "-")
134 break
136 rows.append({"eventID": evt_id, "date": date, "server": server})
138 columns = [
139 Column("Event ID", "eventID", style="dim", max_width=36),
140 Column("Date", "date"),
141 Column("Server", "server"),
142 ]
144 render(rows, format, columns=columns, footer=f"Showing {len(events)} events")
147@app.command("tags")
148def show_tags(
149 issue_id: Annotated[str, typer.Argument(help="Issue ID (numeric or short ID)")],
150 tag_key: Annotated[
151 str | None, typer.Argument(help="Tag key to show values for (e.g., server_name)")
152 ] = None,
153 format: Annotated[
154 OutputFormat,
155 typer.Option("--format", "-f", help="Output format"),
156 ] = OutputFormat.table,
157) -> None:
158 """
159 Show tag values for an issue.
161 Without TAG_KEY, lists available tags. With TAG_KEY, shows values for that tag.
163 Examples:
164 sentry-tool tags OTEL-COLLECTOR-14 # List available tags
165 sentry-tool tags OTEL-COLLECTOR-14 server_name # Show affected hosts
166 sentry-tool tags OTEL-COLLECTOR-14 release # Show affected releases
167 sentry-tool tags 14 server_name --format json
168 """
169 config = get_config()
171 numeric_id, _short_id = resolve_issue_to_numeric(config, issue_id)
173 console = Console()
175 if tag_key:
176 tag_data = api(
177 f"/organizations/{config['org']}/issues/{numeric_id}/tags/{tag_key}/",
178 token=config["auth_token"],
179 base_url=config["url"],
180 )
182 top_values = tag_data.get("topValues", [])
183 if not top_values:
184 console.print(f"No values found for tag '{tag_key}'")
185 return
187 rows = []
188 for val in top_values:
189 name = val.get("value", "")[:30]
190 count = val.get("count", 0)
191 pct = val.get("percentage", 0) * 100
192 rows.append({"value": name, "count": str(count), "percent": f"{pct:.1f}%"})
194 columns = [
195 Column("Value", "value", max_width=30),
196 Column("Count", "count", justify="right"),
197 Column("Percent", "percent", justify="right"),
198 ]
200 render(
201 rows,
202 format,
203 columns=columns,
204 footer=f"Total unique values: {tag_data.get('uniqueValues', 'N/A')}",
205 )
206 else:
207 issue = api(
208 f"/organizations/{config['org']}/issues/{numeric_id}/",
209 token=config["auth_token"],
210 base_url=config["url"],
211 )
212 tags = issue.get("tags", [])
213 if not tags:
214 console.print("No tags found")
215 return
217 rows = [
218 {"key": tag.get("key", ""), "total": str(tag.get("totalValues", 0))} for tag in tags
219 ]
221 columns = [
222 Column("Tag Key", "key"),
223 Column("Unique Values", "total", justify="right"),
224 ]
226 render(rows, format, columns=columns)