Skip to content

Commit 36fa985

Browse files
authored
Merge branch 'master' into romtsn/feat/android-tombstones-onboarding-banner
2 parents b60e58f + 1ea2a6c commit 36fa985

File tree

842 files changed

+4440
-2188
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

842 files changed

+4440
-2188
lines changed

eslint.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ export default typescript.config([
405405
'prefer-promise-reject-errors': 'off', // Disabled in favor of @typescript-eslint/prefer-promise-reject-errors
406406
'object-shorthand': ['error', 'properties'],
407407
'prefer-arrow-callback': ['error', {allowNamedFunctions: true}],
408+
quotes: ['error', 'single', {avoidEscape: true, allowTemplateLiterals: false}],
408409
radix: 'error',
409410
'require-await': 'off', // Disabled in favor of @typescript-eslint/require-await
410411
'spaced-comment': [

scripts/analyze-styled.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ if (!config.startDate) {
422422
if (config.targetFile) {
423423
logger.fatal(`❌ File not found: ${config.targetFile}`);
424424
} else {
425-
logger.fatal(`❌ No .tsx files found in search directory`);
425+
logger.fatal('❌ No .tsx files found in search directory');
426426
}
427427
process.exit(1);
428428
}

scripts/build-js-loader.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import * as ts from 'typescript';
1717
* then verify the build output of both generated `js.tmpl` files and check all three files in.
1818
*/
1919

20-
const header = `{% load sentry_helpers %}`;
20+
const header = '{% load sentry_helpers %}';
2121
const loaderScriptPath = './src/sentry/templates/sentry/js-sdk-loader.ts';
2222
const loaderTmplPath = './src/sentry/templates/sentry/js-sdk-loader.js.tmpl';
2323
const loaderMinTmplPath = './src/sentry/templates/sentry/js-sdk-loader.min.js.tmpl';

scripts/routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ const useDefaults = (values.defaults as boolean) ?? false;
148148
if (!showAll && Object.keys(userParams).length === 0 && !useDefaults) {
149149
const bin = path.basename(process.argv[1] ?? '');
150150
console.error(`Usage: node ${bin} [OPTIONS] --<param> <value> [...]`);
151-
console.error(`Run with --help for full usage.`);
151+
console.error('Run with --help for full usage.');
152152
process.exit(1);
153153
}
154154

scripts/type-coverage-diff.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ async function main() {
529529

530530
console.log(
531531
colors.dim(
532-
`\n📝 Note: Only showing type issues on lines that were literally added (+) or removed (-) in git diff`
532+
'\n📝 Note: Only showing type issues on lines that were literally added (+) or removed (-) in git diff'
533533
)
534534
);
535535

src/sentry/api/base.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"all_silo_endpoint",
7676
"internal_cell_silo_endpoint",
7777
"internal_all_silo_endpoint",
78+
"internal_control_silo_endpoint",
7879
]
7980

8081
PAGINATION_DEFAULT_PER_PAGE = 100
@@ -723,6 +724,12 @@ def __call__(self, decorated_obj: Any) -> Any:
723724
mode 404s will be returned.
724725
"""
725726

727+
internal_control_silo_endpoint = EndpointSiloLimit(SiloMode.CONTROL, internal=True)
728+
"""
729+
Apply to endpoints that exist in CONTROL silo that
730+
should not be included in the frontend URL mapping
731+
"""
732+
726733
cell_silo_endpoint = EndpointSiloLimit(SiloMode.CELL)
727734
"""
728735
Apply to endpoints that exist in CELL silo.

src/sentry/api/bases/organization_events.py

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from sentry.api.helpers.teams import get_teams
3030
from sentry.api.serializers.snuba import SnubaTSResultSerializer
3131
from sentry.api.utils import handle_query_errors
32-
from sentry.discover.arithmetic import is_equation, strip_equation
32+
from sentry.discover.arithmetic import is_equation, parse_arithmetic, strip_equation
3333
from sentry.discover.models import (
3434
DatasetSourcesTypes,
3535
DiscoverSavedQuery,
@@ -52,7 +52,7 @@
5252
from sentry.search.eap.occurrences.rollout_utils import EAPOccurrencesComparator
5353
from sentry.search.eap.types import AdditionalQueries, SupportedTraceItemType
5454
from sentry.search.events.constants import DURATION_UNITS, SIZE_UNITS
55-
from sentry.search.events.fields import get_function_alias
55+
from sentry.search.events.fields import get_function_alias, is_function
5656
from sentry.search.events.types import SAMPLING_MODES, SnubaParams
5757
from sentry.snuba import discover
5858
from sentry.snuba.dataset import Dataset
@@ -346,22 +346,25 @@ def handle_unit_meta(
346346

347347
def _get_rate_unit(self, field: str) -> str | None:
348348
"""Get the rate unit for a field by checking for known rate functions."""
349-
# Only use the opening parenthesis to identify rate functions because
350-
# some functions may contain arguments, making a direct string match not work.
351-
per_second_fns = {"eps(", "sps(", "tps(", "sample_eps(", "per_second("}
352-
per_minute_fns = {"epm(", "spm(", "tpm(", "sample_epm(", "per_minute("}
353-
354-
if any(field.startswith(fn) for fn in per_second_fns):
355-
return "1/second"
356-
if any(field.startswith(fn) for fn in per_minute_fns):
357-
return "1/minute"
349+
per_second_fns = {"eps", "sps", "tps", "sample_eps", "per_second", "per_second_if"}
350+
per_minute_fns = {"epm", "spm", "tpm", "sample_epm", "per_minute", "per_minute_if"}
351+
358352
# For equation fields, check if any known rate function appears in the expression
359-
if field.startswith("equation|"):
360-
for fn in per_second_fns:
361-
if fn in field:
353+
if is_equation(field):
354+
_, _, functions = parse_arithmetic(strip_equation(field))
355+
for function in functions:
356+
if function_match := is_function(function):
357+
function_name = function_match.group("function")
358+
if function_name in per_second_fns:
359+
return "1/second"
360+
elif function_name in per_minute_fns:
361+
return "1/minute"
362+
else:
363+
if function_match := is_function(field):
364+
function_name = function_match.group("function")
365+
if function_name in per_second_fns:
362366
return "1/second"
363-
for fn in per_minute_fns:
364-
if fn in field:
367+
if function_name in per_minute_fns:
365368
return "1/minute"
366369
return None
367370

src/sentry/api/endpoints/organization_insights_tree.py

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import logging
22
import re
3+
from typing import NamedTuple
34

45
from rest_framework.request import Request
56
from rest_framework.response import Response
@@ -12,6 +13,33 @@
1213
logger = logging.getLogger(__name__)
1314

1415

16+
class _ExtractionPattern(NamedTuple):
17+
"""Extraction pattern for component type and path from span.description.
18+
group(1) = kind/type (mapped through kind_map when present), group(2) = path.
19+
"""
20+
21+
regex: re.Pattern[str]
22+
kind_map: dict[str, str] | None = None
23+
24+
25+
# SDK <10.32.0: '{component_type} ({path})'
26+
# e.g. 'Page Server Component (/dashboard)'
27+
_ParenthesisPattern = _ExtractionPattern(
28+
regex=re.compile(r"^(.*?)\s+\((.*?)\)$"),
29+
)
30+
31+
# SDK >=10.32.0: 'resolve {page|root layout|layout} server component "{route_or_segment}"'
32+
# e.g. 'resolve page server component "/dashboard"'
33+
_ResolvePattern = _ExtractionPattern(
34+
regex=re.compile(r'^resolve (page|root layout|layout) server component(?:\s+"(.*)")?$'),
35+
kind_map={
36+
"page": "Page Server Component",
37+
"layout": "Layout Server Component",
38+
"root layout": "Layout Server Component",
39+
},
40+
)
41+
42+
1543
@cell_silo_endpoint
1644
class OrganizationInsightsTreeEndpoint(OrganizationEventsEndpoint):
1745
"""
@@ -41,22 +69,27 @@ def get(self, request: Request, organization: Organization) -> Response:
4169
response = super().get(request, organization)
4270
return self._separate_span_description_info(response)
4371

44-
def _separate_span_description_info(self, response):
45-
# Regex to split string into '{component_type}{space}({path})'
46-
pattern = re.compile(r"^(.*?)\s+\((.*?)\)$")
72+
_EXTRACTION_PATTERNS = (_ParenthesisPattern, _ResolvePattern)
73+
74+
@staticmethod
75+
def _parse_path(path: str | None) -> list[str]:
76+
if not path:
77+
return []
78+
parts = path.strip("/").split("/")
79+
return parts if parts != [""] else []
4780

81+
def _separate_span_description_info(self, response):
4882
for line in response.data["data"]:
49-
match = pattern.match(line["span.description"])
50-
if match:
51-
component_type = match.group(1)
52-
path = match.group(2)
53-
path_components = path.strip("/").split("/")
54-
if not path_components or (len(path_components) == 1 and path_components[0] == ""):
55-
path_components = [] # Handle root path case
56-
57-
else:
58-
component_type = None
59-
path_components = []
83+
component_type = None
84+
path_components: list[str] = []
85+
86+
for pattern, kind_map in self._EXTRACTION_PATTERNS:
87+
match = pattern.match(line["span.description"])
88+
if match:
89+
component_type = kind_map[match.group(1)] if kind_map else match.group(1)
90+
path_components = self._parse_path(match.group(2))
91+
break
92+
6093
line["function.nextjs.component_type"] = component_type
6194
line["function.nextjs.path"] = path_components
6295

src/sentry/api/serializers/models/groupsearchview.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from collections.abc import MutableMapping
22
from typing import Any, TypedDict
33

4+
from django.db.models import prefetch_related_objects
5+
46
from sentry.api.serializers import Serializer, register
57
from sentry.models.groupsearchview import GroupSearchView
68
from sentry.models.groupsearchviewlastvisited import GroupSearchViewLastVisited
@@ -33,6 +35,7 @@ def __init__(self, *args, **kwargs):
3335
super().__init__(*args, **kwargs)
3436

3537
def get_attrs(self, item_list, user, **kwargs) -> MutableMapping[Any, Any]:
38+
prefetch_related_objects(item_list, "projects")
3639
attrs: MutableMapping[Any, Any] = {}
3740

3841
last_visited_views = GroupSearchViewLastVisited.objects.filter(
@@ -68,7 +71,7 @@ def get_attrs(self, item_list, user, **kwargs) -> MutableMapping[Any, Any]:
6871
return attrs
6972

7073
def serialize(self, obj, attrs, user, **kwargs) -> GroupSearchViewSerializerResponse:
71-
projects = [-1] if obj.is_all_projects else list(obj.projects.values_list("id", flat=True))
74+
projects = [-1] if obj.is_all_projects else [p.id for p in obj.projects.all()]
7275

7376
return {
7477
"id": str(obj.id),

src/sentry/api/serializers/models/groupsearchviewstarred.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from sentry.api.serializers import Serializer, register, serialize
44
from sentry.api.serializers.models.groupsearchview import (
55
GroupSearchViewSerializer,
6-
GroupSearchViewSerializerResponse,
76
)
87
from sentry.models.groupsearchviewstarred import GroupSearchViewStarred
98
from sentry.models.savedsearch import SORT_LITERALS
@@ -28,13 +27,14 @@ def __init__(self, *args, **kwargs):
2827
self.organization = kwargs.pop("organization", None)
2928
super().__init__(*args, **kwargs)
3029

31-
def serialize(self, obj, attrs, user, **kwargs) -> GroupSearchViewStarredSerializerResponse:
32-
serialized_view: GroupSearchViewSerializerResponse = serialize(
33-
obj.group_search_view,
30+
def get_attrs(self, item_list, user, **kwargs):
31+
views = [item.group_search_view for item in item_list]
32+
serialized_views = serialize(
33+
views,
3434
user,
35-
serializer=GroupSearchViewSerializer(
36-
organization=self.organization,
37-
),
35+
serializer=GroupSearchViewSerializer(organization=self.organization),
3836
)
37+
return dict(zip(item_list, serialized_views))
3938

40-
return serialized_view
39+
def serialize(self, obj, attrs, user, **kwargs) -> GroupSearchViewStarredSerializerResponse:
40+
return attrs

0 commit comments

Comments
 (0)