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: recordings ordering #24794

Merged
merged 58 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
fd6ea28
include ordering clause
daibhin Sep 3, 2024
5183a11
remove durationToShow and rely on ordering
daibhin Sep 3, 2024
77a7ceb
remove earliest and correct order when merging
daibhin Sep 3, 2024
cc482df
cleanup function
daibhin Sep 4, 2024
108d492
Merge branch 'master' into dn-feat/replay-filtering
daibhin Sep 4, 2024
91653a7
inline header actions
daibhin Sep 4, 2024
874d3cc
incomparible filters
daibhin Sep 4, 2024
f34c534
Merge branch 'master' into dn-feat/replay-filtering
daibhin Sep 4, 2024
6e92bf6
add test
daibhin Sep 4, 2024
10c4a74
Merge branch 'master' into dn-feat/replay-filtering
daibhin Sep 4, 2024
841d907
feat: random sample recordings order
daibhin Sep 4, 2024
e5f62bb
Merge branch 'master' into dn-feat/replay-filtering
daibhin Sep 5, 2024
635ef6f
rename latest to start_time
daibhin Sep 5, 2024
dd6baf2
much cleaner tests
daibhin Sep 5, 2024
5629032
fix name
daibhin Sep 5, 2024
971b590
remove comment
daibhin Sep 5, 2024
e79b621
Update query snapshots
github-actions[bot] Sep 5, 2024
16c9a56
Update query snapshots
github-actions[bot] Sep 5, 2024
93c56af
Update query snapshots
github-actions[bot] Sep 5, 2024
347e0ea
Update query snapshots
github-actions[bot] Sep 5, 2024
43c5e2b
Update query snapshots
github-actions[bot] Sep 5, 2024
baea08a
Update query snapshots
github-actions[bot] Sep 5, 2024
a9ff478
Update query snapshots
github-actions[bot] Sep 5, 2024
2391d38
Update query snapshots
github-actions[bot] Sep 5, 2024
0f25271
Update query snapshots
github-actions[bot] Sep 5, 2024
04cc08b
Update query snapshots
github-actions[bot] Sep 5, 2024
34dcb3c
Update query snapshots
github-actions[bot] Sep 5, 2024
434c5b3
Update query snapshots
github-actions[bot] Sep 5, 2024
6965f88
Merge branch 'master' into dn-feat/replay-filtering
daibhin Sep 5, 2024
3b363aa
random sample toggle
daibhin Sep 5, 2024
66e3c98
Update query snapshots
github-actions[bot] Sep 5, 2024
03ccf64
Update query snapshots
github-actions[bot] Sep 5, 2024
947bbfb
Update query snapshots
github-actions[bot] Sep 5, 2024
45cb534
Update query snapshots
github-actions[bot] Sep 5, 2024
0ff53f8
Update query snapshots
github-actions[bot] Sep 5, 2024
f2b86a9
Update query snapshots
github-actions[bot] Sep 5, 2024
7560042
Update query snapshots
github-actions[bot] Sep 5, 2024
cc32efd
make table qualifier optional when replacing session id in snapshots
pauldambra Sep 5, 2024
d885e8a
Update query snapshots
github-actions[bot] Sep 5, 2024
288488e
Update query snapshots
github-actions[bot] Sep 5, 2024
437d1e0
Fangle types
pauldambra Sep 5, 2024
b8cfa22
Merge branch 'master' into dn-feat/replay-filtering
daibhin Sep 6, 2024
dbd6b65
remove random sampling
daibhin Sep 6, 2024
8287d70
Update query snapshots
github-actions[bot] Sep 6, 2024
5fa2c25
move ordering
daibhin Sep 6, 2024
b8dae38
Merge branch 'dn-feat/replay-filtering' of github.com:PostHog/posthog…
daibhin Sep 6, 2024
bf04127
update snapshots
daibhin Sep 6, 2024
bcf3aee
Update query snapshots
github-actions[bot] Sep 6, 2024
1971815
Update UI snapshots for `chromium` (1)
github-actions[bot] Sep 6, 2024
c0db65b
Update UI snapshots for `chromium` (2)
github-actions[bot] Sep 6, 2024
c920e67
Merge branch 'master' into dn-feat/replay-filtering
daibhin Sep 9, 2024
4c4470b
Update UI snapshots for `chromium` (1)
github-actions[bot] Sep 9, 2024
2452f83
Merge branch 'master' into dn-feat/replay-filtering
daibhin Sep 9, 2024
b637a33
reset snapshots
daibhin Sep 9, 2024
aabb1eb
Update UI snapshots for `chromium` (1)
github-actions[bot] Sep 9, 2024
ed64528
Update UI snapshots for `chromium` (2)
github-actions[bot] Sep 9, 2024
47a93f1
Merge branch 'master' into dn-feat/replay-filtering
daibhin Sep 9, 2024
d79ae5c
Update UI snapshots for `chromium` (1)
github-actions[bot] Sep 9, 2024
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

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions frontend/src/queries/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -8852,6 +8852,22 @@
{
"const": "console_error_count",
"type": "string"
},
{
"const": "click_count",
"type": "string"
},
{
"const": "keypress_count",
"type": "string"
},
{
"const": "mouse_activity_count",
"type": "string"
},
{
"const": "random_sample",
"type": "string"
}
]
},
Expand Down
9 changes: 8 additions & 1 deletion frontend/src/queries/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,14 @@ export interface RecordingsQuery extends DataNode<RecordingsQueryResponse> {
operand?: FilterLogicalOperator
session_ids?: string[]
person_uuid?: string
order: DurationType | 'start_time' | 'console_error_count'
order:
| DurationType
| 'start_time'
| 'console_error_count'
| 'click_count'
| 'keypress_count'
| 'mouse_activity_count'
| 'random_sample'
limit?: integer
offset?: integer
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { actions, connect, kea, listeners, path, reducers, selectors } from 'kea
import { eventUsageLogic } from 'lib/utils/eventUsageLogic'
import { teamLogic } from 'scenes/teamLogic'

import { AutoplayDirection, DurationType, SessionRecordingPlayerTab } from '~/types'
import { AutoplayDirection, SessionRecordingPlayerTab } from '~/types'

import type { playerSettingsLogicType } from './playerSettingsLogicType'

Expand Down Expand Up @@ -197,7 +197,6 @@ export const playerSettingsLogic = kea<playerSettingsLogicType>([
setTab: (tab: SessionRecordingPlayerTab) => ({ tab }),
setMiniFilter: (key: string, enabled: boolean) => ({ key, enabled }),
setSearchQuery: (search: string) => ({ search }),
setDurationTypeToShow: (type: DurationType) => ({ type }),
setShowFilters: (showFilters: boolean) => ({ showFilters }),
setQuickFilterProperties: (properties: string[]) => ({ properties }),
setTimestampFormat: (format: TimestampFormat) => ({ format }),
Expand Down Expand Up @@ -242,13 +241,6 @@ export const playerSettingsLogic = kea<playerSettingsLogicType>([
setQuickFilterProperties: (_, { properties }) => properties,
},
],
durationTypeToShow: [
'duration' as DurationType,
{ persist: true },
{
setDurationTypeToShow: (_, { type }) => type,
},
],
speed: [
1,
{ persist: true },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import { useState } from 'react'
import { countryCodeToName } from 'scenes/insights/views/WorldMap'
import { DraggableToNotebook } from 'scenes/notebooks/AddToNotebook/DraggableToNotebook'
import { asDisplay } from 'scenes/persons/person-utils'
import { playerSettingsLogic } from 'scenes/session-recordings/player/playerSettingsLogic'
import { urls } from 'scenes/urls'

import { DurationType, SessionRecordingType } from '~/types'
import { RecordingsQuery } from '~/queries/schema'
import { SessionRecordingType } from '~/types'

import { sessionRecordingsListPropertiesLogic } from './sessionRecordingsListPropertiesLogic'
import { sessionRecordingsPlaylistLogic } from './sessionRecordingsPlaylistLogic'
Expand Down Expand Up @@ -161,12 +161,12 @@ function ViewedIndicator(): JSX.Element {
)
}

function durationToShow(recording: SessionRecordingType, durationType: DurationType | undefined): number | undefined {
return {
duration: recording.recording_duration,
active_seconds: recording.active_seconds,
inactive_seconds: recording.inactive_seconds,
}[durationType || 'duration']
function durationToShow(recording: SessionRecordingType, order: RecordingsQuery['order']): number | undefined {
return order === 'active_seconds'
? recording.active_seconds
: order === 'inactive_seconds'
? recording.inactive_seconds
: recording.recording_duration
}

export function SessionRecordingPreview({
Expand All @@ -178,7 +178,6 @@ export function SessionRecordingPreview({
sessionSummaryLoading,
}: SessionRecordingPreviewProps): JSX.Element {
const { orderBy } = useValues(sessionRecordingsPlaylistLogic)
const { durationTypeToShow } = useValues(playerSettingsLogic)

const { recordingPropertiesById, recordingPropertiesLoading } = useValues(sessionRecordingsListPropertiesLogic)
const recordingProperties = recordingPropertiesById[recording.id]
Expand Down Expand Up @@ -278,12 +277,7 @@ export function SessionRecordingPreview({
{orderBy === 'console_error_count' ? (
<ErrorCount iconClassNames={iconClassNames} errorCount={recording.console_error_count} />
) : (
<RecordingDuration
recordingDuration={durationToShow(
recording,
orderBy === 'start_time' ? durationTypeToShow : orderBy
)}
/>
<RecordingDuration recordingDuration={durationToShow(recording, orderBy)} />
)}
</div>

Expand Down
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commenting here so we can thread if need be

high quality photoshop warning

image

should it be here (but not beige)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh... and we have a random sample toggle, and a random ordering in the menu... so i can choose random random 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hesitant to put something on that level in case it ends up getting muddled with a large amount of filters. Going to go with something like this instead:

Screenshot 2024-09-06 at 11 35 47

Experimented with an even more natural language based approach but I don't think it quite works because of our button paddings

natural_language

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙌

Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,11 @@ export function SessionRecordingsPlaylist(props: SessionRecordingPlaylistLogicPr
const notebookNode = useNotebookNode()

const sections: PlaylistSection<SessionRecordingType>[] = []
const headerActions = []

const onSummarizeClick = (recording: SessionRecordingType): void => {
summarizeSession(recording.id)
}

headerActions.push({
key: 'settings',
tooltip: 'Playlist settings',
content: <SessionRecordingsPlaylistSettings />,
icon: <IconGear />,
})

if (pinnedRecordings.length) {
sections.push({
key: 'pinned',
Expand Down Expand Up @@ -116,7 +108,14 @@ export function SessionRecordingsPlaylist(props: SessionRecordingPlaylistLogicPr
title="Recordings"
embedded={!!notebookNode}
sections={sections}
headerActions={headerActions}
headerActions={[
{
key: 'settings',
tooltip: 'Playlist settings',
content: <SessionRecordingsPlaylistSettings />,
icon: <IconGear />,
},
]}
loading={sessionRecordingsResponseLoading}
onScrollListEdge={(edge) => {
if (edge === 'top') {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { LemonSwitch } from '@posthog/lemon-ui'
import { LemonSelect, LemonSwitch } from '@posthog/lemon-ui'
import { useActions, useValues } from 'kea'
import { DurationTypeSelect } from 'scenes/session-recordings/filters/DurationTypeSelect'

import { playerSettingsLogic } from '../player/playerSettingsLogic'
import { sessionRecordingsPlaylistLogic } from './sessionRecordingsPlaylistLogic'

export function SessionRecordingsPlaylistSettings(): JSX.Element {
const { durationTypeToShow, hideViewedRecordings } = useValues(playerSettingsLogic)
const { setDurationTypeToShow, setHideViewedRecordings } = useActions(playerSettingsLogic)
const { hideViewedRecordings } = useValues(playerSettingsLogic)
const { setHideViewedRecordings } = useActions(playerSettingsLogic)
const { orderBy } = useValues(sessionRecordingsPlaylistLogic)
const { setOrderBy } = useActions(sessionRecordingsPlaylistLogic)

return (
<div className="relative flex flex-col gap-2 p-3 border-b">
Expand All @@ -20,16 +20,62 @@ export function SessionRecordingsPlaylistSettings(): JSX.Element {
onChange={() => setHideViewedRecordings(!hideViewedRecordings)}
/>
</div>
{orderBy === 'start_time' && (
<div className="flex flex-row items-center justify-between space-x-2">
<span className="text-black font-medium">Show</span>
<DurationTypeSelect
value={durationTypeToShow}
onChange={(value) => setDurationTypeToShow(value)}
onChangeEventDescription="session recording list duration type to show selected"
/>
</div>
)}
<div className="flex flex-row items-center justify-between space-x-2">
<span className="text-black font-medium">Order by</span>
<LemonSelect
options={[
{
value: 'start_time',
label: 'Latest',
},
{
label: 'Longest',
options: [
{
value: 'duration',
label: 'Total duration',
},
{
value: 'active_seconds',
label: 'Active duration',
},
{
value: 'inactive_seconds',
label: 'Inactive duration',
},
],
},
{
label: 'Most active',
options: [
{
value: 'click_count',
label: 'Clicks',
},
{
value: 'keypress_count',
label: 'Keypresses',
},
{
value: 'mouse_activity_count',
label: 'Mouse activity',
},
],
},
{
value: 'console_error_count',
label: 'Most errors',
},
{
value: 'random_sample',
label: 'Random sample',
},
]}
size="small"
value={orderBy}
onChange={setOrderBy}
/>
</div>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,28 @@ import {

describe('sessionRecordingsPlaylistLogic', () => {
let logic: ReturnType<typeof sessionRecordingsPlaylistLogic.build>
const aRecording = { id: 'abc', viewed: false, recording_duration: 10, console_error_count: 50 }
const bRecording = { id: 'def', viewed: false, recording_duration: 10, console_error_count: 100 }
const aRecording = {
id: 'abc',
viewed: false,
recording_duration: 10,
start_time: '2023-10-12T16:55:36.404000Z',
console_error_count: 50,
}
const bRecording = {
id: 'def',
viewed: false,
recording_duration: 10,
start_time: '2023-05-12T16:55:36.404000Z',
console_error_count: 100,
}
const listOfSessionRecordings = [aRecording, bRecording]
const offsetRecording = {
id: `recording_offset_by_${listOfSessionRecordings.length}`,
viewed: false,
recording_duration: 10,
start_time: '2023-08-12T16:55:36.404000Z',
console_error_count: 75,
}

beforeEach(() => {
useMocks({
Expand Down Expand Up @@ -54,7 +73,7 @@ describe('sessionRecordingsPlaylistLogic', () => {
return [
200,
{
results: [`List of recordings offset by ${listOfSessionRecordings.length}`],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sortRecordings function did not like that this was not an object

results: [offsetRecording],
},
]
} else if (
Expand Down Expand Up @@ -167,6 +186,11 @@ describe('sessionRecordingsPlaylistLogic', () => {
})

describe('ordering', () => {
afterEach(() => {
logic.actions.setOrderBy('start_time')
logic.actions.loadSessionRecordings()
})

it('is set by setOrderBy, loads filtered results and orders the non pinned recordings', async () => {
await expectLogic(logic, () => {
logic.actions.setOrderBy('console_error_count')
Expand All @@ -179,21 +203,22 @@ describe('sessionRecordingsPlaylistLogic', () => {
expect(logic.values.otherRecordings.map((r) => r.console_error_count)).toEqual([100, 50])
})

it('adds an offset when not using latest ordering', async () => {
it('adds an offset', async () => {
await expectLogic(logic, () => {
logic.actions.setOrderBy('console_error_count')
logic.actions.loadSessionRecordings()
})
.toDispatchActionsInAnyOrder(['loadSessionRecordingsSuccess'])
.toDispatchActions(['loadSessionRecordingsSuccess'])
.toMatchValues({
sessionRecordings: listOfSessionRecordings,
})

await expectLogic(logic, () => {
logic.actions.maybeLoadSessionRecordings('newer')
logic.actions.loadSessionRecordings('older')
})
.toDispatchActions(['loadSessionRecordingsSuccess'])
.toMatchValues({
sessionRecordings: [...listOfSessionRecordings, 'List of recordings offset by 2'],
// reorganises recordings based on start_time
sessionRecordings: [aRecording, offsetRecording, bRecording],
})
})
})
Expand Down Expand Up @@ -306,6 +331,7 @@ describe('sessionRecordingsPlaylistLogic', () => {
expect(router.values.searchParams.filters).toHaveProperty('date_to', '2021-10-20')
})
})

describe('duration filter', () => {
it('is set by setFilters and fetches results from server and sets the url', async () => {
await expectLogic(logic, () => {
Expand Down Expand Up @@ -370,8 +396,9 @@ describe('sessionRecordingsPlaylistLogic', () => {
.toFinishAllListeners()
.toMatchValues({
sessionRecordingsResponse: {
results: listOfSessionRecordings,
order: 'start_time',
has_next: undefined,
results: listOfSessionRecordings,
},
sessionRecordings: listOfSessionRecordings,
})
Expand All @@ -382,6 +409,8 @@ describe('sessionRecordingsPlaylistLogic', () => {
.toFinishAllListeners()
.toMatchValues({
sessionRecordingsResponse: {
has_next: undefined,
order: 'start_time',
results: [
{
...aRecording,
Expand Down
Loading
Loading