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): Add funnels to paths insight #21133

Merged
merged 25 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
51bd5a9
Use funnel in paths query
webjunkie Mar 20, 2024
f4bfbbd
Fix set active view when changing insight
webjunkie Mar 20, 2024
3bf8be9
Add clauses
webjunkie Mar 20, 2024
ee95326
Clean up
webjunkie Mar 20, 2024
458c62b
Update query snapshots
github-actions[bot] Mar 20, 2024
0414b29
Update query snapshots
github-actions[bot] Mar 20, 2024
e4aefc9
Merge branch 'master' into feature/paths-funnels
webjunkie Mar 21, 2024
7c6cf17
Adjust session threshold calculation
webjunkie Mar 21, 2024
5795cb2
Fix code review suggestions
webjunkie Mar 22, 2024
da35c14
change schema
thmsobrmlr Mar 25, 2024
f9b8ae0
remove leftover todo
thmsobrmlr Mar 25, 2024
7de91a6
remove funnelPaths from funnelFilter
thmsobrmlr Mar 25, 2024
79cb03c
make funnelPathType and funnelSource require in FunnelPathsFilter
thmsobrmlr Mar 25, 2024
97c9132
adapt backend
thmsobrmlr Mar 25, 2024
432588b
adapt filtersToQueryNode
thmsobrmlr Mar 25, 2024
76bda03
adapt queryNodeToFilters
thmsobrmlr Mar 25, 2024
294b351
adapt filter_to_query
thmsobrmlr Mar 27, 2024
7c8323b
convert timestamp to utc before adding interval
thmsobrmlr Mar 27, 2024
ce3b487
fix mypy issues
thmsobrmlr Mar 27, 2024
e102cc8
fix paths frontend
thmsobrmlr Mar 27, 2024
d47bd8c
more fixes
thmsobrmlr Mar 27, 2024
8d60b20
Merge branch 'master' into feature/paths-funnels-followups
thmsobrmlr Mar 28, 2024
5ff98e1
Fix query not working and adjust display of start and end
webjunkie Apr 3, 2024
1523e6c
Merge branch 'master' into feature/paths-funnels-followups
webjunkie Apr 3, 2024
520a861
Merge branch 'master' into feature/paths-funnels-followups
webjunkie Apr 3, 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
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
Loading