Skip to content
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

fix(trends): fix active users persons date range #17356

Merged
merged 14 commits into from
Sep 11, 2023
147 changes: 146 additions & 1 deletion posthog/queries/trends/test/test_person.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import json
from datetime import datetime
from uuid import UUID

from dateutil.relativedelta import relativedelta
from django.utils import timezone
from freezegun.api import freeze_time
from unittest.case import skip

from posthog.models.entity import Entity
from posthog.models.filters import Filter
Expand All @@ -15,12 +18,12 @@
ClickhouseTestMixin,
_create_event,
_create_person,
flush_persons_and_events,
snapshot_clickhouse_queries,
)


class TestPerson(ClickhouseTestMixin, APIBaseTest):

# Note: not using `@snapshot_clickhouse_queries` here because the ordering of the session_ids in the recording
# query is not guaranteed, so adding it would lead to a flaky test.
@freeze_time("2021-01-21T20:00:00.000Z")
Expand Down Expand Up @@ -155,3 +158,145 @@ def test_group_query_includes_recording_events(self):
}
],
)


class TestPersonIntegration(ClickhouseTestMixin, APIBaseTest):
def test_weekly_active_users(self):
for d in range(10, 18): # create a person and event for each day 10. Sep - 17. Sep
_create_person(team_id=self.team.pk, distinct_ids=[f"u_{d}"])
_create_event(
event="pageview",
distinct_id=f"u_{d}",
team=self.team,
timestamp=datetime(2023, 9, d, 00, 42),
)
flush_persons_and_events()

# request weekly active users in the following week
filter = {
"insight": "TRENDS",
"date_from": "2023-09-17T13:37:00",
"date_to": "2023-09-24T13:37:00",
"events": json.dumps([{"id": "pageview", "math": "weekly_active"}]),
}
insight_response = self.client.get(f"/api/projects/{self.team.pk}/insights/trend", data=filter)
insight_response = (insight_response.json()).get("result")

self.assertEqual(insight_response[0].get("labels")[5], "22-Sep-2023")
self.assertEqual(insight_response[0].get("data")[5], 2)

persons_url = insight_response[0].get("persons_urls")[5].get("url")
response = self.client.get("/" + persons_url)

data = response.json()
self.assertEqual(data.get("results")[0].get("count"), 2)
self.assertEqual([item["name"] for item in data.get("results")[0].get("people")], ["u_17", "u_16"])

def test_weekly_active_users_grouped_by_week(self):
for d in range(10, 18): # create a person and event for each day 10. Sep - 17. Sep
_create_person(team_id=self.team.pk, distinct_ids=[f"u_{d}"])
_create_event(
event="pageview",
distinct_id=f"u_{d}",
team=self.team,
timestamp=datetime(2023, 9, d, 00, 42),
)
flush_persons_and_events()

# request weekly active users in the following week
filter = {
"insight": "TRENDS",
"date_from": "2023-09-17T13:37:00",
"date_to": "2023-09-24T13:37:00",
"interval": "week",
"events": json.dumps([{"id": "pageview", "math": "weekly_active"}]),
}
insight_response = self.client.get(f"/api/projects/{self.team.pk}/insights/trend", data=filter)
insight_response = (insight_response.json()).get("result")

self.assertEqual(insight_response[0].get("labels")[0], "17-Sep-2023")
self.assertEqual(insight_response[0].get("data")[0], 7)

persons_url = insight_response[0].get("persons_urls")[0].get("url")
response = self.client.get("/" + persons_url)

data = response.json()
self.assertEqual(data.get("results")[0].get("count"), 7)
self.assertEqual(
[item["name"] for item in data.get("results")[0].get("people")],
["u_17", "u_16", "u_15", "u_14", "u_13", "u_12", "u_11"],
)

def test_weekly_active_users_cumulative(self):
for d in range(10, 18): # create a person and event for each day 10. Sep - 17. Sep
_create_person(team_id=self.team.pk, distinct_ids=[f"u_{d}"])
_create_event(
event="pageview",
distinct_id=f"u_{d}",
team=self.team,
timestamp=datetime(2023, 9, d, 00, 42),
)
flush_persons_and_events()

