Skip to content

Commit 2347515

Browse files
committed
feat(seer): Add rca_source to supergroup queries and use feature flag
Add RCASource enum and rca_source field to supergroup query requests so Seer knows which embedding space to query. The source is determined by the organizations:supergroups-lightweight-rca-clustering feature flag. Replace the supergroups.lightweight-enabled-orgs sentry-option with the feature flag for both the write path (post_process task dispatch) and read path (supergroup query endpoints), consistent with how all other supergroup features are gated.
1 parent 977cb0f commit 2347515

File tree

9 files changed

+141
-22
lines changed

9 files changed

+141
-22
lines changed

src/sentry/features/temporary.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,8 @@ def register_temporary_features(manager: FeatureManager) -> None:
565565
manager.add("projects:supergroup-embeddings-explorer", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
566566
# Enable lightweight Explorer RCA runs for supergroup quality evaluation
567567
manager.add("projects:supergroup-lightweight-rca", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
568+
# Use lightweight RCA source for supergroup clustering and queries
569+
manager.add("organizations:supergroups-lightweight-rca-clustering", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
568570

569571
manager.add("projects:workflow-engine-performance-detectors", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
570572

src/sentry/options/defaults.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,14 +1374,6 @@
13741374
flags=FLAG_MODIFIABLE_RATE | FLAG_AUTOMATOR_MODIFIABLE,
13751375
)
13761376

1377-
# Supergroups / Lightweight RCA
1378-
register(
1379-
"supergroups.lightweight-enabled-orgs",
1380-
type=Sequence,
1381-
default=[],
1382-
flags=FLAG_ALLOW_EMPTY | FLAG_AUTOMATOR_MODIFIABLE,
1383-
)
1384-
13851377
# ## sentry.killswitches
13861378
#
13871379
# The following options are documented in sentry.killswitches in more detail

src/sentry/seer/signed_seer_api.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import hashlib
22
import hmac
33
import logging
4+
from enum import StrEnum
45
from typing import Any, NotRequired, TypedDict
56
from urllib.parse import urlparse
67

@@ -369,6 +370,11 @@ class SummarizeIssueRequest(TypedDict):
369370
experiment_variant: NotRequired[str | None]
370371

371372

373+
class RCASource(StrEnum):
374+
EXPLORER = "explorer"
375+
LIGHTWEIGHT = "lightweight"
376+
377+
372378
class SupergroupsEmbeddingRequest(TypedDict):
373379
organization_id: int
374380
group_id: int
@@ -387,11 +393,13 @@ class LightweightRCAClusterRequest(TypedDict):
387393
class SupergroupsGetRequest(TypedDict):
388394
organization_id: int
389395
supergroup_id: int
396+
rca_source: str
390397

391398

392399
class SupergroupsGetByGroupIdsRequest(TypedDict):
393400
organization_id: int
394401
group_ids: list[int]
402+
rca_source: str
395403

396404

397405
class SupergroupDetailData(TypedDict):

src/sentry/seer/supergroups/endpoints/organization_supergroup_details.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from sentry.api.base import cell_silo_endpoint
1313
from sentry.api.bases.organization import OrganizationEndpoint, OrganizationPermission
1414
from sentry.models.organization import Organization
15-
from sentry.seer.signed_seer_api import SeerViewerContext, make_supergroups_get_request
15+
from sentry.seer.signed_seer_api import RCASource, SeerViewerContext, make_supergroups_get_request
1616

1717
logger = logging.getLogger(__name__)
1818

@@ -35,8 +35,18 @@ def get(self, request: Request, organization: Organization, supergroup_id: int)
3535
if not features.has("organizations:top-issues-ui", organization, actor=request.user):
3636
return Response({"detail": "Feature not available"}, status=403)
3737

38+
rca_source = (
39+
RCASource.LIGHTWEIGHT
40+
if features.has("organizations:supergroups-lightweight-rca-clustering", organization)
41+
else RCASource.EXPLORER
42+
)
43+
3844
response = make_supergroups_get_request(
39-
{"organization_id": organization.id, "supergroup_id": supergroup_id},
45+
{
46+
"organization_id": organization.id,
47+
"supergroup_id": supergroup_id,
48+
"rca_source": rca_source,
49+
},
4050
SeerViewerContext(organization_id=organization.id, user_id=request.user.id),
4151
timeout=10,
4252
)

src/sentry/seer/supergroups/endpoints/organization_supergroups_by_group.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from sentry.models.group import STATUS_QUERY_CHOICES, Group
1616
from sentry.models.organization import Organization
1717
from sentry.seer.signed_seer_api import (
18+
RCASource,
1819
SeerViewerContext,
1920
SupergroupsByGroupIdsResponse,
2021
make_supergroups_get_by_group_ids_request,
@@ -77,8 +78,14 @@ def get(self, request: Request, organization: Organization) -> Response:
7778
status=status_codes.HTTP_404_NOT_FOUND,
7879
)
7980

81+
rca_source = (
82+
RCASource.LIGHTWEIGHT
83+
if features.has("organizations:supergroups-lightweight-rca-clustering", organization)
84+
else RCASource.EXPLORER
85+
)
86+
8087
response = make_supergroups_get_by_group_ids_request(
81-
{"organization_id": organization.id, "group_ids": group_ids},
88+
{"organization_id": organization.id, "group_ids": group_ids, "rca_source": rca_source},
8289
SeerViewerContext(organization_id=organization.id, user_id=request.user.id),
8390
timeout=10,
8491
)

src/sentry/tasks/post_process.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,8 +1593,7 @@ def kick_off_lightweight_rca_cluster(job: PostProcessJob) -> None:
15931593
event = job["event"]
15941594
group = event.group
15951595

1596-
enabled_orgs: list[int] = options.get("supergroups.lightweight-enabled-orgs")
1597-
if group.organization.id not in enabled_orgs:
1596+
if not features.has("organizations:supergroups-lightweight-rca-clustering", group.organization):
15981597
return
15991598

16001599
trigger_lightweight_rca_cluster_task.delay(group.id)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from __future__ import annotations
2+
3+
from typing import Any
4+
from unittest.mock import MagicMock, patch
5+
6+
import orjson
7+
8+
from sentry.testutils.cases import APITestCase
9+
10+
11+
def mock_seer_response(data: dict[str, Any]) -> MagicMock:
12+
response = MagicMock()
13+
response.status = 200
14+
response.data = orjson.dumps(data)
15+
return response
16+
17+
18+
class OrganizationSupergroupDetailsEndpointTest(APITestCase):
19+
endpoint = "sentry-api-0-organization-supergroup-details"
20+
21+
def setUp(self):
22+
super().setUp()
23+
self.login_as(self.user)
24+
25+
@patch(
26+
"sentry.seer.supergroups.endpoints.organization_supergroup_details.make_supergroups_get_request"
27+
)
28+
def test_get_supergroup_details(self, mock_seer):
29+
mock_seer.return_value = mock_seer_response(
30+
{"id": 1, "title": "NullPointerException in auth", "group_ids": [10, 20]}
31+
)
32+
33+
with self.feature("organizations:top-issues-ui"):
34+
response = self.get_success_response(self.organization.slug, "1")
35+
36+
assert response.data["id"] == 1
37+
assert response.data["title"] == "NullPointerException in auth"
38+
assert response.data["group_ids"] == [10, 20]
39+
40+
@patch(
41+
"sentry.seer.supergroups.endpoints.organization_supergroup_details.make_supergroups_get_request"
42+
)
43+
def test_rca_source_defaults_to_explorer(self, mock_seer):
44+
mock_seer.return_value = mock_seer_response({"id": 1, "title": "test"})
45+
46+
with self.feature("organizations:top-issues-ui"):
47+
self.get_success_response(self.organization.slug, "1")
48+
49+
body = mock_seer.call_args.args[0]
50+
assert body["rca_source"] == "explorer"
51+
52+
@patch(
53+
"sentry.seer.supergroups.endpoints.organization_supergroup_details.make_supergroups_get_request"
54+
)
55+
def test_rca_source_lightweight_when_flag_enabled(self, mock_seer):
56+
mock_seer.return_value = mock_seer_response({"id": 1, "title": "test"})
57+
58+
with self.feature(
59+
{
60+
"organizations:top-issues-ui": True,
61+
"organizations:supergroups-lightweight-rca-clustering": True,
62+
}
63+
):
64+
self.get_success_response(self.organization.slug, "1")
65+
66+
body = mock_seer.call_args.args[0]
67+
assert body["rca_source"] == "lightweight"

tests/sentry/seer/supergroups/endpoints/test_organization_supergroups_by_group.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,38 @@ def test_status_filter_invalid(self):
7474
status="bogus",
7575
status_code=400,
7676
)
77+
78+
@patch(
79+
"sentry.seer.supergroups.endpoints.organization_supergroups_by_group.make_supergroups_get_by_group_ids_request"
80+
)
81+
def test_rca_source_defaults_to_explorer(self, mock_seer):
82+
mock_seer.return_value = mock_seer_response({"data": []})
83+
84+
with self.feature("organizations:top-issues-ui"):
85+
self.get_success_response(
86+
self.organization.slug,
87+
group_id=[self.unresolved_group.id],
88+
)
89+
90+
body = mock_seer.call_args.args[0]
91+
assert body["rca_source"] == "explorer"
92+
93+
@patch(
94+
"sentry.seer.supergroups.endpoints.organization_supergroups_by_group.make_supergroups_get_by_group_ids_request"
95+
)
96+
def test_rca_source_lightweight_when_flag_enabled(self, mock_seer):
97+
mock_seer.return_value = mock_seer_response({"data": []})
98+
99+
with self.feature(
100+
{
101+
"organizations:top-issues-ui": True,
102+
"organizations:supergroups-lightweight-rca-clustering": True,
103+
}
104+
):
105+
self.get_success_response(
106+
self.organization.slug,
107+
group_id=[self.unresolved_group.id],
108+
)
109+
110+
body = mock_seer.call_args.args[0]
111+
assert body["rca_source"] == "lightweight"

tests/sentry/tasks/test_post_process.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3078,7 +3078,7 @@ def test_kick_off_lightweight_rca_cluster_when_enabled(self, mock_task):
30783078
project_id=self.project.id,
30793079
)
30803080

3081-
with self.options({"supergroups.lightweight-enabled-orgs": [self.project.organization.id]}):
3081+
with self.feature("organizations:supergroups-lightweight-rca-clustering"):
30823082
self.call_post_process_group(
30833083
is_new=True,
30843084
is_regression=False,
@@ -3095,13 +3095,12 @@ def test_kick_off_lightweight_rca_cluster_skips_when_not_enabled(self, mock_task
30953095
project_id=self.project.id,
30963096
)
30973097

3098-
with self.options({"supergroups.lightweight-enabled-orgs": []}):
3099-
self.call_post_process_group(
3100-
is_new=True,
3101-
is_regression=False,
3102-
is_new_group_environment=True,
3103-
event=event,
3104-
)
3098+
self.call_post_process_group(
3099+
is_new=True,
3100+
is_regression=False,
3101+
is_new_group_environment=True,
3102+
event=event,
3103+
)
31053104

31063105
mock_task.assert_not_called()
31073106

@@ -3112,7 +3111,7 @@ def test_kick_off_lightweight_rca_cluster_skips_when_not_new(self, mock_task):
31123111
project_id=self.project.id,
31133112
)
31143113

3115-
with self.options({"supergroups.lightweight-enabled-orgs": [self.project.organization.id]}):
3114+
with self.feature("organizations:supergroups-lightweight-rca-clustering"):
31163115
self.call_post_process_group(
31173116
is_new=False,
31183117
is_regression=False,

0 commit comments

Comments
 (0)