Skip to content

Commit

Permalink
feat(web-analytics): Add channel to web dashboard (#19357)
Browse files Browse the repository at this point in the history
  • Loading branch information
robbie-c authored Dec 15, 2023
1 parent bd0fb1c commit 4649e6c
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 16 deletions.
1 change: 1 addition & 0 deletions frontend/src/queries/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3443,6 +3443,7 @@
"enum": [
"Page",
"InitialPage",
"InitialChannelType",
"InitialReferringDomain",
"InitialUTMSource",
"InitialUTMCampaign",
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 @@ -711,6 +711,7 @@ export enum WebStatsBreakdown {
Page = 'Page',
InitialPage = 'InitialPage',
// ExitPage = 'ExitPage'
InitialChannelType = 'InitialChannelType',
InitialReferringDomain = 'InitialReferringDomain',
InitialUTMSource = 'InitialUTMSource',
InitialUTMCampaign = 'InitialUTMCampaign',
Expand Down
23 changes: 18 additions & 5 deletions frontend/src/scenes/web-analytics/WebAnalyticsTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const BreakdownValueTitle: QueryContextColumnTitleComponent = (props) => {
return <>Path</>
case WebStatsBreakdown.InitialPage:
return <>Initial Path</>
case WebStatsBreakdown.InitialChannelType:
return <>Initial Channel Type</>
case WebStatsBreakdown.InitialReferringDomain:
return <>Referring Domain</>
case WebStatsBreakdown.InitialUTMSource:
Expand Down Expand Up @@ -114,12 +116,14 @@ const BreakdownValueCell: QueryContextColumnComponent = (props) => {

export const webStatsBreakdownToPropertyName = (
breakdownBy: WebStatsBreakdown
): { key: string; type: PropertyFilterType.Person | PropertyFilterType.Event } => {
): { key: string; type: PropertyFilterType.Person | PropertyFilterType.Event } | undefined => {
switch (breakdownBy) {
case WebStatsBreakdown.Page:
return { key: '$pathname', type: PropertyFilterType.Event }
case WebStatsBreakdown.InitialPage:
return { key: '$initial_pathname', type: PropertyFilterType.Person }
case WebStatsBreakdown.InitialChannelType:
return undefined
case WebStatsBreakdown.InitialReferringDomain:
return { key: '$initial_referring_domain', type: PropertyFilterType.Person }
case WebStatsBreakdown.InitialUTMSource:
Expand Down Expand Up @@ -189,11 +193,14 @@ export const WebStatsTrendTile = ({
hasOSFilter,
dateFilter: { interval },
} = useValues(webAnalyticsLogic)
const { key: worldMapPropertyName } = webStatsBreakdownToPropertyName(WebStatsBreakdown.Country)
const { key: deviceTypePropertyName } = webStatsBreakdownToPropertyName(WebStatsBreakdown.DeviceType)
const worldMapPropertyName = webStatsBreakdownToPropertyName(WebStatsBreakdown.Country)?.key
const deviceTypePropertyName = webStatsBreakdownToPropertyName(WebStatsBreakdown.DeviceType)?.key

const onWorldMapClick = useCallback(
(breakdownValue: string) => {
if (!worldMapPropertyName) {
return
}
togglePropertyFilter(PropertyFilterType.Event, worldMapPropertyName, breakdownValue)
if (!hasCountryFilter) {
// if we just added a country filter, switch to the region tab, as the world map will not be useful
Expand All @@ -216,6 +223,9 @@ export const WebStatsTrendTile = ({
if (!breakdownValue) {
return
}
if (!deviceTypePropertyName) {
return
}
togglePropertyFilter(PropertyFilterType.Event, deviceTypePropertyName, breakdownValue)

// switch to a different tab if we can, try them in this order: DeviceType Browser OS
Expand Down Expand Up @@ -283,10 +293,13 @@ export const WebStatsTableTile = ({
breakdownBy: WebStatsBreakdown
}): JSX.Element => {
const { togglePropertyFilter } = useActions(webAnalyticsLogic)
const { key, type } = webStatsBreakdownToPropertyName(breakdownBy)
const { key, type } = webStatsBreakdownToPropertyName(breakdownBy) || {}

const onClick = useCallback(
(breakdownValue: string) => {
if (!key || !type) {
return
}
togglePropertyFilter(type, key, breakdownValue)
},
[togglePropertyFilter, type, key]
Expand All @@ -299,7 +312,7 @@ export const WebStatsTableTile = ({
return {}
}
return {
onClick: () => onClick(breakdownValue),
onClick: key && type ? () => onClick(breakdownValue) : undefined,
}
}
return {
Expand Down
27 changes: 19 additions & 8 deletions frontend/src/scenes/web-analytics/WebTabs.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LemonTabs } from '@posthog/lemon-ui'
import { LemonSelect, LemonTabs } from '@posthog/lemon-ui'
import clsx from 'clsx'
import React from 'react'

Expand All @@ -19,13 +19,24 @@ export const WebTabs = ({
<div className={clsx(className, 'flex flex-col')}>
<div className="flex flex-row items-center self-stretch mb-3">
{<h2 className="flex-1 m-0">{activeTab?.title}</h2>}
<LemonTabs
inline
borderless
activeKey={activeTabId}
onChange={setActiveTabId}
tabs={tabs.map(({ id, linkText }) => ({ key: id, label: linkText }))}
/>
{tabs.length > 3 ? (
<LemonSelect
size={'small'}
disabled={false}
value={activeTabId}
dropdownMatchSelectWidth={false}
onChange={setActiveTabId}
options={tabs.map(({ id, linkText }) => ({ value: id, label: linkText }))}
/>
) : (
<LemonTabs
inline
borderless
activeKey={activeTabId}
onChange={setActiveTabId}
tabs={tabs.map(({ id, linkText }) => ({ key: id, label: linkText }))}
/>
)}
</div>
<div className="flex-1 flex flex-col">{activeTab?.content}</div>
</div>
Expand Down
68 changes: 66 additions & 2 deletions frontend/src/scenes/web-analytics/webAnalyticsLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,12 @@ export enum GraphsTab {

export enum SourceTab {
REFERRING_DOMAIN = 'REFERRING_DOMAIN',
CHANNEL = 'CHANNEL',
UTM_SOURCE = 'UTM_SOURCE',
UTM_MEDIUM = 'UTM_MEDIUM',
UTM_CAMPAIGN = 'UTM_CAMPAIGN',
UTM_CONTENT = 'UTM_CONTENT',
UTM_TERM = 'UTM_TERM',
}

export enum DeviceTab {
Expand Down Expand Up @@ -427,7 +431,7 @@ export const webAnalyticsLogic = kea<webAnalyticsLogicType>([
{
id: SourceTab.REFERRING_DOMAIN,
title: 'Top referrers',
linkText: 'Referrer',
linkText: 'Referrering domain',
query: {
full: true,
kind: NodeKind.DataTableNode,
Expand All @@ -439,6 +443,21 @@ export const webAnalyticsLogic = kea<webAnalyticsLogicType>([
},
},
},
{
id: SourceTab.CHANNEL,
title: 'Top channels',
linkText: 'Channel',
query: {
full: true,
kind: NodeKind.DataTableNode,
source: {
kind: NodeKind.WebStatsTableQuery,
properties: webAnalyticsFilters,
breakdownBy: WebStatsBreakdown.InitialChannelType,
dateRange,
},
},
},
{
id: SourceTab.UTM_SOURCE,
title: 'Top sources',
Expand All @@ -454,9 +473,24 @@ export const webAnalyticsLogic = kea<webAnalyticsLogicType>([
},
},
},
{
id: SourceTab.UTM_MEDIUM,
title: 'Top UTM medium',
linkText: 'UTM medium',
query: {
full: true,
kind: NodeKind.DataTableNode,
source: {
kind: NodeKind.WebStatsTableQuery,
properties: webAnalyticsFilters,
breakdownBy: WebStatsBreakdown.InitialUTMMedium,
dateRange,
},
},
},
{
id: SourceTab.UTM_CAMPAIGN,
title: 'Top campaigns',
title: 'Top UTM campaigns',
linkText: 'UTM campaign',
query: {
full: true,
Expand All @@ -469,6 +503,36 @@ export const webAnalyticsLogic = kea<webAnalyticsLogicType>([
},
},
},
{
id: SourceTab.UTM_CONTENT,
title: 'Top UTM content',
linkText: 'UTM content',
query: {
full: true,
kind: NodeKind.DataTableNode,
source: {
kind: NodeKind.WebStatsTableQuery,
properties: webAnalyticsFilters,
breakdownBy: WebStatsBreakdown.InitialUTMContent,
dateRange,
},
},
},
{
id: SourceTab.UTM_TERM,
title: 'Top UTM terms',
linkText: 'UTM term',
query: {
full: true,
kind: NodeKind.DataTableNode,
source: {
kind: NodeKind.WebStatsTableQuery,
properties: webAnalyticsFilters,
breakdownBy: WebStatsBreakdown.InitialUTMTerm,
dateRange,
},
},
},
],
},
{
Expand Down
2 changes: 1 addition & 1 deletion posthog/hogql/printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,7 +842,7 @@ def visit_call(self, node: ast.Call):
validate_function_args(node.args, func_meta.min_args, func_meta.max_args, node.name)
args = [self.visit(arg) for arg in node.args]

if self.dialect == "clickhouse":
if self.dialect in ("hogql", "clickhouse"):
if node.name == "hogql_lookupDomainType":
return f"dictGetOrNull('channel_definition_dict', 'domain_type', (cutToFirstSignificantSubdomain(coalesce({args[0]}, '')), 'source'))"
elif node.name == "hogql_lookupPaidDomainType":
Expand Down
64 changes: 64 additions & 0 deletions posthog/hogql_queries/web_analytics/stats_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,68 @@ def counts_breakdown(self):
match self.query.breakdownBy:
case WebStatsBreakdown.Page:
return ast.Field(chain=["properties", "$pathname"])
case WebStatsBreakdown.InitialChannelType:
# use this for now, switch to person.$virt_initial_channel_type when it's working. If fixing or adding
# anything to this, keep in sync with channel_type.py
return parse_expr(
"""
multiIf(
match(person.properties.$initial_utm_campaign, 'cross-network'),
'Cross Network',
(
match(person.properties.$initial_utm_medium, '^(.*cp.*|ppc|retargeting|paid.*)$') OR
properties.$initial_gclid IS NOT NULL OR
properties.$initial_gad_source IS NOT NULL
),
coalesce(
hogql_lookupPaidSourceType(person.properties.$initial_utm_source),
hogql_lookupPaidDomainType(person.properties.$initial_referring_domain),
if(
match(properties.$initial_utm_campaign, '^(.*(([^a-df-z]|^)shop|shopping).*)$'),
'Paid Shopping',
NULL
),
hogql_lookupPaidMediumType(person.properties.$initial_utm_medium),
multiIf (
person.properties.$initial_gad_source = '1',
'Paid Search',
match(person.properties.$initial_utm_campaign, '^(.*video.*)$'),
'Paid Video',
'Paid Other'
)
),
(
person.properties.$initial_referring_domain = '$direct'
AND (person.properties.$initial_utm_medium IS NULL OR person.properties.$initial_utm_medium = '')
AND (person.properties.$initial_utm_source IS NULL OR person.properties.$initial_utm_source IN ('', '(direct)', 'direct'))
),
'Direct',
coalesce(
hogql_lookupOrganicSourceType(person.properties.$initial_utm_source),
hogql_lookupOrganicDomainType(person.properties.$initial_referring_domain),
if(
match(person.properties.$initial_utm_campaign, '^(.*(([^a-df-z]|^)shop|shopping).*)$'),
'Organic Shopping',
NULL
),
hogql_lookupOrganicMediumType(person.properties.$initial_utm_medium),
multiIf(
match(person.properties.$initial_utm_campaign, '^(.*video.*)$'),
'Organic Video',
match(person.properties.$initial_utm_medium, 'push$'),
'Push',
NULL
)
)
)"""
)
case WebStatsBreakdown.InitialPage:
return ast.Field(chain=["person", "properties", "$initial_pathname"])
case WebStatsBreakdown.InitialReferringDomain:
Expand Down Expand Up @@ -138,6 +200,8 @@ def where_breakdown(self):
return parse_expr('tupleElement("context.columns.breakdown_value", 2) IS NOT NULL')
case WebStatsBreakdown.City:
return parse_expr('tupleElement("context.columns.breakdown_value", 2) IS NOT NULL')
case WebStatsBreakdown.InitialChannelType:
return parse_expr("TRUE") # actually show null values
case WebStatsBreakdown.InitialUTMSource:
return parse_expr("TRUE") # actually show null values
case WebStatsBreakdown.InitialUTMCampaign:
Expand Down
1 change: 1 addition & 0 deletions posthog/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,7 @@ class WebOverviewQueryResponse(BaseModel):
class WebStatsBreakdown(str, Enum):
Page = "Page"
InitialPage = "InitialPage"
InitialChannelType = "InitialChannelType"
InitialReferringDomain = "InitialReferringDomain"
InitialUTMSource = "InitialUTMSource"
InitialUTMCampaign = "InitialUTMCampaign"
Expand Down

0 comments on commit 4649e6c

Please sign in to comment.