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): sequential steps funnel #19951

Merged
merged 90 commits into from
Feb 7, 2024
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
54c172d
feat(hogql): add funnel event query
thmsobrmlr Jan 22, 2024
234f767
fix hogql aggregation
thmsobrmlr Jan 23, 2024
46f3950
handle entities
thmsobrmlr Jan 23, 2024
1a66d0e
add properties helper
thmsobrmlr Jan 23, 2024
cb9d26a
cleanup
thmsobrmlr Jan 23, 2024
7a0e5e6
composition approach for query context
thmsobrmlr Jan 23, 2024
d79e0ab
refactor date range
thmsobrmlr Jan 23, 2024
89ee57a
more context
thmsobrmlr Jan 24, 2024
714ab8f
fixes
thmsobrmlr Jan 24, 2024
2f93043
add entities utils and type aliases
thmsobrmlr Jan 24, 2024
1a58358
move existing insights utils
thmsobrmlr Jan 24, 2024
612b234
wip
thmsobrmlr Jan 24, 2024
ccd7935
wip
thmsobrmlr Jan 24, 2024
b6ddf16
wip
thmsobrmlr Jan 24, 2024
50f76ab
wip
thmsobrmlr Jan 24, 2024
8b3d462
cleanup
thmsobrmlr Jan 25, 2024
3d6fa74
tmp fix
thmsobrmlr Jan 25, 2024
eb6cbce
build ast manually
thmsobrmlr Jan 25, 2024
6af213d
scaffold inner events query
thmsobrmlr Jan 25, 2024
c8991b2
add tests
thmsobrmlr Jan 25, 2024
0004056
fix steps
thmsobrmlr Jan 25, 2024
22f05e3
fixes
thmsobrmlr Jan 25, 2024
88e51b7
wip
thmsobrmlr Jan 29, 2024
41add4b
fixes
thmsobrmlr Jan 29, 2024
8ac3c11
more fixes
thmsobrmlr Jan 29, 2024
800befb
tests
thmsobrmlr Jan 29, 2024
87daf01
start formatting results
thmsobrmlr Jan 29, 2024
f3e8310
undo accidental changes
thmsobrmlr Jan 29, 2024
f2d666a
more formatting
thmsobrmlr Jan 29, 2024
0ff074b
timings
thmsobrmlr Jan 30, 2024
49e88fe
tests
thmsobrmlr Jan 30, 2024
7e3472d
debug timings
thmsobrmlr Jan 30, 2024
31619b4
tmp fix for exponential explosion in traverse tree
thmsobrmlr Jan 30, 2024
1da57fe
fix action name
thmsobrmlr Jan 30, 2024
e59fa7c
fix test results access
thmsobrmlr Jan 30, 2024
a8e8db0
Revert "debug timings"
thmsobrmlr Jan 31, 2024
1db0922
Revert "timings"
thmsobrmlr Jan 31, 2024
a394b94
response handling
thmsobrmlr Jan 31, 2024
7dde4eb
merge tests
thmsobrmlr Jan 31, 2024
376da1b
fix base query event conditions
thmsobrmlr Jan 31, 2024
e967995
add poev2 test
thmsobrmlr Jan 31, 2024
772cf95
update reference to results in tests
thmsobrmlr Jan 31, 2024
35e69b0
add funnel with messed up order test
thmsobrmlr Jan 31, 2024
948df04
add any events test
thmsobrmlr Jan 31, 2024
e614668
add funnel prop filter test
thmsobrmlr Jan 31, 2024
ceae694
person prop filter
thmsobrmlr Jan 31, 2024
f95d5f8
more tests
thmsobrmlr Jan 31, 2024
d29aa84
more tests
thmsobrmlr Jan 31, 2024
2bcc33b
more tests
thmsobrmlr Jan 31, 2024
b7afe53
Update query snapshots
github-actions[bot] Jan 31, 2024
a4aa289
tests and types
thmsobrmlr Jan 31, 2024
f874415
more tests
thmsobrmlr Jan 31, 2024
0b880fe
more tests
thmsobrmlr Jan 31, 2024
109c6d3
fix aggregate by hogql
thmsobrmlr Jan 31, 2024
08a04ad
fix hogql property test
thmsobrmlr Jan 31, 2024
a89c800
comment out cohort test
thmsobrmlr Jan 31, 2024
1e3849f
add another hogql aggregation test
thmsobrmlr Jan 31, 2024
53d07a3
funnel exclusion condition
thmsobrmlr Jan 31, 2024
73787bc
fixes
thmsobrmlr Jan 31, 2024
d541bb5
Merge branch 'master' into funnel-ordered-steps
thmsobrmlr Feb 5, 2024
bc34bf0
type fixes
thmsobrmlr Feb 5, 2024
ea68ad4
more types
thmsobrmlr Feb 5, 2024
0dbeac8
fix(funnels): fix adding exclusion
thmsobrmlr Feb 5, 2024
689e0a1
types
thmsobrmlr Feb 5, 2024
2d507d0
fix exclusions
thmsobrmlr Feb 5, 2024
95d703c
snapshot updates
thmsobrmlr Feb 5, 2024
295638e
add test_advanced_funnel_exclusions_between_steps
thmsobrmlr Feb 5, 2024
ffd911f
add test_advanced_funnel_multiple_exclusions_between_steps
thmsobrmlr Feb 5, 2024
ba7fe00
add test_funnel_exclusions_with_actions
thmsobrmlr Feb 5, 2024
7ee6a7e
Update query snapshots
github-actions[bot] Feb 5, 2024
bc388e5
Update query snapshots
github-actions[bot] Feb 5, 2024
6f1600c
add test_funnel_exclusion_no_end_event
thmsobrmlr Feb 5, 2024
1ecbeda
add conversion time cases
thmsobrmlr Feb 5, 2024
8857568
adapt response schema for breakdowns
thmsobrmlr Feb 5, 2024
071068b
Merge branch 'master' into funnel-ordered-steps
thmsobrmlr Feb 5, 2024
eb8a313
comment out unreachable code
thmsobrmlr Feb 5, 2024
8bd69cb
update mypy baseline
thmsobrmlr Feb 5, 2024
980b7cf
remove redundant default
thmsobrmlr Feb 5, 2024
532e1c3
correct result for sampling
thmsobrmlr Feb 5, 2024
346236b
add test_funnel_aggregation_with_groups_with_cohort_filtering
thmsobrmlr Feb 5, 2024
7dcfd80
uncomment lines relating to exponential explosion
thmsobrmlr Feb 5, 2024
e3b878a
cleanup
thmsobrmlr Feb 5, 2024
94af315
move util
thmsobrmlr Feb 5, 2024
caebf68
funnel base import
thmsobrmlr Feb 6, 2024
b8f2772
update mypy baseline
thmsobrmlr Feb 6, 2024
4047c81
remove funnel single step tests
thmsobrmlr Feb 6, 2024
aed6841
more single step funnels
thmsobrmlr Feb 6, 2024
5d0eb53
fix aggregating by group
thmsobrmlr Feb 6, 2024
02c3925
getClampedExclusionStepRange
thmsobrmlr Feb 6, 2024
daff0b0
Update query snapshots
github-actions[bot] Feb 6, 2024
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
35 changes: 25 additions & 10 deletions frontend/src/queries/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1412,7 +1412,7 @@
"type": "object"
}
},
"required": ["id", "kind"],
"required": ["funnelFromStep", "funnelToStep", "id", "kind"],
"type": "object"
},
"FunnelExclusionEventsNode": {
Expand Down Expand Up @@ -1509,7 +1509,7 @@
"type": "object"
}
},
"required": ["kind"],
"required": ["funnelFromStep", "funnelToStep", "kind"],
"type": "object"
},
"FunnelExclusionLegacy": {
Expand Down Expand Up @@ -1540,6 +1540,7 @@
"$ref": "#/definitions/EntityType"
}
},
"required": ["funnel_from_step", "funnel_to_step"],
"type": "object"
},
"FunnelExclusionSteps": {
Expand All @@ -1552,6 +1553,7 @@
"type": "integer"
}
},
"required": ["funnelFromStep", "funnelToStep"],
"type": "object"
},
"FunnelLayout": {
Expand Down Expand Up @@ -1580,7 +1582,7 @@
"$ref": "#/definitions/BreakdownAttributionType"
},
"breakdownAttributionValue": {
"type": "number"
"type": "integer"
},
"exclusions": {
"items": {
Expand All @@ -1592,7 +1594,7 @@
"type": "string"
},
"funnelFromStep": {
"type": "number"
"type": "integer"
},
"funnelOrderType": {
"$ref": "#/definitions/StepOrderValue"
Expand All @@ -1601,13 +1603,13 @@
"$ref": "#/definitions/FunnelStepReference"
},
"funnelToStep": {
"type": "number"
"type": "integer"
},
"funnelVizType": {
"$ref": "#/definitions/FunnelVizType"
},
"funnelWindowInterval": {
"type": "number"
"type": "integer"
},
"funnelWindowIntervalUnit": {
"$ref": "#/definitions/FunnelConversionWindowTimeUnit"
Expand Down Expand Up @@ -1755,10 +1757,23 @@
"type": "string"
},
"results": {
"items": {
"type": "object"
},
"type": "array"
"anyOf": [
{
"items": {
"type": "object"
},
"type": "array"
},
{
"items": {
"items": {
"type": "object"
},
"type": "array"
},
"type": "array"
}
]
},
"timings": {
"items": {
Expand Down
10 changes: 7 additions & 3 deletions frontend/src/queries/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,9 +574,9 @@ export type FunnelsFilterLegacy = Omit<

export interface FunnelExclusionSteps {
/** @asType integer */
funnelFromStep?: number
funnelFromStep: number
/** @asType integer */
funnelToStep?: number
funnelToStep: number
}
export interface FunnelExclusionEventsNode extends EventsNode, FunnelExclusionSteps {}
export interface FunnelExclusionActionsNode extends ActionsNode, FunnelExclusionSteps {}
Expand All @@ -587,12 +587,16 @@ export type FunnelsFilter = {
layout?: FunnelsFilterLegacy['layout']
binCount?: FunnelsFilterLegacy['bin_count']
breakdownAttributionType?: FunnelsFilterLegacy['breakdown_attribution_type']
/** @asType integer */
breakdownAttributionValue?: FunnelsFilterLegacy['breakdown_attribution_value']
funnelAggregateByHogQL?: FunnelsFilterLegacy['funnel_aggregate_by_hogql']
/** @asType integer */
funnelToStep?: FunnelsFilterLegacy['funnel_to_step']
/** @asType integer */
funnelFromStep?: FunnelsFilterLegacy['funnel_from_step']
funnelOrderType?: FunnelsFilterLegacy['funnel_order_type']
funnelVizType?: FunnelsFilterLegacy['funnel_viz_type']
/** @asType integer */
funnelWindowInterval?: FunnelsFilterLegacy['funnel_window_interval']
funnelWindowIntervalUnit?: FunnelsFilterLegacy['funnel_window_interval_unit']
hidden_legend_breakdowns?: FunnelsFilterLegacy['hidden_legend_breakdowns']
Expand All @@ -612,7 +616,7 @@ export interface FunnelsQuery extends InsightsQueryBase {
}

export interface FunnelsQueryResponse extends QueryResponse {
results: Record<string, any>[]
results: Record<string, any>[] | Record<string, any>[][]
}

/** `RetentionFilterType` minus everything inherited from `FilterType` */
Expand Down
31 changes: 5 additions & 26 deletions frontend/src/scenes/funnels/funnelUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import {
EMPTY_BREAKDOWN_VALUES,
getBreakdownStepValues,
getClampedStepRange,
getClampedExclusionStepRange,
getIncompleteConversionWindowStartDate,
getMeanAndStandardDeviation,
getVisibilityKey,
Expand Down Expand Up @@ -171,7 +171,7 @@ describe('getIncompleteConversionWindowStartDate()', () => {
})
})

describe('getClampedStepRange', () => {
describe('getClampedExclusionStepRange', () => {
it('prefers step range to existing filters', () => {
const stepRange: FunnelExclusionSteps = {
funnelFromStep: 0,
Expand All @@ -185,7 +185,7 @@ describe('getClampedStepRange', () => {
},
series: [{}, {}] as EventsNode[],
}
const clampedStepRange = getClampedStepRange({
const clampedStepRange = getClampedExclusionStepRange({
stepRange,
query,
})
Expand All @@ -196,17 +196,16 @@ describe('getClampedStepRange', () => {
})

it('ensures step range is clamped to step range', () => {
const stepRange: FunnelExclusionSteps = {}
const stepRange = {} as FunnelExclusionSteps
const query: FunnelsQuery = {
kind: NodeKind.FunnelsQuery,
funnelsFilter: {
funnelFromStep: -1,

funnelToStep: 12,
},
series: [{}, {}, {}] as EventsNode[],
}
const clampedStepRange = getClampedStepRange({
const clampedStepRange = getClampedExclusionStepRange({
stepRange,
query,
})
Expand All @@ -215,26 +214,6 @@ describe('getClampedStepRange', () => {
funnelToStep: 2,
})
})

it('returns undefined if the incoming filters are undefined', () => {
const stepRange: FunnelExclusionSteps = {}
const query: FunnelsQuery = {
kind: NodeKind.FunnelsQuery,
funnelsFilter: {
funnelFromStep: undefined,
funnelToStep: undefined,
},
series: [{}, {}] as EventsNode[],
}
const clampedStepRange = getClampedStepRange({
stepRange,
query,
})
expect(clampedStepRange).toEqual({
funnelFromStep: undefined,
funnelToStep: undefined,
})
})
})

describe('parseEventAndProperty', () => {
Expand Down
19 changes: 5 additions & 14 deletions frontend/src/scenes/funnels/funnelUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,28 +218,19 @@ export const getBreakdownStepValues = (
return EMPTY_BREAKDOWN_VALUES
}

const findFirstNumber = (candidates: (number | undefined)[]): number | undefined =>
candidates.find((s) => typeof s === 'number')

export const getClampedStepRange = ({
export const getClampedExclusionStepRange = ({
stepRange,
query,
}: {
stepRange?: FunnelExclusionSteps
stepRange: FunnelExclusionSteps
query: FunnelsQuery
}): FunnelExclusionSteps => {
const maxStepIndex = Math.max((query.series.length || 0) - 1, 1)

let funnelFromStep = findFirstNumber([stepRange?.funnelFromStep, query.funnelsFilter?.funnelFromStep])
let funnelToStep = findFirstNumber([stepRange?.funnelToStep, query.funnelsFilter?.funnelToStep])

const funnelFromStepIsSet = typeof funnelFromStep === 'number'
const funnelToStepIsSet = typeof funnelToStep === 'number'
let { funnelFromStep, funnelToStep } = stepRange

if (funnelFromStepIsSet && funnelToStepIsSet) {
funnelFromStep = clamp(funnelFromStep ?? 0, 0, maxStepIndex)
funnelToStep = clamp(funnelToStep ?? maxStepIndex, funnelFromStep + 1, maxStepIndex)
}
funnelFromStep = clamp(funnelFromStep ?? 0, 0, maxStepIndex)
funnelToStep = clamp(funnelToStep ?? maxStepIndex, funnelFromStep + 1, maxStepIndex)

return {
...(stepRange || {}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { LemonButton, LemonSelect } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useActions, useValues } from 'kea'
import { IconDelete } from 'lib/lemon-ui/icons'
import { getClampedStepRange } from 'scenes/funnels/funnelUtils'
import { getClampedExclusionStepRange } from 'scenes/funnels/funnelUtils'
import { insightLogic } from 'scenes/insights/insightLogic'
import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic'

Expand Down Expand Up @@ -33,11 +33,8 @@ export function ExclusionRowSuffix({
funnelToStep: exclusions?.[index]?.funnelToStep ?? exclusionDefaultStepRange.funnelToStep,
}

const onChange = (
funnelFromStep: number | undefined = stepRange.funnelFromStep,
funnelToStep: number | undefined = stepRange.funnelToStep
): void => {
const newStepRange = getClampedStepRange({
const onChange = (funnelFromStep = stepRange.funnelFromStep, funnelToStep = stepRange.funnelToStep): void => {
const newStepRange = getClampedExclusionStepRange({
stepRange: { funnelFromStep, funnelToStep },
query: querySource as FunnelsQuery,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { insightVizDataLogic } from 'scenes/insights/insightVizDataLogic'
import { keyForInsightLogicProps } from 'scenes/insights/sharedUtils'

import { legacyEntityToNode } from '~/queries/nodes/InsightQuery/utils/filtersToQueryNode'
import { ActionFilter as ActionFilterType, EntityTypes, FilterType, FunnelExclusionLegacy } from '~/types'
import { ActionFilter as ActionFilterType, EntityTypes, FilterType } from '~/types'

import { ExclusionRow } from './ExclusionRow'
import { ExclusionRowSuffix } from './ExclusionRowSuffix'
Expand All @@ -26,7 +26,7 @@ export function FunnelExclusionsFilter(): JSX.Element {
const isVerticalLayout = !!width && width < 450 // If filter container shrinks below 500px, initiate verticality

const setFilters = (filters: Partial<FilterType>): void => {
const exclusions = filters.events?.map((entity: FunnelExclusionLegacy) => {
const exclusions = filters.events?.map((entity) => {
const baseEntity = legacyEntityToNode(entity as ActionFilterType, false, MathAvailability.None)
return { ...baseEntity, funnelFromStep: entity.funnel_from_step, funnelToStep: entity.funnel_to_step }
})
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/scenes/insights/utils/cleanFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ export const getClampedStepRangeFilter = ({
stepRange,
filters,
}: {
stepRange?: FunnelExclusionLegacy
stepRange?: FunnelExclusionLegacy | { funnel_from_step?: number; funnel_to_step?: number }
filters: FunnelsFilterType
}): FunnelExclusionLegacy => {
}): FunnelExclusionLegacy | { funnel_from_step?: number; funnel_to_step?: number } => {
const maxStepIndex = Math.max((filters.events?.length || 0) + (filters.actions?.length || 0) - 1, 1)

let funnel_from_step = findFirstNumber([stepRange?.funnel_from_step, filters.funnel_from_step])
Expand Down Expand Up @@ -368,7 +368,7 @@ export function cleanFilters(
stepRange: e,
filters: funnelsFilter,
})
),
) as FunnelExclusionLegacy[],
}
return returnedParams
} else if (isPathsFilter(filters)) {
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/scenes/notebooks/Notebook/migrations/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
isLegacyTrendsFilter,
} from '~/queries/nodes/InsightQuery/utils/legacy'
import { InsightVizNode, NodeKind } from '~/queries/schema'
import { NotebookNodeType, NotebookType } from '~/types'
import { FunnelExclusionLegacy, NotebookNodeType, NotebookType } from '~/types'

// NOTE: Increment this number when you add a new content migration
// It will bust the cache on the localContent in the notebookLogic
Expand Down Expand Up @@ -111,7 +111,9 @@ function convertInsightQueriesToNewSchema(content: JSONContent[]): JSONContent[]
} else if (isLegacyFunnelsExclusion(query.funnelsFilter as any)) {
query.funnelsFilter = {
...query.funnelsFilter,
exclusions: query.funnelsFilter!.exclusions!.map((entity) => exlusionEntityToNode(entity)),
exclusions: query.funnelsFilter!.exclusions!.map((entity) =>
exlusionEntityToNode(entity as unknown as FunnelExclusionLegacy)
),
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -901,8 +901,8 @@ export interface ActionFilter extends EntityFilter {
}

export interface FunnelExclusionLegacy extends Partial<EntityFilter> {
funnel_from_step?: number
funnel_to_step?: number
funnel_from_step: number
funnel_to_step: number
}

export type EntityFilterTypes = EntityFilter | ActionFilter | null
Expand Down
Loading
Loading