PostHog / posthog

🦔 PostHog provides open-source product analytics, session recording, feature flagging and A/B testing that you can self-host.
https://posthog.com
Other
19.45k stars 1.14k forks source link

Bug Report: Funnel query fails when time_to_convert is used #23489

Closed skoob13 closed 3 days ago

skoob13 commented 3 days ago

Bug Description

Bug description

If funnelVizType is set to time_to_convert and there is a breakdown filter, a funnels query fails:

{"task_id": "a2359333-163e-4e2a-9f38-a2a21af377ce", "request_id": "f7705379-2396-490f-9035-9b732d6c2bde", "ip": "188.26.221.133", "event": "Error processing query for team 19261 query 68b379e3-d736-4f56-b9a4-05a5729ff57c", "task_name": "posthog.tasks.tasks.process_query_task", "timestamp": "2024-07-05T10:02:33.684409Z", "logger": "posthog.clickhouse.client.execute_async", "level": "error", "pid": 274, "tid": 281473447689536, "exception": "Traceback (most recent call last):\n  File \"/code/posthog/clickhouse/client/execute_async.py\", line 168, in execute_process_query\n    results = process_query_dict(\n              ^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/api/services/query.py\", line 46, in process_query_dict\n    return process_query_model(\n           ^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/api/services/query.py\", line 114, in process_query_model\n    result = query_runner.run(execution_mode=execution_mode, user=user, query_id=query_id)\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql_queries/query_runner.py\", line 490, in run\n    **self.calculate().model_dump(),\n      ^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql_queries/insights/funnels/funnels_query_runner.py\", line 86, in calculate\n    response = execute_hogql_query(\n               ^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/query.py\", line 157, in execute_hogql_query\n    clickhouse_sql = print_ast(\n                     ^^^^^^^^^^\n  File \"/code/posthog/hogql/printer.py\", line 80, in print_ast\n    prepared_ast = prepare_ast_for_printing(node=node, context=context, dialect=dialect, stack=stack, settings=settings)\n                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/printer.py\", line 109, in prepare_ast_for_printing\n    node = resolve_types(node, context, dialect=dialect, scopes=[node.type for node in stack] if stack else None)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 84, in resolve_types\n    return Resolver(scopes=scopes, context=context, dialect=dialect).visit(node)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 122, in visit\n    return super().visit(node)\n           ^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 27, in visit\n    return node.accept(self)\n           ^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/base.py\", line 32, in accept\n    return visit(self)\n           ^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 165, in visit_select_query\n    new_node.select_from = self.visit(node.select_from)\n                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 122, in visit\n    return super().visit(node)\n           ^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 27, in visit\n    return node.accept(self)\n           ^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/base.py\", line 32, in accept\n    return visit(self)\n           ^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 379, in visit_join_expr\n    node.table = super().visit(node.table)\n                 ^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 27, in visit\n    return node.accept(self)\n           ^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/base.py\", line 32, in accept\n    return visit(self)\n           ^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 183, in visit_select_query\n    new_expr = self.visit(expr)\n               ^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 122, in visit\n    return super().visit(node)\n           ^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 27, in visit\n    return node.accept(self)\n           ^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/base.py\", line 32, in accept\n    return visit(self)\n           ^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 426, in visit_alias\n    node = super().visit_alias(node)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 352, in visit_alias\n    expr=self.visit(node.expr),\n         ^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 122, in visit\n    return super().visit(node)\n           ^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 27, in visit\n    return node.accept(self)\n           ^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/base.py\", line 32, in accept\n    return visit(self)\n           ^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 433, in visit_arithmetic_operation\n    node = super().visit_arithmetic_operation(node)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 360, in visit_arithmetic_operation\n    left=self.visit(node.left),\n         ^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 122, in visit\n    return super().visit(node)\n           ^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 27, in visit\n    return node.accept(self)\n           ^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/base.py\", line 32, in accept\n    return visit(self)\n           ^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 588, in visit_field\n    response = self.visit(clone_expr(cte.expr))\n               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 122, in visit\n    return super().visit(node)\n           ^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 27, in visit\n    return node.accept(self)\n           ^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/base.py\", line 32, in accept\n    return visit(self)\n           ^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 165, in visit_select_query\n    new_node.select_from = self.visit(node.select_from)\n                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 122, in visit\n    return super().visit(node)\n           ^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 27, in visit\n    return node.accept(self)\n           ^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/base.py\", line 32, in accept\n    return visit(self)\n           ^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 294, in visit_join_expr\n    response = self.visit(node)\n               ^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 122, in visit\n    return super().visit(node)\n           ^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 27, in visit\n    return node.accept(self)\n           ^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/base.py\", line 32, in accept\n    return visit(self)\n           ^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 379, in visit_join_expr\n    node.table = super().visit(node.table)\n                 ^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 27, in visit\n    return node.accept(self)\n           ^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/base.py\", line 32, in accept\n    return visit(self)\n           ^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 183, in visit_select_query\n    new_expr = self.visit(expr)\n               ^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 122, in visit\n    return super().visit(node)\n           ^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 27, in visit\n    return node.accept(self)\n           ^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/base.py\", line 32, in accept\n    return visit(self)\n           ^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 426, in visit_alias\n    node = super().visit_alias(node)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 352, in visit_alias\n    expr=self.visit(node.expr),\n         ^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 122, in visit\n    return super().visit(node)\n           ^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 27, in visit\n    return node.accept(self)\n           ^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/base.py\", line 32, in accept\n    return visit(self)\n           ^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 469, in visit_call\n    node = super().visit_call(node)\n           ^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 489, in visit_call\n    args=[self.visit(arg) for arg in node.args],\n         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 489, in <listcomp>\n    args=[self.visit(arg) for arg in node.args],\n          ^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 122, in visit\n    return super().visit(node)\n           ^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 27, in visit\n    return node.accept(self)\n           ^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/base.py\", line 32, in accept\n    return visit(self)\n           ^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 469, in visit_call\n    node = super().visit_call(node)\n           ^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 489, in visit_call\n    args=[self.visit(arg) for arg in node.args],\n         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 489, in <listcomp>\n    args=[self.visit(arg) for arg in node.args],\n          ^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 122, in visit\n    return super().visit(node)\n           ^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 27, in visit\n    return node.accept(self)\n           ^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/base.py\", line 32, in accept\n    return visit(self)\n           ^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 469, in visit_call\n    node = super().visit_call(node)\n           ^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 489, in visit_call\n    args=[self.visit(arg) for arg in node.args],\n         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 489, in <listcomp>\n    args=[self.visit(arg) for arg in node.args],\n          ^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 122, in visit\n    return super().visit(node)\n           ^^^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/visitor.py\", line 27, in visit\n    return node.accept(self)\n           ^^^^^^^^^^^^^^^^^\n  File \"/code/posthog/hogql/base.py\", line 32, in accept\n    return visit(self)\n           ^^^^^^^^^^^\n  File \"/code/posthog/hogql/resolver.py\", line 617, in visit_field\n    raise QueryError(f\"Unable to resolve field: {name}\")\nposthog.hogql.errors.QueryError: Unable to resolve field: step_1_average_conversion_time_inner"}

