Skip to content

Commit

Permalink
feat: Improve Heatmaps UI and notices (#21887)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjackwhite authored Apr 26, 2024
1 parent 8766db1 commit 9bd7618
Showing 1 changed file with 179 additions and 65 deletions.
244 changes: 179 additions & 65 deletions frontend/src/toolbar/stats/HeatmapToolbarMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IconMagicWand } from '@posthog/icons'
import { LemonLabel, LemonSegmentedButton } from '@posthog/lemon-ui'
import { IconInfo, IconMagicWand } from '@posthog/icons'
import { LemonLabel, LemonSegmentedButton, LemonTag } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { CUSTOM_OPTION_KEY } from 'lib/components/DateFilter/types'
import { IconSync } from 'lib/lemon-ui/icons'
Expand All @@ -11,12 +11,14 @@ import { LemonSwitch } from 'lib/lemon-ui/LemonSwitch'
import { Spinner } from 'lib/lemon-ui/Spinner'
import { Tooltip } from 'lib/lemon-ui/Tooltip'
import { dateFilterToText, dateMapping } from 'lib/utils'
import React, { useState } from 'react'

import { ToolbarMenu } from '~/toolbar/bar/ToolbarMenu'
import { elementsLogic } from '~/toolbar/elements/elementsLogic'
import { heatmapLogic } from '~/toolbar/elements/heatmapLogic'
import { currentPageLogic } from '~/toolbar/stats/currentPageLogic'

import { toolbarConfigLogic } from '../toolbarConfigLogic'
import { useToolbarFeatureFlag } from '../toolbarPosthogJS'

const ScrollDepthJSWarning = (): JSX.Element | null => {
Expand All @@ -40,6 +42,91 @@ const ScrollDepthJSWarning = (): JSX.Element | null => {
)
}

const HeatmapsJSWarning = (): JSX.Element | null => {
const { posthog } = useValues(toolbarConfigLogic)

if (!posthog || posthog?.heatmaps?.isEnabled) {
return null
}

return (
<p className="my-2 bg-danger-highlight border border-danger rounded p-2">
{!posthog.heatmaps ? (
<>The version of posthog-js you are using does not support collecting heatmap data.</>
) : !posthog.heatmaps.isEnabled ? (
<>
Heatmap collection is disabled in your posthog-js configuration. If you do not see heatmap data then
this is likely why.
</>
) : null}
</p>
)
}

const SectionButton = ({
children,
checked,
onChange,
loading,
}: {
children: React.ReactNode
checked: boolean
onChange: (checked: boolean) => void
loading?: boolean
}): JSX.Element => {
return (
<div className="flex items-center">
<LemonButton
className="flex-1 -mx-2 p-2"
noPadding
onClick={() => onChange(!checked)}
sideIcon={<LemonSwitch checked={checked} />}
>
<span className="flex items-center gap-2">
{children}

{loading ? <Spinner /> : null}
</span>
</LemonButton>
</div>
)
}

const SectionSetting = ({
children,
title,
info,
}: {
children: React.ReactNode
title: React.ReactNode
info?: React.ReactNode
}): JSX.Element => {
const [showInfo, setShowInfo] = useState(false)
return (
<div className="space-y-2 mb-2">
<div className="flex items-center gap-2">
<LemonLabel className="flex-1">
{title}

{info && (
<LemonButton
icon={<IconInfo />}
size="xsmall"
active={showInfo}
onClick={() => setShowInfo(!showInfo)}
noPadding
/>
)}
</LemonLabel>
</div>

{showInfo ? <div className="text-sm">{info}</div> : null}

{children}
</div>
)
}

