Skip to content

ref(analytics): Transform analytics events for TET-825 #95205

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8143000
ref(analytics): Transform analytics events for TET-825
constantinius Jul 10, 2025
724b035
fix(theme): Neutral chart color is white (#95201)
ArthurKnaus Jul 10, 2025
7712413
ref(onboarding): Move onboarding types out of step component (#95200)
ArthurKnaus Jul 10, 2025
8146e7c
fix(profiling-onboarding): iframe blocked by CSP (#95202)
ArthurKnaus Jul 10, 2025
1867317
fix(demo-mode): includes demo header in stacked nav layout (#95203)
obostjancic Jul 10, 2025
885e367
chore(dynamic-sampling): remove dynamic sampling minimum samplerate p…
shellmayr Jul 10, 2025
ca88475
fix(analytics): missing 'data' attribute exclusion
constantinius Jul 10, 2025
6234116
Revert "chore(dynamic-sampling): remove dynamic sampling minimum samp…
constantinius Jul 10, 2025
054dbdf
Revert "fix(demo-mode): includes demo header in stacked nav layout (#…
constantinius Jul 10, 2025
41841aa
Revert "fix(profiling-onboarding): iframe blocked by CSP (#95202)"
constantinius Jul 10, 2025
111464d
Revert "ref(onboarding): Move onboarding types out of step component …
constantinius Jul 10, 2025
c376be9
Revert "fix(theme): Neutral chart color is white (#95201)"
constantinius Jul 10, 2025
fd49aba
fix(analytics): fixing mypy issues
constantinius Jul 10, 2025
baf5a77
fix(analytics): exclude UUID from Event equality checks to allow for …
constantinius Jul 10, 2025
5755ef6
Merge branch 'master' into constantinius/ref/analytics/tet-825
constantinius Jul 11, 2025
e2a04cf
Revert "fix(analytics): exclude UUID from Event equality checks to al…
constantinius Jul 11, 2025
91d8172
test(analytics): fix tests for record calls
constantinius Jul 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions src/sentry/analytics/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def wrapper(cls: type[Event]) -> type[Event]:
# set the Event subclass `type` attribute, if it is set to anything
if isinstance(event_name_or_class, str):
cls.type = event_name_or_class
return cast(type[Event], dataclass(kw_only=True)(cls))
return cast(type[Event], dataclass(kw_only=True, eq=False)(cls))

# for using without parenthesis, wrap the passed class
if isinstance(event_name_or_class, type) and issubclass(event_name_or_class, Event):
Expand All @@ -75,7 +75,7 @@ def wrapper(cls: type[Event]) -> type[Event]:


# unfortunately we cannot directly use `eventclass` here, as it is making a typecheck to Event
@dataclass(kw_only=True)
@dataclass(kw_only=True, eq=False)
class Event:
"""
Base class for custom analytics Events.
Expand Down Expand Up @@ -113,10 +113,28 @@ def from_instance(cls, instance: Any, **kwargs: Any) -> Self:
**{
f.name: kwargs.get(f.name, getattr(instance, f.name, None))
for f in fields(cls)
if f.name not in ("type", "uuid_", "datetime_")
if f.name
not in (
"type",
"uuid_",
"datetime_",
"data", # TODO: remove this data field once migrated
)
}
)

def __eq__(self, other: Any) -> bool:
if not isinstance(other, Event):
return False

self_values = self.serialize()
other_values = other.serialize()

self_values.pop("uuid")
other_values.pop("uuid")

return self_values == other_values


def serialize_event(event: Event) -> dict[str, Any]:
# TODO: this is the "old-style" attributes based serializer. Once all events are migrated to the new style,
Expand Down
13 changes: 5 additions & 8 deletions src/sentry/analytics/events/eventuser_snuba_for_projects.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
from sentry import analytics


@analytics.eventclass("eventuser_snuba.for_projects")
class EventUserSnubaForProjects(analytics.Event):
type = "eventuser_snuba.for_projects"

attributes = (
analytics.Attribute("project_ids", type=list),
analytics.Attribute("total_tries", type=int),
analytics.Attribute("total_rows_returned", required=True, type=int),
analytics.Attribute("total_time_ms", type=int),
)
project_ids: list[int]
total_tries: int
total_rows_returned: int
total_time_ms: int


analytics.register(EventUserSnubaForProjects)
17 changes: 7 additions & 10 deletions src/sentry/analytics/events/eventuser_snuba_query.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
from sentry import analytics


@analytics.eventclass("eventuser_snuba.query")
class EventUserSnubaQuery(analytics.Event):
type = "eventuser_snuba.query"

attributes = (
analytics.Attribute("project_ids", type=list),
analytics.Attribute("query"),
analytics.Attribute("query_try", type=int),
analytics.Attribute("count_rows_returned", required=True, type=int),
analytics.Attribute("count_rows_filtered", required=True, type=int),
analytics.Attribute("query_time_ms", type=int),
)
project_ids: list[int]
query: str
query_try: int
count_rows_returned: int
count_rows_filtered: int
query_time_ms: int


analytics.register(EventUserSnubaQuery)
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
from sentry import analytics


@analytics.eventclass("grouping.experiments.parameterization")
class GroupingParameterizationExperiment(analytics.Event):
type = "grouping.experiments.parameterization"

attributes = (
analytics.Attribute("experiment_name"),
analytics.Attribute("project_id"),
analytics.Attribute("event_id"),
)
experiment_name: str
project_id: str
event_id: str
Comment on lines +7 to +8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't these be ints?



analytics.register(GroupingParameterizationExperiment)
11 changes: 4 additions & 7 deletions src/sentry/analytics/events/notifications_settings_updated.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
from sentry import analytics


@analytics.eventclass("notifications.settings_updated")
class NotificationSettingsUpdated(analytics.Event):
type = "notifications.settings_updated"

attributes = (
analytics.Attribute("target_type"),
analytics.Attribute("actor_id", required=False),
analytics.Attribute("id"),
)
target_type: str
actor_id: str | None = None
id: str
Comment on lines +7 to +8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above - are these ints or str?



analytics.register(NotificationSettingsUpdated)
25 changes: 11 additions & 14 deletions src/sentry/mail/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,18 @@


# TODO: should have same base class as SlackIntegrationNotificationSent
@analytics.eventclass("integrations.email.notification_sent")
class EmailNotificationSent(analytics.Event):
type = "integrations.email.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("group_id", required=False),
analytics.Attribute("id"),
analytics.Attribute("actor_type"),
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
group_id: str | None = None
id: str
actor_type: str
notification_uuid: str
alert_id: str | None = None


analytics.register(EmailNotificationSent)
28 changes: 16 additions & 12 deletions src/sentry/utils/eventuser.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
)

from sentry import analytics
from sentry.analytics.events.eventuser_snuba_for_projects import EventUserSnubaForProjects
from sentry.analytics.events.eventuser_snuba_query import EventUserSnubaQuery
from sentry.eventstore.models import Event, GroupEvent
from sentry.models.project import Project
from sentry.snuba.dataset import Dataset, EntityKey
Expand Down Expand Up @@ -233,13 +235,14 @@ def for_projects(
query_end_time = time.time()

analytics.record(
"eventuser_snuba.query",
project_ids=[p.id for p in projects],
query=query.print(),
query_try=tries,
count_rows_returned=len(data_results),
count_rows_filtered=len(data_results) - len(unique_event_users),
query_time_ms=int((query_end_time - query_start_time) * 1000),
EventUserSnubaQuery(
project_ids=[p.id for p in projects],
query=query.print(),
query_try=tries,
count_rows_returned=len(data_results),
count_rows_filtered=len(data_results) - len(unique_event_users),
query_time_ms=int((query_end_time - query_start_time) * 1000),
)
)
tries += 1
if (
Expand All @@ -252,11 +255,12 @@ def for_projects(

end_time = time.time()
analytics.record(
"eventuser_snuba.for_projects",
project_ids=[p.id for p in projects],
total_tries=tries,
total_rows_returned=len(full_results),
total_time_ms=int((end_time - start_time) * 1000),
EventUserSnubaForProjects(
project_ids=[p.id for p in projects],
total_tries=tries,
total_rows_returned=len(full_results),
total_time_ms=int((end_time - start_time) * 1000),
)
)

if result_limit:
Expand Down
15 changes: 15 additions & 0 deletions tests/sentry/analytics/test_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,21 @@ def test_simple(self, mock_uuid1):
"uuid": b"AAEC",
}

def test_equality_no_uuid_mock(self):
a = ExampleEvent(
id="1", # type: ignore[arg-type]
map={"key": "value"},
optional=False,
)
a.datetime_ = datetime(2001, 4, 18, tzinfo=timezone.utc)
b = ExampleEvent(
id="1", # type: ignore[arg-type]
map={"key": "value"},
optional=False,
)
b.datetime_ = datetime(2001, 4, 18, tzinfo=timezone.utc)
assert a == b

@patch("sentry.analytics.event.uuid1")
def test_simple_old_style(self, mock_uuid1):
mock_uuid1.return_value = self.get_mock_uuid()
Expand Down
Loading