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(web-analytics): Move revenue tracking to data management #28087

Merged
merged 5 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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])) {
robbie-c marked this conversation as resolved.
Show resolved Hide resolved
// 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)
robbie-c marked this conversation as resolved.
Show resolved Hide resolved
}}
/>
</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))
robbie-c marked this conversation as resolved.
Show resolved Hide resolved
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">
robbie-c marked this conversation as resolved.
Show resolved Hide resolved
<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:
robbie-c marked this conversation as resolved.
Show resolved Hide resolved
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]
robbie-c marked this conversation as resolved.
Show resolved Hide resolved
})
.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()}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What a lovely hack :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It actually feels pretty nice in the UI as well - the taxonomic popover opens upwards so even if you click the ProductIntro's button, your mouse will be over the list of events

/>
<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
Loading