Skip to content

Commit

Permalink
feat(web-analytics): Display values from previous period alongside cu…
Browse files Browse the repository at this point in the history
…rrent data (#26474)
  • Loading branch information
rafaeelaudibert authored Dec 2, 2024
1 parent 6dfacb0 commit 7ec7356
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 134 deletions.
4 changes: 4 additions & 0 deletions frontend/src/lib/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ export function percentage(
maximumFractionDigits: number = 2,
fixedPrecision: boolean = false
): string {
if (division === Infinity) {
return '∞%'
}

return division.toLocaleString('en-US', {
style: 'percent',
maximumFractionDigits,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/queries/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export type QueryContextColumnComponent = ComponentType<{
}>

interface QueryContextColumn {
title?: string
title?: JSX.Element | string
renderTitle?: QueryContextColumnTitleComponent
render?: QueryContextColumnComponent
align?: 'left' | 'right' | 'center' // default is left
Expand Down
118 changes: 89 additions & 29 deletions frontend/src/scenes/web-analytics/tiles/WebAnalyticsTile.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { IconGear } from '@posthog/icons'
import { IconGear, IconTrending } from '@posthog/icons'
import { Tooltip } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useActions, useValues } from 'kea'
import { getColorVar } from 'lib/colors'
import { IntervalFilterStandalone } from 'lib/components/IntervalFilter'
import { ProductIntroduction } from 'lib/components/ProductIntroduction/ProductIntroduction'
import { IconOpenInNew } from 'lib/lemon-ui/icons'
import { IconOpenInNew, IconTrendingDown, IconTrendingFlat } from 'lib/lemon-ui/icons'
import { LemonButton } from 'lib/lemon-ui/LemonButton'
import { LemonSwitch } from 'lib/lemon-ui/LemonSwitch'
import { UnexpectedNeverError } from 'lib/utils'
import { percentage, UnexpectedNeverError } from 'lib/utils'
import { useCallback, useMemo } from 'react'
import { NewActionButton } from 'scenes/actions/NewActionButton'
import { countryCodeToFlag, countryCodeToName } from 'scenes/insights/views/WorldMap'
Expand Down Expand Up @@ -37,15 +40,72 @@ const toUtcOffsetFormat = (value: number): string => {
return `UTC${sign}${integerPart}${formattedMinutes}`
}

const PercentageCell: QueryContextColumnComponent = ({ value }) => {
if (typeof value === 'number') {
return <span>{`${(value * 100).toFixed(1)}%`}</span>
}
return null
}
type VariationCellProps = { isPercentage?: boolean; reverseColors?: boolean }
const VariationCell = (
{ isPercentage, reverseColors }: VariationCellProps = { isPercentage: false, reverseColors: false }
): QueryContextColumnComponent => {
const formatNumber = (value: number): string =>
isPercentage ? `${(value * 100).toFixed(1)}%` : value.toLocaleString()

return function Cell({ value }) {
if (!value) {
return null
}

if (!Array.isArray(value)) {
return <span>{String(value)}</span>
}

const [current, previous] = value as [number, number]
const pctChangeFromPrevious =
previous === 0 && current === 0 // Special case, render as flatline
? 0
: current === null
? null
: previous === null || previous === 0
? Infinity
: current / previous - 1

const trend =
pctChangeFromPrevious === null
? null
: pctChangeFromPrevious === 0
? { Icon: IconTrendingFlat, color: getColorVar('muted') }
: pctChangeFromPrevious > 0
? {
Icon: IconTrending,
color: reverseColors ? getColorVar('danger') : getColorVar('success'),
}
: {
Icon: IconTrendingDown,
color: reverseColors ? getColorVar('success') : getColorVar('danger'),
}

// If current === previous, say "increased by 0%"
const tooltip =
pctChangeFromPrevious !== null
? `${current >= previous ? 'Increased' : 'Decreased'} by ${percentage(
Math.abs(pctChangeFromPrevious),
0
)} since last period (from ${formatNumber(previous)} to ${formatNumber(current)})`
: null

const NumericCell: QueryContextColumnComponent = ({ value }) => {
return <span>{typeof value === 'number' ? value.toLocaleString() : String(value)}</span>
return (
<div className={clsx({ 'pr-4': !trend })}>
<Tooltip title={tooltip}>
<span>
{formatNumber(current)}&nbsp;
{trend && (
// eslint-disable-next-line react/forbid-dom-props
<span style={{ color: trend.color }}>
<trend.Icon color={trend.color} className="ml-1" />
</span>
)}
</span>
</Tooltip>
</div>
)
}
}

const BreakdownValueTitle: QueryContextColumnTitleComponent = (props) => {
Expand Down Expand Up @@ -227,48 +287,48 @@ export const webAnalyticsDataTableQueryContext: QueryContext = {
render: BreakdownValueCell,
},
bounce_rate: {
title: 'Bounce Rate',
render: PercentageCell,
title: <span className="pr-5">Bounce Rate</span>,
render: VariationCell({ isPercentage: true, reverseColors: true }),
align: 'right',
},
views: {
title: 'Views',
render: NumericCell,
title: <span className="pr-5">Views</span>,
render: VariationCell(),
align: 'right',
},
clicks: {
title: 'Clicks',
render: NumericCell,
title: <span className="pr-5">Clicks</span>,
render: VariationCell(),
align: 'right',
},
visitors: {
title: 'Visitors',
render: NumericCell,
title: <span className="pr-5">Visitors</span>,
render: VariationCell(),
align: 'right',
},
average_scroll_percentage: {
title: 'Average Scroll',
render: PercentageCell,
title: <span className="pr-5">Average Scroll</span>,
render: VariationCell({ isPercentage: true }),
align: 'right',
},
scroll_gt80_percentage: {
title: 'Deep Scroll Rate',
render: PercentageCell,
title: <span className="pr-5">Deep Scroll Rate</span>,
render: VariationCell({ isPercentage: true }),
align: 'right',
},
total_conversions: {
title: 'Total Conversions',
render: NumericCell,
title: <span className="pr-5">Total Conversions</span>,
render: VariationCell(),
align: 'right',
},
conversion_rate: {
title: 'Conversion Rate',
render: PercentageCell,
title: <span className="pr-5">Conversion Rate</span>,
render: VariationCell({ isPercentage: true }),
align: 'right',
},
converting_users: {
title: 'Converting Users',
render: NumericCell,
title: <span className="pr-5">Converting Users</span>,
render: VariationCell(),
align: 'right',
},
action_name: {
Expand Down
1 change: 1 addition & 0 deletions posthog/hogql/functions/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,7 @@ def compare_types(arg_types: list[ConstantType], sig_arg_types: tuple[ConstantTy
"argMaxMerge": HogQLFunctionMeta("argMaxMerge", 1, 1, aggregate=True),
"avgState": HogQLFunctionMeta("avgState", 1, 1, aggregate=True),
"avgMerge": HogQLFunctionMeta("avgMerge", 1, 1, aggregate=True),
"avgMergeIf": HogQLFunctionMeta("avgMergeIf", 2, 2, aggregate=True),
"avgWeighted": HogQLFunctionMeta("avgWeighted", 2, 2, aggregate=True),
"avgWeightedIf": HogQLFunctionMeta("avgWeightedIf", 3, 3, aggregate=True),
"avgArray": HogQLFunctionMeta("avgArrayOrNull", 1, 1, aggregate=True),
Expand Down
Loading

0 comments on commit 7ec7356

Please sign in to comment.