diff --git a/src/sentry/analytics/events/codeowners_assignment.py b/src/sentry/analytics/events/codeowners_assignment.py index af342ce95c1fcf..27e68d6df43230 100644 --- a/src/sentry/analytics/events/codeowners_assignment.py +++ b/src/sentry/analytics/events/codeowners_assignment.py @@ -1,15 +1,12 @@ from sentry import analytics +@analytics.eventclass("codeowners.assignment") class CodeownersAssignment(analytics.Event): - type = "codeowners.assignment" - - attributes = ( - analytics.Attribute("organization_id"), - analytics.Attribute("project_id"), - analytics.Attribute("group_id"), - analytics.Attribute("updated_assignment"), - ) + organization_id: str + project_id: str + group_id: str + updated_assignment: str analytics.register(CodeownersAssignment) diff --git a/src/sentry/analytics/events/cron_monitor_broken_status_recovery.py b/src/sentry/analytics/events/cron_monitor_broken_status_recovery.py index cc0155621ca146..55fb6803406fc7 100644 --- a/src/sentry/analytics/events/cron_monitor_broken_status_recovery.py +++ b/src/sentry/analytics/events/cron_monitor_broken_status_recovery.py @@ -1,15 +1,12 @@ from sentry import analytics +@analytics.eventclass("cron_monitor_broken_status.recovery") class CronMonitorBrokenStatusRecovery(analytics.Event): - type = "cron_monitor_broken_status.recovery" - - attributes = ( - analytics.Attribute("organization_id"), - analytics.Attribute("project_id"), - analytics.Attribute("monitor_id"), - analytics.Attribute("monitor_env_id"), - ) + organization_id: str + project_id: str + monitor_id: str + monitor_env_id: str analytics.register(CronMonitorBrokenStatusRecovery) diff --git a/src/sentry/analytics/events/eventuser_equality_check.py b/src/sentry/analytics/events/eventuser_equality_check.py index 304b56f4421b1d..9a85775d743b29 100644 --- a/src/sentry/analytics/events/eventuser_equality_check.py +++ b/src/sentry/analytics/events/eventuser_equality_check.py @@ -1,17 +1,14 @@ from sentry import analytics +@analytics.eventclass("eventuser_equality.check") class EventUserEqualityCheck(analytics.Event): - type = "eventuser_equality.check" - - attributes = ( - analytics.Attribute("event_id"), - analytics.Attribute("project_id"), - analytics.Attribute("group_id"), - analytics.Attribute("snuba_eventuser_equality", type=bool), - analytics.Attribute("event_eventuser_equality", type=bool), - analytics.Attribute("snuba_event_equality", type=bool), - ) + event_id: str + project_id: str + group_id: str + snuba_eventuser_equality: bool + event_eventuser_equality: bool + snuba_event_equality: bool analytics.register(EventUserEqualityCheck) diff --git a/src/sentry/analytics/events/first_user_context_sent.py b/src/sentry/analytics/events/first_user_context_sent.py index 03f50a55b7de1d..4b515165d259c8 100644 --- a/src/sentry/analytics/events/first_user_context_sent.py +++ b/src/sentry/analytics/events/first_user_context_sent.py @@ -1,14 +1,11 @@ from sentry import analytics +@analytics.eventclass("first_user_context.sent") class FirstUserContextSentEvent(analytics.Event): - type = "first_user_context.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(FirstUserContextSentEvent) diff --git a/src/sentry/analytics/events/integration_failed_to_fetch_commit_context.py b/src/sentry/analytics/events/integration_failed_to_fetch_commit_context.py index 0409c5f5861f1a..1ae97e3acc2e8f 100644 --- a/src/sentry/analytics/events/integration_failed_to_fetch_commit_context.py +++ b/src/sentry/analytics/events/integration_failed_to_fetch_commit_context.py @@ -1,17 +1,14 @@ from sentry import analytics +@analytics.eventclass("integrations.failed_to_fetch_commit_context") class IntegrationsFailedToFetchCommitContext(analytics.Event): - type = "integrations.failed_to_fetch_commit_context" - - attributes = ( - analytics.Attribute("organization_id"), - analytics.Attribute("project_id"), - analytics.Attribute("code_mapping_id"), - analytics.Attribute("group_id"), - analytics.Attribute("provider", type=str), - analytics.Attribute("error_message", type=str), - ) + organization_id: str + project_id: str + code_mapping_id: str + group_id: str + provider: str + error_message: str analytics.register(IntegrationsFailedToFetchCommitContext) diff --git a/src/sentry/analytics/events/integration_pipeline_step.py b/src/sentry/analytics/events/integration_pipeline_step.py index 6544e26a2acc9f..14215db7b9bb3b 100644 --- a/src/sentry/analytics/events/integration_pipeline_step.py +++ b/src/sentry/analytics/events/integration_pipeline_step.py @@ -1,16 +1,13 @@ from sentry import analytics +@analytics.eventclass("integrations.pipeline_step") class IntegrationPipelineStep(analytics.Event): - type = "integrations.pipeline_step" - - attributes = ( - analytics.Attribute("user_id"), - analytics.Attribute("organization_id"), - analytics.Attribute("integration"), - analytics.Attribute("step_index"), - analytics.Attribute("pipeline_type"), - ) + user_id: str + organization_id: str + integration: str + step_index: str + pipeline_type: str analytics.register(IntegrationPipelineStep) diff --git a/src/sentry/analytics/events/issueowners_assignment.py b/src/sentry/analytics/events/issueowners_assignment.py index 48bbc96d4b35b0..f16017bcc08101 100644 --- a/src/sentry/analytics/events/issueowners_assignment.py +++ b/src/sentry/analytics/events/issueowners_assignment.py @@ -1,15 +1,12 @@ from sentry import analytics +@analytics.eventclass("issueowners.assignment") class IssueOwnersAssignment(analytics.Event): - type = "issueowners.assignment" - - attributes = ( - analytics.Attribute("organization_id"), - analytics.Attribute("project_id"), - analytics.Attribute("group_id"), - analytics.Attribute("updated_assignment"), - ) + organization_id: str + project_id: str + group_id: str + updated_assignment: str analytics.register(IssueOwnersAssignment) diff --git a/src/sentry/analytics/events/join_request_created.py b/src/sentry/analytics/events/join_request_created.py index c49c99229702c4..4ba5f853bfa062 100644 --- a/src/sentry/analytics/events/join_request_created.py +++ b/src/sentry/analytics/events/join_request_created.py @@ -1,14 +1,11 @@ from sentry import analytics +@analytics.eventclass("join_request.created") class JoinRequestCreatedEvent(analytics.Event): - type = "join_request.created" - - attributes = ( - analytics.Attribute("member_id"), - analytics.Attribute("organization_id"), - analytics.Attribute("referrer", required=False), - ) + member_id: str + organization_id: str + referrer: str | None = None analytics.register(JoinRequestCreatedEvent) diff --git a/src/sentry/analytics/events/join_request_link_viewed.py b/src/sentry/analytics/events/join_request_link_viewed.py index c56597478d6106..4b23942a526ed7 100644 --- a/src/sentry/analytics/events/join_request_link_viewed.py +++ b/src/sentry/analytics/events/join_request_link_viewed.py @@ -1,10 +1,9 @@ from sentry import analytics +@analytics.eventclass("join_request.link_viewed") class JoinRequestLinkViewedEvent(analytics.Event): - type = "join_request.link_viewed" - - attributes = (analytics.Attribute("organization_id"),) + organization_id: str analytics.register(JoinRequestLinkViewedEvent) diff --git a/src/sentry/analytics/events/missing_members_nudge.py b/src/sentry/analytics/events/missing_members_nudge.py index cc8ce709c26305..1288050b041e76 100644 --- a/src/sentry/analytics/events/missing_members_nudge.py +++ b/src/sentry/analytics/events/missing_members_nudge.py @@ -3,11 +3,9 @@ from sentry import analytics +@analytics.eventclass("missing_members_nudge.sent") class MissingMembersNudgeEvent(analytics.Event, abc.ABC): - type = "missing_members_nudge.sent" - attributes = [ - analytics.Attribute("organization_id"), - ] + organization_id: str analytics.register(MissingMembersNudgeEvent) diff --git a/src/sentry/analytics/events/quick_trace_connected_services.py b/src/sentry/analytics/events/quick_trace_connected_services.py index c69283c8c1514c..4b2cad6c654c3e 100644 --- a/src/sentry/analytics/events/quick_trace_connected_services.py +++ b/src/sentry/analytics/events/quick_trace_connected_services.py @@ -1,14 +1,11 @@ from sentry import analytics +@analytics.eventclass("quick_trace.connected_services") class QuickTraceConnectedServices(analytics.Event): - type = "quick_trace.connected_services" - - attributes = ( - analytics.Attribute("trace_id"), - analytics.Attribute("organization_id"), - analytics.Attribute("projects"), - ) + trace_id: str + organization_id: str + projects: str analytics.register(QuickTraceConnectedServices) diff --git a/src/sentry/analytics/events/user_created.py b/src/sentry/analytics/events/user_created.py index b71a7ee4969ec9..b70ae844515df8 100644 --- a/src/sentry/analytics/events/user_created.py +++ b/src/sentry/analytics/events/user_created.py @@ -1,14 +1,11 @@ from sentry import analytics +@analytics.eventclass("user.created") class UserCreatedEvent(analytics.Event): - type = "user.created" - - attributes = ( - analytics.Attribute("id"), - analytics.Attribute("username"), - analytics.Attribute("email"), - ) + id: str + username: str + email: str analytics.register(UserCreatedEvent) 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/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/discord/analytics.py b/src/sentry/integrations/discord/analytics.py index f0bcc61291b346..6cfbb85bf05c82 100644 --- a/src/sentry/integrations/discord/analytics.py +++ b/src/sentry/integrations/discord/analytics.py @@ -1,65 +1,50 @@ from sentry import analytics +@analytics.eventclass("integrations.discord.notification_sent") class DiscordIntegrationNotificationSent(analytics.Event): - type = "integrations.discord.notification_sent" - - attributes = ( - analytics.Attribute("organization_id"), - analytics.Attribute("project_id"), - analytics.Attribute("category"), - analytics.Attribute("group_id"), - analytics.Attribute("notification_uuid"), - analytics.Attribute("alert_id", required=False), - ) + organization_id: str + project_id: str + category: str + group_id: str + notification_uuid: str + alert_id: str | None = None +@analytics.eventclass("integrations.discord.command_interaction") class DiscordIntegrationCommandInteractionReceived(analytics.Event): - type = "integrations.discord.command_interaction" - - attributes = (analytics.Attribute("command_name"),) + command_name: str +@analytics.eventclass("integrations.discord.identity_linked") class DiscordIntegrationIdentityLinked(analytics.Event): - type = "integrations.discord.identity_linked" - - attributes = ( - analytics.Attribute("provider"), - analytics.Attribute("actor_id"), - analytics.Attribute("actor_type"), - ) + provider: str + actor_id: str + actor_type: str +@analytics.eventclass("integrations.discord.identity_unlinked") class DiscordIntegrationIdentityUnlinked(analytics.Event): - type = "integrations.discord.identity_unlinked" - - attributes = ( - analytics.Attribute("provider"), - analytics.Attribute("actor_id"), - analytics.Attribute("actor_type"), - ) + provider: str + actor_id: str + actor_type: str +@analytics.eventclass("integrations.discord.message_interaction") class DiscordIntegrationMessageInteractionReceived(analytics.Event): - type = "integrations.discord.message_interaction" - - attributes = (analytics.Attribute("custom_id"),) + custom_id: str +@analytics.eventclass("integrations.discord.assign") class DiscordIntegrationAssign(analytics.Event): - type = "integrations.discord.assign" - - attributes = (analytics.Attribute("actor_id"),) + actor_id: str +@analytics.eventclass("integrations.discord.status") class DiscordIntegrationStatus(analytics.Event): - type = "integrations.discord.status" - - attributes = ( - analytics.Attribute("organization_id"), - analytics.Attribute("user_id"), - analytics.Attribute("status"), - ) + organization_id: str + user_id: str + status: str analytics.register(DiscordIntegrationCommandInteractionReceived) diff --git a/src/sentry/integrations/discord/webhooks/base.py b/src/sentry/integrations/discord/webhooks/base.py index f647e6694960d9..6a9e93a7651119 100644 --- a/src/sentry/integrations/discord/webhooks/base.py +++ b/src/sentry/integrations/discord/webhooks/base.py @@ -11,6 +11,10 @@ from sentry.api.api_owners import ApiOwner from sentry.api.api_publish_status import ApiPublishStatus from sentry.api.base import Endpoint, all_silo_endpoint +from sentry.integrations.discord.analytics import ( + DiscordIntegrationCommandInteractionReceived, + DiscordIntegrationMessageInteractionReceived, +) from sentry.integrations.discord.requests.base import DiscordRequest, DiscordRequestError from sentry.integrations.discord.webhooks.command import DiscordCommandHandler from sentry.integrations.discord.webhooks.message_component import DiscordMessageComponentHandler @@ -58,15 +62,17 @@ def post(self, request: Request) -> HttpResponse: elif discord_request.is_command(): analytics.record( - "integrations.discord.command_interaction", - command_name=discord_request.get_command_name(), + DiscordIntegrationCommandInteractionReceived( + command_name=discord_request.get_command_name(), + ) ) return DiscordCommandHandler(discord_request).handle() elif discord_request.is_message_component(): analytics.record( - "integrations.discord.message_interaction", - custom_id=discord_request.get_component_custom_id(), + DiscordIntegrationMessageInteractionReceived( + custom_id=discord_request.get_component_custom_id(), + ) ) return DiscordMessageComponentHandler(discord_request).handle() diff --git a/src/sentry/integrations/discord/webhooks/message_component.py b/src/sentry/integrations/discord/webhooks/message_component.py index 04f4691617166c..147c857acab5dc 100644 --- a/src/sentry/integrations/discord/webhooks/message_component.py +++ b/src/sentry/integrations/discord/webhooks/message_component.py @@ -7,6 +7,7 @@ from sentry import analytics from sentry.api.helpers.group_index.update import update_groups +from sentry.integrations.discord.analytics import DiscordIntegrationAssign, DiscordIntegrationStatus from sentry.integrations.discord.message_builder.base.base import DiscordMessageBuilder from sentry.integrations.discord.message_builder.base.component import ( DiscordComponentCustomIds as CustomIds, @@ -175,8 +176,9 @@ def assign(self) -> Response: assert self.request.user is not None analytics.record( - "integrations.discord.assign", - actor_id=self.request.user.id, + DiscordIntegrationAssign( + actor_id=self.request.user.id, + ) ) message = DiscordMessageBuilder( @@ -241,10 +243,11 @@ def archive(self) -> Response: def update_group(self, data: Mapping[str, object]) -> None: if self.group: analytics.record( - "integrations.discord.status", - organization_id=self.group.organization.id, - user_id=self.user.id, - status=data, + DiscordIntegrationStatus( + organization_id=self.group.organization.id, + user_id=self.user.id, + status=data, + ) ) update_groups( request=self.request.request, groups=[self.group], user=self.user, data=data diff --git a/src/sentry/integrations/msteams/analytics.py b/src/sentry/integrations/msteams/analytics.py index 9e95650f7fe447..c8259f1a634ecd 100644 --- a/src/sentry/integrations/msteams/analytics.py +++ b/src/sentry/integrations/msteams/analytics.py @@ -1,18 +1,15 @@ from sentry import analytics +@analytics.eventclass("integrations.msteams.notification_sent") class MSTeamsIntegrationNotificationSent(analytics.Event): - type = "integrations.msteams.notification_sent" - - attributes = ( - analytics.Attribute("organization_id"), - analytics.Attribute("project_id", required=False), - analytics.Attribute("category"), - analytics.Attribute("actor_id", required=False), - analytics.Attribute("user_id", required=False), - analytics.Attribute("notification_uuid"), - analytics.Attribute("alert_id", required=False), - ) + organization_id: str + project_id: str | None = None + category: str + actor_id: str | None = None + user_id: str | None = None + notification_uuid: str + alert_id: str | None = None analytics.register(MSTeamsIntegrationNotificationSent) diff --git a/src/sentry/integrations/opsgenie/analytics.py b/src/sentry/integrations/opsgenie/analytics.py index 248004248bee98..32ac3d78a659f3 100644 --- a/src/sentry/integrations/opsgenie/analytics.py +++ b/src/sentry/integrations/opsgenie/analytics.py @@ -1,17 +1,14 @@ from sentry import analytics +@analytics.eventclass("integrations.opsgenie.notification_sent") class OpsgenieIntegrationNotificationSent(analytics.Event): - type = "integrations.opsgenie.notification_sent" - - attributes = ( - analytics.Attribute("organization_id"), - analytics.Attribute("project_id"), - analytics.Attribute("category"), - analytics.Attribute("group_id"), - analytics.Attribute("notification_uuid"), - analytics.Attribute("alert_id", required=False), - ) + organization_id: str + project_id: str + category: str + group_id: str + notification_uuid: str + alert_id: str | None = None analytics.register(OpsgenieIntegrationNotificationSent) diff --git a/src/sentry/integrations/pagerduty/analytics.py b/src/sentry/integrations/pagerduty/analytics.py index 3f82831ab23bf9..ee1174de4beaaa 100644 --- a/src/sentry/integrations/pagerduty/analytics.py +++ b/src/sentry/integrations/pagerduty/analytics.py @@ -1,17 +1,14 @@ from sentry import analytics +@analytics.eventclass("integrations.pagerduty.notification_sent") class PagerdutyIntegrationNotificationSent(analytics.Event): - type = "integrations.pagerduty.notification_sent" - - attributes = ( - analytics.Attribute("organization_id"), - analytics.Attribute("project_id"), - analytics.Attribute("category"), - analytics.Attribute("group_id"), - analytics.Attribute("notification_uuid"), - analytics.Attribute("alert_id", required=False), - ) + organization_id: str + project_id: str + category: str + group_id: str + notification_uuid: str + alert_id: str | None = None analytics.register(PagerdutyIntegrationNotificationSent) 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/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_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/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/monitors/logic/incidents.py b/src/sentry/monitors/logic/incidents.py index 052c03666636e4..b9fd6ad7ccfe51 100644 --- a/src/sentry/monitors/logic/incidents.py +++ b/src/sentry/monitors/logic/incidents.py @@ -7,6 +7,9 @@ from django.utils import timezone from sentry import analytics +from sentry.analytics.events.cron_monitor_broken_status_recovery import ( + CronMonitorBrokenStatusRecovery, +) from sentry.monitors.logic.incident_occurrence import ( dispatch_incident_occurrence, resolve_incident_group, @@ -180,11 +183,12 @@ def try_incident_resolution(ok_checkin: MonitorCheckIn) -> bool: ): if incident.monitorenvbrokendetection_set.exists(): analytics.record( - "cron_monitor_broken_status.recovery", - organization_id=monitor_env.monitor.organization_id, - project_id=monitor_env.monitor.project_id, - monitor_id=monitor_env.monitor.id, - monitor_env_id=monitor_env.id, + CronMonitorBrokenStatusRecovery( + organization_id=monitor_env.monitor.organization_id, + project_id=monitor_env.monitor.project_id, + monitor_id=monitor_env.monitor.id, + monitor_env_id=monitor_env.id, + ) ) return True 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/experiments.py b/src/sentry/receivers/experiments.py index 9682372ea3e446..23fe1241edf7d1 100644 --- a/src/sentry/receivers/experiments.py +++ b/src/sentry/receivers/experiments.py @@ -1,20 +1,23 @@ from sentry import analytics +from sentry.analytics.events.join_request_created import JoinRequestCreatedEvent +from sentry.analytics.events.join_request_link_viewed import JoinRequestLinkViewedEvent from sentry.signals import join_request_created, join_request_link_viewed, user_signup @join_request_created.connect(weak=False) def record_join_request_created(member, **kwargs): analytics.record( - "join_request.created", - member_id=member.id, - organization_id=member.organization_id, - referrer=kwargs.get("referrer"), + JoinRequestCreatedEvent( + member_id=member.id, + organization_id=member.organization_id, + referrer=kwargs.get("referrer"), + ) ) @join_request_link_viewed.connect(weak=False) def record_join_request_link_viewed(organization, **kwargs): - analytics.record("join_request.link_viewed", organization_id=organization.id) + analytics.record(JoinRequestLinkViewedEvent(organization_id=organization.id)) @user_signup.connect(weak=False) 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/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, + ) )