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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/sentry/integrations/utils/external_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from sentry import features
from sentry.models.group import Group
from sentry.seer.seer_setup import has_seer_access
from sentry.seer.signed_seer_api import (
LlmGenerateRequest,
SeerViewerContext,
Expand Down Expand Up @@ -123,9 +124,7 @@ def maybe_generate_external_issue_details(
) -> GeneratedExternalIssueDetails:
organization = group.organization
empty_result = GeneratedExternalIssueDetails(title=None, description=None)
if not features.has("organizations:gen-ai-features", organization, actor=user):
return empty_result
if organization.get_option("sentry:hide_ai_features", False):
if not has_seer_access(organization, actor=user):
return empty_result
if not features.has("organizations:external-issues-ai-generate", organization, actor=user):
return empty_result
Expand Down
9 changes: 2 additions & 7 deletions src/sentry/seer/code_review/preflight.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

from sentry import features, options, quotas
from sentry.constants import (
HIDE_AI_FEATURES_DEFAULT,
DataCategory,
)
from sentry.integrations.services.integration.model import RpcIntegration
Expand All @@ -21,6 +20,7 @@
CodeReviewSettings,
RepositorySettings,
)
from sentry.seer.seer_setup import has_seer_access


class PreflightDenialReason(StrEnum):
Expand Down Expand Up @@ -78,12 +78,7 @@ def check(self) -> CodeReviewPreflightResult:
# -------------------------------------------------------------------------

def _check_legal_ai_consent(self) -> PreflightDenialReason | None:
has_gen_ai_flag = features.has("organizations:gen-ai-features", self.organization)
has_hidden_ai = self.organization.get_option(
"sentry:hide_ai_features", HIDE_AI_FEATURES_DEFAULT
)

if not has_gen_ai_flag or has_hidden_ai:
if not has_seer_access(self.organization):
return PreflightDenialReason.ORG_LEGAL_AI_CONSENT_NOT_GRANTED
return None

Expand Down
7 changes: 2 additions & 5 deletions src/sentry/tasks/llm_issue_detection/detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from sentry.models.project import Project
from sentry.net.http import connection_from_url
from sentry.seer.agent.utils import normalize_description
from sentry.seer.seer_setup import has_seer_access
from sentry.seer.signed_seer_api import SeerViewerContext, make_signed_seer_api_request
from sentry.tasks.base import instrumented_task
from sentry.taskworker.namespaces import issues_tasks
Expand Down Expand Up @@ -255,11 +256,7 @@ def _is_org_eligible(org_id: int) -> bool:
org = Organization.objects.get_from_cache(id=org_id)
except Organization.DoesNotExist:
return False
return (
features.has("organizations:ai-issue-detection", org)
and features.has("organizations:gen-ai-features", org)
and not org.get_option("sentry:hide_ai_features")
)
return features.has("organizations:ai-issue-detection", org) and has_seer_access(org)


