Skip to content

Commit

Permalink
feat(web-analytics): Move revenue tracking to data management (#28087)
Browse files Browse the repository at this point in the history
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Jan 31, 2025
1 parent 4285c1c commit 0234cae
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 60 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { BuilderHog3, DetectiveHog } from '../hedgehogs'
export type ProductIntroductionProps = {
/** The name of the product, e.g. "Cohorts" */
productName: string
productKey: ProductKey
productKey?: ProductKey
/** The name of the thing that they will create, e.g. "cohort" */
thingName: string
description: string
Expand Down Expand Up @@ -52,7 +52,7 @@ export const ProductIntroduction = ({
return null
}

if (!isEmpty && user.has_seen_product_intro_for?.[productKey]) {
if (!isEmpty && (!productKey || user.has_seen_product_intro_for?.[productKey])) {
// Hide if its not an empty list but the user has seen it before
return null
}
Expand All @@ -66,7 +66,7 @@ export const ProductIntroduction = ({
<LemonButton
icon={<IconX />}
onClick={() => {
updateHasSeenProductIntroFor(productKey, true)
productKey && updateHasSeenProductIntroFor(productKey, true)
}}
/>
</div>
Expand Down Expand Up @@ -107,7 +107,7 @@ export const ProductIntroduction = ({
type="primary"
icon={<IconPlus />}
onClick={() => {
updateHasSeenProductIntroFor(productKey, true)
productKey && updateHasSeenProductIntroFor(productKey, true)
action && action()
}}
data-attr={'create-' + thingName.replace(' ', '-').toLowerCase()}
Expand Down
48 changes: 28 additions & 20 deletions frontend/src/lib/components/TaxonomicPopover/TaxonomicPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { TaxonomicFilter } from 'lib/components/TaxonomicFilter/TaxonomicFilter'
import { TaxonomicFilterGroupType, TaxonomicFilterValue } from 'lib/components/TaxonomicFilter/types'
import { LemonButton, LemonButtonProps } from 'lib/lemon-ui/LemonButton'
import { LemonDropdown } from 'lib/lemon-ui/LemonDropdown'
import { useEffect, useState } from 'react'
import { forwardRef, Ref, useEffect, useState } from 'react'
import { LocalFilter } from 'scenes/insights/filters/ActionFilter/entityFilterLogic'

import { AnyDataNode, DatabaseSchemaField } from '~/queries/schema'
Expand Down Expand Up @@ -41,23 +41,28 @@ export function TaxonomicStringPopover(props: TaxonomicPopoverProps<string>): JS
)
}

export function TaxonomicPopover<ValueType extends TaxonomicFilterValue = TaxonomicFilterValue>({
groupType,
value,
filter,
onChange,
renderValue,
groupTypes,
eventNames = [],
placeholder = 'Please select',
placeholderClass = 'text-muted',
allowClear = false,
excludedProperties,
metadataSource,
schemaColumns,
showNumericalPropsOnly,
...buttonPropsRest
}: TaxonomicPopoverProps<ValueType>): JSX.Element {
export const TaxonomicPopover = forwardRef(function TaxonomicPopover_<
ValueType extends TaxonomicFilterValue = TaxonomicFilterValue
>(
{
groupType,
value,
filter,
onChange,
renderValue,
groupTypes,
eventNames = [],
placeholder = 'Please select',
placeholderClass = 'text-muted',
allowClear = false,
excludedProperties,
metadataSource,
schemaColumns,
showNumericalPropsOnly,
...buttonPropsRest
}: TaxonomicPopoverProps<ValueType>,
ref: Ref<HTMLButtonElement>
): JSX.Element {
const [localValue, setLocalValue] = useState<ValueType>(value || ('' as ValueType))
const [visible, setVisible] = useState(false)

Expand Down Expand Up @@ -119,10 +124,13 @@ export function TaxonomicPopover<ValueType extends TaxonomicFilterValue = Taxono
divider: false,
}}
{...buttonPropsFinal}
ref={ref}
/>
) : (
<LemonButton {...buttonPropsFinal} />
<LemonButton {...buttonPropsFinal} ref={ref} />
)}
</LemonDropdown>
)
}
}) as <ValueType extends TaxonomicFilterValue = TaxonomicFilterValue>(
p: TaxonomicPopoverProps<ValueType> & { ref?: Ref<HTMLButtonElement> }
) => JSX.Element
25 changes: 22 additions & 3 deletions frontend/src/queries/nodes/WebOverview/WebOverview.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IconTrending } from '@posthog/icons'
import { LemonSkeleton } from '@posthog/lemon-ui'
import { IconGear, IconTrending } from '@posthog/icons'
import { LemonButton, LemonSkeleton } from '@posthog/lemon-ui'
import { useValues } from 'kea'
import { getColorVar } from 'lib/colors'
import { useFeatureFlag } from 'lib/hooks/useFeatureFlag'
Expand All @@ -8,6 +8,7 @@ import { LemonBanner } from 'lib/lemon-ui/LemonBanner'
import { Tooltip } from 'lib/lemon-ui/Tooltip'
import { humanFriendlyDuration, humanFriendlyLargeNumber, isNotNil, range } from 'lib/utils'
import { useState } from 'react'
import { urls } from 'scenes/urls'

