Skip to content

Commit 4847126

Browse files
authored
Merge branch 'master' into billy/replay-2-releases-drawer-needs-to-have-deeplinks
2 parents 6e11363 + cb5d2f6 commit 4847126

File tree

190 files changed

+2930
-938
lines changed

Some content is hidden

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

190 files changed

+2930
-938
lines changed

.github/workflows/backend.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,15 @@ jobs:
8787
fail-fast: false
8888
matrix:
8989
# XXX: When updating this, make sure you also update MATRIX_INSTANCE_TOTAL.
90-
instance: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
90+
instance:
91+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
9192
pg-version: ['14']
9293

9394
env:
9495
# XXX: `MATRIX_INSTANCE_TOTAL` must be hardcoded to the length of `strategy.matrix.instance`.
9596
# If this increases, make sure to also increase `flags.backend.after_n_builds` in `codecov.yml`.
96-
MATRIX_INSTANCE_TOTAL: 11
97+
MATRIX_INSTANCE_TOTAL: 22
98+
TEST_GROUP_STRATEGY: roundrobin
9799

98100
steps:
99101
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"editor.tabSize": 4,
3434
"editor.formatOnSave": true,
3535
"editor.codeActionsOnSave": {
36-
"source.organizeImports": "explicit"
36+
"source.organizeImports": "explicit",
37+
"source.unusedImports": "explicit"
3738
},
3839
"editor.defaultFormatter": "ms-python.black-formatter"
3940
},

