Skip to content

Commit

Permalink
feat: Heatmaps toolbar code (#21630)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjackwhite authored Apr 23, 2024
1 parent 2f6344a commit c0b3406
Show file tree
Hide file tree
Showing 30 changed files with 1,103 additions and 269 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-other-toolbar--heatmap--dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified frontend/__snapshots__/scenes-other-toolbar--heatmap--light.png
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions frontend/src/lib/constants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ export const FEATURE_FLAGS = {
SESSION_REPLAY_MOBILE_ONBOARDING: 'session-replay-mobile-onboarding', // owner: #team-replay
IP_ALLOWLIST_SETTING: 'ip-allowlist-setting', // owner: @benjackwhite
EMAIL_VERIFICATION_TICKET_SUBMISSION: 'email-verification-ticket-submission', // owner: #team-growth
TOOLBAR_HEATMAPS: 'toolbar-heatmaps', // owner: #team-replay
THEME: 'theme', // owner: @aprilfools
} as const
export type FeatureFlagKey = (typeof FEATURE_FLAGS)[keyof typeof FEATURE_FLAGS]
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/loadPostHogJS.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export function loadPostHogJS(): void {
capture_copied_text: true,
},
process_person: 'identified_only',

__preview_heatmaps: true,

// Helper to capture events for assertions in Cypress
_onCapture: (window as any)._cypress_posthog_captures
? (_, event) => (window as any)._cypress_posthog_captures.push(event)
Expand Down
44 changes: 25 additions & 19 deletions frontend/src/toolbar/actions/ActionsEditingToolbarMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IconPencil, IconPlus, IconSearch, IconTrash, IconX } from '@posthog/icons'
import { IconPencil, IconPlus, IconSearch, IconTrash } from '@posthog/icons'
import { LemonDivider, LemonTag } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { Field, Form, Group } from 'kea-forms'
Expand All @@ -9,7 +9,7 @@ import { actionsTabLogic } from '~/toolbar/actions/actionsTabLogic'
import { SelectorEditingModal } from '~/toolbar/actions/SelectorEditingModal'
import { StepField } from '~/toolbar/actions/StepField'
import { ToolbarMenu } from '~/toolbar/bar/ToolbarMenu'
import { posthog } from '~/toolbar/posthog'
import { toolbarPosthogJS } from '~/toolbar/toolbarPosthogJS'

