Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hogql): HogQLQuery node and data table support #14265

Merged
merged 90 commits into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
7e44195
feat(hogql): select statements
mariusandra Feb 8, 2023
a87115f
visitor
mariusandra Feb 8, 2023
c55f170
cleanup
mariusandra Feb 8, 2023
d47bc61
parse limit by
mariusandra Feb 8, 2023
839d505
parse limit by
mariusandra Feb 8, 2023
fef9463
merge limit clauses
mariusandra Feb 8, 2023
9cd96bc
Update snapshots
github-actions[bot] Feb 8, 2023
2d0d1e8
fix placeholders
mariusandra Feb 8, 2023
5064dad
resolve symbols for events table
mariusandra Feb 9, 2023
62dd464
resolve aliases
mariusandra Feb 9, 2023
6c04d20
refactor column and table aliases
mariusandra Feb 9, 2023
c550147
column resolver
mariusandra Feb 9, 2023
2253f41
make sure some things error
mariusandra Feb 9, 2023
a006872
annotate
mariusandra Feb 9, 2023
b7b5521
constants
mariusandra Feb 9, 2023
60376dd
simple sql query
mariusandra Feb 9, 2023
9683e92
Update snapshots
github-actions[bot] Feb 9, 2023
ac3048e
introduce "print name"
mariusandra Feb 10, 2023
86ddae5
visit_unknown
mariusandra Feb 10, 2023
c48024c
basic printer via a visitor
mariusandra Feb 10, 2023
afe485d
completely redo printing
mariusandra Feb 13, 2023
761a398
Merge branch 'hogql-symbol-resolution' of github.com:PostHog/posthog …
mariusandra Feb 13, 2023
fb35bc7
Merge branch 'master' into hogql-further-improvements
mariusandra Feb 13, 2023
8af1c79
Merge branch 'hogql-further-improvements' into hogql-symbol-resolution
mariusandra Feb 13, 2023
076916e
some sample queries
mariusandra Feb 13, 2023
f1cbd94
Merge branch 'master' into hogql-symbol-resolution
mariusandra Feb 13, 2023
fd271dd
query tests
mariusandra Feb 13, 2023
b41055e
test selecting from persons and distinct id table
mariusandra Feb 13, 2023
309a764
document current join ast
mariusandra Feb 13, 2023
592a18c
children, joins and aliases
mariusandra Feb 13, 2023
72da1d9
turn joins around
mariusandra Feb 13, 2023
54e5539
get some actual joins working
mariusandra Feb 13, 2023
95b6a5e
query aliases
mariusandra Feb 13, 2023
da83eec
errors, cleanup, recordings
mariusandra Feb 13, 2023
2e27c1e
resolver tests
mariusandra Feb 14, 2023
78caa57
cleanup
mariusandra Feb 14, 2023
48f684f
more tiny cleanup
mariusandra Feb 14, 2023
5cea7bd
resolver cleanup
mariusandra Feb 14, 2023
9e995fb
bit of printer cleanup
mariusandra Feb 14, 2023
0522eb5
Merge branch 'master' into hogql-symbol-resolution
mariusandra Feb 14, 2023
166e7fe
placeholders in query
mariusandra Feb 14, 2023
2287c07
changes
mariusandra Feb 14, 2023
92983fb
person properties fake table
mariusandra Feb 14, 2023
66b47ab
pass two more tests
mariusandra Feb 14, 2023
fa22ddd
legacy person properties
mariusandra Feb 15, 2023
3004248
Update snapshots
github-actions[bot] Feb 15, 2023
e7efe0a
Update snapshots
github-actions[bot] Feb 15, 2023
b075bc2
simple splash and table property printing
mariusandra Feb 15, 2023
74957e0
Merge remote-tracking branch 'origin/hogql-symbol-resolution' into ho…
mariusandra Feb 15, 2023
e59ef94
Update snapshots
github-actions[bot] Feb 15, 2023
f095fec
fix pp
mariusandra Feb 15, 2023
ff60757
Merge remote-tracking branch 'origin/hogql-symbol-resolution' into ho…
mariusandra Feb 15, 2023
4715e8a
revert what was different about hogql access logs
mariusandra Feb 15, 2023
07204aa
explicit names for person non properties
mariusandra Feb 15, 2023
1002382
consolidate into print_ast
mariusandra Feb 15, 2023
1d681f8
merge symbol printer into printer
mariusandra Feb 15, 2023
b32cb4b
move it up
mariusandra Feb 15, 2023
425a02e
Update snapshots
github-actions[bot] Feb 15, 2023
0d17eb7
Update snapshots
github-actions[bot] Feb 15, 2023
4851b8f
Update snapshots
github-actions[bot] Feb 15, 2023
d17d725
basic hogql node
mariusandra Feb 16, 2023
cc17eff
no need to discard resolved symbols... we use them again and again
mariusandra Feb 16, 2023
f3e7900
actual sql editor
mariusandra Feb 16, 2023
d731524
hide query editor if opening a hogql table
mariusandra Feb 16, 2023
0759e08
Update snapshots
github-actions[bot] Feb 16, 2023
5605a94
Update snapshots
github-actions[bot] Feb 16, 2023
1b02f02
query into snapshot data
mariusandra Feb 16, 2023
5cf6e77
code quality issues
mariusandra Feb 16, 2023
2f7d3df
Merge branch 'master' into hogql-symbol-resolution
mariusandra Feb 20, 2023
947e7bf
asterisk and obelisk
mariusandra Feb 20, 2023
74bf09c
yeet
mariusandra Feb 20, 2023
d8eb091
class is for internal use only
mariusandra Feb 20, 2023
9f1ae92
fix aliases
mariusandra Feb 20, 2023
c767159
not needed
mariusandra Feb 20, 2023
e496907
fix a few fields
mariusandra Feb 20, 2023
b5cd169
Merge branch 'hogql-symbol-resolution' into hogql-node
mariusandra Feb 20, 2023
4e3830c
pretty printing
mariusandra Feb 20, 2023
b25fd20
update sample query
mariusandra Feb 20, 2023
bf29398
Update snapshots
github-actions[bot] Feb 20, 2023
f21c928
update sample query
mariusandra Feb 20, 2023
b35777f
Update snapshots
github-actions[bot] Feb 20, 2023
7271431
fix webpack build for storybook
mariusandra Feb 21, 2023
edcbbb6
Merge remote-tracking branch 'origin/hogql-node' into hogql-node
mariusandra Feb 21, 2023
36c5491
Merge branch 'master' into hogql-node
mariusandra Feb 21, 2023
e844256
revert tiny changes lost in merge
mariusandra Feb 21, 2023
59ac65f
Merge branch 'master' into hogql-node
mariusandra Feb 21, 2023
6a498f5
Merge branch 'master' into hogql-node
mariusandra Feb 22, 2023
3a7dfcc
Update snapshots
github-actions[bot] Feb 22, 2023
5804f88
raise if the "hogql-queries" flag is not enabled
mariusandra Feb 22, 2023
deb8958
Merge branch 'hogql-node' of github.com:PostHog/posthog into hogql-node
mariusandra Feb 22, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/src/lib/lemon-ui/LemonTable/LemonTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export function LemonTable<T extends Record<string, any>>({
)

const columnGroups = (
'children' in rawColumns[0]
rawColumns.length > 0 && 'children' in rawColumns[0]
? rawColumns
: [
{
Expand Down
22 changes: 22 additions & 0 deletions frontend/src/queries/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
EventsNode,
EventsQuery,
FunnelsQuery,
HogQLQuery,
LegacyQuery,
LifecycleQuery,
Node,
Expand Down Expand Up @@ -290,6 +291,25 @@ const TimeToSeeDataWaterfall: TimeToSeeDataWaterfallNode = {
},
}

const HogQL: HogQLQuery = {
kind: NodeKind.HogQLQuery,
query:
' select event,\n' +
' properties.$geoip_country_name as `Country Name`,\n' +
' count() as `Event count`\n' +
' from events\n' +
' group by event,\n' +
' properties.$geoip_country_name\n' +
' order by count() desc\n' +
' limit 100',
}

const HogQLTable: DataTableNode = {
kind: NodeKind.DataTableNode,
full: true,
source: HogQL,
}

export const examples: Record<string, Node> = {
Events,
EventsTable,
Expand All @@ -310,6 +330,8 @@ export const examples: Record<string, Node> = {
TimeToSeeDataSessions,
TimeToSeeDataWaterfall,
TimeToSeeDataJSON,
HogQL,
HogQLTable,
}

export const stringifiedExamples: Record<string, string> = Object.fromEntries(
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/queries/nodes/DataNode/DataNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ let uniqueNode = 0
export function DataNode(props: DataNodeProps): JSX.Element {
const [key] = useState(() => `DataNode.${uniqueNode++}`)
const logic = dataNodeLogic({ ...props, key })
const { response, responseLoading } = useValues(logic)
const { response, responseLoading, responseErrorObject } = useValues(logic)

return (
<div className="relative">
Expand All @@ -36,7 +36,7 @@ export function DataNode(props: DataNodeProps): JSX.Element {
theme="vs-light"
className="border"
language={'json'}
value={JSON.stringify(response, null, 2)}
value={JSON.stringify(response ?? responseErrorObject, null, 2)}
height={Math.max(height, 300)}
/>
)}
Expand Down
15 changes: 14 additions & 1 deletion frontend/src/queries/nodes/DataNode/dataNodeLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,24 @@ export const dataNodeLogic = kea<dataNodeLogicType>([
// Clear the response if a failure to avoid showing inconsistencies in the UI
loadDataFailure: () => null,
},
responseErrorObject: [
null as Record<string, any> | null,
{
loadData: () => null,
loadDataFailure: (_, { errorObject }) => errorObject,
loadDataSuccess: () => null,
},
],
responseError: [
null as string | null,
{
loadData: () => null,
loadDataFailure: () => 'Error loading data',
loadDataFailure: (_, { error, errorObject }) => {
if (errorObject && 'error' in errorObject) {
return errorObject.error
}
return error ?? 'Error loading data'
},
loadDataSuccess: () => null,
},
],
Expand Down
37 changes: 29 additions & 8 deletions frontend/src/queries/nodes/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import './DataTable.scss'
import { DataTableNode, EventsNode, EventsQuery, Node, PersonsNode, QueryContext } from '~/queries/schema'
import { DataTableNode, EventsNode, EventsQuery, HogQLQuery, Node, PersonsNode, QueryContext } from '~/queries/schema'
import { useCallback, useState } from 'react'
import { BindLogic, useValues } from 'kea'
import { dataNodeLogic, DataNodeLogicProps } from '~/queries/nodes/DataNode/dataNodeLogic'
Expand All @@ -21,7 +21,7 @@ import { EventBufferNotice } from 'scenes/events/EventBufferNotice'
import clsx from 'clsx'
import { SessionPlayerModal } from 'scenes/session-recordings/player/modal/SessionPlayerModal'
import { InlineEditorButton } from '~/queries/nodes/Node/InlineEditorButton'
import { isEventsQuery, isHogQlAggregation, isPersonsNode, taxonomicFilterToHogQl } from '~/queries/utils'
import { isEventsQuery, isHogQlAggregation, isHogQLQuery, isPersonsNode, taxonomicFilterToHogQl } from '~/queries/utils'
import { PersonPropertyFilters } from '~/queries/nodes/PersonsNode/PersonPropertyFilters'
import { PersonsSearch } from '~/queries/nodes/PersonsNode/PersonsSearch'
import { PersonDeleteModal } from 'scenes/persons/PersonDeleteModal'
Expand All @@ -34,6 +34,7 @@ import { extractExpressionComment, removeExpressionComment } from '~/queries/nod
import { InsightEmptyState, InsightErrorState } from 'scenes/insights/EmptyStates'
import { EventType } from '~/types'
import { SavedQueries } from '~/queries/nodes/DataTable/SavedQueries'
import { HogQLQueryEditor } from '~/queries/nodes/HogQLQuery/HogQLQueryEditor'

interface DataTableProps {
query: DataTableNode
Expand Down Expand Up @@ -79,6 +80,7 @@ export function DataTable({ query, setQuery, context }: DataTableProps): JSX.Ele
showSearch,
showEventFilter,
showPropertyFilter,
showHogQLEditor,
showReload,
showExport,
showElapsedTime,
Expand All @@ -89,22 +91,23 @@ export function DataTable({ query, setQuery, context }: DataTableProps): JSX.Ele
} = queryWithDefaults

const actionsColumnShown = showActions && isEventsQuery(query.source) && columnsInResponse?.includes('*')
const columnsInLemonTable = isHogQLQuery(query.source) ? columnsInResponse ?? columnsInQuery : columnsInQuery
const lemonColumns: LemonTableColumn<DataTableRow, any>[] = [
...columnsInQuery.map((key, index) => ({
...columnsInLemonTable.map((key, index) => ({
dataIndex: key as any,
...renderColumnMeta(key, query, context),
render: function RenderDataTableColumn(_: any, { result, label }: DataTableRow) {
if (label) {
if (index === (expandable ? 1 : 0)) {
return {
children: label,
props: { colSpan: columnsInQuery.length + (actionsColumnShown ? 1 : 0) },
props: { colSpan: columnsInLemonTable.length + (actionsColumnShown ? 1 : 0) },
}
} else {
return { props: { colSpan: 0 } }
}
} else if (result) {
if (isEventsQuery(query.source)) {
if (isEventsQuery(query.source) || isHogQLQuery(query.source)) {
return renderColumn(key, result[index], result, query, setQuery, context)
}
return renderColumn(key, result[key], result, query, setQuery, context)
Expand Down Expand Up @@ -292,7 +295,7 @@ export function DataTable({ query, setQuery, context }: DataTableProps): JSX.Ele
].filter((column) => !query.hiddenColumns?.includes(column.dataIndex) && column.dataIndex !== '*')

const setQuerySource = useCallback(
(source: EventsNode | EventsQuery | PersonsNode) => setQuery?.({ ...query, source }),
(source: EventsNode | EventsQuery | PersonsNode | HogQLQuery) => setQuery?.({ ...query, source }),
[setQuery]
)

Expand Down Expand Up @@ -337,7 +340,10 @@ export function DataTable({ query, setQuery, context }: DataTableProps): JSX.Ele
return (
<BindLogic logic={dataTableLogic} props={dataTableLogicProps}>
<BindLogic logic={dataNodeLogic} props={dataNodeLogicProps}>
<div className="relative w-full h-full">
<div className="relative w-full h-full space-y-4">
{showHogQLEditor && isHogQLQuery(query.source) ? (
<HogQLQueryEditor query={query.source} setQuery={setQuerySource} />
) : null}
{showFirstRow && (
<div className="flex gap-4 items-center">
{firstRowLeft}
Expand Down Expand Up @@ -398,7 +404,22 @@ export function DataTable({ query, setQuery, context }: DataTableProps): JSX.Ele
}}
sorting={null}
useURLForSorting={false}
emptyState={responseError ? <InsightErrorState /> : <InsightEmptyState />}
emptyState={
responseError ? (
isHogQLQuery(query.source) ? (
<InsightErrorState
excludeDetail
title={
response && 'error' in response ? (response as any).error : responseError
}
/>
) : (
<InsightErrorState />
)
) : (
<InsightEmptyState />
)
}
expandable={
expandable && isEventsQuery(query.source) && columnsInResponse?.includes('*')
? {
Expand Down
12 changes: 5 additions & 7 deletions frontend/src/queries/nodes/DataTable/dataTableLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,7 @@ export const dataTableLogic = kea<dataTableLogicType>([
columnsInResponse: [
(s) => [s.response],
(response: AnyDataNode['response']): string[] | null =>
response &&
'columns' in response &&
Array.isArray(response.columns) &&
!response.columns.find((c) => typeof c !== 'string')
? (response?.columns as string[])
: null,
response && 'columns' in response && Array.isArray(response.columns) ? response?.columns : null,
],
dataTableRows: [
(s) => [s.sourceKind, s.orderBy, s.response, s.columnsInQuery, s.columnsInResponse],
Expand Down Expand Up @@ -143,10 +138,13 @@ export const dataTableLogic = kea<dataTableLogicType>([
showDateRange: query.showDateRange ?? showIfFull,
showExport: query.showExport ?? showIfFull,
showReload: query.showReload ?? showIfFull,
showElapsedTime: query.showElapsedTime ?? (flagQueryRunningTimeEnabled ? showIfFull : false),
showElapsedTime:
query.showElapsedTime ??
(flagQueryRunningTimeEnabled || source.kind === NodeKind.HogQLQuery ? showIfFull : false),
showColumnConfigurator: query.showColumnConfigurator ?? showIfFull,
showSavedQueries: query.showSavedQueries ?? false,
showEventsBufferWarning: query.showEventsBufferWarning ?? showIfFull,
showHogQLEditor: query.showHogQLEditor ?? showIfFull,
allowSorting: query.allowSorting ?? true,
}),
}
Expand Down
55 changes: 55 additions & 0 deletions frontend/src/queries/nodes/HogQLQuery/HogQLQueryEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useActions, useValues } from 'kea'
import { HogQLQuery } from '~/queries/schema'
import { useState } from 'react'
import { hogQLQueryEditorLogic } from './hogQLQueryEditorLogic'
import MonacoEditor from '@monaco-editor/react'
import { AutoSizer } from 'react-virtualized/dist/es/AutoSizer'
import { LemonButton } from 'lib/lemon-ui/LemonButton'

export interface HogQLQueryEditorProps {
query: HogQLQuery
setQuery?: (query: HogQLQuery) => void
}

let uniqueNode = 0
export function HogQLQueryEditor(props: HogQLQueryEditorProps): JSX.Element {
const [key] = useState(() => uniqueNode++)
const hogQLQueryEditorLogicProps = { query: props.query, setQuery: props.setQuery, key }
const { queryInput } = useValues(hogQLQueryEditorLogic(hogQLQueryEditorLogicProps))
const { setQueryInput, saveQuery } = useActions(hogQLQueryEditorLogic(hogQLQueryEditorLogicProps))

return (
<div className={'flex flex-col p-2 bg-border space-y-2 resize-y overflow-auto h-80 w-full'}>
<div className="flex-1">
<AutoSizer disableWidth>
{({ height }) => (
<MonacoEditor
theme="vs-light"
className="border"
language="mysql"
value={queryInput}
onChange={(v) => setQueryInput(v ?? '')}
height={height}
options={{
minimap: {
enabled: false,
},
wordWrap: 'on',
}}
/>
)}
</AutoSizer>
</div>
<LemonButton
onClick={saveQuery}
type="primary"
status={'muted-alt'}
disabledReason={!props.setQuery ? 'No permission to update' : undefined}
fullWidth
center
>
{!props.setQuery ? 'No permission to update' : 'Update'}
</LemonButton>
</div>
)
}
45 changes: 45 additions & 0 deletions frontend/src/queries/nodes/HogQLQuery/hogQLQueryEditorLogic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { actions, kea, key, listeners, path, props, propsChanged, reducers } from 'kea'
import { format } from 'sql-formatter'
import { HogQLQuery } from '~/queries/schema'

import type { hogQLQueryEditorLogicType } from './hogQLQueryEditorLogicType'

function formatSQL(sql: string): string {
return format(sql, {
language: 'mysql',
tabWidth: 2,
keywordCase: 'preserve',
linesBetweenQueries: 2,
indentStyle: 'tabularRight',
})
}
export interface HogQLQueryEditorLogicProps {
key: number
query: HogQLQuery
setQuery?: (query: HogQLQuery) => void
}

export const hogQLQueryEditorLogic = kea<hogQLQueryEditorLogicType>([
path(['queries', 'nodes', 'HogQLQuery', 'hogQLQueryEditorLogic']),
props({} as HogQLQueryEditorLogicProps),
key((props) => props.key),
propsChanged(({ actions, props }, oldProps) => {
if (props.query.query !== oldProps.query.query) {
actions.setQueryInput(formatSQL(props.query.query))
}
}),
actions({
saveQuery: true,
setQueryInput: (queryInput: string) => ({ queryInput }),
}),
reducers(({ props }) => ({
queryInput: [formatSQL(props.query.query), { setQueryInput: (_, { queryInput }) => queryInput }],
})),
listeners(({ actions, props, values }) => ({
saveQuery: () => {
const formattedQuery = formatSQL(values.queryInput)
actions.setQueryInput(formattedQuery)
props.setQuery?.({ ...props.query, query: formattedQuery })
},
})),
])
5 changes: 5 additions & 0 deletions frontend/src/queries/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
isRecentPerformancePageViewNode,
isDataTableNode,
isTimeToSeeDataSessionsNode,
isHogQLQuery,
isInsightVizNode,
} from './utils'
import api, { ApiMethodOptions } from 'lib/api'
Expand Down Expand Up @@ -47,6 +48,8 @@ export function queryExportContext<N extends DataNode = DataNode>(
after: now().subtract(EVENTS_DAYS_FIRST_FETCH, 'day').toISOString(),
},
}
} else if (isHogQLQuery(query)) {
return { path: api.queryURL(), method: 'POST', body: query }
} else if (isPersonsNode(query)) {
return { path: getPersonsEndpoint(query) }
} else if (isInsightQueryNode(query)) {
Expand Down Expand Up @@ -118,6 +121,8 @@ export async function query<N extends DataNode = DataNode>(
}
}
return await api.query({ after: now().subtract(1, 'year').toISOString(), ...query }, methodOptions)
} else if (isHogQLQuery(query)) {
return api.query(query, methodOptions)
} else if (isPersonsNode(query)) {
return await api.get(getPersonsEndpoint(query), methodOptions)
} else if (isInsightQueryNode(query)) {
Expand Down
Loading