Skip to content

Commit bfd005d

Browse files
Merge branch 'master' into isabella/drop-incident-index
2 parents 36650e4 + 3d29bad commit bfd005d

File tree

55 files changed

+1003
-345
lines changed

Some content is hidden

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

55 files changed

+1003
-345
lines changed

.github/workflows/backend.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ jobs:
6666
needs: files-changed
6767
name: backend test
6868
runs-on: ubuntu-20.04
69-
timeout-minutes: 40
69+
timeout-minutes: 60
7070
strategy:
7171
# This helps not having to run multiple jobs because one fails, thus, reducing resource usage
7272
# and reducing the risk that one of many runs would turn red again (read: intermittent tests)

.github/workflows/migrations.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ jobs:
8585
echo 'EOF' >> "$GITHUB_OUTPUT"
8686
8787
- name: Generate SQL for migration
88-
uses: getsentry/action-migrations@f1dc34590460c0fe06ec11c00fec6c16a2159977 # main
88+
uses: getsentry/action-migrations@4d8ed0388dfc0774302bbfd5204e518f9ac4f066 # main
8989
env:
9090
SENTRY_LOG_LEVEL: ERROR
9191
with:

Makefile

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,11 @@ test-js-ci: node-version-check
128128
test-python-ci: create-db
129129
@echo "--> Running CI Python tests"
130130
pytest \
131-
tests/integration \
132-
tests/relay_integration \
133-
tests/sentry \
134-
tests/sentry_plugins \
135-
tests/symbolicator \
131+
tests \
132+
--ignore tests/acceptance \
133+
--ignore tests/apidocs \
134+
--ignore tests/js \
135+
--ignore tests/tools \
136136
--cov . --cov-report="xml:.artifacts/python.coverage.xml"
137137
@echo ""
138138

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,6 @@ module = [
600600
"sentry.sentry_metrics.configuration",
601601
"sentry.sentry_metrics.consumers.indexer.slicing_router",
602602
"sentry.sentry_metrics.indexer.postgres.postgres_v2",
603-
"sentry.services.smtp",
604603
"sentry.shared_integrations.client.base",
605604
"sentry.shared_integrations.client.proxy",
606605
"sentry.similarity.backends.dummy",

src/sentry/api/endpoints/organization_spans_aggregation.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ def fingerprint_nodes(
104104
the md5 hash of the value A-C-D and for span E would be md5 hash of the value A-C1-E.
105105
"""
106106
key = span_tree["key"]
107-
description = span_tree["description"]
108107
start_timestamp = span_tree["start_timestamp_ms"]
109108
if root_prefix is None:
110109
prefix = key
@@ -151,7 +150,7 @@ def fingerprint_nodes(
151150
"parent_node_fingerprint": parent_node_fingerprint,
152151
"group": span_tree["group"],
153152
"op": span_tree["op"],
154-
"description": description if span_tree["group"] == NULL_GROUP else description,
153+
"description": "" if span_tree["group"] == NULL_GROUP else span_tree["description"],
155154
"start_timestamp": start_timestamp,
156155
"start_ms": start_timestamp, # TODO: Remove after updating frontend, duplicated for backward compatibility
157156
"avg(exclusive_time)": span_tree["exclusive_time"],
@@ -263,9 +262,7 @@ def build_aggregate_span_tree(self, results: Any):
263262
"group": span.get("sentry_tags", {}).get("group")
264263
or span.get("data", {}).get("span.group", NULL_GROUP),
265264
"group_raw": span["hash"],
266-
"description": span.get("sentry_tags", {}).get("description")
267-
or span.get("data", {}).get("span.description")
268-
or span.get("description", ""),
265+
"description": span.get("sentry_tags", {}).get("description", ""),
269266
"op": span.get("op", ""),
270267
"start_timestamp_ms": span["start_timestamp"]
271268
* 1000, # timestamp is unix timestamp, convert to ms
@@ -315,6 +312,7 @@ def get(self, request: Request, organization: Organization) -> Response:
315312
return Response(status=404)
316313

317314
transaction = request.query_params.get("transaction", None)
315+
http_method = request.query_params.get("http.method", None)
318316
if transaction is None:
319317
return Response(
320318
status=status.HTTP_400_BAD_REQUEST, data={"details": "Transaction not provided"}
@@ -326,12 +324,16 @@ def get(self, request: Request, organization: Organization) -> Response:
326324
status=status.HTTP_400_BAD_REQUEST, data={"details": "Backend not supported"}
327325
)
328326

327+
query = f"transaction:{transaction}"
328+
if http_method is not None:
329+
query += f" transaction.method:{http_method}"
330+
329331
if backend == "indexedSpans":
330332
builder = SpansIndexedQueryBuilder(
331333
dataset=Dataset.SpansIndexed,
332334
params=params,
333335
selected_columns=["transaction_id", "count()"],
334-
query=f"transaction:{transaction}",
336+
query=query,
335337
limit=100,
336338
)
337339

@@ -370,9 +372,13 @@ def get(self, request: Request, organization: Organization) -> Response:
370372

371373
return Response(data=aggregated_tree)
372374

375+
conditions = [["transaction", "=", transaction]]
376+
if http_method is not None:
377+
conditions.append(["http.method", "=", http_method])
378+
373379
events = eventstore.backend.get_events(
374380
filter=eventstore.Filter(
375-
conditions=[["transaction", "=", transaction]],
381+
conditions=conditions,
376382
start=params["start"],
377383
end=params["end"],
378384
project_ids=params["project_id"],

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

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,9 @@ def measure_span(op_tag):
316316
return span
317317

318318
use_notifications_v2 = should_use_notifications_v2(item_list[0].organization)
319+
skip_subscriptions = features.has(
320+
"organizations:cleanup-project-serializer", item_list[0].organization
321+
)
319322
with measure_span("preamble"):
320323
project_ids = [i.id for i in item_list]
321324
if user.is_authenticated and item_list:
@@ -325,26 +328,30 @@ def measure_span(op_tag):
325328
).values_list("project_id", flat=True)
326329
)
327330

328-
if use_notifications_v2:
329-
subscriptions = notifications_service.get_subscriptions_for_projects(
330-
user_id=user.id,
331-
project_ids=project_ids,
332-
type=NotificationSettingEnum.ISSUE_ALERTS,
333-
)
334-
else:
335-
notification_settings_by_scope = transform_to_notification_settings_by_scope(
336-
notifications_service.get_settings_for_user_by_projects(
337-
type=NotificationSettingTypes.ISSUE_ALERTS,
331+
if not skip_subscriptions:
332+
if use_notifications_v2:
333+
subscriptions = notifications_service.get_subscriptions_for_projects(
338334
user_id=user.id,
339-
parent_ids=project_ids,
335+
project_ids=project_ids,
336+
type=NotificationSettingEnum.ISSUE_ALERTS,
337+
)
338+
else:
339+
notification_settings_by_scope = (
340+
transform_to_notification_settings_by_scope(
341+
notifications_service.get_settings_for_user_by_projects(
342+
type=NotificationSettingTypes.ISSUE_ALERTS,
343+
user_id=user.id,
344+
parent_ids=project_ids,
345+
)
346+
)
340347
)
341-
)
342348
else:
343349
bookmarks = set()
344-
if use_notifications_v2:
345-
subscriptions = {}
346-
else:
347-
notification_settings_by_scope = {}
350+
if not skip_subscriptions:
351+
if use_notifications_v2:
352+
subscriptions = {}
353+
else:
354+
notification_settings_by_scope = {}
348355

349356
with measure_span("stats"):
350357
stats = None
@@ -388,29 +395,29 @@ def measure_span(op_tag):
388395
else:
389396
recipient_actor = RpcActor.from_object(user)
390397
for project, serialized in result.items():
391-
# TODO(snigdha): why is this not included in the serializer
392-
is_subscribed = False
393-
if use_notifications_v2:
394-
if project.id in subscriptions:
395-
(_, has_enabled_subscriptions, _) = subscriptions[project.id]
396-
is_subscribed = has_enabled_subscriptions
398+
if not skip_subscriptions:
399+
is_subscribed = False
400+
if use_notifications_v2:
401+
if project.id in subscriptions:
402+
(_, has_enabled_subscriptions, _) = subscriptions[project.id]
403+
is_subscribed = has_enabled_subscriptions
404+
else:
405+
# If there are no settings, default to the EMAIL default
406+
# setting, which is ALWAYS.
407+
is_subscribed = True
397408
else:
398-
# If there are no settings, default to the EMAIL default
399-
# setting, which is ALWAYS.
400-
is_subscribed = True
401-
else:
402-
value = get_most_specific_notification_setting_value(
403-
notification_settings_by_scope,
404-
recipient=recipient_actor,
405-
parent_id=project.id,
406-
type=NotificationSettingTypes.ISSUE_ALERTS,
407-
)
408-
is_subscribed = value == NotificationSettingOptionValues.ALWAYS
409+
value = get_most_specific_notification_setting_value(
410+
notification_settings_by_scope,
411+
recipient=recipient_actor,
412+
parent_id=project.id,
413+
type=NotificationSettingTypes.ISSUE_ALERTS,
414+
)
415+
is_subscribed = value == NotificationSettingOptionValues.ALWAYS
416+
serialized["isSubscribed"] = is_subscribed
409417

410418
serialized.update(
411419
{
412420
"is_bookmarked": project.id in bookmarks,
413-
"is_subscribed": is_subscribed,
414421
"avatar": avatars.get(project.id),
415422
"platforms": platforms_by_project[project.id],
416423
}

src/sentry/conf/server.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,6 +1386,8 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]:
13861386
"organizations:api-keys": False,
13871387
# Enable multiple Apple app-store-connect sources per project.
13881388
"organizations:app-store-connect-multiple": False,
1389+
# Removes extra fields from the project serializers
1390+
"organizations:cleanup-project-serializer": False,
13891391
# Enable change alerts for an org
13901392
"organizations:change-alerts": True,
13911393
# Enable alerting based on crash free sessions/users

src/sentry/features/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
default_manager.add("organizations:alerts-migration-enabled", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
7373
default_manager.add("organizations:api-keys", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
7474
default_manager.add("organizations:auto-enable-codecov", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
75+
default_manager.add("organizations:cleanup-project-serializer", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
7576
default_manager.add("organizations:crons-new-monitor-form", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
7677
default_manager.add("organizations:customer-domains", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
7778
default_manager.add("organizations:dashboards-mep", OrganizationFeature, FeatureHandlerStrategy.REMOTE)

src/sentry/monitors/logic/mark_ok.py

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,6 @@
1313
def mark_ok(checkin: MonitorCheckIn, ts: datetime):
1414
monitor_env = checkin.monitor_environment
1515

16-
recovery_threshold = monitor_env.monitor.config.get("recovery_threshold", 0)
17-
if recovery_threshold:
18-
previous_checkins = (
19-
MonitorCheckIn.objects.filter(monitor_environment=monitor_env)
20-
.order_by("-date_added")
21-
.values("id", "date_added", "status")[:recovery_threshold]
22-
)
23-
# check for successive OK previous check-ins
24-
if not all(
25-
previous_checkin["status"] == CheckInStatus.OK for previous_checkin in previous_checkins
26-
):
27-
# don't send occurrence for active issue on an OK check-in
28-
return
29-
3016
next_checkin = monitor_env.monitor.get_next_expected_checkin(ts)
3117
next_checkin_latest = monitor_env.monitor.get_next_expected_checkin_latest(ts)
3218

@@ -35,17 +21,43 @@ def mark_ok(checkin: MonitorCheckIn, ts: datetime):
3521
"next_checkin": next_checkin,
3622
"next_checkin_latest": next_checkin_latest,
3723
}
38-
if checkin.status == CheckInStatus.OK:
39-
if monitor_env.monitor.status != ObjectStatus.DISABLED:
40-
params["status"] = MonitorStatus.OK
41-
# in the future this will auto-resolve associated issues
42-
if monitor_env.status != MonitorStatus.OK:
43-
params["last_state_change"] = ts
44-
# resolve any associated incidents
45-
incidents = MonitorIncident.objects.filter(
46-
monitor_environment=monitor_env, grouphash=monitor_env.incident_grouphash
24+
25+
if (
26+
monitor_env.monitor.status != ObjectStatus.DISABLED
27+
and monitor_env.status != MonitorStatus.OK
28+
):
29+
params["status"] = MonitorStatus.OK
30+
recovery_threshold = monitor_env.monitor.config.get("recovery_threshold")
31+
32+
# Run incident logic if recovery threshold is set
33+
if recovery_threshold:
34+
# Check if our incident is recovering
35+
previous_checkins = (
36+
MonitorCheckIn.objects.filter(monitor_environment=monitor_env)
37+
.values("id", "date_added", "status")
38+
.order_by("-date_added")[:recovery_threshold]
4739
)
48-
incidents.update(resolving_checkin=checkin, resolving_timestamp=checkin.date_added)
40+
41+
# Incident recovers when we have successive threshold check-ins
42+
incident_recovering = all(
43+
previous_checkin["status"] == CheckInStatus.OK
44+
for previous_checkin in previous_checkins
45+
)
46+
47+
# Resolve the incident
48+
if incident_recovering:
49+
MonitorIncident.objects.filter(
50+
monitor_environment=monitor_env,
51+
grouphash=monitor_env.incident_grouphash,
52+
).update(
53+
resolving_checkin=checkin,
54+
resolving_timestamp=checkin.date_added,
55+
)
56+
57+
params["last_state_change"] = ts
58+
else:
59+
# Don't update status if incident isn't recovered
60+
params.pop("status", None)
4961

5062
MonitorEnvironment.objects.filter(id=monitor_env.id).exclude(last_checkin__gt=ts).update(
5163
**params

src/sentry/runner/commands/run.py

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -201,34 +201,6 @@ def web(bind, workers, upgrade, with_lock, noinput):
201201
SentryHTTPServer(host=bind[0], port=bind[1], workers=workers).run()
202202

203203

204-
@run.command()
205-
@click.option(
206-
"--bind",
207-
"-b",
208-
default=None,
209-
help="Bind address.",
210-
metavar="ADDRESS",
211-
callback=_address_validate,
212-
)
213-
@click.option("--upgrade", default=False, is_flag=True, help="Upgrade before starting.")
214-
@click.option(
215-
"--noinput", default=False, is_flag=True, help="Do not prompt the user for input of any kind."
216-
)
217-
@configuration
218-
def smtp(bind, upgrade, noinput):
219-
"Run inbound email service."
220-
if upgrade:
221-
click.echo("Performing upgrade before service startup...")
222-
from sentry.runner import call_command
223-
224-
call_command("sentry.runner.commands.upgrade.upgrade", verbosity=0, noinput=noinput)
225-
226-
from sentry.services.smtp import SentrySMTPServer
227-
228-
with managed_bgtasks(role="smtp"):
229-
SentrySMTPServer(host=bind[0], port=bind[1]).run()
230-
231-
232204
def run_worker(**options):
233205
"""
234206
This is the inner function to actually start worker.

src/sentry/runner/commands/start.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
SERVICES = {
88
"http": "sentry.services.http.SentryHTTPServer",
9-
"smtp": "sentry.services.smtp.SentrySMTPServer",
109
}
1110

1211

src/sentry/search/events/datasets/field_aliases.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from sentry.models.projectteam import ProjectTeam
1111
from sentry.search.events import builder, constants, fields
1212
from sentry.search.events.types import SelectType
13+
from sentry.search.utils import DEVICE_CLASS
1314
from sentry.utils.numbers import format_grouped_length
1415

1516

@@ -123,3 +124,16 @@ def resolve_span_module(builder, alias: str) -> SelectType:
123124
],
124125
alias,
125126
)
127+
128+
129+
def resolve_device_class(builder: builder.QueryBuilder, alias: str) -> SelectType:
130+
values: List[str] = []
131+
keys: List[str] = []
132+
for device_key, device_values in DEVICE_CLASS.items():
133+
values.extend(device_values)
134+
keys.extend([device_key] * len(device_values))
135+
return Function(
136+
"transform",
137+
[builder.column("device.class"), values, keys, "Unknown"],
138+
alias,
139+
)

src/sentry/search/events/datasets/filter_aliases.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
to_list,
1717
)
1818
from sentry.search.events.types import WhereType
19-
from sentry.search.utils import parse_release
19+
from sentry.search.utils import DEVICE_CLASS, parse_release
2020
from sentry.utils.strings import oxfordize_list
2121

2222

@@ -270,3 +270,12 @@ def semver_build_filter_converter(
270270
versions = [constants.SEMVER_EMPTY_RELEASE]
271271

272272
return Condition(builder.column("release"), Op.IN, versions)
273+
274+
275+
def device_class_converter(
276+
builder: builder.QueryBuilder, search_filter: SearchFilter
277+
) -> Optional[WhereType]:
278+
value = search_filter.value.value
279+
if value not in DEVICE_CLASS:
280+
raise InvalidSearchQuery(f"{value} is not a supported device.class")
281+
return Condition(builder.column("device.class"), Op.IN, list(DEVICE_CLASS[value]))

0 commit comments

Comments
 (0)