Skip to content

Commit e55db77

Browse files
jas-kascursoragentgetsantry[bot]michellewzhang
authored
feat(user feedback): update default title for tickets created from user feedback (#94889)
For external issues, the current default title "User Feedback" is generic and lacks context when integrated with external systems like JIRA, GitHub, or Linear. This PR updates the issue title to contain the first few words of the feedback message (by default, first 10 words). Fixes #76156 --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: getsantry[bot] <66042841+getsantry[bot]@users.noreply.github.com> Co-authored-by: Michelle Zhang <[email protected]>
1 parent dbdd535 commit e55db77

File tree

3 files changed

+107
-5
lines changed

3 files changed

+107
-5
lines changed

src/sentry/feedback/usecases/create_feedback.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,39 @@ def should_filter_feedback(
278278
return False, None
279279

280280

281+
def get_feedback_title(feedback_message: str, max_words: int = 10) -> str:
282+
"""
283+
Generate a descriptive title for user feedback issues.
284+
Format: "User Feedback: [first few words of message]"
285+
286+
Args:
287+
feedback_message: The user's feedback message
288+
max_words: Maximum number of words to include from the message
289+
290+
Returns:
291+
A formatted title string
292+
"""
293+
stripped_message = feedback_message.strip()
294+
295+
# Clean and split the message into words
296+
words = stripped_message.split()
297+
298+
if len(words) <= max_words:
299+
summary = stripped_message
300+
else:
301+
summary = " ".join(words[:max_words])
302+
if len(summary) < len(stripped_message):
303+
summary += "..."
304+
305+
title = f"User Feedback: {summary}"
306+
307+
# Truncate if necessary (keeping some buffer for external system limits)
308+
if len(title) > 200: # Conservative limit
309+
title = title[:197] + "..."
310+
311+
return title
312+
313+
281314
def create_feedback_issue(
282315
event: dict[str, Any], project_id: int, source: FeedbackCreationSource
283316
) -> dict[str, Any] | None:
@@ -343,7 +376,7 @@ def create_feedback_issue(
343376
event_id=event.get("event_id") or uuid4().hex,
344377
project_id=project_id,
345378
fingerprint=issue_fingerprint, # random UUID for fingerprint so feedbacks are grouped individually
346-
issue_title="User Feedback",
379+
issue_title=get_feedback_title(feedback_message),
347380
subtitle=feedback_message,
348381
resource_id=None,
349382
evidence_data=evidence_data,

tests/sentry/feedback/usecases/test_create_feedback.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
FeedbackCreationSource,
1515
create_feedback_issue,
1616
fix_for_issue_platform,
17+
get_feedback_title,
1718
is_in_feedback_denylist,
1819
shim_to_feedback,
1920
validate_issue_platform_event_schema,
@@ -1143,3 +1144,61 @@ def test_shim_to_feedback_missing_fields(default_project, monkeypatch):
11431144
report_dict, event, default_project, FeedbackCreationSource.USER_REPORT_ENVELOPE # type: ignore[arg-type]
11441145
)
11451146
assert mock_create_feedback_issue.call_count == 0
1147+
1148+
1149+
@django_db_all
1150+
def test_get_feedback_title():
1151+
"""Test the get_feedback_title function with various message types."""
1152+
1153+
# Test normal short message
1154+
assert get_feedback_title("Login button broken") == "User Feedback: Login button broken"
1155+
1156+
# Test message with exactly 10 words (default max_words)
1157+
message_10_words = "This is a test message with exactly ten words total"
1158+
assert get_feedback_title(message_10_words) == f"User Feedback: {message_10_words}"
1159+
1160+
# Test message with more than 10 words (should truncate)
1161+
long_message = "This is a very long feedback message that goes on and on and describes many different issues"
1162+
expected = "User Feedback: This is a very long feedback message that goes on..."
1163+
assert get_feedback_title(long_message) == expected
1164+
1165+
# Test very short message
1166+
assert get_feedback_title("Bug") == "User Feedback: Bug"
1167+
1168+
# Test custom max_words parameter
1169+
message = "This is a test with custom word limit"
1170+
assert get_feedback_title(message, max_words=3) == "User Feedback: This is a..."
1171+
1172+
# Test message that would create a title longer than 200 characters
1173+
very_long_message = "a" * 300 # 300 character message
1174+
result = get_feedback_title(very_long_message)
1175+
assert len(result) <= 200
1176+
assert result.endswith("...")
1177+
assert result.startswith("User Feedback: ")
1178+
1179+
# Test message with special characters
1180+
special_message = "The @login button doesn't work! It's broken & needs fixing."
1181+
expected_special = "User Feedback: The @login button doesn't work! It's broken & needs fixing."
1182+
assert get_feedback_title(special_message) == expected_special
1183+
1184+
1185+
@django_db_all
1186+
def test_create_feedback_issue_title(default_project, mock_produce_occurrence_to_kafka):
1187+
"""Test that create_feedback_issue uses the generated title."""
1188+
long_message = "This is a very long feedback message that describes multiple issues with the application including performance problems, UI bugs, and various other concerns that users are experiencing"
1189+
1190+
event = mock_feedback_event(default_project.id)
1191+
event["contexts"]["feedback"]["message"] = long_message
1192+
1193+
create_feedback_issue(event, default_project.id, FeedbackCreationSource.NEW_FEEDBACK_ENVELOPE)
1194+
1195+
assert mock_produce_occurrence_to_kafka.call_count == 1
1196+
1197+
call_args = mock_produce_occurrence_to_kafka.call_args
1198+
occurrence = call_args[1]["occurrence"]
1199+
1200+
# Check that the title is truncated properly
1201+
expected_title = (
1202+
"User Feedback: This is a very long feedback message that describes multiple..."
1203+
)
1204+
assert occurrence.issue_title == expected_title

tests/sentry/tasks/test_post_process.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from sentry.eventstore.models import Event
1919
from sentry.eventstore.processing import event_processing_store
2020
from sentry.eventstream.types import EventStreamEventType
21-
from sentry.feedback.usecases.create_feedback import FeedbackCreationSource
21+
from sentry.feedback.usecases.create_feedback import FeedbackCreationSource, get_feedback_title
2222
from sentry.integrations.models.integration import Integration
2323
from sentry.integrations.source_code_management.commit_context import CommitInfo, FileBlameInfo
2424
from sentry.issues.auto_source_code_config.utils.platform import get_supported_platforms
@@ -2076,6 +2076,11 @@ def test_user_report_shims_to_feedback(self, mock_produce_occurrence_to_kafka):
20762076
assert mock_event_data["contexts"]["feedback"]["associated_event_id"] == event.event_id
20772077
assert mock_event_data["level"] == "error"
20782078

2079+
occurrence = mock_produce_occurrence_to_kafka.call_args_list[0][1]["occurrence"]
2080+
assert occurrence.issue_title == get_feedback_title(
2081+
mock_event_data["contexts"]["feedback"]["message"]
2082+
)
2083+
20792084
@patch("sentry.feedback.usecases.create_feedback.produce_occurrence_to_kafka")
20802085
def test_user_reports_no_shim_if_group_exists_on_report(self, mock_produce_occurrence_to_kafka):
20812086
with self.feature("organizations:user-feedback-event-link-ingestion-changes"):
@@ -3190,6 +3195,9 @@ def create_event(
31903195
is_spam=False,
31913196
):
31923197
data["type"] = "generic"
3198+
if "message" not in data:
3199+
data["message"] = "It Broke!!!"
3200+
31933201
event = self.store_event(
31943202
data=data, project_id=project_id, assert_no_errors=assert_no_errors
31953203
)
@@ -3211,7 +3219,7 @@ def create_event(
32113219
**{
32123220
"id": uuid.uuid4().hex,
32133221
"fingerprint": ["c" * 32],
3214-
"issue_title": "User Feedback",
3222+
"issue_title": get_feedback_title(data["message"]),
32153223
"subtitle": "it was bad",
32163224
"culprit": "api/123",
32173225
"resource_id": "1234",
@@ -3351,7 +3359,7 @@ def test_not_ran_if_crash_report_setting_option_epoch_0(self):
33513359

33523360
def test_ran_if_default_on_new_projects(self):
33533361
event = self.create_event(
3354-
data={},
3362+
data={"message": "It Broke!!!"},
33553363
project_id=self.project.id,
33563364
feedback_type=FeedbackCreationSource.CRASH_REPORT_EMBED_FORM,
33573365
)
@@ -3372,10 +3380,11 @@ def test_ran_if_default_on_new_projects(self):
33723380
cache_key="total_rubbish",
33733381
)
33743382
assert mock_process_func.call_count == 1
3383+
assert event.occurrence.issue_title == "User Feedback: It Broke!!!"
33753384

33763385
def test_ran_if_crash_feedback_envelope(self):
33773386
event = self.create_event(
3378-
data={},
3387+
data={"message": "It Broke!!!"},
33793388
project_id=self.project.id,
33803389
feedback_type=FeedbackCreationSource.NEW_FEEDBACK_ENVELOPE,
33813390
)
@@ -3396,6 +3405,7 @@ def test_ran_if_crash_feedback_envelope(self):
33963405
cache_key="total_rubbish",
33973406
)
33983407
assert mock_process_func.call_count == 1
3408+
assert event.occurrence.issue_title == "User Feedback: It Broke!!!"
33993409

34003410
def test_logs_if_source_missing(self):
34013411
event = self.create_event(

0 commit comments

Comments
 (0)