eslint.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ export default typescript.config([
632632
'unicorn/prefer-array-flat-map': 'error',
633633
'unicorn/prefer-array-flat': 'off', // TODO(ryan953): Fix violations and enable this rule
634634
'unicorn/prefer-array-index-of': 'error',
635-
'unicorn/prefer-array-some': 'off', // TODO(ryan953): Fix violations and enable this rule
635+
'unicorn/prefer-array-some': 'error',
636636
'unicorn/prefer-date-now': 'error',
637637
'unicorn/prefer-default-parameters': 'warn', // TODO(ryan953): Fix violations and enable this rule
638638
'unicorn/prefer-export-from': 'off', // TODO(ryan953): Fix violations and enable this rule

migrations_lockfile.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ remote_subscriptions: 0003_drop_remote_subscription
1919

2020
replays: 0004_index_together
2121

22-
sentry: 0864_move_monitors
22+
sentry: 0865_file_offsets
2323

2424
social_auth: 0002_default_auto_field
2525

2626
tempest: 0002_make_message_type_nullable
2727

2828
uptime: 0031_translate_uptime_object_headers_to_lists_take_three
2929

30-
workflow_engine: 0042_workflow_fire_history_add_fired_actions_bool
30+
workflow_engine: 0043_create_incidentgroupopenperiod_lookup_table

requirements-base.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ requests>=2.32.3
6565
rfc3339-validator>=0.1.2
6666
rfc3986-validator>=0.1.1
6767
# [end] jsonschema format validators
68-
sentry-arroyo>=2.20.8
69-
sentry-kafka-schemas>=1.1.4
68+
sentry-arroyo>=2.21.0
69+
sentry-kafka-schemas>=1.1.6
7070
sentry-ophio==1.0.0
7171
sentry-protos>=0.1.66
7272
sentry-redis-tools>=0.5.0

requirements-dev-frozen.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,14 +180,14 @@ rpds-py==0.20.0
180180
rsa==4.8
181181
s3transfer==0.10.0
182182
selenium==4.16.0
183-
sentry-arroyo==2.20.8
183+
sentry-arroyo==2.21.0
184184
sentry-cli==2.16.0
185185
sentry-covdefaults-disable-branch-coverage==1.0.2
186186
sentry-devenv==1.16.0
187187
sentry-forked-django-stubs==5.1.3.post2
188188
sentry-forked-djangorestframework-stubs==3.15.3.post1
189189
sentry-forked-email-reply-parser==0.5.12.post1
190-
sentry-kafka-schemas==1.1.4
190+
sentry-kafka-schemas==1.1.6
191191
sentry-ophio==1.0.0
192192
sentry-protos==0.1.66
193193
sentry-redis-tools==0.5.0

requirements-frozen.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,9 @@ rfc3986-validator==0.1.1
123123
rpds-py==0.20.0
124124
rsa==4.8
125125
s3transfer==0.10.0
126-
sentry-arroyo==2.20.8
126+
sentry-arroyo==2.21.0
127127
sentry-forked-email-reply-parser==0.5.12.post1
128-
sentry-kafka-schemas==1.1.4
128+
sentry-kafka-schemas==1.1.6
129129
sentry-ophio==1.0.0
130130
sentry-protos==0.1.66
131131
sentry-redis-tools==0.5.0
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import logging
2+
import re
3+
4+
from rest_framework.request import Request
5+
from rest_framework.response import Response
6+
7+
from sentry.api.api_publish_status import ApiPublishStatus
8+
from sentry.api.base import region_silo_endpoint
9+
from sentry.api.endpoints.organization_events import OrganizationEventsEndpoint
10+
11+
logger = logging.getLogger(__name__)
12+
13+
14+
@region_silo_endpoint
15+
class OrganizationInsightsTreeEndpoint(OrganizationEventsEndpoint):
16+
"""
17+
Endpoint for querying Next.js Insights data to display a tree view of files and components.
18+
19+
Currently, the component and path information is extracted from the span.description field using a regex.
20+
In the future, this data will be properly structured through:
21+
1. The Next.js SDK adding these as explicit attributes
22+
2. EAP adding support for array data storage and querying
23+
24+
These improvements will enable more efficient querying and level-by-level tree navigation.
25+
This endpoint is temporary and will be replaced by the standard /events/ endpoint once
26+
these features are implemented elsewhere in the system.
27+
"""
28+
29+
publish_status = {
30+
"GET": ApiPublishStatus.EXPERIMENTAL,
31+
}
32+
33+
def get(self, request: Request, organization) -> Response:
34+
if not self.has_feature(organization, request):
35+
return Response(status=404)
36+
37+
if not request.GET.get("useRpc", False) or not request.GET.get("noPagination", False):
38+
return Response(status=404)
39+
40+
response = super().get(request, organization)
41+
return self._separate_span_description_info(response)
42+
43+
def _separate_span_description_info(self, response):
44+
# Regex to split string into '{component_type}{space}({path})'
45+
pattern = re.compile(r"^(.*?)\s+\((.*?)\)$")
46+
47+
for line in response.data["data"]:
48+
match = pattern.match(line["span.description"])
49+
if match:
50+
component_type = match.group(1)
51+
path = match.group(2)
52+
path_components = path.strip("/").split("/")
53+
if not path_components or (len(path_components) == 1 and path_components[0] == ""):
54+
path_components = ["/"] # Handle root path case
55+
56+
else:
57+
component_type = None
58+
path_components = []
59+
line["function.nextjs.component_type"] = component_type
60+
line["function.nextjs.path"] = path_components
61+
62+
return response

src/sentry/api/endpoints/organization_pinned_searches.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,14 @@ def put(self, request: Request, organization) -> Response:
6666
"visibility": GroupSearchViewVisibility.ORGANIZATION,
6767
},
6868
)
69-
default_view_id = default_view.id if created else default_view
69+
if created:
70+
default_view_id = default_view.id
71+
else:
72+
default_view_id = GroupSearchView.objects.get(
73+
organization=organization,
74+
user_id=request.user.id,
75+
).id
76+
7077
GroupSearchViewStarred.objects.create_or_update(
7178
organization=organization,
7279
user_id=request.user.id,

src/sentry/api/endpoints/warmup.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1+
import django.db.models.sql.compiler # NOQA
12
from django.conf import settings
23
from django.urls import reverse
3-
from django.utils.translation import override
4+
from django.utils import translation
45
from rest_framework.request import Request
56
from rest_framework.response import Response
67

8+
import sentry.identity.services.identity.impl # NOQA
9+
import sentry.integrations.services.integration.impl # NOQA
10+
import sentry.middleware.integrations.parsers.plugin # NOQA
11+
import sentry.notifications.services.impl # NOQA
12+
import sentry.sentry_apps.services.app.impl # NOQA
13+
import sentry.users.services.user.impl # NOQA
14+
import sentry.users.services.user_option.impl # NOQA
715
from sentry.api.api_owners import ApiOwner
816
from sentry.api.api_publish_status import ApiPublishStatus
917
from sentry.api.base import Endpoint, all_silo_endpoint
@@ -24,7 +32,17 @@ def get(self, request: Request) -> Response:
2432
# this fixes an issue we were seeing where many languages trying
2533
# to resolve at once would cause lock contention
2634
for lang, _ in settings.LANGUAGES:
27-
with override(lang):
35+
with translation.override(lang):
2836
reverse("sentry-warmup")
2937

38+
# for each possible language we support, warm up the translations
39+
# cache for faster access
40+
for lang, _ in settings.LANGUAGES:
41+
try:
42+
language = translation.get_supported_language_variant(lang)
43+
except LookupError:
44+
pass
45+
else:
46+
translation.activate(language)
47+
3048
return Response(200)

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,20 @@ class ExploreSavedQueryResponse(ExploreSavedQueryResponseOptional):
3434
lastVisited: str
3535
createdBy: UserSerializerResponse
3636
starred: bool
37+
position: int | None
3738

3839

3940
@register(ExploreSavedQuery)
4041
class ExploreSavedQueryModelSerializer(Serializer):
4142
def get_attrs(self, item_list, user, **kwargs):
4243
result: DefaultDict[str, dict] = defaultdict(lambda: {"created_by": {}})
4344

44-
starred_query_ids = set(
45+
starred_queries = dict(
4546
ExploreSavedQueryStarred.objects.filter(
4647
explore_saved_query__in=item_list,
4748
user_id=user.id,
4849
organization=item_list[0].organization if item_list else None,
49-
).values_list("explore_saved_query_id", flat=True)
50+
).values_list("explore_saved_query_id", "position")
5051
)
5152

5253
service_serialized = user_service.serialize_many(
@@ -65,7 +66,12 @@ def get_attrs(self, item_list, user, **kwargs):
6566
result[explore_saved_query]["created_by"] = serialized_users.get(
6667
str(explore_saved_query.created_by_id)
6768
)
68-
result[explore_saved_query]["starred"] = explore_saved_query.id in starred_query_ids
69+
if explore_saved_query.id in starred_queries:
70+
result[explore_saved_query]["starred"] = True
71+
result[explore_saved_query]["position"] = starred_queries[explore_saved_query.id]
72+
else:
73+
result[explore_saved_query]["starred"] = False
74+
result[explore_saved_query]["position"] = None
6975

7076
return result
7177

@@ -89,6 +95,7 @@ def serialize(self, obj, attrs, user, **kwargs) -> ExploreSavedQueryResponse:
8995
"lastVisited": obj.last_visited,
9096
"createdBy": attrs.get("created_by"),
9197
"starred": attrs.get("starred"),
98+
"position": attrs.get("position"),
9299
}
93100

94101
for key in query_keys:

src/sentry/api/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
OrganizationEventsRootCauseAnalysisEndpoint,
1717
)
1818
from sentry.api.endpoints.organization_fork import OrganizationForkEndpoint
19+
from sentry.api.endpoints.organization_insights_tree import OrganizationInsightsTreeEndpoint
1920
from sentry.api.endpoints.organization_member_invite.details import (
2021
OrganizationMemberInviteDetailsEndpoint,
2122
)
@@ -2901,6 +2902,11 @@ def create_group_urls(name_prefix: str) -> list[URLPattern | URLResolver]:
29012902
name="sentry-api-0-project-seer-preferences",
29022903
),
29032904
*workflow_urls.project_urlpatterns,
2905+
re_path(
2906+
r"^(?P<organization_id_or_slug>[^\/]+)/insights/tree/$",
2907+
OrganizationInsightsTreeEndpoint.as_view(),
2908+
name="sentry-api-0-organization-insights-tree",
2909+
),
29042910
]
29052911