export const HeatmapToolbarMenu = (): JSX.Element => {
const { wildcardHref } = useValues(currentPageLogic)
const { setWildcardHref, autoWildcardHref } = useActions(currentPageLogic)
Expand Down Expand Up @@ -109,26 +196,41 @@ export const HeatmapToolbarMenu = (): JSX.Element => {
<ToolbarMenu.Body>
{showNewHeatmaps ? (
<div className="border-b p-2">
<LemonSwitch
className="w-full"
checked={!!heatmapFilters.enabled}
label={<>Heatmaps {rawHeatmapLoading ? <Spinner /> : null}</>}
<SectionButton
onChange={(e) =>
patchHeatmapFilters({
enabled: e,
})
}
/>
loading={rawHeatmapLoading}
checked={!!heatmapFilters.enabled}
>
Heatmaps <LemonTag type="highlight">NEW</LemonTag>{' '}
</SectionButton>

{heatmapFilters.enabled && (
<>
<HeatmapsJSWarning />
<p>
Heatmaps are calculated using additional data sent along with standard events. They
are based off of general pointer interactions and might not be 100% accurate to the
page you are viewing.
</p>
<div className="space-y-2">
<LemonLabel>Heatmap type</LemonLabel>

<SectionSetting
title="Heatmap type"
info={
<>
Select the kind of heatmap you want to view. Clicks, rageclicks, and mouse
moves options will show different "heat" based on the number of interactions
at that area of the page. Scroll depth will show how far down the page users
have reached.
<br />
Scroll depth uses additional information from Pageview and Pageleave events
to indicate how far down the page users have scrolled.
</>
}
>
<div className="flex gap-2 justify-between items-center">
<LemonSegmentedButton
onChange={(e) => patchHeatmapFilters({ type: e })}
Expand All @@ -153,19 +255,21 @@ export const HeatmapToolbarMenu = (): JSX.Element => {
]}
size="small"
/>

{heatmapFilters.type === 'scrolldepth' && <ScrollDepthJSWarning />}
</div>
</SectionSetting>

{heatmapFilters.type === 'scrolldepth' && (
<SectionSetting
title="Aggregation"
info={
<>
<p>
Scroll depth uses additional information from Pageview and Pageleave
events to indicate how far down the page users have scrolled.
</p>
<ScrollDepthJSWarning />
Heatmaps can be aggregated by total count or unique visitors. Total count
will show the total number of interactions on the page, while unique
visitors will only count each visitor once.
</>
)}

<LemonLabel>Aggregation</LemonLabel>
}
>
<div className="flex gap-2 justify-between items-center">
<LemonSegmentedButton
onChange={(e) => patchHeatmapFilters({ aggregation: e })}
Expand All @@ -183,8 +287,23 @@ export const HeatmapToolbarMenu = (): JSX.Element => {
size="small"
/>
</div>
</SectionSetting>

<LemonLabel>Viewport accuracy</LemonLabel>
<SectionSetting
title="Viewport accuracy"
info={
<>
The viewport accuracy setting will determine how closely the loaded data
will be to your current viewport.
<br />
For example if you set this to 100%, only visitors whose viewport width is
identical to yours will be included in the heatmap.
<br />
At 90% you will see data from viewports that are 10% smaller or larger than
yours.
</>
}
>
<div className="flex gap-2 justify-between items-center">
<LemonSlider
className="flex-1"
Expand All @@ -194,68 +313,63 @@ export const HeatmapToolbarMenu = (): JSX.Element => {
value={heatmapFilters.viewportAccuracy ?? 0}
onChange={(value) => patchHeatmapFilters({ viewportAccuracy: value })}
/>
<Tooltip
title={`
The range of values
Heatmap will be loaded for all viewports where the width is above
`}
>
<code className="w-[12rem] text-right text-xs whitsepace-nowrap">
{`${Math.round((heatmapFilters.viewportAccuracy ?? 1) * 100)}% (${
viewportRange.min
}px - ${viewportRange.max}px)`}
</code>
</Tooltip>
<code className="w-[12rem] text-right text-xs whitsepace-nowrap">
{`${Math.round((heatmapFilters.viewportAccuracy ?? 1) * 100)}% (${
viewportRange.min
}px - ${viewportRange.max}px)`}
</code>
</div>
</SectionSetting>

{heatmapFilters.type !== 'scrolldepth' ? (
<>
<LemonLabel>Fixed positioning calculation</LemonLabel>
<p>
{heatmapFilters.type !== 'scrolldepth' && (
<SectionSetting
title="Fixed positioning calculation"
info={
<>
PostHog JS will attempt to detect fixed elements such as headers or
modals and will therefore show those heatmap areas, ignoring the scroll
value.
<br />
You can choose to show these areas as fixed, include them with scrolled
data or hide them altogether.
</p>

<LemonSegmentedButton
onChange={setHeatmapFixedPositionMode}
value={heatmapFixedPositionMode}
options={[
{
value: 'fixed',
label: 'Show fixed',
},
{
value: 'relative',
label: 'Show scrolled',
},
{
value: 'hidden',
label: 'Hide',
},
]}
size="small"
/>
</>
) : null}
</div>
</>
}
>
<LemonSegmentedButton
onChange={setHeatmapFixedPositionMode}
value={heatmapFixedPositionMode}
options={[
{
value: 'fixed',
label: 'Show fixed',
},
{
value: 'relative',
label: 'Show scrolled',
},
{
value: 'hidden',
label: 'Hide',
},
]}
size="small"
/>
</SectionSetting>
)}
</>
)}
</div>
) : null}

<div className="p-2">
{showNewHeatmaps ? (
<LemonSwitch
className="w-full"
checked={!!clickmapsEnabled}
label={<>Clickmaps (autocapture) {elementStatsLoading ? <Spinner /> : null}</>}
<SectionButton
onChange={(e) => toggleClickmapsEnabled(e)}
/>
loading={elementStatsLoading}
checked={!!clickmapsEnabled}
>
Clickmaps (autocapture)
</SectionButton>
) : null}

{(clickmapsEnabled || !showNewHeatmaps) && (
Expand Down

0 comments on commit 9bd7618

Please sign in to comment.