Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/sentry/features/temporary.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,10 @@ def register_temporary_features(manager: FeatureManager) -> None:
manager.add("projects:supergroup-embeddings-explorer", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
# Enable lightweight Explorer RCA runs for supergroup quality evaluation
manager.add("projects:supergroup-lightweight-rca", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
# Enable lightweight RCA clustering write path (generate embeddings on new issues)
manager.add("organizations:supergroups-lightweight-rca-clustering-write", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
# Enable lightweight RCA clustering read path (query lightweight embeddings in supergroup APIs)
manager.add("organizations:supergroups-lightweight-rca-clustering-read", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)

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

Expand Down
8 changes: 0 additions & 8 deletions src/sentry/options/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -1385,14 +1385,6 @@
flags=FLAG_AUTOMATOR_MODIFIABLE,
)

# Supergroups / Lightweight RCA
register(
"supergroups.lightweight-enabled-orgs",
type=Sequence,
default=[],
flags=FLAG_ALLOW_EMPTY | FLAG_AUTOMATOR_MODIFIABLE,
)

# ## sentry.killswitches
#
# The following options are documented in sentry.killswitches in more detail
Expand Down
8 changes: 8 additions & 0 deletions src/sentry/seer/signed_seer_api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import hashlib
import hmac
import logging
from enum import StrEnum
from typing import Any, NotRequired, TypedDict
from urllib.parse import urlparse

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


class RCASource(StrEnum):
EXPLORER = "EXPLORER"
LIGHTWEIGHT = "LIGHTWEIGHT"


class SupergroupsEmbeddingRequest(TypedDict):
organization_id: int
group_id: int
Expand All @@ -387,11 +393,13 @@ class LightweightRCAClusterRequest(TypedDict):
class SupergroupsGetRequest(TypedDict):
organization_id: int
supergroup_id: int
rca_source: str


class SupergroupsGetByGroupIdsRequest(TypedDict):
organization_id: int
group_ids: list[int]
rca_source: str


class SupergroupDetailData(TypedDict):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from sentry.api.base import cell_silo_endpoint
from sentry.api.bases.organization import OrganizationEndpoint, OrganizationPermission
from sentry.models.organization import Organization
from sentry.seer.signed_seer_api import SeerViewerContext, make_supergroups_get_request
from sentry.seer.signed_seer_api import RCASource, SeerViewerContext, make_supergroups_get_request

logger = logging.getLogger(__name__)

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

rca_source = (
RCASource.LIGHTWEIGHT
if features.has(
"organizations:supergroups-lightweight-rca-clustering-read", organization
)
else RCASource.EXPLORER
)

response = make_supergroups_get_request(
{"organization_id": organization.id, "supergroup_id": supergroup_id},
{
"organization_id": organization.id,
"supergroup_id": supergroup_id,
"rca_source": rca_source,
},
SeerViewerContext(organization_id=organization.id, user_id=request.user.id),
timeout=10,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from sentry.models.group import STATUS_QUERY_CHOICES, Group
from sentry.models.organization import Organization
from sentry.seer.signed_seer_api import (
RCASource,
SeerViewerContext,
SupergroupsByGroupIdsResponse,
make_supergroups_get_by_group_ids_request,
Expand Down Expand Up @@ -77,8 +78,16 @@ def get(self, request: Request, organization: Organization) -> Response:
status=status_codes.HTTP_404_NOT_FOUND,
)

rca_source = (
RCASource.LIGHTWEIGHT
if features.has(
"organizations:supergroups-lightweight-rca-clustering-read", organization
)
else RCASource.EXPLORER
)

response = make_supergroups_get_by_group_ids_request(
{"organization_id": organization.id, "group_ids": group_ids},
{"organization_id": organization.id, "group_ids": group_ids, "rca_source": rca_source},
SeerViewerContext(organization_id=organization.id, user_id=request.user.id),
timeout=10,
)
Expand Down
5 changes: 3 additions & 2 deletions src/sentry/tasks/post_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -1593,8 +1593,9 @@ def kick_off_lightweight_rca_cluster(job: PostProcessJob) -> None:
event = job["event"]
group = event.group

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

trigger_lightweight_rca_cluster_task.delay(group.id)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from __future__ import annotations

from typing import Any
from unittest.mock import MagicMock, patch

import orjson

from sentry.testutils.cases import APITestCase


def mock_seer_response(data: dict[str, Any]) -> MagicMock:
response = MagicMock()
response.status = 200
response.data = orjson.dumps(data)
return response


class OrganizationSupergroupDetailsEndpointTest(APITestCase):
endpoint = "sentry-api-0-organization-supergroup-details"

def setUp(self):
super().setUp()
self.login_as(self.user)

@patch(
"sentry.seer.supergroups.endpoints.organization_supergroup_details.make_supergroups_get_request"
)
def test_get_supergroup_details(self, mock_seer):
mock_seer.return_value = mock_seer_response(
{"id": 1, "title": "NullPointerException in auth", "group_ids": [10, 20]}
)

with self.feature("organizations:top-issues-ui"):
response = self.get_success_response(self.organization.slug, "1")

assert response.data["id"] == 1
assert response.data["title"] == "NullPointerException in auth"
assert response.data["group_ids"] == [10, 20]

@patch(
"sentry.seer.supergroups.endpoints.organization_supergroup_details.make_supergroups_get_request"
)
def test_rca_source_defaults_to_explorer(self, mock_seer):
mock_seer.return_value = mock_seer_response({"id": 1, "title": "test"})

with self.feature("organizations:top-issues-ui"):
self.get_success_response(self.organization.slug, "1")

body = mock_seer.call_args.args[0]
assert body["rca_source"] == "EXPLORER"

@patch(
"sentry.seer.supergroups.endpoints.organization_supergroup_details.make_supergroups_get_request"
)
def test_rca_source_lightweight_when_flag_enabled(self, mock_seer):
mock_seer.return_value = mock_seer_response({"id": 1, "title": "test"})

with self.feature(
{
"organizations:top-issues-ui": True,
"organizations:supergroups-lightweight-rca-clustering-read": True,
}
):
self.get_success_response(self.organization.slug, "1")

body = mock_seer.call_args.args[0]
assert body["rca_source"] == "LIGHTWEIGHT"
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,38 @@ def test_status_filter_invalid(self):
status="bogus",
status_code=400,
)

@patch(
"sentry.seer.supergroups.endpoints.organization_supergroups_by_group.make_supergroups_get_by_group_ids_request"
)
def test_rca_source_defaults_to_explorer(self, mock_seer):
mock_seer.return_value = mock_seer_response({"data": []})

with self.feature("organizations:top-issues-ui"):
self.get_success_response(
self.organization.slug,
group_id=[self.unresolved_group.id],
)

body = mock_seer.call_args.args[0]
assert body["rca_source"] == "EXPLORER"

@patch(
"sentry.seer.supergroups.endpoints.organization_supergroups_by_group.make_supergroups_get_by_group_ids_request"
)
def test_rca_source_lightweight_when_flag_enabled(self, mock_seer):
mock_seer.return_value = mock_seer_response({"data": []})

with self.feature(
{
"organizations:top-issues-ui": True,
"organizations:supergroups-lightweight-rca-clustering-read": True,
}
):
self.get_success_response(
self.organization.slug,
group_id=[self.unresolved_group.id],
)

body = mock_seer.call_args.args[0]
assert body["rca_source"] == "LIGHTWEIGHT"
17 changes: 8 additions & 9 deletions tests/sentry/tasks/test_post_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -3078,7 +3078,7 @@ def test_kick_off_lightweight_rca_cluster_when_enabled(self, mock_task):
project_id=self.project.id,
)

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

with self.options({"supergroups.lightweight-enabled-orgs": []}):
self.call_post_process_group(
is_new=True,
is_regression=False,
is_new_group_environment=True,
event=event,
)
self.call_post_process_group(
is_new=True,
is_regression=False,
is_new_group_environment=True,
event=event,
)

mock_task.assert_not_called()

Expand All @@ -3112,7 +3111,7 @@ def test_kick_off_lightweight_rca_cluster_skips_when_not_new(self, mock_task):
project_id=self.project.id,
)

with self.options({"supergroups.lightweight-enabled-orgs": [self.project.organization.id]}):
with self.feature("organizations:supergroups-lightweight-rca-clustering-write"):
self.call_post_process_group(
is_new=False,
is_regression=False,
Expand Down
Loading