Skip to content

feat(user feedback): update default title for tickets created from user feedback #94889

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
38 changes: 37 additions & 1 deletion src/sentry/feedback/usecases/create_feedback.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,42 @@ def should_filter_feedback(
return False, None


def generate_feedback_title(feedback_message: str, max_words: int = 10) -> str:
"""
Generate a descriptive title for user feedback issues.
Format: "User Feedback: [first few words of message]"

Args:
feedback_message: The user's feedback message
max_words: Maximum number of words to include from the message

Returns:
A formatted title string
"""
if not feedback_message or not feedback_message.strip():
return "User Feedback"

# Clean and split the message into words
words = feedback_message.strip().split()

# Take first few words, respecting word boundaries
if len(words) <= max_words:
summary = feedback_message.strip()
else:
summary = " ".join(words[:max_words])
if len(summary) < len(feedback_message.strip()):
summary += "..."

# Ensure the title isn't too long for external systems
title = f"User Feedback: {summary}"

# Truncate if necessary (keeping some buffer for external system limits)
if len(title) > 200: # Conservative limit
title = title[:197] + "..."

return title


def create_feedback_issue(
event: dict[str, Any], project_id: int, source: FeedbackCreationSource
) -> dict[str, Any] | None:
Expand Down Expand Up @@ -343,7 +379,7 @@ def create_feedback_issue(
event_id=event.get("event_id") or uuid4().hex,
project_id=project_id,
fingerprint=issue_fingerprint, # random UUID for fingerprint so feedbacks are grouped individually
issue_title="User Feedback",
issue_title=generate_feedback_title(feedback_message),
subtitle=feedback_message,
resource_id=None,
evidence_data=evidence_data,
Expand Down
109 changes: 109 additions & 0 deletions tests/sentry/feedback/usecases/test_create_feedback.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
FeedbackCreationSource,
create_feedback_issue,
fix_for_issue_platform,
generate_feedback_title,
is_in_feedback_denylist,
shim_to_feedback,
validate_issue_platform_event_schema,
Expand Down Expand Up @@ -1143,3 +1144,111 @@ def test_shim_to_feedback_missing_fields(default_project, monkeypatch):
report_dict, event, default_project, FeedbackCreationSource.USER_REPORT_ENVELOPE # type: ignore[arg-type]
)
assert mock_create_feedback_issue.call_count == 0


@django_db_all
def test_generate_feedback_title():
"""Test the generate_feedback_title function with various message types."""

# Test normal short message
assert generate_feedback_title("Login button broken") == "User Feedback: Login button broken"

# Test message with exactly 10 words (default max_words)
message_10_words = "This is a test message with exactly ten words total"
assert generate_feedback_title(message_10_words) == f"User Feedback: {message_10_words}"

# Test message with more than 10 words (should truncate)
long_message = "This is a very long feedback message that goes on and on and describes many different issues"
expected = "User Feedback: This is a very long feedback message that goes on and..."
assert generate_feedback_title(long_message) == expected

# Test very short message
assert generate_feedback_title("Bug") == "User Feedback: Bug"

# Test empty message
assert generate_feedback_title("") == "User Feedback"

# Test whitespace-only message
assert generate_feedback_title(" ") == "User Feedback"

# Test None message
assert generate_feedback_title(None) == "User Feedback"

# Test custom max_words parameter
message = "This is a test with custom word limit"
assert generate_feedback_title(message, max_words=3) == "User Feedback: This is a..."

# Test message that would create a title longer than 200 characters
very_long_message = "a" * 300 # 300 character message
result = generate_feedback_title(very_long_message)
assert len(result) <= 200
assert result.endswith("...")
assert result.startswith("User Feedback: ")

# Test message with special characters
special_message = "The @login button doesn't work! It's broken & needs fixing."
expected_special = "User Feedback: The @login button doesn't work! It's broken & needs fixing."
assert generate_feedback_title(special_message) == expected_special


@django_db_all
def test_create_feedback_issue_uses_generated_title(
default_project, mock_produce_occurrence_to_kafka
):
"""Test that create_feedback_issue uses the generated title instead of hardcoded 'User Feedback'."""

# Test with a simple message
event = mock_feedback_event(default_project.id)
event["contexts"]["feedback"]["message"] = "Login button is broken"

create_feedback_issue(event, default_project.id, FeedbackCreationSource.NEW_FEEDBACK_ENVELOPE)

# Verify the occurrence was created with the right title
assert mock_produce_occurrence_to_kafka.call_count == 1

# Get the occurrence from the call
call_args = mock_produce_occurrence_to_kafka.call_args
occurrence = call_args[1]["occurrence"]

# Check that the title is generated, not hardcoded
assert occurrence.issue_title == "User Feedback: Login button is broken"
assert occurrence.issue_title != "User Feedback"


@django_db_all
def test_create_feedback_issue_title_with_long_message(
default_project, mock_produce_occurrence_to_kafka
):
"""Test that long feedback messages are properly truncated in the title."""

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"

event = mock_feedback_event(default_project.id)
event["contexts"]["feedback"]["message"] = long_message

create_feedback_issue(event, default_project.id, FeedbackCreationSource.NEW_FEEDBACK_ENVELOPE)

# Verify the occurrence was created
assert mock_produce_occurrence_to_kafka.call_count == 1

# Get the occurrence from the call
call_args = mock_produce_occurrence_to_kafka.call_args
occurrence = call_args[1]["occurrence"]

# Check that the title is truncated properly
expected_title = (
"User Feedback: This is a very long feedback message that describes multiple issues..."
)
assert occurrence.issue_title == expected_title
assert len(occurrence.issue_title) <= 200


@django_db_all
def test_create_feedback_issue_title_with_empty_message(
default_project, mock_produce_occurrence_to_kafka
):
"""Test that empty feedback messages fall back to 'User Feedback'."""

# This test won't actually run since empty messages are filtered out,
# but we test the function directly above to ensure the fallback works
pass
Loading