From ae7b08e94fb808728004b106d415a7723d468131 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Fri, 11 Apr 2025 18:39:46 -0700 Subject: [PATCH 1/7] Move shim to own file --- src/sentry/feedback/lib/utils.py | 0 .../feedback/usecases/create_feedback.py | 78 +---------------- .../feedback/usecases/shim_to_feedback.py | 87 +++++++++++++++++++ src/sentry/ingest/userreport.py | 2 +- src/sentry/tasks/post_process.py | 3 +- src/sentry/tasks/update_user_reports.py | 7 +- src/sentry/web/frontend/error_page_embed.py | 3 +- .../feedback/usecases/test_create_feedback.py | 2 +- 8 files changed, 96 insertions(+), 86 deletions(-) create mode 100644 src/sentry/feedback/lib/utils.py create mode 100644 src/sentry/feedback/usecases/shim_to_feedback.py diff --git a/src/sentry/feedback/lib/utils.py b/src/sentry/feedback/lib/utils.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/src/sentry/feedback/usecases/create_feedback.py b/src/sentry/feedback/usecases/create_feedback.py index 70e5afa4e707cf..7b4d44cb625e74 100644 --- a/src/sentry/feedback/usecases/create_feedback.py +++ b/src/sentry/feedback/usecases/create_feedback.py @@ -4,14 +4,13 @@ import random from datetime import UTC, datetime from enum import Enum -from typing import Any, TypedDict +from typing import Any from uuid import uuid4 import jsonschema from sentry import features, options from sentry.constants import DataCategory -from sentry.eventstore.models import Event, GroupEvent from sentry.feedback.usecases.spam_detection import is_spam, spam_detection_enabled from sentry.issues.grouptype import FeedbackGroup from sentry.issues.issue_occurrence import IssueEvidence, IssueOccurrence @@ -428,80 +427,5 @@ def auto_ignore_spam_feedbacks(project, issue_fingerprint): ) -########### -# Shim code -########### - - -class UserReportShimDict(TypedDict): - name: str - email: str - comments: str - event_id: str - level: str - - -def shim_to_feedback( - report: UserReportShimDict, - event: Event | GroupEvent, - project: Project, - source: FeedbackCreationSource, -): - """ - takes user reports from the legacy user report form/endpoint and - user reports that come from relay envelope ingestion and - creates a new User Feedback from it. - User feedbacks are an event type, so we try and grab as much from the - legacy user report and event to create the new feedback. - """ - if is_in_feedback_denylist(project.organization): - track_outcome( - org_id=project.organization_id, - project_id=project.id, - key_id=None, - outcome=Outcome.RATE_LIMITED, - reason="feedback_denylist", - timestamp=datetime.fromisoformat(event.timestamp), - event_id=event.event_id, - category=DataCategory.USER_REPORT_V2, - quantity=1, - ) - return - - try: - feedback_event: dict[str, Any] = { - "contexts": { - "feedback": { - "name": report.get("name", ""), - "contact_email": report["email"], - "message": report["comments"], - }, - }, - } - - feedback_event["contexts"]["feedback"]["associated_event_id"] = event.event_id - - if get_path(event.data, "contexts", "replay", "replay_id"): - feedback_event["contexts"]["replay"] = event.data["contexts"]["replay"] - feedback_event["contexts"]["feedback"]["replay_id"] = event.data["contexts"]["replay"][ - "replay_id" - ] - - if get_path(event.data, "contexts", "trace", "trace_id"): - feedback_event["contexts"]["trace"] = event.data["contexts"]["trace"] - - feedback_event["timestamp"] = event.datetime.timestamp() - feedback_event["platform"] = event.platform - feedback_event["level"] = event.data["level"] - feedback_event["environment"] = event.get_environment().name - feedback_event["tags"] = [list(item) for item in event.tags] - - # Entrypoint for "new" (issue platform based) feedback. This emits outcomes. - create_feedback_issue(feedback_event, project.id, source) - except Exception: - logger.exception("Error attempting to create new user feedback by shimming a user report") - metrics.incr("feedback.shim_to_feedback.failed", tags={"referrer": source.value}) - - def is_in_feedback_denylist(organization): return organization.slug in options.get("feedback.organizations.slug-denylist") diff --git a/src/sentry/feedback/usecases/shim_to_feedback.py b/src/sentry/feedback/usecases/shim_to_feedback.py new file mode 100644 index 00000000000000..7151962e76758d --- /dev/null +++ b/src/sentry/feedback/usecases/shim_to_feedback.py @@ -0,0 +1,87 @@ +import logging +from datetime import datetime +from typing import Any, TypedDict + +from sentry.constants import DataCategory +from sentry.eventstore.models import Event, GroupEvent +from sentry.feedback.usecases.create_feedback import ( + FeedbackCreationSource, + create_feedback_issue, + is_in_feedback_denylist, +) +from sentry.models.project import Project +from sentry.utils import metrics +from sentry.utils.outcomes import Outcome, track_outcome +from sentry.utils.safe import get_path + +logger = logging.getLogger(__name__) + + +class UserReportShimDict(TypedDict): + name: str + email: str + comments: str + event_id: str + level: str + + +def shim_to_feedback( + report: UserReportShimDict, + event: Event | GroupEvent, + project: Project, + source: FeedbackCreationSource, +): + """ + takes user reports from the legacy user report form/endpoint and + user reports that come from relay envelope ingestion and + creates a new User Feedback from it. + User feedbacks are an event type, so we try and grab as much from the + legacy user report and event to create the new feedback. + """ + if is_in_feedback_denylist(project.organization): + track_outcome( + org_id=project.organization_id, + project_id=project.id, + key_id=None, + outcome=Outcome.RATE_LIMITED, + reason="feedback_denylist", + timestamp=datetime.fromisoformat(event.timestamp), + event_id=event.event_id, + category=DataCategory.USER_REPORT_V2, + quantity=1, + ) + return + + try: + feedback_event: dict[str, Any] = { + "contexts": { + "feedback": { + "name": report.get("name", ""), + "contact_email": report["email"], + "message": report["comments"], + }, + }, + } + + feedback_event["contexts"]["feedback"]["associated_event_id"] = event.event_id + + if get_path(event.data, "contexts", "replay", "replay_id"): + feedback_event["contexts"]["replay"] = event.data["contexts"]["replay"] + feedback_event["contexts"]["feedback"]["replay_id"] = event.data["contexts"]["replay"][ + "replay_id" + ] + + if get_path(event.data, "contexts", "trace", "trace_id"): + feedback_event["contexts"]["trace"] = event.data["contexts"]["trace"] + + feedback_event["timestamp"] = event.datetime.timestamp() + feedback_event["platform"] = event.platform + feedback_event["level"] = event.data["level"] + feedback_event["environment"] = event.get_environment().name + feedback_event["tags"] = [list(item) for item in event.tags] + + # Entrypoint for "new" (issue platform based) feedback. This emits outcomes. + create_feedback_issue(feedback_event, project.id, source) + except Exception: + logger.exception("Error attempting to create new user feedback by shimming a user report") + metrics.incr("feedback.shim_to_feedback.failed", tags={"referrer": source.value}) diff --git a/src/sentry/ingest/userreport.py b/src/sentry/ingest/userreport.py index d97dd4e84b7878..41cfd079ff305f 100644 --- a/src/sentry/ingest/userreport.py +++ b/src/sentry/ingest/userreport.py @@ -14,8 +14,8 @@ UNREAL_FEEDBACK_UNATTENDED_MESSAGE, FeedbackCreationSource, is_in_feedback_denylist, - shim_to_feedback, ) +from sentry.feedback.usecases.shim_to_feedback import shim_to_feedback from sentry.models.project import Project from sentry.models.userreport import UserReport from sentry.signals import user_feedback_received diff --git a/src/sentry/tasks/post_process.py b/src/sentry/tasks/post_process.py index 59493e52eb6c6e..628ead8535f827 100644 --- a/src/sentry/tasks/post_process.py +++ b/src/sentry/tasks/post_process.py @@ -1363,7 +1363,8 @@ def check_has_high_priority_alerts(job: PostProcessJob) -> None: def link_event_to_user_report(job: PostProcessJob) -> None: - from sentry.feedback.usecases.create_feedback import FeedbackCreationSource, shim_to_feedback + from sentry.feedback.usecases.create_feedback import FeedbackCreationSource + from sentry.feedback.usecases.shim_to_feedback import shim_to_feedback from sentry.models.userreport import UserReport event = job["event"] diff --git a/src/sentry/tasks/update_user_reports.py b/src/sentry/tasks/update_user_reports.py index 0075f46dd8e02e..79f455c9d3b9bd 100644 --- a/src/sentry/tasks/update_user_reports.py +++ b/src/sentry/tasks/update_user_reports.py @@ -6,11 +6,8 @@ from django.utils import timezone from sentry import eventstore, quotas -from sentry.feedback.usecases.create_feedback import ( - FeedbackCreationSource, - is_in_feedback_denylist, - shim_to_feedback, -) +from sentry.feedback.usecases.create_feedback import FeedbackCreationSource, is_in_feedback_denylist +from sentry.feedback.usecases.shim_to_feedback import shim_to_feedback from sentry.models.project import Project from sentry.models.userreport import UserReport from sentry.silo.base import SiloMode diff --git a/src/sentry/web/frontend/error_page_embed.py b/src/sentry/web/frontend/error_page_embed.py index 702f0447b0b3fb..10c4ccde20538a 100644 --- a/src/sentry/web/frontend/error_page_embed.py +++ b/src/sentry/web/frontend/error_page_embed.py @@ -12,7 +12,8 @@ from django.views.generic import View from sentry import eventstore -from sentry.feedback.usecases.create_feedback import FeedbackCreationSource, shim_to_feedback +from sentry.feedback.usecases.create_feedback import FeedbackCreationSource +from sentry.feedback.usecases.shim_to_feedback import shim_to_feedback from sentry.models.options.project_option import ProjectOption from sentry.models.project import Project from sentry.models.projectkey import ProjectKey diff --git a/tests/sentry/feedback/usecases/test_create_feedback.py b/tests/sentry/feedback/usecases/test_create_feedback.py index f02afaec9a0c39..30af92409f05b2 100644 --- a/tests/sentry/feedback/usecases/test_create_feedback.py +++ b/tests/sentry/feedback/usecases/test_create_feedback.py @@ -15,9 +15,9 @@ create_feedback_issue, fix_for_issue_platform, is_in_feedback_denylist, - shim_to_feedback, validate_issue_platform_event_schema, ) +from sentry.feedback.usecases.shim_to_feedback import shim_to_feedback from sentry.models.group import Group, GroupStatus from sentry.testutils.helpers import Feature from sentry.testutils.pytest.fixtures import django_db_all From 03d1e1266c376566e9676c5aea32693df825b475 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Fri, 11 Apr 2025 18:42:16 -0700 Subject: [PATCH 2/7] Move shim tests --- .../feedback/usecases/test_create_feedback.py | 48 ----------------- .../usecases/test_shim_to_feedback.py | 51 +++++++++++++++++++ 2 files changed, 51 insertions(+), 48 deletions(-) create mode 100644 tests/sentry/feedback/usecases/test_shim_to_feedback.py diff --git a/tests/sentry/feedback/usecases/test_create_feedback.py b/tests/sentry/feedback/usecases/test_create_feedback.py index 30af92409f05b2..e24f6ee34abf63 100644 --- a/tests/sentry/feedback/usecases/test_create_feedback.py +++ b/tests/sentry/feedback/usecases/test_create_feedback.py @@ -9,7 +9,6 @@ from openai.types.chat.chat_completion import ChatCompletion, Choice from openai.types.chat.chat_completion_message import ChatCompletionMessage -from sentry.eventstore.models import Event from sentry.feedback.usecases.create_feedback import ( FeedbackCreationSource, create_feedback_issue, @@ -17,7 +16,6 @@ is_in_feedback_denylist, validate_issue_platform_event_schema, ) -from sentry.feedback.usecases.shim_to_feedback import shim_to_feedback from sentry.models.group import Group, GroupStatus from sentry.testutils.helpers import Feature from sentry.testutils.pytest.fixtures import django_db_all @@ -866,52 +864,6 @@ def test_create_feedback_evidence_has_spam( assert evidence["is_spam"] is True -""" -Unit tests for shim_to_feedback error cases. The typical behavior of this function is tested in -test_project_user_reports, test_post_process, and test_update_user_reports. -""" - - -@django_db_all -def test_shim_to_feedback_missing_event(default_project, monkeypatch): - # Not allowing this since creating feedbacks with no environment (copied from the associated event) doesn't work well. - mock_create_feedback_issue = Mock() - monkeypatch.setattr( - "sentry.feedback.usecases.create_feedback.create_feedback_issue", mock_create_feedback_issue - ) - report_dict = { - "name": "andrew", - "email": "aliu@example.com", - "comments": "Shim this", - "event_id": "a" * 32, - "level": "error", - } - shim_to_feedback( - report_dict, None, default_project, FeedbackCreationSource.USER_REPORT_ENVELOPE # type: ignore[arg-type] - ) - # Error is handled: - assert mock_create_feedback_issue.call_count == 0 - - -@django_db_all -def test_shim_to_feedback_missing_fields(default_project, monkeypatch): - # Email and comments are required to shim. Tests key errors are handled. - mock_create_feedback_issue = Mock() - monkeypatch.setattr( - "sentry.feedback.usecases.create_feedback.create_feedback_issue", mock_create_feedback_issue - ) - report_dict = { - "name": "andrew", - "event_id": "a" * 32, - "level": "error", - } - event = Event(event_id="a" * 32, project_id=default_project.id) - shim_to_feedback( - 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_denylist(set_sentry_option, default_project): with set_sentry_option( diff --git a/tests/sentry/feedback/usecases/test_shim_to_feedback.py b/tests/sentry/feedback/usecases/test_shim_to_feedback.py new file mode 100644 index 00000000000000..237411849c1d06 --- /dev/null +++ b/tests/sentry/feedback/usecases/test_shim_to_feedback.py @@ -0,0 +1,51 @@ +from unittest.mock import Mock + +from sentry.eventstore.models import Event +from sentry.feedback.usecases.create_feedback import FeedbackCreationSource +from sentry.feedback.usecases.shim_to_feedback import shim_to_feedback +from sentry.testutils.pytest.fixtures import django_db_all + +""" +Unit tests for shim_to_feedback error cases. The typical behavior of this function is tested in +test_project_user_reports, test_post_process, and test_update_user_reports. +""" + + +@django_db_all +def test_shim_to_feedback_missing_event(default_project, monkeypatch): + # Not allowing this since creating feedbacks with no environment (copied from the associated event) doesn't work well. + mock_create_feedback_issue = Mock() + monkeypatch.setattr( + "sentry.feedback.usecases.create_feedback.create_feedback_issue", mock_create_feedback_issue + ) + report_dict = { + "name": "andrew", + "email": "aliu@example.com", + "comments": "Shim this", + "event_id": "a" * 32, + "level": "error", + } + shim_to_feedback( + report_dict, None, default_project, FeedbackCreationSource.USER_REPORT_ENVELOPE # type: ignore[arg-type] + ) + # Error is handled: + assert mock_create_feedback_issue.call_count == 0 + + +@django_db_all +def test_shim_to_feedback_missing_fields(default_project, monkeypatch): + # Email and comments are required to shim. Tests key errors are handled. + mock_create_feedback_issue = Mock() + monkeypatch.setattr( + "sentry.feedback.usecases.create_feedback.create_feedback_issue", mock_create_feedback_issue + ) + report_dict = { + "name": "andrew", + "event_id": "a" * 32, + "level": "error", + } + event = Event(event_id="a" * 32, project_id=default_project.id) + shim_to_feedback( + report_dict, event, default_project, FeedbackCreationSource.USER_REPORT_ENVELOPE # type: ignore[arg-type] + ) + assert mock_create_feedback_issue.call_count == 0 From 351c39cd84213f69cc191bc747e1718223e6152c Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Fri, 11 Apr 2025 18:52:33 -0700 Subject: [PATCH 3/7] Move FeedbackCreationSource, unattended msg const, and is_in_feedback_denylist --- .../api/endpoints/project_user_reports.py | 2 +- src/sentry/feedback/lib/utils.py | 39 +++++++++++++++++++ .../feedback/usecases/create_feedback.py | 37 +----------------- .../feedback/usecases/shim_to_feedback.py | 7 +--- src/sentry/ingest/consumer/processors.py | 2 +- src/sentry/ingest/userreport.py | 2 +- src/sentry/tasks/post_process.py | 4 +- src/sentry/tasks/store.py | 3 +- src/sentry/tasks/update_user_reports.py | 2 +- src/sentry/utils/mockdata/core.py | 2 +- src/sentry/web/frontend/error_page_embed.py | 2 +- .../test_organization_user_reports.py | 2 +- .../feedback/usecases/test_create_feedback.py | 3 +- .../usecases/test_shim_to_feedback.py | 2 +- tests/sentry/ingest/test_userreport.py | 5 +-- .../test_organization_group_index.py | 3 +- tests/sentry/tasks/test_post_process.py | 2 +- 17 files changed, 59 insertions(+), 60 deletions(-) diff --git a/src/sentry/api/endpoints/project_user_reports.py b/src/sentry/api/endpoints/project_user_reports.py index f4f99bee554a8c..2e18b31127c9a1 100644 --- a/src/sentry/api/endpoints/project_user_reports.py +++ b/src/sentry/api/endpoints/project_user_reports.py @@ -14,7 +14,7 @@ from sentry.api.helpers.user_reports import user_reports_filter_to_unresolved from sentry.api.paginator import DateTimePaginator from sentry.api.serializers import UserReportWithGroupSerializer, serialize -from sentry.feedback.usecases.create_feedback import FeedbackCreationSource +from sentry.feedback.lib.utils import FeedbackCreationSource from sentry.ingest.userreport import Conflict, save_userreport from sentry.models.environment import Environment from sentry.models.userreport import UserReport diff --git a/src/sentry/feedback/lib/utils.py b/src/sentry/feedback/lib/utils.py index e69de29bb2d1d6..28a1a7b067b65f 100644 --- a/src/sentry/feedback/lib/utils.py +++ b/src/sentry/feedback/lib/utils.py @@ -0,0 +1,39 @@ +from enum import Enum + +from sentry import options +from sentry.models.organization import Organization + +UNREAL_FEEDBACK_UNATTENDED_MESSAGE = "Sent in the unattended mode" + + +class FeedbackCreationSource(Enum): + NEW_FEEDBACK_ENVELOPE = "new_feedback_envelope" + USER_REPORT_DJANGO_ENDPOINT = "user_report_sentry_django_endpoint" + USER_REPORT_ENVELOPE = "user_report_envelope" + CRASH_REPORT_EMBED_FORM = "crash_report_embed_form" + UPDATE_USER_REPORTS_TASK = "update_user_reports_task" + + @classmethod + def new_feedback_category_values(cls) -> set[str]: + return { + c.value + for c in [ + cls.NEW_FEEDBACK_ENVELOPE, + ] + } + + @classmethod + def old_feedback_category_values(cls) -> set[str]: + return { + c.value + for c in [ + cls.CRASH_REPORT_EMBED_FORM, + cls.USER_REPORT_ENVELOPE, + cls.USER_REPORT_DJANGO_ENDPOINT, + cls.UPDATE_USER_REPORTS_TASK, + ] + } + + +def is_in_feedback_denylist(organization: Organization) -> bool: + return organization.slug in options.get("feedback.organizations.slug-denylist") diff --git a/src/sentry/feedback/usecases/create_feedback.py b/src/sentry/feedback/usecases/create_feedback.py index 7b4d44cb625e74..560f7266f71894 100644 --- a/src/sentry/feedback/usecases/create_feedback.py +++ b/src/sentry/feedback/usecases/create_feedback.py @@ -3,7 +3,6 @@ import logging import random from datetime import UTC, datetime -from enum import Enum from typing import Any from uuid import uuid4 @@ -11,6 +10,7 @@ from sentry import features, options from sentry.constants import DataCategory +from sentry.feedback.lib.utils import UNREAL_FEEDBACK_UNATTENDED_MESSAGE, FeedbackCreationSource from sentry.feedback.usecases.spam_detection import is_spam, spam_detection_enabled from sentry.issues.grouptype import FeedbackGroup from sentry.issues.issue_occurrence import IssueEvidence, IssueOccurrence @@ -27,37 +27,6 @@ logger = logging.getLogger(__name__) -UNREAL_FEEDBACK_UNATTENDED_MESSAGE = "Sent in the unattended mode" - - -class FeedbackCreationSource(Enum): - NEW_FEEDBACK_ENVELOPE = "new_feedback_envelope" - USER_REPORT_DJANGO_ENDPOINT = "user_report_sentry_django_endpoint" - USER_REPORT_ENVELOPE = "user_report_envelope" - CRASH_REPORT_EMBED_FORM = "crash_report_embed_form" - UPDATE_USER_REPORTS_TASK = "update_user_reports_task" - - @classmethod - def new_feedback_category_values(cls) -> set[str]: - return { - c.value - for c in [ - cls.NEW_FEEDBACK_ENVELOPE, - ] - } - - @classmethod - def old_feedback_category_values(cls) -> set[str]: - return { - c.value - for c in [ - cls.CRASH_REPORT_EMBED_FORM, - cls.USER_REPORT_ENVELOPE, - cls.USER_REPORT_DJANGO_ENDPOINT, - cls.UPDATE_USER_REPORTS_TASK, - ] - } - def make_evidence(feedback, source: FeedbackCreationSource, is_message_spam: bool | None): evidence_data = {} @@ -425,7 +394,3 @@ def auto_ignore_spam_feedbacks(project, issue_fingerprint): new_substatus=GroupSubStatus.FOREVER, ), ) - - -def is_in_feedback_denylist(organization): - return organization.slug in options.get("feedback.organizations.slug-denylist") diff --git a/src/sentry/feedback/usecases/shim_to_feedback.py b/src/sentry/feedback/usecases/shim_to_feedback.py index 7151962e76758d..fb0b6196abdf0c 100644 --- a/src/sentry/feedback/usecases/shim_to_feedback.py +++ b/src/sentry/feedback/usecases/shim_to_feedback.py @@ -4,11 +4,8 @@ from sentry.constants import DataCategory from sentry.eventstore.models import Event, GroupEvent -from sentry.feedback.usecases.create_feedback import ( - FeedbackCreationSource, - create_feedback_issue, - is_in_feedback_denylist, -) +from sentry.feedback.lib.utils import FeedbackCreationSource +from sentry.feedback.usecases.create_feedback import create_feedback_issue, is_in_feedback_denylist from sentry.models.project import Project from sentry.utils import metrics from sentry.utils.outcomes import Outcome, track_outcome diff --git a/src/sentry/ingest/consumer/processors.py b/src/sentry/ingest/consumer/processors.py index 56959ce32418a3..5abd64a9e89ec3 100644 --- a/src/sentry/ingest/consumer/processors.py +++ b/src/sentry/ingest/consumer/processors.py @@ -14,7 +14,7 @@ from sentry.attachments import CachedAttachment, attachment_cache from sentry.event_manager import EventManager, save_attachment from sentry.eventstore.processing import event_processing_store, transaction_processing_store -from sentry.feedback.usecases.create_feedback import FeedbackCreationSource, is_in_feedback_denylist +from sentry.feedback.lib.utils import FeedbackCreationSource, is_in_feedback_denylist from sentry.ingest.types import ConsumerType from sentry.ingest.userreport import Conflict, save_userreport from sentry.killswitches import killswitch_matches_context diff --git a/src/sentry/ingest/userreport.py b/src/sentry/ingest/userreport.py index 41cfd079ff305f..ebd37922f15163 100644 --- a/src/sentry/ingest/userreport.py +++ b/src/sentry/ingest/userreport.py @@ -10,7 +10,7 @@ from sentry import eventstore, options from sentry.constants import DataCategory from sentry.eventstore.models import Event, GroupEvent -from sentry.feedback.usecases.create_feedback import ( +from sentry.feedback.lib.utils import ( UNREAL_FEEDBACK_UNATTENDED_MESSAGE, FeedbackCreationSource, is_in_feedback_denylist, diff --git a/src/sentry/tasks/post_process.py b/src/sentry/tasks/post_process.py index 628ead8535f827..91e9a0e300ccaa 100644 --- a/src/sentry/tasks/post_process.py +++ b/src/sentry/tasks/post_process.py @@ -1278,7 +1278,7 @@ def wrapper(job): def should_postprocess_feedback(job: PostProcessJob) -> bool: - from sentry.feedback.usecases.create_feedback import FeedbackCreationSource + from sentry.feedback.lib.utils import FeedbackCreationSource event = job["event"] @@ -1363,7 +1363,7 @@ def check_has_high_priority_alerts(job: PostProcessJob) -> None: def link_event_to_user_report(job: PostProcessJob) -> None: - from sentry.feedback.usecases.create_feedback import FeedbackCreationSource + from sentry.feedback.lib.utils import FeedbackCreationSource from sentry.feedback.usecases.shim_to_feedback import shim_to_feedback from sentry.models.userreport import UserReport diff --git a/src/sentry/tasks/store.py b/src/sentry/tasks/store.py index b480ac5a9e9bbe..80c659397bfd26 100644 --- a/src/sentry/tasks/store.py +++ b/src/sentry/tasks/store.py @@ -16,7 +16,8 @@ from sentry.constants import DEFAULT_STORE_NORMALIZER_ARGS from sentry.datascrubbing import scrub_data from sentry.eventstore import processing -from sentry.feedback.usecases.create_feedback import FeedbackCreationSource, create_feedback_issue +from sentry.feedback.lib.utils import FeedbackCreationSource +from sentry.feedback.usecases.create_feedback import create_feedback_issue from sentry.ingest.types import ConsumerType from sentry.killswitches import killswitch_matches_context from sentry.lang.native.symbolicator import SymbolicatorTaskKind diff --git a/src/sentry/tasks/update_user_reports.py b/src/sentry/tasks/update_user_reports.py index 79f455c9d3b9bd..5ae8d720260e42 100644 --- a/src/sentry/tasks/update_user_reports.py +++ b/src/sentry/tasks/update_user_reports.py @@ -6,7 +6,7 @@ from django.utils import timezone from sentry import eventstore, quotas -from sentry.feedback.usecases.create_feedback import FeedbackCreationSource, is_in_feedback_denylist +from sentry.feedback.lib.utils import FeedbackCreationSource, is_in_feedback_denylist from sentry.feedback.usecases.shim_to_feedback import shim_to_feedback from sentry.models.project import Project from sentry.models.userreport import UserReport diff --git a/src/sentry/utils/mockdata/core.py b/src/sentry/utils/mockdata/core.py index ff10601311719d..c39f8529cbc597 100644 --- a/src/sentry/utils/mockdata/core.py +++ b/src/sentry/utils/mockdata/core.py @@ -21,7 +21,7 @@ from sentry import buffer, roles, tsdb from sentry.constants import ObjectStatus from sentry.exceptions import HashDiscarded -from sentry.feedback.usecases.create_feedback import FeedbackCreationSource, create_feedback_issue +from sentry.feedback.lib.utils import FeedbackCreationSource, create_feedback_issue from sentry.incidents.logic import create_alert_rule, create_alert_rule_trigger, create_incident from sentry.incidents.models.alert_rule import AlertRuleThresholdType from sentry.incidents.models.incident import IncidentType diff --git a/src/sentry/web/frontend/error_page_embed.py b/src/sentry/web/frontend/error_page_embed.py index 10c4ccde20538a..28e31ce0263fe5 100644 --- a/src/sentry/web/frontend/error_page_embed.py +++ b/src/sentry/web/frontend/error_page_embed.py @@ -12,7 +12,7 @@ from django.views.generic import View from sentry import eventstore -from sentry.feedback.usecases.create_feedback import FeedbackCreationSource +from sentry.feedback.lib.utils import FeedbackCreationSource from sentry.feedback.usecases.shim_to_feedback import shim_to_feedback from sentry.models.options.project_option import ProjectOption from sentry.models.project import Project diff --git a/tests/sentry/api/endpoints/test_organization_user_reports.py b/tests/sentry/api/endpoints/test_organization_user_reports.py index b984e43cd7e307..1e99919817d2a3 100644 --- a/tests/sentry/api/endpoints/test_organization_user_reports.py +++ b/tests/sentry/api/endpoints/test_organization_user_reports.py @@ -1,7 +1,7 @@ from datetime import UTC, datetime, timedelta from unittest.mock import patch -from sentry.feedback.usecases.create_feedback import FeedbackCreationSource +from sentry.feedback.lib.utils import FeedbackCreationSource from sentry.ingest.userreport import save_userreport from sentry.models.group import GroupStatus from sentry.models.userreport import UserReport diff --git a/tests/sentry/feedback/usecases/test_create_feedback.py b/tests/sentry/feedback/usecases/test_create_feedback.py index e24f6ee34abf63..9ec75ee0c99fc9 100644 --- a/tests/sentry/feedback/usecases/test_create_feedback.py +++ b/tests/sentry/feedback/usecases/test_create_feedback.py @@ -9,11 +9,10 @@ from openai.types.chat.chat_completion import ChatCompletion, Choice from openai.types.chat.chat_completion_message import ChatCompletionMessage +from sentry.feedback.lib.utils import FeedbackCreationSource, is_in_feedback_denylist from sentry.feedback.usecases.create_feedback import ( - FeedbackCreationSource, create_feedback_issue, fix_for_issue_platform, - is_in_feedback_denylist, validate_issue_platform_event_schema, ) from sentry.models.group import Group, GroupStatus diff --git a/tests/sentry/feedback/usecases/test_shim_to_feedback.py b/tests/sentry/feedback/usecases/test_shim_to_feedback.py index 237411849c1d06..58fe4d88840571 100644 --- a/tests/sentry/feedback/usecases/test_shim_to_feedback.py +++ b/tests/sentry/feedback/usecases/test_shim_to_feedback.py @@ -1,7 +1,7 @@ from unittest.mock import Mock from sentry.eventstore.models import Event -from sentry.feedback.usecases.create_feedback import FeedbackCreationSource +from sentry.feedback.lib.utils import FeedbackCreationSource from sentry.feedback.usecases.shim_to_feedback import shim_to_feedback from sentry.testutils.pytest.fixtures import django_db_all diff --git a/tests/sentry/ingest/test_userreport.py b/tests/sentry/ingest/test_userreport.py index c49bbb457d7b5e..8f1920edaac8db 100644 --- a/tests/sentry/ingest/test_userreport.py +++ b/tests/sentry/ingest/test_userreport.py @@ -1,9 +1,6 @@ from unittest.mock import Mock -from sentry.feedback.usecases.create_feedback import ( - UNREAL_FEEDBACK_UNATTENDED_MESSAGE, - FeedbackCreationSource, -) +from sentry.feedback.lib.utils import UNREAL_FEEDBACK_UNATTENDED_MESSAGE, FeedbackCreationSource from sentry.ingest.userreport import save_userreport, should_filter_user_report from sentry.models.userreport import UserReport from sentry.testutils.pytest.fixtures import django_db_all diff --git a/tests/sentry/issues/endpoints/test_organization_group_index.py b/tests/sentry/issues/endpoints/test_organization_group_index.py index 0fa1414ad5e188..7e0c0a3ccf037e 100644 --- a/tests/sentry/issues/endpoints/test_organization_group_index.py +++ b/tests/sentry/issues/endpoints/test_organization_group_index.py @@ -9,7 +9,8 @@ from django.utils import timezone from sentry import options -from sentry.feedback.usecases.create_feedback import FeedbackCreationSource, create_feedback_issue +from sentry.feedback.lib.utils import FeedbackCreationSource +from sentry.feedback.usecases.create_feedback import create_feedback_issue from sentry.integrations.models.external_issue import ExternalIssue from sentry.integrations.models.organization_integration import OrganizationIntegration from sentry.issues.grouptype import ( diff --git a/tests/sentry/tasks/test_post_process.py b/tests/sentry/tasks/test_post_process.py index 49a57389a0aac5..0c9db4c870af58 100644 --- a/tests/sentry/tasks/test_post_process.py +++ b/tests/sentry/tasks/test_post_process.py @@ -18,7 +18,7 @@ from sentry.eventstore.models import Event from sentry.eventstore.processing import event_processing_store from sentry.eventstream.types import EventStreamEventType -from sentry.feedback.usecases.create_feedback import FeedbackCreationSource +from sentry.feedback.lib.utils import FeedbackCreationSource from sentry.integrations.models.integration import Integration from sentry.integrations.source_code_management.commit_context import CommitInfo, FileBlameInfo from sentry.issues.auto_source_code_config.utils.platform import get_supported_platforms From f9dcc78fe2893eedeb6bde1d3510f3053c7f43a7 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Fri, 11 Apr 2025 18:54:32 -0700 Subject: [PATCH 4/7] Missed a denylist spot --- src/sentry/feedback/usecases/shim_to_feedback.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sentry/feedback/usecases/shim_to_feedback.py b/src/sentry/feedback/usecases/shim_to_feedback.py index fb0b6196abdf0c..751059a9c915cf 100644 --- a/src/sentry/feedback/usecases/shim_to_feedback.py +++ b/src/sentry/feedback/usecases/shim_to_feedback.py @@ -4,8 +4,8 @@ from sentry.constants import DataCategory from sentry.eventstore.models import Event, GroupEvent -from sentry.feedback.lib.utils import FeedbackCreationSource -from sentry.feedback.usecases.create_feedback import create_feedback_issue, is_in_feedback_denylist +from sentry.feedback.lib.utils import FeedbackCreationSource, is_in_feedback_denylist +from sentry.feedback.usecases.create_feedback import create_feedback_issue from sentry.models.project import Project from sentry.utils import metrics from sentry.utils.outcomes import Outcome, track_outcome From 6aac9df359a71d3e8e20db276a45c9f105131caa Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Fri, 11 Apr 2025 19:09:02 -0700 Subject: [PATCH 5/7] Move denylist tests --- tests/sentry/feedback/lib/test_utils.py | 16 ++++++++++++++++ .../feedback/usecases/test_create_feedback.py | 16 +--------------- .../feedback/usecases/test_shim_to_feedback.py | 2 +- 3 files changed, 18 insertions(+), 16 deletions(-) create mode 100644 tests/sentry/feedback/lib/test_utils.py diff --git a/tests/sentry/feedback/lib/test_utils.py b/tests/sentry/feedback/lib/test_utils.py new file mode 100644 index 00000000000000..9f9e68b63f84af --- /dev/null +++ b/tests/sentry/feedback/lib/test_utils.py @@ -0,0 +1,16 @@ +from sentry.feedback.lib.utils import is_in_feedback_denylist +from sentry.testutils.pytest.fixtures import django_db_all + + +@django_db_all +def test_denylist(set_sentry_option, default_project): + with set_sentry_option( + "feedback.organizations.slug-denylist", [default_project.organization.slug] + ): + assert is_in_feedback_denylist(default_project.organization) is True + + +@django_db_all +def test_denylist_not_in_list(set_sentry_option, default_project): + with set_sentry_option("feedback.organizations.slug-denylist", ["not-in-list"]): + assert is_in_feedback_denylist(default_project.organization) is False diff --git a/tests/sentry/feedback/usecases/test_create_feedback.py b/tests/sentry/feedback/usecases/test_create_feedback.py index 9ec75ee0c99fc9..30c01252878c0b 100644 --- a/tests/sentry/feedback/usecases/test_create_feedback.py +++ b/tests/sentry/feedback/usecases/test_create_feedback.py @@ -9,7 +9,7 @@ from openai.types.chat.chat_completion import ChatCompletion, Choice from openai.types.chat.chat_completion_message import ChatCompletionMessage -from sentry.feedback.lib.utils import FeedbackCreationSource, is_in_feedback_denylist +from sentry.feedback.lib.utils import FeedbackCreationSource from sentry.feedback.usecases.create_feedback import ( create_feedback_issue, fix_for_issue_platform, @@ -861,17 +861,3 @@ def test_create_feedback_evidence_has_spam( assert mock_produce_occurrence_to_kafka.call_count == 1 evidence = mock_produce_occurrence_to_kafka.call_args.kwargs["occurrence"].evidence_data assert evidence["is_spam"] is True - - -@django_db_all -def test_denylist(set_sentry_option, default_project): - with set_sentry_option( - "feedback.organizations.slug-denylist", [default_project.organization.slug] - ): - assert is_in_feedback_denylist(default_project.organization) is True - - -@django_db_all -def test_denylist_not_in_list(set_sentry_option, default_project): - with set_sentry_option("feedback.organizations.slug-denylist", ["not-in-list"]): - assert is_in_feedback_denylist(default_project.organization) is False diff --git a/tests/sentry/feedback/usecases/test_shim_to_feedback.py b/tests/sentry/feedback/usecases/test_shim_to_feedback.py index 58fe4d88840571..84543fb59ab69c 100644 --- a/tests/sentry/feedback/usecases/test_shim_to_feedback.py +++ b/tests/sentry/feedback/usecases/test_shim_to_feedback.py @@ -6,7 +6,7 @@ from sentry.testutils.pytest.fixtures import django_db_all """ -Unit tests for shim_to_feedback error cases. The typical behavior of this function is tested in +Unit tests for shim_to_feedback. These are mostly error cases - the typical behavior of this function is covered in test_project_user_reports, test_post_process, and test_update_user_reports. """ From 22dd0e562263504963747300cd07ada3c1f41e47 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Sat, 12 Apr 2025 23:17:32 -0700 Subject: [PATCH 6/7] Fix import in mockdata --- src/sentry/utils/mockdata/core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sentry/utils/mockdata/core.py b/src/sentry/utils/mockdata/core.py index c39f8529cbc597..2cf5910b2bbca1 100644 --- a/src/sentry/utils/mockdata/core.py +++ b/src/sentry/utils/mockdata/core.py @@ -21,7 +21,8 @@ from sentry import buffer, roles, tsdb from sentry.constants import ObjectStatus from sentry.exceptions import HashDiscarded -from sentry.feedback.lib.utils import FeedbackCreationSource, create_feedback_issue +from sentry.feedback.lib.utils import FeedbackCreationSource +from sentry.feedback.usecases.create_feedback import create_feedback_issue from sentry.incidents.logic import create_alert_rule, create_alert_rule_trigger, create_incident from sentry.incidents.models.alert_rule import AlertRuleThresholdType from sentry.incidents.models.incident import IncidentType From 5a579a02661251245768e3922f3a9186486becc5 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Sat, 12 Apr 2025 23:34:37 -0700 Subject: [PATCH 7/7] Fix test_utils name conflict --- .../sentry/feedback/lib/{test_utils.py => test_feedback_utils.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/sentry/feedback/lib/{test_utils.py => test_feedback_utils.py} (100%) diff --git a/tests/sentry/feedback/lib/test_utils.py b/tests/sentry/feedback/lib/test_feedback_utils.py similarity index 100% rename from tests/sentry/feedback/lib/test_utils.py rename to tests/sentry/feedback/lib/test_feedback_utils.py