Skip to content

Commit c34a898

Browse files
authored
feat(discover): Add docs for organization_events (#34768)
- This adds a new doc sections for Visibility called `Discover and Performance` - This documents the events endpoint
1 parent 8a637fd commit c34a898

File tree

4 files changed

+207
-14
lines changed

4 files changed

+207
-14
lines changed

src/sentry/api/endpoints/organization_events.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22

33
import sentry_sdk
4+
from drf_spectacular.utils import OpenApiExample, OpenApiResponse, extend_schema
45
from rest_framework.exceptions import ParseError
56
from rest_framework.request import Request
67
from rest_framework.response import Response
@@ -9,6 +10,9 @@
910
from sentry.api.bases import NoProjects, OrganizationEventsV2EndpointBase
1011
from sentry.api.paginator import GenericOffsetPaginator
1112
from sentry.api.utils import InvalidParams
13+
from sentry.apidocs import constants as api_constants
14+
from sentry.apidocs.parameters import GLOBAL_PARAMS, VISIBILITY_PARAMS
15+
from sentry.apidocs.utils import inline_sentry_response_serializer
1216
from sentry.search.events.fields import is_function
1317
from sentry.snuba import discover, metrics_enhanced_performance
1418

@@ -47,6 +51,8 @@
4751

4852

4953
class OrganizationEventsV2Endpoint(OrganizationEventsV2EndpointBase):
54+
"""Deprecated in favour of OrganizationEventsEndpoint"""
55+
5056
def get(self, request: Request, organization) -> Response:
5157
if not self.has_feature(organization, request):
5258
return Response(status=404)
@@ -127,10 +133,77 @@ def data_fn(offset, limit):
127133
)
128134

129135

136+
@extend_schema(tags=["Discover"])
130137
class OrganizationEventsEndpoint(OrganizationEventsV2EndpointBase):
131-
private = True
132-
138+
public = {"GET"}
139+
140+
@extend_schema(
141+
operation_id="Query Discover Events in Table Format",
142+
parameters=[
143+
VISIBILITY_PARAMS.QUERY,
144+
VISIBILITY_PARAMS.FIELD,
145+
VISIBILITY_PARAMS.SORT,
146+
VISIBILITY_PARAMS.PER_PAGE,
147+
GLOBAL_PARAMS.STATS_PERIOD,
148+
GLOBAL_PARAMS.START,
149+
GLOBAL_PARAMS.END,
150+
GLOBAL_PARAMS.PROJECT,
151+
GLOBAL_PARAMS.ENVIRONMENT,
152+
],
153+
responses={
154+
200: inline_sentry_response_serializer(
155+
"OrganizationEventsResponseDict", discover.EventsResponse
156+
),
157+
400: OpenApiResponse(description="Invalid Query"),
158+
404: api_constants.RESPONSE_NOTFOUND,
159+
},
160+
examples=[
161+
OpenApiExample(
162+
"Success",
163+
value={
164+
"data": [
165+
{
166+
"count_if(transaction.duration,greater,300)": 5,
167+
"count()": 10,
168+
"equation|count_if(transaction.duration,greater,300) / count() * 100": 50,
169+
"transaction": "foo",
170+
},
171+
{
172+
"count_if(transaction.duration,greater,300)": 3,
173+
"count()": 20,
174+
"equation|count_if(transaction.duration,greater,300) / count() * 100": 15,
175+
"transaction": "bar",
176+
},
177+
{
178+
"count_if(transaction.duration,greater,300)": 8,
179+
"count()": 40,
180+
"equation|count_if(transaction.duration,greater,300) / count() * 100": 20,
181+
"transaction": "baz",
182+
},
183+
],
184+
"meta": {
185+
"fields": {
186+
"count_if(transaction.duration,greater,300)": "integer",
187+
"count()": "integer",
188+
"equation|count_if(transaction.duration,greater,300) / count() * 100": "number",
189+
"transaction": "string",
190+
},
191+
},
192+
},
193+
)
194+
],
195+
)
133196
def get(self, request: Request, organization) -> Response:
197+
"""
198+
Retrieves discover (also known as events) data for a given organization.
199+
200+
**Note**: This endpoint is intended to get a table of results, and is not for doing a full export of data sent to
201+
Sentry.
202+
203+
The `field` query parameter determines what fields will be selected in the `data` and `meta` keys of the endpoint response.
204+
- The `data` key contains a list of results row by row that match the `query` made
205+
- The `meta` key contains information about the response, including the unit or type of the fields requested
206+
"""
134207
if not self.has_feature(organization, request):
135208
return Response(status=404)
136209