@instrumented_task(
Expand Down
4 changes: 4 additions & 0 deletions src/sentry/tasks/seer/explorer_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from sentry.constants import ObjectStatus
from sentry.models.project import Project
from sentry.seer.models import SeerApiError
from sentry.seer.seer_setup import is_seer_available
from sentry.seer.signed_seer_api import (
AgentIndexProject,
AgentIndexRequest,
Expand Down Expand Up @@ -48,6 +49,9 @@ def get_seer_explorer_enabled_projects() -> Generator[tuple[int, int]]:
Yields:
Tuple of (project_id, organization_id)
"""
if not is_seer_available():
return
Comment on lines +52 to +53

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

new condition


projects = Project.objects.filter(status=ObjectStatus.ACTIVE).select_related("organization")
current_hour = django_timezone.now().hour

Expand Down
4 changes: 4 additions & 0 deletions src/sentry/tasks/seer/night_shift/cron.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
SeerWorkflowStrategy,
)
from sentry.seer.night_shift.models import NightShiftPayload, TriageCandidate, TriageTweaks
from sentry.seer.seer_setup import is_seer_available
from sentry.seer.workflows.schemas import WorkflowRunSource
from sentry.tasks.base import instrumented_task
from sentry.tasks.seer.night_shift.simple_triage import (
Expand Down Expand Up @@ -578,6 +579,9 @@ def _get_eligible_orgs_from_batch(
Check feature flags for a batch of orgs.
Returns orgs that have all required feature flags enabled.
"""
if not is_seer_available():
return []
Comment on lines +582 to +583

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

new condition


# enable_seer_coding off => night shift can't open a PR for the org.
enable_coding = OrganizationOption.objects.get_value_bulk(
orgs, "sentry:enable_seer_coding", ENABLE_SEER_CODING_DEFAULT
Expand Down
9 changes: 3 additions & 6 deletions src/sentry/tasks/web_vitals_issue_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
from collections.abc import Generator
from datetime import UTC, datetime, timedelta

from sentry import features, options
from sentry import options
from sentry.constants import ObjectStatus
from sentry.issue_detection.performance_detection import get_merged_settings
from sentry.models.project import Project
from sentry.search.eap.types import SearchResolverConfig
from sentry.search.events.types import SnubaParams
from sentry.seer.agent.utils import normalize_description
from sentry.seer.autofix.utils import get_autofix_repos_from_project_code_mappings
from sentry.seer.seer_setup import get_supported_scm_providers
from sentry.seer.seer_setup import get_supported_scm_providers, has_seer_access
from sentry.snuba.referrer import Referrer
from sentry.snuba.spans_rpc import Spans
from sentry.tasks.base import instrumented_task
Expand Down Expand Up @@ -340,10 +340,7 @@ def check_seer_setup_for_project(project: Project) -> bool:
Checks if a project and it's organization have the necessary Seer setup to detect web vitals issues.
The project must have seer feature flags, seer acknowledgement, and a supported SCM code mapping.
"""
if not features.has("organizations:gen-ai-features", project.organization):
return False

if project.organization.get_option("sentry:hide_ai_features"):
if not has_seer_access(project.organization):
return False

repos = get_autofix_repos_from_project_code_mappings(project)
Expand Down
2 changes: 2 additions & 0 deletions src/sentry/testutils/helpers/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import orjson
from django.http.response import HttpResponseBase
from django.test import override_settings

from sentry import options
from sentry.integrations.github.webhook import GitHubIntegrationsWebhookEndpoint
Expand Down Expand Up @@ -140,6 +141,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"}
Expand Down
5 changes: 2 additions & 3 deletions static/app/components/group/issueSeerBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import {IconSeer} from 'sentry/icons';
import {t} from 'sentry/locale';
import type {Group} from 'sentry/types/group';
import {areAiFeaturesAllowed} from 'sentry/utils/seer/areAiFeaturesAllowed';
import {useLocation} from 'sentry/utils/useLocation';
import {useOrganization} from 'sentry/utils/useOrganization';

Expand All @@ -25,9 +26,7 @@ export function IssueSeerBadge({group}: IssueSeerBadgeProps) {
const autofixRunExists = getAutofixRunExists(group);
const seerFixable = isIssueQuickFixable(group);
const showSeer =
organization.features.includes('gen-ai-features') &&
!organization.hideAiFeatures &&
(autofixRunExists || seerFixable);
areAiFeaturesAllowed(organization) && (autofixRunExists || seerFixable);

let seerTitle = null;
if (autofixRunExists && seerFixable) {
Expand Down
5 changes: 2 additions & 3 deletions static/app/components/groupMetaRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {IconChat} from 'sentry/icons';
import type {Group} from 'sentry/types/group';
import {getTitle} from 'sentry/utils/events';
import {projectCanLinkToReplay} from 'sentry/utils/replays/projectSupportsReplay';
import {areAiFeaturesAllowed} from 'sentry/utils/seer/areAiFeaturesAllowed';
import {useLocation} from 'sentry/utils/useLocation';
import {useOrganization} from 'sentry/utils/useOrganization';

Expand Down Expand Up @@ -73,9 +74,7 @@ export function GroupMetaRow({data, showLifetime = true}: Props) {
const autofixRunExists = getAutofixRunExists(data);
const seerFixable = isIssueQuickFixable(data);
const showSeer =
organization.features.includes('gen-ai-features') &&
!organization.hideAiFeatures &&
(autofixRunExists || seerFixable);
areAiFeaturesAllowed(organization) && (autofixRunExists || seerFixable);

const {subtitle} = getTitle(data);

Expand Down
5 changes: 2 additions & 3 deletions static/app/components/searchQueryBuilder/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type {SavedSearchType, TagCollection} from 'sentry/types/group';
import {defined} from 'sentry/utils/defined';
import {getFieldDefinition as defaultGetFieldDefinition} from 'sentry/utils/fields';
import {isEmptyObject} from 'sentry/utils/object/isEmptyObject';
import {areAiFeaturesAllowed} from 'sentry/utils/seer/areAiFeaturesAllowed';
import {useDimensions} from 'sentry/utils/useDimensions';
import {useOrganization} from 'sentry/utils/useOrganization';
import {usePrevious} from 'sentry/utils/usePrevious';
Expand Down Expand Up @@ -216,9 +217,7 @@ export function SearchQueryBuilderProvider({

const organization = useOrganization();
const enableAISearch =
Boolean(enableAISearchProp) &&
!organization.hideAiFeatures &&
organization.features.includes('gen-ai-features');
Boolean(enableAISearchProp) && areAiFeaturesAllowed(organization);
const defaultToAskSeerOnFreeTextSearch =
enableAISearch && Boolean(defaultToAskSeerOnFreeTextSearchProp);

Expand Down
6 changes: 2 additions & 4 deletions static/app/views/dashboards/manage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {trackAnalytics} from 'sentry/utils/analytics';
import {selectJsonWithHeaders} from 'sentry/utils/api/apiOptions';
import {dashboardsApiOptions} from 'sentry/utils/dashboards/dashboardsApiOptions';
import {decodeScalar} from 'sentry/utils/queryString';
import {areAiFeaturesAllowed} from 'sentry/utils/seer/areAiFeaturesAllowed';
import {normalizeUrl} from 'sentry/utils/url/normalizeUrl';
import {useApi} from 'sentry/utils/useApi';
import {useHasProjectAccess} from 'sentry/utils/useHasProjectAccess';
Expand Down Expand Up @@ -111,9 +112,6 @@ function ManageDashboards() {
? CUSTOM_DASHBOARD_LABEL
: t('All Dashboards');

const areAiFeaturesAllowed =
!organization.hideAiFeatures && organization.features.includes('gen-ai-features');

const {hasProjectAccess, projectsLoaded} = useHasProjectAccess();

const hasUserLastVisited = organization.features.includes(
Expand Down Expand Up @@ -283,7 +281,7 @@ function ManageDashboards() {
position="bottom-end"
data-test-id="sort-by-select"
/>
{areAiFeaturesAllowed ? (
{areAiFeaturesAllowed(organization) ? (
<DashboardCreateLimitWrapper>
{({
hasReachedDashboardLimit,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {areAiFeaturesAllowed} from 'sentry/utils/seer/areAiFeaturesAllowed';
import {useOrganization} from 'sentry/utils/useOrganization';

export function useUptimeAssertionFeatures() {
const organization = useOrganization();

const hasAiAssertionSuggestions =
organization.features.includes('uptime-ai-assertion-suggestions') &&
organization.features.includes('gen-ai-features') &&
!organization.hideAiFeatures;
areAiFeaturesAllowed(organization);

return {hasRuntimeAssertions: true, hasAiAssertionSuggestions};
}
7 changes: 2 additions & 5 deletions static/app/views/issueDetails/header/seerBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,15 @@ import {isIssueQuickFixable} from 'sentry/components/events/autofix/utils';
import {IconSeer} from 'sentry/icons';
import {t} from 'sentry/locale';
import type {Group} from 'sentry/types/group';
import {areAiFeaturesAllowed} from 'sentry/utils/seer/areAiFeaturesAllowed';
import {useOrganization} from 'sentry/utils/useOrganization';
import {Divider} from 'sentry/views/issueDetails/divider';

export function SeerBadge({group}: {group: Group}) {
const organization = useOrganization();
const seerFixable = isIssueQuickFixable(group);

if (
!organization.features.includes('gen-ai-features') ||
organization.hideAiFeatures ||
!seerFixable
) {
if (!areAiFeaturesAllowed(organization) || !seerFixable) {
return null;
}

Expand Down
6 changes: 2 additions & 4 deletions static/app/views/issueDetails/sidebar/seerDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {SeerDrawer} from 'sentry/components/events/autofix/v3/drawer';
import {t} from 'sentry/locale';
import type {Group} from 'sentry/types/group';
import type {Project} from 'sentry/types/project';
import {areAiFeaturesAllowed} from 'sentry/utils/seer/areAiFeaturesAllowed';
import {normalizeUrl} from 'sentry/utils/url/normalizeUrl';
import {useOrganization} from 'sentry/utils/useOrganization';

Expand All @@ -22,10 +23,7 @@ export const useOpenSeerDrawer = ({group, project}: {group: Group; project: Proj
const organization = useOrganization();

const openSeerDrawer = useCallback(() => {
if (
!organization.features.includes('gen-ai-features') ||
organization.hideAiFeatures
) {
if (!areAiFeaturesAllowed(organization)) {
return;
}

Expand Down
4 changes: 2 additions & 2 deletions static/app/views/issueDetails/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {Group, TeamParticipant, UserParticipant} from 'sentry/types/group';
import type {Project} from 'sentry/types/project';
import {DemoTourStep, SharedTourElement} from 'sentry/utils/demoMode/demoTours';
import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig';
import {areAiFeaturesAllowed} from 'sentry/utils/seer/areAiFeaturesAllowed';
import {useOrganization} from 'sentry/utils/useOrganization';
import {useUser} from 'sentry/utils/useUser';
import {ActivitySection} from 'sentry/views/issueDetails/activitySection';
Expand Down Expand Up @@ -60,8 +61,7 @@ export function IssueDetailsSidebar({group, event, project}: Props) {
// Check if Seer (AI features) will be shown - must match SeerSection's logic
// SeerSection shows "Seer" title when the issue type supports it AND AI features are allowed
const hasSeerFeatures =
organization.features.includes('gen-ai-features') &&
!organization.hideAiFeatures &&
areAiFeaturesAllowed(organization) &&
(issueTypeConfig.issueSummary.enabled || issueTypeConfig.autofix);
const showSeerSection = hasSeerFeatures || issueTypeConfig.resources;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {explorerAutofixApiOptions} from 'sentry/components/events/autofix/useExp
import {linkedPullRequestsApiOptions} from 'sentry/components/group/externalIssuesList/linkedPullRequests';
import type {Group} from 'sentry/types/group';
import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig';
import {areAiFeaturesAllowed} from 'sentry/utils/seer/areAiFeaturesAllowed';
import {useOrganization} from 'sentry/utils/useOrganization';
import {groupApiOptions} from 'sentry/views/issueDetails/useGroup';
import {useEnvironmentsFromUrl} from 'sentry/views/issueDetails/utils';
Expand Down Expand Up @@ -41,8 +42,7 @@ export function useInboxPreviewPrefetch(group: Group) {
});

const shouldPrefetchAutofix =
!organization.hideAiFeatures &&
organization.features.includes('gen-ai-features') &&
areAiFeaturesAllowed(organization) &&
getConfigForIssueType(group, group.project).autofix;
if (shouldPrefetchAutofix) {
void queryClient.prefetchQuery({
Expand Down
7 changes: 3 additions & 4 deletions static/app/views/seerExplorer/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {trackAnalytics} from 'sentry/utils/analytics';
import type {ApiQueryKey} from 'sentry/utils/api/apiQueryKey';
import {getApiUrl} from 'sentry/utils/api/getApiUrl';
import {getRouteStringFromRoutes} from 'sentry/utils/getRouteStringFromRoutes';
import {areAiFeaturesAllowed} from 'sentry/utils/seer/areAiFeaturesAllowed';
import {isUUID} from 'sentry/utils/string/isUUID';
import {useLocation} from 'sentry/utils/useLocation';
import {useMedia} from 'sentry/utils/useMedia';
Expand Down Expand Up @@ -767,9 +768,8 @@ export function getExplorerFeedbackOptions(
/**
* Checks if Seer Explorer is enabled for the organization.
* Requires the rollout flag and:
* - 'gen-ai-features' feature flag
* - AI features allowed for the organization (see areAiFeaturesAllowed)
* - Organization has not disabled open membership
* - Organization has not disabled AI features (hideAiFeatures is false)
*/
export function isSeerExplorerEnabled(organization: Organization | null): boolean {
if (!organization) {
Expand All @@ -778,8 +778,7 @@ export function isSeerExplorerEnabled(organization: Organization | null): boolea

return (
organization.openMembership &&
!organization.hideAiFeatures &&
organization.features.includes('gen-ai-features') &&
areAiFeaturesAllowed(organization) &&
organization.features.includes('seer-explorer')
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {LoadingError} from 'sentry/components/loadingError';
import {LoadingIndicator} from 'sentry/components/loadingIndicator';
import {t} from 'sentry/locale';
import {decodeScalar} from 'sentry/utils/queryString';
import {areAiFeaturesAllowed} from 'sentry/utils/seer/areAiFeaturesAllowed';
import {useLocation} from 'sentry/utils/useLocation';
import {useNavigate} from 'sentry/utils/useNavigate';
import {useOrganization} from 'sentry/utils/useOrganization';
Expand Down Expand Up @@ -35,8 +36,7 @@ export function useOverviewSeerDrawer() {
});
const openGroupIdRef = useRef<string | undefined>(undefined);

const hasSeerAccess =
organization.features.includes('gen-ai-features') && !organization.hideAiFeatures;
const hasSeerAccess = areAiFeaturesAllowed(organization);
const groupId = decodeScalar(location.query.seerDrawer);

useEffect(() => {
Expand Down
4 changes: 2 additions & 2 deletions static/app/views/settings/projectPerformance/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {t} from 'sentry/locale';
import type {Scope} from 'sentry/types/core';
import {isActiveSuperuser} from 'sentry/utils/isActiveSuperuser';
import {useDetailedProject} from 'sentry/utils/project/useDetailedProject';
import {areAiFeaturesAllowed} from 'sentry/utils/seer/areAiFeaturesAllowed';
import {useOrganization} from 'sentry/utils/useOrganization';
import {useParams} from 'sentry/utils/useParams';
import {useHasSeerWebVitalsSuggestions} from 'sentry/views/insights/browser/webVitals/utils/useHasSeerWebVitalsSuggestions';
Expand Down Expand Up @@ -65,9 +66,8 @@ export function ProjectPerformance() {

const hasWebVitalsSeerSuggestions = useHasSeerWebVitalsSuggestions(project);
const hasAIIssueDetection =
organization.features.includes('gen-ai-features') &&
organization.features.includes('ai-issue-detection') &&
!organization.hideAiFeatures;
areAiFeaturesAllowed(organization);

const {
data: threshold,
Expand Down
Loading
Loading