Skip to content

Commit

Permalink
feat(web-analytics): Add last click exit url (#24725)
Browse files Browse the repository at this point in the history
  • Loading branch information
robbie-c authored Aug 30, 2024
1 parent c3e907c commit 0a6ab26
Show file tree
Hide file tree
Showing 14 changed files with 85 additions and 16 deletions.
1 change: 1 addition & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ export const FEATURE_FLAGS = {
BATCH_EXPORTS_POSTHOG_HTTP: 'posthog-http-batch-exports',
EXPERIMENT_MAKE_DECISION: 'experiment-make-decision', // owner: @jurajmajerik #team-feature-success
WEB_ANALYTICS_CONVERSION_GOALS: 'web-analytics-conversion-goals', // owner: @robbie-c
WEB_ANALYTICS_LAST_CLICK: 'web-analytics-last-click', // owner: @robbie-c
} as const
export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS]

Expand Down
5 changes: 5 additions & 0 deletions frontend/src/lib/taxonomy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,11 @@ export const CORE_FILTER_DEFINITIONS_BY_GROUP = {
description: <span>Whether the session was a bounce.</span>,
examples: ['true', 'false'],
},
$last_external_click_url: {
label: 'Last external click URL',
description: <span>The last external URL clicked in this session</span>,
examples: ['https://example.com/interesting-article?parameter=true'],
},
},
groups: {
$group_key: {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/queries/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -10242,6 +10242,7 @@
"Page",
"InitialPage",
"ExitPage",
"ExitClick",
"InitialChannelType",
"InitialReferringDomain",
"InitialUTMSource",
Expand Down
1 change: 1 addition & 0 deletions frontend/src/queries/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1296,6 +1296,7 @@ export enum WebStatsBreakdown {
Page = 'Page',
InitialPage = 'InitialPage',
ExitPage = 'ExitPage', // not supported in the legacy version
ExitClick = 'ExitClick',
InitialChannelType = 'InitialChannelType',
InitialReferringDomain = 'InitialReferringDomain',
InitialUTMSource = 'InitialUTMSource',
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/scenes/web-analytics/WebDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ export const WebTabs = ({
)}
</h2>

{tabs.length > 3 ? (
{tabs.length > 4 ? (
<LemonSelect
size="small"
disabled={false}
Expand Down
8 changes: 6 additions & 2 deletions frontend/src/scenes/web-analytics/tiles/WebAnalyticsTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ const BreakdownValueTitle: QueryContextColumnTitleComponent = (props) => {
case WebStatsBreakdown.InitialPage:
return <>Initial Path</>
case WebStatsBreakdown.ExitPage:
return <>Exit Path</>
return <>End Path</>
case WebStatsBreakdown.ExitClick:
return <>Exit Click</>
case WebStatsBreakdown.InitialChannelType:
return <>Initial Channel Type</>
case WebStatsBreakdown.InitialReferringDomain:
Expand Down Expand Up @@ -136,7 +138,9 @@ export const webStatsBreakdownToPropertyName = (
case WebStatsBreakdown.InitialPage:
return { key: '$entry_pathname', type: PropertyFilterType.Session }
case WebStatsBreakdown.ExitPage:
return { key: '$exit_pathname', type: PropertyFilterType.Session }
return { key: '$end_pathname', type: PropertyFilterType.Session }
case WebStatsBreakdown.ExitClick:
return { key: '$last_external_click_url', type: PropertyFilterType.Session }
case WebStatsBreakdown.InitialChannelType:
return { key: '$channel_type', type: PropertyFilterType.Session }
case WebStatsBreakdown.InitialReferringDomain:
Expand Down
39 changes: 32 additions & 7 deletions frontend/src/scenes/web-analytics/webAnalyticsLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ export enum DeviceTab {
export enum PathTab {
PATH = 'PATH',
INITIAL_PATH = 'INITIAL_PATH',
EXIT_PATH = 'EXIT_PATH',
END_PATH = 'END_PATH',
EXIT_CLICK = 'EXIT_CLICK',
}

export enum GeographyTab {
Expand Down Expand Up @@ -698,7 +699,7 @@ export const webAnalyticsLogic = kea<webAnalyticsLogicType>([
showPathCleaningControls: true,
},
{
id: PathTab.EXIT_PATH,
id: PathTab.END_PATH,
title: 'End paths',
linkText: 'End path',
query: {
Expand All @@ -717,10 +718,34 @@ export const webAnalyticsLogic = kea<webAnalyticsLogicType>([
},
embedded: false,
},
insightProps: createInsightProps(TileId.PATHS, PathTab.EXIT_PATH),
insightProps: createInsightProps(TileId.PATHS, PathTab.END_PATH),
canOpenModal: true,
showPathCleaningControls: true,
},
featureFlags[FEATURE_FLAGS.WEB_ANALYTICS_LAST_CLICK]
? {
id: PathTab.EXIT_CLICK,
title: 'Exit clicks',
linkText: 'Exit clicks',
query: {
full: true,
kind: NodeKind.DataTableNode,
source: {
kind: NodeKind.WebStatsTableQuery,
properties: webAnalyticsFilters,
breakdownBy: WebStatsBreakdown.ExitClick,
dateRange,
includeScrollDepth: false,
sampling,
limit: 10,
filterTestAccounts,
},
embedded: false,
},
insightProps: createInsightProps(TileId.PATHS, PathTab.END_PATH),
canOpenModal: true,
}
: null,
] as (TabsTileTab | undefined)[]
).filter(isNotNil),
},
Expand Down Expand Up @@ -1196,7 +1221,7 @@ export const webAnalyticsLogic = kea<webAnalyticsLogicType>([
const { tileId, tabId } = modalTileAndTab
const tile = tiles.find((tile) => tile.tileId === tileId)
if (!tile) {
throw new Error('Developer Error, tile not found')
return null
}

const extendQuery = (query: QuerySchema): QuerySchema => {
Expand All @@ -1215,7 +1240,7 @@ export const webAnalyticsLogic = kea<webAnalyticsLogicType>([
if (tile.kind === 'tabs') {
const tab = tile.tabs.find((tab) => tab.id === tabId)
if (!tab) {
throw new Error('Developer Error, tab not found')
return null
}
return {
tileId,
Expand Down Expand Up @@ -1322,12 +1347,12 @@ export const webAnalyticsLogic = kea<webAnalyticsLogicType>([

const tile = tiles.find((tile) => tile.tileId === tileId)
if (!tile) {
throw new Error('Developer Error, tile not found')
return undefined
}
if (tile.kind === 'tabs') {
const tab = tile.tabs.find((tab) => tab.id === tabId)
if (!tab) {
throw new Error('Developer Error, tab not found')
return undefined
}
return urls.insightNew(undefined, undefined, formatQueryForNewInsight(tab.query))
} else if (tile.kind === 'query') {
Expand Down
10 changes: 8 additions & 2 deletions posthog/api/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ def values(self, request: request.Request, **kwargs) -> response.Response:
raise ValidationError(detail=f"Key not provided")

modifiers = create_default_modifiers_for_team(team)
if modifiers.sessionTableVersion == SessionTableVersion.V2:
if (
modifiers.sessionTableVersion == SessionTableVersion.V2
or modifiers.sessionTableVersion == SessionTableVersion.AUTO
):
result = get_lazy_session_table_values_v2(key, search_term=search_term, team=team)
else:
result = get_lazy_session_table_values_v1(key, search_term=search_term, team=team)
Expand All @@ -61,7 +64,10 @@ def property_definitions(self, request: request.Request, **kwargs) -> response.R
# unlike e.g. event properties, there's a very limited number of session properties,
# so we can just return them all
modifiers = create_default_modifiers_for_team(self.team)
if modifiers.sessionTableVersion == SessionTableVersion.V2:
if (
modifiers.sessionTableVersion == SessionTableVersion.V2
or modifiers.sessionTableVersion == SessionTableVersion.AUTO
):
results = get_lazy_session_table_properties_v2(search)
else:
results = get_lazy_session_table_properties_v1(search)
Expand Down
6 changes: 4 additions & 2 deletions posthog/api/test/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ def test_expected_session_properties(self):
"$end_timestamp",
"$entry_current_url",
"$entry_pathname",
"$exit_current_url",
"$exit_pathname",
"$end_current_url",
"$end_pathname",
"$entry_gad_source",
"$entry_gclid",
"$entry_referring_domain",
Expand All @@ -48,9 +48,11 @@ def test_expected_session_properties(self):
"$entry_utm_source",
"$entry_utm_term",
"$pageview_count",
"$screen_count",
"$session_duration",
"$start_timestamp",
"$is_bounce",
"$last_external_click_url",
}
assert actual_properties == expected_properties

Expand Down
19 changes: 18 additions & 1 deletion posthog/demo/products/hedgebox/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

import pytz

from posthog.demo.matrix.models import Effect, SimPerson, SimSessionIntent
from posthog.demo.matrix.models import Effect, SimPerson, SimSessionIntent, EVENT_AUTOCAPTURE
from .taxonomy import (
EVENT_SIGNED_UP,
EVENT_LOGGED_IN,
Expand Down Expand Up @@ -43,6 +43,8 @@
GROUP_TYPE_ACCOUNT,
dyn_url_file,
dyn_url_invite,
URL_PRODUCT_AD_LINK_1,
URL_PRODUCT_AD_LINK_2,
)

if TYPE_CHECKING:
Expand All @@ -64,6 +66,7 @@ class HedgeboxSessionIntent(SimSessionIntent):
JOIN_TEAM = auto()
UPGRADE_PLAN = auto()
DOWNGRADE_PLAN = auto()
CHECK_LINKED_PR = auto()


class HedgeboxPlan(StrEnum):
Expand Down Expand Up @@ -355,6 +358,10 @@ def simulate_session(self):
{"$referrer": "$direct" if entered_url_directly else "https://www.youtube.com/"}
)
self.go_to_marius_tech_tips(None if entered_url_directly else {"utm_source": "youtube"})
if self.cluster.random.random() < 0.2:
self.click_product_ad_1()
elif self.cluster.random.random() < 0.5:
self.click_product_ad_2()
elif self.active_session_intent in (
HedgeboxSessionIntent.UPLOAD_FILE_S,
HedgeboxSessionIntent.DELETE_FILE_S,
Expand Down Expand Up @@ -810,6 +817,16 @@ def invitable_neighbors(self) -> list["HedgeboxPerson"]:
if neighbor.is_invitable
]

def click_product_ad_1(self):
self.active_client.capture(
EVENT_AUTOCAPTURE, {"$event_type": "click", "$external_click_url": URL_PRODUCT_AD_LINK_1}
)

def click_product_ad_2(self):
self.active_client.capture(
EVENT_AUTOCAPTURE, {"$event_type": "click", "$external_click_url": URL_PRODUCT_AD_LINK_2}
)


def add_params_to_url(url, query_params):
if not query_params:
Expand Down
3 changes: 3 additions & 0 deletions posthog/demo/products/hedgebox/taxonomy.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
URL_ACCOUNT_BILLING = f"{SITE_URL}/account/billing/"
URL_ACCOUNT_TEAM = f"{SITE_URL}/account/team/"

URL_PRODUCT_AD_LINK_1 = f"https://shop.example.com/products/10ft-hedgehog-statue?utm_source=hedgebox&utm_medium=paid"
URL_PRODUCT_AD_LINK_2 = f"https://travel.example.com/cruise/hedge-watching?utm_source=hedgebox&utm_medium=paid"

# Event taxonomy

EVENT_SIGNED_UP = "signed_up" # Properties: from_invite
Expand Down
1 change: 1 addition & 0 deletions posthog/hogql/database/schema/sessions_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ def is_match(field_name: str) -> bool:
"$entry_pathname": "path(finalizeAggregation(entry_url))",
"$end_current_url": "finalizeAggregation(end_url)",
"$end_pathname": "path(finalizeAggregation(end_url))",
"$last_external_click_url": "finalizeAggregation(last_external_click_url)",
}


Expand Down
4 changes: 3 additions & 1 deletion posthog/hogql_queries/web_analytics/stats_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,9 @@ def _counts_breakdown_value(self):
case WebStatsBreakdown.INITIAL_PAGE:
return self._apply_path_cleaning(ast.Field(chain=["session", "$entry_pathname"]))
case WebStatsBreakdown.EXIT_PAGE:
return self._apply_path_cleaning(ast.Field(chain=["session", "$exit_pathname"]))
return self._apply_path_cleaning(ast.Field(chain=["session", "$end_pathname"]))
case WebStatsBreakdown.EXIT_CLICK:
return ast.Field(chain=["session", "$last_external_click_url"])
case WebStatsBreakdown.INITIAL_REFERRING_DOMAIN:
return ast.Field(chain=["session", "$entry_referring_domain"])
case WebStatsBreakdown.INITIAL_UTM_SOURCE:
Expand Down
1 change: 1 addition & 0 deletions posthog/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -1466,6 +1466,7 @@ class WebStatsBreakdown(StrEnum):
PAGE = "Page"
INITIAL_PAGE = "InitialPage"
EXIT_PAGE = "ExitPage"
EXIT_CLICK = "ExitClick"
INITIAL_CHANNEL_TYPE = "InitialChannelType"
INITIAL_REFERRING_DOMAIN = "InitialReferringDomain"
INITIAL_UTM_SOURCE = "InitialUTMSource"
Expand Down

0 comments on commit 0a6ab26

Please sign in to comment.