src/sentry/apidocs/build.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,16 @@ def get_old_json_paths(filename: str) -> json.JSONData:
7979
"url": "https://github.com/getsentry/sentry-docs/issues/new/?title=API%20Documentation%20Error:%20/api/integration-platform/&template=api_error_template.md",
8080
},
8181
},
82+
{
83+
# Not using visibility here since users won't be aware what that is, this "name" is only used in the URL so not
84+
# a big deal that its missing Performance
85+
"name": "Discover",
86+
"x-sidebar-name": "Discover & Performance",
87+
"description": "Discover and Performance allow you to slice and dice your Error and Transaction events",
88+
"x-display-description": True,
89+
"externalDocs": {
90+
"description": "Found an error? Let us know.",
91+
"url": "https://github.com/getsentry/sentry-docs/issues/new/?title=API%20Documentation%20Error:%20/api/integration-platform/&template=api_error_template.md",
92+
},
93+
},
8294
]

src/sentry/apidocs/parameters.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from drf_spectacular.types import OpenApiTypes
12
from drf_spectacular.utils import OpenApiParameter
23
from rest_framework import serializers
34

@@ -17,6 +18,50 @@ class GLOBAL_PARAMS:
1718
type=str,
1819
location="path",
1920
)
21+
STATS_PERIOD = OpenApiParameter(
22+
name="statsPeriod",
23+
location="query",
24+
required=False,
25+
type=str,
26+
description="""The period of time for the query, will override the start & end parameters, a number followed by one of:
27+
- `d` for days
28+
- `h` for hours
29+
- `m` for minutes
30+
- `s` for seconds
31+
- `w` for weeks
32+
33+
For example `24h`, to mean query data starting from 24 hours ago to now.""",
34+
)
35+
START = OpenApiParameter(
36+
name="start",
37+
location="query",
38+
required=False,
39+
type=OpenApiTypes.DATETIME,
40+
description="The start of the period of time for the query, expected in ISO-8601 format. For example `2001-12-14T12:34:56.7890`",
41+
)
42+
END = OpenApiParameter(
43+
name="end",
44+
location="query",
45+
required=False,
46+
type=OpenApiTypes.DATETIME,
47+
description="The end of the period of time for the query, expected in ISO-8601 format. For example `2001-12-14T12:34:56.7890`",
48+
)
49+
PROJECT = OpenApiParameter(
50+
name="project",
51+
location="query",
52+
required=False,
53+
many=True,
54+
type=int,
55+
description="The ids of projects to filter by. `-1` means all available projects. If this parameter is omitted, the request will default to using 'My Projects'",
56+
)
57+
ENVIRONMENT = OpenApiParameter(
58+
name="environment",
59+
location="query",
60+
required=False,
61+
many=True,
62+
type=str,
63+
description="The name of environments to filter by.",
64+
)
2065

2166

2267
class SCIM_PARAMS:
@@ -46,6 +91,51 @@ class ISSUE_ALERT_PARAMS:
4691
)
4792

4893

