Skip to content

Commit b1e86fe

Browse files
Mihir-Mavalankarryan953
authored andcommitted
feat(triage signals): Use new seat based pricing feature flag (#104528)
## PR Details + Added a new function to check for the triage-signals or the [billing feature flag in getsentry](https://github.com/getsentry/getsentry/blob/af9c348984e21886e0067ff5e8df9ad94489af1e/getsentry/features.py#L1915) to check if the org has signed up for new billing. + The function also caches the billing flag value since we want to reduce the number of DB reads. This function will later be used in `kick_off_seer_autoamtion` which is called for every event in every issue! + Just called this function once with a try catch to check that it works as expected. If there are no issues I will remove the try-except and replace all instances of the `features.has("organizations:triage-signals-v0-org")` check with this function. + Added cache invalidation when orgs sign up for the new billing.
1 parent 1bf0842 commit b1e86fe

File tree

5 files changed

+118
-3
lines changed

5 files changed

+118
-3
lines changed

src/sentry/seer/autofix/issue_summary.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
AutofixStoppingPoint,
2828
get_autofix_state,
2929
is_seer_autotriggered_autofix_rate_limited,
30+
is_seer_seat_based_tier_enabled,
3031
)
3132
from sentry.seer.models import SummarizeIssueResponse
3233
from sentry.seer.seer_setup import get_seer_org_acknowledgement
@@ -341,8 +342,16 @@ def run_automation(
341342
)
342343
return
343344

344-
# Only log for orgs with triage-signals-v0-org
345-
if features.has("organizations:triage-signals-v0-org", group.organization):
345+
# Only log for orgs with seat-based Seer tier
346+
try:
347+
should_log = is_seer_seat_based_tier_enabled(group.organization)
348+
except Exception:
349+
logger.exception(
350+
"Error checking if seat-based Seer tier is enabled", extra={"group_id": group.id}
351+
)
352+
should_log = False
353+
354+
if should_log:
346355
try:
347356
times_seen = group.times_seen_with_pending
348357
except (AssertionError, AttributeError):

src/sentry/seer/autofix/utils.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
)
2929
from sentry.seer.signed_seer_api import make_signed_seer_api_request, sign_with_seer_secret
3030
from sentry.utils import json
31+
from sentry.utils.cache import cache
3132
from sentry.utils.outcomes import Outcome, track_outcome
3233

3334
logger = logging.getLogger(__name__)
@@ -364,6 +365,30 @@ def is_seer_scanner_rate_limited(project: Project, organization: Organization) -
364365
return is_rate_limited
365366

366367

368+
def get_seer_seat_based_tier_cache_key(organization_id: int) -> str:
369+
"""Get the cache key for seat-based Seer tier check."""
370+
return f"seer:seat-based-tier:{organization_id}"
371+
372+
373+
def is_seer_seat_based_tier_enabled(organization: Organization) -> bool:
374+
"""
375+
Check if organization has Seer seat-based pricing via billing.
376+
"""
377+
if features.has("organizations:triage-signals-v0-org", organization):
378+
return True
379+
380+
cache_key = get_seer_seat_based_tier_cache_key(organization.id)
381+
cached_value = cache.get(cache_key)
382+
if cached_value is not None:
383+
return cached_value
384+
385+
logger.info("Checking if seat-based Seer tier is enabled for organization=%s", organization.id)
386+
has_seat_based_seer = features.has("organizations:seat-based-seer-enabled", organization)
387+
cache.set(cache_key, has_seat_based_seer, timeout=60 * 60 * 4) # 4 hours TTL
388+
389+
return has_seat_based_seer
390+
391+
367392
def is_issue_eligible_for_seer_automation(group: Group) -> bool:
368393
"""Check if Seer automation is allowed for a given group based on permissions and issue type."""
369394
from sentry import quotas

src/sentry/tasks/autofix.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
bulk_get_project_preferences,
1010
bulk_set_project_preferences,
1111
get_autofix_state,
12+
get_seer_seat_based_tier_cache_key,
1213
)
1314
from sentry.tasks.base import instrumented_task
1415
from sentry.taskworker.namespaces import ingest_errors_tasks, issues_tasks
1516
from sentry.taskworker.retry import Retry
17+
from sentry.utils.cache import cache
1618

1719
logger = logging.getLogger(__name__)
1820

@@ -123,6 +125,9 @@ def configure_seer_for_existing_org(organization_id: int) -> None:
123125
# Set org-level options
124126
organization.update_option("sentry:enable_seer_coding", True)
125127

128+
# Invalidate seat-based tier cache so new settings take effect immediately
129+
cache.delete(get_seer_seat_based_tier_cache_key(organization_id))
130+
126131
projects = list(Project.objects.filter(organization_id=organization_id, status=0))
127132
project_ids = [p.id for p in projects]
128133