import { EvenlyDistributedRows } from '~/queries/nodes/WebOverview/EvenlyDistributedRows'
import {
Expand Down Expand Up @@ -101,6 +102,8 @@ const WebOverviewItemCell = ({ item }: { item: WebOverviewItem }): JSX.Element =
}
: undefined

const docsUrl = settingsLinkFromKey(item.key)

// If current === previous, say "increased by 0%"
const tooltip =
isNotNil(item.value) && isNotNil(item.previous) && isNotNil(item.changeFromPreviousPct)
Expand All @@ -119,7 +122,13 @@ const WebOverviewItemCell = ({ item }: { item: WebOverviewItem }): JSX.Element =
return (
<Tooltip title={tooltip}>
<div className={OVERVIEW_ITEM_CELL_CLASSES}>
<div className="font-bold uppercase text-xs">{label}</div>
<div className="flex flex-row w-full">
<div className="flex-1" />
<div className="font-bold uppercase text-xs py-1">{label}</div>
<div className="flex flex-1 flex-row justify-end items-start">
{docsUrl && <LemonButton to={docsUrl} icon={<IconGear />} size="xsmall" />}
</div>
</div>
<div className="w-full flex-1 flex items-center justify-center">
<div className="text-2xl">{formatItem(item.value, item.kind)}</div>
</div>
Expand Down Expand Up @@ -212,3 +221,13 @@ const labelFromKey = (key: string): string => {
.join(' ')
}
}

const settingsLinkFromKey = (key: string): string | null => {
switch (key) {
case 'revenue':
case 'conversion revenue':
return urls.revenue()
default:
return null
}
}
47 changes: 33 additions & 14 deletions frontend/src/scenes/data-management/DataManagementScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ import { actionToUrl, combineUrl, router, urlToAction } from 'kea-router'
import { ActivityLog } from 'lib/components/ActivityLog/ActivityLog'
import { PageHeader } from 'lib/components/PageHeader'
import { TitleWithIcon } from 'lib/components/TitleWithIcon'
import { FEATURE_FLAGS } from 'lib/constants'
import { FEATURE_FLAGS, FeatureFlagKey } from 'lib/constants'
import { LemonTab, LemonTabs } from 'lib/lemon-ui/LemonTabs'
import { LemonTag } from 'lib/lemon-ui/LemonTag'
import { Tooltip } from 'lib/lemon-ui/Tooltip'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { capitalizeFirstLetter } from 'lib/utils'
import React from 'react'
import { NewActionButton } from 'scenes/actions/NewActionButton'
import { Annotations } from 'scenes/annotations'
import { NewAnnotationButton } from 'scenes/annotations/AnnotationModal'
import { RevenueEventsSettings } from 'scenes/data-management/revenue/RevenueEventsSettings'
import { Scene, SceneExport } from 'scenes/sceneTypes'
import { urls } from 'scenes/urls'

Expand All @@ -31,11 +33,17 @@ export enum DataManagementTab {
Annotations = 'annotations',
History = 'history',
IngestionWarnings = 'warnings',
Revenue = 'revenue',
}

const tabs: Record<
DataManagementTab,
{ url: string; label: LemonTab<any>['label']; content: JSX.Element; buttons?: React.ReactNode }
{
url: string
label: LemonTab<any>['label']
content: JSX.Element
buttons?: React.ReactNode
flag?: FeatureFlagKey
}
> = {
[DataManagementTab.EventDefinitions]: {
url: urls.eventDefinitions(),
Expand Down Expand Up @@ -89,10 +97,24 @@ const tabs: Record<
/>
),
},
[DataManagementTab.Revenue]: {
url: urls.revenue(),
label: (
<>
Revenue{' '}
<LemonTag type="warning" size="small" className="ml-2">
BETA
</LemonTag>
</>
),
content: <RevenueEventsSettings />,
flag: FEATURE_FLAGS.WEB_REVENUE_TRACKING,
},
[DataManagementTab.IngestionWarnings]: {
url: urls.ingestionWarnings(),
label: 'Ingestion warnings',
content: <IngestionWarningsView />,
flag: FEATURE_FLAGS.INGESTION_WARNINGS_ENABLED,
},
}

