diff --git a/frontend/src/queries/schema.json b/frontend/src/queries/schema.json index fd5e7c729d3026..35406985cd7f93 100644 --- a/frontend/src/queries/schema.json +++ b/frontend/src/queries/schema.json @@ -546,22 +546,6 @@ }, "type": "object" }, - "AssistantCompareFilter": { - "additionalProperties": false, - "properties": { - "compare": { - "default": false, - "description": "Whether to compare the current date range to a previous date range.", - "type": "boolean" - }, - "compare_to": { - "default": "-7d", - "description": "The date range to compare to. The value is a relative date. Examples of relative dates are: `-1y` for 1 year ago, `-14m` for 14 months ago, `-100w` for 100 weeks ago, `-14d` for 14 days ago, `-30h` for 30 hours ago.", - "type": "string" - } - }, - "type": "object" - }, "AssistantDateTimePropertyFilter": { "additionalProperties": false, "properties": { @@ -3792,9 +3776,13 @@ "additionalProperties": false, "properties": { "compare": { + "default": false, + "description": "Whether to compare the current date range to a previous date range.", "type": "boolean" }, "compare_to": { + "default": "-7d", + "description": "The date range to compare to. The value is a relative date. Examples of relative dates are: `-1y` for 1 year ago, `-14m` for 14 months ago, `-100w` for 100 weeks ago, `-14d` for 14 days ago, `-30h` for 30 hours ago.", "type": "string" } }, @@ -12869,6 +12857,9 @@ "WebExternalClicksTableQuery": { "additionalProperties": false, "properties": { + "compareFilter": { + "$ref": "#/definitions/CompareFilter" + }, "conversionGoal": { "anyOf": [ { @@ -12982,6 +12973,9 @@ "WebGoalsQuery": { "additionalProperties": false, "properties": { + "compareFilter": { + "$ref": "#/definitions/CompareFilter" + }, "conversionGoal": { "anyOf": [ { @@ -13122,14 +13116,7 @@ "additionalProperties": false, "properties": { "compareFilter": { - "anyOf": [ - { - "$ref": "#/definitions/CompareFilter" - }, - { - "type": "null" - } - ] + "$ref": "#/definitions/CompareFilter" }, "conversionGoal": { "anyOf": [ @@ -13261,14 +13248,7 @@ "$ref": "#/definitions/WebStatsBreakdown" }, "compareFilter": { - "anyOf": [ - { - "$ref": "#/definitions/CompareFilter" - }, - { - "type": "null" - } - ] + "$ref": "#/definitions/CompareFilter" }, "conversionGoal": { "anyOf": [ diff --git a/frontend/src/queries/schema.ts b/frontend/src/queries/schema.ts index f2000c03511381..431f0a6ab9bc98 100644 --- a/frontend/src/queries/schema.ts +++ b/frontend/src/queries/schema.ts @@ -1171,7 +1171,7 @@ export interface AssistantTrendsFilter { yAxisScaleType?: TrendsFilterLegacy['y_axis_scale_type'] } -export interface AssistantCompareFilter { +export interface CompareFilter { /** * Whether to compare the current date range to a previous date range. * @default false @@ -1789,6 +1789,7 @@ interface WebAnalyticsQueryBase> extends DataNode< dateRange?: DateRange properties: WebAnalyticsPropertyFilters conversionGoal?: WebAnalyticsConversionGoal | null + compareFilter?: CompareFilter sampling?: { enabled?: boolean forceSamplingRate?: SamplingRate @@ -1800,7 +1801,6 @@ interface WebAnalyticsQueryBase> extends DataNode< export interface WebOverviewQuery extends WebAnalyticsQueryBase { kind: NodeKind.WebOverviewQuery - compareFilter?: CompareFilter | null includeLCPScore?: boolean } @@ -1852,7 +1852,6 @@ export enum WebStatsBreakdown { export interface WebStatsTableQuery extends WebAnalyticsQueryBase { kind: NodeKind.WebStatsTableQuery breakdownBy: WebStatsBreakdown - compareFilter?: CompareFilter | null includeScrollDepth?: boolean // automatically sets includeBounceRate to true includeBounceRate?: boolean doPathCleaning?: boolean @@ -2356,11 +2355,6 @@ export interface BreakdownFilter { breakdown_hide_other_aggregation?: boolean | null // hides the "other" field for trends } -export interface CompareFilter { - compare?: boolean - compare_to?: string -} - // TODO: Rename to `DashboardFilters` for consistency with `HogQLFilters` export interface DashboardFilter { date_from?: string | null diff --git a/frontend/src/scenes/web-analytics/tiles/WebAnalyticsTile.tsx b/frontend/src/scenes/web-analytics/tiles/WebAnalyticsTile.tsx index cda07bc69ee55d..940c7a47a4d8d8 100644 --- a/frontend/src/scenes/web-analytics/tiles/WebAnalyticsTile.tsx +++ b/frontend/src/scenes/web-analytics/tiles/WebAnalyticsTile.tsx @@ -48,6 +48,8 @@ const VariationCell = ( isPercentage ? `${(value * 100).toFixed(1)}%` : value.toLocaleString() return function Cell({ value }) { + const { compareFilter } = useValues(webAnalyticsLogic) + if (!value) { return null } @@ -57,10 +59,11 @@ const VariationCell = ( } const [current, previous] = value as [number, number] + const pctChangeFromPrevious = previous === 0 && current === 0 // Special case, render as flatline ? 0 - : current === null + : current === null || !compareFilter || compareFilter.compare === false ? null : previous === null || previous === 0 ? Infinity diff --git a/frontend/src/scenes/web-analytics/webAnalyticsLogic.tsx b/frontend/src/scenes/web-analytics/webAnalyticsLogic.tsx index a535b5b54ed766..92f1905b6495b7 100644 --- a/frontend/src/scenes/web-analytics/webAnalyticsLogic.tsx +++ b/frontend/src/scenes/web-analytics/webAnalyticsLogic.tsx @@ -281,7 +281,7 @@ export const webAnalyticsLogic = kea([ return { tileId, tabId } }, setConversionGoalWarning: (warning: ConversionGoalWarning | null) => ({ warning }), - setCompareFilter: (compareFilter: CompareFilter | null) => ({ compareFilter }), + setCompareFilter: (compareFilter: CompareFilter) => ({ compareFilter }), }), reducers({ webAnalyticsFilters: [ @@ -475,7 +475,7 @@ export const webAnalyticsLogic = kea([ }, ], compareFilter: [ - { compare: true } as CompareFilter | null, + { compare: true } as CompareFilter, persistConfig, { setCompareFilter: (_, { compareFilter }) => compareFilter, @@ -621,7 +621,7 @@ export const webAnalyticsLogic = kea([ display: ChartDisplayType.ActionsLineGraph, ...trendsFilter, }, - compareFilter: compareFilter || { compare: false }, + compareFilter, filterTestAccounts, conversionGoal: featureFlags[FEATURE_FLAGS.WEB_ANALYTICS_CONVERSION_GOAL_FILTERS] ? conversionGoal @@ -882,6 +882,7 @@ export const webAnalyticsLogic = kea([ kind: NodeKind.WebExternalClicksTableQuery, properties: webAnalyticsFilters, dateRange, + compareFilter, // NOTE: Backend is not using this yet sampling, limit: 10, filterTestAccounts, @@ -1290,6 +1291,7 @@ export const webAnalyticsLogic = kea([ kind: NodeKind.WebGoalsQuery, properties: webAnalyticsFilters, dateRange, + compareFilter, sampling, limit: 10, filterTestAccounts, diff --git a/posthog/hogql_queries/web_analytics/stats_table.py b/posthog/hogql_queries/web_analytics/stats_table.py index a85e1a47dc7b30..ecb26f451adcc9 100644 --- a/posthog/hogql_queries/web_analytics/stats_table.py +++ b/posthog/hogql_queries/web_analytics/stats_table.py @@ -126,9 +126,6 @@ def to_path_scroll_bounce_query(self) -> ast.SelectQuery: with self.timings.measure("stats_table_bounce_query"): query = parse_select( """ -WITH - start_timestamp >= {date_from} AND start_timestamp < {date_to} AS current_period_segment, - start_timestamp >= {date_from_previous_period} AND start_timestamp < {date_from} AS previous_period_segment SELECT counts.breakdown_value AS "context.columns.breakdown_value", tuple(counts.visitors, counts.previous_visitors) AS "context.columns.visitors", @@ -139,10 +136,10 @@ def to_path_scroll_bounce_query(self) -> ast.SelectQuery: FROM ( SELECT breakdown_value, - uniqIf(filtered_person_id, current_period_segment) AS visitors, - uniqIf(filtered_person_id, previous_period_segment) AS previous_visitors, - sumIf(filtered_pageview_count, current_period_segment) AS views, - sumIf(filtered_pageview_count, previous_period_segment) AS previous_views + uniqIf(filtered_person_id, {current_period}) AS visitors, + uniqIf(filtered_person_id, {previous_period}) AS previous_visitors, + sumIf(filtered_pageview_count, {current_period}) AS views, + sumIf(filtered_pageview_count, {previous_period}) AS previous_views FROM ( SELECT any(person_id) AS filtered_person_id, @@ -152,12 +149,11 @@ def to_path_scroll_bounce_query(self) -> ast.SelectQuery: min(session.$start_timestamp ) AS start_timestamp FROM events WHERE and( - timestamp >= {date_from_previous_period}, - timestamp < {date_to}, events.event == '$pageview', + breakdown_value IS NOT NULL, + {inside_periods}, {event_properties}, {session_properties}, - breakdown_value IS NOT NULL ) GROUP BY session_id, breakdown_value ) @@ -166,8 +162,8 @@ def to_path_scroll_bounce_query(self) -> ast.SelectQuery: LEFT JOIN ( SELECT breakdown_value, - avgIf(is_bounce, current_period_segment) AS bounce_rate, - avgIf(is_bounce, previous_period_segment) AS previous_bounce_rate + avgIf(is_bounce, {current_period}) AS bounce_rate, + avgIf(is_bounce, {previous_period}) AS previous_bounce_rate FROM ( SELECT {bounce_breakdown_value} AS breakdown_value, -- use $entry_pathname to find the bounce rate for sessions that started on this pathname @@ -176,12 +172,11 @@ def to_path_scroll_bounce_query(self) -> ast.SelectQuery: min(session.$start_timestamp) as start_timestamp FROM events WHERE and( - timestamp >= {date_from_previous_period}, - timestamp < {date_to}, events.event == '$pageview', + breakdown_value IS NOT NULL, + {inside_periods}, {event_properties}, {session_properties}, - breakdown_value IS NOT NULL ) GROUP BY session_id, breakdown_value ) @@ -191,10 +186,10 @@ def to_path_scroll_bounce_query(self) -> ast.SelectQuery: LEFT JOIN ( SELECT breakdown_value, - avgMergeIf(average_scroll_percentage_state, current_period_segment) AS average_scroll_percentage, - avgMergeIf(average_scroll_percentage_state, previous_period_segment) AS previous_average_scroll_percentage, - avgMergeIf(scroll_gt80_percentage_state, current_period_segment) AS scroll_gt80_percentage, - avgMergeIf(scroll_gt80_percentage_state, previous_period_segment) AS previous_scroll_gt80_percentage + avgMergeIf(average_scroll_percentage_state, {current_period}) AS average_scroll_percentage, + avgMergeIf(average_scroll_percentage_state, {previous_period}) AS previous_average_scroll_percentage, + avgMergeIf(scroll_gt80_percentage_state, {current_period}) AS scroll_gt80_percentage, + avgMergeIf(scroll_gt80_percentage_state, {previous_period}) AS previous_scroll_gt80_percentage FROM ( SELECT {scroll_breakdown_value} AS breakdown_value, -- use $prev_pageview_pathname to find the scroll depth when leaving this pathname @@ -209,12 +204,11 @@ def to_path_scroll_bounce_query(self) -> ast.SelectQuery: min(session.$start_timestamp) AS start_timestamp FROM events WHERE and( - timestamp >= {date_from_previous_period}, - timestamp < {date_to}, or(events.event == '$pageview', events.event == '$pageleave'), + breakdown_value IS NOT NULL, + {inside_periods}, {event_properties_for_scroll}, {session_properties}, - breakdown_value IS NOT NULL ) GROUP BY session_id, breakdown_value ) @@ -230,12 +224,12 @@ def to_path_scroll_bounce_query(self) -> ast.SelectQuery: "session_properties": self._session_properties(), "event_properties": self._event_properties(), "event_properties_for_scroll": self._event_properties_for_scroll(), - "date_from_previous_period": self._date_from_previous_period(), - "date_from": self._date_from(), - "date_to": self._date_to(), "breakdown_value": self._counts_breakdown_value(), "scroll_breakdown_value": self._scroll_prev_pathname_breakdown(), "bounce_breakdown_value": self._bounce_entry_pathname_breakdown(), + "current_period": self._current_period_expression(), + "previous_period": self._previous_period_expression(), + "inside_periods": self._periods_expression(), }, ) assert isinstance(query, ast.SelectQuery) @@ -249,8 +243,8 @@ def to_path_bounce_query(self) -> ast.SelectQuery: query = parse_select( """ WITH - start_timestamp >= {date_from} AND start_timestamp < {date_to} AS current_period_segment, - start_timestamp >= {date_from_previous_period} AND start_timestamp < {date_from} AS previous_period_segment + start_timestamp >= {current_date_range_start} AND start_timestamp < {current_date_range_end} AS current_period_segment, + start_timestamp >= {previous_date_range_start} AND start_timestamp < {previous_date_range_end} AS previous_period_segment SELECT counts.breakdown_value AS "context.columns.breakdown_value", tuple(counts.visitors, counts.previous_visitors) AS "context.columns.visitors", @@ -259,10 +253,10 @@ def to_path_bounce_query(self) -> ast.SelectQuery: FROM ( SELECT breakdown_value, - uniqIf(filtered_person_id, current_period_segment) AS visitors, - uniqIf(filtered_person_id, previous_period_segment) AS previous_visitors, - sumIf(filtered_pageview_count, current_period_segment) AS views, - sumIf(filtered_pageview_count, previous_period_segment) AS previous_views + uniqIf(filtered_person_id, {current_period}) AS visitors, + uniqIf(filtered_person_id, {previous_period}) AS previous_visitors, + sumIf(filtered_pageview_count, {current_period}) AS views, + sumIf(filtered_pageview_count, {previous_period}) AS previous_views FROM ( SELECT any(person_id) AS filtered_person_id, @@ -272,12 +266,11 @@ def to_path_bounce_query(self) -> ast.SelectQuery: min(session.$start_timestamp) AS start_timestamp FROM events WHERE and( - timestamp >= {date_from_previous_period}, - timestamp < {date_to}, events.event == '$pageview', + {inside_periods}, {event_properties}, {session_properties}, - {where_breakdown} + {where_breakdown}, ) GROUP BY session_id, breakdown_value ) @@ -286,8 +279,8 @@ def to_path_bounce_query(self) -> ast.SelectQuery: LEFT JOIN ( SELECT breakdown_value, - avgIf(is_bounce, current_period_segment) AS bounce_rate, - avgIf(is_bounce, previous_period_segment) AS previous_bounce_rate + avgIf(is_bounce, {current_period}) AS bounce_rate, + avgIf(is_bounce, {previous_period}) AS previous_bounce_rate FROM ( SELECT {bounce_breakdown_value} AS breakdown_value, -- use $entry_pathname to find the bounce rate for sessions that started on this pathname @@ -296,12 +289,11 @@ def to_path_bounce_query(self) -> ast.SelectQuery: min(session.$start_timestamp) AS start_timestamp FROM events WHERE and( - timestamp >= {date_from_previous_period}, - timestamp < {date_to}, events.event == '$pageview', + breakdown_value IS NOT NULL, + {inside_periods}, {event_properties}, {session_properties}, - breakdown_value IS NOT NULL ) GROUP BY session_id, breakdown_value ) @@ -318,10 +310,10 @@ def to_path_bounce_query(self) -> ast.SelectQuery: "where_breakdown": self.where_breakdown(), "session_properties": self._session_properties(), "event_properties": self._event_properties(), - "date_from_previous_period": self._date_from_previous_period(), - "date_from": self._date_from(), - "date_to": self._date_to(), "bounce_breakdown_value": self._bounce_entry_pathname_breakdown(), + "current_period": self._current_period_expression(), + "previous_period": self._previous_period_expression(), + "inside_periods": self._periods_expression(), }, ) assert isinstance(query, ast.SelectQuery) @@ -338,17 +330,16 @@ def _main_inner_query(self, breakdown): any(session.$is_bounce) AS is_bounce, min(session.$start_timestamp) as start_timestamp FROM events -WHERE and(timestamp >= {date_from}, timestamp < {date_to}, {event_where}, {all_properties}, {where_breakdown}) +WHERE and({inside_periods}, {event_where}, {all_properties}, {where_breakdown}) GROUP BY session_id, breakdown_value """, timings=self.timings, placeholders={ "breakdown_value": breakdown, - "date_from": self._date_from_previous_period(), - "date_to": self._date_to(), "event_where": self.event_type_expr, "all_properties": self._all_properties(), "where_breakdown": self.where_breakdown(), + "inside_periods": self._periods_expression(), }, ) @@ -360,6 +351,52 @@ def _main_inner_query(self, breakdown): return query + def _current_period_expression(self, field="start_timestamp"): + return ast.Call( + name="and", + args=[ + ast.CompareOperation( + left=ast.Field(chain=[field]), + right=self.query_date_range.date_from_as_hogql(), + op=ast.CompareOperationOp.GtEq, + ), + ast.CompareOperation( + left=ast.Field(chain=[field]), + right=self.query_date_range.date_to_as_hogql(), + op=ast.CompareOperationOp.Lt, + ), + ], + ) + + def _previous_period_expression(self, field="start_timestamp"): + if not self.query_compare_to_date_range: + return ast.Constant(value=None) + + return ast.Call( + name="and", + args=[ + ast.CompareOperation( + left=ast.Field(chain=[field]), + right=self.query_compare_to_date_range.date_from_as_hogql(), + op=ast.CompareOperationOp.GtEq, + ), + ast.CompareOperation( + left=ast.Field(chain=[field]), + right=self.query_compare_to_date_range.date_to_as_hogql(), + op=ast.CompareOperationOp.Lt, + ), + ], + ) + + def _periods_expression(self, field="timestamp"): + return ast.Call( + name="or", + args=[ + self._current_period_expression(field), + self._previous_period_expression(field), + ], + ) + def _period_comparison_tuple(self, column, alias, function_name): return ast.Alias( alias=alias, @@ -372,10 +409,26 @@ def _period_comparison_tuple(self, column, alias, function_name): ) def _current_period_aggregate(self, function_name, column_name): - return self.period_aggregate(function_name, column_name, self._date_from(), self._date_to()) + if not self.query_compare_to_date_range: + return ast.Call(name=function_name, args=[ast.Field(chain=[column_name])]) + + return self.period_aggregate( + function_name, + column_name, + self.query_date_range.date_from_as_hogql(), + self.query_date_range.date_to_as_hogql(), + ) def _previous_period_aggregate(self, function_name, column_name): - return self.period_aggregate(function_name, column_name, self._date_from_previous_period(), self._date_from()) + if not self.query_compare_to_date_range: + return ast.Constant(value=None) + + return self.period_aggregate( + function_name, + column_name, + self.query_compare_to_date_range.date_from_as_hogql(), + self.query_compare_to_date_range.date_to_as_hogql(), + ) def _event_properties(self) -> ast.Expr: properties = [ @@ -410,15 +463,6 @@ def _all_properties(self) -> ast.Expr: properties = self.query.properties + self._test_account_filters return property_to_expr(properties, team=self.team) - def _date_to(self) -> ast.Expr: - return self.query_date_range.date_to_as_hogql() - - def _date_from(self) -> ast.Expr: - return self.query_date_range.date_from_as_hogql() - - def _date_from_previous_period(self) -> ast.Expr: - return self.query_date_range.previous_period_date_from_as_hogql() - def calculate(self): query = self.to_query() response = self.paginator.execute_hogql_query( diff --git a/posthog/hogql_queries/web_analytics/web_analytics_query_runner.py b/posthog/hogql_queries/web_analytics/web_analytics_query_runner.py index 016e1e50e8dada..cd1c4409be900f 100644 --- a/posthog/hogql_queries/web_analytics/web_analytics_query_runner.py +++ b/posthog/hogql_queries/web_analytics/web_analytics_query_runner.py @@ -15,6 +15,8 @@ from posthog.hogql.query import execute_hogql_query from posthog.hogql_queries.query_runner import QueryRunner from posthog.hogql_queries.utils.query_date_range import QueryDateRange +from posthog.hogql_queries.utils.query_compare_to_date_range import QueryCompareToDateRange +from posthog.hogql_queries.utils.query_previous_period_date_range import QueryPreviousPeriodDateRange from posthog.models import Action from posthog.models.filters.mixins.utils import cached_property from posthog.schema import ( @@ -47,6 +49,27 @@ def query_date_range(self): now=datetime.now(), ) + @cached_property + def query_compare_to_date_range(self): + if self.query.compareFilter is not None: + if isinstance(self.query.compareFilter.compare_to, str): + return QueryCompareToDateRange( + date_range=self.query.dateRange, + team=self.team, + interval=None, + now=datetime.now(), + compare_to=self.query.compareFilter.compare_to, + ) + elif self.query.compareFilter.compare: + return QueryPreviousPeriodDateRange( + date_range=self.query.dateRange, + team=self.team, + interval=None, + now=datetime.now(), + ) + + return None + @cached_property def pathname_property_filter(self) -> Optional[EventPropertyFilter]: for p in self.query.properties: diff --git a/posthog/hogql_queries/web_analytics/web_overview.py b/posthog/hogql_queries/web_analytics/web_overview.py index 62149a1eb7289a..cdf22494911cc0 100644 --- a/posthog/hogql_queries/web_analytics/web_overview.py +++ b/posthog/hogql_queries/web_analytics/web_overview.py @@ -1,13 +1,10 @@ from typing import Optional, Union import math -from django.utils.timezone import datetime - from posthog.hogql import ast from posthog.hogql.parser import parse_select from posthog.hogql.property import property_to_expr, get_property_type from posthog.hogql.query import execute_hogql_query -from posthog.hogql_queries.utils.query_date_range import QueryDateRange from posthog.hogql_queries.web_analytics.web_analytics_query_runner import ( WebAnalyticsQueryRunner, ) @@ -69,15 +66,6 @@ def calculate(self): dateTo=self.query_date_range.date_to_str, ) - @cached_property - def query_date_range(self): - return QueryDateRange( - date_range=self.query.dateRange, - team=self.team, - interval=None, - now=datetime.now(), - ) - def all_properties(self) -> ast.Expr: properties = self.query.properties + self._test_account_filters return property_to_expr(properties, team=self.team) @@ -112,10 +100,6 @@ def pageview_count_expression(self) -> ast.Expr: @cached_property def inner_select(self) -> ast.SelectQuery: - start = self.query_date_range.previous_period_date_from_as_hogql() - mid = self.query_date_range.date_from_as_hogql() - end = self.query_date_range.date_to_as_hogql() - parsed_select = parse_select( """ SELECT @@ -126,23 +110,34 @@ def inner_select(self) -> ast.SelectQuery: WHERE and( events.`$session_id` IS NOT NULL, {event_type_expr}, - timestamp >= {date_range_start}, - timestamp < {date_range_end}, + or( + and(timestamp >= {current_date_range_start}, timestamp < {current_date_range_end}), + and(timestamp >= {previous_date_range_start}, timestamp < {previous_date_range_end}) + ), {event_properties}, {session_properties} ) GROUP BY session_id -HAVING and( - start_timestamp >= {date_range_start}, - start_timestamp < {date_range_end} +HAVING or( + and(start_timestamp >= {current_date_range_start}, start_timestamp < {current_date_range_end}), + and(start_timestamp >= {previous_date_range_start}, start_timestamp < {previous_date_range_end}) ) """, placeholders={ - "date_range_start": start if self.query.compareFilter and self.query.compareFilter.compare else mid, - "date_range_end": end, "event_properties": self.event_properties(), "session_properties": self.session_properties(), "event_type_expr": self.event_type_expr, + # It's ok that we return ast.Constant(value=None) for previous_date_range_start + # and previous_date_range_end if there's no compare_to date range + # because HogQL will simply ignore that part of the where clause, it's that good! + "current_date_range_start": self.query_date_range.date_from_as_hogql(), + "current_date_range_end": self.query_date_range.date_to_as_hogql(), + "previous_date_range_start": self.query_compare_to_date_range.date_from_as_hogql() + if self.query_compare_to_date_range + else ast.Constant(value=None), + "previous_date_range_end": self.query_compare_to_date_range.date_to_as_hogql() + if self.query_compare_to_date_range + else ast.Constant(value=None), }, ) assert isinstance(parsed_select, ast.SelectQuery) @@ -177,21 +172,31 @@ def inner_select(self) -> ast.SelectQuery: @cached_property def outer_select(self) -> ast.SelectQuery: - start = self.query_date_range.previous_period_date_from_as_hogql() - mid = self.query_date_range.date_from_as_hogql() - end = self.query_date_range.date_to_as_hogql() - def current_period_aggregate(function_name, column_name, alias, params=None): - if not self.query.compareFilter or not self.query.compareFilter.compare: + if not self.query_compare_to_date_range: return ast.Call(name=function_name, params=params, args=[ast.Field(chain=[column_name])]) - return self.period_aggregate(function_name, column_name, mid, end, alias=alias, params=params) + return self.period_aggregate( + function_name, + column_name, + self.query_date_range.date_from_as_hogql(), + self.query_date_range.date_to_as_hogql(), + alias=alias, + params=params, + ) def previous_period_aggregate(function_name, column_name, alias, params=None): - if not self.query.compareFilter or not self.query.compareFilter.compare: + if not self.query_compare_to_date_range: return ast.Alias(alias=alias, expr=ast.Constant(value=None)) - return self.period_aggregate(function_name, column_name, start, mid, alias=alias, params=params) + return self.period_aggregate( + function_name, + column_name, + self.query_compare_to_date_range.date_from_as_hogql(), + self.query_compare_to_date_range.date_to_as_hogql(), + alias=alias, + params=params, + ) if self.query.conversionGoal: select = [ diff --git a/posthog/schema.py b/posthog/schema.py index 11d82df2dcbcd3..1c779ca75aebf4 100644 --- a/posthog/schema.py +++ b/posthog/schema.py @@ -82,23 +82,6 @@ class AssistantBreakdownFilter(BaseModel): breakdown_limit: Optional[int] = Field(default=25, description="How many distinct values to show.") -class AssistantCompareFilter(BaseModel): - model_config = ConfigDict( - extra="forbid", - ) - compare: Optional[bool] = Field( - default=False, description="Whether to compare the current date range to a previous date range." - ) - compare_to: Optional[str] = Field( - default="-7d", - description=( - "The date range to compare to. The value is a relative date. Examples of relative dates are: `-1y` for 1" - " year ago, `-14m` for 14 months ago, `-100w` for 100 weeks ago, `-14d` for 14 days ago, `-30h` for 30" - " hours ago." - ), - ) - - class AssistantDateTimePropertyFilterOperator(StrEnum): IS_DATE_EXACT = "is_date_exact" IS_DATE_BEFORE = "is_date_before" @@ -538,8 +521,17 @@ class CompareFilter(BaseModel): model_config = ConfigDict( extra="forbid", ) - compare: Optional[bool] = None - compare_to: Optional[str] = None + compare: Optional[bool] = Field( + default=False, description="Whether to compare the current date range to a previous date range." + ) + compare_to: Optional[str] = Field( + default="-7d", + description=( + "The date range to compare to. The value is a relative date. Examples of relative dates are: `-1y` for 1" + " year ago, `-14m` for 14 months ago, `-100w` for 100 weeks ago, `-14d` for 14 days ago, `-30h` for 30" + " hours ago." + ), + ) class ColorMode(StrEnum): @@ -5395,6 +5387,7 @@ class WebExternalClicksTableQuery(BaseModel): model_config = ConfigDict( extra="forbid", ) + compareFilter: Optional[CompareFilter] = None conversionGoal: Optional[Union[ActionConversionGoal, CustomEventConversionGoal]] = None dateRange: Optional[DateRange] = None filterTestAccounts: Optional[bool] = None @@ -5414,6 +5407,7 @@ class WebGoalsQuery(BaseModel): model_config = ConfigDict( extra="forbid", ) + compareFilter: Optional[CompareFilter] = None conversionGoal: Optional[Union[ActionConversionGoal, CustomEventConversionGoal]] = None dateRange: Optional[DateRange] = None filterTestAccounts: Optional[bool] = None