Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
EXPLORER = "explorer"
LIGHTWEIGHT = "lightweight"
EXPLORER = "EXPLORER"
LIGHTWEIGHT = "LIGHTWEIGHT"

to be consistent with seer?



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