diff --git a/src/sentry/api/urls.py b/src/sentry/api/urls.py index 0ac5fc3728fc02..7362df9441db26 100644 --- a/src/sentry/api/urls.py +++ b/src/sentry/api/urls.py @@ -20,7 +20,6 @@ from sentry.api.endpoints.organization_events_root_cause_analysis import ( OrganizationEventsRootCauseAnalysisEndpoint, ) -from sentry.api.endpoints.organization_feedback_summary import OrganizationFeedbackSummaryEndpoint from sentry.api.endpoints.organization_fork import OrganizationForkEndpoint from sentry.api.endpoints.organization_insights_tree import OrganizationInsightsTreeEndpoint from sentry.api.endpoints.organization_member_invite.details import ( @@ -104,6 +103,11 @@ from sentry.explore.endpoints.explore_saved_query_starred_order import ( ExploreSavedQueryStarredOrderEndpoint, ) +from sentry.feedback.endpoints.organization_feedback_summary import ( + OrganizationFeedbackSummaryEndpoint, +) +from sentry.feedback.endpoints.organization_user_reports import OrganizationUserReportsEndpoint +from sentry.feedback.endpoints.project_user_reports import ProjectUserReportsEndpoint from sentry.flags.endpoints.hooks import OrganizationFlagsHooksEndpoint from sentry.flags.endpoints.logs import ( OrganizationFlagLogDetailsEndpoint, @@ -647,7 +651,6 @@ OrganizationTraceSpansEndpoint, ) from .endpoints.organization_user_details import OrganizationUserDetailsEndpoint -from .endpoints.organization_user_reports import OrganizationUserReportsEndpoint from .endpoints.organization_user_teams import OrganizationUserTeamsEndpoint from .endpoints.organization_users import OrganizationUsersEndpoint from .endpoints.project_artifact_bundle_file_details import ProjectArtifactBundleFileDetailsEndpoint @@ -714,7 +717,6 @@ ProjectTransactionThresholdOverrideEndpoint, ) from .endpoints.project_transfer import ProjectTransferEndpoint -from .endpoints.project_user_reports import ProjectUserReportsEndpoint from .endpoints.project_user_stats import ProjectUserStatsEndpoint from .endpoints.project_users import ProjectUsersEndpoint from .endpoints.prompts_activity import PromptsActivityEndpoint diff --git a/src/sentry/feedback/endpoints/__init__.py b/src/sentry/feedback/endpoints/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/src/sentry/api/endpoints/organization_feedback_summary.py b/src/sentry/feedback/endpoints/organization_feedback_summary.py similarity index 100% rename from src/sentry/api/endpoints/organization_feedback_summary.py rename to src/sentry/feedback/endpoints/organization_feedback_summary.py diff --git a/src/sentry/api/endpoints/organization_user_reports.py b/src/sentry/feedback/endpoints/organization_user_reports.py similarity index 100% rename from src/sentry/api/endpoints/organization_user_reports.py rename to src/sentry/feedback/endpoints/organization_user_reports.py diff --git a/src/sentry/api/endpoints/project_user_reports.py b/src/sentry/feedback/endpoints/project_user_reports.py similarity index 98% rename from src/sentry/api/endpoints/project_user_reports.py rename to src/sentry/feedback/endpoints/project_user_reports.py index c8f06673ef6516..8d0dabb2952e79 100644 --- a/src/sentry/api/endpoints/project_user_reports.py +++ b/src/sentry/feedback/endpoints/project_user_reports.py @@ -16,7 +16,7 @@ from sentry.api.paginator import DateTimePaginator from sentry.api.serializers import UserReportWithGroupSerializer, serialize from sentry.feedback.lib.utils import FeedbackCreationSource -from sentry.ingest.userreport import Conflict, save_userreport +from sentry.feedback.usecases.ingest.userreport import Conflict, save_userreport from sentry.models.environment import Environment from sentry.models.userreport import UserReport from sentry.utils.dates import epoch diff --git a/src/sentry/feedback/usecases/ingest/__init__.py b/src/sentry/feedback/usecases/ingest/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/src/sentry/feedback/usecases/create_feedback.py b/src/sentry/feedback/usecases/ingest/create_feedback.py similarity index 100% rename from src/sentry/feedback/usecases/create_feedback.py rename to src/sentry/feedback/usecases/ingest/create_feedback.py diff --git a/src/sentry/feedback/usecases/save_feedback_event.py b/src/sentry/feedback/usecases/ingest/save_event_feedback.py similarity index 76% rename from src/sentry/feedback/usecases/save_feedback_event.py rename to src/sentry/feedback/usecases/ingest/save_event_feedback.py index 7e678a40deada8..d45cb84085b0e4 100644 --- a/src/sentry/feedback/usecases/save_feedback_event.py +++ b/src/sentry/feedback/usecases/ingest/save_event_feedback.py @@ -4,8 +4,8 @@ from typing import Any from sentry.feedback.lib.utils import FeedbackCreationSource -from sentry.feedback.usecases.create_feedback import create_feedback_issue -from sentry.ingest.userreport import Conflict, save_userreport +from sentry.feedback.usecases.ingest.create_feedback import create_feedback_issue +from sentry.feedback.usecases.ingest.userreport import Conflict, save_userreport from sentry.models.environment import Environment from sentry.models.project import Project from sentry.utils import metrics @@ -13,12 +13,15 @@ logger = logging.getLogger(__name__) -def save_feedback_event(event_data: Mapping[str, Any], project_id: int): - """Saves a feedback from a feedback event envelope. +def save_event_feedback(event_data: Mapping[str, Any], project_id: int): + """Saves feedback given data in an event format. This function should only + be called by the new feedback consumer's ingest strategy, to process + feedback envelopes (feedback v2). It is currently instrumented as a task in + sentry.tasks.store. - If the save is successful and the `associated_event_id` field is present, this will - also save a UserReport in Postgres. This is to ensure the feedback can be queried by - group_id, which is hard to associate in clickhouse. + If the save is successful and the `associated_event_id` field is present, + this will also save a UserReport in Postgres (shim to v1). This is to allow + queries by the group_id relation, which we don't have in clickhouse. """ if not isinstance(event_data, dict): event_data = dict(event_data) @@ -32,6 +35,9 @@ def save_feedback_event(event_data: Mapping[str, Any], project_id: int): try: # Shim to UserReport + # TODO: this logic should be extracted to a shim_to_userreport function + # which returns a report dict. After that this function can be removed + # and the store task can directly call feedback ingest functions. feedback_context = fixed_event_data["contexts"]["feedback"] associated_event_id = feedback_context.get("associated_event_id") diff --git a/src/sentry/feedback/usecases/shim_to_feedback.py b/src/sentry/feedback/usecases/ingest/shim_to_feedback.py similarity index 97% rename from src/sentry/feedback/usecases/shim_to_feedback.py rename to src/sentry/feedback/usecases/ingest/shim_to_feedback.py index 844a471398c24a..ff35413867681e 100644 --- a/src/sentry/feedback/usecases/shim_to_feedback.py +++ b/src/sentry/feedback/usecases/ingest/shim_to_feedback.py @@ -8,7 +8,7 @@ from sentry.eventstore.models import Event, GroupEvent from sentry.feedback.lib.types import UserReportDict from sentry.feedback.lib.utils import FeedbackCreationSource, is_in_feedback_denylist -from sentry.feedback.usecases.create_feedback import create_feedback_issue +from sentry.feedback.usecases.ingest.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 diff --git a/src/sentry/feedback/usecases/ingest/userreport.py b/src/sentry/feedback/usecases/ingest/userreport.py new file mode 100644 index 00000000000000..e891d441765e83 --- /dev/null +++ b/src/sentry/feedback/usecases/ingest/userreport.py @@ -0,0 +1,265 @@ +from __future__ import annotations + +import logging +import random +import uuid +from datetime import datetime, timedelta + +from django.core.exceptions import PermissionDenied +from django.db import IntegrityError, router +from django.utils import timezone + +from sentry import eventstore, options +from sentry.api.exceptions import BadRequest +from sentry.constants import DataCategory +from sentry.eventstore.models import Event, GroupEvent +from sentry.feedback.lib.types import UserReportDict +from sentry.feedback.lib.utils import ( + UNREAL_FEEDBACK_UNATTENDED_MESSAGE, + FeedbackCreationSource, + is_in_feedback_denylist, +) +from sentry.feedback.usecases.ingest.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 +from sentry.utils import metrics +from sentry.utils.db import atomic_transaction +from sentry.utils.outcomes import Outcome, track_outcome + +logger = logging.getLogger(__name__) + + +class Conflict(Exception): + pass + + +def save_userreport( + project: Project, + report: UserReportDict, + source: FeedbackCreationSource, + start_time: datetime | None = None, +) -> UserReport | None: + with metrics.timer("userreport.create_user_report", tags={"referrer": source.value}): + if start_time is None: + start_time = timezone.now() + + if is_in_feedback_denylist(project.organization): + metrics.incr( + "user_report.create_user_report.filtered", + tags={"reason": "org.denylist", "referrer": source.value}, + ) + track_outcome( + org_id=project.organization_id, + project_id=project.id, + key_id=None, + outcome=Outcome.RATE_LIMITED, + reason="feedback_denylist", + timestamp=start_time, + event_id=None, # Note report["event_id"] is id of the associated event, not the report itself. + category=DataCategory.USER_REPORT_V2, + quantity=1, + ) + if ( + source == FeedbackCreationSource.USER_REPORT_DJANGO_ENDPOINT + or source == FeedbackCreationSource.CRASH_REPORT_EMBED_FORM + ): + raise PermissionDenied() + return None + + should_filter, metrics_reason, display_reason = validate_user_report( + report, project.id, source=source + ) + if should_filter: + metrics.incr( + "user_report.create_user_report.filtered", + tags={"reason": metrics_reason, "referrer": source.value}, + ) + track_outcome( + org_id=project.organization_id, + project_id=project.id, + key_id=None, + outcome=Outcome.INVALID, + reason=display_reason, + timestamp=start_time, + event_id=None, # Note report["event_id"] is id of the associated event, not the report itself. + category=DataCategory.USER_REPORT_V2, + quantity=1, + ) + if ( + source == FeedbackCreationSource.USER_REPORT_DJANGO_ENDPOINT + or source == FeedbackCreationSource.CRASH_REPORT_EMBED_FORM + ): + raise BadRequest(display_reason) + return None + + # XXX(dcramer): enforce case insensitivity by coercing this to a lowercase string + report["event_id"] = report["event_id"].lower() + report["project_id"] = project.id + + # Use the associated event to validate and update the report. + event: Event | GroupEvent | None = eventstore.backend.get_event_by_id( + project.id, report["event_id"] + ) + + if event: + # if the event is more than 30 minutes old, we don't allow updates + # as it might be abusive + if event.datetime < start_time - timedelta(minutes=30): + metrics.incr( + "user_report.create_user_report.filtered", + tags={"reason": "event_too_old", "referrer": source.value}, + ) + track_outcome( + org_id=project.organization_id, + project_id=project.id, + key_id=None, + outcome=Outcome.INVALID, + reason="Associated event is too old", + timestamp=start_time, + event_id=None, + category=DataCategory.USER_REPORT_V2, + quantity=1, + ) + raise Conflict("Feedback for this event cannot be modified.") + + report["environment_id"] = event.get_environment().id + if event.group_id: + report["group_id"] = event.group_id + + # Save the report. + try: + with atomic_transaction(using=router.db_for_write(UserReport)): + report_instance = UserReport.objects.create(**report) + + except IntegrityError: + # There was a duplicate, so just overwrite the existing + # row with the new one. The only way this ever happens is + # if someone is messing around with the API, or doing + # something wrong with the SDK, but this behavior is + # more reasonable than just hard erroring and is more + # expected. + existing_report = UserReport.objects.get( + project_id=report["project_id"], event_id=report["event_id"] + ) + + # if the existing report was submitted more than 5 minutes ago, we dont + # allow updates as it might be abusive (replay attacks) + if existing_report.date_added < timezone.now() - timedelta(minutes=5): + metrics.incr( + "user_report.create_user_report.filtered", + tags={"reason": "duplicate_report", "referrer": source.value}, + ) + track_outcome( + org_id=project.organization_id, + project_id=project.id, + key_id=None, + outcome=Outcome.INVALID, + reason="Duplicate report", + timestamp=start_time, + event_id=None, + category=DataCategory.USER_REPORT_V2, + quantity=1, + ) + raise Conflict("Feedback for this event cannot be modified.") + + existing_report.update( + name=report.get("name", ""), + email=report.get("email", ""), + comments=report["comments"], + ) + report_instance = existing_report + + metrics.incr( + "user_report.create_user_report.overwrite_duplicate", + tags={"referrer": source.value}, + ) + + else: + if report_instance.group_id: + report_instance.notify() + + # Additionally processing if save is successful. + user_feedback_received.send_robust(project=project, sender=save_userreport) + + metrics.incr( + "user_report.create_user_report.saved", + tags={"has_event": bool(event), "referrer": source.value}, + ) + if event and source.value in FeedbackCreationSource.old_feedback_category_values(): + shim_to_feedback(report, event, project, source) + # XXX(aliu): the update_user_reports task will still try to shim the report after a period, unless group_id or environment is set. + + return report_instance + + +def validate_user_report( + report: UserReportDict, + project_id: int, + source: FeedbackCreationSource = FeedbackCreationSource.USER_REPORT_ENVELOPE, +) -> tuple[bool, str | None, str | None]: + """ + Validates required fields, field lengths, and garbage messages. Also checks that event_id is a valid UUID. Does not raise errors. + + Reformatting: strips whitespace from comments and dashes from event_id. + + Returns a tuple of (should_filter, metrics_reason, display_reason). XXX: ensure metrics and outcome reasons have bounded cardinality. + """ + if "comments" not in report: + return True, "missing_comments", "Missing comments" # type: ignore[unreachable] + if "event_id" not in report: + return True, "missing_event_id", "Missing event_id" # type: ignore[unreachable] + + report["comments"] = report["comments"].strip() + + name, email, comments = ( + report.get("name", ""), + report.get("email", ""), + report["comments"], + ) + + if options.get("feedback.filter_garbage_messages"): # Message-based filter kill-switch. + if not comments: + return True, "empty", "Empty Feedback Messsage" + + if comments == UNREAL_FEEDBACK_UNATTENDED_MESSAGE: + return True, "unreal.unattended", "Sent in Unreal Unattended Mode" + + max_comment_length = UserReport._meta.get_field("comments").max_length + if max_comment_length and len(comments) > max_comment_length: + metrics.distribution( + "feedback.large_message", + len(comments), + tags={ + "entrypoint": "save_userreport", + "referrer": source.value, + }, + ) + if random.random() < 0.1: + logger.info( + "Feedback message exceeds max size.", + extra={ + "project_id": project_id, + "entrypoint": "save_userreport", + "referrer": source.value, + "length": len(comments), + "feedback_message": comments[:100], + }, + ) + return True, "too_large.message", "Message Too Large" + + max_name_length = UserReport._meta.get_field("name").max_length + if max_name_length and len(name) > max_name_length: + return True, "too_large.name", "Name Too Large" + + max_email_length = UserReport._meta.get_field("email").max_length + if max_email_length and len(email) > max_email_length: + return True, "too_large.email", "Email Too Large" + + try: + # Validates UUID and strips dashes. + report["event_id"] = uuid.UUID(report["event_id"].lower()).hex + except ValueError: + return True, "invalid_event_id", "Invalid Event ID" + + return False, None, None diff --git a/src/sentry/ingest/consumer/processors.py b/src/sentry/ingest/consumer/processors.py index 56e8041cede3b8..a6099bc9a4696c 100644 --- a/src/sentry/ingest/consumer/processors.py +++ b/src/sentry/ingest/consumer/processors.py @@ -15,8 +15,8 @@ from sentry.event_manager import EventManager, save_attachment from sentry.eventstore.processing import event_processing_store, transaction_processing_store from sentry.feedback.lib.utils import FeedbackCreationSource, is_in_feedback_denylist +from sentry.feedback.usecases.ingest.userreport import Conflict, save_userreport from sentry.ingest.types import ConsumerType -from sentry.ingest.userreport import Conflict, save_userreport from sentry.killswitches import killswitch_matches_context from sentry.models.organization import Organization from sentry.models.project import Project diff --git a/src/sentry/ingest/userreport.py b/src/sentry/ingest/userreport.py index ffc5cc9d24f01f..e69de29bb2d1d6 100644 --- a/src/sentry/ingest/userreport.py +++ b/src/sentry/ingest/userreport.py @@ -1,265 +0,0 @@ -from __future__ import annotations - -import logging -import random -import uuid -from datetime import datetime, timedelta - -from django.core.exceptions import PermissionDenied -from django.db import IntegrityError, router -from django.utils import timezone - -from sentry import eventstore, options -from sentry.api.exceptions import BadRequest -from sentry.constants import DataCategory -from sentry.eventstore.models import Event, GroupEvent -from sentry.feedback.lib.types import UserReportDict -from sentry.feedback.lib.utils import ( - UNREAL_FEEDBACK_UNATTENDED_MESSAGE, - 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.signals import user_feedback_received -from sentry.utils import metrics -from sentry.utils.db import atomic_transaction -from sentry.utils.outcomes import Outcome, track_outcome - -logger = logging.getLogger(__name__) - - -class Conflict(Exception): - pass - - -def save_userreport( - project: Project, - report: UserReportDict, - source: FeedbackCreationSource, - start_time: datetime | None = None, -) -> UserReport | None: - with metrics.timer("sentry.ingest.userreport.save_userreport", tags={"referrer": source.value}): - if start_time is None: - start_time = timezone.now() - - if is_in_feedback_denylist(project.organization): - metrics.incr( - "user_report.create_user_report.filtered", - tags={"reason": "org.denylist", "referrer": source.value}, - ) - track_outcome( - org_id=project.organization_id, - project_id=project.id, - key_id=None, - outcome=Outcome.RATE_LIMITED, - reason="feedback_denylist", - timestamp=start_time, - event_id=None, # Note report["event_id"] is id of the associated event, not the report itself. - category=DataCategory.USER_REPORT_V2, - quantity=1, - ) - if ( - source == FeedbackCreationSource.USER_REPORT_DJANGO_ENDPOINT - or source == FeedbackCreationSource.CRASH_REPORT_EMBED_FORM - ): - raise PermissionDenied() - return None - - should_filter, metrics_reason, display_reason = validate_user_report( - report, project.id, source=source - ) - if should_filter: - metrics.incr( - "user_report.create_user_report.filtered", - tags={"reason": metrics_reason, "referrer": source.value}, - ) - track_outcome( - org_id=project.organization_id, - project_id=project.id, - key_id=None, - outcome=Outcome.INVALID, - reason=display_reason, - timestamp=start_time, - event_id=None, # Note report["event_id"] is id of the associated event, not the report itself. - category=DataCategory.USER_REPORT_V2, - quantity=1, - ) - if ( - source == FeedbackCreationSource.USER_REPORT_DJANGO_ENDPOINT - or source == FeedbackCreationSource.CRASH_REPORT_EMBED_FORM - ): - raise BadRequest(display_reason) - return None - - # XXX(dcramer): enforce case insensitivity by coercing this to a lowercase string - report["event_id"] = report["event_id"].lower() - report["project_id"] = project.id - - # Use the associated event to validate and update the report. - event: Event | GroupEvent | None = eventstore.backend.get_event_by_id( - project.id, report["event_id"] - ) - - if event: - # if the event is more than 30 minutes old, we don't allow updates - # as it might be abusive - if event.datetime < start_time - timedelta(minutes=30): - metrics.incr( - "user_report.create_user_report.filtered", - tags={"reason": "event_too_old", "referrer": source.value}, - ) - track_outcome( - org_id=project.organization_id, - project_id=project.id, - key_id=None, - outcome=Outcome.INVALID, - reason="Associated event is too old", - timestamp=start_time, - event_id=None, - category=DataCategory.USER_REPORT_V2, - quantity=1, - ) - raise Conflict("Feedback for this event cannot be modified.") - - report["environment_id"] = event.get_environment().id - if event.group_id: - report["group_id"] = event.group_id - - # Save the report. - try: - with atomic_transaction(using=router.db_for_write(UserReport)): - report_instance = UserReport.objects.create(**report) - - except IntegrityError: - # There was a duplicate, so just overwrite the existing - # row with the new one. The only way this ever happens is - # if someone is messing around with the API, or doing - # something wrong with the SDK, but this behavior is - # more reasonable than just hard erroring and is more - # expected. - existing_report = UserReport.objects.get( - project_id=report["project_id"], event_id=report["event_id"] - ) - - # if the existing report was submitted more than 5 minutes ago, we dont - # allow updates as it might be abusive (replay attacks) - if existing_report.date_added < timezone.now() - timedelta(minutes=5): - metrics.incr( - "user_report.create_user_report.filtered", - tags={"reason": "duplicate_report", "referrer": source.value}, - ) - track_outcome( - org_id=project.organization_id, - project_id=project.id, - key_id=None, - outcome=Outcome.INVALID, - reason="Duplicate report", - timestamp=start_time, - event_id=None, - category=DataCategory.USER_REPORT_V2, - quantity=1, - ) - raise Conflict("Feedback for this event cannot be modified.") - - existing_report.update( - name=report.get("name", ""), - email=report.get("email", ""), - comments=report["comments"], - ) - report_instance = existing_report - - metrics.incr( - "user_report.create_user_report.overwrite_duplicate", - tags={"referrer": source.value}, - ) - - else: - if report_instance.group_id: - report_instance.notify() - - # Additionally processing if save is successful. - user_feedback_received.send_robust(project=project, sender=save_userreport) - - metrics.incr( - "user_report.create_user_report.saved", - tags={"has_event": bool(event), "referrer": source.value}, - ) - if event and source.value in FeedbackCreationSource.old_feedback_category_values(): - shim_to_feedback(report, event, project, source) - # XXX(aliu): the update_user_reports task will still try to shim the report after a period, unless group_id or environment is set. - - return report_instance - - -def validate_user_report( - report: UserReportDict, - project_id: int, - source: FeedbackCreationSource = FeedbackCreationSource.USER_REPORT_ENVELOPE, -) -> tuple[bool, str | None, str | None]: - """ - Validates required fields, field lengths, and garbage messages. Also checks that event_id is a valid UUID. Does not raise errors. - - Reformatting: strips whitespace from comments and dashes from event_id. - - Returns a tuple of (should_filter, metrics_reason, display_reason). XXX: ensure metrics and outcome reasons have bounded cardinality. - """ - if "comments" not in report: - return True, "missing_comments", "Missing comments" # type: ignore[unreachable] - if "event_id" not in report: - return True, "missing_event_id", "Missing event_id" # type: ignore[unreachable] - - report["comments"] = report["comments"].strip() - - name, email, comments = ( - report.get("name", ""), - report.get("email", ""), - report["comments"], - ) - - if options.get("feedback.filter_garbage_messages"): # Message-based filter kill-switch. - if not comments: - return True, "empty", "Empty Feedback Messsage" - - if comments == UNREAL_FEEDBACK_UNATTENDED_MESSAGE: - return True, "unreal.unattended", "Sent in Unreal Unattended Mode" - - max_comment_length = UserReport._meta.get_field("comments").max_length - if max_comment_length and len(comments) > max_comment_length: - metrics.distribution( - "feedback.large_message", - len(comments), - tags={ - "entrypoint": "save_userreport", - "referrer": source.value, - }, - ) - if random.random() < 0.1: - logger.info( - "Feedback message exceeds max size.", - extra={ - "project_id": project_id, - "entrypoint": "save_userreport", - "referrer": source.value, - "length": len(comments), - "feedback_message": comments[:100], - }, - ) - return True, "too_large.message", "Message Too Large" - - max_name_length = UserReport._meta.get_field("name").max_length - if max_name_length and len(name) > max_name_length: - return True, "too_large.name", "Name Too Large" - - max_email_length = UserReport._meta.get_field("email").max_length - if max_email_length and len(email) > max_email_length: - return True, "too_large.email", "Email Too Large" - - try: - # Validates UUID and strips dashes. - report["event_id"] = uuid.UUID(report["event_id"].lower()).hex - except ValueError: - return True, "invalid_event_id", "Invalid Event ID" - - return False, None, None diff --git a/src/sentry/tasks/post_process.py b/src/sentry/tasks/post_process.py index 065d3ad7b07ad7..faa058879aa5e2 100644 --- a/src/sentry/tasks/post_process.py +++ b/src/sentry/tasks/post_process.py @@ -1413,7 +1413,7 @@ def check_has_high_priority_alerts(job: PostProcessJob) -> None: def link_event_to_user_report(job: PostProcessJob) -> None: from sentry.feedback.lib.utils import FeedbackCreationSource - from sentry.feedback.usecases.shim_to_feedback import shim_to_feedback + from sentry.feedback.usecases.ingest.shim_to_feedback import shim_to_feedback from sentry.models.userreport import UserReport event = job["event"] diff --git a/src/sentry/tasks/store.py b/src/sentry/tasks/store.py index d688edcb4ce45c..6cb7a245787a48 100644 --- a/src/sentry/tasks/store.py +++ b/src/sentry/tasks/store.py @@ -16,7 +16,9 @@ from sentry.constants import DEFAULT_STORE_NORMALIZER_ARGS from sentry.datascrubbing import scrub_data from sentry.eventstore import processing -from sentry.feedback.usecases.save_feedback_event import save_feedback_event +from sentry.feedback.usecases.ingest.save_event_feedback import ( + save_event_feedback as save_event_feedback_impl, +) from sentry.ingest.types import ConsumerType from sentry.killswitches import killswitch_matches_context from sentry.lang.native.symbolicator import SymbolicatorTaskKind @@ -703,7 +705,7 @@ def save_event_feedback( project_id: int, **kwargs: Any, ) -> None: - save_feedback_event(data, project_id) + save_event_feedback_impl(data, project_id) @instrumented_task( diff --git a/src/sentry/tasks/update_user_reports.py b/src/sentry/tasks/update_user_reports.py index fe9eeb047e3696..5db1ca0e8cbedb 100644 --- a/src/sentry/tasks/update_user_reports.py +++ b/src/sentry/tasks/update_user_reports.py @@ -6,7 +6,7 @@ from sentry import eventstore, quotas from sentry.feedback.lib.utils import FeedbackCreationSource, is_in_feedback_denylist -from sentry.feedback.usecases.shim_to_feedback import shim_to_feedback +from sentry.feedback.usecases.ingest.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/utils/mockdata/core.py b/src/sentry/utils/mockdata/core.py index 405ea1f7157ae0..5fd4c3c322ede8 100644 --- a/src/sentry/utils/mockdata/core.py +++ b/src/sentry/utils/mockdata/core.py @@ -22,7 +22,7 @@ from sentry.constants import ObjectStatus from sentry.exceptions import HashDiscarded from sentry.feedback.lib.utils import FeedbackCreationSource -from sentry.feedback.usecases.create_feedback import create_feedback_issue +from sentry.feedback.usecases.ingest.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 diff --git a/src/sentry/web/frontend/error_page_embed.py b/src/sentry/web/frontend/error_page_embed.py index 2f91a7b87ba3b1..08eec8d13854b2 100644 --- a/src/sentry/web/frontend/error_page_embed.py +++ b/src/sentry/web/frontend/error_page_embed.py @@ -13,7 +13,7 @@ from sentry import eventstore from sentry.feedback.lib.utils import FeedbackCreationSource -from sentry.feedback.usecases.shim_to_feedback import shim_to_feedback +from sentry.feedback.usecases.ingest.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/api/endpoints/test_organization_feedback_summary.py b/tests/sentry/feedback/endpoints/test_organization_feedback_summary.py similarity index 89% rename from tests/sentry/api/endpoints/test_organization_feedback_summary.py rename to tests/sentry/feedback/endpoints/test_organization_feedback_summary.py index 61b16c8a571b55..6f8f91fe8384fa 100644 --- a/tests/sentry/api/endpoints/test_organization_feedback_summary.py +++ b/tests/sentry/feedback/endpoints/test_organization_feedback_summary.py @@ -4,7 +4,7 @@ from django.urls import reverse from sentry.feedback.lib.utils import FeedbackCreationSource -from sentry.feedback.usecases.create_feedback import create_feedback_issue +from sentry.feedback.usecases.ingest.create_feedback import create_feedback_issue from sentry.testutils.cases import APITestCase from sentry.testutils.pytest.fixtures import django_db_all from sentry.testutils.silo import region_silo_test @@ -39,7 +39,7 @@ def test_get_feedback_summary_without_feature_flag(self): @django_db_all @patch( - "sentry.api.endpoints.organization_feedback_summary.generate_summary", + "sentry.feedback.endpoints.organization_feedback_summary.generate_summary", return_value="Test summary of feedback", ) def test_get_feedback_summary_basic(self, mock_generate_summary): @@ -58,7 +58,7 @@ def test_get_feedback_summary_basic(self, mock_generate_summary): @django_db_all @patch( - "sentry.api.endpoints.organization_feedback_summary.generate_summary", + "sentry.feedback.endpoints.organization_feedback_summary.generate_summary", return_value="Test summary of feedback", ) def test_get_feedback_summary_with_date_filter(self, mock_generate_summary): @@ -89,7 +89,7 @@ def test_get_feedback_summary_with_date_filter(self, mock_generate_summary): @django_db_all @patch( - "sentry.api.endpoints.organization_feedback_summary.generate_summary", + "sentry.feedback.endpoints.organization_feedback_summary.generate_summary", return_value="Test summary of feedback", ) def test_get_feedback_summary_with_project_filter(self, mock_generate_summary): @@ -118,7 +118,7 @@ def test_get_feedback_summary_with_project_filter(self, mock_generate_summary): @django_db_all @patch( - "sentry.api.endpoints.organization_feedback_summary.generate_summary", + "sentry.feedback.endpoints.organization_feedback_summary.generate_summary", return_value="Test summary of feedback", ) def test_get_feedback_summary_with_many_project_filter_as_list(self, mock_generate_summary): @@ -147,7 +147,7 @@ def test_get_feedback_summary_with_many_project_filter_as_list(self, mock_genera @django_db_all @patch( - "sentry.api.endpoints.organization_feedback_summary.generate_summary", + "sentry.feedback.endpoints.organization_feedback_summary.generate_summary", return_value="Test summary of feedback", ) def test_get_feedback_summary_with_many_project_filter_separate(self, mock_generate_summary): @@ -175,7 +175,7 @@ def test_get_feedback_summary_with_many_project_filter_separate(self, mock_gener @django_db_all @patch( - "sentry.api.endpoints.organization_feedback_summary.generate_summary", + "sentry.feedback.endpoints.organization_feedback_summary.generate_summary", return_value="Test summary of feedback", ) def test_get_feedback_summary_too_few_feedbacks(self, mock_generate_summary): @@ -192,11 +192,11 @@ def test_get_feedback_summary_too_few_feedbacks(self, mock_generate_summary): @django_db_all @patch( - "sentry.api.endpoints.organization_feedback_summary.generate_summary", + "sentry.feedback.endpoints.organization_feedback_summary.generate_summary", return_value="Test summary of feedback", ) @patch( - "sentry.api.endpoints.organization_feedback_summary.MAX_FEEDBACKS_TO_SUMMARIZE_CHARS", + "sentry.feedback.endpoints.organization_feedback_summary.MAX_FEEDBACKS_TO_SUMMARIZE_CHARS", 1000, ) def test_get_feedback_summary_character_limit(self, mock_generate_summary): @@ -226,10 +226,10 @@ def test_get_feedback_summary_character_limit(self, mock_generate_summary): @django_db_all @patch( - "sentry.api.endpoints.organization_feedback_summary.generate_summary", + "sentry.feedback.endpoints.organization_feedback_summary.generate_summary", return_value="Test summary of feedback", ) - @patch("sentry.api.endpoints.organization_feedback_summary.cache") + @patch("sentry.feedback.endpoints.organization_feedback_summary.cache") def test_get_feedback_summary_cache_hit(self, mock_cache, mock_generate_summary): mock_cache.get.return_value = { "summary": "Test cached summary of feedback", @@ -254,10 +254,10 @@ def test_get_feedback_summary_cache_hit(self, mock_cache, mock_generate_summary) @django_db_all @patch( - "sentry.api.endpoints.organization_feedback_summary.generate_summary", + "sentry.feedback.endpoints.organization_feedback_summary.generate_summary", return_value="Test summary of feedback", ) - @patch("sentry.api.endpoints.organization_feedback_summary.cache") + @patch("sentry.feedback.endpoints.organization_feedback_summary.cache") def test_get_feedback_summary_cache_miss(self, mock_cache, mock_generate_summary): mock_cache.get.return_value = None diff --git a/tests/sentry/api/endpoints/test_organization_user_reports.py b/tests/sentry/feedback/endpoints/test_organization_user_reports.py similarity index 99% rename from tests/sentry/api/endpoints/test_organization_user_reports.py rename to tests/sentry/feedback/endpoints/test_organization_user_reports.py index 59bf2fb93e54af..328671dd782ad6 100644 --- a/tests/sentry/api/endpoints/test_organization_user_reports.py +++ b/tests/sentry/feedback/endpoints/test_organization_user_reports.py @@ -3,7 +3,7 @@ from sentry.feedback.lib.types import UserReportDict from sentry.feedback.lib.utils import FeedbackCreationSource -from sentry.ingest.userreport import save_userreport +from sentry.feedback.usecases.ingest.userreport import save_userreport from sentry.models.group import GroupStatus from sentry.models.userreport import UserReport from sentry.testutils.cases import APITestCase, SnubaTestCase diff --git a/tests/sentry/api/endpoints/test_project_user_reports.py b/tests/sentry/feedback/endpoints/test_project_user_reports.py similarity index 98% rename from tests/sentry/api/endpoints/test_project_user_reports.py rename to tests/sentry/feedback/endpoints/test_project_user_reports.py index 1bb405823f8c96..0911bd525beeab 100644 --- a/tests/sentry/api/endpoints/test_project_user_reports.py +++ b/tests/sentry/feedback/endpoints/test_project_user_reports.py @@ -415,7 +415,7 @@ def test_environments(self): == self.environment.id ) - @patch("sentry.feedback.usecases.create_feedback.produce_occurrence_to_kafka") + @patch("sentry.feedback.usecases.ingest.create_feedback.produce_occurrence_to_kafka") def test_simple_shim_to_feedback(self, mock_produce_occurrence_to_kafka): replay_id = "b" * 32 event_with_replay = self.store_event( @@ -471,7 +471,7 @@ def test_simple_shim_to_feedback(self, mock_produce_occurrence_to_kafka): ) assert mock_event_data["level"] == "error" - @patch("sentry.feedback.usecases.create_feedback.produce_occurrence_to_kafka") + @patch("sentry.feedback.usecases.ingest.create_feedback.produce_occurrence_to_kafka") def test_simple_shim_to_feedback_no_event_should_not_call( self, mock_produce_occurrence_to_kafka ): @@ -499,7 +499,7 @@ def test_simple_shim_to_feedback_no_event_should_not_call( assert len(mock_produce_occurrence_to_kafka.mock_calls) == 0 - @patch("sentry.ingest.userreport.validate_user_report") + @patch("sentry.feedback.usecases.ingest.userreport.validate_user_report") def test_validation_error(self, mock_validate_user_report): mock_validate_user_report.return_value = (True, "data_invalid", "Data invalid") self.login_as(user=self.user) @@ -518,7 +518,7 @@ def test_validation_error(self, mock_validate_user_report): assert response.status_code == 400, response.content assert UserReport.objects.count() == 0 - @patch("sentry.ingest.userreport.is_in_feedback_denylist") + @patch("sentry.feedback.usecases.ingest.userreport.is_in_feedback_denylist") def test_denylist(self, mock_is_in_feedback_denylist): mock_is_in_feedback_denylist.return_value = True self.login_as(user=self.user) diff --git a/tests/sentry/feedback/test_utils.py b/tests/sentry/feedback/lib/test_feedback_utils.py similarity index 100% rename from tests/sentry/feedback/test_utils.py rename to tests/sentry/feedback/lib/test_feedback_utils.py diff --git a/tests/sentry/feedback/conftest.py b/tests/sentry/feedback/usecases/conftest.py similarity index 87% rename from tests/sentry/feedback/conftest.py rename to tests/sentry/feedback/usecases/conftest.py index 56a4ccac16c26d..8409bf866f99d4 100644 --- a/tests/sentry/feedback/conftest.py +++ b/tests/sentry/feedback/usecases/conftest.py @@ -7,7 +7,7 @@ def mock_produce_occurrence_to_kafka(monkeypatch): mock = Mock() monkeypatch.setattr( - "sentry.feedback.usecases.create_feedback.produce_occurrence_to_kafka", mock + "sentry.feedback.usecases.ingest.create_feedback.produce_occurrence_to_kafka", mock ) return mock diff --git a/tests/sentry/feedback/test_create_feedback.py b/tests/sentry/feedback/usecases/ingest/test_create_feedback.py similarity index 99% rename from tests/sentry/feedback/test_create_feedback.py rename to tests/sentry/feedback/usecases/ingest/test_create_feedback.py index 1972edfa5969bb..3401bebc053b36 100644 --- a/tests/sentry/feedback/test_create_feedback.py +++ b/tests/sentry/feedback/usecases/ingest/test_create_feedback.py @@ -7,7 +7,7 @@ import pytest from sentry.feedback.lib.utils import FeedbackCreationSource -from sentry.feedback.usecases.create_feedback import ( +from sentry.feedback.usecases.ingest.create_feedback import ( create_feedback_issue, fix_for_issue_platform, get_feedback_title, @@ -839,7 +839,7 @@ def test_create_feedback_evidence_has_spam( default_project, mock_produce_occurrence_to_kafka, monkeypatch ): """We need this evidence field in post process, to determine if we should send alerts.""" - monkeypatch.setattr("sentry.feedback.usecases.create_feedback.is_spam", lambda _: True) + monkeypatch.setattr("sentry.feedback.usecases.ingest.create_feedback.is_spam", lambda _: True) default_project.update_option("sentry:feedback_ai_spam_detection", True) with Feature({"organizations:user-feedback-spam-ingest": True}): diff --git a/tests/sentry/feedback/test_save_feedback_event.py b/tests/sentry/feedback/usecases/ingest/test_save_event_feedback.py similarity index 87% rename from tests/sentry/feedback/test_save_feedback_event.py rename to tests/sentry/feedback/usecases/ingest/test_save_event_feedback.py index 6495b7c577288e..28e8b23adf34cb 100644 --- a/tests/sentry/feedback/test_save_feedback_event.py +++ b/tests/sentry/feedback/usecases/ingest/test_save_event_feedback.py @@ -5,7 +5,7 @@ from sentry.eventstore.models import Event from sentry.feedback.lib.utils import FeedbackCreationSource -from sentry.feedback.usecases.save_feedback_event import save_feedback_event +from sentry.feedback.usecases.ingest.save_event_feedback import save_event_feedback from sentry.models.environment import Environment from sentry.models.userreport import UserReport from sentry.testutils.factories import Factories @@ -15,7 +15,9 @@ @pytest.fixture def mock_create_feedback_issue(): - with mock.patch("sentry.feedback.usecases.save_feedback_event.create_feedback_issue") as m: + with mock.patch( + "sentry.feedback.usecases.ingest.save_event_feedback.create_feedback_issue" + ) as m: yield m @@ -30,11 +32,11 @@ def create_test_event(project_id: int, environment: Environment) -> Event: @django_db_all -def test_save_feedback_event_no_associated_event(default_project, mock_create_feedback_issue): +def test_save_event_feedback_no_associated_event(default_project, mock_create_feedback_issue): event_data = mock_feedback_event(default_project.id) mock_create_feedback_issue.return_value = None - save_feedback_event(event_data, default_project.id) + save_event_feedback(event_data, default_project.id) mock_create_feedback_issue.assert_called_once_with( event_data, default_project.id, FeedbackCreationSource.NEW_FEEDBACK_ENVELOPE @@ -47,7 +49,7 @@ def test_save_feedback_event_no_associated_event(default_project, mock_create_fe "timestamp_format", ["number", "iso"], ) -def test_save_feedback_event_with_associated_event( +def test_save_event_feedback_with_associated_event( default_project, mock_create_feedback_issue, timestamp_format ): environment = Factories.create_environment(default_project, name="production") @@ -63,7 +65,7 @@ def test_save_feedback_event_with_associated_event( event_data["environment"] = "production" mock_create_feedback_issue.return_value = event_data - save_feedback_event(event_data, default_project.id) + save_event_feedback(event_data, default_project.id) mock_create_feedback_issue.assert_called_once_with( event_data, default_project.id, FeedbackCreationSource.NEW_FEEDBACK_ENVELOPE @@ -83,7 +85,7 @@ def test_save_feedback_event_with_associated_event( @django_db_all -def test_save_feedback_event_with_unprocessed_associated_event( +def test_save_event_feedback_with_unprocessed_associated_event( default_project, mock_create_feedback_issue, ): @@ -95,7 +97,7 @@ def test_save_feedback_event_with_unprocessed_associated_event( event_data["environment"] = "production" mock_create_feedback_issue.return_value = event_data - save_feedback_event(event_data, default_project.id) + save_event_feedback(event_data, default_project.id) mock_create_feedback_issue.assert_called_once_with( event_data, default_project.id, FeedbackCreationSource.NEW_FEEDBACK_ENVELOPE @@ -116,7 +118,7 @@ def test_save_feedback_event_with_unprocessed_associated_event( @django_db_all -def test_save_feedback_event_with_missing_fields(default_project, mock_create_feedback_issue): +def test_save_event_feedback_with_missing_fields(default_project, mock_create_feedback_issue): environment = Factories.create_environment(default_project, name="production") event_data = mock_feedback_event(default_project.id) @@ -130,7 +132,7 @@ def test_save_feedback_event_with_missing_fields(default_project, mock_create_fe mock_create_feedback_issue.return_value = event_data - save_feedback_event(event_data, default_project.id) + save_event_feedback(event_data, default_project.id) mock_create_feedback_issue.assert_called_once_with( event_data, default_project.id, FeedbackCreationSource.NEW_FEEDBACK_ENVELOPE diff --git a/tests/sentry/feedback/test_shim_to_feedback.py b/tests/sentry/feedback/usecases/ingest/test_shim_to_feedback.py similarity index 96% rename from tests/sentry/feedback/test_shim_to_feedback.py rename to tests/sentry/feedback/usecases/ingest/test_shim_to_feedback.py index bf526141f4b469..18085a65800f83 100644 --- a/tests/sentry/feedback/test_shim_to_feedback.py +++ b/tests/sentry/feedback/usecases/ingest/test_shim_to_feedback.py @@ -8,7 +8,7 @@ from sentry.eventstore.models import Event from sentry.feedback.lib.utils import FeedbackCreationSource -from sentry.feedback.usecases.shim_to_feedback import shim_to_feedback +from sentry.feedback.usecases.ingest.shim_to_feedback import shim_to_feedback from sentry.testutils.factories import Factories from sentry.testutils.pytest.fixtures import django_db_all @@ -111,7 +111,7 @@ def test_shim_to_feedback_fails_if_required_fields_missing(default_project, monk # Email and comments are required to shim. Tests key errors are handled. mock_create_feedback_issue = Mock() monkeypatch.setattr( - "sentry.feedback.usecases.shim_to_feedback.create_feedback_issue", + "sentry.feedback.usecases.ingest.shim_to_feedback.create_feedback_issue", mock_create_feedback_issue, ) report_dict = { diff --git a/tests/sentry/ingest/test_userreport.py b/tests/sentry/feedback/usecases/ingest/test_userreport.py similarity index 93% rename from tests/sentry/ingest/test_userreport.py rename to tests/sentry/feedback/usecases/ingest/test_userreport.py index f5611ee99b4f23..b39c3a0fd1356e 100644 --- a/tests/sentry/ingest/test_userreport.py +++ b/tests/sentry/feedback/usecases/ingest/test_userreport.py @@ -5,7 +5,7 @@ from sentry.feedback.lib.types import UserReportDict from sentry.feedback.lib.utils import UNREAL_FEEDBACK_UNATTENDED_MESSAGE, FeedbackCreationSource -from sentry.ingest.userreport import save_userreport, validate_user_report +from sentry.feedback.usecases.ingest.userreport import save_userreport, validate_user_report from sentry.models.userreport import UserReport from sentry.testutils.factories import Factories from sentry.testutils.pytest.fixtures import django_db_all @@ -30,13 +30,15 @@ def mock_report_dict() -> UserReportDict: @pytest.fixture def skip_denylist(monkeypatch): - monkeypatch.setattr("sentry.ingest.userreport.is_in_feedback_denylist", lambda org: False) + monkeypatch.setattr( + "sentry.feedback.usecases.ingest.userreport.is_in_feedback_denylist", lambda org: False + ) @pytest.fixture def skip_filters(monkeypatch): monkeypatch.setattr( - "sentry.ingest.userreport.validate_user_report", + "sentry.feedback.usecases.ingest.userreport.validate_user_report", Mock(return_value=(False, None, None)), ) @@ -219,7 +221,9 @@ def test_save_user_report_basic( def test_save_user_report_filters_denylist( default_project, monkeypatch, skip_filters, mock_report_dict ): - monkeypatch.setattr("sentry.ingest.userreport.is_in_feedback_denylist", lambda org: True) + monkeypatch.setattr( + "sentry.feedback.usecases.ingest.userreport.is_in_feedback_denylist", lambda org: True + ) result = save_userreport( default_project, mock_report_dict, FeedbackCreationSource.USER_REPORT_ENVELOPE ) @@ -286,7 +290,9 @@ def test_save_user_report_shims_if_event_found( ) mock_shim_to_feedback = Mock() - monkeypatch.setattr("sentry.ingest.userreport.shim_to_feedback", mock_shim_to_feedback) + monkeypatch.setattr( + "sentry.feedback.usecases.ingest.userreport.shim_to_feedback", mock_shim_to_feedback + ) mock_report_dict["event_id"] = event.event_id @@ -305,7 +311,9 @@ def test_save_user_report_does_not_shim_if_event_found_but_source_is_new_feedbac ) mock_shim_to_feedback = Mock() - monkeypatch.setattr("sentry.ingest.userreport.shim_to_feedback", mock_shim_to_feedback) + monkeypatch.setattr( + "sentry.feedback.usecases.ingest.userreport.shim_to_feedback", mock_shim_to_feedback + ) # Source is new feedback, so no shim save_userreport( default_project, diff --git a/tests/sentry/feedback/test_feedback_summaries.py b/tests/sentry/feedback/usecases/test_feedback_summaries.py similarity index 100% rename from tests/sentry/feedback/test_feedback_summaries.py rename to tests/sentry/feedback/usecases/test_feedback_summaries.py diff --git a/tests/sentry/feedback/test_spam_detection.py b/tests/sentry/feedback/usecases/test_spam_detection.py similarity index 100% rename from tests/sentry/feedback/test_spam_detection.py rename to tests/sentry/feedback/usecases/test_spam_detection.py diff --git a/tests/sentry/issues/endpoints/test_organization_group_index.py b/tests/sentry/issues/endpoints/test_organization_group_index.py index a7009a2dbfa6f2..3713483ba574a7 100644 --- a/tests/sentry/issues/endpoints/test_organization_group_index.py +++ b/tests/sentry/issues/endpoints/test_organization_group_index.py @@ -13,7 +13,7 @@ from sentry import options from sentry.feedback.lib.utils import FeedbackCreationSource -from sentry.feedback.usecases.create_feedback import create_feedback_issue +from sentry.feedback.usecases.ingest.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 a4666e11abf2c5..311dc565057248 100644 --- a/tests/sentry/tasks/test_post_process.py +++ b/tests/sentry/tasks/test_post_process.py @@ -19,7 +19,7 @@ from sentry.eventstore.processing import event_processing_store from sentry.eventstream.types import EventStreamEventType from sentry.feedback.lib.utils import FeedbackCreationSource -from sentry.feedback.usecases.create_feedback import get_feedback_title +from sentry.feedback.usecases.ingest.create_feedback import get_feedback_title 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 @@ -2050,7 +2050,7 @@ def test_user_report_gets_environment_with_new_link_features(self): assert UserReport.objects.get(event_id=event_id).environment_id == environment.id - @patch("sentry.feedback.usecases.create_feedback.produce_occurrence_to_kafka") + @patch("sentry.feedback.usecases.ingest.create_feedback.produce_occurrence_to_kafka") def test_user_report_shims_to_feedback(self, mock_produce_occurrence_to_kafka): project = self.create_project() environment = Environment.objects.create( @@ -2104,7 +2104,7 @@ def test_user_report_shims_to_feedback(self, mock_produce_occurrence_to_kafka): mock_event_data["contexts"]["feedback"]["message"] ) - @patch("sentry.feedback.usecases.create_feedback.produce_occurrence_to_kafka") + @patch("sentry.feedback.usecases.ingest.create_feedback.produce_occurrence_to_kafka") def test_user_reports_no_shim_if_group_exists_on_report(self, mock_produce_occurrence_to_kafka): project = self.create_project() environment = Environment.objects.create( diff --git a/tests/sentry/tasks/test_update_user_reports.py b/tests/sentry/tasks/test_update_user_reports.py index 794f7f9e5e8265..11a5bd0ce97485 100644 --- a/tests/sentry/tasks/test_update_user_reports.py +++ b/tests/sentry/tasks/test_update_user_reports.py @@ -126,7 +126,7 @@ def test_event_timerange(self): assert report4.group_id is None assert report4.environment_id is None - @patch("sentry.feedback.usecases.create_feedback.produce_occurrence_to_kafka") + @patch("sentry.feedback.usecases.ingest.create_feedback.produce_occurrence_to_kafka") def test_calls_feedback_shim_if_ff_enabled(self, mock_produce_occurrence_to_kafka): project = self.create_project() event1 = self.store_event( @@ -166,7 +166,7 @@ def test_calls_feedback_shim_if_ff_enabled(self, mock_produce_occurrence_to_kafk assert mock_event_data["contexts"]["feedback"]["associated_event_id"] == event1.event_id assert mock_event_data["level"] == "error" - @patch("sentry.feedback.usecases.create_feedback.produce_occurrence_to_kafka") + @patch("sentry.feedback.usecases.ingest.create_feedback.produce_occurrence_to_kafka") def test_does_not_call_feedback_shim_if_environment_is_set( self, mock_produce_occurrence_to_kafka ): diff --git a/tests/sentry/web/frontend/test_error_page_embed.py b/tests/sentry/web/frontend/test_error_page_embed.py index 79b5be23dc4a64..3cefcb406e276a 100644 --- a/tests/sentry/web/frontend/test_error_page_embed.py +++ b/tests/sentry/web/frontend/test_error_page_embed.py @@ -242,7 +242,7 @@ def test_environment_gets_user_report(self): assert response.status_code == 200, response.content assert UserReport.objects.get(event_id=self.event_id).environment_id == self.environment.id - @mock.patch("sentry.feedback.usecases.create_feedback.produce_occurrence_to_kafka") + @mock.patch("sentry.feedback.usecases.ingest.create_feedback.produce_occurrence_to_kafka") def test_calls_feedback_shim_if_ff_enabled(self, mock_produce_occurrence_to_kafka): self.make_event(environment=self.environment.name, event_id=self.event_id) self.client.post( @@ -264,7 +264,7 @@ def test_calls_feedback_shim_if_ff_enabled(self, mock_produce_occurrence_to_kafk assert mock_event_data["contexts"]["feedback"]["associated_event_id"] == self.event_id assert mock_event_data["level"] == "error" - @mock.patch("sentry.feedback.usecases.create_feedback.produce_occurrence_to_kafka") + @mock.patch("sentry.feedback.usecases.ingest.create_feedback.produce_occurrence_to_kafka") def test_does_not_call_feedback_shim_no_event_if_ff_enabled( self, mock_produce_occurrence_to_kafka ):