Skip to content

Commit eb35fac

Browse files
committed
Merge branch 'master' into tkdodo/ref/knip-zero
2 parents 454888c + 3a32207 commit eb35fac

File tree

19 files changed

+110
-183
lines changed

19 files changed

+110
-183
lines changed

rspack.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -784,7 +784,8 @@ appConfig.plugins?.push(
784784
create: false,
785785
},
786786
reactComponentAnnotation: {
787-
enabled: true,
787+
// Enabled only in production because annotating is slow
788+
enabled: IS_PRODUCTION,
788789
},
789790
bundleSizeOptimizations: {
790791
// This is enabled so that our SDKs send exceptions to Sentry

src/sentry/api/endpoints/group_ai_summary.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from sentry.api.api_publish_status import ApiPublishStatus
1111
from sentry.api.base import region_silo_endpoint
1212
from sentry.api.bases.group import GroupAiEndpoint
13-
from sentry.autofix.utils import SeerAutomationSource
1413
from sentry.models.group import Group
1514
from sentry.seer.issue_summary import get_issue_summary
1615
from sentry.types.ratelimit import RateLimit, RateLimitCategory
@@ -38,10 +37,7 @@ def post(self, request: Request, group: Group) -> Response:
3837
force_event_id = data.get("event_id", None)
3938

4039
summary_data, status_code = get_issue_summary(
41-
group=group,
42-
user=request.user,
43-
force_event_id=force_event_id,
44-
source=SeerAutomationSource.ISSUE_DETAILS,
40+
group=group, user=request.user, force_event_id=force_event_id, source="issue_details"
4541
)
4642

4743
return Response(summary_data, status=status_code)

src/sentry/api/endpoints/group_autofix_setup_check.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,12 @@
77
from django.conf import settings
88
from rest_framework.response import Response
99

10-
from sentry import quotas
1110
from sentry.api.api_owners import ApiOwner
1211
from sentry.api.api_publish_status import ApiPublishStatus
1312
from sentry.api.base import region_silo_endpoint
1413
from sentry.api.bases.group import GroupAiEndpoint
1514
from sentry.autofix.utils import get_autofix_repos_from_project_code_mappings
16-
from sentry.constants import DataCategory, ObjectStatus
15+
from sentry.constants import ObjectStatus
1716
from sentry.integrations.services.integration import integration_service
1817
from sentry.models.group import Group
1918
from sentry.models.organization import Organization
@@ -127,11 +126,6 @@ def get(self, request: Request, group: Group) -> Response:
127126
if not user_acknowledgement: # If the user has acknowledged, the org must have too.
128127
org_acknowledgement = get_seer_org_acknowledgement(org_id=org.id)
129128

130-
# TODO return BOTH trial status and autofix quota
131-
has_autofix_quota: bool = quotas.backend.has_available_reserved_budget(
132-
org_id=org.id, data_category=DataCategory.SEER_AUTOFIX
133-
)
134-
135129
return Response(
136130
{
137131
"integration": {
@@ -143,8 +137,5 @@ def get(self, request: Request, group: Group) -> Response:
143137
"orgHasAcknowledged": org_acknowledgement,
144138
"userHasAcknowledged": user_acknowledgement,
145139
},
146-
"billing": {
147-
"hasAutofixQuota": has_autofix_quota,
148-
},
149140
}
150141
)

src/sentry/api/endpoints/project_details.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,6 @@ class ProjectMemberSerializer(serializers.Serializer):
128128
"copy_from_project",
129129
"targetSampleRate",
130130
"dynamicSamplingBiases",
131-
"performanceIssueCreationRate",
132-
"performanceIssueCreationThroughPlatform",
133-
"performanceIssueSendToPlatform",
134131
"tempestFetchScreenshots",
135132
"tempestFetchDumps",
136133
"autofixAutomationTuning",
@@ -223,9 +220,6 @@ class ProjectAdminSerializer(ProjectMemberSerializer):
223220
copy_from_project = serializers.IntegerField(required=False)
224221
targetSampleRate = serializers.FloatField(required=False, min_value=0, max_value=1)
225222
dynamicSamplingBiases = DynamicSamplingBiasSerializer(required=False, many=True)
226-
performanceIssueCreationRate = serializers.FloatField(required=False, min_value=0, max_value=1)
227-
performanceIssueCreationThroughPlatform = serializers.BooleanField(required=False)
228-
performanceIssueSendToPlatform = serializers.BooleanField(required=False)
229223
tempestFetchScreenshots = serializers.BooleanField(required=False)
230224
tempestFetchDumps = serializers.BooleanField(required=False)
231225
autofixAutomationTuning = serializers.ChoiceField(

src/sentry/api/endpoints/project_performance_issue_settings.py

Lines changed: 78 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -34,72 +34,84 @@
3434
SETTINGS_PROJECT_OPTION_KEY = "sentry:performance_issue_settings"
3535

3636

37-
# These options should only be accessible internally and used by
38-
# support to enable/disable performance issue detection for an outlying project
39-
# on a case-by-case basis.
4037
class InternalProjectOptions(Enum):
41-
N_PLUS_ONE_DB = "n_plus_one_db_queries_detection_enabled"
42-
UNCOMPRESSED_ASSET = "uncompressed_assets_detection_enabled"
43-
CONSECUTIVE_HTTP_SPANS = "consecutive_http_spans_detection_enabled"
44-
LARGE_HTTP_PAYLOAD = "large_http_payload_detection_enabled"
45-
N_PLUS_ONE_API_CALLS = "n_plus_one_api_calls_detection_enabled"
46-
DB_ON_MAIN_THREAD = "db_on_main_thread_detection_enabled"
47-
FILE_IO_MAIN_THREAD = "file_io_on_main_thread_detection_enabled"
48-
CONSECUTIVE_DB_QUERIES = "consecutive_db_queries_detection_enabled"
49-
RENDER_BLOCKING_ASSET = "large_render_blocking_asset_detection_enabled"
50-
SLOW_DB_QUERY = "slow_db_queries_detection_enabled"
51-
HTTP_OVERHEAD = "http_overhead_detection_enabled"
38+
"""
39+
Settings that are only accessible to superusers.
40+
"""
41+
5242
TRANSACTION_DURATION_REGRESSION = "transaction_duration_regression_detection_enabled"
5343
FUNCTION_DURATION_REGRESSION = "function_duration_regression_detection_enabled"
5444

5545

5646
class ConfigurableThresholds(Enum):
47+
"""
48+
All the settings that can be configured by users with the appropriate permissions.
49+
"""
50+
51+
N_PLUS_ONE_DB = "n_plus_one_db_queries_detection_enabled"
5752
N_PLUS_ONE_DB_DURATION = "n_plus_one_db_duration_threshold"
5853
N_PLUS_ONE_DB_COUNT = "n_plus_one_db_count"
54+
UNCOMPRESSED_ASSET = "uncompressed_assets_detection_enabled"
5955
UNCOMPRESSED_ASSET_DURATION = "uncompressed_asset_duration_threshold"
6056
UNCOMPRESSED_ASSET_SIZE = "uncompressed_asset_size_threshold"
57+
LARGE_HTTP_PAYLOAD = "large_http_payload_detection_enabled"
6158
LARGE_HTTP_PAYLOAD_SIZE = "large_http_payload_size_threshold"
59+
DB_ON_MAIN_THREAD = "db_on_main_thread_detection_enabled"
6260
DB_ON_MAIN_THREAD_DURATION = "db_on_main_thread_duration_threshold"
61+
FILE_IO_MAIN_THREAD = "file_io_on_main_thread_detection_enabled"
6362
FILE_IO_MAIN_THREAD_DURATION = "file_io_on_main_thread_duration_threshold"
63+
CONSECUTIVE_DB_QUERIES = "consecutive_db_queries_detection_enabled"
6464
CONSECUTIVE_DB_QUERIES_MIN_TIME_SAVED = "consecutive_db_min_time_saved_threshold"
65+
RENDER_BLOCKING_ASSET = "large_render_blocking_asset_detection_enabled"
6566
RENDER_BLOCKING_ASSET_FCP_RATIO = "render_blocking_fcp_ratio"
67+
SLOW_DB_QUERY = "slow_db_queries_detection_enabled"
6668
SLOW_DB_QUERY_DURATION = "slow_db_query_duration_threshold"
69+
N_PLUS_ONE_API_CALLS = "n_plus_one_api_calls_detection_enabled"
6770
N_PLUS_API_CALLS_DURATION = "n_plus_one_api_calls_total_duration_threshold"
71+
CONSECUTIVE_HTTP_SPANS = "consecutive_http_spans_detection_enabled"
6872
CONSECUTIVE_HTTP_SPANS_MIN_TIME_SAVED = "consecutive_http_spans_min_time_saved_threshold"
73+
HTTP_OVERHEAD = "http_overhead_detection_enabled"
6974
HTTP_OVERHEAD_REQUEST_DELAY = "http_request_delay_threshold"
7075

7176

72-
internal_only_project_settings_to_group_map: dict[str, type[GroupType]] = {
73-
InternalProjectOptions.UNCOMPRESSED_ASSET.value: PerformanceUncompressedAssetsGroupType,
74-
InternalProjectOptions.CONSECUTIVE_HTTP_SPANS.value: PerformanceConsecutiveHTTPQueriesGroupType,
75-
InternalProjectOptions.LARGE_HTTP_PAYLOAD.value: PerformanceLargeHTTPPayloadGroupType,
76-
InternalProjectOptions.N_PLUS_ONE_DB.value: PerformanceNPlusOneGroupType,
77-
InternalProjectOptions.N_PLUS_ONE_API_CALLS.value: PerformanceNPlusOneAPICallsGroupType,
78-
InternalProjectOptions.DB_ON_MAIN_THREAD.value: PerformanceDBMainThreadGroupType,
79-
InternalProjectOptions.FILE_IO_MAIN_THREAD.value: PerformanceFileIOMainThreadGroupType,
80-
InternalProjectOptions.CONSECUTIVE_DB_QUERIES.value: PerformanceConsecutiveDBQueriesGroupType,
81-
InternalProjectOptions.RENDER_BLOCKING_ASSET.value: PerformanceRenderBlockingAssetSpanGroupType,
82-
InternalProjectOptions.SLOW_DB_QUERY.value: PerformanceSlowDBQueryGroupType,
83-
InternalProjectOptions.HTTP_OVERHEAD.value: PerformanceHTTPOverheadGroupType,
77+
project_settings_to_group_map: dict[str, type[GroupType]] = {
78+
ConfigurableThresholds.UNCOMPRESSED_ASSET.value: PerformanceUncompressedAssetsGroupType,
79+
ConfigurableThresholds.CONSECUTIVE_HTTP_SPANS.value: PerformanceConsecutiveHTTPQueriesGroupType,
80+
ConfigurableThresholds.LARGE_HTTP_PAYLOAD.value: PerformanceLargeHTTPPayloadGroupType,
81+
ConfigurableThresholds.N_PLUS_ONE_DB.value: PerformanceNPlusOneGroupType,
82+
ConfigurableThresholds.N_PLUS_ONE_API_CALLS.value: PerformanceNPlusOneAPICallsGroupType,
83+
ConfigurableThresholds.DB_ON_MAIN_THREAD.value: PerformanceDBMainThreadGroupType,
84+
ConfigurableThresholds.FILE_IO_MAIN_THREAD.value: PerformanceFileIOMainThreadGroupType,
85+
ConfigurableThresholds.CONSECUTIVE_DB_QUERIES.value: PerformanceConsecutiveDBQueriesGroupType,
86+
ConfigurableThresholds.RENDER_BLOCKING_ASSET.value: PerformanceRenderBlockingAssetSpanGroupType,
87+
ConfigurableThresholds.SLOW_DB_QUERY.value: PerformanceSlowDBQueryGroupType,
88+
ConfigurableThresholds.HTTP_OVERHEAD.value: PerformanceHTTPOverheadGroupType,
8489
InternalProjectOptions.TRANSACTION_DURATION_REGRESSION.value: PerformanceP95EndpointRegressionGroupType,
8590
InternalProjectOptions.FUNCTION_DURATION_REGRESSION.value: ProfileFunctionRegressionType,
8691
}
87-
88-
configurable_thresholds_to_internal_settings_map: dict[str, str] = {
89-
ConfigurableThresholds.N_PLUS_ONE_DB_DURATION.value: InternalProjectOptions.N_PLUS_ONE_DB.value,
90-
ConfigurableThresholds.N_PLUS_ONE_DB_COUNT.value: InternalProjectOptions.N_PLUS_ONE_DB.value,
91-
ConfigurableThresholds.UNCOMPRESSED_ASSET_DURATION.value: InternalProjectOptions.UNCOMPRESSED_ASSET.value,
92-
ConfigurableThresholds.UNCOMPRESSED_ASSET_SIZE.value: InternalProjectOptions.UNCOMPRESSED_ASSET.value,
93-
ConfigurableThresholds.LARGE_HTTP_PAYLOAD_SIZE.value: InternalProjectOptions.LARGE_HTTP_PAYLOAD.value,
94-
ConfigurableThresholds.DB_ON_MAIN_THREAD_DURATION.value: InternalProjectOptions.DB_ON_MAIN_THREAD.value,
95-
ConfigurableThresholds.FILE_IO_MAIN_THREAD_DURATION.value: InternalProjectOptions.FILE_IO_MAIN_THREAD.value,
96-
ConfigurableThresholds.CONSECUTIVE_DB_QUERIES_MIN_TIME_SAVED.value: InternalProjectOptions.CONSECUTIVE_DB_QUERIES.value,
97-
ConfigurableThresholds.RENDER_BLOCKING_ASSET_FCP_RATIO.value: InternalProjectOptions.RENDER_BLOCKING_ASSET.value,
98-
ConfigurableThresholds.SLOW_DB_QUERY_DURATION.value: InternalProjectOptions.SLOW_DB_QUERY.value,
99-
ConfigurableThresholds.N_PLUS_API_CALLS_DURATION.value: InternalProjectOptions.N_PLUS_ONE_API_CALLS.value,
100-
ConfigurableThresholds.CONSECUTIVE_HTTP_SPANS_MIN_TIME_SAVED.value: InternalProjectOptions.CONSECUTIVE_HTTP_SPANS.value,
101-
ConfigurableThresholds.HTTP_OVERHEAD_REQUEST_DELAY.value: InternalProjectOptions.HTTP_OVERHEAD.value,
92+
"""
93+
A mapping of the management settings to the group type that the detector spawns.
94+
"""
95+
96+
thresholds_to_manage_map: dict[str, str] = {
97+
ConfigurableThresholds.N_PLUS_ONE_DB_DURATION.value: ConfigurableThresholds.N_PLUS_ONE_DB.value,
98+
ConfigurableThresholds.N_PLUS_ONE_DB_COUNT.value: ConfigurableThresholds.N_PLUS_ONE_DB.value,
99+
ConfigurableThresholds.UNCOMPRESSED_ASSET_DURATION.value: ConfigurableThresholds.UNCOMPRESSED_ASSET.value,
100+
ConfigurableThresholds.UNCOMPRESSED_ASSET_SIZE.value: ConfigurableThresholds.UNCOMPRESSED_ASSET.value,
101+
ConfigurableThresholds.LARGE_HTTP_PAYLOAD_SIZE.value: ConfigurableThresholds.LARGE_HTTP_PAYLOAD.value,
102+
ConfigurableThresholds.DB_ON_MAIN_THREAD_DURATION.value: ConfigurableThresholds.DB_ON_MAIN_THREAD.value,
103+
ConfigurableThresholds.FILE_IO_MAIN_THREAD_DURATION.value: ConfigurableThresholds.FILE_IO_MAIN_THREAD.value,
104+
ConfigurableThresholds.CONSECUTIVE_DB_QUERIES_MIN_TIME_SAVED.value: ConfigurableThresholds.CONSECUTIVE_DB_QUERIES.value,
105+
ConfigurableThresholds.RENDER_BLOCKING_ASSET_FCP_RATIO.value: ConfigurableThresholds.RENDER_BLOCKING_ASSET.value,
106+
ConfigurableThresholds.SLOW_DB_QUERY_DURATION.value: ConfigurableThresholds.SLOW_DB_QUERY.value,
107+
ConfigurableThresholds.N_PLUS_API_CALLS_DURATION.value: ConfigurableThresholds.N_PLUS_ONE_API_CALLS.value,
108+
ConfigurableThresholds.CONSECUTIVE_HTTP_SPANS_MIN_TIME_SAVED.value: ConfigurableThresholds.CONSECUTIVE_HTTP_SPANS.value,
109+
ConfigurableThresholds.HTTP_OVERHEAD_REQUEST_DELAY.value: ConfigurableThresholds.HTTP_OVERHEAD.value,
102110
}
111+
"""
112+
A mapping of threshold setting to the parent setting that manages it's detection.
113+
Used to determine if a threshold setting can be modified.
114+
"""
103115

104116

105117
class ProjectPerformanceIssueSettingsSerializer(serializers.Serializer):
@@ -155,15 +167,26 @@ class ProjectPerformanceIssueSettingsSerializer(serializers.Serializer):
155167
function_duration_regression_detection_enabled = serializers.BooleanField(required=False)
156168

157169

170+
def get_management_options() -> list[str]:
171+
"""
172+
Returns the option keys that control whether a performance issue detector is enabled.
173+
"""
174+
return [
175+
*[setting.value for setting in InternalProjectOptions],
176+
*list(thresholds_to_manage_map.values()),
177+
]
178+
179+
158180
def get_disabled_threshold_options(payload, current_settings):
181+
"""
182+
Returns the option keys that are disabled, based on the current management settings.
183+
"""
159184
options = []
160-
internal_only_settings = [setting.value for setting in InternalProjectOptions]
185+
management_options = get_management_options()
161186
for option in payload:
162-
if option not in internal_only_settings:
163-
internal_setting_for_option = configurable_thresholds_to_internal_settings_map.get(
164-
option
165-
)
166-
is_threshold_enabled = current_settings.get(internal_setting_for_option)
187+
if option not in management_options:
188+
manage_detector_setting = thresholds_to_manage_map.get(option)
189+
is_threshold_enabled = current_settings.get(manage_detector_setting)
167190
if not is_threshold_enabled:
168191
options.append(option)
169192
return options
@@ -222,15 +245,7 @@ def put(self, request: Request, project) -> Response:
222245
)
223246

224247
body_has_admin_options = any([option in request.data for option in internal_only_settings])
225-
if (
226-
body_has_admin_options
227-
and not superuser_has_permission(request)
228-
and not features.has(
229-
"organizations:performance-manage-detectors",
230-
project.organization,
231-
actor=request.user,
232-
)
233-
):
248+
if body_has_admin_options and not superuser_has_permission(request):
234249
return Response(
235250
{
236251
"detail": {
@@ -241,6 +256,9 @@ def put(self, request: Request, project) -> Response:
241256
status=status.HTTP_403_FORBIDDEN,
242257
)
243258

259+
body_has_management_options = any(
260+
[option in get_management_options() for option in request.data]
261+
)
244262
serializer = ProjectPerformanceIssueSettingsSerializer(data=request.data)
245263

246264
if not serializer.is_valid():
@@ -273,7 +291,7 @@ def put(self, request: Request, project) -> Response:
273291
{**performance_issue_settings_default, **performance_issue_settings, **data},
274292
)
275293

276-
if body_has_admin_options:
294+
if body_has_admin_options or body_has_management_options:
277295
self.create_audit_entry(
278296
request=self.request,
279297
actor=request.user,
@@ -290,16 +308,16 @@ def delete(self, request: Request, project) -> Response:
290308
return self.respond(status=status.HTTP_404_NOT_FOUND)
291309

292310
project_settings = project.get_option(SETTINGS_PROJECT_OPTION_KEY, default={})
311+
management_options = get_management_options()
293312
threshold_options = [setting.value for setting in ConfigurableThresholds]
294-
internal_only_settings = [setting.value for setting in InternalProjectOptions]
295313
disabled_options = get_disabled_threshold_options(threshold_options, project_settings)
296314

297315
if project_settings:
298316
unchanged_options = (
299-
{ # internal settings and disabled threshold settings can not be reset
317+
{ # Management settings and disabled threshold settings can not be reset
300318
option: project_settings[option]
301319
for option in project_settings
302-
if option in internal_only_settings or option in disabled_options
320+
if option in management_options or option in disabled_options
303321
}
304322
)
305323
project.update_option(SETTINGS_PROJECT_OPTION_KEY, unchanged_options)

src/sentry/audit_log/events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def __init__(self):
181181

182182
def render(self, audit_log_entry: AuditLogEntry):
183183
from sentry.api.endpoints.project_performance_issue_settings import (
184-
internal_only_project_settings_to_group_map as map,
184+
project_settings_to_group_map as map,
185185
)
186186

187187
data = audit_log_entry.data

src/sentry/autofix/utils.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,3 @@ def get_autofix_state_from_pr_id(provider: str, pr_id: int) -> AutofixState | No
146146
return None
147147

148148
return AutofixState.validate(result.get("state", None))
149-
150-
151-
class SeerAutomationSource(enum.Enum):
152-
ISSUE_DETAILS = "issue_details"
153-
ALERT = "alert"
154-
POST_PROCESS = "post_process"

src/sentry/integrations/utils/issue_summary_for_alerts.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import sentry_sdk
66

77
from sentry import features, options
8-
from sentry.autofix.utils import SeerAutomationSource
98
from sentry.issues.grouptype import GroupCategory
109
from sentry.models.group import Group
1110
from sentry.seer.issue_summary import get_issue_summary
@@ -33,9 +32,7 @@ def fetch_issue_summary(group: Group) -> dict[str, Any] | None:
3332
try:
3433
with sentry_sdk.start_span(op="ai_summary.fetch_issue_summary_for_alert"):
3534
with concurrent.futures.ThreadPoolExecutor() as executor:
36-
future = executor.submit(
37-
get_issue_summary, group, source=SeerAutomationSource.ALERT
38-
)
35+
future = executor.submit(get_issue_summary, group, source="alert")
3936
summary_result, status_code = future.result(timeout=timeout)
4037

4138
if status_code == 200:

src/sentry/seer/autofix.py

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
from django.utils import timezone
1212
from rest_framework.response import Response
1313

14-
from sentry import eventstore, features, quotas
14+
from sentry import eventstore, features
1515
from sentry.api.serializers import EventSerializer, serialize
1616
from sentry.autofix.utils import get_autofix_repos_from_project_code_mappings
17-
from sentry.constants import DataCategory, ObjectStatus
17+
from sentry.constants import ObjectStatus
1818
from sentry.eventstore.models import Event, GroupEvent
1919
from sentry.models.group import Group
2020
from sentry.models.project import Project
@@ -855,14 +855,6 @@ def trigger_autofix(
855855
403,
856856
)
857857

858-
# check billing quota for autofix
859-
has_budget: bool = quotas.backend.has_available_reserved_budget(
860-
org_id=group.organization.id,
861-
data_category=DataCategory.SEER_AUTOFIX,
862-
)
863-
if not has_budget:
864-
return _respond_with_error("No budget for Seer Autofix.", 402)
865-
866858
if event_id is None:
867859
event: Event | GroupEvent | None = group.get_recommended_event_for_environments()
868860
if not event:
@@ -932,11 +924,6 @@ def trigger_autofix(
932924

933925
group.update(seer_autofix_last_triggered=timezone.now())
934926

935-
# log billing event for seer autofix
936-
quotas.backend.record_seer_run(
937-
group.organization.id, group.project.id, DataCategory.SEER_AUTOFIX
938-
)
939-
940927
return Response(
941928
{
942929
"run_id": run_id,

0 commit comments

Comments
 (0)