Skip to content

Commit

Permalink
feat(hogql): Add funnels to paths insight (#21133)
Browse files Browse the repository at this point in the history
* Use funnel in paths query

Closes #21044 

---------

Co-authored-by: Julian Bez <[email protected]>
  • Loading branch information
thmsobrmlr and webjunkie authored Apr 3, 2024
1 parent 2fe9d37 commit b169fc1
Show file tree
Hide file tree
Showing 20 changed files with 540 additions and 214 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,25 @@ describe('filtersToQueryNode', () => {
end_point: 'b',
path_groupings: ['c', 'd'],
funnel_paths: FunnelPathType.between,
funnel_filter: { a: 1 },
funnel_filter: {
events: [
{
id: '$pageview',
name: '$pageview',
order: 0,
type: 'events',
},
{
id: null,
order: 1,
type: 'events',
},
],
exclusions: [],
funnel_step: 1,
funnel_viz_type: 'steps',
insight: 'FUNNELS',
},
exclude_events: ['e', 'f'],
step_limit: 1,
path_start_key: 'g',
Expand All @@ -529,8 +547,6 @@ describe('filtersToQueryNode', () => {
startPoint: 'a',
endPoint: 'b',
pathGroupings: ['c', 'd'],
funnelPaths: FunnelPathType.between,
funnelFilter: { a: 1 },
excludeEvents: ['e', 'f'],
stepLimit: 1,
pathReplacements: true,
Expand All @@ -539,6 +555,27 @@ describe('filtersToQueryNode', () => {
minEdgeWeight: 1,
maxEdgeWeight: 1,
},
funnelPathsFilter: {
funnelPathType: FunnelPathType.between,
funnelStep: 1,
funnelSource: {
funnelsFilter: {
funnelVizType: FunnelVizType.Steps,
},
kind: NodeKind.FunnelsQuery,
series: [
{
event: '$pageview',
kind: NodeKind.EventsNode,
name: '$pageview',
},
{
event: null,
kind: NodeKind.EventsNode,
},
],
},
},
}
expect(result).toEqual(query)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import {
EventsNode,
FunnelExclusionActionsNode,
FunnelExclusionEventsNode,
FunnelPathsFilter,
FunnelsFilter,
FunnelsQuery,
InsightNodeKind,
InsightQueryNode,
InsightsQueryBase,
Expand Down Expand Up @@ -303,6 +305,7 @@ export const filtersToQueryNode = (filters: Partial<FilterType>): InsightQueryNo
// paths filter
if (isPathsFilter(filters) && isPathsQuery(query)) {
query.pathsFilter = pathsFilterToQuery(filters)
query.funnelPathsFilter = filtersToFunnelPathsQuery(filters)
}

// stickiness filter
Expand Down Expand Up @@ -378,8 +381,6 @@ export const pathsFilterToQuery = (filters: Partial<PathsFilterType>): PathsFilt
startPoint: filters.start_point,
endPoint: filters.end_point,
pathGroupings: filters.path_groupings,
funnelPaths: filters.funnel_paths,
funnelFilter: filters.funnel_filter,
excludeEvents: filters.exclude_events,
stepLimit: filters.step_limit,
pathReplacements: filters.path_replacements,
Expand All @@ -390,6 +391,18 @@ export const pathsFilterToQuery = (filters: Partial<PathsFilterType>): PathsFilt
})
}

export const filtersToFunnelPathsQuery = (filters: Partial<PathsFilterType>): FunnelPathsFilter | undefined => {
if (filters.funnel_paths === undefined || filters.funnel_filter === undefined) {
return undefined
}

return {
funnelPathType: filters.funnel_paths,
funnelSource: filtersToQueryNode(filters.funnel_filter) as FunnelsQuery,
funnelStep: filters.funnel_filter?.funnel_step,
}
}

export const stickinessFilterToQuery = (filters: Record<string, any>): StickinessFilter => {
return objectCleanWithEmpty({
display: filters.display,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { FunnelLayout } from 'lib/constants'

import { hiddenLegendItemsToKeys, queryNodeToFilter } from '~/queries/nodes/InsightQuery/utils/queryNodeToFilter'
import { FunnelsQuery, LifecycleQuery, NodeKind, TrendsQuery } from '~/queries/schema'
import { FunnelsQuery, LifecycleQuery, NodeKind, PathsQuery, TrendsQuery } from '~/queries/schema'
import {
BreakdownAttributionType,
ChartDisplayType,
FunnelConversionWindowTimeUnit,
FunnelPathType,
FunnelsFilterType,
FunnelStepReference,
FunnelVizType,
InsightType,
LifecycleFilterType,
PathsFilterType,
PathType,
StepOrderValue,
TrendsFilterType,
} from '~/types'
Expand Down Expand Up @@ -178,6 +181,102 @@ describe('queryNodeToFilter', () => {
}
expect(result).toEqual(filters)
})

test('converts a pathsFilter and funnelPathsFilter into filter properties', () => {
const query: PathsQuery = {
kind: NodeKind.PathsQuery,
pathsFilter: {
includeEventTypes: [PathType.Screen, PathType.PageView],
startPoint: 'a',
endPoint: 'b',
pathGroupings: ['c', 'd'],
excludeEvents: ['e', 'f'],
stepLimit: 1,
pathReplacements: true,
localPathCleaningFilters: [{ alias: 'home' }],
edgeLimit: 1,
minEdgeWeight: 1,
maxEdgeWeight: 1,
},
funnelPathsFilter: {
funnelPathType: FunnelPathType.between,
funnelStep: 1,
funnelSource: {
funnelsFilter: {
funnelVizType: FunnelVizType.Steps,
},
kind: NodeKind.FunnelsQuery,
series: [
{
event: '$pageview',
kind: NodeKind.EventsNode,
name: '$pageview',
},
{
event: null,
kind: NodeKind.EventsNode,
},
],
},
},
}

const result = queryNodeToFilter(query)

const filters: Partial<PathsFilterType> = {
insight: InsightType.PATHS,
include_event_types: [PathType.Screen, PathType.PageView],
start_point: 'a',
end_point: 'b',
path_groupings: ['c', 'd'],
funnel_paths: FunnelPathType.between,
entity_type: 'events',
funnel_filter: {
entity_type: 'events',
events: [
{
id: '$pageview',
name: '$pageview',
order: 0,
type: 'events',
},
{
id: null,
order: 1,
type: 'events',
},
],
exclusions: undefined,
funnel_step: 1,
funnel_viz_type: 'steps',
insight: 'FUNNELS',
bin_count: undefined,
breakdown_attribution_type: undefined,
breakdown_attribution_value: undefined,
funnel_aggregate_by_hogql: undefined,
funnel_from_step: undefined,
funnel_to_step: undefined,
funnel_order_type: undefined,
funnel_step_reference: undefined,
funnel_window_interval: undefined,
funnel_window_interval_unit: undefined,
hidden_legend_keys: undefined,
interval: undefined,
},
exclude_events: ['e', 'f'],
step_limit: 1,
// path_start_key: 'g',
// path_end_key: 'h',
// path_dropoff_key: 'i',
path_replacements: true,
local_path_cleaning_filters: [{ alias: 'home' }],
edge_limit: 1,
min_edge_weight: 1,
max_edge_weight: 1,
paths_hogql_expression: undefined,
}
expect(result).toEqual(filters)
})
})

describe('hiddenLegendItemsToKeys', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,14 @@ export const queryNodeToFilter = (query: InsightQueryNode): Partial<FilterType>
camelCasedPathsProps.local_path_cleaning_filters = queryCopy.pathsFilter?.localPathCleaningFilters
camelCasedPathsProps.min_edge_weight = queryCopy.pathsFilter?.minEdgeWeight
camelCasedPathsProps.max_edge_weight = queryCopy.pathsFilter?.maxEdgeWeight
camelCasedPathsProps.funnel_paths = queryCopy.pathsFilter?.funnelPaths
camelCasedPathsProps.funnel_filter = queryCopy.pathsFilter?.funnelFilter
camelCasedPathsProps.funnel_paths = queryCopy.funnelPathsFilter?.funnelPathType
camelCasedPathsProps.funnel_filter =
queryCopy.funnelPathsFilter !== undefined
? {
...queryNodeToFilter(queryCopy.funnelPathsFilter.funnelSource),
funnel_step: queryCopy.funnelPathsFilter.funnelStep,
}
: undefined
delete queryCopy.pathsFilter?.edgeLimit
delete queryCopy.pathsFilter?.pathsHogQLExpression
delete queryCopy.pathsFilter?.includeEventTypes
Expand All @@ -280,8 +286,7 @@ export const queryNodeToFilter = (query: InsightQueryNode): Partial<FilterType>
delete queryCopy.pathsFilter?.localPathCleaningFilters
delete queryCopy.pathsFilter?.minEdgeWeight
delete queryCopy.pathsFilter?.maxEdgeWeight
delete queryCopy.pathsFilter?.funnelPaths
delete queryCopy.pathsFilter?.funnelFilter
delete queryCopy.funnelPathsFilter
} else if (isStickinessQuery(queryCopy)) {
camelCasedStickinessProps.show_legend = queryCopy.stickinessFilter?.showLegend
camelCasedStickinessProps.show_values_on_series = queryCopy.stickinessFilter?.showValuesOnSeries
Expand Down
26 changes: 20 additions & 6 deletions frontend/src/queries/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1989,6 +1989,22 @@
"enum": ["funnel_path_before_step", "funnel_path_between_steps", "funnel_path_after_step"],
"type": "string"
},
"FunnelPathsFilter": {
"additionalProperties": false,
"properties": {
"funnelPathType": {
"$ref": "#/definitions/FunnelPathType"
},
"funnelSource": {
"$ref": "#/definitions/FunnelsQuery"
},
"funnelStep": {
"type": "integer"
}
},
"required": ["funnelSource"],
"type": "object"
},
"FunnelStepReference": {
"enum": ["total", "previous"],
"type": "string"
Expand Down Expand Up @@ -3273,12 +3289,6 @@
},
"type": "array"
},
"funnelFilter": {
"type": "object"
},
"funnelPaths": {
"$ref": "#/definitions/FunnelPathType"
},
"includeEventTypes": {
"items": {
"$ref": "#/definitions/PathType"
Expand Down Expand Up @@ -3409,6 +3419,10 @@
"description": "Exclude internal and test users by applying the respective filters",
"type": "boolean"
},
"funnelPathsFilter": {
"$ref": "#/definitions/FunnelPathsFilter",
"description": "Used for displaying paths in relation to funnel steps."
},
"kind": {
"const": "PathsQuery",
"type": "string"
Expand Down
10 changes: 8 additions & 2 deletions frontend/src/queries/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -788,8 +788,6 @@ export type PathsFilter = {
localPathCleaningFilters?: PathsFilterLegacy['local_path_cleaning_filters']
minEdgeWeight?: PathsFilterLegacy['min_edge_weight']
maxEdgeWeight?: PathsFilterLegacy['max_edge_weight']
funnelPaths?: PathsFilterLegacy['funnel_paths']
funnelFilter?: PathsFilterLegacy['funnel_filter']

/** Relevant only within actors query */
pathStartKey?: string
Expand All @@ -799,11 +797,19 @@ export type PathsFilter = {
pathDropoffKey?: string
}

export type FunnelPathsFilter = {
funnelPathType: PathsFilterLegacy['funnel_paths']
funnelSource: FunnelsQuery
funnelStep?: integer
}

export interface PathsQuery extends InsightsQueryBase {
kind: NodeKind.PathsQuery
response?: PathsQueryResponse
/** Properties specific to the paths insight */
pathsFilter: PathsFilter
/** Used for displaying paths in relation to funnel steps. */
funnelPathsFilter?: FunnelPathsFilter
}

/** `StickinessFilterType` minus everything inherited from `FilterType` and persons modal related params
Expand Down
Loading

0 comments on commit b169fc1

Please sign in to comment.