Expand Down Expand Up @@ -130,18 +152,15 @@ const dataManagementSceneLogic = kea<dataManagementSceneLogicType>([
]
},
],
showWarningsTab: [
(s) => [s.featureFlags],
(featureFlags): boolean => !!featureFlags[FEATURE_FLAGS.INGESTION_WARNINGS_ENABLED],
],
enabledTabs: [
(s) => [s.showWarningsTab],
(showWarningsTab): DataManagementTab[] => {
const allTabs = Object.keys(tabs)

return allTabs.filter((x) => {
return x === DataManagementTab.IngestionWarnings ? showWarningsTab : true
}) as DataManagementTab[]
(s) => [s.featureFlags],
(featureFlags): DataManagementTab[] => {
const allTabs = Object.entries(tabs)
return allTabs
.filter(([_, tab]) => {
return !tab.flag || !!featureFlags[tab.flag]
})
.map(([tabName, _]) => tabName) as DataManagementTab[]
},
],
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { IconTrash } from '@posthog/icons'
import { useActions, useValues } from 'kea'
import { ProductIntroduction } from 'lib/components/ProductIntroduction/ProductIntroduction'
import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
import { TaxonomicPopover } from 'lib/components/TaxonomicPopover/TaxonomicPopover'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
import { LemonTable } from 'lib/lemon-ui/LemonTable'
import { useCallback } from 'react'
import { revenueEventsSettingsLogic } from 'scenes/settings/environment/revenueEventsSettingsLogic'
import { useCallback, useRef } from 'react'
import { revenueEventsSettingsLogic } from 'scenes/data-management/revenue/revenueEventsSettingsLogic'

import { RevenueTrackingEventItem } from '~/queries/schema'

const ADD_EVENT_BUTTON_ID = 'data-management-revenue-settings-add-event'

export function RevenueEventsSettings(): JSX.Element {
const { saveDisabledReason, events } = useValues(revenueEventsSettingsLogic)
const { addEvent, deleteEvent, updatePropertyName, save } = useActions(revenueEventsSettingsLogic)
Expand All @@ -34,11 +37,17 @@ export function RevenueEventsSettings(): JSX.Element {
[updatePropertyName]
)

const buttonRef = useRef<HTMLButtonElement | null>(null)

return (
<div className="space-y-4">
<div>
<p>Add revenue tracking for Custom events in Web analytics</p>
</div>
<ProductIntroduction
productName="Revenue tracking"
thingName="revenue event"
description="Revenue events are used to track revenue in Web analytics. You can choose which custom events PostHog should consider as revenue events, and which event property corresponds to the value of the event."
isEmpty={events.length === 0}
action={() => buttonRef.current?.click()}
/>
<LemonTable<RevenueTrackingEventItem>
columns={[
{ key: 'eventName', title: 'Event name', dataIndex: 'eventName' },
Expand Down Expand Up @@ -67,13 +76,16 @@ export function RevenueEventsSettings(): JSX.Element {
/>

<TaxonomicPopover
type="primary"
groupType={TaxonomicFilterGroupType.CustomEvents}
onChange={addEvent}
value={undefined}
placeholder="Choose custom events to add"
placeholder="Create revenue event"
excludedProperties={{
[TaxonomicFilterGroupType.CustomEvents]: [null, ...events.map((item) => item.eventName)],
}}
id={ADD_EVENT_BUTTON_ID}
ref={buttonRef}
/>
<div className="mt-4">
<LemonButton type="primary" onClick={save} disabledReason={saveDisabledReason}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { actions, afterMount, connect, kea, path, reducers, selectors } from 'kea'
import { loaders } from 'kea-loaders'
import { TaxonomicFilterValue } from 'lib/components/TaxonomicFilter/types'
import { objectsEqual } from 'lib/utils'
import { teamLogic } from 'scenes/teamLogic'

Expand All @@ -10,13 +11,13 @@ import type { revenueEventsSettingsLogicType } from './revenueEventsSettingsLogi
const createEmptyConfig = (): RevenueTrackingConfig => ({ events: [] })

export const revenueEventsSettingsLogic = kea<revenueEventsSettingsLogicType>([
path(['scenes', 'settings', 'environment', 'revenueEventsSettingsLogic']),
path(['scenes', 'data-management', 'revenue', 'revenueEventsSettingsLogic']),
connect({
values: [teamLogic, ['currentTeam', 'currentTeamId']],
actions: [teamLogic, ['updateCurrentTeam']],
}),
actions({
addEvent: (eventName: string) => ({ eventName }),
addEvent: (eventName: TaxonomicFilterValue) => ({ eventName }),
deleteEvent: (eventName: string) => ({ eventName }),
updatePropertyName: (eventName: string, revenueProperty: string) => ({ eventName, revenueProperty }),
}),
Expand All @@ -35,10 +36,11 @@ export const revenueEventsSettingsLogic = kea<revenueEventsSettingsLogicType>([
return state
}
const existingEvents = new Set(state.events.map((item: RevenueTrackingEventItem) => item.eventName))
if (!existingEvents.has(eventName)) {
return { ...state, events: [...state.events, { eventName, revenueProperty: 'revenue' }] }
if (existingEvents.has(eventName)) {
return state
}
return state

return { ...state, events: [...state.events, { eventName, revenueProperty: 'revenue' }] }
},
deleteEvent: (state, { eventName }) => {
if (!state) {
Expand Down Expand Up @@ -70,7 +72,10 @@ export const revenueEventsSettingsLogic = kea<revenueEventsSettingsLogicType>([
],
})),
selectors({
events: [(s) => [s.revenueTrackingConfig], (revenueTrackingConfig) => revenueTrackingConfig?.events || []],
events: [
(s) => [s.revenueTrackingConfig],
(revenueTrackingConfig: RevenueTrackingConfig | null) => revenueTrackingConfig?.events || [],
],
saveDisabledReason: [
(s) => [s.revenueTrackingConfig, s.savedRevenueTrackingConfig],
(config, savedConfig): string | null => {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/scenes/scenes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ export const routes: Record<string, [Scene | string, string]> = {
[urls.savedInsights()]: [Scene.SavedInsights, 'savedInsights'],
[urls.webAnalytics()]: [Scene.WebAnalytics, 'webAnalytics'],
[urls.webAnalyticsWebVitals()]: [Scene.WebAnalytics, 'webAnalyticsWebVitals'],
[urls.revenue()]: [Scene.DataManagement, 'revenue'],
[urls.actions()]: [Scene.DataManagement, 'actions'],
[urls.eventDefinitions()]: [Scene.DataManagement, 'eventDefinitions'],
[urls.eventDefinition(':id')]: [Scene.EventDefinition, 'eventDefinition'],
Expand Down
7 changes: 0 additions & 7 deletions frontend/src/scenes/settings/SettingsMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { DeadClicksAutocaptureSettings } from 'scenes/settings/environment/DeadC
import { PersonsJoinMode } from 'scenes/settings/environment/PersonsJoinMode'
import { PersonsOnEvents } from 'scenes/settings/environment/PersonsOnEvents'
import { ReplayTriggers } from 'scenes/settings/environment/ReplayTriggers'
import { RevenueEventsSettings } from 'scenes/settings/environment/RevenueEventsSettings'
import { SessionsTableVersion } from 'scenes/settings/environment/SessionsTableVersion'

import { Realm } from '~/types'
Expand Down Expand Up @@ -249,12 +248,6 @@ export const SETTINGS_MAP: SettingSection[] = [
component: <BounceRatePageViewModeSetting />,
flag: 'SETTINGS_BOUNCE_RATE_PAGE_VIEW_MODE',
},
{
id: 'web-revenue-events',
title: 'Revenue tracking',
component: <RevenueEventsSettings />,
flag: 'WEB_REVENUE_TRACKING',
},
],
},

Expand Down
1 change: 1 addition & 0 deletions frontend/src/scenes/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export const urls = {
event: (id: string, timestamp: string): string =>
`/events/${encodeURIComponent(id)}/${encodeURIComponent(timestamp)}`,
ingestionWarnings: (): string => '/data-management/ingestion-warnings',
revenue: (): string => '/data-management/revenue',
insights: (): string => '/insights',
insightNew: (type?: InsightType, dashboardId?: DashboardType['id'] | null, query?: Node): string =>
combineUrl('/insights/new', dashboardId ? { dashboard: dashboardId } : {}, {
Expand Down

0 comments on commit 0234cae

Please sign in to comment.