# request weekly active users in the following week
filter = {
"insight": "TRENDS",
"date_from": "2023-09-10T13:37:00",
"date_to": "2023-09-24T13:37:00",
"events": json.dumps([{"id": "pageview", "math": "weekly_active"}]),
"display": "ActionsLineGraphCumulative",
}
insight_response = self.client.get(f"/api/projects/{self.team.pk}/insights/trend", data=filter)
insight_response = (insight_response.json()).get("result")

self.assertEqual(insight_response[0].get("labels")[1], "11-Sep-2023")
self.assertEqual(insight_response[0].get("data")[1], 3)

persons_url = insight_response[0].get("persons_urls")[1].get("url")
response = self.client.get("/" + persons_url)

data = response.json()
self.assertEqual(data.get("results")[0].get("count"), 2)
self.assertEqual([item["name"] for item in data.get("results")[0].get("people")], ["u_11", "u_10"])

@skip("see PR 17356")
def test_weekly_active_users_breakdown(self):
for d in range(10, 18): # create a person and event for each day 10. Sep - 17. Sep
_create_person(team_id=self.team.pk, distinct_ids=[f"a_{d}"])
_create_person(team_id=self.team.pk, distinct_ids=[f"b_{d}"])
_create_event(
event="pageview",
distinct_id=f"a_{d}",
properties={"some_prop": "a"},
team=self.team,
timestamp=datetime(2023, 9, d, 00, 42),
)
_create_event(
event="pageview",
distinct_id=f"b_{d}",
properties={"some_prop": "b"},
team=self.team,
timestamp=datetime(2023, 9, d, 00, 42),
)
flush_persons_and_events()

# request weekly active users in the following week
filter = {
"insight": "TRENDS",
"date_from": "2023-09-17T13:37:00",
"date_to": "2023-09-24T13:37:00",
"events": json.dumps([{"id": "pageview", "math": "weekly_active"}]),
"breakdown": "some_prop",
}
insight_response = self.client.get(f"/api/projects/{self.team.pk}/insights/trend", data=filter)
insight_response = (insight_response.json()).get("result")

self.assertEqual(insight_response[0].get("labels")[5], "22-Sep-2023")
# self.assertEqual(insight_response[0].get("data")[5], 2)

persons_url = insight_response[0].get("persons_urls")[5].get("url")
response = self.client.get("/" + persons_url)

data = response.json()
# self.assertEqual(data.get("results")[0].get("count"), 2)
self.assertEqual([item["name"] for item in data.get("results")[0].get("people")], ["a_17", "a_16"])
21 changes: 18 additions & 3 deletions posthog/queries/trends/total_volume.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import urllib.parse
from datetime import date, datetime
from datetime import date, datetime, timedelta
from typing import Any, Callable, Dict, List, Tuple, Union

from posthog.clickhouse.query_tagging import tag_queries
Expand Down Expand Up @@ -256,6 +256,21 @@ def _parse(result: List) -> List:

return _parse

def _offset_date_from(self, point_datetime: datetime, filter: Filter, entity: Entity) -> datetime | None:
if filter.display == TRENDS_CUMULATIVE:
return filter.date_from
elif entity.math in [WEEKLY_ACTIVE, MONTHLY_ACTIVE]:
# :TRICKY: We have to offset the date by one, as the final query already subtracts 7 days
return point_datetime + timedelta(days=1)
else:
return point_datetime

def _offset_date_to(self, point_datetime: datetime, filter: Filter, entity: Entity, team: Team) -> datetime:
if entity.math in [WEEKLY_ACTIVE, MONTHLY_ACTIVE]:
return point_datetime
else:
return offset_time_series_date_by_interval(point_datetime, filter=filter, team=team)

def _get_persons_url(
self, filter: Filter, entity: Entity, team: Team, point_datetimes: List[datetime]
) -> List[Dict[str, Any]]:
Expand All @@ -267,8 +282,8 @@ def _get_persons_url(
"entity_id": entity.id,
"entity_type": entity.type,
"entity_math": entity.math,
"date_from": filter.date_from if filter.display == TRENDS_CUMULATIVE else point_datetime,
"date_to": offset_time_series_date_by_interval(point_datetime, filter=filter, team=team),
"date_from": self._offset_date_from(point_datetime, filter=filter, entity=entity),
"date_to": self._offset_date_to(point_datetime, filter=filter, entity=entity, team=team),
"entity_order": entity.order,
}

Expand Down