Skip to content

Commit d7f82d9

Browse files
wedamijaclaude
andcommitted
chore: remove graduated user-feedback AI feature flags
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d035763 commit d7f82d9

File tree

10 files changed

+193
-324
lines changed

10 files changed

+193
-324
lines changed

src/sentry/features/temporary.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -463,16 +463,6 @@ def register_temporary_features(manager: FeatureManager) -> None:
463463
manager.add("organizations:uptime-response-capture", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False, default=True)
464464
# Enable runtime assertions for uptime monitors
465465
manager.add("organizations:uptime-runtime-assertions", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
466-
# Enable endpoint and frontend for AI-powered UF summaries
467-
manager.add("organizations:user-feedback-ai-summaries", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
468-
# Enable org-level caching for user feedback summaries and categorization
469-
manager.add("organizations:user-feedback-ai-summaries-cache", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE)
470-
# Enable label generation at ingest time for user feedbacks
471-
manager.add("organizations:user-feedback-ai-categorization", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
472-
# Enable the backend endpoint and frontend for categorizing user feedbacks
473-
manager.add("organizations:user-feedback-ai-categorization-features", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
474-
# Enable AI-powered title generation for user feedback
475-
manager.add("organizations:user-feedback-ai-titles", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
476466
# Enable auto spam classification at User Feedback ingest time
477467
manager.add("organizations:user-feedback-spam-ingest", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
478468
# Enable view hierarchies options

src/sentry/feedback/endpoints/organization_feedback_categories.py

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from rest_framework.response import Response
88
from urllib3 import Retry
99

10-
from sentry import features
1110
from sentry.api.api_owners import ApiOwner
1211
from sentry.api.api_publish_status import ApiPublishStatus
1312
from sentry.api.base import region_silo_endpoint
@@ -117,11 +116,7 @@ def get(self, request: Request, organization: Organization) -> Response:
117116
:auth: required
118117
"""
119118

120-
if not features.has(
121-
"organizations:user-feedback-ai-categorization-features",
122-
organization,
123-
actor=request.user,
124-
) or not has_seer_access(organization, actor=request.user):
119+
if not has_seer_access(organization, actor=request.user):
125120
return Response(
126121
{"detail": "AI categorization is not available for this organization."}, status=403
127122
)
@@ -149,20 +144,15 @@ def get(self, request: Request, organization: Organization) -> Response:
149144
# Day granularity date range. Date range is long enough that the categories won't change much (as long as the same day is selected)
150145
categorization_cache_key = f"feedback_categorization:{organization.id}:{start.strftime('%Y-%m-%d')}:{end.strftime('%Y-%m-%d')}:{hashed_project_ids}"
151146

152-
has_cache = features.has(
153-
"organizations:user-feedback-ai-summaries-cache", organization, actor=request.user
154-
)
155-
156-
if has_cache:
157-
cache_entry = cache.get(categorization_cache_key)
158-
if cache_entry:
159-
return Response(
160-
{
161-
"categories": cache_entry["categories"],
162-
"success": True,
163-
"numFeedbacksContext": cache_entry["numFeedbacksContext"],
164-
}
165-
)
147+
cache_entry = cache.get(categorization_cache_key)
148+
if cache_entry:
149+
return Response(
150+
{
151+
"categories": cache_entry["categories"],
152+
"success": True,
153+
"numFeedbacksContext": cache_entry["numFeedbacksContext"],
154+
}
155+
)
166156

167157
recent_feedbacks = query_recent_feedbacks_with_ai_labels(
168158
organization_id=organization.id,
@@ -327,12 +317,11 @@ def get(self, request: Request, organization: Organization) -> Response:
327317
categories.sort(key=lambda x: x["feedbackCount"], reverse=True)
328318
categories = categories[:MAX_RETURN_CATEGORIES]
329319

330-
if has_cache:
331-
cache.set(
332-
categorization_cache_key,
333-
{"categories": categories, "numFeedbacksContext": len(context_feedbacks)},
334-
timeout=CATEGORIES_CACHE_TIMEOUT,
335-
)
320+
cache.set(
321+
categorization_cache_key,
322+
{"categories": categories, "numFeedbacksContext": len(context_feedbacks)},
323+
timeout=CATEGORIES_CACHE_TIMEOUT,
324+
)
336325

337326
return Response(
338327
{

src/sentry/feedback/endpoints/organization_feedback_summary.py

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from rest_framework.response import Response
77
from urllib3 import Retry
88

9-
from sentry import features
109
from sentry.api.api_owners import ApiOwner
1110
from sentry.api.api_publish_status import ApiPublishStatus
1211
from sentry.api.base import region_silo_endpoint
@@ -90,9 +89,7 @@ def get(self, request: Request, organization: Organization) -> Response:
9089
:auth: required
9190
"""
9291

93-
if not features.has(
94-
"organizations:user-feedback-ai-summaries", organization, actor=request.user
95-
) or not has_seer_access(organization, actor=request.user):
92+
if not has_seer_access(organization, actor=request.user):
9693
return Response(
9794
{"detail": "AI summaries are not available for this organization."}, status=403
9895
)
@@ -113,22 +110,18 @@ def get(self, request: Request, organization: Organization) -> Response:
113110
project_ids = [str(project_id) for project_id in numeric_project_ids]
114111
hashed_project_ids = hash_from_values(project_ids)
115112

116-
has_cache = features.has(
117-
"organizations:user-feedback-ai-summaries-cache", organization, actor=request.user
118-
)
119113
summary_cache_key = f"feedback_summary:{organization.id}:{start.strftime('%Y-%m-%d-%H')}:{end.strftime('%Y-%m-%d-%H')}:{hashed_project_ids}"
120114

121-
if has_cache:
122-
# Hour granularity date range.
123-
summary_cache = cache.get(summary_cache_key)
124-
if summary_cache:
125-
return Response(
126-
{
127-
"summary": summary_cache["summary"],
128-
"success": True,
129-
"numFeedbacksUsed": summary_cache["numFeedbacksUsed"],
130-
}
131-
)
115+
# Hour granularity date range.
116+
summary_cache = cache.get(summary_cache_key)
117+
if summary_cache:
118+
return Response(
119+
{
120+
"summary": summary_cache["summary"],
121+
"success": True,
122+
"numFeedbacksUsed": summary_cache["numFeedbacksUsed"],
123+
}
124+
)
132125

133126
filters = {
134127
"type": FeedbackGroup.type_id,
@@ -172,12 +165,11 @@ def get(self, request: Request, organization: Organization) -> Response:
172165
{"detail": "Failed to generate a summary for a list of feedbacks"}, status=500
173166
)
174167

175-
if has_cache:
176-
cache.set(
177-
summary_cache_key,
178-
{"summary": summary, "numFeedbacksUsed": len(feedback_msgs)},
179-
timeout=SUMMARY_CACHE_TIMEOUT,
180-
)
168+
cache.set(
169+
summary_cache_key,
170+
{"summary": summary, "numFeedbacksUsed": len(feedback_msgs)},
171+
timeout=SUMMARY_CACHE_TIMEOUT,
172+
)
181173

182174
return Response(
183175
{

src/sentry/feedback/usecases/ingest/create_feedback.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import jsonschema
1010

11-
from sentry import features, options
11+
from sentry import options
1212
from sentry.constants import DataCategory
1313
from sentry.feedback.lib.utils import UNREAL_FEEDBACK_UNATTENDED_MESSAGE, FeedbackCreationSource
1414
from sentry.feedback.usecases.label_generation import (
@@ -298,9 +298,7 @@ def create_feedback_issue(
298298
)
299299
issue_fingerprint = [uuid4().hex]
300300

301-
use_ai_title = should_query_seer and features.has(
302-
"organizations:user-feedback-ai-titles", project.organization
303-
)
301+
use_ai_title = should_query_seer
304302
title = truncate_feedback_title(
305303
get_feedback_title(feedback_message, project.organization_id, use_ai_title)
306304
)
@@ -335,9 +333,7 @@ def create_feedback_issue(
335333
)
336334

337335
# Generating labels using Seer, which will later be used to categorize feedbacks
338-
if should_query_seer and features.has(
339-
"organizations:user-feedback-ai-categorization", project.organization
340-
):
336+
if should_query_seer:
341337
try:
342338
labels = generate_labels(feedback_message, project.organization_id)
343339
# This will rarely happen unless the user writes a really long feedback message

static/app/components/feedback/feedbackItem/feedbackItemUsername.spec.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ describe('FeedbackItemUsername', () => {
148148

149149
render(<FeedbackItemUsername feedbackIssue={issue} />, {
150150
organization: {
151-
features: ['user-feedback-ai-titles', 'gen-ai-features'],
151+
features: ['gen-ai-features'],
152152
},
153153
});
154154

@@ -168,17 +168,12 @@ describe('FeedbackItemUsername', () => {
168168
it.each([
169169
{
170170
description: 'AI features are disabled',
171-
features: ['user-feedback-ai-titles'],
172-
summary: 'Login issue with payment flow',
173-
},
174-
{
175-
description: 'user feedback AI titles are disabled',
176-
features: ['gen-ai-features'],
171+
features: [] as string[],
177172
summary: 'Login issue with payment flow',
178173
},
179174
{
180175
description: 'AI features enabled but summary is null',
181-
features: ['user-feedback-ai-titles', 'gen-ai-features'],
176+
features: ['gen-ai-features'],
182177
summary: null,
183178
},
184179
])(

static/app/components/feedback/feedbackItem/feedbackItemUsername.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ export default function FeedbackItemUsername({className, feedbackIssue, style}:
3232
const user = name && email && !isSameNameAndEmail ? `${name} <${email}>` : nameOrEmail;
3333

3434
const summary = feedbackIssue.metadata.summary;
35-
const isAiSummaryEnabled =
36-
areAiFeaturesAllowed && organization.features.includes('user-feedback-ai-titles');
35+
const isAiSummaryEnabled = areAiFeaturesAllowed;
3736

3837
const userNodeId = useId();
3938

static/app/components/feedback/summaryCategories/feedbackSummaryCategories.tsx

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,17 @@ import FeedbackSummary from 'sentry/components/feedback/summaryCategories/feedba
1010
import FeedbackButton from 'sentry/components/feedbackButton/feedbackButton';
1111
import {IconThumb} from 'sentry/icons';
1212
import {t} from 'sentry/locale';
13-
import useOrganization from 'sentry/utils/useOrganization';
1413
import {useSyncedLocalStorageState} from 'sentry/utils/useSyncedLocalStorageState';
1514

1615
export default function FeedbackSummaryCategories() {
17-
const organization = useOrganization();
18-
1916
const {areAiFeaturesAllowed} = useOrganizationSeerSetup();
2017

2118
const [isExpanded, setIsExpanded] = useSyncedLocalStorageState(
2219
'user-feedback-ai-summary-categories-expanded',
2320
true
2421
);
2522

26-
const showSummaryCategories =
27-
(organization.features.includes('user-feedback-ai-summaries') ||
28-
organization.features.includes('user-feedback-ai-categorization-features')) &&
29-
areAiFeaturesAllowed;
30-
31-
if (!showSummaryCategories) {
23+
if (!areAiFeaturesAllowed) {
3224
return null;
3325
}
3426

@@ -78,12 +70,8 @@ export default function FeedbackSummaryCategories() {
7870
</Disclosure.Title>
7971
<Disclosure.Content>
8072
<Stack gap="md" width="100%">
81-
{organization.features.includes('user-feedback-ai-summaries') && (
82-
<FeedbackSummary />
83-
)}
84-
{organization.features.includes(
85-
'user-feedback-ai-categorization-features'
86-
) && <FeedbackCategories />}
73+
<FeedbackSummary />
74+
<FeedbackCategories />
8775
</Stack>
8876
</Disclosure.Content>
8977
</Disclosure>

tests/sentry/feedback/endpoints/test_organization_feedback_categories.py

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ def setUp(self) -> None:
2222
self.org = self.organization
2323
self.project1 = self.project
2424
self.project2 = self.create_project(teams=[self.team])
25-
self.features = {
26-
"organizations:user-feedback-ai-categorization-features": True,
27-
}
2825
self.url = reverse(
2926
self.endpoint,
3027
kwargs={"organization_id_or_slug": self.org.slug},
@@ -74,15 +71,10 @@ def _create_feedback(
7471
)
7572
create_feedback_issue(event, project, FeedbackCreationSource.NEW_FEEDBACK_ENVELOPE)
7673

77-
def test_get_feedback_categories_without_feature_flag(self) -> None:
78-
response = self.get_error_response(self.org.slug)
79-
assert response.status_code == 403
80-
8174
def test_get_feedback_categories_without_seer_access(self) -> None:
8275
self.mock_has_seer_access.return_value = False
83-
with self.feature(self.features):
84-
response = self.get_error_response(self.org.slug)
85-
assert response.status_code == 403
76+
response = self.get_error_response(self.org.slug)
77+
assert response.status_code == 403
8678

8779
def test_get_feedback_categories_basic(self) -> None:
8880
self._create_feedback("a", ["User Interface", "Speed"], self.project1)
@@ -105,8 +97,7 @@ def test_get_feedback_categories_basic(self) -> None:
10597
},
10698
)
10799

108-
with self.feature(self.features):
109-
response = self.get_success_response(self.org.slug)
100+
response = self.get_success_response(self.org.slug)
110101

111102
assert response.data["success"] is True
112103
assert response.data["numFeedbacksContext"] == 4
@@ -148,8 +139,7 @@ def test_get_feedback_categories_with_project_filter(self) -> None:
148139
},
149140
)
150141

151-
with self.feature(self.features):
152-
response = self.get_success_response(self.org.slug, project=[self.project1.id])
142+
response = self.get_success_response(self.org.slug, project=[self.project1.id])
153143

154144
assert response.data["success"] is True
155145
assert response.data["numFeedbacksContext"] == 2
@@ -189,8 +179,7 @@ def test_max_group_labels_limit(self) -> None:
189179
},
190180
)
191181

192-
with self.feature(self.features):
193-
response = self.get_success_response(self.org.slug)
182+
response = self.get_success_response(self.org.slug)
194183

195184
assert response.data["success"] is True
196185
categories = response.data["categories"]
@@ -221,8 +210,7 @@ def test_filter_invalid_associated_labels_by_count_ratio(self) -> None:
221210
},
222211
)
223212

224-
with self.feature(self.features):
225-
response = self.get_success_response(self.org.slug)
213+
response = self.get_success_response(self.org.slug)
226214

227215
assert response.data["success"] is True
228216
categories = response.data["categories"]
@@ -235,8 +223,7 @@ def test_seer_request_error(self) -> None:
235223
self._create_feedback("a", ["User Interface", "Issues UI"], self.project1)
236224
self.mock_make_signed_seer_api_request.side_effect = Exception("seer failed")
237225

238-
with self.feature(self.features):
239-
response = self.get_error_response(self.org.slug)
226+
response = self.get_error_response(self.org.slug)
240227

241228
assert response.status_code == 500
242229
assert response.data["detail"] == "Failed to generate user feedback label groups"
@@ -248,8 +235,7 @@ def test_seer_http_errors(self) -> None:
248235
status=status, json_data={"detail": "seer failed"}
249236
)
250237

251-
with self.feature(self.features):
252-
response = self.get_error_response(self.org.slug)
238+
response = self.get_error_response(self.org.slug)
253239

254240
assert response.status_code == 500
255241
assert response.data["detail"] == "Failed to generate user feedback label groups"
@@ -263,8 +249,7 @@ def test_fallback_to_primary_labels_when_below_threshold(self) -> None:
263249
):
264250
self._create_feedback("a", ["User Interface", "Usability"], self.project1)
265251

266-
with self.feature(self.features):
267-
response = self.get_success_response(self.org.slug)
252+
response = self.get_success_response(self.org.slug)
268253

269254
assert self.mock_make_signed_seer_api_request.call_count == 0
270255

0 commit comments

Comments
 (0)