29062912
TEAM_URLS = [

src/sentry/backup/comparators.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,9 @@ def get_default_comparators() -> dict[str, list[JSONScrubbingComparator]]:
918918
"workflow_engine.dataconditionalertruletrigger": [
919919
DateUpdatedComparator("date_updated", "date_added")
920920
],
921+
"workflow_engine.incidentgroupopenperiod": [
922+
DateUpdatedComparator("date_updated", "date_added")
923+
],
921924
"tempest.tempestcredentials": [
922925
DateUpdatedComparator("date_updated", "date_added"),
923926
],

src/sentry/db/models/fields/bounded.py

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@
1111
"BoundedBigIntegerField",
1212
"BoundedPositiveIntegerField",
1313
"BoundedPositiveBigIntegerField",
14+
"WrappingU32IntegerField",
1415
)
1516

17+
I32_MAX = 2_147_483_647 # 2**31 - 1
18+
U32_MAX = 4_294_967_295 # 2**32 - 1
19+
I64_MAX = 9_223_372_036_854_775_807 # 2**63 - 1
20+
1621

1722
class BoundedIntegerField(models.IntegerField):
18-
MAX_VALUE = 2147483647
23+
MAX_VALUE = I32_MAX
1924

2025
def get_prep_value(self, value: int) -> int:
2126
if value:
@@ -25,7 +30,7 @@ def get_prep_value(self, value: int) -> int:
2530