How to reproduce

Funnel query schema:

query = FunnelsQuery(
    series=[
        EventsNode(
            event="$pageview",
            properties=[
                EventPropertyFilter(key="$pathname", value=["/app/"], operator=PropertyOperator.EXACT)
            ],
        ),
        EventsNode(event="$pageview", name="Pageview"),
    ],
    interval=IntervalType.MONTH,
    dateRange=InsightDateRange(
        date_from=None,
        date_to="all",
    ),
    breakdownFilter=BreakdownFilter(breakdown="userRoles", breakdown_type=BreakdownType.PERSON),
    filterTestAccounts=True,
    funnelsFilter=FunnelsFilter(
        funnelVizType=FunnelVizType.TIME_TO_CONVERT,
        funnelWindowInterval=10,
        funnelWindowIntervalUnit=FunnelConversionWindowTimeUnit.MINUTE,
        funnelAggregateByHogQL="properties.$session_id",
    ),
)

Query:

{
    "kind": "FunnelsQuery",
    "properties": {
      "type": "AND",
      "values": [{ "type": "AND", "values": [] }]
    },
    "filterTestAccounts": true,
    "dateRange": { "date_to": null, "date_from": "all" },
    "series": [
      {
        "kind": "EventsNode",
        "event": "$pageview",
        "name": "$pageview",
        "properties": [
          {
            "key": "$pathname",
            "type": "event",
            "value": ["/app/"],
            "operator": "exact"
          }
        ]
      },
      { "kind": "EventsNode", "event": "$pageview", "name": "Pageview" }
    ],
    "interval": "month",
    "breakdownFilter": { "breakdown_type": "person", "breakdown": "userRoles" },
    "funnelsFilter": {
      "funnelVizType": "time_to_convert",
      "funnelWindowIntervalUnit": "minute",
      "funnelWindowInterval": 10,
      "funnelAggregateByHogQL": "properties.$session_id"
    }
  }

Additional context

Support Ticket

Debug info

No response