From f2b84624de37357aeca52ec7b353546cc47e007b Mon Sep 17 00:00:00 2001 From: Robbie Date: Wed, 14 Feb 2024 11:22:30 +0000 Subject: [PATCH] feat(web-analytics): Add compare option to web overview (#20315) Add compare option to web overview --- frontend/src/queries/schema.json | 3 + frontend/src/queries/schema.ts | 1 + .../scenes/web-analytics/webAnalyticsLogic.ts | 3 +- .../web_analytics/test/test_web_overview.py | 11 ++- .../web_analytics/web_overview.py | 98 ++++++++++++++++--- posthog/schema.py | 1 + 6 files changed, 95 insertions(+), 22 deletions(-) diff --git a/frontend/src/queries/schema.json b/frontend/src/queries/schema.json index 8d5c567d9d999..98d40729fc536 100644 --- a/frontend/src/queries/schema.json +++ b/frontend/src/queries/schema.json @@ -4993,6 +4993,9 @@ "WebOverviewQuery": { "additionalProperties": false, "properties": { + "compare": { + "type": "boolean" + }, "dateRange": { "$ref": "#/definitions/DateRange" }, diff --git a/frontend/src/queries/schema.ts b/frontend/src/queries/schema.ts index 82e230be29ab4..44912c3862bf0 100644 --- a/frontend/src/queries/schema.ts +++ b/frontend/src/queries/schema.ts @@ -968,6 +968,7 @@ export interface WebAnalyticsQueryBase { export interface WebOverviewQuery extends WebAnalyticsQueryBase { kind: NodeKind.WebOverviewQuery response?: WebOverviewQueryResponse + compare?: boolean } export interface WebOverviewItem { diff --git a/frontend/src/scenes/web-analytics/webAnalyticsLogic.ts b/frontend/src/scenes/web-analytics/webAnalyticsLogic.ts index fea26dcf8fa8e..7a6dcd00332c2 100644 --- a/frontend/src/scenes/web-analytics/webAnalyticsLogic.ts +++ b/frontend/src/scenes/web-analytics/webAnalyticsLogic.ts @@ -402,7 +402,7 @@ export const webAnalyticsLogic = kea([ date_from: dateFrom, date_to: dateTo, } - const compare = !!(dateRange.date_from && dateRange.date_to) + const compare = !!dateRange.date_from && dateRange.date_from !== 'all' const sampling = { enabled: !!values.featureFlags[FEATURE_FLAGS.WEB_ANALYTICS_SAMPLING], @@ -428,6 +428,7 @@ export const webAnalyticsLogic = kea([ properties: webAnalyticsFilters, dateRange, sampling, + compare, }, insightProps: createInsightProps(TileId.OVERVIEW), canOpenModal: false, diff --git a/posthog/hogql_queries/web_analytics/test/test_web_overview.py b/posthog/hogql_queries/web_analytics/test/test_web_overview.py index 0d560ee6c182e..e4fc03121ab1b 100644 --- a/posthog/hogql_queries/web_analytics/test/test_web_overview.py +++ b/posthog/hogql_queries/web_analytics/test/test_web_overview.py @@ -35,10 +35,11 @@ def _create_events(self, data, event="$pageview"): ) return person_result - def _run_web_overview_query(self, date_from, date_to): + def _run_web_overview_query(self, date_from, date_to, compare=True): query = WebOverviewQuery( dateRange=DateRange(date_from=date_from, date_to=date_to), properties=[], + compare=compare, ) runner = WebOverviewQueryRunner(team=self.team, query=query) return runner.calculate() @@ -95,24 +96,24 @@ def test_all_time(self): ] ) - results = self._run_web_overview_query("all", "2023-12-15").results + results = self._run_web_overview_query("all", "2023-12-15", compare=False).results visitors = results[0] self.assertEqual("visitors", visitors.key) self.assertEqual(2, visitors.value) - self.assertEqual(0, visitors.previous) + self.assertEqual(None, visitors.previous) self.assertEqual(None, visitors.changeFromPreviousPct) views = results[1] self.assertEqual("views", views.key) self.assertEqual(4, views.value) - self.assertEqual(0, views.previous) + self.assertEqual(None, views.previous) self.assertEqual(None, views.changeFromPreviousPct) sessions = results[2] self.assertEqual("sessions", sessions.key) self.assertEqual(3, sessions.value) - self.assertEqual(0, sessions.previous) + self.assertEqual(None, sessions.previous) self.assertEqual(None, sessions.changeFromPreviousPct) duration_s = results[3] diff --git a/posthog/hogql_queries/web_analytics/web_overview.py b/posthog/hogql_queries/web_analytics/web_overview.py index 26a9255c940cf..2019803faf78a 100644 --- a/posthog/hogql_queries/web_analytics/web_overview.py +++ b/posthog/hogql_queries/web_analytics/web_overview.py @@ -24,8 +24,9 @@ def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery: mid = self.query_date_range.date_from_as_hogql() end = self.query_date_range.date_to_as_hogql() with self.timings.measure("overview_stats_query"): - query = parse_select( - """ + if self.query.compare: + return parse_select( + """ WITH pages_query AS ( SELECT uniq(if(timestamp >= {mid} AND timestamp < {end}, events.person_id, NULL)) AS unique_users, @@ -86,21 +87,86 @@ def to_query(self) -> ast.SelectQuery | ast.SelectUnionQuery: FROM pages_query CROSS JOIN sessions_query """, - timings=self.timings, - placeholders={ - "start": start, - "mid": mid, - "end": end, - "event_properties": self.event_properties(), - "session_where": self.session_where(include_previous_period=True), - "session_having": self.session_having(include_previous_period=True), - "sample_rate": self._sample_ratio, - "sample_expr": ast.SampleExpr(sample_value=self._sample_ratio), - }, - backend="cpp", - ) + timings=self.timings, + placeholders={ + "start": start, + "mid": mid, + "end": end, + "event_properties": self.event_properties(), + "session_where": self.session_where(include_previous_period=True), + "session_having": self.session_having(include_previous_period=True), + "sample_rate": self._sample_ratio, + }, + ) + else: + return parse_select( + """ +WITH pages_query AS ( + SELECT + uniq(events.person_id) AS unique_users, + count() AS current_pageviews, + uniq(events.properties.$session_id) AS unique_sessions + FROM + events + SAMPLE {sample_rate} + WHERE + event = '$pageview' AND + timestamp >= {mid} AND + timestamp < {end} AND + {event_properties} + ), +sessions_query AS ( + SELECT + avg(duration_s) AS avg_duration_s, + avg(is_bounce) AS bounce_rate + FROM (SELECT + events.properties.`$session_id` AS session_id, + min(events.timestamp) AS min_timestamp, + max(events.timestamp) AS max_timestamp, + dateDiff('second', min_timestamp, max_timestamp) AS duration_s, + countIf(events.event == '$pageview') AS num_pageviews, + countIf(events.event == '$autocapture') AS num_autocaptures, - return query + -- definition of a GA4 bounce from here https://support.google.com/analytics/answer/12195621?hl=en + (num_autocaptures == 0 AND num_pageviews <= 1 AND duration_s < 10) AS is_bounce + FROM + events + SAMPLE {sample_rate} + WHERE + session_id IS NOT NULL + AND (events.event == '$pageview' OR events.event == '$autocapture' OR events.event == '$pageleave') + AND ({session_where}) + GROUP BY + events.properties.`$session_id` + HAVING + ({session_having}) + ) + ) +SELECT + unique_users, + NULL as previous_unique_users, + current_pageviews, + NULL as previous_pageviews, + unique_sessions, + NULL as previous_unique_sessions, + avg_duration_s, + NULL as prev_avg_duration_s, + bounce_rate, + NULL as prev_bounce_rate +FROM pages_query +CROSS JOIN sessions_query + """, + timings=self.timings, + placeholders={ + "start": start, + "mid": mid, + "end": end, + "event_properties": self.event_properties(), + "session_where": self.session_where(include_previous_period=False), + "session_having": self.session_having(include_previous_period=False), + "sample_rate": self._sample_ratio, + }, + ) def calculate(self): response = execute_hogql_query( diff --git a/posthog/schema.py b/posthog/schema.py index c82ea0682f99a..9e5fcc92a12b3 100644 --- a/posthog/schema.py +++ b/posthog/schema.py @@ -1540,6 +1540,7 @@ class WebOverviewQuery(BaseModel): model_config = ConfigDict( extra="forbid", ) + compare: Optional[bool] = None dateRange: Optional[DateRange] = None kind: Literal["WebOverviewQuery"] = "WebOverviewQuery" properties: List[Union[EventPropertyFilter, PersonPropertyFilter]]