diff --git a/src/sentry/dashboards/endpoints/organization_dashboard_generate.py b/src/sentry/dashboards/endpoints/organization_dashboard_generate.py index b5d148a4867e..b6a71e7b32a3 100644 --- a/src/sentry/dashboards/endpoints/organization_dashboard_generate.py +++ b/src/sentry/dashboards/endpoints/organization_dashboard_generate.py @@ -158,7 +158,7 @@ class OrganizationDashboardGenerateEndpoint(OrganizationEndpoint): permission_classes = (OrganizationDashboardGeneratePermission,) def post(self, request: Request, organization: Organization) -> Response: - has_access, error = has_seer_access_with_detail(organization, request.user) + has_access, error = has_seer_access_with_detail(organization) if not has_access: raise PermissionDenied(error) diff --git a/src/sentry/features/temporary.py b/src/sentry/features/temporary.py index f2fa8f7524b3..5e9bc5152f91 100644 --- a/src/sentry/features/temporary.py +++ b/src/sentry/features/temporary.py @@ -113,8 +113,6 @@ def register_temporary_features(manager: FeatureManager) -> None: manager.add("organizations:explore-errors", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True) # Enable returning the migrated discover queries in explore saved queries manager.add("organizations:expose-migrated-discover-queries", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True) - # Enable GenAI features such as Autofix and Issue Summary - manager.add("organizations:gen-ai-features", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True) # Enable organization investigation notebooks. manager.add("organizations:investigations", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True) # Enable the 'translate' functionality for GenAI on the explore > traces page diff --git a/src/sentry/feedback/endpoints/organization_feedback_categories.py b/src/sentry/feedback/endpoints/organization_feedback_categories.py index 1bb71c8270ea..13cab166f826 100644 --- a/src/sentry/feedback/endpoints/organization_feedback_categories.py +++ b/src/sentry/feedback/endpoints/organization_feedback_categories.py @@ -103,7 +103,7 @@ def get(self, request: Request, organization: Organization) -> Response: :auth: required """ - if not has_seer_access(organization, actor=request.user): + if not has_seer_access(organization): return Response( {"detail": "AI categorization is not available for this organization."}, status=403 ) diff --git a/src/sentry/feedback/endpoints/organization_feedback_summary.py b/src/sentry/feedback/endpoints/organization_feedback_summary.py index 4f10d724582c..ad5c8096ee69 100644 --- a/src/sentry/feedback/endpoints/organization_feedback_summary.py +++ b/src/sentry/feedback/endpoints/organization_feedback_summary.py @@ -90,7 +90,7 @@ def get(self, request: Request, organization: Organization) -> Response: :auth: required """ - if not has_seer_access(organization, actor=request.user): + if not has_seer_access(organization): return Response( {"detail": "AI summaries are not available for this organization."}, status=403 ) diff --git a/src/sentry/integrations/utils/external_issues.py b/src/sentry/integrations/utils/external_issues.py index 1f0c5b40df57..a32569d252b6 100644 --- a/src/sentry/integrations/utils/external_issues.py +++ b/src/sentry/integrations/utils/external_issues.py @@ -124,7 +124,7 @@ def maybe_generate_external_issue_details( ) -> GeneratedExternalIssueDetails: organization = group.organization empty_result = GeneratedExternalIssueDetails(title=None, description=None) - if not has_seer_access(organization, actor=user): + if not has_seer_access(organization): return empty_result if not features.has("organizations:external-issues-ai-generate", organization, actor=user): return empty_result diff --git a/src/sentry/replays/endpoints/project_replay_summary.py b/src/sentry/replays/endpoints/project_replay_summary.py index 10feec9b7856..512f65827835 100644 --- a/src/sentry/replays/endpoints/project_replay_summary.py +++ b/src/sentry/replays/endpoints/project_replay_summary.py @@ -152,7 +152,7 @@ def has_replay_summary_access(self, project: Project, request: Request) -> bool: project.organization, actor=request.user, ) - and has_seer_access(project.organization, actor=request.user) + and has_seer_access(project.organization) ) def get(self, request: Request, project: Project, replay_id: str) -> Response: diff --git a/src/sentry/seer/agent/client.py b/src/sentry/seer/agent/client.py index c0eba83a3747..be65e745393b 100644 --- a/src/sentry/seer/agent/client.py +++ b/src/sentry/seer/agent/client.py @@ -375,7 +375,7 @@ def __init__( raise ValueError("category_key and category_value must be provided together") # Validate base Seer access on init (agent-specific flag checks are done at the endpoint level) - has_access, error = has_seer_access_with_detail(organization, user) + has_access, error = has_seer_access_with_detail(organization) if not has_access: raise SeerPermissionError(error or "Access denied") diff --git a/src/sentry/seer/agent/client_utils.py b/src/sentry/seer/agent/client_utils.py index f3b013c7498c..800d444d0938 100644 --- a/src/sentry/seer/agent/client_utils.py +++ b/src/sentry/seer/agent/client_utils.py @@ -389,8 +389,8 @@ def has_seer_agent_access_with_detail( Returns: tuple[bool, str | None]: (has_access, error_message) """ - # Check base Seer access (gen-ai-features, hide_ai_features, acknowledgement) - has_access, error = has_seer_access_with_detail(organization, actor) + # Check base Seer access (self-hosted, hide_ai_features, acknowledgement) + has_access, error = has_seer_access_with_detail(organization) if not has_access: return False, error diff --git a/src/sentry/seer/autofix/issue_summary.py b/src/sentry/seer/autofix/issue_summary.py index a502f9158851..ba5ad795d836 100644 --- a/src/sentry/seer/autofix/issue_summary.py +++ b/src/sentry/seer/autofix/issue_summary.py @@ -41,7 +41,7 @@ from sentry.seer.entrypoints.operator import SeerAutofixOperator from sentry.seer.models import SummarizeIssueResponse from sentry.seer.models.run import SeerRun, SeerRunMirrorStatus -from sentry.seer.seer_setup import has_seer_access +from sentry.seer.seer_setup import has_seer_access, is_seer_available from sentry.seer.signed_seer_api import ( SeerViewerContext, SummarizeIssueRequest, @@ -585,8 +585,8 @@ def get_issue_summary( """ if user is None: user = AnonymousUser() - if not features.has("organizations:gen-ai-features", group.organization, actor=user): - return {"detail": "Feature flag not enabled"}, 400 + if not is_seer_available(): + return {"detail": "Seer is not available on this installation."}, 400 if group.organization.get_option("sentry:hide_ai_features"): return {"detail": "AI features are disabled for this organization."}, 403 diff --git a/src/sentry/seer/autofix/trigger.py b/src/sentry/seer/autofix/trigger.py index 7b73d1ec0d99..ea19890ee90a 100644 --- a/src/sentry/seer/autofix/trigger.py +++ b/src/sentry/seer/autofix/trigger.py @@ -28,14 +28,15 @@ def get_seer_automation_ineligibility_reason( group: Group, ) -> SeerAutomationIneligibilityReason | None: """Return the reason an issue is ineligible for Seer automation, or None if eligible.""" - from sentry import features, quotas + from sentry import quotas from sentry.constants import DataCategory from sentry.seer.autofix.utils import is_issue_category_eligible + from sentry.seer.seer_setup import is_seer_available if not is_issue_category_eligible(group): return "not_eligible.issue_category_ineligible" - if not features.has("organizations:gen-ai-features", group.organization): + if not is_seer_available(): return "not_eligible.gen_ai_feature_disabled" gen_ai_allowed = not group.organization.get_option("sentry:hide_ai_features") diff --git a/src/sentry/seer/endpoints/organization_seer_agent_chat.py b/src/sentry/seer/endpoints/organization_seer_agent_chat.py index 5517171f226d..9211597fb165 100644 --- a/src/sentry/seer/endpoints/organization_seer_agent_chat.py +++ b/src/sentry/seer/endpoints/organization_seer_agent_chat.py @@ -213,7 +213,7 @@ def get( """ has_access, error = has_seer_agent_access_with_detail(organization, request.user) - has_seer_access, _ = has_seer_access_with_detail(organization, request.user) + has_seer_access, _ = has_seer_access_with_detail(organization) if not has_access and not has_seer_access: raise PermissionDenied(error) @@ -263,7 +263,7 @@ def post( """ has_access, error = has_seer_agent_access_with_detail(organization, request.user) - has_seer_access, _ = has_seer_access_with_detail(organization, request.user) + has_seer_access, _ = has_seer_access_with_detail(organization) # Orgs with Seer access can continue existing dashboard generate runs, but cannot start new runs from this endpoint. can_continue_dashboards_generate_run = has_seer_access and run_id is not None diff --git a/src/sentry/seer/endpoints/search_agent_start.py b/src/sentry/seer/endpoints/search_agent_start.py index 359bff24649f..9e89af26e98b 100644 --- a/src/sentry/seer/endpoints/search_agent_start.py +++ b/src/sentry/seer/endpoints/search_agent_start.py @@ -161,7 +161,7 @@ def post(self, request: Request, organization: Organization) -> Response: status=status.HTTP_403_FORBIDDEN, ) - has_seer_access, detail = has_seer_access_with_detail(organization, actor=request.user) + has_seer_access, detail = has_seer_access_with_detail(organization) if not has_seer_access: return Response( {"detail": detail}, diff --git a/src/sentry/seer/endpoints/search_agent_state.py b/src/sentry/seer/endpoints/search_agent_state.py index 6fd186069eea..553b32caf1ea 100644 --- a/src/sentry/seer/endpoints/search_agent_state.py +++ b/src/sentry/seer/endpoints/search_agent_state.py @@ -91,7 +91,7 @@ def get(self, request: Request, organization: Organization, run_id: str) -> Resp status=status.HTTP_403_FORBIDDEN, ) - has_seer_access, detail = has_seer_access_with_detail(organization, actor=request.user) + has_seer_access, detail = has_seer_access_with_detail(organization) if not has_seer_access: return Response( {"detail": detail}, diff --git a/src/sentry/seer/endpoints/trace_explorer_ai_query.py b/src/sentry/seer/endpoints/trace_explorer_ai_query.py index 12146d67bf06..27d49e4c7599 100644 --- a/src/sentry/seer/endpoints/trace_explorer_ai_query.py +++ b/src/sentry/seer/endpoints/trace_explorer_ai_query.py @@ -8,7 +8,6 @@ from rest_framework.request import Request from rest_framework.response import Response -from sentry import features from sentry.api.api_owners import ApiOwner from sentry.api.api_publish_status import ApiPublishStatus from sentry.api.base import cell_silo_endpoint @@ -16,6 +15,7 @@ from sentry.models.organization import Organization from sentry.seer.endpoints.trace_explorer_ai_setup import OrganizationTraceExplorerAIPermission from sentry.seer.models import SeerApiError +from sentry.seer.seer_setup import is_seer_available from sentry.seer.signed_seer_api import ( SeerViewerContext, TranslateQueryRequest, @@ -97,9 +97,7 @@ def post(self, request: Request, organization: Organization) -> Response: status=status.HTTP_403_FORBIDDEN, ) - if not features.has( - "organizations:gen-ai-features", organization=organization, actor=request.user - ): + if not is_seer_available(): return Response( {"detail": "Organization does not have access to this feature"}, status=status.HTTP_403_FORBIDDEN, diff --git a/src/sentry/seer/endpoints/trace_explorer_ai_setup.py b/src/sentry/seer/endpoints/trace_explorer_ai_setup.py index 6c34880a50f8..c5095052005c 100644 --- a/src/sentry/seer/endpoints/trace_explorer_ai_setup.py +++ b/src/sentry/seer/endpoints/trace_explorer_ai_setup.py @@ -7,7 +7,6 @@ from rest_framework.exceptions import ParseError from rest_framework.response import Response -from sentry import features from sentry.api.api_owners import ApiOwner from sentry.api.api_publish_status import ApiPublishStatus from sentry.api.base import cell_silo_endpoint @@ -15,6 +14,7 @@ from sentry.api.bases.organization import OrganizationPermission from sentry.models.organization import Organization from sentry.seer.models import SeerApiError +from sentry.seer.seer_setup import is_seer_available from sentry.seer.signed_seer_api import ( CreateCacheRequest, SeerViewerContext, @@ -84,9 +84,7 @@ def post(self, request: Request, organization: Organization) -> Response: status=status.HTTP_403_FORBIDDEN, ) - if not features.has( - "organizations:gen-ai-features", organization=organization, actor=request.user - ): + if not is_seer_available(): return Response( {"detail": "Organization does not have access to this feature"}, status=status.HTTP_403_FORBIDDEN, diff --git a/src/sentry/seer/endpoints/trace_explorer_ai_translate_agentic.py b/src/sentry/seer/endpoints/trace_explorer_ai_translate_agentic.py index f9102b40100a..58620f024fd5 100644 --- a/src/sentry/seer/endpoints/trace_explorer_ai_translate_agentic.py +++ b/src/sentry/seer/endpoints/trace_explorer_ai_translate_agentic.py @@ -129,7 +129,7 @@ def post(self, request: Request, organization: Organization) -> Response: status=status.HTTP_403_FORBIDDEN, ) - has_seer_access, detail = has_seer_access_with_detail(organization, actor=request.user) + has_seer_access, detail = has_seer_access_with_detail(organization) if not has_seer_access: return Response( {"detail": detail}, diff --git a/src/sentry/seer/seer_setup.py b/src/sentry/seer/seer_setup.py index b2184c91b624..cec829053451 100644 --- a/src/sentry/seer/seer_setup.py +++ b/src/sentry/seer/seer_setup.py @@ -1,11 +1,7 @@ -from django.contrib.auth.models import AnonymousUser - from sentry import features from sentry.models.organization import Organization from sentry.organizations.services.organization.model import RpcOrganization from sentry.seer.constants import SEER_GITLAB_SCM_PROVIDERS, SEER_SUPPORTED_SCM_PROVIDERS -from sentry.users.models.user import User -from sentry.users.services.user.model import RpcUser from sentry.utils.settings import is_self_hosted @@ -20,27 +16,16 @@ def is_seer_available() -> bool: return not is_self_hosted() -def has_seer_access( - organization: Organization | RpcOrganization, - actor: User | AnonymousUser | RpcUser | None = None, -) -> bool: - return ( - is_seer_available() - and features.has("organizations:gen-ai-features", organization, actor=actor) - and not bool(organization.get_option("sentry:hide_ai_features")) - ) +def has_seer_access(organization: Organization | RpcOrganization) -> bool: + return is_seer_available() and not bool(organization.get_option("sentry:hide_ai_features")) def has_seer_access_with_detail( organization: Organization | RpcOrganization, - actor: User | AnonymousUser | RpcUser | None = None, ) -> tuple[bool, str | None]: if not is_seer_available(): return False, "Seer is not available on this installation." - if not features.has("organizations:gen-ai-features", organization, actor=actor): - return False, "Feature flag not enabled" - if organization.get_option("sentry:hide_ai_features"): return False, "AI features are disabled for this organization." diff --git a/src/sentry/tasks/seer/explorer_index.py b/src/sentry/tasks/seer/explorer_index.py index 467a2e523429..970e81ce14c4 100644 --- a/src/sentry/tasks/seer/explorer_index.py +++ b/src/sentry/tasks/seer/explorer_index.py @@ -35,7 +35,6 @@ EXPLORER_INDEX_DISPATCH_STEP = timedelta(seconds=37) FEATURE_NAMES = [ - "organizations:gen-ai-features", "organizations:seer-explorer-index", "organizations:seat-based-seer-enabled", "organizations:seer-added", @@ -73,43 +72,18 @@ def get_seer_explorer_enabled_projects() -> Generator[tuple[int, int]]: if bool(project.organization.get_option("sentry:hide_ai_features")): continue - is_eligible = False with start_span( op="seer_explorer_index.has_feature", name="seer_explorer_index.has_feature" ): batch_result = features.batch_has(FEATURE_NAMES, organization=project.organization) - if batch_result: - org_key = f"organization:{project.organization.id}" - org_features = batch_result.get(org_key, {}) - has_gen_ai = org_features.get("organizations:gen-ai-features", False) - has_explorer_index = org_features.get("organizations:seer-explorer-index", False) - - if has_explorer_index and has_gen_ai: - is_eligible = True - - has_seer_plan = org_features.get( - "organizations:seat-based-seer-enabled", False - ) or org_features.get("organizations:seer-added", False) - - if has_seer_plan and has_gen_ai: - is_eligible = True - + org_features = batch_result.get(f"organization:{project.organization.id}", {}) else: - has_gen_ai = features.has("organizations:gen-ai-features", project.organization) - has_explorer_index = features.has( - "organizations:seer-explorer-index", project.organization - ) - - if has_explorer_index and has_gen_ai: - is_eligible = True - - has_seer_plan = features.has( - "organizations:seat-based-seer-enabled", project.organization - ) or features.has("organizations:seer-added", project.organization) + org_features = { + name: features.has(name, project.organization) for name in FEATURE_NAMES + } - if has_seer_plan and has_gen_ai: - is_eligible = True + is_eligible = any(org_features.get(name, False) for name in FEATURE_NAMES) if not is_eligible: continue diff --git a/src/sentry/tasks/seer/night_shift/cron.py b/src/sentry/tasks/seer/night_shift/cron.py index 76ba01b010c6..39bbd7c5330c 100644 --- a/src/sentry/tasks/seer/night_shift/cron.py +++ b/src/sentry/tasks/seer/night_shift/cron.py @@ -79,7 +79,6 @@ BATCH_FEATURE_NAMES = [ "organizations:seer-night-shift", - "organizations:gen-ai-features", ] PER_ORG_FEATURE_NAMES = [ # INTERNAL handlers aren't routed through batch_has_for_organizations, @@ -610,7 +609,7 @@ def _get_eligible_orgs_from_batch( for org in eligible: if all(features.has(f, org) for f in PER_ORG_FEATURE_NAMES): paid_eligible.append(org) - elif features.has("organizations:gen-ai-features", org) and is_free_cohort_org(org): + elif is_free_cohort_org(org): free_cohort_eligible.append(org) return paid_eligible + free_cohort_eligible diff --git a/src/sentry/testutils/helpers/github.py b/src/sentry/testutils/helpers/github.py index 4ad4c8046938..b6bc753fe04e 100644 --- a/src/sentry/testutils/helpers/github.py +++ b/src/sentry/testutils/helpers/github.py @@ -144,7 +144,7 @@ def send_github_webhook_event( @override_settings(SENTRY_SELF_HOSTED=False) class GitHubWebhookCodeReviewTestCase(GitHubWebhookTestCase): # Code review features are org features as set in options automator - CODE_REVIEW_FEATURES = {"organizations:gen-ai-features", "organizations:code-review-beta"} + CODE_REVIEW_FEATURES = {"organizations:code-review-beta"} # Options to set are regional options as set in options automator OPTIONS_TO_SET: dict[str, Any] = {} # Org options are org options as set via OrganizationOption.objects.set_value diff --git a/src/sentry/uptime/endpoints/organization_uptime_assertion_suggestions.py b/src/sentry/uptime/endpoints/organization_uptime_assertion_suggestions.py index 855aa428ac54..4f4a5d91945b 100644 --- a/src/sentry/uptime/endpoints/organization_uptime_assertion_suggestions.py +++ b/src/sentry/uptime/endpoints/organization_uptime_assertion_suggestions.py @@ -84,8 +84,7 @@ def post( request: Request, organization: Organization, ) -> Response: - # Check if AI features are enabled (includes gen-ai-features flag + hide_ai_features opt-out) - if not has_seer_access(organization, actor=request.user): + if not has_seer_access(organization): return self.respond( {"detail": "AI features are not enabled for this organization"}, status=403, diff --git a/static/app/components/feedback/feedbackItem/feedbackItemUsername.spec.tsx b/static/app/components/feedback/feedbackItem/feedbackItemUsername.spec.tsx index 704fd2f0da43..7f5c1353f571 100644 --- a/static/app/components/feedback/feedbackItem/feedbackItemUsername.spec.tsx +++ b/static/app/components/feedback/feedbackItem/feedbackItemUsername.spec.tsx @@ -3,6 +3,7 @@ import {FeedbackIssueFixture} from 'sentry-fixture/feedbackIssue'; import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary'; import {FeedbackItemUsername} from 'sentry/components/feedback/feedbackItem/feedbackItemUsername'; +import {ConfigStore} from 'sentry/stores/configStore'; describe('FeedbackItemUsername', () => { let seerSetupMock: any; @@ -135,6 +136,10 @@ describe('FeedbackItemUsername', () => { }); describe('AI summary functionality', () => { + beforeEach(() => { + ConfigStore.set('isSelfHosted', false); + }); + it('should display summary and include it in email subject when AI summary is enabled', async () => { seerSetupMock = mockSeerSetup(); @@ -146,11 +151,7 @@ describe('FeedbackItemUsername', () => { }, }); - render(, { - organization: { - features: ['gen-ai-features'], - }, - }); + render(); await waitFor(() => { expect(seerSetupMock).toHaveBeenCalled(); @@ -168,17 +169,18 @@ describe('FeedbackItemUsername', () => { it.each([ { description: 'AI features are disabled', - features: [] as string[], + isSelfHosted: true, summary: 'Login issue with payment flow', }, { description: 'AI features enabled but summary is null', - features: ['gen-ai-features'], + isSelfHosted: false, summary: null, }, ])( 'should not display summary or include it in email subject when $description', - async ({features, summary}) => { + async ({isSelfHosted, summary}) => { + ConfigStore.set('isSelfHosted', isSelfHosted); seerSetupMock = mockSeerSetup(); const issue = FeedbackIssueFixture({ @@ -189,11 +191,7 @@ describe('FeedbackItemUsername', () => { }, }); - render(, { - organization: { - features, - }, - }); + render(); await waitFor(() => { expect(seerSetupMock).toHaveBeenCalled(); diff --git a/static/app/components/feedback/summaryCategories/feedbackCategories.tsx b/static/app/components/feedback/summaryCategories/feedbackCategories.tsx index 68459967f6ac..a78e4f7fe980 100644 --- a/static/app/components/feedback/summaryCategories/feedbackCategories.tsx +++ b/static/app/components/feedback/summaryCategories/feedbackCategories.tsx @@ -39,8 +39,7 @@ function getSearchTermForLabelList(labels: string[]) { export function FeedbackCategories() { const {isError, isPending, categories, tooFewFeedbacks} = useFeedbackCategories(); - // if we are showing this component, gen-ai-features must be true - // and org.hideAiFeatures must be false, + // if we are showing this component, AI features are allowed for the org, // but we still need to check that their seer acknowledgement exists const {isPending: isOrgSeerSetupPending} = useOrganizationSeerSetup(); diff --git a/static/app/components/feedback/summaryCategories/feedbackSummary.tsx b/static/app/components/feedback/summaryCategories/feedbackSummary.tsx index ac59749a977e..fe7dffbaa742 100644 --- a/static/app/components/feedback/summaryCategories/feedbackSummary.tsx +++ b/static/app/components/feedback/summaryCategories/feedbackSummary.tsx @@ -11,8 +11,7 @@ import {useOrganization} from 'sentry/utils/useOrganization'; export function FeedbackSummary() { const {isError, isPending, summary, tooFewFeedbacks} = useFeedbackSummary(); - // if we are showing this component, gen-ai-features must be true - // and org.hideAiFeatures must be false, + // if we are showing this component, AI features are allowed for the org, // but we still need to check that their seer acknowledgement exists const {isPending: isOrgSeerSetupPending} = useOrganizationSeerSetup(); const organization = useOrganization(); diff --git a/static/app/components/searchQueryBuilder/askSeerCombobox/askSeerComboBox.spec.tsx b/static/app/components/searchQueryBuilder/askSeerCombobox/askSeerComboBox.spec.tsx index c5892126fa39..cf394b3542c9 100644 --- a/static/app/components/searchQueryBuilder/askSeerCombobox/askSeerComboBox.spec.tsx +++ b/static/app/components/searchQueryBuilder/askSeerCombobox/askSeerComboBox.spec.tsx @@ -13,6 +13,7 @@ import { SearchQueryBuilderProvider, useSearchQueryBuilderAI, } from 'sentry/components/searchQueryBuilder/context'; +import {ConfigStore} from 'sentry/stores/configStore'; import * as analytics from 'sentry/utils/analytics'; import {getApiUrl} from 'sentry/utils/api/getApiUrl'; import {fetchMutation} from 'sentry/utils/queryClient'; @@ -47,7 +48,7 @@ const askSeerMutationOptions = mutationOptions({ }); const {organization} = initializeOrg({ - organization: {features: ['gen-ai-features'], hideAiFeatures: false}, + organization: {hideAiFeatures: false}, }); const feedbackIntegration = { @@ -78,6 +79,8 @@ describe('AskSeerComboBox', () => { // Combobox announcements will pollute the test output if we don't clear them destroyAnnouncer(); + ConfigStore.set('isSelfHosted', false); + MockApiClient.clearMockResponses(); MockApiClient.addMockResponse({ @@ -586,7 +589,8 @@ describe('AskSeerComboBox', () => { await waitFor(() => expect(queryRequest).toHaveBeenCalledTimes(2)); }); - it('does not render if the organization does not have the gen-ai-features feature', () => { + it('does not render when self-hosted', () => { + ConfigStore.set('isSelfHosted', true); const {container} = render( { }); const {organization} = initializeOrg({ organization: { - features: ['gen-ai-features'], hideAiFeatures: false, }, }); diff --git a/static/app/components/searchQueryBuilder/index.spec.tsx b/static/app/components/searchQueryBuilder/index.spec.tsx index 8953517fe9f5..b64b282f4d18 100644 --- a/static/app/components/searchQueryBuilder/index.spec.tsx +++ b/static/app/components/searchQueryBuilder/index.spec.tsx @@ -7258,11 +7258,7 @@ describe('SearchQueryBuilder', () => { describe('ask seer', () => { it('renders ask seer in the footer', async () => { - render(, { - organization: { - features: ['gen-ai-features'], - }, - }); + render(); await userEvent.click(getLastInput()); @@ -7284,12 +7280,7 @@ describe('SearchQueryBuilder', () => { onCaseInsensitiveClick={jest.fn()} /> - , - { - organization: { - features: ['gen-ai-features'], - }, - } + ); await userEvent.click(getLastInput()); @@ -7326,12 +7317,7 @@ describe('SearchQueryBuilder', () => { {...defaultProps} enableAISearch initialQuery="browser.name:Firefox" - />, - { - organization: { - features: ['gen-ai-features'], - }, - } + /> ); await userEvent.click( @@ -7345,11 +7331,7 @@ describe('SearchQueryBuilder', () => { }); it('does not render ask seer in the footer when AI search is disabled', async () => { - render(, { - organization: { - features: ['gen-ai-features'], - }, - }); + render(); await userEvent.click(getLastInput()); @@ -7456,12 +7438,7 @@ describe('SearchQueryBuilder', () => { render( - , - { - organization: { - features: ['gen-ai-features'], - }, - } + ); await userEvent.click(getLastInput()); @@ -7542,11 +7519,7 @@ describe('SearchQueryBuilder', () => { } it('keeps ask seer in the footer when searching free text', async () => { - render(, { - organization: { - features: ['gen-ai-features'], - }, - }); + render(); await userEvent.click(getLastInput()); await userEvent.type(screen.getByRole('combobox'), 'some free text'); @@ -7572,12 +7545,7 @@ describe('SearchQueryBuilder', () => { - , - { - organization: { - features: ['gen-ai-features'], - }, - } + ); await userEvent.click(getLastInput()); @@ -7614,12 +7582,7 @@ describe('SearchQueryBuilder', () => { - , - { - organization: { - features: ['gen-ai-features'], - }, - } + ); await userEvent.click(getLastInput()); @@ -7656,12 +7619,7 @@ describe('SearchQueryBuilder', () => { - , - { - organization: { - features: ['gen-ai-features'], - }, - } + ); await userEvent.click(getLastInput()); @@ -7689,12 +7647,7 @@ describe('SearchQueryBuilder', () => { - , - { - organization: { - features: ['gen-ai-features'], - }, - } + ); await userEvent.click(screen.getByRole('row', {name: 'find slow'})); @@ -7731,12 +7684,7 @@ describe('SearchQueryBuilder', () => { - , - { - organization: { - features: ['gen-ai-features'], - }, - } + ); await userEvent.click(getLastInput()); @@ -7767,12 +7715,7 @@ describe('SearchQueryBuilder', () => { - , - { - organization: { - features: ['gen-ai-features'], - }, - } + ); await userEvent.click(getLastInput()); @@ -7804,12 +7747,7 @@ describe('SearchQueryBuilder', () => { - , - { - organization: { - features: ['gen-ai-features'], - }, - } + ); await userEvent.click(getLastInput()); diff --git a/static/app/components/seer/autofixChatContext.spec.tsx b/static/app/components/seer/autofixChatContext.spec.tsx index 9ccd73bfbcff..950bdcf34d2b 100644 --- a/static/app/components/seer/autofixChatContext.spec.tsx +++ b/static/app/components/seer/autofixChatContext.spec.tsx @@ -48,7 +48,7 @@ describe('AutofixChatProvider', () => { const organization = OrganizationFixture({ openMembership: true, hideAiFeatures: false, - features: ['seer-explorer', 'gen-ai-features'], + features: ['seer-explorer'], }); const chatUrl = `/organizations/${organization.slug}/seer/explorer-chat/`; diff --git a/static/app/utils/replays/hooks/useActiveReplayTab.spec.tsx b/static/app/utils/replays/hooks/useActiveReplayTab.spec.tsx index e38eb9ad541a..0794a657799d 100644 --- a/static/app/utils/replays/hooks/useActiveReplayTab.spec.tsx +++ b/static/app/utils/replays/hooks/useActiveReplayTab.spec.tsx @@ -22,7 +22,7 @@ describe('useActiveReplayTab', () => { const {result} = renderHookWithProviders(useActiveReplayTab, { initialProps: {}, organization: OrganizationFixture({ - features: ['gen-ai-features', 'replay-ai-summaries'], + features: ['replay-ai-summaries'], }), }); @@ -35,7 +35,7 @@ describe('useActiveReplayTab', () => { const {result} = renderHookWithProviders(useActiveReplayTab, { initialProps: {}, organization: OrganizationFixture({ - features: ['gen-ai-features', 'replay-ai-summaries'], + features: ['replay-ai-summaries'], }), }); @@ -49,7 +49,7 @@ describe('useActiveReplayTab', () => { location: {pathname: '/mock-pathname/', query: {query: 'click.tag:button'}}, }, organization: OrganizationFixture({ - features: ['gen-ai-features', 'replay-ai-summaries'], + features: ['replay-ai-summaries'], }), }); expect(result.current.getActiveTab()).toBe(TabKey.AI); @@ -67,11 +67,7 @@ describe('useActiveReplayTab', () => { const {result} = renderHookWithProviders(useActiveReplayTab, { initialProps: {isVideoReplay: true}, organization: OrganizationFixture({ - features: [ - 'gen-ai-features', - 'replay-ai-summaries', - 'replay-ai-summaries-mobile', - ], + features: ['replay-ai-summaries', 'replay-ai-summaries-mobile'], }), }); @@ -82,7 +78,7 @@ describe('useActiveReplayTab', () => { const {result} = renderHookWithProviders(useActiveReplayTab, { initialProps: {isVideoReplay: true}, organization: OrganizationFixture({ - features: ['gen-ai-features', 'replay-ai-summaries'], + features: ['replay-ai-summaries'], }), }); diff --git a/static/app/utils/seer/areAiFeaturesAllowed.spec.ts b/static/app/utils/seer/areAiFeaturesAllowed.spec.ts index 3f11286e12b6..8c0d4709909a 100644 --- a/static/app/utils/seer/areAiFeaturesAllowed.spec.ts +++ b/static/app/utils/seer/areAiFeaturesAllowed.spec.ts @@ -8,33 +8,19 @@ describe('areAiFeaturesAllowed', () => { ConfigStore.set('isSelfHosted', false); }); - it('allows when flagged, not hidden, and not self-hosted', () => { - const organization = OrganizationFixture({ - features: ['gen-ai-features'], - hideAiFeatures: false, - }); + it('allows when not hidden and not self-hosted', () => { + const organization = OrganizationFixture({hideAiFeatures: false}); expect(areAiFeaturesAllowed(organization)).toBe(true); }); - it('denies without the flag', () => { - const organization = OrganizationFixture({features: [], hideAiFeatures: false}); - expect(areAiFeaturesAllowed(organization)).toBe(false); - }); - it('denies when the org hides AI features', () => { - const organization = OrganizationFixture({ - features: ['gen-ai-features'], - hideAiFeatures: true, - }); + const organization = OrganizationFixture({hideAiFeatures: true}); expect(areAiFeaturesAllowed(organization)).toBe(false); }); it('denies on self-hosted', () => { ConfigStore.set('isSelfHosted', true); - const organization = OrganizationFixture({ - features: ['gen-ai-features'], - hideAiFeatures: false, - }); + const organization = OrganizationFixture({hideAiFeatures: false}); expect(areAiFeaturesAllowed(organization)).toBe(false); }); }); diff --git a/static/app/utils/seer/areAiFeaturesAllowed.ts b/static/app/utils/seer/areAiFeaturesAllowed.ts index 66084530b2c8..81d818f2d365 100644 --- a/static/app/utils/seer/areAiFeaturesAllowed.ts +++ b/static/app/utils/seer/areAiFeaturesAllowed.ts @@ -2,11 +2,7 @@ import {ConfigStore} from 'sentry/stores/configStore'; import type {Organization} from 'sentry/types/organization'; export function areAiFeaturesAllowed( - organization: Pick + organization: Pick ): boolean { - return ( - !ConfigStore.get('isSelfHosted') && - !organization.hideAiFeatures && - organization.features.includes('gen-ai-features') - ); + return !ConfigStore.get('isSelfHosted') && !organization.hideAiFeatures; } diff --git a/static/app/views/dashboards/manage/index.spec.tsx b/static/app/views/dashboards/manage/index.spec.tsx index 03e4989774b0..9e22cacd789a 100644 --- a/static/app/views/dashboards/manage/index.spec.tsx +++ b/static/app/views/dashboards/manage/index.spec.tsx @@ -119,7 +119,7 @@ describe('Dashboards > Detail', () => { }); it('creates new dashboard', async () => { - const org = OrganizationFixture({features: FEATURES}); + const org = OrganizationFixture({features: FEATURES, hideAiFeatures: true}); const {router} = render(, { organization: org, diff --git a/static/app/views/dashboards/widgetBuilder/buildSteps/filterResultsStep/eventsSearchBar.spec.tsx b/static/app/views/dashboards/widgetBuilder/buildSteps/filterResultsStep/eventsSearchBar.spec.tsx index 95bfa2be2c83..b1e94c691efa 100644 --- a/static/app/views/dashboards/widgetBuilder/buildSteps/filterResultsStep/eventsSearchBar.spec.tsx +++ b/static/app/views/dashboards/widgetBuilder/buildSteps/filterResultsStep/eventsSearchBar.spec.tsx @@ -43,7 +43,7 @@ describe('EventsSearchBar', () => { it('hides Ask Seer for errors widgets', async () => { organization = OrganizationFixture({ - features: ['gen-ai-features', 'gen-ai-search-agent-translate'], + features: ['gen-ai-search-agent-translate'], }); render( diff --git a/static/app/views/explore/spans/content.spec.tsx b/static/app/views/explore/spans/content.spec.tsx index ddfbdb000878..33f5b36fdb85 100644 --- a/static/app/views/explore/spans/content.spec.tsx +++ b/static/app/views/explore/spans/content.spec.tsx @@ -26,15 +26,11 @@ function TopBarWrapper({children}: {children: ReactNode}) { } describe('ExploreContent', () => { - const {organization, project} = initializeOrg({ - organization: { - features: ['gen-ai-features'], - }, - }); + const {organization, project} = initializeOrg(); const {organization: highRangeOrganization, project: highRangeProject} = initializeOrg({ organization: { slug: 'high-range-org', - features: ['gen-ai-features', 'visibility-explore-range-high'], + features: ['visibility-explore-range-high'], }, }); @@ -213,7 +209,7 @@ describe('ExploreContent', () => { const highRangeOrganizationWithoutFeature = { ...highRangeOrganization, - features: ['gen-ai-features'], + features: [], }; const {rerender} = render( @@ -291,7 +287,7 @@ describe('ExploreContent', () => { const highRangeOrganizationWithOverride = { ...highRangeOrganization, - features: ['gen-ai-features'], + features: [], }; OrganizationStore.onUpdate(highRangeOrganizationWithOverride, {replace: true}); diff --git a/static/app/views/explore/spans/spansTab.spec.tsx b/static/app/views/explore/spans/spansTab.spec.tsx index 81bf33d57432..099d3e39ca50 100644 --- a/static/app/views/explore/spans/spansTab.spec.tsx +++ b/static/app/views/explore/spans/spansTab.spec.tsx @@ -14,6 +14,7 @@ import { import {ALL_ACCESS_PROJECTS} from 'sentry/components/pageFilters/constants'; import type {DatePageFilterProps} from 'sentry/components/pageFilters/date/datePageFilter'; import {PageFiltersStore} from 'sentry/components/pageFilters/store'; +import {ConfigStore} from 'sentry/stores/configStore'; import {ProjectsStore} from 'sentry/stores/projectsStore'; import type {Project} from 'sentry/types/project'; import {trackAnalytics} from 'sentry/utils/analytics'; @@ -64,11 +65,7 @@ const invalidAttributeValidationBody: EventValidationData = { }; describe('SpansTabContent', () => { - const {organization, project} = initializeOrg({ - organization: { - features: ['gen-ai-features'], - }, - }); + const {organization, project} = initializeOrg(); function setProjects(projects: Project[], selectedProjectIds?: number[]) { ProjectsStore.loadInitialData(projects); @@ -536,9 +533,14 @@ describe('SpansTabContent', () => { describe('Ask Seer', () => { describe('when the AI features are disabled', () => { + afterEach(() => { + ConfigStore.set('isSelfHosted', false); + }); + it('does not display the Ask Seer combobox', async () => { + ConfigStore.set('isSelfHosted', true); render(, { - organization: {...organization, features: []}, + organization, additionalWrapper: Wrapper, }); diff --git a/static/app/views/issueDetails/actions/index.spec.tsx b/static/app/views/issueDetails/actions/index.spec.tsx index beef441cfaa2..c71dc5e9f3c8 100644 --- a/static/app/views/issueDetails/actions/index.spec.tsx +++ b/static/app/views/issueDetails/actions/index.spec.tsx @@ -113,6 +113,14 @@ describe('GroupActions', () => { seerReposLinked: false, }, }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/issues/${group.id}/autofix/`, + body: {autofix: null}, + }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/seer/onboarding-check/`, + body: {isSeerConfigured: false}, + }); MockApiClient.addMockResponse({ url: `/organizations/${organization.slug}/integrations/coding-agents/`, body: {integrations: []}, diff --git a/static/app/views/issueDetails/groupDetails.spec.tsx b/static/app/views/issueDetails/groupDetails.spec.tsx index 49fdec450f4b..7bb46f0b5607 100644 --- a/static/app/views/issueDetails/groupDetails.spec.tsx +++ b/static/app/views/issueDetails/groupDetails.spec.tsx @@ -147,6 +147,14 @@ describe('groupDetails', () => { url: `/projects/${defaultInit.organization.slug}/${project.slug}/`, body: project, }); + MockApiClient.addMockResponse({ + url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/autofix/`, + body: {autofix: null}, + }); + MockApiClient.addMockResponse({ + url: `/organizations/${defaultInit.organization.slug}/seer/onboarding-check/`, + body: {isSeerConfigured: false}, + }); MockApiClient.addMockResponse({ url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/autofix/setup/`, body: AutofixSetupFixture({}), @@ -236,7 +244,6 @@ describe('groupDetails', () => { const organization = { ...defaultInit.organization, hideAiFeatures: false, - features: ['gen-ai-features'], }; const query = { project: group.project.id, diff --git a/static/app/views/issueDetails/groupDetailsLayout.spec.tsx b/static/app/views/issueDetails/groupDetailsLayout.spec.tsx index 28ebcbd4f35e..e9c8f79a5a42 100644 --- a/static/app/views/issueDetails/groupDetailsLayout.spec.tsx +++ b/static/app/views/issueDetails/groupDetailsLayout.spec.tsx @@ -100,6 +100,14 @@ describe('GroupDetailsLayout', () => { integration: {ok: true, reason: null}, }), }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/issues/${group.id}/autofix/`, + body: {autofix: null}, + }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/seer/onboarding-check/`, + body: {isSeerConfigured: false}, + }); MockApiClient.addMockResponse({ url: '/projects/org-slug/project-slug/', body: [project], diff --git a/static/app/views/issueDetails/sidebar/autofixSection.spec.tsx b/static/app/views/issueDetails/sidebar/autofixSection.spec.tsx index a5d7fa3ff558..b869d1a661c0 100644 --- a/static/app/views/issueDetails/sidebar/autofixSection.spec.tsx +++ b/static/app/views/issueDetails/sidebar/autofixSection.spec.tsx @@ -22,7 +22,6 @@ describe('AutofixSection', () => { const mockProject = DetailedProjectFixture(); const organization = OrganizationFixture({ hideAiFeatures: false, - features: ['gen-ai-features'], }); let mockGroup: ReturnType; @@ -71,7 +70,6 @@ describe('AutofixSection', () => { it('renders Resources section when AI features are disabled', () => { const customOrganization = OrganizationFixture({ hideAiFeatures: true, - features: ['gen-ai-features'], }); const performanceGroup: Group = { @@ -124,7 +122,6 @@ describe('AutofixSection', () => { it('returns null when AI features are disabled and no resources exist', () => { const customOrganization = OrganizationFixture({ hideAiFeatures: true, - features: ['gen-ai-features'], }); const {container} = render( @@ -446,7 +443,7 @@ describe('AutofixSection', () => { it('shows org setup UI when SCM integration is missing', async () => { const seatBasedOrg = OrganizationFixture({ hideAiFeatures: false, - features: ['gen-ai-features', 'seat-based-seer-enabled'], + features: ['seat-based-seer-enabled'], }); MockApiClient.addMockResponse({ @@ -479,7 +476,7 @@ describe('AutofixSection', () => { it('shows project setup UI when repos are not linked', async () => { const seatBasedOrg = OrganizationFixture({ hideAiFeatures: false, - features: ['gen-ai-features', 'seat-based-seer-enabled'], + features: ['seat-based-seer-enabled'], }); MockApiClient.addMockResponse({ @@ -512,7 +509,7 @@ describe('AutofixSection', () => { it('skips setup UI for legacy seer plan orgs without SCM integration', async () => { const legacyOrg = OrganizationFixture({ hideAiFeatures: false, - features: ['gen-ai-features', 'seer-added'], + features: ['seer-added'], }); MockApiClient.addMockResponse({ diff --git a/static/app/views/issueDetails/sidebar/seerDrawer.spec.tsx b/static/app/views/issueDetails/sidebar/seerDrawer.spec.tsx index 5404147fec2c..40ebe8dd40ff 100644 --- a/static/app/views/issueDetails/sidebar/seerDrawer.spec.tsx +++ b/static/app/views/issueDetails/sidebar/seerDrawer.spec.tsx @@ -59,7 +59,6 @@ function makeExplorerAutofixData({ describe('SeerDrawer', () => { const organization = OrganizationFixture({ hideAiFeatures: false, - features: ['gen-ai-features'], }); const mockGroup = GroupFixture(); @@ -402,7 +401,7 @@ describe('SeerDrawer', () => { render(, { organization: OrganizationFixture({ hideAiFeatures: false, - features: ['gen-ai-features', feature], + features: [feature], }), }); diff --git a/static/app/views/issueDetails/sidebar/sidebar.spec.tsx b/static/app/views/issueDetails/sidebar/sidebar.spec.tsx index a83b3cccde02..019dcd52449e 100644 --- a/static/app/views/issueDetails/sidebar/sidebar.spec.tsx +++ b/static/app/views/issueDetails/sidebar/sidebar.spec.tsx @@ -25,7 +25,7 @@ describe('IssueDetailsSidebar', () => { const activityContent = 'test-note'; const issueTrackingKey = 'issue-key'; - const organization = OrganizationFixture({features: ['gen-ai-features']}); + const organization = OrganizationFixture(); const project = ProjectFixture(); const group = GroupFixture({ activity: [ diff --git a/static/app/views/issueList/pages/inbox/index.spec.tsx b/static/app/views/issueList/pages/inbox/index.spec.tsx index 6306796f9874..23b039c8c1b2 100644 --- a/static/app/views/issueList/pages/inbox/index.spec.tsx +++ b/static/app/views/issueList/pages/inbox/index.spec.tsx @@ -34,7 +34,7 @@ jest.mock('sentry/utils/useMedia'); describe('InboxPage', () => { const organization = OrganizationFixture({ - features: ['issue-inbox', 'gen-ai-features', 'seat-based-seer-enabled'], + features: ['issue-inbox', 'seat-based-seer-enabled'], }); const seerOrganization = organization; const project = ProjectFixture({ diff --git a/static/app/views/issueList/pages/inbox/issuePreview/issuePreview.spec.tsx b/static/app/views/issueList/pages/inbox/issuePreview/issuePreview.spec.tsx index 8e7df6413f6f..7ba66ec612f3 100644 --- a/static/app/views/issueList/pages/inbox/issuePreview/issuePreview.spec.tsx +++ b/static/app/views/issueList/pages/inbox/issuePreview/issuePreview.spec.tsx @@ -17,7 +17,7 @@ import {GroupStatus, ProgressState, type Group} from 'sentry/types/group'; import {IssuePreview} from './issuePreview'; describe('IssuePreview', () => { - const organization = OrganizationFixture({features: ['gen-ai-features']}); + const organization = OrganizationFixture(); const project = ProjectFixture({id: '1'}); const group = GroupFixture({id: '101', project, hasSeen: true}); const fixAppliedGroup = GroupFixture({ diff --git a/static/app/views/navigation/secondary/sections/issues/issuesSecondaryNavigation.spec.tsx b/static/app/views/navigation/secondary/sections/issues/issuesSecondaryNavigation.spec.tsx index 737afd76efd6..3bcd183e4218 100644 --- a/static/app/views/navigation/secondary/sections/issues/issuesSecondaryNavigation.spec.tsx +++ b/static/app/views/navigation/secondary/sections/issues/issuesSecondaryNavigation.spec.tsx @@ -12,7 +12,7 @@ import type {LLMContextNodeSnapshot} from 'sentry/views/seerExplorer/contexts/ll describe('IssuesSecondaryNavigation', () => { const inboxCountQuery = `is:unresolved issue.progress:[fix_proposed,diagnosed,assigned,identified] assigned_or_suggested:[me,my_teams]${INBOX_AUTOFIX_CATEGORY_FILTER}`; const organization = OrganizationFixture({ - features: ['issue-inbox', 'gen-ai-features', 'seat-based-seer-enabled'], + features: ['issue-inbox', 'seat-based-seer-enabled'], }); beforeEach(() => { @@ -73,7 +73,7 @@ describe('IssuesSecondaryNavigation', () => { it('does not render Inbox or request its count without the inbox feature', async () => { const request = mockInboxCount({}); const organizationWithoutAutofix = OrganizationFixture({ - features: ['gen-ai-features', 'seat-based-seer-enabled'], + features: ['seat-based-seer-enabled'], }); renderNavigation(organizationWithoutAutofix); @@ -86,12 +86,7 @@ describe('IssuesSecondaryNavigation', () => { it('renders the Autofix Overview link when the org has seer-night-shift-ui', async () => { mockInboxCount({}); const organizationWithOverview = OrganizationFixture({ - features: [ - 'issue-inbox', - 'gen-ai-features', - 'seat-based-seer-enabled', - 'seer-night-shift-ui', - ], + features: ['issue-inbox', 'seat-based-seer-enabled', 'seer-night-shift-ui'], }); renderNavigation(organizationWithOverview); diff --git a/static/app/views/navigation/topBar.spec.tsx b/static/app/views/navigation/topBar.spec.tsx index 1ac5ce3676ac..488f804739a3 100644 --- a/static/app/views/navigation/topBar.spec.tsx +++ b/static/app/views/navigation/topBar.spec.tsx @@ -33,7 +33,7 @@ function renderTopBar(width?: number) { render({topBar}, { organization: OrganizationFixture({ - features: ['gen-ai-features', 'seer-explorer'], + features: ['seer-explorer'], }), }); } diff --git a/static/app/views/seerExplorer/components/drawer/useSeerExplorerDrawer.spec.tsx b/static/app/views/seerExplorer/components/drawer/useSeerExplorerDrawer.spec.tsx index 4b8e25785175..bc6bc1ed87a6 100644 --- a/static/app/views/seerExplorer/components/drawer/useSeerExplorerDrawer.spec.tsx +++ b/static/app/views/seerExplorer/components/drawer/useSeerExplorerDrawer.spec.tsx @@ -35,7 +35,7 @@ const DRAWER_LABEL = 'Seer Explorer Drawer'; const enabledOrg = OrganizationFixture({ openMembership: true, - features: ['seer-explorer', 'gen-ai-features'], + features: ['seer-explorer'], hideAiFeatures: false, }); diff --git a/static/app/views/seerExplorer/components/seerExplorerContent.spec.tsx b/static/app/views/seerExplorer/components/seerExplorerContent.spec.tsx index 4d365da8c574..e71e6c91ddbe 100644 --- a/static/app/views/seerExplorer/components/seerExplorerContent.spec.tsx +++ b/static/app/views/seerExplorer/components/seerExplorerContent.spec.tsx @@ -42,7 +42,7 @@ const defaultHookReturn: ReturnType { const organization = OrganizationFixture({ openMembership: true, - features: ['seer-explorer', 'gen-ai-features'], + features: ['seer-explorer'], hideAiFeatures: false, }); @@ -84,7 +84,7 @@ describe('SeerExplorerContent', () => { it('renders thinking traces when code mode tools is enabled', async () => { const codeModeOrganization = OrganizationFixture({ openMembership: true, - features: ['seer-explorer', 'gen-ai-features', 'seer-explorer-code-mode-tools'], + features: ['seer-explorer', 'seer-explorer-code-mode-tools'], hideAiFeatures: false, }); @@ -972,11 +972,7 @@ describe('SeerExplorerContent', () => { const orgWithFlag = OrganizationFixture({ openMembership: true, hideAiFeatures: false, - features: [ - 'seer-explorer', - 'gen-ai-features', - 'seer-explorer-context-engine-fe-override-ui-flag', - ], + features: ['seer-explorer', 'seer-explorer-context-engine-fe-override-ui-flag'], }); it('does not show the debug menu without any debug feature flag', async () => { @@ -1163,7 +1159,7 @@ describe('SeerExplorerContent', () => { it('hides the reinstall nudge when the user cannot manage integrations', async () => { const memberOrg = OrganizationFixture({ openMembership: true, - features: ['seer-explorer', 'gen-ai-features'], + features: ['seer-explorer'], hideAiFeatures: false, access: ['org:read', 'project:read', 'team:read', 'alerts:read'], }); diff --git a/static/app/views/seerExplorer/components/seerExplorerHeader.spec.tsx b/static/app/views/seerExplorer/components/seerExplorerHeader.spec.tsx index dbb233a9c932..7873a18a5725 100644 --- a/static/app/views/seerExplorer/components/seerExplorerHeader.spec.tsx +++ b/static/app/views/seerExplorer/components/seerExplorerHeader.spec.tsx @@ -5,7 +5,7 @@ import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; import {SeerExplorerHeader} from 'sentry/views/seerExplorer/components/seerExplorerHeader'; import {SeerExplorerSessionsProvider} from 'sentry/views/seerExplorer/seerExplorerSessionContext'; -const BASE_FEATURES = ['seer-explorer', 'gen-ai-features']; +const BASE_FEATURES = ['seer-explorer']; function orgWith(...extraFeatures: string[]) { return OrganizationFixture({ diff --git a/static/app/views/seerExplorer/components/sidebar/seerExplorerSidebarLayout.spec.tsx b/static/app/views/seerExplorer/components/sidebar/seerExplorerSidebarLayout.spec.tsx index d55f95953aaf..c9ffb646b424 100644 --- a/static/app/views/seerExplorer/components/sidebar/seerExplorerSidebarLayout.spec.tsx +++ b/static/app/views/seerExplorer/components/sidebar/seerExplorerSidebarLayout.spec.tsx @@ -21,7 +21,7 @@ import { const POSITION_KEY = 'seer-explorer-sidebar-position'; -const seerFeatures = ['seer-explorer', 'gen-ai-features']; +const seerFeatures = ['seer-explorer']; const defaultHookReturn: ReturnType = { sessionData: null, diff --git a/static/app/views/seerExplorer/hooks/useSeerExplorer.spec.tsx b/static/app/views/seerExplorer/hooks/useSeerExplorer.spec.tsx index c52556410233..8a193f72f0e3 100644 --- a/static/app/views/seerExplorer/hooks/useSeerExplorer.spec.tsx +++ b/static/app/views/seerExplorer/hooks/useSeerExplorer.spec.tsx @@ -31,7 +31,7 @@ describe('useSeerExplorer', () => { }); const organization = OrganizationFixture({ - features: ['seer-explorer', 'gen-ai-features'], + features: ['seer-explorer'], hideAiFeatures: false, openMembership: true, }); diff --git a/static/app/views/seerExplorer/hooks/useSeerExplorerPolling.spec.tsx b/static/app/views/seerExplorer/hooks/useSeerExplorerPolling.spec.tsx index 94ddac2cdd76..4f76907864b1 100644 --- a/static/app/views/seerExplorer/hooks/useSeerExplorerPolling.spec.tsx +++ b/static/app/views/seerExplorer/hooks/useSeerExplorerPolling.spec.tsx @@ -6,7 +6,7 @@ import {useSeerExplorerPolling} from './useSeerExplorerPolling'; describe('useSeerExplorerPolling', () => { const organization = OrganizationFixture({ - features: ['seer-explorer', 'gen-ai-features'], + features: ['seer-explorer'], hideAiFeatures: false, openMembership: true, }); diff --git a/static/app/views/seerWorkflows/overview/index.spec.tsx b/static/app/views/seerWorkflows/overview/index.spec.tsx index ab96ca3a0f83..b32f4d182898 100644 --- a/static/app/views/seerWorkflows/overview/index.spec.tsx +++ b/static/app/views/seerWorkflows/overview/index.spec.tsx @@ -23,6 +23,7 @@ import { setPageFiltersStorage, } from 'sentry/components/pageFilters/persistence'; import {PageFiltersStore} from 'sentry/components/pageFilters/store'; +import {ConfigStore} from 'sentry/stores/configStore'; import {OrganizationStore} from 'sentry/stores/organizationStore'; import {ProjectsStore} from 'sentry/stores/projectsStore'; import {TeamStore} from 'sentry/stores/teamStore'; @@ -39,7 +40,7 @@ import {useOverviewSeerDrawer} from 'sentry/views/seerWorkflows/overview/useOver describe('AutofixOverview', () => { const organization = OrganizationFixture({ - features: ['seer-night-shift-ui', 'gen-ai-features'], + features: ['seer-night-shift-ui'], }); const basePath = `/organizations/${organization.slug}/issues/autofix/`; @@ -374,6 +375,10 @@ describe('AutofixOverview', () => { }); describe('Seer drawer', () => { + beforeEach(() => { + ConfigStore.set('isSelfHosted', false); + }); + // Holds setup open so the drawer sits in its loading state and fires no // downstream content requests. function mockDrawerFor(groupId: string) { @@ -429,7 +434,8 @@ describe('AutofixOverview', () => { expect(groupRequest).toHaveBeenCalled(); }); - it('stays closed and clears the param when the org lacks gen-ai access', async () => { + it('stays closed and clears the param when self-hosted', async () => { + ConfigStore.set('isSelfHosted', true); mockOverview({base: {autofix_root_cause: [rootCauseRun]}}); mockDrawerFor('2'); diff --git a/static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.spec.tsx b/static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.spec.tsx index 32e68000ebc2..2257f7dfec84 100644 --- a/static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.spec.tsx +++ b/static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.spec.tsx @@ -9,6 +9,7 @@ import { waitFor, } from 'sentry-test/reactTestingLibrary'; +import {ConfigStore} from 'sentry/stores/configStore'; import {OrganizationStore} from 'sentry/stores/organizationStore'; import * as RegionUtils from 'sentry/utils/cells'; import {OrganizationSettingsForm} from 'sentry/views/settings/organizationGeneralSettings/organizationSettingsForm'; @@ -43,6 +44,10 @@ describe('OrganizationSettingsForm', () => { onSave.mockReset(); }); + afterEach(() => { + ConfigStore.set('isSelfHosted', false); + }); + it('can change a form field', async () => { putMock = MockApiClient.addMockResponse({ url: `/organizations/${organization.slug}/`, @@ -183,7 +188,7 @@ describe('OrganizationSettingsForm', () => { // initialData.hideAiFeatures = false (default) → switch starts OFF render( , - {organization: {...organization, features: ['gen-ai-features']}} + {organization} ); const mock = MockApiClient.addMockResponse({ url: `/organizations/${organization.slug}/`, @@ -216,7 +221,7 @@ describe('OrganizationSettingsForm', () => { initialData={OrganizationFixture({hideAiFeatures: true})} onSave={onSave} />, - {organization: {...organization, features: ['gen-ai-features']}} + {organization} ); const mock = MockApiClient.addMockResponse({ url: `/organizations/${organization.slug}/`, @@ -254,7 +259,7 @@ describe('OrganizationSettingsForm', () => { { organization: { ...organization, - features: ['autofix', 'gen-ai-features'], + features: ['autofix'], }, } ); @@ -263,15 +268,11 @@ describe('OrganizationSettingsForm', () => { expect(toggle).toBeEnabled(); }); - it('disables "Show Generative AI Features" toggle when feature flag is off', () => { + it('disables "Show Generative AI Features" toggle when self-hosted', () => { + ConfigStore.set('isSelfHosted', true); render( , - { - organization: { - ...organization, - features: [], // No gen-ai-features flag - }, - } + {organization} ); const checkbox = screen.getByRole('checkbox', { diff --git a/static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.tsx b/static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.tsx index 227e8108ce76..4a36197bcb39 100644 --- a/static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.tsx +++ b/static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.tsx @@ -437,12 +437,12 @@ export function OrganizationSettingsForm({initialData, onSave}: Props) { }); const access = useMemo(() => new Set(organization.access), [organization]); const hasWriteAccess = access.has('org:write'); - const hasGenAiFeatureFlag = organization.features.includes('gen-ai-features'); + const isSeerAvailable = !ConfigStore.get('isSelfHosted'); const localityData = shouldDisplayLocalities() ? getLocalityDataFromOrganization(organization) : null; - const aiEnabled = hasGenAiFeatureFlag ? (initialData.hideAiFeatures ?? false) : false; + const aiEnabled = isSeerAvailable ? (initialData.hideAiFeatures ?? false) : false; // Shared mutation options for most general fields const orgMutationOptions = mutationOptions({ @@ -653,7 +653,7 @@ export function OrganizationSettingsForm({initialData, onSave}: Props) { )} diff --git a/static/app/views/settings/projectPerformance/projectPerformance.spec.tsx b/static/app/views/settings/projectPerformance/projectPerformance.spec.tsx index 086b32da432b..aa0e524cbe1c 100644 --- a/static/app/views/settings/projectPerformance/projectPerformance.spec.tsx +++ b/static/app/views/settings/projectPerformance/projectPerformance.spec.tsx @@ -85,11 +85,7 @@ function getDetectorSlider({label, index}: {index: number; label: string}) { describe('projectPerformance', () => { const org = OrganizationFixture({ - features: [ - 'performance-view', - 'performance-web-vitals-seer-suggestions', - 'gen-ai-features', - ], + features: ['performance-view', 'performance-web-vitals-seer-suggestions'], }); const project = ProjectFixture(); const configUrl = '/projects/org-slug/project-slug/transaction-threshold/configure/'; @@ -810,7 +806,6 @@ describe('projectPerformance', () => { features: [ 'performance-view', 'performance-web-vitals-seer-suggestions', - 'gen-ai-features', 'ai-issue-detection', ], }), diff --git a/static/app/views/settings/projectUserFeedback/index.spec.tsx b/static/app/views/settings/projectUserFeedback/index.spec.tsx index 79ee7d30f606..c69ff0aed4bd 100644 --- a/static/app/views/settings/projectUserFeedback/index.spec.tsx +++ b/static/app/views/settings/projectUserFeedback/index.spec.tsx @@ -105,7 +105,6 @@ describe('ProjectUserFeedback', () => { }); it('cannot toggle spam detection when the user does not have the spam feature flag', () => { - organization.features.push('gen-ai-features'); seerSetupMock = mockSeerSetup(); render(, { @@ -120,7 +119,6 @@ describe('ProjectUserFeedback', () => { it('can toggle spam detection', async () => { organization.features.push('user-feedback-spam-ingest'); - organization.features.push('gen-ai-features'); seerSetupMock = mockSeerSetup(); const detailedProject = DetailedProjectFixture(project); diff --git a/tests/sentry/dashboards/endpoints/test_organization_dashboard_generate.py b/tests/sentry/dashboards/endpoints/test_organization_dashboard_generate.py index bdca7ac3e484..cd6e418742a4 100644 --- a/tests/sentry/dashboards/endpoints/test_organization_dashboard_generate.py +++ b/tests/sentry/dashboards/endpoints/test_organization_dashboard_generate.py @@ -9,11 +9,9 @@ from sentry.dashboards.on_completion_hook import DashboardOnCompletionHook from sentry.seer.models import SeerPermissionError from sentry.testutils.cases import APITestCase -from sentry.testutils.helpers.features import with_feature @override_settings(SENTRY_SELF_HOSTED=False) -@with_feature("organizations:gen-ai-features") class OrganizationDashboardGenerateEndpointTest(APITestCase): endpoint = "sentry-api-0-organization-dashboards-generate" diff --git a/tests/sentry/integrations/slack/test_message_builder.py b/tests/sentry/integrations/slack/test_message_builder.py index db77174a8167..558e6b067c53 100644 --- a/tests/sentry/integrations/slack/test_message_builder.py +++ b/tests/sentry/integrations/slack/test_message_builder.py @@ -42,7 +42,6 @@ from sentry.silo.base import SiloMode from sentry.testutils.cases import PerformanceIssueTestCase, TestCase from sentry.testutils.factories import EventType -from sentry.testutils.helpers import with_feature from sentry.testutils.helpers.datetime import before_now, freeze_time from sentry.testutils.outbox import outbox_runner from sentry.testutils.silo import assume_test_silo_mode @@ -1000,7 +999,7 @@ def _has_autofix_button(self, blocks: dict[str, Any]) -> bool: @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.quotas.backend.check_seer_quota", return_value=True) - @with_feature({"organizations:gen-ai-features": True}) + @override_settings(SENTRY_SELF_HOSTED=False) def test_autofix_button_shown_when_all_conditions_met(self, mock_quota: MagicMock) -> None: group = self.create_group(project=self.project) blocks = SlackIssuesMessageBuilder(group).build() @@ -1008,7 +1007,7 @@ def test_autofix_button_shown_when_all_conditions_met(self, mock_quota: MagicMoc @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.quotas.backend.check_seer_quota", return_value=True) - @with_feature({"organizations:gen-ai-features": True}) + @override_settings(SENTRY_SELF_HOSTED=False) def test_autofix_button_hidden_on_unfurl(self, mock_quota: MagicMock) -> None: group = self.create_group(project=self.project) blocks = SlackIssuesMessageBuilder(group, is_unfurl=True).build() @@ -1016,7 +1015,7 @@ def test_autofix_button_hidden_on_unfurl(self, mock_quota: MagicMock) -> None: @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.quotas.backend.check_seer_quota", return_value=True) - @with_feature({"organizations:gen-ai-features": True}) + @override_settings(SENTRY_SELF_HOSTED=False) def test_autofix_button_hidden_when_no_other_actions(self, mock_quota: MagicMock) -> None: group = self.create_group(project=self.project) blocks = SlackIssuesMessageBuilder(group, issue_details=True).build() diff --git a/tests/sentry/integrations/slack/webhooks/events/__init__.py b/tests/sentry/integrations/slack/webhooks/events/__init__.py index 7baa2039fc9f..acf49f3e5962 100644 --- a/tests/sentry/integrations/slack/webhooks/events/__init__.py +++ b/tests/sentry/integrations/slack/webhooks/events/__init__.py @@ -13,7 +13,6 @@ UNSET = object() SEER_EXPLORER_FEATURES = { - "organizations:gen-ai-features": True, "organizations:seer-explorer": True, } diff --git a/tests/sentry/integrations/test_issues.py b/tests/sentry/integrations/test_issues.py index 83e842e38b2c..c1f9a01bdc76 100644 --- a/tests/sentry/integrations/test_issues.py +++ b/tests/sentry/integrations/test_issues.py @@ -2,6 +2,7 @@ from unittest.mock import MagicMock, patch import pytest +from django.test import override_settings from django.utils import timezone from sentry.analytics.events.issue_resolved import IssueResolvedEvent @@ -635,6 +636,7 @@ def test_status_sync_inbound_unresolve_webhook_and_sends_to_sentry_app( assert data["installation"]["uuid"] == str(self.sentry_app_installation.uuid) +@override_settings(SENTRY_SELF_HOSTED=False) class IssueDefaultTest(TestCase): def setUp(self) -> None: event = self.store_event( @@ -823,9 +825,7 @@ def test_feature_flag_disabled_skips_ai(self, mock_request: MagicMock) -> None: def test_hide_ai_features_skips_ai(self, mock_request: MagicMock) -> None: self.group.organization.update_option("sentry:hide_ai_features", True) - with self.feature( - ["organizations:gen-ai-features", "organizations:external-issues-ai-generate"] - ): + with self.feature(["organizations:external-issues-ai-generate"]): config = self.installation.get_create_issue_config(self.group, self.user) title_field = next(f for f in config if f["name"] == "title") @@ -836,9 +836,7 @@ def test_hide_ai_features_skips_ai(self, mock_request: MagicMock) -> None: def test_ai_exception_falls_back(self, mock_request: MagicMock) -> None: mock_request.side_effect = Exception("Connection error") - with self.feature( - ["organizations:gen-ai-features", "organizations:external-issues-ai-generate"] - ): + with self.feature(["organizations:external-issues-ai-generate"]): config = self.installation.get_create_issue_config(self.group, self.user) title_field = next(f for f in config if f["name"] == "title") diff --git a/tests/sentry/integrations/utils/test_external_issues.py b/tests/sentry/integrations/utils/test_external_issues.py index 357adac5eedd..6922d16f8630 100644 --- a/tests/sentry/integrations/utils/test_external_issues.py +++ b/tests/sentry/integrations/utils/test_external_issues.py @@ -117,16 +117,15 @@ def test_feature_flag_disabled_returns_empty(self, mock_request: MagicMock) -> N def test_hide_ai_features_returns_empty(self, mock_request: MagicMock) -> None: self.group.organization.update_option("sentry:hide_ai_features", True) - with self.feature( - ["organizations:gen-ai-features", "organizations:external-issues-ai-generate"] - ): + with self.feature(["organizations:external-issues-ai-generate"]): result = maybe_generate_external_issue_details(group=self.group, user=self.user) assert result == GeneratedExternalIssueDetails(title=None, description=None) mock_request.assert_not_called() + @override_settings(SENTRY_SELF_HOSTED=True) @patch("sentry.integrations.utils.external_issues.make_llm_generate_request") - def test_gen_ai_features_disabled_returns_empty(self, mock_request: MagicMock) -> None: + def test_self_hosted_returns_empty(self, mock_request: MagicMock) -> None: with self.feature("organizations:external-issues-ai-generate"): result = maybe_generate_external_issue_details(group=self.group, user=self.user) @@ -137,9 +136,7 @@ def test_gen_ai_features_disabled_returns_empty(self, mock_request: MagicMock) - def test_exception_returns_empty(self, mock_request: MagicMock) -> None: mock_request.side_effect = Exception("Connection error") - with self.feature( - ["organizations:gen-ai-features", "organizations:external-issues-ai-generate"] - ): + with self.feature(["organizations:external-issues-ai-generate"]): result = maybe_generate_external_issue_details(group=self.group, user=self.user) assert result == GeneratedExternalIssueDetails(title=None, description=None) @@ -152,9 +149,7 @@ def test_successful_returns_details(self, mock_request: MagicMock) -> None: } mock_request.return_value = mock_response - with self.feature( - ["organizations:gen-ai-features", "organizations:external-issues-ai-generate"] - ): + with self.feature(["organizations:external-issues-ai-generate"]): result = maybe_generate_external_issue_details(group=self.group, user=self.user) assert result == GeneratedExternalIssueDetails( diff --git a/tests/sentry/pr_metrics/test_emit.py b/tests/sentry/pr_metrics/test_emit.py index 9f3cb3cf2fdb..75007a60b782 100644 --- a/tests/sentry/pr_metrics/test_emit.py +++ b/tests/sentry/pr_metrics/test_emit.py @@ -3,6 +3,7 @@ from unittest.mock import patch import pytest +from django.test import override_settings from sentry.analytics.events.pr_metrics_events import PrCloseMetricsEvent from sentry.models.grouplink import GroupLink @@ -175,9 +176,9 @@ def _doc_suite( @with_feature( [ "organizations:pr-metrics", - "organizations:gen-ai-features", ] ) +@override_settings(SENTRY_SELF_HOSTED=False) class PrMetricsEmissionTest(TestCase): def setUp(self) -> None: self.repo = self.create_repo( @@ -1697,9 +1698,9 @@ def test_untracked_pr_does_not_enqueue_cleanup( @with_feature( [ "organizations:pr-metrics", - "organizations:gen-ai-features", ] ) +@override_settings(SENTRY_SELF_HOSTED=False) class MultiOrgEmissionDedupeTest(TestCase): """A provider PR shared across orgs fans out to one tracked row per org; only the canonical (run's-org) row should emit.""" @@ -1826,9 +1827,9 @@ def test_tracked_sibling_emits_when_lower_id_row_untracked(self, mock_record: An @with_feature( [ "organizations:pr-metrics", - "organizations:gen-ai-features", ] ) +@override_settings(SENTRY_SELF_HOSTED=False) class DeduplicationKeyTest(TestCase): """The same provider PR, fanned out to one row per org, must build the same opaque deduplication_key so a consumer can collapse them.""" diff --git a/tests/sentry/pr_metrics/test_webhooks.py b/tests/sentry/pr_metrics/test_webhooks.py index 10885137b1ac..7db79315cdc5 100644 --- a/tests/sentry/pr_metrics/test_webhooks.py +++ b/tests/sentry/pr_metrics/test_webhooks.py @@ -392,8 +392,9 @@ def test_missing_pr_logs_unresolved_and_does_not_raise(self) -> None: CLOSED_AT = datetime(2020, 6, 4, 10, 0, 0, tzinfo=timezone.utc) -@with_feature(["organizations:pr-metrics", "organizations:gen-ai-features"]) +@with_feature(["organizations:pr-metrics"]) @cell_silo_test +@override_settings(SENTRY_SELF_HOSTED=False) class HandleWebhookForPrMetricsEmissionTest(TestCase): def setUp(self) -> None: self.project = self.create_project(organization=self.organization) @@ -565,12 +566,12 @@ def test_does_nothing_when_flag_off(self, mock_record: MagicMock) -> None: ).exists() @patch("sentry.analytics.record") + @override_settings(SENTRY_SELF_HOSTED=True) def test_emits_without_seer_access(self, mock_record: MagicMock) -> None: # Seer access is no longer required for activity tracking, so the # commits-after-open signal is present regardless — a clean merge can # still resolve to merged_unchanged without Seer access. - with self.feature({"organizations:gen-ai-features": False}): - self._call(merged=True) + self._call(merged=True) assert get_event_count(mock_record, PrCloseMetricsEvent) == 1 assert ( PullRequestMetrics.objects.get(pull_request=self.pull_request).verdict @@ -648,8 +649,9 @@ def test_missing_pr_logs_unresolved_and_does_not_emit( assert get_event_count(mock_record, PrCloseMetricsEvent) == 0 -@with_feature(["organizations:pr-metrics", "organizations:gen-ai-features"]) +@with_feature(["organizations:pr-metrics"]) @cell_silo_test +@override_settings(SENTRY_SELF_HOSTED=False) class HandleWebhookForPrMetricsCooldownTest(TestCase): """The webhook-side scheduling of deferred emission and its cooldown claim.""" @@ -1307,9 +1309,9 @@ def test_unhandled_actions_do_not_write_activity(self) -> None: assert not PullRequestActivity.objects.filter(pull_request=self.pr).exists() + @override_settings(SENTRY_SELF_HOSTED=True) def test_activity_written_without_seer_access(self) -> None: - with self.feature({"organizations:gen-ai-features": False}): - self._call(action="opened") + self._call(action="opened") assert PullRequestActivity.objects.filter(pull_request=self.pr).exists() @@ -1462,9 +1464,9 @@ def test_regular_comment_has_is_review_false(self) -> None: activity = PullRequestActivity.objects.get(pull_request=self.pr) assert activity.payload["is_review"] is False + @override_settings(SENTRY_SELF_HOSTED=True) def test_comment_written_without_seer_access(self) -> None: - with self.feature({"organizations:gen-ai-features": False}): - self._call() + self._call() assert PullRequestActivity.objects.filter(pull_request=self.pr).exists() @@ -1643,9 +1645,9 @@ def test_unknown_pr_number_logs_unresolved_and_does_not_raise(self) -> None: ) assert not PullRequestActivity.objects.filter(pull_request=self.pr).exists() + @override_settings(SENTRY_SELF_HOSTED=True) def test_review_written_without_seer_access(self) -> None: - with self.feature({"organizations:gen-ai-features": False}): - self._call() + self._call() assert PullRequestActivity.objects.filter(pull_request=self.pr).exists() @@ -1756,9 +1758,9 @@ def test_unknown_pr_number_logs_unresolved_and_does_not_raise(self) -> None: ) assert not PullRequestActivity.objects.filter(pull_request=self.pr).exists() + @override_settings(SENTRY_SELF_HOSTED=True) def test_review_comment_written_without_seer_access(self) -> None: - with self.feature({"organizations:gen-ai-features": False}): - self._call() + self._call() assert PullRequestActivity.objects.filter(pull_request=self.pr).exists() @@ -1864,9 +1866,9 @@ def test_unknown_pr_number_logs_unresolved_and_does_not_raise(self) -> None: ) assert not PullRequestActivity.objects.filter(pull_request=self.pr).exists() + @override_settings(SENTRY_SELF_HOSTED=True) def test_thread_event_written_without_seer_access(self) -> None: - with self.feature({"organizations:gen-ai-features": False}): - self._call() + self._call() assert PullRequestActivity.objects.filter(pull_request=self.pr).exists() @@ -2059,9 +2061,9 @@ def test_check_suite_flag_off_skips(self) -> None: assert not PullRequestActivity.objects.filter(pull_request=self.pr).exists() + @override_settings(SENTRY_SELF_HOSTED=True) def test_check_suite_written_without_seer_access(self) -> None: - with self.feature({"organizations:gen-ai-features": False}): - self._call_suite() + self._call_suite() assert PullRequestActivity.objects.filter(pull_request=self.pr).exists() @@ -2173,9 +2175,9 @@ def test_check_suite_judge_in_progress_skips(self) -> None: assert not PullRequestActivity.objects.filter(pull_request=self.pr).exists() -@override_settings(SENTRY_SELF_HOSTED=False) -@with_feature(["organizations:pr-metrics", "organizations:gen-ai-features"]) +@with_feature(["organizations:pr-metrics"]) @cell_silo_test +@override_settings(SENTRY_SELF_HOSTED=False) class HandleWebhookForPrMetricsJudgeForwardTest(TestCase): """The needs-judge branch: claim the sentinel and forward.""" @@ -2310,12 +2312,12 @@ def test_untracked_pr_is_not_forwarded( @patch("sentry.pr_metrics.tasks.forward_pr_to_seer_task.delay") @patch("sentry.analytics.record") + @override_settings(SENTRY_SELF_HOSTED=True) def test_no_seer_access_skips_judge( self, mock_record: MagicMock, mock_delay: MagicMock ) -> None: # Without Seer access the judge path is not eligible regardless of attribution. - with self.feature({"organizations:gen-ai-features": False}): - self._call() + self._call() assert mock_delay.call_count == 0 assert PullRequestMetrics.objects.get(pull_request=self.pull_request).verdict is None @@ -2340,17 +2342,17 @@ def test_ineligible_attribution_emits_merged_with_iteration( @patch(f"{MODULE}.forward_pr_to_seer_task.delay") @patch("sentry.analytics.record") + @override_settings(SENTRY_SELF_HOSTED=True) def test_ineligible_attribution_emits_without_seer_access( self, mock_record: MagicMock, mock_delay: MagicMock ) -> None: # The fallback never talks to Seer, so an org's Seer-access consent gate - # (gen-ai-features / hide_ai_features) must not block it — only the actual - # forward-to-Seer branch, reached for judge-eligible attribution, needs it. + # must not block it — only the actual forward-to-Seer branch, reached for + # judge-eligible attribution, needs it. PullRequestAttribution.objects.filter(pull_request=self.pull_request).update( signal_type=PullRequestAttributionSignalType.MCP ) - with self.feature({"organizations:gen-ai-features": False}): - self._call() + self._call() assert mock_delay.call_count == 0 assert PullRequestMetrics.objects.get(pull_request=self.pull_request).verdict == ( "merged_with_iteration" @@ -2398,10 +2400,10 @@ def test_ineligible_attribution_stays_unemitted_when_indeterminate( @with_feature( [ "organizations:pr-metrics", - "organizations:gen-ai-features", ] ) @cell_silo_test +@override_settings(SENTRY_SELF_HOSTED=False) class HandleDelegatedAgentDetectionTest(TestCase): def setUp(self) -> None: self.project = self.create_project(organization=self.organization) diff --git a/tests/sentry/seer/agent/test_agent_client.py b/tests/sentry/seer/agent/test_agent_client.py index 19d58a2f30c1..7b8d5d49599c 100644 --- a/tests/sentry/seer/agent/test_agent_client.py +++ b/tests/sentry/seer/agent/test_agent_client.py @@ -1629,7 +1629,7 @@ def test_flush_true_dispatch_failure_marks_failed_and_raises( assert run.seer_run_state_id is None def test_access_gate_blocks_dispatch(self) -> None: - # No gen-ai-features -> client construction raises before any run is created. + # No Seer access -> client construction raises before any run is created. with pytest.raises(SeerPermissionError): SeerAgentClient(self.organization, self.user) assert not SeerRun.objects.filter(organization=self.organization).exists() diff --git a/tests/sentry/seer/agent/test_client_utils.py b/tests/sentry/seer/agent/test_client_utils.py index 1adaa63cd637..ab9bc1fd6e59 100644 --- a/tests/sentry/seer/agent/test_client_utils.py +++ b/tests/sentry/seer/agent/test_client_utils.py @@ -33,37 +33,29 @@ def setUp(self) -> None: self.org.flags.allow_joinleave = True self.org.save() - def test_gen_ai_features_disabled(self) -> None: + @override_settings(SENTRY_SELF_HOSTED=True) + def test_denied_on_self_hosted(self) -> None: result = has_seer_agent_access_with_detail(self.org, self.user) - assert result == (False, "Feature flag not enabled") + assert result == (False, "Seer is not available on this installation.") def test_hide_ai_features_option_set(self) -> None: self.org.update_option("sentry:hide_ai_features", True) - with self.feature("organizations:gen-ai-features"): - result = has_seer_agent_access_with_detail(self.org, self.user) + result = has_seer_agent_access_with_detail(self.org, self.user) assert result == (False, "AI features are disabled for this organization.") def test_no_explorer_flag_enabled(self) -> None: - with self.feature("organizations:gen-ai-features"): - result = has_seer_agent_access_with_detail(self.org, self.user) + result = has_seer_agent_access_with_detail(self.org, self.user) assert result == (False, "Feature flag not enabled") def test_seer_explorer_flag_enabled(self) -> None: - with self.feature( - {"organizations:gen-ai-features": True, "organizations:seer-explorer": True} - ): + with self.feature({"organizations:seer-explorer": True}): result = has_seer_agent_access_with_detail(self.org, self.user) assert result == (True, None) def test_allow_joinleave_disabled(self) -> None: self.org.flags.allow_joinleave = False self.org.save() - with self.feature( - { - "organizations:gen-ai-features": True, - "organizations:seer-explorer": True, - } - ): + with self.feature({"organizations:seer-explorer": True}): result = has_seer_agent_access_with_detail(self.org, self.user) assert result == ( False, diff --git a/tests/sentry/seer/autofix/test_autofix_agent.py b/tests/sentry/seer/autofix/test_autofix_agent.py index a44ccccddd05..fceb73d269b6 100644 --- a/tests/sentry/seer/autofix/test_autofix_agent.py +++ b/tests/sentry/seer/autofix/test_autofix_agent.py @@ -1822,7 +1822,7 @@ def setUp(self): super().setUp() self.group = self.create_group(project=self.project) - def _push(self, mock_post, features="organizations:gen-ai-features", **kwargs): + def _push(self, mock_post, features=None, **kwargs): """Push with a minimal run state and return the payload sent to Seer.""" mock_post.return_value = MagicMock(status=200) state = SeerRunState( @@ -1834,7 +1834,7 @@ def _push(self, mock_post, features="organizations:gen-ai-features", **kwargs): metadata={"group_id": self.group.id}, ) - with self.feature(features): + with self.feature(features or {}): trigger_push_changes( group=self.group, run_id=123, @@ -1878,7 +1878,6 @@ def test_opens_as_draft_when_review_request_enabled(self, mock_post): payload = self._push( mock_post, features={ - "organizations:gen-ai-features": True, "organizations:autofix-pr-iteration-review-request": True, }, ) diff --git a/tests/sentry/seer/autofix/test_autofix_utils.py b/tests/sentry/seer/autofix/test_autofix_utils.py index ecfaf5a7414d..e4a13da0bb47 100644 --- a/tests/sentry/seer/autofix/test_autofix_utils.py +++ b/tests/sentry/seer/autofix/test_autofix_utils.py @@ -2,6 +2,7 @@ from unittest.mock import Mock, patch import pytest +from django.test import override_settings from sentry.constants import ( SEER_AUTOMATED_RUN_STOPPING_POINT_DEFAULT, @@ -128,6 +129,7 @@ def test_autofix_state_validate_parses_nested_structures(self) -> None: assert state.coding_agents["agent-1"].status == CodingAgentStatus.COMPLETED +@override_settings(SENTRY_SELF_HOSTED=False) class TestIsIssueEligibleForSeerAutomation(TestCase): """Test the is_issue_eligible_for_seer_automation function.""" @@ -138,75 +140,68 @@ def setUp(self) -> None: self.group = self.create_group(project=self.project) def test_returns_false_for_unsupported_issue_categories(self) -> None: - """Test returns False for unsupported issue categories like REPLAY and FEEDBACK.""" - from sentry.issues.grouptype import FeedbackGroup, ReplayRageClickType + """Test returns False for unsupported issue categories like FEEDBACK.""" + from sentry.issues.grouptype import FeedbackGroup - # Create groups with unsupported categories - replay_group = self.create_group(project=self.project, type=ReplayRageClickType.type_id) feedback_group = self.create_group(project=self.project, type=FeedbackGroup.type_id) - assert is_issue_eligible_for_seer_automation(replay_group) is False assert is_issue_eligible_for_seer_automation(feedback_group) is False def test_returns_true_for_supported_issue_categories(self) -> None: """Test returns True for supported issue categories when all conditions are met.""" - with self.feature("organizations:gen-ai-features"): - with patch("sentry.quotas.backend.check_seer_quota") as mock_budget: - mock_budget.return_value = True - self.project.update_option("sentry:seer_scanner_automation", True) + with patch("sentry.quotas.backend.check_seer_quota") as mock_budget: + mock_budget.return_value = True + self.project.update_option("sentry:seer_scanner_automation", True) - # Test supported categories - using default error group - result = is_issue_eligible_for_seer_automation(self.group) + # Test supported categories - using default error group + result = is_issue_eligible_for_seer_automation(self.group) - assert result is True + assert result is True - def test_returns_false_when_gen_ai_features_not_enabled(self) -> None: - """Test returns False when organizations:gen-ai-features feature flag is not enabled.""" + @override_settings(SENTRY_SELF_HOSTED=True) + def test_returns_false_when_self_hosted(self) -> None: + """Test returns False when Seer is not available (self-hosted).""" result = is_issue_eligible_for_seer_automation(self.group) assert result is False def test_returns_false_when_ai_features_hidden(self) -> None: """Test returns False when sentry:hide_ai_features option is enabled.""" - with self.feature("organizations:gen-ai-features"): - self.organization.update_option("sentry:hide_ai_features", True) - result = is_issue_eligible_for_seer_automation(self.group) - assert result is False + self.organization.update_option("sentry:hide_ai_features", True) + result = is_issue_eligible_for_seer_automation(self.group) + assert result is False def test_returns_false_when_scanner_automation_disabled_and_not_always_trigger(self) -> None: """Test returns False when scanner automation is disabled and issue type doesn't always trigger.""" - with self.feature("organizations:gen-ai-features"): - self.project.update_option("sentry:seer_scanner_automation", False) - result = is_issue_eligible_for_seer_automation(self.group) - assert result is False + self.project.update_option("sentry:seer_scanner_automation", False) + result = is_issue_eligible_for_seer_automation(self.group) + assert result is False @patch("sentry.quotas.backend.check_seer_quota") def test_returns_false_when_no_budget_available(self, mock_has_budget): """Test returns False when organization has no available budget for scanner.""" - with self.feature("organizations:gen-ai-features"): - self.project.update_option("sentry:seer_scanner_automation", True) - mock_has_budget.return_value = False + self.project.update_option("sentry:seer_scanner_automation", True) + mock_has_budget.return_value = False - result = is_issue_eligible_for_seer_automation(self.group) + result = is_issue_eligible_for_seer_automation(self.group) - assert result is False - mock_has_budget.assert_called_once_with( - org_id=self.organization.id, data_category=DataCategory.SEER_SCANNER - ) + assert result is False + mock_has_budget.assert_called_once_with( + org_id=self.organization.id, data_category=DataCategory.SEER_SCANNER + ) @patch("sentry.quotas.backend.check_seer_quota") def test_returns_true_when_all_conditions_met(self, mock_has_budget): """Test returns True when all eligibility conditions are met.""" - with self.feature("organizations:gen-ai-features"): - self.project.update_option("sentry:seer_scanner_automation", True) + self.project.update_option("sentry:seer_scanner_automation", True) - mock_has_budget.return_value = True + mock_has_budget.return_value = True - result = is_issue_eligible_for_seer_automation(self.group) + result = is_issue_eligible_for_seer_automation(self.group) - assert result is True - mock_has_budget.assert_called_once_with( - org_id=self.organization.id, data_category=DataCategory.SEER_SCANNER - ) + assert result is True + mock_has_budget.assert_called_once_with( + org_id=self.organization.id, data_category=DataCategory.SEER_SCANNER + ) @patch("sentry.quotas.backend.check_seer_quota") def test_returns_true_when_issue_type_always_triggers( @@ -214,17 +209,16 @@ def test_returns_true_when_issue_type_always_triggers( mock_has_budget, ): """Test returns True when issue type has always_trigger_seer_automation even if scanner automation is disabled.""" - with self.feature("organizations:gen-ai-features"): - # Disable scanner automation - self.project.update_option("sentry:seer_scanner_automation", False) + # Disable scanner automation + self.project.update_option("sentry:seer_scanner_automation", False) - mock_has_budget.return_value = True + mock_has_budget.return_value = True - # Mock the group's issue_type to always trigger - with patch.object(self.group.issue_type, "always_trigger_seer_automation", True): - result = is_issue_eligible_for_seer_automation(self.group) + # Mock the group's issue_type to always trigger + with patch.object(self.group.issue_type, "always_trigger_seer_automation", True): + result = is_issue_eligible_for_seer_automation(self.group) - assert result is True + assert result is True class TestIsSeerSeatBasedTierEnabled(TestCase): diff --git a/tests/sentry/seer/autofix/test_issue_summary.py b/tests/sentry/seer/autofix/test_issue_summary.py index 50fb72261bd3..4a3817e5ae33 100644 --- a/tests/sentry/seer/autofix/test_issue_summary.py +++ b/tests/sentry/seer/autofix/test_issue_summary.py @@ -31,7 +31,6 @@ from sentry.testutils.cases import APITestCase, SnubaTestCase, TestCase from sentry.testutils.helpers.action_log import capture_action_log from sentry.testutils.helpers.datetime import before_now -from sentry.testutils.helpers.features import with_feature from sentry.testutils.skips import requires_snuba from sentry.types.activity import ActivityType from sentry.utils.cache import cache @@ -72,7 +71,6 @@ def test_post_process_kickoff_creates_system_activity( @override_settings(SENTRY_SELF_HOSTED=False) -@with_feature("organizations:gen-ai-features") class IssueSummaryTest(APITestCase, SnubaTestCase, OccurrenceTestMixin): def setUp(self) -> None: super().setUp() @@ -882,7 +880,7 @@ def test_stopping_point_mapping(self, score, expected): @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.seer.autofix.issue_summary.is_seer_seat_based_tier_enabled", return_value=True) -@with_feature({"organizations:gen-ai-features": True}) +@override_settings(SENTRY_SELF_HOSTED=False) class TestRunAutomationStoppingPoint(APITestCase, SnubaTestCase): def setUp(self) -> None: super().setUp() @@ -950,8 +948,7 @@ def test_without_seat_based_tier( mock_seat_based_tier, ): mock_seat_based_tier.return_value = False - with self.feature({"organizations:gen-ai-features": True}): - run_automation(self.group, self.user, self.event, SeerAutomationSource.POST_PROCESS) + run_automation(self.group, self.user, self.event, SeerAutomationSource.POST_PROCESS) mock_trigger.assert_called_once() assert mock_trigger.call_args[1]["stopping_point"] == AutofixStoppingPoint.CODE_CHANGES @@ -1032,7 +1029,7 @@ def test_upper_bound_combinations(self, fixability, user_pref, expected): @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.seer.autofix.issue_summary.is_seer_seat_based_tier_enabled", return_value=True) -@with_feature({"organizations:gen-ai-features": True}) +@override_settings(SENTRY_SELF_HOSTED=False) class TestRunAutomationWithUpperBound(APITestCase, SnubaTestCase): def setUp(self) -> None: super().setUp() @@ -1107,7 +1104,7 @@ def test_fixability_limits_permissive_user_preference( assert mock_trigger.call_args[1]["stopping_point"] == AutofixStoppingPoint.ROOT_CAUSE -@with_feature("organizations:gen-ai-features") +@override_settings(SENTRY_SELF_HOSTED=False) class TestGetAndUpdateGroupFixabilityScore(APITestCase, SnubaTestCase): def setUp(self) -> None: super().setUp() @@ -1249,7 +1246,6 @@ def test_no_summary_in_cache_calls_seer_without_summary(self, mock_request): @override_settings(SENTRY_SELF_HOSTED=False) -@with_feature("organizations:gen-ai-features") class TestIsGroupEligibleForAutomation(APITestCase, SnubaTestCase): def setUp(self) -> None: super().setUp() @@ -1268,10 +1264,10 @@ def test_returns_true_when_all_checks_pass(self, mock_fixability, mock_quota, mo assert is_group_eligible_for_automation(self.group) is True + @override_settings(SENTRY_SELF_HOSTED=True) @patch("sentry.seer.autofix.issue_summary.get_and_update_group_fixability_score") def test_returns_false_without_seer_access(self, mock_fixability): - with self.feature({"organizations:gen-ai-features": False}): - assert is_group_eligible_for_automation(self.group) is False + assert is_group_eligible_for_automation(self.group) is False mock_fixability.assert_not_called() @@ -1339,7 +1335,7 @@ def test_returns_false_when_rate_limited(self, mock_fixability, mock_quota, mock @patch("sentry.seer.autofix.issue_summary.is_seer_seat_based_tier_enabled", return_value=True) -@with_feature({"organizations:gen-ai-features": True}) +@override_settings(SENTRY_SELF_HOSTED=False) class TestGetAutomationStoppingPoint(TestCase): def setUp(self) -> None: super().setUp() diff --git a/tests/sentry/seer/code_review/test_preflight.py b/tests/sentry/seer/code_review/test_preflight.py index 5b3f3905a30e..a4c734c887c9 100644 --- a/tests/sentry/seer/code_review/test_preflight.py +++ b/tests/sentry/seer/code_review/test_preflight.py @@ -47,14 +47,14 @@ def _create_service( # Legal AI consent tests # ------------------------------------------------------------------------- - def test_denied_when_gen_ai_feature_flag_disabled(self) -> None: + @override_settings(SENTRY_SELF_HOSTED=True) + def test_denied_when_self_hosted(self) -> None: service = self._create_service() result = service.check() assert result.allowed is False assert result.denial_reason == PreflightDenialReason.ORG_LEGAL_AI_CONSENT_NOT_GRANTED - @with_feature("organizations:gen-ai-features") def test_denied_when_hide_ai_features_enabled(self) -> None: self.organization.update_option("sentry:hide_ai_features", True) @@ -68,7 +68,7 @@ def test_denied_when_hide_ai_features_enabled(self) -> None: # Org feature enablement tests # ------------------------------------------------------------------------- - @with_feature(["organizations:gen-ai-features", "organizations:code-review-beta"]) + @with_feature(["organizations:code-review-beta"]) def test_denied_when_beta_org_has_no_repo_settings(self) -> None: service = self._create_service() result = service.check() @@ -76,7 +76,7 @@ def test_denied_when_beta_org_has_no_repo_settings(self) -> None: assert result.allowed is False assert result.denial_reason == PreflightDenialReason.REPO_CODE_REVIEW_DISABLED - @with_feature(["organizations:gen-ai-features", "organizations:code-review-beta"]) + @with_feature(["organizations:code-review-beta"]) def test_allowed_when_beta_org_has_repo_settings_enabled(self) -> None: self.create_repository_settings( repository=self.repo, @@ -95,7 +95,6 @@ def test_allowed_when_beta_org_has_repo_settings_enabled(self) -> None: assert result.allowed is True assert result.denial_reason is None - @with_feature("organizations:gen-ai-features") def test_denied_when_org_has_no_seer_feature_flags(self) -> None: service = self._create_service() result = service.check() @@ -107,7 +106,7 @@ def test_denied_when_org_has_no_seer_feature_flags(self) -> None: # Seer-added (legacy usage-based) org tests - not eligible for code review # ------------------------------------------------------------------------- - @with_feature(["organizations:gen-ai-features", "organizations:seer-added"]) + @with_feature(["organizations:seer-added"]) def test_denied_when_seer_added_only_org_not_eligible(self) -> None: service = self._create_service() result = service.check() @@ -115,7 +114,7 @@ def test_denied_when_seer_added_only_org_not_eligible(self) -> None: assert result.allowed is False assert result.denial_reason == PreflightDenialReason.ORG_NOT_ELIGIBLE_FOR_CODE_REVIEW - @with_feature(["organizations:gen-ai-features", "organizations:seer-added"]) + @with_feature(["organizations:seer-added"]) def test_denied_when_seer_added_only_org_even_with_repo_settings_enabled(self) -> None: self.create_repository_settings( repository=self.repo, @@ -136,7 +135,6 @@ def test_denied_when_seer_added_only_org_even_with_repo_settings_enabled(self) - @with_feature( [ - "organizations:gen-ai-features", "organizations:seer-added", "organizations:code-review-beta", ] @@ -164,7 +162,7 @@ def test_allowed_when_seer_added_and_code_review_beta_org_has_repo_settings(self # Seat-based org tests # ------------------------------------------------------------------------- - @with_feature(["organizations:gen-ai-features", "organizations:seat-based-seer-enabled"]) + @with_feature(["organizations:seat-based-seer-enabled"]) def test_denied_when_seat_based_org_has_no_repo_settings(self) -> None: service = self._create_service() result = service.check() @@ -172,7 +170,7 @@ def test_denied_when_seat_based_org_has_no_repo_settings(self) -> None: assert result.allowed is False assert result.denial_reason == PreflightDenialReason.REPO_CODE_REVIEW_DISABLED - @with_feature(["organizations:gen-ai-features", "organizations:seat-based-seer-enabled"]) + @with_feature(["organizations:seat-based-seer-enabled"]) def test_denied_when_seat_based_org_has_repo_settings_disabled(self) -> None: self.create_repository_settings( repository=self.repo, @@ -185,7 +183,7 @@ def test_denied_when_seat_based_org_has_repo_settings_disabled(self) -> None: assert result.denial_reason == PreflightDenialReason.REPO_CODE_REVIEW_DISABLED @patch("sentry.quotas.backend.check_seer_quota") - @with_feature(["organizations:gen-ai-features", "organizations:seat-based-seer-enabled"]) + @with_feature(["organizations:seat-based-seer-enabled"]) def test_allowed_when_seat_based_org_has_repo_settings_enabled( self, mock_check_quota: MagicMock ) -> None: @@ -215,7 +213,7 @@ def test_allowed_when_seat_based_org_has_repo_settings_enabled( # ------------------------------------------------------------------------- @patch("sentry.quotas.backend.check_seer_quota") - @with_feature(["organizations:gen-ai-features", "organizations:seat-based-seer-enabled"]) + @with_feature(["organizations:seat-based-seer-enabled"]) def test_returns_repo_settings_when_allowed(self, mock_check_quota: MagicMock) -> None: mock_check_quota.return_value = True @@ -246,7 +244,6 @@ def test_returns_repo_settings_when_allowed(self, mock_check_quota: MagicMock) - @patch("sentry.quotas.backend.check_seer_quota") @with_feature( [ - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seat-based-seer-enabled", ] @@ -282,7 +279,7 @@ def test_uses_repo_settings_when_has_both_code_review_beta_and_seat_based_featur # Billing tests # ------------------------------------------------------------------------- - @with_feature(["organizations:gen-ai-features", "organizations:seat-based-seer-enabled"]) + @with_feature(["organizations:seat-based-seer-enabled"]) def test_denied_when_missing_integration(self) -> None: self.create_repository_settings( repository=self.repo, @@ -305,7 +302,7 @@ def test_denied_when_missing_integration(self) -> None: "sentry.seer.code_review.preflight.instance_hostname", side_effect=InstanceHostnameError("missing"), ) - @with_feature(["organizations:gen-ai-features", "organizations:seat-based-seer-enabled"]) + @with_feature(["organizations:seat-based-seer-enabled"]) def test_denied_when_integration_missing_hostname( self, mock_hostname: MagicMock, mock_capture: MagicMock ) -> None: @@ -326,7 +323,7 @@ def test_denied_when_integration_missing_hostname( assert result.denial_reason == PreflightDenialReason.BILLING_MISSING_CONTRIBUTOR_INFO mock_capture.assert_called_once() - @with_feature(["organizations:gen-ai-features", "organizations:seat-based-seer-enabled"]) + @with_feature(["organizations:seat-based-seer-enabled"]) def test_denied_when_missing_external_identifier(self) -> None: self.create_repository_settings( repository=self.repo, @@ -344,7 +341,7 @@ def test_denied_when_missing_external_identifier(self) -> None: assert result.allowed is False assert result.denial_reason == PreflightDenialReason.BILLING_MISSING_CONTRIBUTOR_INFO - @with_feature(["organizations:gen-ai-features", "organizations:seat-based-seer-enabled"]) + @with_feature(["organizations:seat-based-seer-enabled"]) def test_denied_when_contributor_does_not_exist(self) -> None: self.create_repository_settings( repository=self.repo, @@ -358,7 +355,7 @@ def test_denied_when_contributor_does_not_exist(self) -> None: assert result.denial_reason == PreflightDenialReason.ORG_CONTRIBUTOR_NOT_FOUND @patch("sentry.quotas.backend.check_seer_quota") - @with_feature(["organizations:gen-ai-features", "organizations:seat-based-seer-enabled"]) + @with_feature(["organizations:seat-based-seer-enabled"]) def test_denied_when_quota_check_fails(self, mock_check_quota: MagicMock) -> None: mock_check_quota.return_value = False @@ -382,7 +379,6 @@ def test_denied_when_quota_check_fails(self, mock_check_quota: MagicMock) -> Non @patch("sentry.quotas.backend.check_seer_quota") @with_feature( [ - "organizations:gen-ai-features", "organizations:seat-based-seer-enabled", "organizations:code-review-beta", ] @@ -417,7 +413,7 @@ def test_checks_seats_when_both_code_review_beta_and_seat_based_features_are_ena mock_check_quota.assert_called_once() @patch("sentry.quotas.backend.check_seer_quota") - @with_feature(["organizations:gen-ai-features", "organizations:seat-based-seer-enabled"]) + @with_feature(["organizations:seat-based-seer-enabled"]) def test_denied_when_pr_author_is_excluded(self, mock_check_quota: MagicMock) -> None: mock_check_quota.return_value = True diff --git a/tests/sentry/seer/code_review/webhooks/test_merge_request.py b/tests/sentry/seer/code_review/webhooks/test_merge_request.py index ab38411ec0e5..3860b4062f89 100644 --- a/tests/sentry/seer/code_review/webhooks/test_merge_request.py +++ b/tests/sentry/seer/code_review/webhooks/test_merge_request.py @@ -77,7 +77,6 @@ class _MergeRequestHandlerTestBase(GitLabTestCase): """ CODE_REVIEW_FEATURES = { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -163,7 +162,6 @@ def _call_handler(self, event: dict[str, Any]) -> None: class MergeRequestEventWebhookTest(_MergeRequestHandlerTestBase): @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -181,7 +179,6 @@ def test_open_uses_review_request_endpoint(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -217,7 +214,7 @@ def test_validation_failure_is_captured_and_review_dropped(self) -> None: ) self.mock_seer.assert_not_called() - @with_feature({"organizations:gen-ai-features", "organizations:code-review-beta"}) + @with_feature({"organizations:code-review-beta"}) def test_skips_when_gitlab_flag_disabled(self) -> None: # The GitLab MR handler is gated on organizations:seer-gitlab-support, # independent of the other code-review flags. @@ -231,7 +228,6 @@ def test_skips_when_gitlab_flag_disabled(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -249,7 +245,6 @@ def test_close_uses_pr_closed_endpoint(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -267,7 +262,6 @@ def test_merge_uses_pr_closed_endpoint(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -285,7 +279,6 @@ def test_update_uses_review_request_endpoint(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -302,7 +295,6 @@ def test_update_without_oldrev_is_skipped(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -320,7 +312,6 @@ def test_update_with_unrelated_changes_is_skipped(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -342,7 +333,6 @@ def test_undraft_update_uses_review_request_endpoint(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -361,7 +351,6 @@ def test_undraft_update_via_work_in_progress_uses_review_request_endpoint(self) @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -379,7 +368,6 @@ def test_undraft_update_trigger_is_ready_for_review(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -397,7 +385,6 @@ def test_undraft_update_filtered_when_ready_trigger_disabled(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -413,7 +400,6 @@ def test_skips_draft_mr(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -429,7 +415,6 @@ def test_skips_work_in_progress_mr(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -445,7 +430,6 @@ def test_close_still_sends_for_draft_mr(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -461,7 +445,6 @@ def test_skips_unsupported_action(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -477,7 +460,6 @@ def test_skips_unknown_action(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -494,7 +476,6 @@ def test_skips_missing_action(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -524,7 +505,6 @@ def test_skips_when_code_review_not_enabled(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -541,7 +521,6 @@ def test_skips_missing_last_commit(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -557,7 +536,6 @@ def test_open_filtered_when_trigger_disabled(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -573,7 +551,6 @@ def test_update_filtered_when_trigger_disabled(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -589,7 +566,6 @@ def test_close_filtered_when_no_triggers_configured(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -605,7 +581,6 @@ def test_close_sends_when_triggers_configured(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -624,7 +599,6 @@ def test_payload_contains_correct_pr_id(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -643,7 +617,6 @@ def test_payload_contains_gitlab_provider(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -664,7 +637,6 @@ def test_payload_owner_and_name_use_path_not_display_name(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -683,7 +655,6 @@ def test_payload_owner_and_name_handle_subgroups(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -702,7 +673,6 @@ def test_payload_is_private_true_for_private_project(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -721,7 +691,6 @@ def test_payload_is_private_true_for_internal_project(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -740,7 +709,6 @@ def test_payload_is_private_false_for_public_project(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -759,7 +727,6 @@ def test_payload_is_private_none_when_visibility_absent(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -778,7 +745,6 @@ def test_payload_trigger_on_ready_for_review_for_open(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -797,7 +763,6 @@ def test_payload_trigger_on_new_commit_for_update(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -816,7 +781,6 @@ def test_payload_contains_trigger_user_from_event(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -847,7 +811,6 @@ def test_open_with_gitlab_space_utc_timestamp_enqueues(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -871,7 +834,6 @@ def test_open_with_iso8601_timestamp_still_enqueues(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -899,7 +861,6 @@ def test_open_with_unparseable_timestamp_captures_and_falls_back(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -916,7 +877,6 @@ def test_duplicate_delivery_within_window_skipped(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -944,7 +904,6 @@ def test_duplicate_delivery_after_ttl_processes_again(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1039,7 +998,6 @@ def _call_handler(self, event: dict[str, Any]) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1058,7 +1016,6 @@ def test_sentry_review_comment_schedules_seer_task(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1076,7 +1033,6 @@ def test_payload_trigger_is_on_command_phrase(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1095,7 +1051,6 @@ def test_payload_contains_trigger_comment_id_and_type(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1113,7 +1068,6 @@ def test_payload_trigger_user_is_commenter(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1134,7 +1088,6 @@ def test_eyes_reaction_added_to_note(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1151,7 +1104,6 @@ def test_non_review_command_is_ignored(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1168,7 +1120,6 @@ def test_issue_note_is_ignored(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1185,7 +1136,6 @@ def test_non_create_action_is_ignored(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1202,7 +1152,6 @@ def test_sentry_review_case_insensitive(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1230,7 +1179,6 @@ def test_skips_when_feature_flag_disabled(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1255,7 +1203,6 @@ def test_skips_when_integration_is_none(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1273,7 +1220,6 @@ def test_reaction_failure_does_not_block_seer_task(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1320,7 +1266,6 @@ class MergeRequestReactionTest(_MergeRequestHandlerTestBase): @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1338,7 +1283,6 @@ def test_eyes_reaction_added_for_open_action(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1365,7 +1309,6 @@ def test_stale_hooray_reaction_deleted_before_eyes_added(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1391,7 +1334,6 @@ def test_other_users_hooray_reaction_not_deleted(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1410,7 +1352,6 @@ def test_no_reaction_for_close_action(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1429,7 +1370,6 @@ def test_no_reaction_for_merge_action(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } @@ -1449,7 +1389,6 @@ def test_reaction_failure_does_not_block_seer_task(self) -> None: @with_feature( { - "organizations:gen-ai-features", "organizations:code-review-beta", "organizations:seer-gitlab-support", } diff --git a/tests/sentry/seer/code_review/webhooks/test_pull_request.py b/tests/sentry/seer/code_review/webhooks/test_pull_request.py index 096f5aaf88b5..4ccc05f60cc4 100644 --- a/tests/sentry/seer/code_review/webhooks/test_pull_request.py +++ b/tests/sentry/seer/code_review/webhooks/test_pull_request.py @@ -248,7 +248,7 @@ def test_pull_request_closed_uses_pr_closed_endpoint(self) -> None: def test_pull_request_opened_filtered_when_trigger_disabled_post_ga(self) -> None: triggers = [CodeReviewTrigger.ON_NEW_COMMIT] - features = {"organizations:gen-ai-features", "organizations:seat-based-seer-enabled"} + features = {"organizations:seat-based-seer-enabled"} with ( self.code_review_setup(triggers=triggers, features=features), self.tasks(), @@ -263,7 +263,7 @@ def test_pull_request_opened_filtered_when_trigger_disabled_post_ga(self) -> Non def test_pull_request_synchronize_filtered_when_trigger_disabled_post_ga(self) -> None: triggers = [CodeReviewTrigger.ON_READY_FOR_REVIEW] - features = {"organizations:gen-ai-features", "organizations:seat-based-seer-enabled"} + features = {"organizations:seat-based-seer-enabled"} with ( self.code_review_setup(triggers=triggers, features=features), self.tasks(), @@ -278,7 +278,7 @@ def test_pull_request_synchronize_filtered_when_trigger_disabled_post_ga(self) - def test_pull_request_ready_for_review_filtered_when_trigger_disabled_post_ga(self) -> None: triggers = [CodeReviewTrigger.ON_NEW_COMMIT] - features = {"organizations:gen-ai-features", "organizations:seat-based-seer-enabled"} + features = {"organizations:seat-based-seer-enabled"} with ( self.code_review_setup(triggers=triggers, features=features), self.tasks(), @@ -301,7 +301,7 @@ def test_pull_request_closed_filtered_when_no_triggers_configured_post_ga(self) helper skips RepositorySettings creation when triggers=[], which would cause the preflight to deny the request before reaching the handler under test. """ - features = {"organizations:gen-ai-features", "organizations:seat-based-seer-enabled"} + features = {"organizations:seat-based-seer-enabled"} with self.feature(features), self.tasks(): event = orjson.loads(PULL_REQUEST_OPENED_EVENT_EXAMPLE) event["action"] = "closed" @@ -320,7 +320,7 @@ def test_pull_request_closed_filtered_when_no_triggers_configured_post_ga(self) def test_pull_request_closed_not_filtered_when_triggers_configured_post_ga(self) -> None: """Test that closed action reaches Seer when at least one trigger is configured.""" triggers: list[CodeReviewTrigger] = [CodeReviewTrigger.ON_READY_FOR_REVIEW] - features = {"organizations:gen-ai-features", "organizations:seat-based-seer-enabled"} + features = {"organizations:seat-based-seer-enabled"} with ( self.code_review_setup(triggers=triggers, features=features), self.tasks(), @@ -335,7 +335,7 @@ def test_pull_request_closed_not_filtered_when_triggers_configured_post_ga(self) def test_pull_request_opened_works_when_trigger_enabled_post_ga(self) -> None: triggers = [CodeReviewTrigger.ON_READY_FOR_REVIEW] - features = {"organizations:gen-ai-features", "organizations:seat-based-seer-enabled"} + features = {"organizations:seat-based-seer-enabled"} with ( self.code_review_setup(triggers=triggers, features=features), self.tasks(), @@ -350,7 +350,7 @@ def test_pull_request_opened_works_when_trigger_enabled_post_ga(self) -> None: def test_pull_request_ready_for_review_works_when_trigger_enabled_post_ga(self) -> None: triggers = [CodeReviewTrigger.ON_READY_FOR_REVIEW] - features = {"organizations:gen-ai-features", "organizations:seat-based-seer-enabled"} + features = {"organizations:seat-based-seer-enabled"} with ( self.code_review_setup(triggers=triggers, features=features), self.tasks(), diff --git a/tests/sentry/seer/endpoints/test_group_ai_autofix.py b/tests/sentry/seer/endpoints/test_group_ai_autofix.py index d61c5c4c703e..537aad588260 100644 --- a/tests/sentry/seer/endpoints/test_group_ai_autofix.py +++ b/tests/sentry/seer/endpoints/test_group_ai_autofix.py @@ -60,7 +60,6 @@ def _user_context_length_calls(mock_distribution: Mock) -> list: @override_settings(SENTRY_SELF_HOSTED=False) -@with_feature("organizations:gen-ai-features") class GroupAutofixEndpointTest(APITestCase, SnubaTestCase): def _get_url(self, group_id: int) -> str: return f"/api/0/organizations/{self.organization.slug}/issues/{group_id}/autofix/" @@ -1487,7 +1486,7 @@ def test_open_pr_coding_disabled(self): assert response.status_code == 403, response.data -@with_feature("organizations:gen-ai-features") +@override_settings(SENTRY_SELF_HOSTED=False) class GroupAutofixConditionalGetTest(APITestCase): def _get_url(self, group_id: int) -> str: return f"/api/0/organizations/{self.organization.slug}/issues/{group_id}/autofix/" diff --git a/tests/sentry/seer/endpoints/test_group_ai_summary.py b/tests/sentry/seer/endpoints/test_group_ai_summary.py index 7dcb07ba1862..2f7cded4e72d 100644 --- a/tests/sentry/seer/endpoints/test_group_ai_summary.py +++ b/tests/sentry/seer/endpoints/test_group_ai_summary.py @@ -1,14 +1,15 @@ from unittest.mock import ANY, MagicMock, patch +from django.test import override_settings + from sentry.seer.autofix.constants import SeerAutomationSource from sentry.testutils.cases import APITestCase, SnubaTestCase -from sentry.testutils.helpers.features import with_feature from sentry.testutils.skips import requires_snuba pytestmark = [requires_snuba] -@with_feature("organizations:gen-ai-features") +@override_settings(SENTRY_SELF_HOSTED=False) class GroupAiSummaryEndpointTest(APITestCase, SnubaTestCase): def setUp(self) -> None: super().setUp() diff --git a/tests/sentry/seer/endpoints/test_group_autofix_repos.py b/tests/sentry/seer/endpoints/test_group_autofix_repos.py index 676f9ddafe66..699aa466f553 100644 --- a/tests/sentry/seer/endpoints/test_group_autofix_repos.py +++ b/tests/sentry/seer/endpoints/test_group_autofix_repos.py @@ -3,11 +3,9 @@ from django.test import override_settings from sentry.testutils.cases import APITestCase, SnubaTestCase -from sentry.testutils.helpers.features import with_feature @override_settings(SENTRY_SELF_HOSTED=False) -@with_feature("organizations:gen-ai-features") class GroupAutofixReposEndpointTest(APITestCase, SnubaTestCase): def setUp(self) -> None: super().setUp() diff --git a/tests/sentry/seer/endpoints/test_group_autofix_setup_check.py b/tests/sentry/seer/endpoints/test_group_autofix_setup_check.py index 83d4accd8cfc..0a5667e0f480 100644 --- a/tests/sentry/seer/endpoints/test_group_autofix_setup_check.py +++ b/tests/sentry/seer/endpoints/test_group_autofix_setup_check.py @@ -1,5 +1,7 @@ from unittest.mock import MagicMock, patch +from django.test import override_settings + from sentry.integrations.types import IntegrationProviderSlug from sentry.models.repository import Repository from sentry.seer.autofix.constants import AutofixAutomationTuningSettings @@ -8,7 +10,6 @@ ) from sentry.silo.base import SiloMode from sentry.testutils.cases import APITestCase, SnubaTestCase, TestCase -from sentry.testutils.helpers.features import with_feature from sentry.testutils.silo import assume_test_silo_mode from sentry.utils.cache import cache @@ -92,7 +93,7 @@ def test_unsupported_gitlab_integration(self) -> None: assert result == "integration_missing" -@with_feature("organizations:gen-ai-features") +@override_settings(SENTRY_SELF_HOSTED=False) class GroupAIAutofixEndpointSuccessTest(APITestCase, SnubaTestCase): def setUp(self) -> None: super().setUp() @@ -274,7 +275,7 @@ def test_missing_integration(self) -> None: assert response.data["seerReposLinked"] is False -@with_feature("organizations:gen-ai-features") +@override_settings(SENTRY_SELF_HOSTED=False) class GroupAIAutofixSetupFreeCohortTest(APITestCase, SnubaTestCase): """Tests for free cohort org behavior in the /autofix/setup/ endpoint.""" diff --git a/tests/sentry/seer/endpoints/test_organization_agent_token.py b/tests/sentry/seer/endpoints/test_organization_agent_token.py index 62ed3ad9c38c..36ce55977707 100644 --- a/tests/sentry/seer/endpoints/test_organization_agent_token.py +++ b/tests/sentry/seer/endpoints/test_organization_agent_token.py @@ -800,7 +800,7 @@ def test_end_to_end_read_allowed_write_denied(self) -> None: @pytest.mark.sentry_metrics @pytest.mark.seer_agent_token_matrix @requires_snuba -@override_settings(SEER_API_SHARED_SECRET=SECRET) +@override_settings(SEER_API_SHARED_SECRET=SECRET, SENTRY_SELF_HOSTED=False) class AgentTokenPublicGetMatrixTest(APITestCase): """Differential, full-stack authentication coverage for the public API. @@ -823,6 +823,11 @@ class AgentTokenPublicGetMatrixTest(APITestCase): def setUp(self) -> None: super().setUp() + rate_limit_patcher = patch( + "sentry.middleware.ratelimit.get_rate_limit_value", return_value=None + ) + rate_limit_patcher.start() + self.addCleanup(rate_limit_patcher.stop) self.owner = self.create_user() self.org = self.create_organization(owner=self.owner) self.team = self.create_team(organization=self.org) @@ -1301,7 +1306,6 @@ def _feature_flags( "ExternalUserDetailsEndpoint": "organizations:integrations-codeowners", "ExternalUserEndpoint": "organizations:integrations-codeowners", "EventAttachmentDetailsEndpoint": "organizations:event-attachments", - "GroupAutofixEndpoint": "organizations:gen-ai-features", "GroupIntegrationDetailsEndpoint": "organizations:integrations-issue-basic", "OrganizationEventsEndpoint": "organizations:discover-basic", "OrganizationGroupSearchViewsEndpoint": "organizations:issue-views", diff --git a/tests/sentry/seer/endpoints/test_organization_seer_agent_chat.py b/tests/sentry/seer/endpoints/test_organization_seer_agent_chat.py index 2e55022b7a75..b53859e3b003 100644 --- a/tests/sentry/seer/endpoints/test_organization_seer_agent_chat.py +++ b/tests/sentry/seer/endpoints/test_organization_seer_agent_chat.py @@ -22,7 +22,6 @@ @with_feature("organizations:seer-explorer") @override_settings(SENTRY_SELF_HOSTED=False) -@with_feature("organizations:gen-ai-features") class OrganizationSeerAgentChatEndpointTest(APITestCase): def setUp(self) -> None: super().setUp() @@ -550,14 +549,10 @@ def test_new_run_denied_without_seer_explorer_flag(self) -> None: assert response.status_code == 403 + @override_settings(SENTRY_SELF_HOSTED=True) def test_get_denied_without_seer_access(self) -> None: """GET should be denied when the org has neither seer-explorer nor base Seer access.""" - with self.feature( - { - "organizations:seer-explorer": False, - "organizations:gen-ai-features": False, - } - ): + with self.feature({"organizations:seer-explorer": False}): response = self.client.get(self.url) assert response.status_code == 403 @@ -674,7 +669,6 @@ def test_outbox_path_flush_error_marks_failed_and_raises( @with_feature("organizations:seer-explorer") @override_settings(SENTRY_SELF_HOSTED=False) -@with_feature("organizations:gen-ai-features") class OrganizationSeerAgentChatContextEngineTest(APITestCase): """End-to-end tests verifying is_context_engine_enabled reaches make_agent_chat_request.""" diff --git a/tests/sentry/seer/endpoints/test_organization_seer_agent_update.py b/tests/sentry/seer/endpoints/test_organization_seer_agent_update.py index c116aa056dbd..2166f00f766f 100644 --- a/tests/sentry/seer/endpoints/test_organization_seer_agent_update.py +++ b/tests/sentry/seer/endpoints/test_organization_seer_agent_update.py @@ -1,6 +1,7 @@ from unittest.mock import MagicMock, patch import orjson +from django.test import override_settings from rest_framework import status from sentry.integrations.types import ExternalProviders @@ -11,7 +12,7 @@ @with_feature("organizations:seer-explorer") -@with_feature("organizations:gen-ai-features") +@override_settings(SENTRY_SELF_HOSTED=False) class TestOrganizationSeerAgentUpdate(APITestCase): def setUp(self) -> None: super().setUp() @@ -257,7 +258,7 @@ def test_explorer_update_feature_flag_disabled(self, mock_has_access: MagicMock) @with_feature("organizations:seer-explorer") -@with_feature("organizations:gen-ai-features") +@override_settings(SENTRY_SELF_HOSTED=False) class TestOrganizationSeerAgentUpdateCodingDisabled(APITestCase): def setUp(self) -> None: super().setUp() @@ -305,7 +306,7 @@ def test_non_coding_payload_allowed_when_coding_disabled( @with_feature("organizations:seer-explorer") -@with_feature("organizations:gen-ai-features") +@override_settings(SENTRY_SELF_HOSTED=False) class TestOrganizationSeerAgentUpdateCommitAuthor(APITestCase): def setUp(self) -> None: super().setUp() diff --git a/tests/sentry/seer/endpoints/test_organization_seer_runs.py b/tests/sentry/seer/endpoints/test_organization_seer_runs.py index f3481585c3da..008bfafe4ff7 100644 --- a/tests/sentry/seer/endpoints/test_organization_seer_runs.py +++ b/tests/sentry/seer/endpoints/test_organization_seer_runs.py @@ -13,7 +13,7 @@ @override_settings(SENTRY_SELF_HOSTED=False) @with_feature("organizations:seer-explorer") -@with_feature("organizations:gen-ai-features") +@override_settings(SENTRY_SELF_HOSTED=False) class OrganizationSeerRunsEndpointTest(APITestCase): endpoint = "sentry-api-0-organization-seer-runs" diff --git a/tests/sentry/seer/endpoints/test_organization_seer_workflows.py b/tests/sentry/seer/endpoints/test_organization_seer_workflows.py index dbd859b00b4f..4fe951c39b84 100644 --- a/tests/sentry/seer/endpoints/test_organization_seer_workflows.py +++ b/tests/sentry/seer/endpoints/test_organization_seer_workflows.py @@ -420,15 +420,14 @@ def test_history_hides_runs_outside_user_access(self) -> None: def create_agent_workflow( self, strategy: SeerWorkflowStrategy, feature_id: str ) -> SeerWorkflowRun: - with self.feature("organizations:gen-ai-features"): - return create_workflow_run( - SeerAgentClient(self.organization, self.user), - strategy=strategy, - feature_id=feature_id, - title="Test workflow", - payload={}, - extras={"project_ids": [], "results": []}, - ) + return create_workflow_run( + SeerAgentClient(self.organization, self.user), + strategy=strategy, + feature_id=feature_id, + title="Test workflow", + payload={}, + extras={"project_ids": [], "results": []}, + ) @override_settings(SENTRY_SELF_HOSTED=False) @@ -514,11 +513,9 @@ def test_requires_feature_and_seer_access(self) -> None: self.get_error_response( self.organization.slug, strategy="duplicate_monitors", status_code=404 ) - with self.feature( - { - "organizations:seer-workflows-monitor-cleanup": True, - "organizations:gen-ai-features": False, - } + with ( + override_settings(SENTRY_SELF_HOSTED=True), + self.feature("organizations:seer-workflows-monitor-cleanup"), ): response = self.get_error_response( self.organization.slug, strategy="duplicate_monitors", status_code=403 @@ -527,9 +524,7 @@ def test_requires_feature_and_seer_access(self) -> None: limit.assert_not_called() limit.return_value = True - with self.feature( - ["organizations:seer-workflows-monitor-cleanup", "organizations:gen-ai-features"] - ): + with self.feature("organizations:seer-workflows-monitor-cleanup"): self.get_error_response( self.organization.slug, strategy="duplicate_monitors", status_code=429 ) @@ -553,9 +548,7 @@ def test_callback_for_deleted_user_marks_run_failed(self) -> None: assert agent_run.extras["error"] == "The triggering user no longer exists." def trigger(self): - with self.feature( - ["organizations:seer-workflows-monitor-cleanup", "organizations:gen-ai-features"] - ): + with self.feature("organizations:seer-workflows-monitor-cleanup"): response = self.get_success_response( self.organization.slug, strategy="duplicate_monitors", status_code=202 ) diff --git a/tests/sentry/seer/endpoints/test_search_agent_start.py b/tests/sentry/seer/endpoints/test_search_agent_start.py index 733ca4984205..39e0cf94592f 100644 --- a/tests/sentry/seer/endpoints/test_search_agent_start.py +++ b/tests/sentry/seer/endpoints/test_search_agent_start.py @@ -2,6 +2,7 @@ from unittest.mock import MagicMock, Mock, patch import pytest +from django.test import override_settings from rest_framework import status from sentry.seer.endpoints.search_agent_start import send_search_agent_start_request @@ -106,7 +107,7 @@ def test_flag_options_are_sent_to_seer(self, mock_request: Mock) -> None: @with_feature("organizations:gen-ai-search-agent-translate") -@with_feature("organizations:gen-ai-features") +@override_settings(SENTRY_SELF_HOSTED=False) class SearchAgentStartEndpointTest(APITestCase): def setUp(self) -> None: super().setUp() diff --git a/tests/sentry/seer/endpoints/test_search_agent_state.py b/tests/sentry/seer/endpoints/test_search_agent_state.py index 55b378f0dffd..e35bc823f55f 100644 --- a/tests/sentry/seer/endpoints/test_search_agent_state.py +++ b/tests/sentry/seer/endpoints/test_search_agent_state.py @@ -6,8 +6,7 @@ from sentry.testutils.cases import APITestCase -@override_settings(SENTRY_SELF_HOSTED=False) -@override_settings(SEER_AUTOFIX_URL="https://seer.example.com") +@override_settings(SEER_AUTOFIX_URL="https://seer.example.com", SENTRY_SELF_HOSTED=False) class SearchAgentStateEndpointTest(APITestCase): endpoint = "sentry-api-0-search-agent-state" @@ -16,7 +15,6 @@ def setUp(self) -> None: self.login_as(self.user) self.features = { "organizations:gen-ai-search-agent-translate": True, - "organizations:gen-ai-features": True, } @patch("sentry.seer.endpoints.search_agent_state.make_search_agent_state_request") diff --git a/tests/sentry/seer/endpoints/test_trace_explorer_ai_query.py b/tests/sentry/seer/endpoints/test_trace_explorer_ai_query.py index f860fbeac320..c46981c1d3b3 100644 --- a/tests/sentry/seer/endpoints/test_trace_explorer_ai_query.py +++ b/tests/sentry/seer/endpoints/test_trace_explorer_ai_query.py @@ -1,12 +1,12 @@ from unittest.mock import patch +from django.test import override_settings from rest_framework import status from sentry.testutils.cases import APITestCase -from sentry.testutils.helpers.features import with_feature -@with_feature("organizations:gen-ai-features") +@override_settings(SENTRY_SELF_HOSTED=False) class TraceExplorerAIQueryTest(APITestCase): def setUp(self) -> None: super().setUp() diff --git a/tests/sentry/seer/endpoints/test_trace_explorer_ai_setup.py b/tests/sentry/seer/endpoints/test_trace_explorer_ai_setup.py index 6fddfd355b86..29a2b4fb4478 100644 --- a/tests/sentry/seer/endpoints/test_trace_explorer_ai_setup.py +++ b/tests/sentry/seer/endpoints/test_trace_explorer_ai_setup.py @@ -1,7 +1,8 @@ from unittest.mock import patch +from django.test import override_settings + from sentry.testutils.cases import APITestCase -from sentry.testutils.helpers.features import with_feature class TraceExplorerAISetupTest(APITestCase): @@ -9,7 +10,7 @@ class TraceExplorerAISetupTest(APITestCase): method = "post" - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.seer.endpoints.trace_explorer_ai_setup.fire_setup_request") def test_simple(self, mock_fire_setup_request): self.login_as(self.user) @@ -30,7 +31,7 @@ def test_simple(self, mock_fire_setup_request): }, ) - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.seer.endpoints.trace_explorer_ai_setup.fire_setup_request") def test_rejects_project_from_other_org(self, mock_fire_setup_request): """Test that requesting projects from another org returns 403""" @@ -48,7 +49,7 @@ def test_rejects_project_from_other_org(self, mock_fire_setup_request): assert response.data == {"detail": "You do not have permission to perform this action."} mock_fire_setup_request.assert_not_called() - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.seer.endpoints.trace_explorer_ai_setup.fire_setup_request") def test_rejects_nonexistent_project(self, mock_fire_setup_request): """Test that requesting non-existent project returns same error as inaccessible project""" @@ -63,7 +64,7 @@ def test_rejects_nonexistent_project(self, mock_fire_setup_request): assert response.data == {"detail": "You do not have permission to perform this action."} mock_fire_setup_request.assert_not_called() - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.seer.endpoints.trace_explorer_ai_setup.fire_setup_request") def test_empty_projects_still_calls_seer(self, mock_fire_setup_request): """Test that empty project list is handled""" @@ -85,7 +86,8 @@ def test_empty_projects_still_calls_seer(self, mock_fire_setup_request): }, ) - def test_requires_feature_flag(self) -> None: + @override_settings(SENTRY_SELF_HOSTED=True) + def test_denied_on_self_hosted(self) -> None: self.login_as(self.user) response = self.get_error_response( @@ -96,7 +98,7 @@ def test_requires_feature_flag(self) -> None: assert response.data == {"detail": "Organization does not have access to this feature"} - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.seer.endpoints.trace_explorer_ai_setup.fire_setup_request") def test_invalid_project_id_returns_400(self, mock_fire_setup_request): """Test that non-integer project_id returns 400""" @@ -111,7 +113,7 @@ def test_invalid_project_id_returns_400(self, mock_fire_setup_request): assert response.data["detail"] == "Invalid project_id value" mock_fire_setup_request.assert_not_called() - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.seer.endpoints.trace_explorer_ai_setup.fire_setup_request") def test_negative_project_id_returns_400(self, mock_fire_setup_request): """Test that negative project_id (like -1 sentinel) returns 400""" @@ -126,7 +128,7 @@ def test_negative_project_id_returns_400(self, mock_fire_setup_request): assert response.data["detail"] == "Invalid project_id value" mock_fire_setup_request.assert_not_called() - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) def test_requires_authentication(self) -> None: response = self.get_error_response( self.organization.slug, diff --git a/tests/sentry/seer/endpoints/test_trace_explorer_ai_translate_agentic.py b/tests/sentry/seer/endpoints/test_trace_explorer_ai_translate_agentic.py index db3a5f4f1001..c1cc2cdea0e0 100644 --- a/tests/sentry/seer/endpoints/test_trace_explorer_ai_translate_agentic.py +++ b/tests/sentry/seer/endpoints/test_trace_explorer_ai_translate_agentic.py @@ -1,5 +1,6 @@ from unittest.mock import MagicMock, patch +from django.test import override_settings from rest_framework import status from sentry.testutils.cases import APITestCase @@ -7,7 +8,7 @@ @with_feature("organizations:seer-explorer") -@with_feature("organizations:gen-ai-features") +@override_settings(SENTRY_SELF_HOSTED=False) class SearchAgentTranslateEndpointTest(APITestCase): def setUp(self) -> None: super().setUp() diff --git a/tests/sentry/seer/entrypoints/slack/test_tasks.py b/tests/sentry/seer/entrypoints/slack/test_tasks.py index fd3a5ec2bc9c..fbe44e81e576 100644 --- a/tests/sentry/seer/entrypoints/slack/test_tasks.py +++ b/tests/sentry/seer/entrypoints/slack/test_tasks.py @@ -41,7 +41,6 @@ _SEER_SLACK_FEATURES = { - "organizations:gen-ai-features": True, "organizations:seer-explorer": True, } @@ -931,13 +930,13 @@ def test_unlinked_identity_records_halt( @patch("sentry.integrations.utils.metrics.EventLifecycle.record_event") @patch("sentry.analytics.record") + @override_settings(SENTRY_SELF_HOSTED=True) def test_no_agent_access_records_halt( self, mock_record, mock_lifecycle_record, ): - with self.feature({"organizations:gen-ai-features": False}): - process_reaction_for_slack(**self.defaults) + process_reaction_for_slack(**self.defaults) assert_not_analytics_event(mock_record, SlackSeerAgentFeedback) assert_halt_metric(mock_lifecycle_record, ProcessReactionHaltReason.NO_AGENT_ACCESS) diff --git a/tests/sentry/seer/entrypoints/test_operator.py b/tests/sentry/seer/entrypoints/test_operator.py index 31389abc0c5b..68bed6bf06c4 100644 --- a/tests/sentry/seer/entrypoints/test_operator.py +++ b/tests/sentry/seer/entrypoints/test_operator.py @@ -513,37 +513,23 @@ def test_process_autofix_updates_skips_entrypoint_without_access( ) def test_can_trigger_autofix_returns_false_without_seer_access(self) -> None: + self.organization.update_option("sentry:hide_ai_features", True) assert SeerAutofixOperator.can_trigger_autofix(group=self.group) is False @patch("sentry.quotas.backend.check_seer_quota", return_value=True) def test_can_trigger_autofix_returns_true_when_all_conditions_met(self, mock_quota): - with self.feature( - { - "organizations:gen-ai-features": True, - } - ): - assert SeerAutofixOperator.can_trigger_autofix(group=self.group) is True + assert SeerAutofixOperator.can_trigger_autofix(group=self.group) is True @patch("sentry.quotas.backend.check_seer_quota", return_value=True) def test_can_trigger_autofix_returns_false_for_ineligible_category(self, mock_quota): from sentry.issues.grouptype import FeedbackGroup feedback_group = self.create_group(project=self.project, type=FeedbackGroup.type_id) - with self.feature( - { - "organizations:gen-ai-features": True, - } - ): - assert SeerAutofixOperator.can_trigger_autofix(group=feedback_group) is False + assert SeerAutofixOperator.can_trigger_autofix(group=feedback_group) is False @patch("sentry.quotas.backend.check_seer_quota", return_value=False) def test_can_trigger_autofix_returns_false_without_quota(self, mock_quota): - with self.feature( - { - "organizations:gen-ai-features": True, - } - ): - assert SeerAutofixOperator.can_trigger_autofix(group=self.group) is False + assert SeerAutofixOperator.can_trigger_autofix(group=self.group) is False @patch.object(SeerAutofixOperator, "has_access", return_value=True) def test_seer_event_creates_activity_rca_completed(self, _mock_has_access): @@ -955,7 +941,6 @@ def test_has_access_with_seer_agent(self): with ( self.feature( { - "organizations:gen-ai-features": True, "organizations:seer-explorer": True, } ), @@ -978,9 +963,9 @@ def test_has_access_with_seer_agent(self): entrypoint_key=MockNoAccessEntrypoint.key, ) + @override_settings(SENTRY_SELF_HOSTED=True) def test_has_access_without_seer_agent(self): - with self.feature({"organizations:gen-ai-features": False}): - assert not SeerAgentOperator.has_access(organization=self.organization) + assert not SeerAgentOperator.has_access(organization=self.organization) class TestSeerOperatorCompletionHook(TestCase): diff --git a/tests/sentry/seer/test_seer_setup.py b/tests/sentry/seer/test_seer_setup.py index 728da784d028..5bd9d49d211f 100644 --- a/tests/sentry/seer/test_seer_setup.py +++ b/tests/sentry/seer/test_seer_setup.py @@ -6,7 +6,6 @@ is_seer_available, ) from sentry.testutils.cases import TestCase -from sentry.testutils.helpers.features import with_feature class IsSeerAvailableTest(TestCase): @@ -21,18 +20,11 @@ def test_unavailable_on_self_hosted(self) -> None: @override_settings(SENTRY_SELF_HOSTED=False) class HasSeerAccessTest(TestCase): - @with_feature("organizations:gen-ai-features") def test_allowed(self) -> None: org = self.create_organization() assert has_seer_access(org) is True assert has_seer_access_with_detail(org) == (True, None) - def test_denied_without_flag(self) -> None: - org = self.create_organization() - assert has_seer_access(org) is False - assert has_seer_access_with_detail(org) == (False, "Feature flag not enabled") - - @with_feature("organizations:gen-ai-features") def test_denied_when_hidden(self) -> None: org = self.create_organization() org.update_option("sentry:hide_ai_features", True) @@ -42,7 +34,6 @@ def test_denied_when_hidden(self) -> None: "AI features are disabled for this organization.", ) - @with_feature("organizations:gen-ai-features") @override_settings(SENTRY_SELF_HOSTED=True) def test_denied_on_self_hosted(self) -> None: org = self.create_organization() diff --git a/tests/sentry/seer/workflows/test_runs.py b/tests/sentry/seer/workflows/test_runs.py index 961482ef0963..d88ff7e90921 100644 --- a/tests/sentry/seer/workflows/test_runs.py +++ b/tests/sentry/seer/workflows/test_runs.py @@ -156,15 +156,14 @@ def test_dispatch_failure_is_reported_without_overwriting_a_completed_result(sel assert status["error"] is None def create_run(self) -> SeerRun: - with self.feature("organizations:gen-ai-features"): - workflow = create_workflow_run( - SeerAgentClient(self.organization, self.user), - strategy=SeerWorkflowStrategy.AGENTIC_TRIAGE, - feature_id="test_workflow", - title="Test workflow", - payload={}, - extras={"summary": None}, - ) + workflow = create_workflow_run( + SeerAgentClient(self.organization, self.user), + strategy=SeerWorkflowStrategy.AGENTIC_TRIAGE, + feature_id="test_workflow", + title="Test workflow", + payload={}, + extras={"summary": None}, + ) run = workflow.executions.get().seer_run assert run is not None return run diff --git a/tests/sentry/tasks/seer/test_explorer_index.py b/tests/sentry/tasks/seer/test_explorer_index.py index 2e8aaa5cd7b7..a77cad09d15a 100644 --- a/tests/sentry/tasks/seer/test_explorer_index.py +++ b/tests/sentry/tasks/seer/test_explorer_index.py @@ -57,7 +57,6 @@ def test_returns_projects_with_feature_flag(self) -> None: with self.feature( { - "organizations:gen-ai-features": [org1.slug, org2.slug], "organizations:seer-explorer-index": [org1.slug, org2.slug], } ): @@ -95,7 +94,6 @@ def test_excludes_inactive_projects(self) -> None: with self.feature( { - "organizations:gen-ai-features": [org.slug], "organizations:seer-explorer-index": [org.slug], } ): @@ -121,7 +119,6 @@ def test_excludes_projects_with_hide_ai_features(self) -> None: with self.feature( { - "organizations:gen-ai-features": [org.slug], "organizations:seer-explorer-index": [org.slug], } ): @@ -140,7 +137,6 @@ def test_excludes_projects_without_seer_acknowledgement(self) -> None: with self.feature( { - "organizations:gen-ai-features": [org.slug], "organizations:seer-explorer-index": [org.slug], } ): @@ -163,7 +159,6 @@ def test_excludes_projects_without_transactions(self) -> None: with self.feature( { - "organizations:gen-ai-features": [org.slug], "organizations:seer-explorer-index": [org.slug], } ): @@ -194,7 +189,6 @@ def test_includes_only_projects_matching_hour_shard(self) -> None: with self.feature( { - "organizations:gen-ai-features": [org.slug], "organizations:seer-explorer-index": [org.slug], } ): @@ -224,12 +218,7 @@ def test_excludes_projects_without_seer_billing_plan(self) -> None: feature="seer_autofix_setup_acknowledged", ) - with self.feature( - { - "organizations:gen-ai-features": [org.slug], - } - ): - result = list(get_seer_explorer_enabled_projects()) + result = list(get_seer_explorer_enabled_projects()) assert len(result) == 0 assert project.id not in [p[0] for p in result] @@ -251,7 +240,6 @@ def test_includes_projects_with_legacy_seer_plan(self) -> None: with self.feature( { - "organizations:gen-ai-features": [org.slug], "organizations:seer-added": [org.slug], } ): @@ -278,7 +266,6 @@ def test_includes_projects_with_seat_based_plan(self) -> None: with self.feature( { - "organizations:gen-ai-features": [org.slug], "organizations:seat-based-seer-enabled": [org.slug], } ): @@ -289,6 +276,7 @@ def test_includes_projects_with_seat_based_plan(self) -> None: assert project.id in project_ids +@override_settings(SENTRY_SELF_HOSTED=False) @django_db_all class TestScheduleExplorerIndex(TestCase): def test_skips_when_killswitch_enabled(self) -> None: @@ -316,7 +304,6 @@ def test_schedules_projects(self) -> None: with self.feature( { - "organizations:gen-ai-features": [org.slug], "organizations:seer-explorer-index": [org.slug], } ): diff --git a/tests/sentry/tasks/seer/test_night_shift.py b/tests/sentry/tasks/seer/test_night_shift.py index 92403dd56963..38802db566b9 100644 --- a/tests/sentry/tasks/seer/test_night_shift.py +++ b/tests/sentry/tasks/seer/test_night_shift.py @@ -224,7 +224,6 @@ def test_dispatches_eligible_orgs(self) -> None: self.feature( { "organizations:seer-night-shift": [org.slug], - "organizations:gen-ai-features": [org.slug], "organizations:seat-based-seer-enabled": [org.slug], } ), @@ -254,7 +253,6 @@ def test_dispatches_with_run_options(self) -> None: self.feature( { "organizations:seer-night-shift": [org.slug], - "organizations:gen-ai-features": [org.slug], "organizations:seat-based-seer-enabled": [org.slug], } ), @@ -278,7 +276,6 @@ def test_redelivery_dispatches_same_schedule_id(self) -> None: self.feature( { "organizations:seer-night-shift": [org.slug], - "organizations:gen-ai-features": [org.slug], "organizations:seat-based-seer-enabled": [org.slug], } ), @@ -305,7 +302,6 @@ def test_skips_orgs_without_seat_based_seer(self) -> None: self.feature( { "organizations:seer-night-shift": [org.slug], - "organizations:gen-ai-features": [org.slug], # seat-based-seer-enabled intentionally omitted } ), @@ -327,7 +323,6 @@ def test_dispatches_legacy_orgs_when_enabled(self) -> None: self.feature( { "organizations:seer-night-shift": [org.slug], - "organizations:gen-ai-features": [org.slug], # seat-based-seer-enabled intentionally omitted } ), @@ -346,7 +341,6 @@ def test_skips_orgs_with_hidden_ai(self) -> None: self.feature( { "organizations:seer-night-shift": [org.slug], - "organizations:gen-ai-features": [org.slug], "organizations:seat-based-seer-enabled": [org.slug], } ), @@ -364,7 +358,6 @@ def test_skips_orgs_with_code_generation_disabled(self) -> None: self.feature( { "organizations:seer-night-shift": [org.slug], - "organizations:gen-ai-features": [org.slug], "organizations:seat-based-seer-enabled": [org.slug], } ), @@ -383,7 +376,6 @@ def test_skips_orgs_without_seer_project_repository(self) -> None: self.feature( { "organizations:seer-night-shift": [org.slug], - "organizations:gen-ai-features": [org.slug], "organizations:seat-based-seer-enabled": [org.slug], } ), @@ -685,9 +677,7 @@ def test_no_eligible_projects(self) -> None: org = self.create_organization() self.create_project(organization=org) - with ( - patch("sentry.tasks.seer.night_shift.cron.logger") as mock_logger, - ): + with patch("sentry.tasks.seer.night_shift.cron.logger") as mock_logger: run_night_shift_for_org(org.id) info_events = [call.args[0] for call in mock_logger.info.call_args_list] assert "night_shift.no_eligible_projects" in info_events @@ -733,8 +723,7 @@ def test_filters_recently_skipped_groups(self) -> None: mark_skipped(skipped_group.id) try: - with self.feature("organizations:gen-ai-features"): - run_night_shift_for_org(org.id) + run_night_shift_for_org(org.id) finally: redis_clusters.get("default").delete(skip_cache_key(skipped_group.id)) @@ -891,7 +880,6 @@ def test_chunking_preserves_order_across_even_shards(self) -> None: with ( self.options({"seer.night_shift.shard_size": 2}), - self.feature("organizations:gen-ai-features"), patch( "sentry.tasks.seer.night_shift.cron.fixability_score_strategy", return_value=scored, @@ -916,7 +904,6 @@ def test_chunking_single_shard_when_size_exceeds_count(self) -> None: with ( self.options({"seer.night_shift.shard_size": 10}), - self.feature("organizations:gen-ai-features"), patch( "sentry.tasks.seer.night_shift.cron.fixability_score_strategy", return_value=scored, @@ -938,7 +925,6 @@ def test_non_positive_shard_size_clamps_to_one(self) -> None: with ( self.options({"seer.night_shift.shard_size": 0}), - self.feature("organizations:gen-ai-features"), patch( "sentry.tasks.seer.night_shift.cron.fixability_score_strategy", return_value=scored, @@ -959,10 +945,7 @@ def test_dispatches_candidates_to_seer_feature(self) -> None: project, "fixable", seer_fixability_score=0.9, times_seen=5, priority=75 ) - with ( - self.feature("organizations:gen-ai-features"), - patch("sentry.seer.night_shift.delivery.trigger_autofix_agent") as mock_autofix, - ): + with patch("sentry.seer.night_shift.delivery.trigger_autofix_agent") as mock_autofix: run_night_shift_for_org(org.id) # Autofix is fired by Seer's pushed-back verdicts, not in-process. @@ -1000,8 +983,7 @@ def test_payload_carries_automation_tuning_for_legacy_orgs(self) -> None: ) self._store_event_and_update_group(project, "fixable", seer_fixability_score=0.9) - with self.feature("organizations:gen-ai-features"): - run_night_shift_for_org(org.id) + run_night_shift_for_org(org.id) _, body = _dispatched_feature_body(org) assert body["payload"]["candidates"][0]["automation_tuning"] == "high" @@ -1013,7 +995,6 @@ def test_payload_omits_automation_tuning_for_seat_based_orgs(self) -> None: self._store_event_and_update_group(project, "fixable", seer_fixability_score=0.9) with ( - self.feature("organizations:gen-ai-features"), patch( "sentry.tasks.seer.night_shift.cron.is_seer_seat_based_tier_enabled", return_value=True, @@ -1039,8 +1020,7 @@ def test_payload_carries_per_project_automation_tuning_within_one_org(self) -> N always, "always-fixable", seer_fixability_score=0.9 ) - with self.feature("organizations:gen-ai-features"): - run_night_shift_for_org(org.id) + run_night_shift_for_org(org.id) _, body = _dispatched_feature_body(org) tuning_by_group_id = { @@ -1063,7 +1043,6 @@ def test_allowed_project_slugs_gives_each_project_its_own_quota(self) -> None: ) with ( - self.feature("organizations:gen-ai-features"), self.options( { "seer.night_shift.org_tweaks": { @@ -1099,10 +1078,7 @@ def test_shards_candidates_across_feature_runs(self) -> None: for i in range(3) ] - with ( - self.options({"seer.night_shift.shard_size": 2}), - self.feature("organizations:gen-ai-features"), - ): + with self.options({"seer.night_shift.shard_size": 2}): run_night_shift_for_org(org.id) run = SeerWorkflowRun.objects.get(organization=org) @@ -1149,7 +1125,6 @@ def fail_second_dispatch(client, *args, **kwargs): with ( self.options({"seer.night_shift.shard_size": 1}), - self.feature("organizations:gen-ai-features"), patch( "sentry.tasks.seer.night_shift.cron.fixability_score_strategy", return_value=scored, @@ -1203,6 +1178,7 @@ def test_no_candidates_skips_dispatch(self) -> None: def test_no_seer_access_keeps_shard_plan_for_resume(self) -> None: org = self.create_organization() + org.update_option("sentry:hide_ai_features", True) project = self.create_project(organization=org) self._make_eligible(project) self._store_event_and_update_group( @@ -1234,7 +1210,6 @@ def test_dispatch_failure_records_error(self) -> None: ) with ( - self.feature("organizations:gen-ai-features"), patch( "sentry.seer.agent.client.SeerAgentClient.start_feature_run", side_effect=RuntimeError("boom"), @@ -1256,8 +1231,7 @@ def test_outbox_drain_mirrors_run_against_seer(self) -> None: project, "fixable", seer_fixability_score=0.9, times_seen=5 ) - with self.feature("organizations:gen-ai-features"): - run_night_shift_for_org(org.id) + run_night_shift_for_org(org.id) seer_run = SeerRun.objects.get(organization=org, type=SeerRunType.FEATURE_RUN) assert seer_run.mirror_status == SeerRunMirrorStatus.PENDING diff --git a/tests/sentry/tasks/test_llm_issue_detection.py b/tests/sentry/tasks/test_llm_issue_detection.py index 6302b60c1f66..e9796b96c835 100644 --- a/tests/sentry/tasks/test_llm_issue_detection.py +++ b/tests/sentry/tasks/test_llm_issue_detection.py @@ -4,6 +4,7 @@ import pytest from django.db.models import F +from django.test import override_settings from sentry.issues.grouptype import AIDetectedDBGroupType from sentry.models.project import Project @@ -24,7 +25,6 @@ ) from sentry.testutils.cases import APITransactionTestCase, SnubaTestCase, SpanTestCase, TestCase from sentry.testutils.helpers.datetime import before_now -from sentry.testutils.helpers.features import with_feature class LLMIssueDetectionTest(TestCase): @@ -42,7 +42,7 @@ def _budget_ok_response() -> Mock: response.data = b'{"has_budget": true}' return response - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.tasks.llm_issue_detection.detection.make_signed_seer_api_request") @patch("sentry.tasks.llm_issue_detection.detection.make_issue_detection_request") @patch( @@ -65,7 +65,7 @@ def test_detect_llm_issues_no_transactions( ) mock_seer_request.assert_not_called() - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.tasks.llm_issue_detection.detection.make_signed_seer_api_request") @patch("sentry.tasks.llm_issue_detection.trace_data.Spans.run_table_query") @patch("sentry.tasks.llm_issue_detection.detection.make_issue_detection_request") @@ -190,7 +190,7 @@ def test_general_type_skips_occurrence_creation(self, mock_produce_occurrence): ) assert not mock_produce_occurrence.called - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.tasks.llm_issue_detection.detection.make_signed_seer_api_request") @patch("sentry.tasks.llm_issue_detection.detection.make_issue_detection_request") @patch("sentry.tasks.llm_issue_detection.trace_data.Spans.run_table_query") @@ -237,7 +237,7 @@ def test_detect_llm_issues_full_flow( assert seer_request.organization_id == self.organization.id assert len(seer_request.traces) == 1 - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.tasks.llm_issue_detection.detection.make_signed_seer_api_request") @patch("sentry.tasks.llm_issue_detection.detection.make_issue_detection_request") @patch("sentry.tasks.llm_issue_detection.trace_data.Spans.run_table_query") @@ -272,7 +272,7 @@ def test_detect_llm_issues_seer_error_logged( assert mock_seer_request.call_count == 1 assert mock_logger_error.call_count == 1 - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.tasks.llm_issue_detection.detection.make_issue_detection_request") @patch( "sentry.tasks.llm_issue_detection.trace_data.get_project_top_transaction_traces_for_llm_detection" @@ -290,7 +290,7 @@ def test_check_budget_fail_open(self, mock_budget_request, mock_get_transactions mock_get_transactions.assert_called_once() - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.tasks.llm_issue_detection.detection.make_issue_detection_request") @patch( "sentry.tasks.llm_issue_detection.trace_data.get_project_top_transaction_traces_for_llm_detection" @@ -309,7 +309,7 @@ def test_check_budget_over_budget( mock_get_transactions.assert_not_called() mock_seer_request.assert_not_called() - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.tasks.llm_issue_detection.detection.make_issue_detection_request") @patch( "sentry.tasks.llm_issue_detection.trace_data.get_project_top_transaction_traces_for_llm_detection" @@ -330,7 +330,7 @@ def test_plan_tier_forwarded_to_seer( == f"{SEER_CHECK_BUDGET_ENDPOINT_PATH}/:organization_id" ) - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.tasks.llm_issue_detection.detection.make_issue_detection_request") @patch( "sentry.tasks.llm_issue_detection.trace_data.get_project_top_transaction_traces_for_llm_detection" @@ -347,7 +347,7 @@ def test_plan_tier_defaults_to_business( budget_url = mock_budget_request.call_args[0][1] assert "plan_tier=business" in budget_url - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.tasks.llm_issue_detection.detection.make_signed_seer_api_request") @patch("sentry.tasks.llm_issue_detection.detection.make_issue_detection_request") @patch( @@ -368,7 +368,7 @@ def test_traces_sent_per_plan_tier( assert len(seer_request.traces) == expected assert seer_request.plan_tier == plan_tier - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.tasks.llm_issue_detection.detection.make_signed_seer_api_request") @patch("sentry.tasks.llm_issue_detection.detection.make_issue_detection_request") @patch( @@ -393,7 +393,7 @@ def test_traces_per_invocation_option_override( class LLMIssueDetectionProjectFilterTest(TestCase): - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.tasks.llm_issue_detection.detection.make_signed_seer_api_request") @patch( "sentry.tasks.llm_issue_detection.trace_data.get_project_top_transaction_traces_for_llm_detection" diff --git a/tests/sentry/tasks/test_post_process.py b/tests/sentry/tasks/test_post_process.py index 9fee8c0e078e..98dc04bf8f6a 100644 --- a/tests/sentry/tasks/test_post_process.py +++ b/tests/sentry/tasks/test_post_process.py @@ -3054,7 +3054,7 @@ def test_step_is_skipped_by_fully_specified_condition( class KickOffSeerAutomationTestMixin(BasePostProcessGroupMixin): @patch("sentry.tasks.seer.autofix.generate_summary_and_run_automation.delay") - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) def test_kick_off_seer_automation_with_features(self, mock_generate_summary_and_run_automation): self.project.update_option("sentry:seer_scanner_automation", True) event = self.create_event( @@ -3073,8 +3073,9 @@ def test_kick_off_seer_automation_with_features(self, mock_generate_summary_and_ event.group.id, trigger_path="old_seer_automation" ) + @override_settings(SENTRY_SELF_HOSTED=True) @patch("sentry.tasks.seer.autofix.generate_summary_and_run_automation.delay") - def test_kick_off_seer_automation_without_org_feature( + def test_kick_off_seer_automation_when_self_hosted( self, mock_generate_summary_and_run_automation ): self.project.update_option("sentry:seer_scanner_automation", True) @@ -3092,7 +3093,7 @@ def test_kick_off_seer_automation_without_org_feature( mock_generate_summary_and_run_automation.assert_not_called() @patch("sentry.tasks.seer.autofix.generate_summary_and_run_automation.delay") - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) def test_kick_off_seer_automation_without_scanner_on( self, mock_generate_summary_and_run_automation ): @@ -3113,7 +3114,7 @@ def test_kick_off_seer_automation_without_scanner_on( mock_generate_summary_and_run_automation.assert_not_called() @patch("sentry.tasks.seer.autofix.generate_summary_and_run_automation.delay") - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) def test_kick_off_seer_automation_skips_existing_fixability_score( self, mock_generate_summary_and_run_automation ): @@ -3138,7 +3139,7 @@ def test_kick_off_seer_automation_skips_existing_fixability_score( mock_generate_summary_and_run_automation.assert_not_called() @patch("sentry.tasks.seer.autofix.generate_summary_and_run_automation.delay") - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) def test_kick_off_seer_automation_skips_existing_issue( self, mock_generate_summary_and_run_automation ): @@ -3162,7 +3163,7 @@ def test_kick_off_seer_automation_skips_existing_issue( mock_generate_summary_and_run_automation.assert_not_called() @patch("sentry.tasks.seer.autofix.generate_summary_and_run_automation.delay") - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) def test_kick_off_seer_automation_skips_with_existing_fixability_score( self, mock_generate_summary_and_run_automation ): @@ -3195,7 +3196,7 @@ def test_kick_off_seer_automation_skips_with_existing_fixability_score( @patch("sentry.seer.autofix.utils.is_seer_scanner_rate_limited") @patch("sentry.quotas.backend.check_seer_quota") @patch("sentry.tasks.seer.autofix.generate_summary_and_run_automation.delay") - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) def test_rate_limit_only_checked_after_all_other_checks_pass( self, mock_generate_summary_and_run_automation, @@ -3266,7 +3267,7 @@ def test_rate_limit_only_checked_after_all_other_checks_pass( mock_generate_summary_and_run_automation.assert_not_called() @patch("sentry.tasks.seer.autofix.generate_summary_and_run_automation.delay") - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) def test_kick_off_seer_automation_skips_when_lock_held( self, mock_generate_summary_and_run_automation ): @@ -3315,7 +3316,7 @@ def test_kick_off_seer_automation_skips_when_lock_held( ) @patch("sentry.tasks.seer.autofix.generate_summary_and_run_automation.delay") - @with_feature("organizations:gen-ai-features") + @override_settings(SENTRY_SELF_HOSTED=False) def test_kick_off_seer_automation_with_hide_ai_features_enabled( self, mock_generate_summary_and_run_automation ): @@ -3403,7 +3404,7 @@ def _seat_based_post_process(self, **group_overrides): return event @patch("sentry.tasks.seer.autofix.generate_issue_summary_only.delay") - @with_feature({"organizations:gen-ai-features": True}) + @override_settings(SENTRY_SELF_HOSTED=False) def test_seat_based_org_skips_old_issues( self, mock_generate_summary_only, mock_seat_based_tier ): @@ -3411,7 +3412,7 @@ def test_seat_based_org_skips_old_issues( mock_generate_summary_only.assert_not_called() @patch("sentry.tasks.seer.autofix.generate_issue_summary_only.delay") - @with_feature({"organizations:gen-ai-features": True}) + @override_settings(SENTRY_SELF_HOSTED=False) def test_seat_based_org_skips_when_fixability_exists( self, mock_generate_summary_only, mock_seat_based_tier ): @@ -3422,6 +3423,7 @@ def test_seat_based_org_skips_when_fixability_exists( class SeerAutomationHelperFunctionsTestMixin(BasePostProcessGroupMixin): """Unit tests for is_issue_eligible_for_seer_automation.""" + @override_settings(SENTRY_SELF_HOSTED=False) @patch("sentry.quotas.backend.check_seer_quota", return_value=True) @patch("sentry.features.has", return_value=True) def test_is_issue_eligible_for_seer_automation(self, mock_features_has, mock_has_budget): @@ -3447,12 +3449,11 @@ def test_is_issue_eligible_for_seer_automation(self, mock_features_has, mock_has mock_category.return_value = GroupCategory.FEEDBACK assert is_issue_eligible_for_seer_automation(group) is False - # Missing feature flag - mock_features_has.return_value = False - assert is_issue_eligible_for_seer_automation(group) is False + # Seer unavailable on self-hosted + with override_settings(SENTRY_SELF_HOSTED=True): + assert is_issue_eligible_for_seer_automation(group) is False # Hide AI features enabled - mock_features_has.return_value = True self.organization.update_option("sentry:hide_ai_features", True) assert is_issue_eligible_for_seer_automation(group) is False self.organization.update_option("sentry:hide_ai_features", False) @@ -3503,7 +3504,7 @@ class PostProcessGroupErrorTest( ): @patch("sentry.seer.autofix.utils.is_seer_seat_based_tier_enabled", return_value=True) @patch("sentry.tasks.seer.autofix.generate_issue_summary_only.delay") - @with_feature({"organizations:gen-ai-features": True}) + @override_settings(SENTRY_SELF_HOSTED=False) def test_seat_based_org_generates_summary_for_new_issues( self, mock_generate_summary_only, mock_seat_based_tier ): diff --git a/tests/sentry/tasks/test_web_vitals_issue_detection.py b/tests/sentry/tasks/test_web_vitals_issue_detection.py index 6086d7d0eaa0..3dc5dabec921 100644 --- a/tests/sentry/tasks/test_web_vitals_issue_detection.py +++ b/tests/sentry/tasks/test_web_vitals_issue_detection.py @@ -47,7 +47,6 @@ def test_run_detection_dispatches_sub_tasks_when_enabled(self, mock_delay): "issue-detection.web-vitals-detection.projects-allowlist": [project.id], } ), - self.feature("organizations:gen-ai-features"), ): run_web_vitals_issue_detection() @@ -65,7 +64,6 @@ def test_run_detection_skips_when_no_github_code_mappings(self, mock_delay): "issue-detection.web-vitals-detection.projects-allowlist": [project.id], } ), - self.feature("organizations:gen-ai-features"), ): run_web_vitals_issue_detection() @@ -81,7 +79,6 @@ def test_run_detection_skips_when_not_allowlisted(self, mock_delay): "issue-detection.web-vitals-detection.projects-allowlist": [], } ), - self.feature("organizations:gen-ai-features"), ): run_web_vitals_issue_detection() @@ -173,7 +170,6 @@ def test_run_detection_produces_occurrences(self, mock_produce_occurrence_to_kaf "issue-detection.web-vitals-detection.projects-allowlist": [project.id], } ), - self.feature("organizations:gen-ai-features"), TaskRunner(), ): run_web_vitals_issue_detection() @@ -291,7 +287,6 @@ def test_run_detection_groups_rendering_vitals(self, mock_produce_occurrence_to_ "issue-detection.web-vitals-detection.projects-allowlist": [project.id], } ), - self.feature("organizations:gen-ai-features"), TaskRunner(), ): run_web_vitals_issue_detection() @@ -377,7 +372,6 @@ def test_run_detection_does_not_produce_occurrences_for_existing_issues( "issue-detection.web-vitals-detection.projects-allowlist": [project.id], } ), - self.feature("organizations:gen-ai-features"), TaskRunner(), ): run_web_vitals_issue_detection() @@ -421,7 +415,6 @@ def test_run_detection_does_not_create_issue_on_insufficient_samples( "issue-detection.web-vitals-detection.projects-allowlist": [project.id], } ), - self.feature("organizations:gen-ai-features"), TaskRunner(), ): run_web_vitals_issue_detection() @@ -502,7 +495,6 @@ def test_run_detection_selects_trace_closest_to_p75_web_vital_value( "issue-detection.web-vitals-detection.projects-allowlist": [project.id], } ), - self.feature("organizations:gen-ai-features"), TaskRunner(), ): run_web_vitals_issue_detection() @@ -611,7 +603,6 @@ def test_run_detection_selects_trace_from_worst_score(self, mock_produce_occurre "issue-detection.web-vitals-detection.projects-allowlist": [project.id], } ), - self.feature("organizations:gen-ai-features"), TaskRunner(), ): run_web_vitals_issue_detection() @@ -644,7 +635,6 @@ def test_run_detection_does_not_run_for_project_when_user_has_disabled( "issue-detection.web-vitals-detection.projects-allowlist": [project.id], } ), - self.feature("organizations:gen-ai-features"), TaskRunner(), ): run_web_vitals_issue_detection()