Skip to content

Commit 19f5cc1

Browse files
authored
feat(customers): Add B2B mode to customer analytics dashboard (#42159)
## Problem Customer Analytics currently only supports B2C analytics. Let's enable B2B analytics too. Closes #41545 ## Changes - Added B2B/B2C toggle in the Customer Analytics filters - Implemented group type selection dropdown for B2B mode - Updated all insights to support both B2C (person-based) and B2B (group-based) analytics - Added support for weekly and monthly active groups in the MathTag component - Dynamically updated labels throughout the UI to reflect the selected business type (users vs. groups) - Fixed tooltip configuration in LineGraph component ### B2C selected <img width="1624" height="979" alt="image" src="https://github.com/user-attachments/assets/a669cc77-c52b-40df-8273-f548aeec8a59" /> ### B2B selected Insights reflect the selected group type <img width="1624" height="979" alt="image" src="https://github.com/user-attachments/assets/db0aabf1-bd40-46f9-ac9d-8235d6af2d7b" /> ### Group selector <img width="1624" height="979" alt="image" src="https://github.com/user-attachments/assets/41c90b5e-ddf2-4c36-bf47-b4101b795a7a" /> ## How did you test this code? - Tested switching between B2C and B2B modes and verified all insights update correctly - Verified group selection dropdown works and updates all insights - Confirmed that labels dynamically update based on the selected business type - Tested with various group types to ensure proper display of metrics - Verified that the B2B option is disabled when group analytics is not enabled ## Changelog: (features only) Is this feature complete? No, under feature flag
1 parent f6d2b99 commit 19f5cc1

File tree

6 files changed

+234
-59
lines changed

6 files changed

+234
-59
lines changed

frontend/src/lib/components/InsightLabel/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ function MathTag({ math, mathProperty, mathHogQL, mathGroupTypeIndex }: MathTagP
6868
if (math === 'unique_group' && mathGroupTypeIndex != undefined) {
6969
return <LemonTag>Unique {aggregationLabel(mathGroupTypeIndex).plural}</LemonTag>
7070
}
71+
if (math === 'weekly_active' && mathGroupTypeIndex != undefined) {
72+
return <LemonTag>Weekly active {aggregationLabel(mathGroupTypeIndex).plural}</LemonTag>
73+
}
74+
if (math === 'monthly_active' && mathGroupTypeIndex != undefined) {
75+
return <LemonTag>Monthly active {aggregationLabel(mathGroupTypeIndex).plural}</LemonTag>
76+
}
7177
if (math && ['sum', 'avg', 'min', 'max', 'median', 'p75', 'p90', 'p95', 'p99'].includes(math)) {
7278
return (
7379
<>

frontend/src/scenes/insights/views/LineGraph/LineGraph.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -787,14 +787,14 @@ export function LineGraph_({
787787
})
788788
}
789789
hideInspectActorsSection={!onClick || !showPersonsModal}
790+
{...tooltipConfig}
790791
groupTypeLabel={
791792
labelGroupType === 'people'
792793
? 'people'
793794
: labelGroupType === 'none'
794795
? ''
795796
: aggregationLabel(labelGroupType).plural
796797
}
797-
{...tooltipConfig}
798798
/>
799799
)
800800
}

products/customer_analytics/frontend/CustomerAnalyticsFilters.tsx

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useActions, useValues } from 'kea'
22

3-
import { Tooltip } from '@posthog/lemon-ui'
3+
import { LemonSegmentedButton, LemonSelect, Tooltip } from '@posthog/lemon-ui'
44