94+
class VISIBILITY_PARAMS:
95+
QUERY = OpenApiParameter(
96+
name="query",
97+
location="query",
98+
required=False,
99+
type=str,
100+
description="""The search filter for your query, read more about query syntax [here](https://docs.sentry.io/product/sentry-basics/search/)
101+
102+
example: `query=(transaction:foo AND release:abc) OR (transaction:[bar,baz] AND release:def)`
103+
""",
104+
)
105+
FIELD = OpenApiParameter(
106+
name="field",
107+
location="query",
108+
required=True,
109+
type=str,
110+
many=True,
111+
description="""The fields, functions, or equations to request for the query. At most 20 fields can be selected per request. Each field can be one of the following types:
112+
- A built-in key field. See possible fields in the [properties table](/product/sentry-basics/search/searchable-properties/#properties-table), under any field that is an event property
113+
- example: `field=transaction`
114+
- A tag. Tags should use the `tag[]` formatting to avoid ambiguity with any fields
115+
- example: `field=tag[isEnterprise]`
116+
- A function which will be in the format of `function_name(parameters,...)`. See possible functions in the [query builder documentation](/product/discover-queries/query-builder/#stacking-functions)
117+
- when a function is included, Discover will group by any tags or fields
118+
- example: `field=count_if(transaction.duration,greater,300)`
119+
- An equation when prefixed with `equation|`. Read more about [equations here](https://docs.sentry.io/product/discover-queries/query-builder/query-equations/)
120+
- example: `field=equation|count_if(transaction.duration,greater,300) / count() * 100`
121+
""",
122+
)
123+
SORT = OpenApiParameter(
124+
name="sort",
125+
location="query",
126+
required=False,
127+
type=str,
128+
description="What to order the results of the query by. Must be something in the `field` list, excluding equations.",
129+
)
130+
PER_PAGE = OpenApiParameter(
131+
name="per_page",
132+
location="query",
133+
required=False,
134+
type=int,
135+
description="Limit the number of rows to return in the result. Default and maximum allowed is 100.",
136+
)
137+
138+
49139
class CURSOR_QUERY_PARAM(serializers.Serializer): # type: ignore
50140
cursor = serializers.CharField(
51141
help_text="A pointer to the last object fetched and its' sort order; used to retrieve the next or previous results.",

src/sentry/snuba/discover.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
from collections import namedtuple
55
from copy import deepcopy
66
from datetime import timedelta
7-
from typing import Dict, Optional, Sequence
7+
from typing import Any, Dict, List, Optional, Sequence
88

99
import sentry_sdk
1010
from dateutil.parser import parse as parse_datetime
1111
from snuba_sdk.conditions import Condition, Op
1212
from snuba_sdk.function import Function
13+
from typing_extensions import TypedDict
1314

1415
from sentry.discover.arithmetic import categorize_columns
1516
from sentry.models import Group
@@ -67,6 +68,16 @@
6768
PaginationResult = namedtuple("PaginationResult", ["next", "previous", "oldest", "latest"])
6869
FacetResult = namedtuple("FacetResult", ["key", "value", "count"])
6970

71+
72+
class EventsMeta(TypedDict):
73+
fields: Dict[str, str]
74+
75+
76+
class EventsResponse(TypedDict):
77+
data: List[Dict[str, Any]]
78+
meta: EventsMeta
79+
80+
7081
resolve_discover_column = resolve_column(Dataset.Discover)
7182

7283
OTHER_KEY = "Other"
@@ -128,14 +139,16 @@ def zerofill(data, start, end, rollup, orderby):
128139
return rv
129140

130141

131-
def transform_results(results, function_alias_map, translated_columns, snuba_filter):
142+
def transform_results(
143+
results, function_alias_map, translated_columns, snuba_filter
144+
) -> EventsResponse:
132145
results = transform_data(results, translated_columns, snuba_filter)
133146
results["meta"] = transform_meta(results, function_alias_map)
134147
return results
135148

136149

137-
def transform_meta(results, function_alias_map):
138-
meta = {
150+
def transform_meta(results: EventsResponse, function_alias_map) -> Dict[str, str]:
151+
meta: Dict[str, str] = {
139152
value["name"]: get_json_meta_type(
140153
value["name"], value.get("type"), function_alias_map.get(value["name"])
141154
)
@@ -149,14 +162,15 @@ def transform_meta(results, function_alias_map):
149162
return meta
150163

151164

152-
def transform_data(result, translated_columns, snuba_filter):
165+
def transform_data(result, translated_columns, snuba_filter) -> EventsResponse:
153166
"""
154167
Transform internal names back to the public schema ones.
155168
156169
When getting timeseries results via rollup, this function will
157170
zerofill the output results.
158171
"""
159-
for col in result["meta"]:
172+
final_result: EventsResponse = {"data": result["data"], "meta": result["meta"]}
173+
for col in final_result["meta"]:
160174
# Translate back column names that were converted to snuba format
161175
col["name"] = translated_columns.get(col["name"], col["name"])
162176

@@ -174,19 +188,23 @@ def get_row(row):
174188

175189
return transformed
176190

177-
result["data"] = [get_row(row) for row in result["data"]]
191+
final_result["data"] = [get_row(row) for row in final_result["data"]]
178192

179193
if snuba_filter and snuba_filter.rollup and snuba_filter.rollup > 0:
180194
rollup = snuba_filter.rollup
181195
with sentry_sdk.start_span(
182196
op="discover.discover", description="transform_results.zerofill"
183197
) as span:
184-
span.set_data("result_count", len(result.get("data", [])))
185-
result["data"] = zerofill(
186-
result["data"], snuba_filter.start, snuba_filter.end, rollup, snuba_filter.orderby
198+
span.set_data("result_count", len(final_result.get("data", [])))
199+
final_result["data"] = zerofill(
200+
final_result["data"],
201+
snuba_filter.start,
202+
snuba_filter.end,
203+
rollup,
204+
snuba_filter.orderby,
187205
)
188206

189-
return result
207+
return final_result
190208

191209

192210
def transform_tips(tips):
@@ -213,7 +231,7 @@ def query(
213231
conditions=None,
214232
functions_acl=None,
215233
transform_alias_to_input_format=False,
216-
):
234+
) -> EventsResponse:
217235
"""
218236
High-level API for doing arbitrary user queries against events.
219237

0 commit comments

Comments
 (0)