export const ActionsEditingToolbarMenu = (): JSX.Element => {
const {
Expand Down Expand Up @@ -38,7 +38,7 @@ export const ActionsEditingToolbarMenu = (): JSX.Element => {
startingSelector={editingSelectorValue}
onChange={(selector) => {
if (selector && editingSelector !== null) {
posthog.capture('toolbar_manual_selector_applied', {
toolbarPosthogJS.capture('toolbar_manual_selector_applied', {
chosenSelector: selector,
})
setElementSelector(selector, editingSelector)
Expand All @@ -52,7 +52,7 @@ export const ActionsEditingToolbarMenu = (): JSX.Element => {
enableFormOnSubmit
className="flex flex-col overflow-hidden flex-1"
>
<ToolbarMenu.Header>
<ToolbarMenu.Header className="border-b">
<h1 className="p-1 font-bold text-sm mb-0">
{selectedActionId === 'new' ? 'New ' : 'Edit '}
action
Expand Down Expand Up @@ -124,9 +124,12 @@ export const ActionsEditingToolbarMenu = (): JSX.Element => {
icon={<IconPencil />}
onClick={(e) => {
e.stopPropagation()
posthog.capture('toolbar_manual_selector_modal_opened', {
selector: step?.selector,
})
toolbarPosthogJS.capture(
'toolbar_manual_selector_modal_opened',
{
selector: step?.selector,
}
)
editSelectorWithIndex(index)
}}
>
Expand Down Expand Up @@ -198,22 +201,25 @@ export const ActionsEditingToolbarMenu = (): JSX.Element => {
</ToolbarMenu.Body>
<ToolbarMenu.Footer>
<span className="flex-1">
<LemonButton
type="secondary"
size="small"
onClick={() => selectAction(null)}
sideIcon={<IconX />}
>
Cancel
</LemonButton>
{selectedActionId !== 'new' ? (
<LemonButton
type="secondary"
status="danger"
onClick={deleteAction}
icon={<IconTrash />}
size="small"
>
Delete
</LemonButton>
) : null}
</span>
<LemonButton type="primary" htmlType="submit">
<LemonButton type="secondary" size="small" onClick={() => selectAction(null)}>
Cancel
</LemonButton>
<LemonButton type="primary" htmlType="submit" size="small">
{selectedActionId === 'new' ? 'Create ' : 'Save '}
action
</LemonButton>
{selectedActionId !== 'new' ? (
<LemonButton type="secondary" status="danger" onClick={deleteAction} icon={<IconTrash />} />
) : null}
</ToolbarMenu.Footer>
</Form>
</ToolbarMenu>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/toolbar/actions/ActionsListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function ActionsListView({ actions }: ActionsListViewProps): JSX.Element
subtle
key={action.id}
onClick={() => selectAction(action.id || null)}
className="font-medium my-1"
className="font-medium my-1 w-full"
>
<span className="min-w-[2rem] inline-block text-left">{index + 1}.</span>
<span className="flex-grow">
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/toolbar/actions/actionsTabLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { urls } from 'scenes/urls'

import { actionsLogic } from '~/toolbar/actions/actionsLogic'
import { toolbarLogic } from '~/toolbar/bar/toolbarLogic'
import { posthog } from '~/toolbar/posthog'
import { toolbarConfigLogic } from '~/toolbar/toolbarConfigLogic'
import { toolbarPosthogJS } from '~/toolbar/toolbarPosthogJS'
import { ActionDraftType, ActionForm } from '~/toolbar/types'
import { actionStepToActionStepFormItem, elementToActionStep, stepToDatabaseFormat } from '~/toolbar/utils'
import { ActionType, ElementType } from '~/types'
Expand Down Expand Up @@ -292,11 +292,11 @@ export const actionsTabLogic = kea<actionsTabLogicType>([
}
},
showButtonActions: () => {
posthog.capture('toolbar mode triggered', { mode: 'actions', enabled: true })
toolbarPosthogJS.capture('toolbar mode triggered', { mode: 'actions', enabled: true })
},
hideButtonActions: () => {
actions.setShowActionsTooltip(false)
posthog.capture('toolbar mode triggered', { mode: 'actions', enabled: false })
toolbarPosthogJS.capture('toolbar mode triggered', { mode: 'actions', enabled: false })
},
[actionsLogic.actionTypes.getActionsSuccess]: () => {
const { userIntent, actionId } = values
Expand Down
25 changes: 17 additions & 8 deletions frontend/src/toolbar/bar/ToolbarMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
export function ToolbarMenu({ children }: { children: React.ReactNode }): JSX.Element {
return <div className="w-full h-full flex flex-col overflow-hidden">{children}</div>
import clsx from 'clsx'

export type ToolbarMenuProps = {
children: React.ReactNode
className?: string
}

export function ToolbarMenu({ children, className }: ToolbarMenuProps): JSX.Element {
return <div className={clsx('w-full h-full flex flex-col overflow-hidden', className)}>{children}</div>
}

ToolbarMenu.Header = function ToolbarMenuHeader({ children }: { children: React.ReactNode }): JSX.Element {
return <div className="pt-1 px-1">{children}</div>
ToolbarMenu.Header = function ToolbarMenuHeader({ children, className }: ToolbarMenuProps): JSX.Element {
return <div className={clsx('pt-1 px-1', className)}>{children}</div>
}

ToolbarMenu.Body = function ToolbarMenuBody({ children }: { children: React.ReactNode }): JSX.Element {
return <div className="flex flex-col flex-1 h-full overflow-y-auto px-1 min-h-20">{children}</div>
ToolbarMenu.Body = function ToolbarMenuBody({ children, className }: ToolbarMenuProps): JSX.Element {
return (
<div className={clsx('flex flex-col flex-1 h-full overflow-y-auto px-1 min-h-20', className)}>{children}</div>
)
}

ToolbarMenu.Footer = function ToolbarMenufooter({ children }: { children: React.ReactNode }): JSX.Element {
return <div className="flex flex-row items-center p-2 border-t">{children}</div>
ToolbarMenu.Footer = function ToolbarMenufooter({ children, className }: ToolbarMenuProps): JSX.Element {
return <div className={clsx('flex flex-row items-center p-2 border-t gap-2', className)}>{children}</div>
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { ElementRect } from '~/toolbar/types'

interface HeatmapElementProps {
interface AutocaptureElementProps {
rect?: ElementRect
style: Record<string, any>
onClick: (event: React.MouseEvent) => void
onMouseOver: (event: React.MouseEvent) => void
onMouseOut: (event: React.MouseEvent) => void
}

export function HeatmapElement({
export function AutocaptureElement({
rect,
style = {},
onClick,
onMouseOver,
onMouseOut,
}: HeatmapElementProps): JSX.Element | null {
}: AutocaptureElementProps): JSX.Element | null {
if (!rect) {
return null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ const heatmapLabelStyle = {
fontFamily: 'monospace',
}

interface HeatmapLabelProps extends React.PropsWithoutRef<JSX.IntrinsicElements['div']> {
interface AutocaptureElementLabelProps extends React.PropsWithoutRef<JSX.IntrinsicElements['div']> {
rect?: ElementRect
align?: 'left' | 'right'
}

export function HeatmapLabel({
export function AutocaptureElementLabel({
rect,
style = {},
align = 'right',
children,
...props
}: HeatmapLabelProps): JSX.Element | null {
}: AutocaptureElementLabelProps): JSX.Element | null {
if (!rect) {
return null
}
Expand Down
13 changes: 10 additions & 3 deletions frontend/src/toolbar/elements/ElementInfoWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ export function ElementInfoWindow(): JSX.Element | null {
transition: 'opacity 0.2s, box-shadow 0.2s',
backgroundBlendMode: 'multiply',
background: 'white',
boxShadow: `hsla(4, 30%, 27%, 0.6) 0px 3px 10px 2px`,
}}
>
{onClose ? (
Expand Down Expand Up @@ -111,8 +110,16 @@ export function ElementInfoWindow(): JSX.Element | null {
<IconX />
</div>
) : null}
{/* eslint-disable-next-line react/forbid-dom-props */}
<div style={{ minHeight, maxHeight, overflow: 'auto' }}>
<div
// eslint-disable-next-line react/forbid-dom-props
style={{
minHeight,
maxHeight,
overflow: 'auto',
boxShadow: `hsla(4, 30%, 27%, 0.6) 0px 3px 10px 2px`,
borderRadius: '8px',
}}
>
<ElementInfo />
</div>
</div>
Expand Down
31 changes: 19 additions & 12 deletions frontend/src/toolbar/elements/Elements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import { useActions, useValues } from 'kea'
import { compactNumber } from 'lib/utils'
import { Fragment } from 'react'

import { AutocaptureElement } from '~/toolbar/elements/AutocaptureElement'
import { AutocaptureElementLabel } from '~/toolbar/elements/AutocaptureElementLabel'
import { ElementInfoWindow } from '~/toolbar/elements/ElementInfoWindow'
import { elementsLogic } from '~/toolbar/elements/elementsLogic'
import { FocusRect } from '~/toolbar/elements/FocusRect'
import { HeatmapElement } from '~/toolbar/elements/HeatmapElement'
import { HeatmapLabel } from '~/toolbar/elements/HeatmapLabel'
import { heatmapLogic } from '~/toolbar/elements/heatmapLogic'
import { getBoxColors, getHeatMapHue } from '~/toolbar/utils'

import { Heatmap } from './Heatmap'
import { ScrollDepth } from './ScrollDepth'

export function Elements(): JSX.Element {
const {
heatmapElements,
Expand Down Expand Up @@ -48,23 +51,26 @@ export function Elements(): JSX.Element {
zIndex: 2147483010,
}}
>
<ScrollDepth />
<Heatmap />
{highlightElementMeta?.rect ? <FocusRect rect={highlightElementMeta.rect} /> : null}

{elementsToDisplay.map(({ rect, element }, index) => (
<HeatmapElement
<AutocaptureElement
key={`inspect-${index}`}
rect={rect}
style={{
pointerEvents: heatmapPointerEvents,
cursor: 'pointer',
zIndex: 0,
zIndex: hoverElement === element ? 2 : 1,
opacity:
(!hoverElement && !selectedElement) ||
selectedElement === element ||
hoverElement === element
? 1
: 0.4,
transition: 'opacity 0.2s, box-shadow 0.2s',
borderRadius: 5,
...getBoxColors('blue', hoverElement === element || selectedElement === element),
}}
onClick={() => selectElement(element)}
Expand All @@ -76,14 +82,15 @@ export function Elements(): JSX.Element {
{heatmapElements.map(({ rect, count, clickCount, rageclickCount, element }, index) => {
return (
<Fragment key={`heatmap-${index}`}>
<HeatmapElement
<AutocaptureElement
rect={rect}
style={{
pointerEvents: inspectEnabled ? 'none' : heatmapPointerEvents,
zIndex: 1,
zIndex: hoverElement === element ? 4 : 3,
opacity: !hoverElement || hoverElement === element ? 1 : 0.4,
transition: 'opacity 0.2s, box-shadow 0.2s',
cursor: 'pointer',
borderRadius: 5,
...getBoxColors(
'red',
hoverElement === element,
Expand All @@ -95,7 +102,7 @@ export function Elements(): JSX.Element {
onMouseOut={() => selectedElement === null && setHoverElement(null)}
/>
{!!clickCount && (
<HeatmapLabel
<AutocaptureElementLabel
rect={rect}
style={{
pointerEvents: heatmapPointerEvents,
Expand All @@ -122,10 +129,10 @@ export function Elements(): JSX.Element {
onMouseOut={() => selectedElement === null && setHoverElement(null)}
>
{compactNumber(clickCount || 0)}
</HeatmapLabel>
</AutocaptureElementLabel>
)}
{!!rageclickCount && (
<HeatmapLabel
<AutocaptureElementLabel
rect={rect}
style={{
pointerEvents: heatmapPointerEvents,
Expand Down Expand Up @@ -153,7 +160,7 @@ export function Elements(): JSX.Element {
onMouseOut={() => selectedElement === null && setHoverElement(null)}
>
{compactNumber(rageclickCount)}&#128545;
</HeatmapLabel>
</AutocaptureElementLabel>
)}
</Fragment>
)
Expand All @@ -162,7 +169,7 @@ export function Elements(): JSX.Element {
{labelsToDisplay.map(({ element, rect, index }, loopIndex) => {
if (rect) {
return (
<HeatmapLabel
<AutocaptureElementLabel
key={`label-${loopIndex}`}
rect={rect}
align="left"
Expand All @@ -182,7 +189,7 @@ export function Elements(): JSX.Element {
onMouseOut={() => selectedElement === null && setHoverElement(null)}
>
{(index || loopIndex) + 1}
</HeatmapLabel>
</AutocaptureElementLabel>
)
}
})}
Expand Down
Loading

0 comments on commit c0b3406

Please sign in to comment.