55
import { DateFilter } from 'lib/components/DateFilter/DateFilter'
66
import { CUSTOM_OPTION_KEY } from 'lib/components/DateFilter/types'
@@ -73,21 +73,46 @@ const DATE_FILTER_DATE_OPTIONS: DateMappingOption[] = [
7373

7474
export function CustomerAnalyticsFilters(): JSX.Element {
7575
const {
76+
businessType,
7677
dateFilter: { dateTo, dateFrom },
78+
groupsEnabled,
79+
groupOptions,
80+
selectedGroupType,
7781
} = useValues(customerAnalyticsSceneLogic)
7882

79-
const { setDates } = useActions(customerAnalyticsSceneLogic)
83+
const { setBusinessType, setDates, setSelectedGroupType } = useActions(customerAnalyticsSceneLogic)
84+
// TODO: Add CTA for cross sell
85+
const b2bDisabledReason = groupsEnabled ? '' : 'Group analytics add-on is not enabled'
8086

8187
return (
8288
<FilterBar
8389
left={
84-
<DateFilter
85-
dateFrom={dateFrom}
86-
dateTo={dateTo}
87-
onChange={setDates}
88-
dateOptions={DATE_FILTER_DATE_OPTIONS}
89-
size="small"
90-
/>
90+
<div className="flex flex-row items-center gap-2">
91+
<DateFilter
92+
dateFrom={dateFrom}
93+
dateTo={dateTo}
94+
onChange={setDates}
95+
dateOptions={DATE_FILTER_DATE_OPTIONS}
96+
size="small"
97+
/>
98+
<LemonSegmentedButton
99+
size="small"
100+
options={[
101+
{ label: 'B2C', value: 'b2c' },
102+
{ label: 'B2B', value: 'b2b', disabledReason: b2bDisabledReason },
103+
]}
104+
value={businessType}
105+
onChange={(value) => setBusinessType(value)}
106+
/>
107+
{businessType === 'b2b' && (
108+
<LemonSelect
109+
size="small"
110+
options={groupOptions}
111+
value={selectedGroupType}
112+
onChange={setSelectedGroupType}
113+
/>
114+
)}
115+
</div>
91116
}
92117
right={
93118
<Tooltip title="Refresh data">

products/customer_analytics/frontend/components/CustomerAnalyticsQueryCard.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useActions } from 'kea'
1+
import { useActions, useValues } from 'kea'
22

33
import { LemonBanner, LemonButton, LemonCard } from '@posthog/lemon-ui'
44

@@ -34,10 +34,16 @@ function getEmptySeriesNames(requiredSeries: object): string[] {
3434
.map(([key]) => key)
3535
}
3636

37+
function generateUniqueKey(name: string, tabId: string, businessType: string, groupType?: number): string {
38+
const suffix = businessType === 'b2b' ? groupType : 'users'
39+
return `${name}-${tabId}-${businessType}-${suffix}`
40+
}
41+
3742
export function CustomerAnalyticsQueryCard({ insight, tabId }: CustomerAnalyticsQueryCardProps): JSX.Element {
43+
const { businessType, selectedGroupType } = useValues(customerAnalyticsSceneLogic)
3844
const { addEventToHighlight, toggleModalOpen } = useActions(eventConfigModalLogic)
3945
const needsConfig = insight?.requiredSeries ? anyValueIsNull(insight.requiredSeries) : false
40-
const uniqueKey = `${insight.name}-${tabId}`
46+
const uniqueKey = generateUniqueKey(insight.name, tabId || '', businessType, selectedGroupType)
4147
const insightProps: InsightLogicProps<QuerySchema> = {
4248
dataNodeCollectionId: CUSTOMER_ANALYTICS_DATA_COLLECTION_NODE_ID,
4349
dashboardItemId: buildDashboardItemId(uniqueKey),

products/customer_analytics/frontend/components/Insights/ActiveUsersInsights.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { CustomerAnalyticsQueryCard } from '../CustomerAnalyticsQueryCard'
1616
import { eventConfigModalLogic } from './eventConfigModalLogic'
1717

1818
export function ActiveUsersInsights(): JSX.Element {
19-
const { activityEvent, activeUsersInsights, tabId } = useValues(customerAnalyticsSceneLogic)
19+
const { activityEvent, activeUsersInsights, customerLabel, tabId } = useValues(customerAnalyticsSceneLogic)
2020
const { toggleModalOpen } = useActions(eventConfigModalLogic)
2121

2222
// Check if using pageview as default, with no properties filter
@@ -38,7 +38,7 @@ export function ActiveUsersInsights(): JSX.Element {
3838
</div>
3939
</LemonBanner>
4040
)}
41-
<h2 className="ml-1">Active users</h2>
41+
<h2 className="ml-1">Active {customerLabel.plural}</h2>
4242
<div className="grid grid-cols-[3fr_1fr] gap-2">
4343
{activeUsersInsights.map((insight, index) => {
4444
return (
@@ -52,20 +52,24 @@ export function ActiveUsersInsights(): JSX.Element {
5252
}
5353

5454
function PowerUsersTable(): JSX.Element {
55-
const { dauSeries, tabId } = useValues(customerAnalyticsSceneLogic)
55+
const { businessType, customerLabel, dauSeries, selectedGroupType, tabId } = useValues(customerAnalyticsSceneLogic)
5656
const uniqueKey = `power-users-${tabId}`
5757
const insightProps: InsightLogicProps<InsightVizNode> = {
5858
dataNodeCollectionId: CUSTOMER_ANALYTICS_DATA_COLLECTION_NODE_ID,
5959
dashboardItemId: buildDashboardItemId(uniqueKey),
6060
}
6161

62+
const isB2c = businessType === 'b2c'
63+
const buttonTo = isB2c ? urls.persons() : urls.groups(selectedGroupType)
64+
const tooltip = isB2c ? 'Open people list' : `Open ${customerLabel.plural} list`
65+
6266
const query = {
6367
kind: NodeKind.DataTableNode,
6468
hiddenColumns: ['id', 'person.$delete', 'event_distinct_ids'],
6569
showSourceQueryOptions: false,
6670
source: {
6771
kind: NodeKind.ActorsQuery,
68-
select: ['person', 'event_count'],
72+
select: isB2c ? ['person', 'event_count'] : ['group', 'event_count'],
6973
source: {
7074
kind: NodeKind.InsightActorsQuery,
7175
source: {
@@ -78,6 +82,7 @@ function PowerUsersTable(): JSX.Element {
7882
trendsFilter: {
7983
display: ChartDisplayType.ActionsTable,
8084
},
85+
...(isB2c ? {} : { aggregation_group_type_index: selectedGroupType }),
8186
},
8287
series: 0,
8388
},
@@ -89,8 +94,8 @@ function PowerUsersTable(): JSX.Element {
8994
return (
9095
<>
9196
<div className="flex items-center gap-2 -mb-2">
92-
<h2 className="mb-0 ml-1">Power users</h2>
93-
<LemonButton size="small" noPadding targetBlank to={urls.persons()} tooltip="Open people list" />
97+
<h2 className="mb-0 ml-1">Power {customerLabel.plural}</h2>
98+
<LemonButton size="small" noPadding targetBlank to={buttonTo} tooltip={tooltip} />
9499
</div>
95100
<Query
96101
uniqueKey={uniqueKey}
@@ -99,6 +104,7 @@ function PowerUsersTable(): JSX.Element {
99104
context={{
100105
columns: {
101106
event_count: { title: 'Event count' },
107+
group: { title: customerLabel.singular },
102108
},
103109
insightProps,
104110
}}

0 commit comments

Comments
 (0)