From ec49ed7abba097e10bffd10651abd27bf1d5340c Mon Sep 17 00:00:00 2001 From: Fabian Schindler Date: Thu, 10 Jul 2025 09:24:06 +0200 Subject: [PATCH] ref(analytics): Transform analytics events for TET-829 - Transform event classes to use @analytics.eventclass decorator - Transform analytics.record calls to use event class instances - Update imports as needed Closes TET-829 --- .../analytics/events/codeowners_updated.py | 13 +- .../analytics/events/first_feedback_sent.py | 13 +- .../analytics/events/first_flag_sent.py | 11 +- .../events/first_release_tag_sent.py | 11 +- .../analytics/events/first_replay_sent.py | 13 +- .../events/integration_serverless_setup.py | 15 +- .../analytics/events/open_pr_comment.py | 13 +- .../events/project_issue_searched.py | 13 +- .../analytics/events/second_platform_added.py | 13 +- src/sentry/analytics/events/weekly_report.py | 13 +- src/sentry/api/analytics.py | 64 ++++---- .../api/endpoints/codeowners/details.py | 12 +- .../endpoints/organization_search_details.py | 10 +- src/sentry/integrations/analytics.py | 147 +++++++----------- .../integrations/aws_lambda/integration.py | 14 +- .../source_code_management/commit_context.py | 12 +- .../integrations/tasks/create_comment.py | 13 +- .../tasks/sync_assignee_outbound.py | 10 +- .../tasks/sync_status_outbound.py | 10 +- .../integrations/tasks/update_comment.py | 13 +- src/sentry/issues/analytics.py | 5 +- .../group_similar_issues_embeddings.py | 28 ++-- .../issues/endpoints/organization_searches.py | 10 +- .../issues/endpoints/project_group_index.py | 12 +- .../endpoints/project_stacktrace_link.py | 20 +-- src/sentry/issues/escalating/forecasts.py | 4 +- src/sentry/middleware/devtoolbar.py | 26 ++-- .../providers/integration_repository.py | 10 +- src/sentry/receivers/features.py | 44 +++--- src/sentry/receivers/onboarding.py | 54 ++++--- src/sentry/receivers/releases.py | 19 ++- src/sentry/tasks/summaries/weekly_reports.py | 12 +- 32 files changed, 326 insertions(+), 351 deletions(-) diff --git a/src/sentry/analytics/events/codeowners_updated.py b/src/sentry/analytics/events/codeowners_updated.py index 16e2db3bb4eefe..3934b1e1f3e64f 100644 --- a/src/sentry/analytics/events/codeowners_updated.py +++ b/src/sentry/analytics/events/codeowners_updated.py @@ -1,15 +1,12 @@ from sentry import analytics +@analytics.eventclass("codeowners.updated") class CodeownersUpdated(analytics.Event): - type = "codeowners.updated" - - attributes = ( - analytics.Attribute("user_id", required=False), - analytics.Attribute("organization_id"), - analytics.Attribute("project_id"), - analytics.Attribute("codeowners_id"), - ) + user_id: str | None = None + organization_id: str + project_id: str + codeowners_id: str analytics.register(CodeownersUpdated) diff --git a/src/sentry/analytics/events/first_feedback_sent.py b/src/sentry/analytics/events/first_feedback_sent.py index 5935d14ba98e9f..f799441c178ca2 100644 --- a/src/sentry/analytics/events/first_feedback_sent.py +++ b/src/sentry/analytics/events/first_feedback_sent.py @@ -1,15 +1,12 @@ from sentry import analytics +@analytics.eventclass("first_feedback.sent") class FirstFeedbackSentEvent(analytics.Event): - type = "first_feedback.sent" - - attributes = ( - analytics.Attribute("organization_id"), - analytics.Attribute("project_id"), - analytics.Attribute("platform", required=False), - analytics.Attribute("user_id", required=False), - ) + organization_id: str + project_id: str + platform: str | None = None + user_id: str | None = None analytics.register(FirstFeedbackSentEvent) diff --git a/src/sentry/analytics/events/first_flag_sent.py b/src/sentry/analytics/events/first_flag_sent.py index 04d607a41e871f..b680b7a9f8c163 100644 --- a/src/sentry/analytics/events/first_flag_sent.py +++ b/src/sentry/analytics/events/first_flag_sent.py @@ -1,14 +1,11 @@ from sentry import analytics +@analytics.eventclass("first_flag.sent") class FirstFlagSentEvent(analytics.Event): - type = "first_flag.sent" - - attributes = ( - analytics.Attribute("organization_id"), - analytics.Attribute("project_id"), - analytics.Attribute("platform", required=False), - ) + organization_id: str + project_id: str + platform: str | None = None analytics.register(FirstFlagSentEvent) diff --git a/src/sentry/analytics/events/first_release_tag_sent.py b/src/sentry/analytics/events/first_release_tag_sent.py index 7e142c8ce5bd2d..7a8461406252b5 100644 --- a/src/sentry/analytics/events/first_release_tag_sent.py +++ b/src/sentry/analytics/events/first_release_tag_sent.py @@ -1,14 +1,11 @@ from sentry import analytics +@analytics.eventclass("first_release_tag.sent") class FirstReleaseTagSentEvent(analytics.Event): - type = "first_release_tag.sent" - - attributes = ( - analytics.Attribute("user_id"), - analytics.Attribute("organization_id"), - analytics.Attribute("project_id"), - ) + user_id: str + organization_id: str + project_id: str analytics.register(FirstReleaseTagSentEvent) diff --git a/src/sentry/analytics/events/first_replay_sent.py b/src/sentry/analytics/events/first_replay_sent.py index 3c94f754d5d704..1aff85d907c01c 100644 --- a/src/sentry/analytics/events/first_replay_sent.py +++ b/src/sentry/analytics/events/first_replay_sent.py @@ -1,15 +1,12 @@ from sentry import analytics +@analytics.eventclass("first_replay.sent") class FirstReplaySentEvent(analytics.Event): - type = "first_replay.sent" - - attributes = ( - analytics.Attribute("organization_id"), - analytics.Attribute("project_id"), - analytics.Attribute("platform", required=False), - analytics.Attribute("user_id", required=False), - ) + organization_id: str + project_id: str + platform: str | None = None + user_id: str | None = None analytics.register(FirstReplaySentEvent) diff --git a/src/sentry/analytics/events/integration_serverless_setup.py b/src/sentry/analytics/events/integration_serverless_setup.py index 9c59ca93b10b1c..a46435bf9931a9 100644 --- a/src/sentry/analytics/events/integration_serverless_setup.py +++ b/src/sentry/analytics/events/integration_serverless_setup.py @@ -1,16 +1,13 @@ from sentry import analytics +@analytics.eventclass("integrations.serverless_setup") class IntegrationServerlessSetup(analytics.Event): - type = "integrations.serverless_setup" - - attributes = ( - analytics.Attribute("user_id"), - analytics.Attribute("organization_id"), - analytics.Attribute("integration"), - analytics.Attribute("success_count"), - analytics.Attribute("failure_count"), - ) + user_id: str + organization_id: str + integration: str + success_count: str + failure_count: str analytics.register(IntegrationServerlessSetup) diff --git a/src/sentry/analytics/events/open_pr_comment.py b/src/sentry/analytics/events/open_pr_comment.py index f325c3d568a3a6..ef1ab96342e2bd 100644 --- a/src/sentry/analytics/events/open_pr_comment.py +++ b/src/sentry/analytics/events/open_pr_comment.py @@ -1,15 +1,12 @@ from sentry import analytics +@analytics.eventclass("open_pr_comment.created") class OpenPRCommentCreatedEvent(analytics.Event): - type = "open_pr_comment.created" - - attributes = ( - analytics.Attribute("comment_id"), - analytics.Attribute("org_id"), - analytics.Attribute("pr_id"), - analytics.Attribute("language"), - ) + comment_id: str + org_id: str + pr_id: str + language: str analytics.register(OpenPRCommentCreatedEvent) diff --git a/src/sentry/analytics/events/project_issue_searched.py b/src/sentry/analytics/events/project_issue_searched.py index 50fab2c5ad6da8..11c6f600f4d58e 100644 --- a/src/sentry/analytics/events/project_issue_searched.py +++ b/src/sentry/analytics/events/project_issue_searched.py @@ -1,15 +1,12 @@ from sentry import analytics +@analytics.eventclass("project_issue.searched") class ProjectIssueSearchEvent(analytics.Event): - type = "project_issue.searched" - - attributes = ( - analytics.Attribute("user_id", required=False), - analytics.Attribute("organization_id"), - analytics.Attribute("project_id"), - analytics.Attribute("query"), - ) + user_id: str | None = None + organization_id: str + project_id: str + query: str analytics.register(ProjectIssueSearchEvent) diff --git a/src/sentry/analytics/events/second_platform_added.py b/src/sentry/analytics/events/second_platform_added.py index 2469ea95f5ade1..1dd9fb4a734950 100644 --- a/src/sentry/analytics/events/second_platform_added.py +++ b/src/sentry/analytics/events/second_platform_added.py @@ -1,15 +1,12 @@ from sentry import analytics +@analytics.eventclass("second_platform.added") class SecondPlatformAddedEvent(analytics.Event): - type = "second_platform.added" - - attributes = ( - analytics.Attribute("user_id"), - analytics.Attribute("organization_id"), - analytics.Attribute("project_id"), - analytics.Attribute("platform", required=False), - ) + user_id: str + organization_id: str + project_id: str + platform: str | None = None analytics.register(SecondPlatformAddedEvent) diff --git a/src/sentry/analytics/events/weekly_report.py b/src/sentry/analytics/events/weekly_report.py index 6f0d28b53edbc0..2e61c0cede9c3d 100644 --- a/src/sentry/analytics/events/weekly_report.py +++ b/src/sentry/analytics/events/weekly_report.py @@ -1,15 +1,12 @@ from sentry import analytics +@analytics.eventclass("weekly_report.sent") class WeeklyReportSent(analytics.Event): - type = "weekly_report.sent" - - attributes = ( - analytics.Attribute("organization_id"), - analytics.Attribute("user_id"), - analytics.Attribute("notification_uuid"), - analytics.Attribute("user_project_count", type=int), - ) + organization_id: str + user_id: str + notification_uuid: str + user_project_count: int analytics.register(WeeklyReportSent) diff --git a/src/sentry/api/analytics.py b/src/sentry/api/analytics.py index e875453f138fea..3ed4184d48093b 100644 --- a/src/sentry/api/analytics.py +++ b/src/sentry/api/analytics.py @@ -1,54 +1,42 @@ from sentry import analytics +@analytics.eventclass("organization_saved_search.created") class OrganizationSavedSearchCreatedEvent(analytics.Event): - type = "organization_saved_search.created" - - attributes = ( - analytics.Attribute("org_id"), - analytics.Attribute("search_type"), - analytics.Attribute("query"), - ) + org_id: str + search_type: str + query: str +@analytics.eventclass("organization_saved_search.deleted") class OrganizationSavedSearchDeletedEvent(analytics.Event): - type = "organization_saved_search.deleted" - - attributes = ( - analytics.Attribute("org_id"), - analytics.Attribute("search_type"), - analytics.Attribute("query"), - ) + org_id: str + search_type: str + query: str +@analytics.eventclass("group_similar_issues_embeddings.count") class GroupSimilarIssuesEmbeddingsCountEvent(analytics.Event): - type = "group_similar_issues_embeddings.count" - - attributes = ( - analytics.Attribute("organization_id"), - analytics.Attribute("project_id"), - analytics.Attribute("group_id"), - analytics.Attribute("user_id"), - analytics.Attribute("count_over_threshold", required=False), - ) + organization_id: str + project_id: str + group_id: str + user_id: str + count_over_threshold: str | None = None +@analytics.eventclass("devtoolbar.api_request") class DevToolbarApiRequestEvent(analytics.Event): - type = "devtoolbar.api_request" - - attributes = ( - analytics.Attribute("view_name"), - analytics.Attribute("route"), - analytics.Attribute("query_string", required=False), - analytics.Attribute("origin", required=False), - analytics.Attribute("method"), - analytics.Attribute("status_code", type=int), - analytics.Attribute("organization_id", type=int, required=False), - analytics.Attribute("organization_slug", required=False), - analytics.Attribute("project_id", type=int, required=False), - analytics.Attribute("project_slug", required=False), - analytics.Attribute("user_id", type=int, required=False), - ) + view_name: str + route: str + query_string: str | None = None + origin: str | None = None + method: str + status_code: int + organization_id: int | None = None + organization_slug: str | None = None + project_id: int | None = None + project_slug: str | None = None + user_id: int | None = None analytics.register(OrganizationSavedSearchCreatedEvent) diff --git a/src/sentry/api/endpoints/codeowners/details.py b/src/sentry/api/endpoints/codeowners/details.py index f8d872f8d6ac88..348b31593545e5 100644 --- a/src/sentry/api/endpoints/codeowners/details.py +++ b/src/sentry/api/endpoints/codeowners/details.py @@ -9,6 +9,7 @@ from rest_framework.response import Response from sentry import analytics +from sentry.analytics.events.codeowners_updated import CodeownersUpdated from sentry.api.api_owners import ApiOwner from sentry.api.api_publish_status import ApiPublishStatus from sentry.api.base import region_silo_endpoint @@ -80,11 +81,12 @@ def put(self, request: Request, project: Project, codeowners: ProjectCodeOwners) user_id = getattr(request.user, "id", None) or None analytics.record( - "codeowners.updated", - user_id=user_id, - organization_id=project.organization_id, - project_id=project.id, - codeowners_id=updated_codeowners.id, + CodeownersUpdated( + user_id=user_id, + organization_id=project.organization_id, + project_id=project.id, + codeowners_id=updated_codeowners.id, + ) ) self.track_response_code("update", status.HTTP_200_OK) return Response( diff --git a/src/sentry/api/endpoints/organization_search_details.py b/src/sentry/api/endpoints/organization_search_details.py index 5648abc9dc0ad4..8cf0d27db64831 100644 --- a/src/sentry/api/endpoints/organization_search_details.py +++ b/src/sentry/api/endpoints/organization_search_details.py @@ -3,6 +3,7 @@ from rest_framework.response import Response from sentry import analytics +from sentry.api.analytics import OrganizationSavedSearchDeletedEvent from sentry.api.api_owners import ApiOwner from sentry.api.api_publish_status import ApiPublishStatus from sentry.api.base import region_silo_endpoint @@ -103,9 +104,10 @@ def delete(self, request: Request, organization: Organization, search: SavedSear """ search.delete() analytics.record( - "organization_saved_search.deleted", - search_type=SearchType(search.type).name, - org_id=organization.id, - query=search.query, + OrganizationSavedSearchDeletedEvent( + search_type=SearchType(search.type).name, + org_id=organization.id, + query=search.query, + ) ) return Response(status=204) diff --git a/src/sentry/integrations/analytics.py b/src/sentry/integrations/analytics.py index 2f155d92537095..8578dbe81d95af 100644 --- a/src/sentry/integrations/analytics.py +++ b/src/sentry/integrations/analytics.py @@ -1,127 +1,94 @@ from sentry import analytics +@analytics.eventclass("integration.added") class IntegrationAddedEvent(analytics.Event): - type = "integration.added" - - attributes = ( - analytics.Attribute("provider"), - analytics.Attribute("id"), - analytics.Attribute("organization_id"), - analytics.Attribute("user_id", required=False), - analytics.Attribute("default_user_id"), - ) + provider: str + id: str + organization_id: str + user_id: str | None = None + default_user_id: str +@analytics.eventclass("integration.disabled.notified") class IntegrationDisabledNotified(analytics.Event): - type = "integration.disabled.notified" - - attributes = ( - analytics.Attribute("organization_id"), - analytics.Attribute("provider"), - analytics.Attribute("integration_type"), - analytics.Attribute("integration_id"), - analytics.Attribute("user_id", required=False), - ) + organization_id: str + provider: str + integration_type: str + integration_id: str + user_id: str | None = None +@analytics.eventclass("integration.issue.created") class IntegrationIssueCreatedEvent(analytics.Event): - type = "integration.issue.created" - - attributes = ( - analytics.Attribute("provider"), - analytics.Attribute("id"), - analytics.Attribute("organization_id"), - analytics.Attribute("user_id", required=False), - analytics.Attribute("default_user_id"), - ) + provider: str + id: str + organization_id: str + user_id: str | None = None + default_user_id: str +@analytics.eventclass("integration.issue.linked") class IntegrationIssueLinkedEvent(analytics.Event): - type = "integration.issue.linked" - - attributes = ( - analytics.Attribute("provider"), - analytics.Attribute("id"), - analytics.Attribute("organization_id"), - analytics.Attribute("user_id", required=False), - analytics.Attribute("default_user_id"), - ) + provider: str + id: str + organization_id: str + user_id: str | None = None + default_user_id: str +@analytics.eventclass("integration.issue.status.synced") class IntegrationIssueStatusSyncedEvent(analytics.Event): - type = "integration.issue.status.synced" - - attributes = ( - analytics.Attribute("provider"), - analytics.Attribute("id"), - analytics.Attribute("organization_id"), - ) + provider: str + id: str + organization_id: str +@analytics.eventclass("integration.issue.assignee.synced") class IntegrationIssueAssigneeSyncedEvent(analytics.Event): - type = "integration.issue.assignee.synced" - - attributes = ( - analytics.Attribute("provider"), - analytics.Attribute("id"), - analytics.Attribute("organization_id"), - ) + provider: str + id: str + organization_id: str +@analytics.eventclass("integration.issue.comments.synced") class IntegrationIssueCommentsSyncedEvent(analytics.Event): - type = "integration.issue.comments.synced" - - attributes = ( - analytics.Attribute("provider"), - analytics.Attribute("id"), - analytics.Attribute("organization_id"), - ) + provider: str + id: str + organization_id: str +@analytics.eventclass("integration.repo.added") class IntegrationRepoAddedEvent(analytics.Event): - type = "integration.repo.added" - - attributes = ( - analytics.Attribute("provider"), - analytics.Attribute("id"), - analytics.Attribute("organization_id"), - ) + provider: str + id: str + organization_id: str +@analytics.eventclass("integration.resolve.commit") class IntegrationResolveCommitEvent(analytics.Event): - type = "integration.resolve.commit" - - attributes = ( - analytics.Attribute("provider"), - analytics.Attribute("id"), - analytics.Attribute("organization_id"), - ) + provider: str + id: str + organization_id: str +@analytics.eventclass("integration.resolve.pr") class IntegrationResolvePREvent(analytics.Event): - type = "integration.resolve.pr" - - attributes = ( - analytics.Attribute("provider"), - analytics.Attribute("id"), - analytics.Attribute("organization_id"), - ) + provider: str + id: str + organization_id: str +@analytics.eventclass("integration.stacktrace.linked") class IntegrationStacktraceLinkEvent(analytics.Event): - type = "integration.stacktrace.linked" - - attributes = ( - analytics.Attribute("provider"), - analytics.Attribute("config_id"), - analytics.Attribute("project_id"), - analytics.Attribute("organization_id"), - analytics.Attribute("filepath"), - analytics.Attribute("status"), - analytics.Attribute("link_fetch_iterations"), - analytics.Attribute("platform", required=False), - ) + provider: str + config_id: str + project_id: str + organization_id: str + filepath: str + status: str + link_fetch_iterations: str + platform: str | None = None def register_analytics() -> None: diff --git a/src/sentry/integrations/aws_lambda/integration.py b/src/sentry/integrations/aws_lambda/integration.py index 36b7b3887a5bcc..12eb3e99992638 100644 --- a/src/sentry/integrations/aws_lambda/integration.py +++ b/src/sentry/integrations/aws_lambda/integration.py @@ -11,6 +11,7 @@ from django.utils.translation import gettext_lazy as _ from sentry import analytics, options +from sentry.analytics.events.integration_serverless_setup import IntegrationServerlessSetup from sentry.integrations.base import ( FeatureDescription, IntegrationData, @@ -452,12 +453,13 @@ def _enable_lambda(function): ) analytics.record( - "integrations.serverless_setup", - user_id=request.user.id, - organization_id=organization.id, - integration="aws_lambda", - success_count=success_count, - failure_count=len(failures), + IntegrationServerlessSetup( + user_id=request.user.id, + organization_id=organization.id, + integration="aws_lambda", + success_count=success_count, + failure_count=len(failures), + ) ) # if we have failures, show them to the user diff --git a/src/sentry/integrations/source_code_management/commit_context.py b/src/sentry/integrations/source_code_management/commit_context.py index 17e2e8c0057c05..bc0c94da425bb7 100644 --- a/src/sentry/integrations/source_code_management/commit_context.py +++ b/src/sentry/integrations/source_code_management/commit_context.py @@ -27,6 +27,7 @@ from snuba_sdk import Request as SnubaRequest from sentry import analytics +from sentry.analytics.events.open_pr_comment import OpenPRCommentCreatedEvent from sentry.auth.exceptions import IdentityNotValid from sentry.integrations.gitlab.constants import GITLAB_CLOUD_BASE_URL from sentry.integrations.models.repository_project_path_config import RepositoryProjectPathConfig @@ -409,11 +410,12 @@ def create_or_update_comment( if comment_type == CommentType.OPEN_PR: analytics.record( - "open_pr_comment.created", - comment_id=comment.id, - org_id=repo.organization_id, - pr_id=pr.id, - language=(language or "not found"), + OpenPRCommentCreatedEvent( + comment_id=comment.id, + org_id=repo.organization_id, + pr_id=pr.id, + language=(language or "not found"), + ) ) else: resp = client.update_pr_comment( diff --git a/src/sentry/integrations/tasks/create_comment.py b/src/sentry/integrations/tasks/create_comment.py index 370bbc03533395..9b5b7a79bbe2c9 100644 --- a/src/sentry/integrations/tasks/create_comment.py +++ b/src/sentry/integrations/tasks/create_comment.py @@ -1,4 +1,5 @@ from sentry import analytics +from sentry.integrations.analytics import IntegrationIssueCommentsSyncedEvent from sentry.integrations.models.external_issue import ExternalIssue from sentry.integrations.models.integration import Integration from sentry.integrations.source_code_management.metrics import ( @@ -68,10 +69,10 @@ def create_comment(external_issue_id: int, user_id: int, group_note_id: int) -> note.data["external_id"] = installation.get_comment_id(comment) note.save() analytics.record( - # TODO(lb): this should be changed and/or specified? - "integration.issue.comments.synced", - provider=installation.model.provider, - id=installation.model.id, - organization_id=external_issue.organization_id, - user_id=user_id, + IntegrationIssueCommentsSyncedEvent( + provider=installation.model.provider, + id=installation.model.id, + organization_id=external_issue.organization_id, + user_id=user_id, + ) ) diff --git a/src/sentry/integrations/tasks/sync_assignee_outbound.py b/src/sentry/integrations/tasks/sync_assignee_outbound.py index c16d51080c7a59..c5c98c0b7edf45 100644 --- a/src/sentry/integrations/tasks/sync_assignee_outbound.py +++ b/src/sentry/integrations/tasks/sync_assignee_outbound.py @@ -3,6 +3,7 @@ from sentry import analytics, features from sentry.constants import ObjectStatus from sentry.exceptions import InvalidConfiguration +from sentry.integrations.analytics import IntegrationIssueAssigneeSyncedEvent from sentry.integrations.errors import OrganizationIntegrationNotFound from sentry.integrations.models.external_issue import ExternalIssue from sentry.integrations.models.integration import Integration @@ -93,10 +94,11 @@ def sync_assignee_outbound( external_issue, user, assign=assign, assignment_source=parsed_assignment_source ) analytics.record( - "integration.issue.assignee.synced", - provider=integration.provider, - id=integration.id, - organization_id=external_issue.organization_id, + IntegrationIssueAssigneeSyncedEvent( + provider=integration.provider, + id=integration.id, + organization_id=external_issue.organization_id, + ) ) except (OrganizationIntegrationNotFound, ApiUnauthorized, InvalidConfiguration) as e: lifecycle.record_halt(halt_reason=e) diff --git a/src/sentry/integrations/tasks/sync_status_outbound.py b/src/sentry/integrations/tasks/sync_status_outbound.py index 1cea6900c38e04..5500f7abc42ac8 100644 --- a/src/sentry/integrations/tasks/sync_status_outbound.py +++ b/src/sentry/integrations/tasks/sync_status_outbound.py @@ -1,6 +1,7 @@ from sentry import analytics, features from sentry.constants import ObjectStatus from sentry.exceptions import InvalidIdentity +from sentry.integrations.analytics import IntegrationIssueStatusSyncedEvent from sentry.integrations.base import IntegrationInstallation from sentry.integrations.errors import OrganizationIntegrationNotFound from sentry.integrations.models.external_issue import ExternalIssue @@ -84,10 +85,11 @@ def sync_status_outbound(group_id: int, external_issue_id: int) -> bool | None: ) analytics.record( - "integration.issue.status.synced", - provider=integration.provider, - id=integration.id, - organization_id=external_issue.organization_id, + IntegrationIssueStatusSyncedEvent( + provider=integration.provider, + id=integration.id, + organization_id=external_issue.organization_id, + ) ) except ( IntegrationFormError, diff --git a/src/sentry/integrations/tasks/update_comment.py b/src/sentry/integrations/tasks/update_comment.py index 25a1ebc11c8ca5..3f79a1db86c29a 100644 --- a/src/sentry/integrations/tasks/update_comment.py +++ b/src/sentry/integrations/tasks/update_comment.py @@ -1,4 +1,5 @@ from sentry import analytics +from sentry.integrations.analytics import IntegrationIssueCommentsSyncedEvent from sentry.integrations.models.external_issue import ExternalIssue from sentry.integrations.models.integration import Integration from sentry.integrations.source_code_management.metrics import ( @@ -68,10 +69,10 @@ def update_comment(external_issue_id: int, user_id: int, group_note_id: int) -> ) installation.update_comment(external_issue.key, user_id, note) analytics.record( - # TODO(lb): this should be changed and/or specified? - "integration.issue.comments.synced", - provider=installation.model.provider, - id=installation.model.id, - organization_id=external_issue.organization_id, - user_id=user_id, + IntegrationIssueCommentsSyncedEvent( + provider=installation.model.provider, + id=installation.model.id, + organization_id=external_issue.organization_id, + user_id=user_id, + ) ) diff --git a/src/sentry/issues/analytics.py b/src/sentry/issues/analytics.py index 9296773062a347..6d92db6c7337b5 100644 --- a/src/sentry/issues/analytics.py +++ b/src/sentry/issues/analytics.py @@ -1,10 +1,9 @@ from sentry import analytics +@analytics.eventclass("issue_forecasts.saved") class IssueForecastSaved(analytics.Event): - type = "issue_forecasts.saved" - - attributes = (analytics.Attribute("num_groups"),) + num_groups: str analytics.register(IssueForecastSaved) diff --git a/src/sentry/issues/endpoints/group_similar_issues_embeddings.py b/src/sentry/issues/endpoints/group_similar_issues_embeddings.py index 4b680bd158f931..91bb208106deba 100644 --- a/src/sentry/issues/endpoints/group_similar_issues_embeddings.py +++ b/src/sentry/issues/endpoints/group_similar_issues_embeddings.py @@ -7,6 +7,7 @@ from rest_framework.response import Response from sentry import analytics, options +from sentry.api.analytics import GroupSimilarIssuesEmbeddingsCountEvent from sentry.api.api_owners import ApiOwner from sentry.api.api_publish_status import ApiPublishStatus from sentry.api.base import region_silo_endpoint @@ -124,19 +125,20 @@ def get(self, request: Request, group: Group) -> Response: results = get_similarity_data_from_seer(similar_issues_params) analytics.record( - "group_similar_issues_embeddings.count", - organization_id=group.organization.id, - project_id=group.project.id, - group_id=group.id, - hash=latest_event.get_primary_hash(), - count_over_threshold=len( - [ - result.stacktrace_distance - for result in results - if result.stacktrace_distance <= 0.01 - ] - ), - user_id=request.user.id, + GroupSimilarIssuesEmbeddingsCountEvent( + organization_id=group.organization.id, + project_id=group.project.id, + group_id=group.id, + hash=latest_event.get_primary_hash(), + count_over_threshold=len( + [ + result.stacktrace_distance + for result in results + if result.stacktrace_distance <= 0.01 + ] + ), + user_id=request.user.id, + ) ) if not results: diff --git a/src/sentry/issues/endpoints/organization_searches.py b/src/sentry/issues/endpoints/organization_searches.py index 96c8b077c6750b..4b74e65766b88d 100644 --- a/src/sentry/issues/endpoints/organization_searches.py +++ b/src/sentry/issues/endpoints/organization_searches.py @@ -3,6 +3,7 @@ from rest_framework.response import Response from sentry import analytics +from sentry.api.analytics import OrganizationSavedSearchCreatedEvent from sentry.api.api_owners import ApiOwner from sentry.api.api_publish_status import ApiPublishStatus from sentry.api.base import region_silo_endpoint @@ -111,9 +112,10 @@ def post(self, request: Request, organization: Organization) -> Response: visibility=result["visibility"], ) analytics.record( - "organization_saved_search.created", - search_type=SearchType(saved_search.type).name, - org_id=organization.id, - query=saved_search.query, + OrganizationSavedSearchCreatedEvent( + search_type=SearchType(saved_search.type).name, + org_id=organization.id, + query=saved_search.query, + ) ) return Response(serialize(saved_search, request.user)) diff --git a/src/sentry/issues/endpoints/project_group_index.py b/src/sentry/issues/endpoints/project_group_index.py index 366b48035cf055..50e8093a4f2b93 100644 --- a/src/sentry/issues/endpoints/project_group_index.py +++ b/src/sentry/issues/endpoints/project_group_index.py @@ -4,6 +4,7 @@ from rest_framework.response import Response from sentry import analytics, eventstore +from sentry.analytics.events.project_issue_searched import ProjectIssueSearchEvent from sentry.api.api_owners import ApiOwner from sentry.api.api_publish_status import ApiPublishStatus from sentry.api.base import region_silo_endpoint @@ -191,11 +192,12 @@ def get(self, request: Request, project: Project) -> Response: if results and query: advanced_search.send(project=project, sender=request.user) analytics.record( - "project_issue.searched", - user_id=request.user.id, - organization_id=project.organization_id, - project_id=project.id, - query=query, + ProjectIssueSearchEvent( + user_id=request.user.id, + organization_id=project.organization_id, + project_id=project.id, + query=query, + ) ) return response diff --git a/src/sentry/issues/endpoints/project_stacktrace_link.py b/src/sentry/issues/endpoints/project_stacktrace_link.py index 5fc9d9a082e7af..be194b8b1fd285 100644 --- a/src/sentry/issues/endpoints/project_stacktrace_link.py +++ b/src/sentry/issues/endpoints/project_stacktrace_link.py @@ -14,6 +14,7 @@ from sentry.api.base import region_silo_endpoint from sentry.api.bases.project import ProjectEndpoint from sentry.api.serializers import serialize +from sentry.integrations.analytics import IntegrationStacktraceLinkEvent from sentry.integrations.api.serializers.models.integration import IntegrationSerializer from sentry.integrations.base import IntegrationFeatures from sentry.integrations.services.integration import integration_service @@ -175,15 +176,16 @@ def get(self, request: Request, project: Project) -> Response: if result["current_config"] and serialized_config: analytics.record( - "integration.stacktrace.linked", - provider=serialized_config["provider"]["key"], - config_id=serialized_config["id"], - project_id=project.id, - organization_id=project.organization_id, - filepath=filepath, - status=error or "success", - link_fetch_iterations=result["iteration_count"], - platform=ctx["platform"], + IntegrationStacktraceLinkEvent( + provider=serialized_config["provider"]["key"], + config_id=serialized_config["id"], + project_id=project.id, + organization_id=project.organization_id, + filepath=filepath, + status=error or "success", + link_fetch_iterations=result["iteration_count"], + platform=ctx["platform"], + ) ) return Response( { diff --git a/src/sentry/issues/escalating/forecasts.py b/src/sentry/issues/escalating/forecasts.py index 2ab277b7263a7c..1184076dfc2418 100644 --- a/src/sentry/issues/escalating/forecasts.py +++ b/src/sentry/issues/escalating/forecasts.py @@ -1,3 +1,5 @@ +from sentry.issues.analytics import IssueForecastSaved + """ This module is for helper functions for escalating issues forecasts. """ @@ -49,7 +51,7 @@ def save_forecast_per_group( "save_forecast_per_group", extra={"group_id": group_id, "group_counts": group_count}, ) - analytics.record("issue_forecasts.saved", num_groups=len(group_counts.keys())) + analytics.record(IssueForecastSaved(num_groups=len(group_counts.keys()))) def generate_and_save_forecasts(groups: Iterable[Group]) -> None: diff --git a/src/sentry/middleware/devtoolbar.py b/src/sentry/middleware/devtoolbar.py index 0d1ef60c8e0117..2f59244c95fe16 100644 --- a/src/sentry/middleware/devtoolbar.py +++ b/src/sentry/middleware/devtoolbar.py @@ -3,6 +3,7 @@ from django.http import HttpRequest, HttpResponse from sentry import analytics, options +from sentry.api.analytics import DevToolbarApiRequestEvent from sentry.utils.http import origin_from_request from sentry.utils.http import query_string as get_query_string from sentry.utils.urls import parse_id_or_slug_param @@ -48,16 +49,17 @@ def _record_api_request(request: HttpRequest, response: HttpResponse) -> None: query_string: str = get_query_string(request) # starts with ? if non-empty analytics.record( - "devtoolbar.api_request", - view_name=view_name, - route=route, - query_string=query_string, - origin=origin, - method=request.method, - status_code=response.status_code, - organization_id=org_id or None, - organization_slug=org_slug, - project_id=project_id or None, - project_slug=project_slug, - user_id=request.user.id if hasattr(request, "user") and request.user else None, + DevToolbarApiRequestEvent( + view_name=view_name, + route=route, + query_string=query_string, + origin=origin, + method=request.method, + status_code=response.status_code, + organization_id=org_id or None, + organization_slug=org_slug, + project_id=project_id or None, + project_slug=project_slug, + user_id=request.user.id if hasattr(request, "user") and request.user else None, + ) ) diff --git a/src/sentry/plugins/providers/integration_repository.py b/src/sentry/plugins/providers/integration_repository.py index f55bb932e670ed..8359834dd97177 100644 --- a/src/sentry/plugins/providers/integration_repository.py +++ b/src/sentry/plugins/providers/integration_repository.py @@ -13,6 +13,7 @@ from sentry import analytics from sentry.api.exceptions import SentryAPIException from sentry.constants import ObjectStatus +from sentry.integrations.analytics import IntegrationRepoAddedEvent from sentry.integrations.base import IntegrationInstallation from sentry.integrations.models.integration import Integration from sentry.integrations.services.integration import integration_service @@ -198,10 +199,11 @@ def dispatch(self, request: Request, organization, **kwargs): repo_linked.send_robust(repo=repo, user=request.user, sender=self.__class__) analytics.record( - "integration.repo.added", - provider=self.id, - id=result.get("integration_id"), - organization_id=organization.id, + IntegrationRepoAddedEvent( + provider=self.id, + id=result.get("integration_id"), + organization_id=organization.id, + ) ) return Response( repository_service.serialize_repository( diff --git a/src/sentry/receivers/features.py b/src/sentry/receivers/features.py index 07b4af5d2528a7..5467cc09edd7e8 100644 --- a/src/sentry/receivers/features.py +++ b/src/sentry/receivers/features.py @@ -4,6 +4,11 @@ from sentry import analytics from sentry.adoption import manager +from sentry.integrations.analytics import ( + IntegrationAddedEvent, + IntegrationIssueCreatedEvent, + IntegrationIssueLinkedEvent, +) from sentry.integrations.services.integration import integration_service from sentry.models.featureadoption import FeatureAdoption from sentry.models.group import Group @@ -643,12 +648,13 @@ def record_integration_added( default_user_id = organization.get_default_owner().id analytics.record( - "integration.added", - user_id=user_id, - default_user_id=default_user_id, - organization_id=organization.id, - provider=integration.provider, - id=integration.id, + IntegrationAddedEvent( + user_id=user_id, + default_user_id=default_user_id, + organization_id=organization.id, + provider=integration.provider, + id=integration.id, + ) ) metrics.incr( "integration.added", @@ -665,12 +671,13 @@ def record_integration_issue_created(integration, organization, user, **kwargs): user_id = None default_user_id = organization.get_default_owner().id analytics.record( - "integration.issue.created", - user_id=user_id, - default_user_id=default_user_id, - organization_id=organization.id, - provider=integration.provider, - id=integration.id, + IntegrationIssueCreatedEvent( + user_id=user_id, + default_user_id=default_user_id, + organization_id=organization.id, + provider=integration.provider, + id=integration.id, + ) ) @@ -682,12 +689,13 @@ def record_integration_issue_linked(integration, organization, user, **kwargs): user_id = None default_user_id = organization.get_default_owner().id analytics.record( - "integration.issue.linked", - user_id=user_id, - default_user_id=default_user_id, - organization_id=organization.id, - provider=integration.provider, - id=integration.id, + IntegrationIssueLinkedEvent( + user_id=user_id, + default_user_id=default_user_id, + organization_id=organization.id, + provider=integration.provider, + id=integration.id, + ) ) diff --git a/src/sentry/receivers/onboarding.py b/src/sentry/receivers/onboarding.py index f2ad6b7158cd74..40ab439582f801 100644 --- a/src/sentry/receivers/onboarding.py +++ b/src/sentry/receivers/onboarding.py @@ -7,6 +7,11 @@ from django.db.models import F from sentry import analytics +from sentry.analytics.events.first_feedback_sent import FirstFeedbackSentEvent +from sentry.analytics.events.first_flag_sent import FirstFlagSentEvent +from sentry.analytics.events.first_release_tag_sent import FirstReleaseTagSentEvent +from sentry.analytics.events.first_replay_sent import FirstReplaySentEvent +from sentry.analytics.events.second_platform_added import SecondPlatformAddedEvent from sentry.integrations.base import IntegrationDomain, get_integration_types from sentry.integrations.services.integration import RpcIntegration, integration_service from sentry.models.organization import Organization @@ -114,10 +119,11 @@ def record_new_project(project, user=None, user_id=None, origin=None, **kwargs): project_id=project.id, ) analytics.record( - "second_platform.added", - user_id=default_user_id, - organization_id=project.organization_id, - project_id=project.id, + SecondPlatformAddedEvent( + user_id=default_user_id, + organization_id=project.organization_id, + project_id=project.id, + ) ) @@ -202,11 +208,12 @@ def record_first_replay(project, **kwargs): if completed: logger.info("record_first_replay_analytics_start") analytics.record( - "first_replay.sent", - user_id=get_owner_id(project), - organization_id=project.organization_id, - project_id=project.id, - platform=project.platform, + FirstReplaySentEvent( + user_id=get_owner_id(project), + organization_id=project.organization_id, + project_id=project.id, + platform=project.platform, + ) ) logger.info("record_first_replay_analytics_end") @@ -214,21 +221,23 @@ def record_first_replay(project, **kwargs): @first_flag_received.connect(weak=False, dispatch_uid="onboarding.record_first_flag") def record_first_flag(project, **kwargs): analytics.record( - "first_flag.sent", - organization_id=project.organization_id, - project_id=project.id, - platform=project.platform, + FirstFlagSentEvent( + organization_id=project.organization_id, + project_id=project.id, + platform=project.platform, + ) ) @first_feedback_received.connect(weak=False, dispatch_uid="onboarding.record_first_feedback") def record_first_feedback(project, **kwargs): analytics.record( - "first_feedback.sent", - user_id=get_owner_id(project), - organization_id=project.organization_id, - project_id=project.id, - platform=project.platform, + FirstFeedbackSentEvent( + user_id=get_owner_id(project), + organization_id=project.organization_id, + project_id=project.id, + platform=project.platform, + ) ) @@ -334,10 +343,11 @@ def record_release_received(project, release, **kwargs): return analytics.record( - "first_release_tag.sent", - user_id=owner_id, - project_id=project.id, - organization_id=project.organization_id, + FirstReleaseTagSentEvent( + user_id=owner_id, + project_id=project.id, + organization_id=project.organization_id, + ) ) diff --git a/src/sentry/receivers/releases.py b/src/sentry/receivers/releases.py index c676a4285acad9..7215fcd306a2a6 100644 --- a/src/sentry/receivers/releases.py +++ b/src/sentry/receivers/releases.py @@ -6,6 +6,7 @@ from sentry import analytics from sentry.db.postgres.transactions import in_test_hide_transaction_boundary +from sentry.integrations.analytics import IntegrationResolveCommitEvent, IntegrationResolvePREvent from sentry.models.activity import Activity from sentry.models.commit import Commit from sentry.models.group import Group, GroupStatus @@ -176,10 +177,11 @@ def resolved_in_commit(instance: Commit, created, **kwargs): if repo is not None: if repo.integration_id is not None: analytics.record( - "integration.resolve.commit", - provider=repo.provider, - id=repo.integration_id, - organization_id=repo.organization_id, + IntegrationResolveCommitEvent( + provider=repo.provider, + id=repo.integration_id, + organization_id=repo.organization_id, + ) ) issue_resolved.send_robust( @@ -251,10 +253,11 @@ def resolved_in_pull_request(instance: PullRequest, created, **kwargs): else: if repo is not None and repo.integration_id is not None: analytics.record( - "integration.resolve.pr", - provider=repo.provider, - id=repo.integration_id, - organization_id=repo.organization_id, + IntegrationResolvePREvent( + provider=repo.provider, + id=repo.integration_id, + organization_id=repo.organization_id, + ) ) diff --git a/src/sentry/tasks/summaries/weekly_reports.py b/src/sentry/tasks/summaries/weekly_reports.py index 1497b45ed8af0a..d13d70699df56d 100644 --- a/src/sentry/tasks/summaries/weekly_reports.py +++ b/src/sentry/tasks/summaries/weekly_reports.py @@ -16,6 +16,7 @@ from sentry_sdk import set_tag from sentry import analytics +from sentry.analytics.events.weekly_report import WeeklyReportSent from sentry.constants import DataCategory from sentry.models.group import Group, GroupStatus from sentry.models.grouphistory import GroupHistoryStatus @@ -338,11 +339,12 @@ def send_email(self, template_ctx: Mapping[str, Any], user_id: int) -> None: message.send(to=(self.email_override,)) else: analytics.record( - "weekly_report.sent", - user_id=user_id, - organization_id=self.ctx.organization.id, - notification_uuid=template_ctx["notification_uuid"], - user_project_count=template_ctx["user_project_count"], + WeeklyReportSent( + user_id=user_id, + organization_id=self.ctx.organization.id, + notification_uuid=template_ctx["notification_uuid"], + user_project_count=template_ctx["user_project_count"], + ) ) # TODO: see if we can use the UUID to track if the email was sent or not