Skip to content

Commit

Permalink
feat: prefer event properties to person properties when describing se…
Browse files Browse the repository at this point in the history
…ssions (#27208)

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
pauldambra and github-actions[bot] authored Jan 3, 2025
1 parent bcc474f commit 26239e7
Show file tree
Hide file tree
Showing 35 changed files with 183 additions and 120 deletions.
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.
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.
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.
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.
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.
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.
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.
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 12 additions & 1 deletion frontend/src/scenes/persons/PersonDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import './PersonDisplay.scss'

import { IconCopy } from '@posthog/icons'
import clsx from 'clsx'
import { router } from 'kea-router'
import { Link } from 'lib/lemon-ui/Link'
import { Popover } from 'lib/lemon-ui/Popover'
import { ProfilePicture, ProfilePictureProps } from 'lib/lemon-ui/ProfilePicture'
import { copyToClipboard } from 'lib/utils/copyToClipboard'
import React, { useMemo, useState } from 'react'
import { useNotebookNode } from 'scenes/notebooks/Nodes/NotebookNodeContext'

Expand All @@ -24,6 +26,7 @@ export interface PersonDisplayProps {
noPopover?: boolean
isCentered?: boolean
children?: React.ReactChild
withCopyButton?: boolean
}

export function PersonIcon({
Expand Down Expand Up @@ -60,6 +63,7 @@ export function PersonDisplay({
isCentered,
href = asLink(person),
children,
withCopyButton,
}: PersonDisplayProps): JSX.Element {
const display = asDisplay(person)
const [visible, setVisible] = useState(false)
Expand Down Expand Up @@ -125,7 +129,14 @@ export function PersonDisplay({
fallbackPlacements={['bottom', 'right']}
showArrow
>
{content}
{withCopyButton ? (
<div className="flex flex-row items-center justify-between">
{content}
<IconCopy className="text-lg cursor-pointer" onClick={() => void copyToClipboard(display)} />
</div>
) : (
<span>{content}</span>
)}
</Popover>
)

Expand Down
19 changes: 11 additions & 8 deletions frontend/src/scenes/session-recordings/components/OverviewGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Tooltip } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { ReactNode } from 'react'

interface OverviewItemBase {
Expand All @@ -23,7 +24,7 @@ export type OverviewItem = TextOverviewItem | PropertyOverviewItem
export function OverviewGrid({ children }: { children: ReactNode }): JSX.Element {
return (
<div className="@container/og">
<div className="grid grid-cols-1 place-items-center gap-4 p-2 @xs/og:grid-cols-2 @md/og:grid-cols-3 ">
<div className="grid grid-cols-1 place-items-center gap-4 px-2 py-1 @xs/og:grid-cols-2 @md/og:grid-cols-3 ">
{children}
</div>
</div>
Expand All @@ -35,20 +36,22 @@ export function OverviewGridItem({
description,
label,
icon,
fadeLabel,
}: {
children?: ReactNode
description: ReactNode
label: ReactNode
icon?: ReactNode
fadeLabel?: boolean
}): JSX.Element {
return (
<Tooltip title={description}>
<div className="flex flex-1 w-full justify-between items-center ">
<div className="text-sm">
{icon} {label}
</div>
<div>{children}</div>
<div className="flex flex-1 w-full justify-between items-center ">
<div className={clsx('text-sm', fadeLabel && 'font-light')}>
{icon} {label}
</div>
</Tooltip>
<Tooltip title={description}>
<div>{children}</div>
</Tooltip>
</div>
)
}
76 changes: 49 additions & 27 deletions frontend/src/scenes/session-recordings/player/playerMetaLogic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import api from 'lib/api'
import { TaxonomicFilterGroupType } from 'lib/components/TaxonomicFilter/types'
import { lemonToast } from 'lib/lemon-ui/LemonToast'
import { getCoreFilterDefinition } from 'lib/taxonomy'
import { ceilMsToClosestSecond, findLastIndex, objectsEqual } from 'lib/utils'
import { ceilMsToClosestSecond, findLastIndex, humanFriendlyDuration, objectsEqual } from 'lib/utils'
import posthog from 'posthog-js'
import { countryCodeToName } from 'scenes/insights/views/WorldMap'
import { OverviewItem } from 'scenes/session-recordings/components/OverviewGrid'
Expand All @@ -22,14 +22,32 @@ import { SimpleTimeLabel } from '../components/SimpleTimeLabel'
import { sessionRecordingsListPropertiesLogic } from '../playlist/sessionRecordingsListPropertiesLogic'
import type { playerMetaLogicType } from './playerMetaLogicType'

const browserPropertyKeys = ['$geoip_country_code', '$browser', '$device_type', '$os']
const browserPropertyKeys = ['$geoip_country_code', '$browser', '$device_type', '$os', '$referring_domain']
const mobilePropertyKeys = ['$geoip_country_code', '$device_type', '$os_name']
const recordingPropertyKeys = ['click_count', 'keypress_count', 'console_error_count'] as const

export interface SessionSummaryResponse {
content: string
}

export function countryTitleFrom(
recordingProperties: Record<string, any> | undefined,
personProperties?: Record<string, any> | undefined
): string {
const props = recordingProperties || personProperties
if (!props) {
return ''
}

// these prop names are safe between recording and person properties
// the "initial" person properties share the same name as the event properties
const country = countryCodeToName[props['$geoip_country_code'] as keyof typeof countryCodeToName]
const subdivision = props['$geoip_subdivision_1_name']
const city = props['$geoip_city_name']

return [city, subdivision, country].filter(Boolean).join(', ')
}

export const playerMetaLogic = kea<playerMetaLogicType>([
path((key) => ['scenes', 'session-recordings', 'player', 'playerMetaLogic', key]),
props({} as SessionRecordingPlayerLogicProps),
Expand Down Expand Up @@ -85,6 +103,11 @@ export const playerMetaLogic = kea<playerMetaLogicType>([
},
})),
selectors(() => ({
loading: [
(s) => [s.sessionPlayerMetaDataLoading, s.recordingPropertiesLoading],
(sessionPlayerMetaDataLoading, recordingPropertiesLoading) =>
sessionPlayerMetaDataLoading || recordingPropertiesLoading,
],
sessionPerson: [
(s) => [s.sessionPlayerData],
(playerData): PersonType | null => {
Expand Down Expand Up @@ -182,27 +205,21 @@ export const playerMetaLogic = kea<playerMetaLogicType>([
}
},
],
sessionProperties: [
(s) => [s.sessionPlayerData, s.recordingPropertiesById, (_, props) => props],
(sessionPlayerData, recordingPropertiesById, props) => {
return recordingPropertiesById[props.sessionRecordingId] ?? sessionPlayerData.person?.properties
},
],
overviewItems: [
(s) => [s.sessionPlayerMetaData, s.startTime, s.endTime],
(sessionPlayerMetaData, startTime, endTime) => {
(s) => [s.sessionPlayerMetaData, s.startTime, s.recordingPropertiesById],
(sessionPlayerMetaData, startTime, recordingPropertiesById) => {
const items: OverviewItem[] = []
if (startTime) {
items.push({
label: 'Session start',
label: 'Start',
value: <SimpleTimeLabel muted={false} size="small" isUTC={true} startTime={startTime} />,
type: 'text',
})
}
if (endTime) {
if (sessionPlayerMetaData?.recording_duration) {
items.push({
label: 'Session end',
value: <SimpleTimeLabel muted={false} size="small" isUTC={true} startTime={endTime} />,
label: 'Duration',
value: humanFriendlyDuration(sessionPlayerMetaData.recording_duration),
type: 'text',
})
}
Expand All @@ -226,26 +243,31 @@ export const playerMetaLogic = kea<playerMetaLogicType>([
}
})

const recordingProperties = sessionPlayerMetaData?.id
? recordingPropertiesById[sessionPlayerMetaData?.id] || {}
: {}
const personProperties = sessionPlayerMetaData?.person?.properties ?? {}

const deviceType = personProperties['$device_type'] || personProperties['$initial_device_type']
const deviceType =
recordingProperties['$device_type'] ||
personProperties['$device_type'] ||
personProperties['$initial_device_type']
const deviceTypePropertyKeys = deviceType === 'Mobile' ? mobilePropertyKeys : browserPropertyKeys

deviceTypePropertyKeys.forEach((property) => {
if (personProperties[property]) {
const value = personProperties[property]

const tooltipTitle =
property === '$geoip_country_code' && value in countryCodeToName
? countryCodeToName[value as keyof typeof countryCodeToName]
: value
if (recordingProperties[property] || personProperties[property]) {
const propertyType = recordingProperties[property]
? TaxonomicFilterGroupType.EventProperties
: TaxonomicFilterGroupType.PersonProperties
const value = recordingProperties[property] || personProperties[property]

items.push({
label:
getCoreFilterDefinition(property, TaxonomicFilterGroupType.PersonProperties)?.label ??
property,
label: getCoreFilterDefinition(property, propertyType)?.label ?? property,
value,
tooltipTitle,
tooltipTitle:
property === '$geoip_country_code' && value in countryCodeToName
? countryTitleFrom(recordingProperties, personProperties)
: value,
type: 'property',
property,
})
Expand All @@ -258,7 +280,7 @@ export const playerMetaLogic = kea<playerMetaLogicType>([
})),
listeners(({ actions, values, props }) => ({
loadRecordingMetaSuccess: () => {
if (values.sessionPlayerMetaData && !values.recordingPropertiesLoading) {
if (values.sessionPlayerMetaData) {
actions.maybeLoadPropertiesForSessions([values.sessionPlayerMetaData])
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,42 @@
import { useValues } from 'kea'
import { PropertyIcon } from 'lib/components/PropertyIcon'
import { LemonSkeleton } from 'lib/lemon-ui/LemonSkeleton'

import { OverviewGrid, OverviewGridItem } from '../../components/OverviewGrid'
import { playerMetaLogic } from '../playerMetaLogic'
import { sessionRecordingPlayerLogic } from '../sessionRecordingPlayerLogic'

export function PlayerSidebarOverviewGrid(): JSX.Element {
const { logicProps } = useValues(sessionRecordingPlayerLogic)
const { overviewItems } = useValues(playerMetaLogic(logicProps))

const { overviewItems, loading } = useValues(playerMetaLogic(logicProps))
return (
<div className="rounded border bg-bg-light m-2">
<OverviewGrid>
{overviewItems.map((item) => (
<OverviewGridItem
key={item.label}
description={item.tooltipTitle}
label={item.label}
icon={item.icon}
>
{item.type === 'property' ? (
<PropertyIcon property={item.property} value={item.value} />
) : (
item.value
)}
</OverviewGridItem>
))}
</OverviewGrid>
<div className="rounded border bg-bg-light">
{loading ? (
<div className="flex flex-col space-y-1">
<LemonSkeleton.Row repeat={6} className="h-5" />
</div>
) : (
<OverviewGrid>
{overviewItems.map((item) => {
return (
<OverviewGridItem
key={item.label}
description={item.tooltipTitle}
label={item.label}
icon={item.icon}
fadeLabel
>
<div className="flex flex-row items-center space-x-2 justify-start font-medium">
{item.type === 'property' && (
<PropertyIcon property={item.property} value={item.value} />
)}
<span>{item.value}</span>
</div>
</OverviewGridItem>
)
})}
</OverviewGrid>
)}
</div>
)
}
Original file line number Diff line number Diff line change
@@ -1,47 +1,20 @@
import { PersonDisplay } from '@posthog/apps-common'
import { IconInfo } from '@posthog/icons'
import { useValues } from 'kea'
import { PropertiesTable } from 'lib/components/PropertiesTable'
import { LemonSkeleton } from 'lib/lemon-ui/LemonSkeleton'
import { Tooltip } from 'lib/lemon-ui/Tooltip'
import { PlayerSidebarSessionSummary } from 'scenes/session-recordings/player/sidebar/PlayerSidebarSessionSummary'

import { PropertyDefinitionType } from '~/types'

import { playerMetaLogic } from '../playerMetaLogic'
import { sessionRecordingPlayerLogic } from '../sessionRecordingPlayerLogic'
import { PlayerSidebarOverviewGrid } from './PlayerSidebarOverviewGrid'

export function PlayerSidebarOverviewTab(): JSX.Element {
const { logicProps } = useValues(sessionRecordingPlayerLogic)
const { sessionPerson, sessionPlayerMetaDataLoading } = useValues(playerMetaLogic(logicProps))
const { sessionPerson } = useValues(playerMetaLogic(logicProps))

return (
<div className="flex flex-col overflow-auto bg-bg-3000">
<div className="flex flex-col overflow-auto bg-bg-3000 px-2 py-1 h-full space-y-1">
<PersonDisplay person={sessionPerson} withIcon withCopyButton />
<PlayerSidebarOverviewGrid />
<PlayerSidebarSessionSummary />
<div className="font-bold bg-bg-light px-2 border-b py-3">
<Tooltip title="These are the person properties right now. They might have changed and not match the person properties at the time of recording.">
<h2>
Latest person properties <IconInfo />
</h2>
</Tooltip>
<PersonDisplay person={sessionPerson} withIcon noPopover />
</div>
{sessionPlayerMetaDataLoading ? (
<div className="space-y-2">
<LemonSkeleton.Row repeat={60} fade={true} className="h-3 w-full" />
</div>
) : (
<PropertiesTable
properties={sessionPerson?.properties || []}
type={PropertyDefinitionType.Person}
sortProperties
embedded
filterable
className="mt-4"
/>
)}
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export function PlayerSidebarSessionSummary(): JSX.Element | null {
return (
<>
<FlaggedFeature flag={FEATURE_FLAGS.AI_SESSION_SUMMARY} match={true}>
<div className="rounded border bg-bg-light m-2 px-2 py-1">
<div className="rounded border bg-bg-light px-2 py-1">
<h2>AI Session Summary</h2>
{sessionSummaryLoading ? (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { LemonSkeleton } from 'lib/lemon-ui/LemonSkeleton'
import { Tooltip } from 'lib/lemon-ui/Tooltip'
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
import { colonDelimitedDuration } from 'lib/utils'
import { countryCodeToName } from 'scenes/insights/views/WorldMap'
import { DraggableToNotebook } from 'scenes/notebooks/AddToNotebook/DraggableToNotebook'
import { asDisplay } from 'scenes/persons/person-utils'
import { SimpleTimeLabel } from 'scenes/session-recordings/components/SimpleTimeLabel'
import { countryTitleFrom } from 'scenes/session-recordings/player/playerMetaLogic'
import { playerSettingsLogic, TimestampFormat } from 'scenes/session-recordings/player/playerSettingsLogic'
import { urls } from 'scenes/urls'

Expand Down Expand Up @@ -88,11 +88,8 @@ export function gatherIconProperties(

return iconPropertyKeys
.flatMap((property) => {
let value = iconProperties?.[property]
const label = value
if (property === '$device_type') {
value = iconProperties?.['$device_type'] || iconProperties?.['$initial_device_type']
}
const value = property === '$device_type' ? deviceType : iconProperties[property]
const label = property === '$geoip_country_code' ? countryTitleFrom(iconProperties) : value

return { property, value, label }
})
Expand All @@ -114,10 +111,7 @@ export function PropertyIcons({ recordingProperties, loading, iconClassNames }:
<LemonSkeleton className="w-16 h-3" />
) : (
recordingProperties.map(({ property, value, label }) => (
<Tooltip
key={property}
title={label && property === '$geoip_country_code' ? countryCodeToName[label] : label}
>
<Tooltip key={property} title={label}>
<PropertyIcon className={iconClassNames} property={property} value={value} />
</Tooltip>
))
Expand Down
Loading

0 comments on commit 26239e7

Please sign in to comment.