diff --git a/frontend/src/lib/components/HogQLEditor/HogQLEditor.tsx b/frontend/src/lib/components/HogQLEditor/HogQLEditor.tsx index df01db9b55538..6b96c7d094b66 100644 --- a/frontend/src/lib/components/HogQLEditor/HogQLEditor.tsx +++ b/frontend/src/lib/components/HogQLEditor/HogQLEditor.tsx @@ -9,6 +9,7 @@ import { hogQLEditorLogic } from './hogQLEditorLogic' export interface HogQLEditorProps { onChange: (value: string) => void value: string | undefined + hogQLTable?: string disablePersonProperties?: boolean disableAutoFocus?: boolean disableCmdEnter?: boolean @@ -20,6 +21,7 @@ let uniqueNode = 0 export function HogQLEditor({ onChange, value, + hogQLTable, disablePersonProperties, disableAutoFocus, disableCmdEnter, @@ -28,7 +30,7 @@ export function HogQLEditor({ }: HogQLEditorProps): JSX.Element { const [key] = useState(() => `HogQLEditor.${uniqueNode++}`) const textareaRef = useRef(null) - const logic = hogQLEditorLogic({ key, value, onChange, textareaRef }) + const logic = hogQLEditorLogic({ key, value, onChange, hogQLTable, textareaRef }) const { localValue, error, responseLoading } = useValues(logic) const { setLocalValue, submit } = useActions(logic) @@ -53,7 +55,9 @@ export function HogQLEditor({ maxRows={6} placeholder={ placeholder ?? - (disablePersonProperties + (hogQLTable === 'persons' + ? "Enter HogQL expression, such as:\n- properties.$geoip_country_name\n- toInt(properties.$browser_version) * 10\n- concat(properties.name, ' <', properties.email, '>')\n- is_identified ? 'user' : 'anon'" + : disablePersonProperties ? "Enter HogQL expression, such as:\n- properties.$current_url\n- toInt(properties.`Long Field Name`) * 10\n- concat(event, ' ', distinct_id)\n- if(1 < 2, 'small', 'large')" : "Enter HogQL Expression, such as:\n- properties.$current_url\n- person.properties.$geoip_country_name\n- toInt(properties.`Long Field Name`) * 10\n- concat(event, ' ', distinct_id)\n- if(1 < 2, 'small', 'large')") } diff --git a/frontend/src/lib/components/HogQLEditor/hogQLEditorLogic.ts b/frontend/src/lib/components/HogQLEditor/hogQLEditorLogic.ts index 98cdeff95c17a..0507e78595775 100644 --- a/frontend/src/lib/components/HogQLEditor/hogQLEditorLogic.ts +++ b/frontend/src/lib/components/HogQLEditor/hogQLEditorLogic.ts @@ -9,6 +9,7 @@ import React from 'react' export interface HogQLEditorLogicProps { key: string value: string | undefined + hogQLTable?: string onChange: (value: string) => void textareaRef?: React.MutableRefObject } @@ -32,6 +33,7 @@ export const hogQLEditorLogic = kea([ const response = await query({ kind: NodeKind.HogQLMetadata, expr: values.localValue, + table: props.hogQLTable || 'events', }) breakpoint() if (response && Array.isArray(response.errors) && response.errors.length > 0) { diff --git a/frontend/src/lib/components/PropertyFilters/PropertyFilters.tsx b/frontend/src/lib/components/PropertyFilters/PropertyFilters.tsx index c9f8dc06f1f28..53cfa3972e9a6 100644 --- a/frontend/src/lib/components/PropertyFilters/PropertyFilters.tsx +++ b/frontend/src/lib/components/PropertyFilters/PropertyFilters.tsx @@ -17,6 +17,7 @@ interface PropertyFiltersProps { disablePopover?: boolean style?: CSSProperties taxonomicGroupTypes?: TaxonomicFilterGroupType[] + hogQLTable?: string showNestedArrow?: boolean eventNames?: string[] logicalRowDivider?: boolean @@ -36,6 +37,7 @@ export function PropertyFilters({ showConditionBadge = false, disablePopover = false, // use bare PropertyFilter without popover taxonomicGroupTypes, + hogQLTable, style = {}, showNestedArrow = false, eventNames = [], @@ -88,6 +90,7 @@ export function PropertyFilters({ onComplete={onComplete} orFiltering={orFiltering} taxonomicGroupTypes={taxonomicGroupTypes} + hogQLTable={hogQLTable} eventNames={eventNames} propertyGroupType={propertyGroupType} disablePopover={disablePopover || orFiltering} diff --git a/frontend/src/lib/components/PropertyFilters/components/TaxonomicPropertyFilter.tsx b/frontend/src/lib/components/PropertyFilters/components/TaxonomicPropertyFilter.tsx index acd36e767d6f8..9a08aa82b702c 100644 --- a/frontend/src/lib/components/PropertyFilters/components/TaxonomicPropertyFilter.tsx +++ b/frontend/src/lib/components/PropertyFilters/components/TaxonomicPropertyFilter.tsx @@ -38,6 +38,7 @@ export function TaxonomicPropertyFilter({ orFiltering, addText = 'Add filter', hasRowOperator, + hogQLTable, }: PropertyFilterInternalProps): JSX.Element { const pageKey = useMemo(() => pageKeyInput || `filter-${uniqueMemoizedIndex++}`, [pageKeyInput]) const groupTypes = taxonomicGroupTypes || [ @@ -98,6 +99,7 @@ export function TaxonomicPropertyFilter({ value={cohortOrOtherValue} onChange={taxonomicOnChange} taxonomicGroupTypes={groupTypes} + hogQLTable={hogQLTable} eventNames={eventNames} /> ) diff --git a/frontend/src/lib/components/PropertyFilters/types.ts b/frontend/src/lib/components/PropertyFilters/types.ts index 824554eaf5708..433fbad88ea55 100644 --- a/frontend/src/lib/components/PropertyFilters/types.ts +++ b/frontend/src/lib/components/PropertyFilters/types.ts @@ -41,4 +41,5 @@ export interface PropertyFilterInternalProps { orFiltering?: boolean addText?: string | null hasRowOperator?: boolean + hogQLTable?: string } diff --git a/frontend/src/lib/components/TaxonomicFilter/InfiniteSelectResults.tsx b/frontend/src/lib/components/TaxonomicFilter/InfiniteSelectResults.tsx index 470aca6ea7e3a..3078f7068fc19 100644 --- a/frontend/src/lib/components/TaxonomicFilter/InfiniteSelectResults.tsx +++ b/frontend/src/lib/components/TaxonomicFilter/InfiniteSelectResults.tsx @@ -60,7 +60,11 @@ export function InfiniteSelectResults({ const RenderComponent = activeTaxonomicGroup?.render const listComponent = RenderComponent ? ( - selectItem(activeTaxonomicGroup, newValue, newValue)} /> + selectItem(activeTaxonomicGroup, newValue, newValue)} + /> ) : ( ) diff --git a/frontend/src/lib/components/TaxonomicFilter/InlineHogQLEditor.tsx b/frontend/src/lib/components/TaxonomicFilter/InlineHogQLEditor.tsx index 320e04d706f03..e7d35c4c18d77 100644 --- a/frontend/src/lib/components/TaxonomicFilter/InlineHogQLEditor.tsx +++ b/frontend/src/lib/components/TaxonomicFilter/InlineHogQLEditor.tsx @@ -4,14 +4,16 @@ import { HogQLEditor } from 'lib/components/HogQLEditor/HogQLEditor' export interface InlineHogQLEditorProps { value?: TaxonomicFilterValue onChange: (value: TaxonomicFilterValue) => void + hogQLTable?: string } -export function InlineHogQLEditor({ value, onChange }: InlineHogQLEditorProps): JSX.Element { +export function InlineHogQLEditor({ value, onChange, hogQLTable }: InlineHogQLEditorProps): JSX.Element { return (
diff --git a/frontend/src/lib/components/TaxonomicFilter/TaxonomicFilter.tsx b/frontend/src/lib/components/TaxonomicFilter/TaxonomicFilter.tsx index 4e48d43e1c6a1..585bbcac0fc50 100644 --- a/frontend/src/lib/components/TaxonomicFilter/TaxonomicFilter.tsx +++ b/frontend/src/lib/components/TaxonomicFilter/TaxonomicFilter.tsx @@ -23,6 +23,7 @@ export function TaxonomicFilter({ onClose, taxonomicGroupTypes, optionsFromProp, + hogQLTable, eventNames, height, width, @@ -50,6 +51,7 @@ export function TaxonomicFilter({ popoverEnabled, selectFirstItem, excludedProperties, + hogQLTable, } const logic = taxonomicFilterLogic(taxonomicFilterLogicProps) diff --git a/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx b/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx index e9014b314ef91..f15d9b8778d3c 100644 --- a/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx +++ b/frontend/src/lib/components/TaxonomicFilter/taxonomicFilterLogic.tsx @@ -136,6 +136,7 @@ export const taxonomicFilterLogic = kea({ (taxonomicFilterLogicKey) => taxonomicFilterLogicKey, ], eventNames: [() => [(_, props) => props.eventNames], (eventNames) => eventNames ?? []], + hogQLTable: [() => [(_, props) => props.hogQLTable], (hogQLTable) => hogQLTable ?? 'events'], excludedProperties: [ () => [(_, props) => props.excludedProperties], (excludedProperties) => excludedProperties ?? {}, @@ -146,6 +147,7 @@ export const taxonomicFilterLogic = kea({ s.groupAnalyticsTaxonomicGroups, s.groupAnalyticsTaxonomicGroupNames, s.eventNames, + s.hogQLTable, s.excludedProperties, ], ( @@ -153,9 +155,10 @@ export const taxonomicFilterLogic = kea({ groupAnalyticsTaxonomicGroups, groupAnalyticsTaxonomicGroupNames, eventNames, + hogQLTable, excludedProperties ): TaxonomicFilterGroup[] => { - const groups = [ + const groups: TaxonomicFilterGroup[] = [ { name: 'Events', searchPlaceholder: 'events', @@ -429,6 +432,7 @@ export const taxonomicFilterLogic = kea({ type: TaxonomicFilterGroupType.HogQLExpression, render: InlineHogQLEditor, getPopoverHeader: () => 'HogQL', + componentProps: { hogQLTable }, }, ...groupAnalyticsTaxonomicGroups, ...groupAnalyticsTaxonomicGroupNames, diff --git a/frontend/src/lib/components/TaxonomicFilter/types.ts b/frontend/src/lib/components/TaxonomicFilter/types.ts index 5dd74ef575aae..6f6351cd9f1b4 100644 --- a/frontend/src/lib/components/TaxonomicFilter/types.ts +++ b/frontend/src/lib/components/TaxonomicFilter/types.ts @@ -21,6 +21,7 @@ export interface TaxonomicFilterProps { selectFirstItem?: boolean /** use to filter results in a group by name, currently only working for EventProperties */ excludedProperties?: { [key in TaxonomicFilterGroupType]?: TaxonomicFilterValue[] } + hogQLTable?: string } export interface TaxonomicFilterLogicProps extends TaxonomicFilterProps { @@ -56,6 +57,8 @@ export interface TaxonomicFilterGroup { groupTypeIndex?: number getFullDetailUrl?: (instance: any) => string excludedProperties?: string[] + /** Passed to the component specified via the `render` key */ + componentProps?: Record } export enum TaxonomicFilterGroupType { diff --git a/frontend/src/lib/components/TaxonomicPopover/TaxonomicPopover.tsx b/frontend/src/lib/components/TaxonomicPopover/TaxonomicPopover.tsx index 1a983d5717c8b..abf4589005691 100644 --- a/frontend/src/lib/components/TaxonomicPopover/TaxonomicPopover.tsx +++ b/frontend/src/lib/components/TaxonomicPopover/TaxonomicPopover.tsx @@ -20,6 +20,7 @@ export interface TaxonomicPopoverProps): JSX.Element { const [localValue, setLocalValue] = useState(value || ('' as ValueType)) @@ -85,6 +87,7 @@ export function TaxonomicPopover } diff --git a/frontend/src/queries/nodes/DataTable/ColumnConfigurator/ColumnConfigurator.tsx b/frontend/src/queries/nodes/DataTable/ColumnConfigurator/ColumnConfigurator.tsx index b831f40740501..bc33d6f298b78 100644 --- a/frontend/src/queries/nodes/DataTable/ColumnConfigurator/ColumnConfigurator.tsx +++ b/frontend/src/queries/nodes/DataTable/ColumnConfigurator/ColumnConfigurator.tsx @@ -11,7 +11,7 @@ import { columnConfiguratorLogic, ColumnConfiguratorLogicProps } from './columnC import { defaultDataTableColumns, extractExpressionComment, removeExpressionComment } from '../utils' import { DataTableNode, NodeKind } from '~/queries/schema' import { LemonModal } from 'lib/lemon-ui/LemonModal' -import { isEventsQuery, taxonomicFilterToHogQl, trimQuotes } from '~/queries/utils' +import { isEventsQuery, taxonomicEventFilterToHogQL, trimQuotes } from '~/queries/utils' import { TaxonomicFilter } from 'lib/components/TaxonomicFilter/TaxonomicFilter' import { PropertyKeyInfo } from 'lib/components/PropertyKeyInfo' import { PropertyFilterIcon } from 'lib/components/PropertyFilters/components/PropertyFilterIcon' @@ -166,7 +166,7 @@ function ColumnConfiguratorModal({ query }: ColumnConfiguratorProps): JSX.Elemen ]} value={undefined} onChange={(group, value) => { - const column = taxonomicFilterToHogQl(group.type, value) + const column = taxonomicEventFilterToHogQL(group.type, value) if (column !== null) { selectColumn(column) } diff --git a/frontend/src/queries/nodes/DataTable/DataTable.tsx b/frontend/src/queries/nodes/DataTable/DataTable.tsx index dcea7a6e2622c..848a00cc47177 100644 --- a/frontend/src/queries/nodes/DataTable/DataTable.tsx +++ b/frontend/src/queries/nodes/DataTable/DataTable.tsx @@ -28,7 +28,7 @@ import { LemonDivider } from 'lib/lemon-ui/LemonDivider' import clsx from 'clsx' import { SessionPlayerModal } from 'scenes/session-recordings/player/modal/SessionPlayerModal' import { OpenEditorButton } from '~/queries/nodes/Node/OpenEditorButton' -import { isEventsQuery, isHogQlAggregation, isHogQLQuery, taxonomicFilterToHogQl } from '~/queries/utils' +import { isEventsQuery, isHogQlAggregation, isHogQLQuery, taxonomicEventFilterToHogQL } from '~/queries/utils' import { PersonPropertyFilters } from '~/queries/nodes/PersonsNode/PersonPropertyFilters' import { PersonsSearch } from '~/queries/nodes/PersonsNode/PersonsSearch' import { PersonDeleteModal } from 'scenes/persons/PersonDeleteModal' @@ -55,7 +55,7 @@ interface DataTableProps { cachedResults?: AnyResponseType } -const groupTypes = [ +const eventGroupTypes = [ TaxonomicFilterGroupType.HogQLExpression, TaxonomicFilterGroupType.EventProperties, TaxonomicFilterGroupType.PersonProperties, @@ -121,6 +121,9 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } ? columnsInResponse ?? columnsInQuery : columnsInQuery + const groupTypes = eventGroupTypes + const hogQLTable = 'events' + const lemonColumns: LemonTableColumn[] = [ ...columnsInLemonTable.map((key, index) => ({ dataIndex: key as any, @@ -144,7 +147,7 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } }, sorter: undefined, // using custom sorting code more: - !isReadOnly && showActions && sourceFeatures.has(QueryFeature.eventActionsColumn) ? ( + !isReadOnly && showActions && sourceFeatures.has(QueryFeature.selectAndOrderByColumns) ? ( <>
{extractExpressionComment(key)}
@@ -156,11 +159,13 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } <>Edit column} type="tertiary" fullWidth onChange={(v, g) => { - const hogQl = taxonomicFilterToHogQl(g, v) + const hogQl = taxonomicEventFilterToHogQL(g, v) if (setQuery && hogQl && sourceFeatures.has(QueryFeature.selectAndOrderByColumns)) { // Typecasting to a query type with select and order_by fields. // The actual query may or may not be an events query. @@ -183,7 +188,6 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } }) } }} - groupTypes={groupTypes} /> {canSort ? ( @@ -230,12 +234,14 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } Add column left} data-attr="datatable-add-column-left" type="tertiary" fullWidth onChange={(v, g) => { - const hogQl = taxonomicFilterToHogQl(g, v) + const hogQl = taxonomicEventFilterToHogQL(g, v) if (setQuery && hogQl && sourceFeatures.has(QueryFeature.selectAndOrderByColumns)) { const isAggregation = isHogQlAggregation(hogQl) const source = query.source as EventsQuery @@ -252,17 +258,18 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } }) } }} - groupTypes={groupTypes} /> Add column right} data-attr="datatable-add-column-right" type="tertiary" fullWidth onChange={(v, g) => { - const hogQl = taxonomicFilterToHogQl(g, v) + const hogQl = taxonomicEventFilterToHogQL(g, v) if (setQuery && hogQl && sourceFeatures.has(QueryFeature.selectAndOrderByColumns)) { const isAggregation = isHogQlAggregation(hogQl) const source = query.source as EventsQuery @@ -279,7 +286,6 @@ export function DataTable({ uniqueKey, query, setQuery, context, cachedResults } }) } }} - groupTypes={groupTypes} /> {columnsInQuery.filter((c) => c !== '*').length > 1 ? ( <> diff --git a/frontend/src/queries/nodes/DataTable/renderColumnMeta.tsx b/frontend/src/queries/nodes/DataTable/renderColumnMeta.tsx index 7f8e546277a7c..fbc905249c3b3 100644 --- a/frontend/src/queries/nodes/DataTable/renderColumnMeta.tsx +++ b/frontend/src/queries/nodes/DataTable/renderColumnMeta.tsx @@ -4,6 +4,7 @@ import { QueryContext, DataTableNode } from '~/queries/schema' import { isEventsQuery, isHogQLQuery, trimQuotes } from '~/queries/utils' import { extractExpressionComment } from '~/queries/nodes/DataTable/utils' import { SortingIndicator } from 'lib/lemon-ui/LemonTable/sorting' +import { getQueryFeatures, QueryFeature } from '~/queries/nodes/DataTable/queryFeatures' export interface ColumnMeta { title?: JSX.Element | string @@ -13,6 +14,7 @@ export interface ColumnMeta { export function renderColumnMeta(key: string, query: DataTableNode, context?: QueryContext): ColumnMeta { let width: number | undefined let title: JSX.Element | string | undefined + const queryFeatures = getQueryFeatures(query.source) if (isHogQLQuery(query.source)) { title = key @@ -27,8 +29,6 @@ export function renderColumnMeta(key: string, query: DataTableNode, context?: Qu title = 'Event' } else if (key === 'person') { title = 'Person' - } else if (key === 'url') { - title = 'URL / Screen' } else if (key.startsWith('properties.')) { title = } else if (key.startsWith('context.columns.')) { @@ -41,7 +41,7 @@ export function renderColumnMeta(key: string, query: DataTableNode, context?: Qu // NOTE: PropertyFilterType.Event is not a mistake. PropertyKeyInfo only knows events vs elements ¯\_(ツ)_/¯ title = } else { - title = isEventsQuery(query.source) ? extractExpressionComment(key) : key + title = queryFeatures.has(QueryFeature.selectAndOrderByColumns) ? extractExpressionComment(key) : key } if (isEventsQuery(query.source) && !query.allowSorting) { diff --git a/frontend/src/queries/nodes/PersonsNode/PersonPropertyFilters.tsx b/frontend/src/queries/nodes/PersonsNode/PersonPropertyFilters.tsx index b27354fe734bc..d0dc26dce6a86 100644 --- a/frontend/src/queries/nodes/PersonsNode/PersonPropertyFilters.tsx +++ b/frontend/src/queries/nodes/PersonsNode/PersonPropertyFilters.tsx @@ -18,6 +18,7 @@ export function PersonPropertyFilters({ query, setQuery }: PersonPropertyFilters onChange={(value: AnyPropertyFilter[]) => setQuery?.({ ...query, properties: value })} pageKey={`PersonPropertyFilters.${id}`} taxonomicGroupTypes={[TaxonomicFilterGroupType.PersonProperties]} + hogQLTable="persons" style={{ marginBottom: 0, marginTop: 0 }} /> ) : ( diff --git a/frontend/src/queries/schema.json b/frontend/src/queries/schema.json index 6517179ed4c38..404038c58b04d 100644 --- a/frontend/src/queries/schema.json +++ b/frontend/src/queries/schema.json @@ -1095,6 +1095,7 @@ "additionalProperties": false, "properties": { "expr": { + "description": "HogQL expression to validate (use `select` or `expr`, but not both)", "type": "string" }, "filters": { @@ -1109,6 +1110,11 @@ "description": "Cached query response" }, "select": { + "description": "Full select query to validate (use `select` or `expr`, but not both)", + "type": "string" + }, + "table": { + "description": "Table to validate the expression against", "type": "string" } }, diff --git a/frontend/src/queries/schema.ts b/frontend/src/queries/schema.ts index 4d3d06b98321c..52373a5e3cdd5 100644 --- a/frontend/src/queries/schema.ts +++ b/frontend/src/queries/schema.ts @@ -170,8 +170,12 @@ export interface HogQLMetadataResponse { export interface HogQLMetadata extends DataNode { kind: NodeKind.HogQLMetadata - expr?: string + /** Full select query to validate (use `select` or `expr`, but not both) */ select?: string + /** HogQL expression to validate (use `select` or `expr`, but not both) */ + expr?: string + /** Table to validate the expression against */ + table?: string filters?: HogQLFilters response?: HogQLMetadataResponse } diff --git a/frontend/src/queries/utils.ts b/frontend/src/queries/utils.ts index 463f0f727dfd4..80148f5d0f08b 100644 --- a/frontend/src/queries/utils.ts +++ b/frontend/src/queries/utils.ts @@ -30,13 +30,22 @@ import { WebTopClicksQuery, WebTopPagesQuery, WebOverviewStatsQuery, + HogQLMetadata, } from '~/queries/schema' import { TaxonomicFilterGroupType, TaxonomicFilterValue } from 'lib/components/TaxonomicFilter/types' import { dayjs } from 'lib/dayjs' import { teamLogic } from 'scenes/teamLogic' export function isDataNode(node?: Node | null): node is EventsQuery | PersonsNode | TimeToSeeDataSessionsQuery { - return isEventsQuery(node) || isPersonsNode(node) || isTimeToSeeDataSessionsQuery(node) || isHogQLQuery(node) + return ( + isEventsNode(node) || + isActionsNode(node) || + isPersonsNode(node) || + isTimeToSeeDataSessionsQuery(node) || + isEventsQuery(node) || + isHogQLQuery(node) || + isHogQLMetadata(node) + ) } function isTimeToSeeDataJSONNode(node?: Node | null): node is TimeToSeeDataJSONNode { @@ -93,9 +102,15 @@ export function isInsightVizNode(node?: Node | null): node is InsightVizNode { export function isHogQLQuery(node?: Node | null): node is HogQLQuery { return node?.kind === NodeKind.HogQLQuery } + +export function isHogQLMetadata(node?: Node | null): node is HogQLMetadata { + return node?.kind === NodeKind.HogQLMetadata +} + export function isWebOverviewStatsQuery(node?: Node | null): node is WebOverviewStatsQuery { return node?.kind === NodeKind.WebOverviewStatsQuery } + export function isWebTopSourcesQuery(node?: Node | null): node is WebTopSourcesQuery { return node?.kind === NodeKind.WebTopSourcesQuery } @@ -260,7 +275,7 @@ export function escapePropertyAsHogQlIdentifier(identifier: string): string { return !identifier.includes('"') ? `"${identifier}"` : `\`${identifier}\`` } -export function taxonomicFilterToHogQl( +export function taxonomicEventFilterToHogQL( groupType: TaxonomicFilterGroupType, value: TaxonomicFilterValue ): string | null { diff --git a/frontend/src/scenes/insights/EditorFilters/PathsHogQL.tsx b/frontend/src/scenes/insights/EditorFilters/PathsHogQL.tsx index d6d8f1a0e650b..a7ee22a7fa426 100644 --- a/frontend/src/scenes/insights/EditorFilters/PathsHogQL.tsx +++ b/frontend/src/scenes/insights/EditorFilters/PathsHogQL.tsx @@ -2,7 +2,7 @@ import { useValues, useActions } from 'kea' import { EditorFilterProps } from '~/types' import { pathsDataLogic } from 'scenes/paths/pathsDataLogic' import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types' -import { taxonomicFilterToHogQl } from '~/queries/utils' +import { taxonomicEventFilterToHogQL } from '~/queries/utils' import { TaxonomicPopover } from 'lib/components/TaxonomicPopover/TaxonomicPopover' export function PathsHogQL({ insightProps }: EditorFilterProps): JSX.Element { @@ -17,7 +17,7 @@ export function PathsHogQL({ insightProps }: EditorFilterProps): JSX.Element { type="secondary" fullWidth onChange={(v, g) => { - const hogQl = taxonomicFilterToHogQl(g, v) + const hogQl = taxonomicEventFilterToHogQL(g, v) if (hogQl) { updateInsightFilter({ paths_hogql_expression: hogQl }) } diff --git a/posthog/hogql/hogql.py b/posthog/hogql/hogql.py index d3f9bf5eb5c3b..87a2e0ee8f47e 100644 --- a/posthog/hogql/hogql.py +++ b/posthog/hogql/hogql.py @@ -14,6 +14,7 @@ def translate_hogql( query: str, context: HogQLContext, dialect: Literal["hogql", "clickhouse"] = "clickhouse", + table: str = "events", *, events_table_alias: Optional[str] = None, placeholders: Optional[Dict[str, ast.Expr]] = None, @@ -29,7 +30,7 @@ def translate_hogql( raise ValueError("Cannot translate HogQL for a filter with no team specified") context.database = create_hogql_database(context.team_id) node = parse_expr(query, placeholders=placeholders) - select_query = ast.SelectQuery(select=[node], select_from=ast.JoinExpr(table=ast.Field(chain=["events"]))) + select_query = ast.SelectQuery(select=[node], select_from=ast.JoinExpr(table=ast.Field(chain=[table]))) if events_table_alias is not None: select_query.select_from.alias = events_table_alias prepared_select_query: ast.SelectQuery = cast( diff --git a/posthog/hogql/metadata.py b/posthog/hogql/metadata.py index e1fb117c9d8c7..1e35bc30978e1 100644 --- a/posthog/hogql/metadata.py +++ b/posthog/hogql/metadata.py @@ -26,7 +26,7 @@ def get_hogql_metadata( try: if isinstance(query.expr, str): context = HogQLContext(team_id=team.pk) - translate_hogql(query.expr, context=context) + translate_hogql(query.expr, context=context, table=query.table or "events") elif isinstance(query.select, str): context = HogQLContext(team_id=team.pk, enable_select_queries=True) diff --git a/posthog/hogql/test/test_metadata.py b/posthog/hogql/test/test_metadata.py index bdd5cbac97772..f833cc39ef290 100644 --- a/posthog/hogql/test/test_metadata.py +++ b/posthog/hogql/test/test_metadata.py @@ -7,8 +7,10 @@ class TestMetadata(ClickhouseTestMixin, APIBaseTest): maxDiff = None - def _expr(self, query: str) -> HogQLMetadataResponse: - return get_hogql_metadata(query=HogQLMetadata(kind="HogQLMetadata", expr=query, response=None), team=self.team) + def _expr(self, query: str, table: str = "events") -> HogQLMetadataResponse: + return get_hogql_metadata( + query=HogQLMetadata(kind="HogQLMetadata", expr=query, table=table, response=None), team=self.team + ) def _select(self, query: str) -> HogQLMetadataResponse: return get_hogql_metadata( @@ -111,6 +113,19 @@ def test_metadata_expr_resolve_error(self): }, ) + def test_metadata_table(self): + metadata = self._expr("timestamp", "events") + self.assertEqual(metadata.isValid, True) + + metadata = self._expr("timestamp", "persons") + self.assertEqual(metadata.isValid, False) + + metadata = self._expr("is_identified", "events") + self.assertEqual(metadata.isValid, False) + + metadata = self._expr("is_identified", "persons") + self.assertEqual(metadata.isValid, True) + def test_metadata_in_cohort(self): cohort = Cohort.objects.create(team=self.team, name="cohort_name") query = ( diff --git a/posthog/schema.py b/posthog/schema.py index 0bde69bffeceb..8a2da426b1786 100644 --- a/posthog/schema.py +++ b/posthog/schema.py @@ -890,11 +890,16 @@ class HogQLMetadata(BaseModel): model_config = ConfigDict( extra="forbid", ) - expr: Optional[str] = None + expr: Optional[str] = Field( + default=None, description="HogQL expression to validate (use `select` or `expr`, but not both)" + ) filters: Optional[HogQLFilters] = None kind: Literal["HogQLMetadata"] = "HogQLMetadata" response: Optional[HogQLMetadataResponse] = Field(default=None, description="Cached query response") - select: Optional[str] = None + select: Optional[str] = Field( + default=None, description="Full select query to validate (use `select` or `expr`, but not both)" + ) + table: Optional[str] = Field(default=None, description="Table to validate the expression against") class HogQLQuery(BaseModel):