diff --git a/frontend/__snapshots__/exporter-exporter--funnel-historical-trends-insight--dark.png b/frontend/__snapshots__/exporter-exporter--funnel-historical-trends-insight--dark.png index a820c399ecbf2..f427261f0c801 100644 Binary files a/frontend/__snapshots__/exporter-exporter--funnel-historical-trends-insight--dark.png and b/frontend/__snapshots__/exporter-exporter--funnel-historical-trends-insight--dark.png differ diff --git a/frontend/__snapshots__/exporter-exporter--funnel-historical-trends-insight--light.png b/frontend/__snapshots__/exporter-exporter--funnel-historical-trends-insight--light.png index 1276a2564f958..fa79b1b313e1d 100644 Binary files a/frontend/__snapshots__/exporter-exporter--funnel-historical-trends-insight--light.png and b/frontend/__snapshots__/exporter-exporter--funnel-historical-trends-insight--light.png differ diff --git a/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx b/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx index 4c97fc48fc3e8..bb66bcbd64f19 100644 --- a/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx +++ b/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx @@ -214,7 +214,7 @@ export const taxonomicFilterLogic = kea([ searchPlaceholder: 'data warehouse table name', type: TaxonomicFilterGroupType.DataWarehouse, logic: dataWarehouseSceneLogic, - value: 'dataWarehouseTables', + value: 'dataWarehouseTablesAndViews', getName: (table: DatabaseSchemaTable) => table.name, getValue: (table: DatabaseSchemaTable) => table.name, getPopoverHeader: () => 'Data Warehouse Table', diff --git a/frontend/src/scenes/data-warehouse/external/dataWarehouseSceneLogic.ts b/frontend/src/scenes/data-warehouse/external/dataWarehouseSceneLogic.ts index 19d12fcf24fce..886927f704110 100644 --- a/frontend/src/scenes/data-warehouse/external/dataWarehouseSceneLogic.ts +++ b/frontend/src/scenes/data-warehouse/external/dataWarehouseSceneLogic.ts @@ -150,6 +150,12 @@ export const dataWarehouseSceneLogic = kea([ }, {}) }, ], + dataWarehouseTablesAndViews: [ + (s) => [s.dataWarehouseTables, s.views], + (dataWarehouseTables, views): DatabaseSchemaTable[] => { + return [...dataWarehouseTables, ...views] + }, + ], }), listeners(({ actions, values }) => ({ deleteDataWarehouseSavedQuery: async (tableId) => { diff --git a/posthog/hogql/database/database.py b/posthog/hogql/database/database.py index 0927e8f7d5ac3..49d1cf5b5eec1 100644 --- a/posthog/hogql/database/database.py +++ b/posthog/hogql/database/database.py @@ -1,5 +1,6 @@ import dataclasses from typing import TYPE_CHECKING, Any, ClassVar, Optional, TypeAlias, cast +from collections.abc import Callable from zoneinfo import ZoneInfo, ZoneInfoNotFoundError from pydantic import ConfigDict, BaseModel from sentry_sdk import capture_exception @@ -247,51 +248,69 @@ def create_hogql_database( for table in DataWarehouseTable.objects.filter(team_id=team.pk).exclude(deleted=True): warehouse_tables[table.name] = table.hogql_definition(modifiers) + for saved_query in DataWarehouseSavedQuery.objects.filter(team_id=team.pk).exclude(deleted=True): + views[saved_query.name] = saved_query.hogql_definition() + + def define_mappings(warehouse: dict[str, Table], get_table: Callable): + if "id" not in warehouse[warehouse_modifier.table_name].fields.keys(): + warehouse[warehouse_modifier.table_name].fields["id"] = ExpressionField( + name="id", + expr=parse_expr(warehouse_modifier.id_field), + ) + + if "timestamp" not in warehouse[warehouse_modifier.table_name].fields.keys(): + table_model = get_table(team=team, warehouse_modifier=warehouse_modifier) + timestamp_field_type = table_model.get_clickhouse_column_type(warehouse_modifier.timestamp_field) + + # If field type is none or datetime, we can use the field directly + if timestamp_field_type is None or timestamp_field_type.startswith("DateTime"): + warehouse[warehouse_modifier.table_name].fields["timestamp"] = ExpressionField( + name="timestamp", + expr=ast.Field(chain=[warehouse_modifier.timestamp_field]), + ) + else: + warehouse[warehouse_modifier.table_name].fields["timestamp"] = ExpressionField( + name="timestamp", + expr=ast.Call(name="toDateTime", args=[ast.Field(chain=[warehouse_modifier.timestamp_field])]), + ) + + # TODO: Need to decide how the distinct_id and person_id fields are going to be handled + if "distinct_id" not in warehouse[warehouse_modifier.table_name].fields.keys(): + warehouse[warehouse_modifier.table_name].fields["distinct_id"] = ExpressionField( + name="distinct_id", + expr=parse_expr(warehouse_modifier.distinct_id_field), + ) + + if "person_id" not in warehouse[warehouse_modifier.table_name].fields.keys(): + warehouse[warehouse_modifier.table_name].fields["person_id"] = ExpressionField( + name="person_id", + expr=parse_expr(warehouse_modifier.distinct_id_field), + ) + + return warehouse + if modifiers.dataWarehouseEventsModifiers: for warehouse_modifier in modifiers.dataWarehouseEventsModifiers: # TODO: add all field mappings - if "id" not in warehouse_tables[warehouse_modifier.table_name].fields.keys(): - warehouse_tables[warehouse_modifier.table_name].fields["id"] = ExpressionField( - name="id", - expr=parse_expr(warehouse_modifier.id_field), - ) - if "timestamp" not in warehouse_tables[warehouse_modifier.table_name].fields.keys(): - table_model = DataWarehouseTable.objects.filter( - team_id=team.pk, name=warehouse_modifier.table_name - ).latest("created_at") - timestamp_field_type = table_model.get_clickhouse_column_type(warehouse_modifier.timestamp_field) - - # If field type is none or datetime, we can use the field directly - if timestamp_field_type is None or timestamp_field_type.startswith("DateTime"): - warehouse_tables[warehouse_modifier.table_name].fields["timestamp"] = ExpressionField( - name="timestamp", - expr=ast.Field(chain=[warehouse_modifier.timestamp_field]), - ) - else: - warehouse_tables[warehouse_modifier.table_name].fields["timestamp"] = ExpressionField( - name="timestamp", - expr=ast.Call(name="toDateTime", args=[ast.Field(chain=[warehouse_modifier.timestamp_field])]), - ) + is_view = warehouse_modifier.table_name in views.keys() - # TODO: Need to decide how the distinct_id and person_id fields are going to be handled - if "distinct_id" not in warehouse_tables[warehouse_modifier.table_name].fields.keys(): - warehouse_tables[warehouse_modifier.table_name].fields["distinct_id"] = ExpressionField( - name="distinct_id", - expr=parse_expr(warehouse_modifier.distinct_id_field), + if is_view: + views = define_mappings( + views, + lambda team, warehouse_modifier: DataWarehouseSavedQuery.objects.filter( + team_id=team.pk, name=warehouse_modifier.table_name + ).latest("created_at"), ) - - if "person_id" not in warehouse_tables[warehouse_modifier.table_name].fields.keys(): - warehouse_tables[warehouse_modifier.table_name].fields["person_id"] = ExpressionField( - name="person_id", - expr=parse_expr(warehouse_modifier.distinct_id_field), + else: + warehouse_tables = define_mappings( + warehouse_tables, + lambda team, warehouse_modifier: DataWarehouseTable.objects.filter( + team_id=team.pk, name=warehouse_modifier.table_name + ).latest("created_at"), ) database.add_warehouse_tables(**warehouse_tables) - - for saved_query in DataWarehouseSavedQuery.objects.filter(team_id=team.pk).exclude(deleted=True): - views[saved_query.name] = saved_query.hogql_definition() - database.add_views(**views) for join in DataWarehouseJoin.objects.filter(team_id=team.pk).exclude(deleted=True):