2631

2732
class BoundedPositiveIntegerField(models.PositiveIntegerField):
28-
MAX_VALUE = 2147483647
33+
MAX_VALUE = I32_MAX
2934

3035
def get_prep_value(self, value: int) -> int:
3136
if value:
@@ -34,8 +39,35 @@ def get_prep_value(self, value: int) -> int:
3439
return super().get_prep_value(value)
3540

3641

42+
class WrappingU32IntegerField(models.IntegerField):
43+
"""
44+
This type allows storing a full unsigned `u32` value by manually wrapping it around,
45+
so it is stored as a signed `i32` value in the database.
46+
"""
47+
48+
MIN_VALUE = 0
49+
MAX_VALUE = U32_MAX
50+
51+
def get_prep_value(self, value: int) -> int:
52+
if value:
53+
value = int(value)
54+
assert self.MIN_VALUE <= value <= self.MAX_VALUE
55+
56+
if value > I32_MAX:
57+
value = value - 2**32
58+
59+
return super().get_prep_value(value)
60+
61+
def from_db_value(self, value: int | None, expression, connection) -> int | None:
62+
if value is None:
63+
return None
64+
if value < 0:
65+
return value + 2**32
66+
return value
67+
68+
3769
class BoundedAutoField(models.AutoField):
38-
MAX_VALUE = 2147483647
70+
MAX_VALUE = I32_MAX
3971

4072
def get_prep_value(self, value: int) -> int:
4173
if value:
@@ -49,7 +81,7 @@ def get_prep_value(self, value: int) -> int:
4981
class BoundedBigIntegerField(models.BigIntegerField):
5082
description = _("Big Integer")
5183

52-
MAX_VALUE = 9223372036854775807
84+
MAX_VALUE = I64_MAX
5385

5486
def get_internal_type(self) -> str:
5587
return "BigIntegerField"
@@ -63,7 +95,7 @@ def get_prep_value(self, value: int) -> int:
6395
class BoundedPositiveBigIntegerField(models.PositiveBigIntegerField):
6496
description = _("Positive big integer")
6597

66-
MAX_VALUE = 9223372036854775807
98+
MAX_VALUE = I64_MAX
6799

68100
def get_internal_type(self) -> str:
69101
return "PositiveBigIntegerField"
@@ -77,7 +109,7 @@ def get_prep_value(self, value: int) -> int:
77109
class BoundedBigAutoField(models.BigAutoField):
78110
description = _("Big Integer")
79111

80-
MAX_VALUE = 9223372036854775807
112+
MAX_VALUE = I64_MAX
81113

82114
def get_internal_type(self) -> str:
83115
return "BigAutoField"

0 commit comments

Comments
 (0)