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: Improve Heatmaps UI and notices #21887

Merged
merged 4 commits into from
Apr 26, 2024
Merged
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
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
Loading