tests/sentry/seer/autofix/test_autofix_utils.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
get_autofix_prompt,
1313
get_coding_agent_prompt,
1414
is_issue_eligible_for_seer_automation,
15+
is_seer_seat_based_tier_enabled,
1516
)
1617
from sentry.seer.models import SeerApiError
1718
from sentry.testutils.cases import TestCase
19+
from sentry.utils.cache import cache
1820

1921

2022
class TestGetAutofixPrompt(TestCase):
@@ -347,3 +349,57 @@ def test_returns_true_when_issue_type_always_triggers(
347349
result = is_issue_eligible_for_seer_automation(self.group)
348350

349351
assert result is True
352+
353+
354+
class TestIsSeerSeatBasedTierEnabled(TestCase):
355+
"""Test the is_seer_seat_based_tier_enabled function."""
356+
357+
def setUp(self):
358+
super().setUp()
359+
self.organization = self.create_organization(name="test-org")
360+
361+
def tearDown(self):
362+
super().tearDown()
363+
cache.delete(f"seer:seat-based-tier:{self.organization.id}")
364+
365+
def test_returns_true_when_triage_signals_enabled(self):
366+
"""Test returns True when triage-signals-v0-org feature flag is enabled."""
367+
with self.feature("organizations:triage-signals-v0-org"):
368+
result = is_seer_seat_based_tier_enabled(self.organization)
369+
assert result is True
370+
371+
@patch("sentry.seer.autofix.utils.features.has")
372+
def test_returns_true_when_seat_based_seer_enabled(self, mock_features_has):
373+
"""Test returns True when seat-based-seer-enabled feature flag is enabled and caches the result."""
374+
375+
def features_side_effect(flag, org):
376+
if flag == "organizations:seat-based-seer-enabled":
377+
return True
378+
return False
379+
380+
mock_features_has.side_effect = features_side_effect
381+
382+
result = is_seer_seat_based_tier_enabled(self.organization)
383+
assert result is True
384+
385+
# Verify it was cached
386+
cache_key = f"seer:seat-based-tier:{self.organization.id}"
387+
assert cache.get(cache_key) is True
388+
389+
def test_returns_false_when_no_flags_enabled(self):
390+
"""Test returns False when neither feature flag is enabled and caches the result."""
391+
result = is_seer_seat_based_tier_enabled(self.organization)
392+
assert result is False
393+
394+
# Verify False was cached
395+
cache_key = f"seer:seat-based-tier:{self.organization.id}"
396+
assert cache.get(cache_key) is False
397+
398+
def test_returns_cached_value(self):
399+
"""Test returns cached value without checking feature flags."""
400+
cache_key = f"seer:seat-based-tier:{self.organization.id}"
401+
cache.set(cache_key, True, timeout=60)
402+
403+
# Even without feature flags enabled, should return cached True
404+
result = is_seer_seat_based_tier_enabled(self.organization)
405+
assert result is True

tests/sentry/tasks/test_autofix.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
from django.test import TestCase
66

77
from sentry.seer.autofix.constants import AutofixStatus, SeerAutomationSource
8-
from sentry.seer.autofix.utils import AutofixState
8+
from sentry.seer.autofix.utils import AutofixState, get_seer_seat_based_tier_cache_key
99
from sentry.seer.models import SeerApiError, SummarizeIssueResponse, SummarizeIssueScores
1010
from sentry.tasks.autofix import (
1111
check_autofix_status,
1212
configure_seer_for_existing_org,
1313
generate_issue_summary_only,
1414
)
1515
from sentry.testutils.cases import TestCase as SentryTestCase
16+
from sentry.utils.cache import cache
1617

1718

1819
class TestCheckAutofixStatus(TestCase):
@@ -233,3 +234,22 @@ def test_raises_on_bulk_set_api_failure(
233234
# Sentry DB options should still be set before the API call
234235
assert project1.get_option("sentry:seer_scanner_automation") is True
235236
assert project2.get_option("sentry:seer_scanner_automation") is True
237+
238+
@patch("sentry.tasks.autofix.bulk_set_project_preferences")
239+
@patch("sentry.tasks.autofix.bulk_get_project_preferences")
240+
def test_invalidates_seat_based_tier_cache(
241+
self, mock_bulk_get: MagicMock, mock_bulk_set: MagicMock
242+
) -> None:
243+
"""Test that the seat-based tier cache is invalidated after configuring org."""
244+
self.create_project(organization=self.organization)
245+
mock_bulk_get.return_value = {}
246+
247+
# Set a cached value before running the task
248+
cache_key = get_seer_seat_based_tier_cache_key(self.organization.id)
249+
cache.set(cache_key, False, timeout=60 * 60 * 4)
250+
assert cache.get(cache_key) is False
251+
252+
configure_seer_for_existing_org(organization_id=self.organization.id)
253+
254+
# Cache should be invalidated
255+
assert cache.get(cache_key) is None

0 commit comments

Comments
 (0)