From 1d2b727868eb9777e748b6b3462cd047d0f0caba Mon Sep 17 00:00:00 2001 From: Tom Owers Date: Fri, 10 Nov 2023 10:32:34 +0000 Subject: [PATCH] chore: adding tests to the insight trends query runner (#18352) * Added the final set of aggregation functions * Added the ability to view all chart types on trends * WIP adding tests * Finished adding tests * Fix test --- .../insights/trends/aggregation_operations.py | 6 +- .../insights/trends/breakdown_values.py | 4 +- .../test/test_aggregation_operations.py | 106 ++- .../trends/test/test_trends_query_runner.py | 629 +++++++++++++++++- .../insights/trends/test/test_utils.py | 38 ++ .../insights/trends/trends_query_runner.py | 8 +- .../hogql_queries/insights/trends/utils.py | 8 +- 7 files changed, 771 insertions(+), 28 deletions(-) create mode 100644 posthog/hogql_queries/insights/trends/test/test_utils.py diff --git a/posthog/hogql_queries/insights/trends/aggregation_operations.py b/posthog/hogql_queries/insights/trends/aggregation_operations.py index e380f9a25865d..38ca1cec71c4e 100644 --- a/posthog/hogql_queries/insights/trends/aggregation_operations.py +++ b/posthog/hogql_queries/insights/trends/aggregation_operations.py @@ -13,7 +13,7 @@ class QueryAlternator: _group_bys: List[ast.Expr] _select_from: ast.JoinExpr | None - def __init__(self, query: ast.SelectQuery): + def __init__(self, query: ast.SelectQuery | ast.SelectUnionQuery): assert isinstance(query, ast.SelectQuery) self._query = query @@ -21,7 +21,7 @@ def __init__(self, query: ast.SelectQuery): self._group_bys = [] self._select_from = None - def build(self) -> ast.SelectQuery: + def build(self) -> ast.SelectQuery | ast.SelectUnionQuery: if len(self._selects) > 0: self._query.select.extend(self._selects) @@ -89,7 +89,7 @@ def select_aggregation(self) -> ast.Expr: else: raise NotImplementedError() - return parse_expr("count(e.uuid)") + return parse_expr("count(e.uuid)") # All "count per actor" get replaced during query orchestration def requires_query_orchestration(self) -> bool: math_to_return_true = [ diff --git a/posthog/hogql_queries/insights/trends/breakdown_values.py b/posthog/hogql_queries/insights/trends/breakdown_values.py index 3754d24bab70b..64e2500e47e50 100644 --- a/posthog/hogql_queries/insights/trends/breakdown_values.py +++ b/posthog/hogql_queries/insights/trends/breakdown_values.py @@ -34,14 +34,14 @@ def __init__( self.histogram_bin_count = int(histogram_bin_count) if histogram_bin_count is not None else None self.group_type_index = int(group_type_index) if group_type_index is not None else None - def get_breakdown_values(self) -> List[str]: + def get_breakdown_values(self) -> List[str | int]: if self.breakdown_type == "cohort": return [int(self.breakdown_field)] if self.breakdown_type == "hogql": select_field = ast.Alias( alias="value", - expr=parse_expr(self.breakdown_field), + expr=parse_expr(str(self.breakdown_field)), ) else: select_field = ast.Alias( diff --git a/posthog/hogql_queries/insights/trends/test/test_aggregation_operations.py b/posthog/hogql_queries/insights/trends/test/test_aggregation_operations.py index 656f2dc26ee69..06ae5974976ee 100644 --- a/posthog/hogql_queries/insights/trends/test/test_aggregation_operations.py +++ b/posthog/hogql_queries/insights/trends/test/test_aggregation_operations.py @@ -1,7 +1,21 @@ -from typing import cast +from datetime import datetime +from typing import Literal, Union, cast + +import pytest from posthog.hogql import ast from posthog.hogql.parser import parse_select -from posthog.hogql_queries.insights.trends.aggregation_operations import QueryAlternator +from posthog.hogql_queries.insights.trends.aggregation_operations import ( + AggregationOperations, + QueryAlternator, +) +from posthog.hogql_queries.utils.query_date_range import QueryDateRange +from posthog.models.team.team import Team +from posthog.schema import ( + BaseMathType, + CountPerActorMathType, + EventsNode, + PropertyMathType, +) class TestQueryAlternator: @@ -44,3 +58,91 @@ def test_replace_select_from(self): query_modifier.build() assert query.select_from.table.chain == ["groups"] + + +@pytest.mark.parametrize( + "math,math_property", + [ + [BaseMathType.total, None], + [BaseMathType.dau, None], + [BaseMathType.weekly_active, None], + [BaseMathType.monthly_active, None], + [BaseMathType.unique_session, None], + [PropertyMathType.avg, "$browser"], + [PropertyMathType.sum, "$browser"], + [PropertyMathType.min, "$browser"], + [PropertyMathType.max, "$browser"], + [PropertyMathType.median, "$browser"], + [PropertyMathType.p90, "$browser"], + [PropertyMathType.p95, "$browser"], + [PropertyMathType.p99, "$browser"], + [CountPerActorMathType.avg_count_per_actor, None], + [CountPerActorMathType.min_count_per_actor, None], + [CountPerActorMathType.max_count_per_actor, None], + [CountPerActorMathType.median_count_per_actor, None], + [CountPerActorMathType.p90_count_per_actor, None], + [CountPerActorMathType.p95_count_per_actor, None], + [CountPerActorMathType.p99_count_per_actor, None], + ["hogql", None], + ], +) +def test_all_cases_return( + math: Union[ + BaseMathType, + PropertyMathType, + CountPerActorMathType, + Literal["unique_group"], + Literal["hogql"], + ], + math_property: str, +): + series = EventsNode(event="$pageview", math=math, math_property=math_property) + query_date_range = QueryDateRange(date_range=None, interval=None, now=datetime.now(), team=Team()) + + agg_ops = AggregationOperations(series, query_date_range) + res = agg_ops.select_aggregation() + assert isinstance(res, ast.Expr) + + +@pytest.mark.parametrize( + "math,result", + [ + [BaseMathType.total, False], + [BaseMathType.dau, False], + [BaseMathType.weekly_active, True], + [BaseMathType.monthly_active, True], + [BaseMathType.unique_session, False], + [PropertyMathType.avg, False], + [PropertyMathType.sum, False], + [PropertyMathType.min, False], + [PropertyMathType.max, False], + [PropertyMathType.median, False], + [PropertyMathType.p90, False], + [PropertyMathType.p95, False], + [PropertyMathType.p99, False], + [CountPerActorMathType.avg_count_per_actor, True], + [CountPerActorMathType.min_count_per_actor, True], + [CountPerActorMathType.max_count_per_actor, True], + [CountPerActorMathType.median_count_per_actor, True], + [CountPerActorMathType.p90_count_per_actor, True], + [CountPerActorMathType.p95_count_per_actor, True], + [CountPerActorMathType.p99_count_per_actor, True], + ["hogql", False], + ], +) +def test_requiring_query_orchestration( + math: Union[ + BaseMathType, + PropertyMathType, + CountPerActorMathType, + Literal["unique_group"], + Literal["hogql"], + ], + result: bool, +): + series = EventsNode(event="$pageview", math=math) + query_date_range = QueryDateRange(date_range=None, interval=None, now=datetime.now(), team=Team()) + + agg_ops = AggregationOperations(series, query_date_range) + res = agg_ops.requires_query_orchestration() + assert res == result diff --git a/posthog/hogql_queries/insights/trends/test/test_trends_query_runner.py b/posthog/hogql_queries/insights/trends/test/test_trends_query_runner.py index 9344a227c48d4..f6afdfd591e85 100644 --- a/posthog/hogql_queries/insights/trends/test/test_trends_query_runner.py +++ b/posthog/hogql_queries/insights/trends/test/test_trends_query_runner.py @@ -1,12 +1,21 @@ from dataclasses import dataclass -from typing import List, Optional +from typing import Dict, List, Optional from freezegun import freeze_time from posthog.hogql_queries.insights.trends.trends_query_runner import TrendsQueryRunner +from posthog.models.cohort.cohort import Cohort +from posthog.models.property_definition import PropertyDefinition from posthog.schema import ( + ActionsNode, + BaseMathType, + BreakdownFilter, + BreakdownType, + ChartDisplayType, + CountPerActorMathType, DateRange, EventsNode, IntervalType, + PropertyMathType, TrendsFilter, TrendsQuery, ) @@ -28,6 +37,7 @@ class Series: class SeriesTestData: distinct_id: str events: List[Series] + properties: Dict[str, str | int] class TestQuery(ClickhouseTestMixin, APIBaseTest): @@ -36,9 +46,22 @@ class TestQuery(ClickhouseTestMixin, APIBaseTest): def _create_events(self, data: List[SeriesTestData]): person_result = [] + properties_to_create: Dict[str, str] = {} for person in data: first_timestamp = person.events[0].timestamps[0] + for key, value in person.properties.items(): + if key not in properties_to_create: + if isinstance(value, str): + type = "String" + elif isinstance(value, bool): + type = "Boolean" + elif isinstance(value, int): + type = "Numeric" + else: + type = "String" + properties_to_create[key] = type + with freeze_time(first_timestamp): person_result.append( _create_person( @@ -55,9 +78,14 @@ def _create_events(self, data: List[SeriesTestData]): _create_event( team=self.team, event=event.event, - distinct_id=id, + distinct_id=person.distinct_id, timestamp=timestamp, + properties=person.properties, ) + + for key, value in properties_to_create.items(): + PropertyDefinition.objects.create(team=self.team, name=key, property_type=value) + return person_result def _create_test_events(self): @@ -86,6 +114,7 @@ def _create_test_events(self): ], ), ], + properties={"$browser": "Chrome", "prop": 10, "bool_field": True}, ), SeriesTestData( distinct_id="p2", @@ -101,6 +130,7 @@ def _create_test_events(self): ], ), ], + properties={"$browser": "Firefox", "prop": 20, "bool_field": False}, ), SeriesTestData( distinct_id="p3", @@ -108,6 +138,7 @@ def _create_test_events(self): Series(event="$pageview", timestamps=["2020-01-12T12:00:00Z"]), Series(event="$pageleave", timestamps=["2020-01-13T12:00:00Z"]), ], + properties={"$browser": "Edge", "prop": 30, "bool_field": True}, ), SeriesTestData( distinct_id="p4", @@ -115,55 +146,94 @@ def _create_test_events(self): Series(event="$pageview", timestamps=["2020-01-15T12:00:00Z"]), Series(event="$pageleave", timestamps=["2020-01-16T12:00:00Z"]), ], + properties={"$browser": "Safari", "prop": 40, "bool_field": False}, ), ] ) - def _create_query_runner(self, date_from, date_to, interval, series, trends_filters) -> TrendsQueryRunner: - query_series = [EventsNode(event="$pageview")] if series is None else series + def _create_query_runner( + self, + date_from: str, + date_to: str, + interval: IntervalType, + series: Optional[List[EventsNode | ActionsNode]], + trends_filters: Optional[TrendsFilter], + breakdown: Optional[BreakdownFilter], + ) -> TrendsQueryRunner: + query_series: List[EventsNode | ActionsNode] = [EventsNode(event="$pageview")] if series is None else series query = TrendsQuery( dateRange=DateRange(date_from=date_from, date_to=date_to), interval=interval, series=query_series, trendsFilter=trends_filters, + breakdown=breakdown, ) return TrendsQueryRunner(team=self.team, query=query) def _run_trends_query( self, - date_from, - date_to, - interval, - series=None, + date_from: str, + date_to: str, + interval: IntervalType, + series: Optional[List[EventsNode | ActionsNode]], trends_filters: Optional[TrendsFilter] = None, + breakdown: Optional[BreakdownFilter] = None, ): - return self._create_query_runner(date_from, date_to, interval, series, trends_filters).calculate() + return self._create_query_runner(date_from, date_to, interval, series, trends_filters, breakdown).calculate() def test_trends_query_label(self): self._create_test_events() - response = self._run_trends_query(self.default_date_from, self.default_date_to, IntervalType.day) + response = self._run_trends_query( + self.default_date_from, + self.default_date_to, + IntervalType.day, + None, + None, + None, + ) self.assertEqual("$pageview", response.results[0]["label"]) def test_trends_query_count(self): self._create_test_events() - response = self._run_trends_query(self.default_date_from, self.default_date_to, IntervalType.day) + response = self._run_trends_query( + self.default_date_from, + self.default_date_to, + IntervalType.day, + None, + None, + None, + ) self.assertEqual(10, response.results[0]["count"]) def test_trends_query_data(self): self._create_test_events() - response = self._run_trends_query(self.default_date_from, self.default_date_to, IntervalType.day) + response = self._run_trends_query( + self.default_date_from, + self.default_date_to, + IntervalType.day, + None, + None, + None, + ) self.assertEqual([1, 0, 1, 3, 1, 0, 2, 0, 1, 0, 1], response.results[0]["data"]) def test_trends_query_days(self): self._create_test_events() - response = self._run_trends_query(self.default_date_from, self.default_date_to, IntervalType.day) + response = self._run_trends_query( + self.default_date_from, + self.default_date_to, + IntervalType.day, + None, + None, + None, + ) self.assertEqual( [ @@ -185,7 +255,14 @@ def test_trends_query_days(self): def test_trends_query_labels(self): self._create_test_events() - response = self._run_trends_query(self.default_date_from, self.default_date_to, IntervalType.day) + response = self._run_trends_query( + self.default_date_from, + self.default_date_to, + IntervalType.day, + None, + None, + None, + ) self.assertEqual( [ @@ -207,7 +284,12 @@ def test_trends_query_labels(self): def test_trends_query_labels_hour(self): self._create_test_events() - response = self._run_trends_query(self.default_date_from, self.default_date_from, IntervalType.hour) + response = self._run_trends_query( + self.default_date_from, + self.default_date_from, + IntervalType.hour, + [EventsNode(event="$pageview")], + ) self.assertEqual( [ @@ -321,3 +403,520 @@ def test_trends_query_formula_with_compare(self): self.assertEqual("Formula (A+B)", response.results[0]["label"]) self.assertEqual("Formula (A+B)", response.results[1]["label"]) + + def test_trends_breakdowns(self): + self._create_test_events() + + response = self._run_trends_query( + "2020-01-09", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview")], + None, + BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + ) + + breakdown_labels = [result["breakdown_value"] for result in response.results] + + assert len(response.results) == 4 + assert breakdown_labels == ["Chrome", "Edge", "Firefox", "Safari"] + assert response.results[0]["label"] == f"$pageview - Chrome" + assert response.results[1]["label"] == f"$pageview - Edge" + assert response.results[2]["label"] == f"$pageview - Firefox" + assert response.results[3]["label"] == f"$pageview - Safari" + assert response.results[0]["count"] == 6 + assert response.results[1]["count"] == 1 + assert response.results[2]["count"] == 2 + assert response.results[3]["count"] == 1 + + def test_trends_breakdowns_boolean(self): + self._create_test_events() + + response = self._run_trends_query( + "2020-01-09", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview")], + None, + BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="bool_field"), + ) + + breakdown_labels = [result["breakdown_value"] for result in response.results] + + assert len(response.results) == 2 + assert breakdown_labels == ["false", "true"] + + assert response.results[0]["label"] == f"$pageview - false" + assert response.results[1]["label"] == f"$pageview - true" + + assert response.results[0]["count"] == 3 + assert response.results[1]["count"] == 7 + + def test_trends_breakdowns_histogram(self): + self._create_test_events() + + response = self._run_trends_query( + "2020-01-09", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview")], + None, + BreakdownFilter( + breakdown_type=BreakdownType.event, + breakdown="prop", + breakdown_histogram_bin_count=4, + ), + ) + + breakdown_labels = [result["breakdown_value"] for result in response.results] + + assert len(response.results) == 5 + assert breakdown_labels == [ + '["",""]', + "[10.0,17.5]", + "[17.5,25.0]", + "[25.0,32.5]", + "[32.5,40.01]", + ] + + assert response.results[0]["label"] == '$pageview - ["",""]' + assert response.results[1]["label"] == "$pageview - [10.0,17.5]" + assert response.results[2]["label"] == "$pageview - [17.5,25.0]" + assert response.results[3]["label"] == "$pageview - [25.0,32.5]" + assert response.results[4]["label"] == "$pageview - [32.5,40.01]" + + assert response.results[0]["data"] == [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + assert response.results[1]["data"] == [0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0] + assert response.results[2]["data"] == [1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0] + assert response.results[3]["data"] == [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0] + assert response.results[4]["data"] == [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0] + + def test_trends_breakdowns_cohort(self): + self._create_test_events() + cohort = Cohort.objects.create( + team=self.team, + groups=[ + { + "properties": [ + { + "key": "name", + "value": "p1", + "type": "person", + } + ] + } + ], + name="cohort", + ) + cohort.calculate_people_ch(pending_version=0) + + response = self._run_trends_query( + "2020-01-09", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview")], + None, + BreakdownFilter(breakdown_type=BreakdownType.cohort, breakdown=[cohort.pk]), + ) + + assert len(response.results) == 1 + + assert response.results[0]["label"] == f"$pageview - cohort" + assert response.results[0]["count"] == 6 + assert response.results[0]["data"] == [ + 0, + 0, + 1, + 1, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + ] + + def test_trends_breakdowns_hogql(self): + self._create_test_events() + + response = self._run_trends_query( + "2020-01-09", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview")], + None, + BreakdownFilter(breakdown_type=BreakdownType.hogql, breakdown="properties.$browser"), + ) + + breakdown_labels = [result["breakdown_value"] for result in response.results] + + assert len(response.results) == 4 + assert breakdown_labels == ["Chrome", "Edge", "Firefox", "Safari"] + assert response.results[0]["label"] == f"$pageview - Chrome" + assert response.results[1]["label"] == f"$pageview - Edge" + assert response.results[2]["label"] == f"$pageview - Firefox" + assert response.results[3]["label"] == f"$pageview - Safari" + assert response.results[0]["count"] == 6 + assert response.results[1]["count"] == 1 + assert response.results[2]["count"] == 2 + assert response.results[3]["count"] == 1 + + def test_trends_breakdowns_and_compare(self): + self._create_test_events() + + response = self._run_trends_query( + "2020-01-15", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview")], + TrendsFilter(compare=True), + BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + ) + + breakdown_labels = [result["breakdown_value"] for result in response.results] + + assert len(response.results) == 5 + assert breakdown_labels == [ + "Chrome", + "Safari", + "Chrome", + "Edge", + "Firefox", + ] + + assert response.results[0]["label"] == f"$pageview - Chrome" + assert response.results[1]["label"] == f"$pageview - Safari" + assert response.results[2]["label"] == f"$pageview - Chrome" + assert response.results[3]["label"] == f"$pageview - Edge" + assert response.results[4]["label"] == f"$pageview - Firefox" + + assert response.results[0]["count"] == 3 + assert response.results[1]["count"] == 1 + assert response.results[2]["count"] == 3 + assert response.results[3]["count"] == 1 + assert response.results[4]["count"] == 2 + + assert response.results[0]["compare_label"] == "current" + assert response.results[1]["compare_label"] == "current" + assert response.results[2]["compare_label"] == "previous" + assert response.results[3]["compare_label"] == "previous" + assert response.results[4]["compare_label"] == "previous" + + assert response.results[0]["compare"] is True + assert response.results[1]["compare"] is True + assert response.results[2]["compare"] is True + assert response.results[3]["compare"] is True + assert response.results[4]["compare"] is True + + def test_trends_breakdown_and_aggregation_query_orchestration(self): + self._create_test_events() + + response = self._run_trends_query( + "2020-01-09", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview", math=PropertyMathType.sum, math_property="prop")], + None, + BreakdownFilter(breakdown_type=BreakdownType.event, breakdown="$browser"), + ) + + breakdown_labels = [result["breakdown_value"] for result in response.results] + + assert len(response.results) == 4 + assert breakdown_labels == ["Chrome", "Edge", "Firefox", "Safari"] + assert response.results[0]["label"] == f"$pageview - Chrome" + assert response.results[1]["label"] == f"$pageview - Edge" + assert response.results[2]["label"] == f"$pageview - Firefox" + assert response.results[3]["label"] == f"$pageview - Safari" + + assert response.results[0]["data"] == [ + 0, + 0, + 10, + 10, + 10, + 0, + 10, + 0, + 10, + 0, + 10, + 0, + ] + assert response.results[1]["data"] == [ + 0, + 0, + 0, + 30, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ] + assert response.results[2]["data"] == [ + 20, + 0, + 0, + 20, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ] + assert response.results[3]["data"] == [ + 0, + 0, + 0, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 0, + 0, + ] + + def test_trends_aggregation_hogql(self): + self._create_test_events() + + response = self._run_trends_query( + "2020-01-09", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview", math="hogql", math_hogql="sum(properties.prop)")], + None, + None, + ) + + assert len(response.results) == 1 + assert response.results[0]["data"] == [ + 20, + 0, + 10, + 60, + 10, + 0, + 50, + 0, + 10, + 0, + 10, + 0, + ] + + def test_trends_aggregation_total(self): + self._create_test_events() + + response = self._run_trends_query( + "2020-01-09", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview", math=BaseMathType.total)], + None, + None, + ) + + assert len(response.results) == 1 + assert response.results[0]["count"] == 10 + + def test_trends_aggregation_dau(self): + self._create_test_events() + + response = self._run_trends_query( + "2020-01-09", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview", math=BaseMathType.dau)], + None, + None, + ) + + assert len(response.results) == 1 + assert response.results[0]["data"] == [1, 0, 1, 3, 1, 0, 2, 0, 1, 0, 1, 0] + + def test_trends_aggregation_wau(self): + self._create_test_events() + + response = self._run_trends_query( + "2020-01-09", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview", math=BaseMathType.weekly_active)], + None, + None, + ) + + assert len(response.results) == 1 + assert response.results[0]["data"] == [1, 1, 2, 3, 3, 3, 4, 4, 4, 4, 2, 2] + + def test_trends_aggregation_mau(self): + self._create_test_events() + + response = self._run_trends_query( + "2020-01-09", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview", math=BaseMathType.monthly_active)], + None, + None, + ) + + assert len(response.results) == 1 + assert response.results[0]["data"] == [1, 1, 2, 3, 3, 3, 4, 4, 4, 4, 4, 4] + + def test_trends_aggregation_unique(self): + self._create_test_events() + + response = self._run_trends_query( + "2020-01-09", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview", math=BaseMathType.unique_session)], + None, + None, + ) + + assert len(response.results) == 1 + assert response.results[0]["data"] == [1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0] + + def test_trends_aggregation_property_sum(self): + self._create_test_events() + + response = self._run_trends_query( + "2020-01-09", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview", math=PropertyMathType.sum, math_property="prop")], + None, + None, + ) + + assert len(response.results) == 1 + assert response.results[0]["data"] == [ + 20, + 0, + 10, + 60, + 10, + 0, + 50, + 0, + 10, + 0, + 10, + 0, + ] + + def test_trends_aggregation_property_avg(self): + self._create_test_events() + + response = self._run_trends_query( + "2020-01-09", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview", math=PropertyMathType.avg, math_property="prop")], + None, + None, + ) + + assert len(response.results) == 1 + assert response.results[0]["data"] == [ + 20, + 0, + 10, + 20, + 10, + 0, + 25, + 0, + 10, + 0, + 10, + 0, + ] + + def test_trends_aggregation_per_actor_max(self): + self._create_test_events() + + response = self._run_trends_query( + "2020-01-09", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview", math=CountPerActorMathType.max_count_per_actor)], + None, + None, + ) + + assert len(response.results) == 1 + assert response.results[0]["data"] == [ + 1, + 0, + 1, + 1, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + ] + + def test_trends_display_aggregate(self): + self._create_test_events() + + response = self._run_trends_query( + "2020-01-09", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview")], + TrendsFilter(display=ChartDisplayType.BoldNumber), + None, + ) + + assert len(response.results) == 1 + assert response.results[0]["data"] == [] + assert response.results[0]["days"] == [] + assert response.results[0]["count"] == 0 + assert response.results[0]["aggregated_value"] == 10 + + def test_trends_display_cumulative(self): + self._create_test_events() + + response = self._run_trends_query( + "2020-01-09", + "2020-01-20", + IntervalType.day, + [EventsNode(event="$pageview")], + TrendsFilter(display=ChartDisplayType.ActionsLineGraphCumulative), + None, + ) + + assert len(response.results) == 1 + assert response.results[0]["data"] == [ + 1, + 1, + 2, + 5, + 6, + 6, + 8, + 8, + 9, + 9, + 10, + 10, + ] diff --git a/posthog/hogql_queries/insights/trends/test/test_utils.py b/posthog/hogql_queries/insights/trends/test/test_utils.py new file mode 100644 index 0000000000000..100809020b8f9 --- /dev/null +++ b/posthog/hogql_queries/insights/trends/test/test_utils.py @@ -0,0 +1,38 @@ +import pytest +from posthog.hogql_queries.insights.trends.utils import get_properties_chain + + +def test_properties_chain_person(): + p1 = get_properties_chain(breakdown_type="person", breakdown_field="field", group_type_index=None) + assert p1 == ["person", "properties", "field"] + + p2 = get_properties_chain(breakdown_type="person", breakdown_field="field", group_type_index=1) + assert p2 == ["person", "properties", "field"] + + +def test_properties_chain_session(): + p1 = get_properties_chain(breakdown_type="session", breakdown_field="anything", group_type_index=None) + assert p1 == ["session", "session_duration"] + + p2 = get_properties_chain(breakdown_type="session", breakdown_field="", group_type_index=None) + assert p2 == ["session", "session_duration"] + + p3 = get_properties_chain(breakdown_type="session", breakdown_field="", group_type_index=1) + assert p3 == ["session", "session_duration"] + + +def test_properties_chain_groups(): + p1 = get_properties_chain(breakdown_type="group", breakdown_field="anything", group_type_index=1) + assert p1 == ["group_1", "properties", "anything"] + + with pytest.raises(Exception) as e: + get_properties_chain(breakdown_type="group", breakdown_field="anything", group_type_index=None) + assert "group_type_index missing from params" in str(e.value) + + +def test_properties_chain_events(): + p1 = get_properties_chain(breakdown_type="event", breakdown_field="anything", group_type_index=None) + assert p1 == ["properties", "anything"] + + p2 = get_properties_chain(breakdown_type="event", breakdown_field="anything_else", group_type_index=1) + assert p2 == ["properties", "anything_else"] diff --git a/posthog/hogql_queries/insights/trends/trends_query_runner.py b/posthog/hogql_queries/insights/trends/trends_query_runner.py index a8d25dd1b5081..ff013658d021e 100644 --- a/posthog/hogql_queries/insights/trends/trends_query_runner.py +++ b/posthog/hogql_queries/insights/trends/trends_query_runner.py @@ -189,13 +189,13 @@ def get_value(name: str, val: Any): item.strftime( "%-d-%b-%Y{}".format(" %H:%M" if self.query_date_range.interval_name == "hour" else "") ) - for item in val[0] + for item in get_value("date", val) ], "days": [ item.strftime( "%Y-%m-%d{}".format(" %H:%M:%S" if self.query_date_range.interval_name == "hour" else "") ) - for item in val[0] + for item in get_value("date", val) ], "count": float(sum(get_value("total", val))), "label": "All events" @@ -373,7 +373,7 @@ def _is_breakdown_field_boolean(self): ) return field_type == "Boolean" - def _convert_boolean(self, value: any): + def _convert_boolean(self, value: Any): bool_map = {1: "true", 0: "false", "": ""} return bool_map.get(value) or value @@ -390,7 +390,7 @@ def _event_property( group_type_index=group_type_index if field_type == PropertyDefinition.Type.GROUP else None, ).property_type - def _query_to_filter(self) -> Dict[str, any]: + def _query_to_filter(self) -> Dict[str, Any]: filter_dict = { "insight": "TRENDS", "properties": self.query.properties, diff --git a/posthog/hogql_queries/insights/trends/utils.py b/posthog/hogql_queries/insights/trends/utils.py index 9a7566969c61b..2c7ec5d1eff66 100644 --- a/posthog/hogql_queries/insights/trends/utils.py +++ b/posthog/hogql_queries/insights/trends/utils.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List, Literal, Optional, Union from posthog.schema import ActionsNode, EventsNode @@ -9,7 +9,9 @@ def series_event_name(series: EventsNode | ActionsNode) -> str | None: def get_properties_chain( - breakdown_type: str, breakdown_field: str, group_type_index: Optional[float | int] + breakdown_type: Union[Literal["person"], Literal["session"], Literal["group"], Literal["event"]], + breakdown_field: str, + group_type_index: Optional[float | int], ) -> List[str]: if breakdown_type == "person": return ["person", "properties", breakdown_field] @@ -20,5 +22,7 @@ def get_properties_chain( if breakdown_type == "group" and group_type_index is not None: group_type_index_int = int(group_type_index) return [f"group_{group_type_index_int}", "properties", breakdown_field] + elif breakdown_type == "group" and group_type_index is None: + raise Exception("group_type_index missing from params") return ["properties", breakdown_field]