Skip to content

Commit

Permalink
feat: recordings ordering (#24794)
Browse files Browse the repository at this point in the history
  • Loading branch information
daibhin authored Sep 10, 2024
1 parent c9bf238 commit f855d23
Show file tree
Hide file tree
Showing 19 changed files with 1,103 additions and 889 deletions.

Large diffs are not rendered by default.

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.
12 changes: 12 additions & 0 deletions frontend/src/queries/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -9310,6 +9310,18 @@
{
"const": "console_error_count",
"type": "string"
},
{
"const": "click_count",
"type": "string"
},
{
"const": "keypress_count",
"type": "string"
},
{
"const": "mouse_activity_count",
"type": "string"
}
]
},
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/queries/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,13 @@ 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'
limit?: integer
offset?: integer
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { LemonSelect } from '@posthog/lemon-ui'
import clsx from 'clsx'
import { useActions, useMountedLogic, useValues } from 'kea'
import { DateFilter } from 'lib/components/DateFilter/DateFilter'
Expand All @@ -13,6 +14,7 @@ import { cohortsModel } from '~/models/cohortsModel'
import { AndOrFilterSelect } from '~/queries/nodes/InsightViz/PropertyGroupFilters/AndOrFilterSelect'
import { RecordingUniversalFilters } from '~/types'

import { sessionRecordingsPlaylistLogic } from '../playlist/sessionRecordingsPlaylistLogic'
import { DurationFilter } from './DurationFilter'

export const RecordingsUniversalFilters = ({
Expand All @@ -27,12 +29,15 @@ export const RecordingsUniversalFilters = ({
useMountedLogic(cohortsModel)
useMountedLogic(actionsModel)

const { orderBy } = useValues(sessionRecordingsPlaylistLogic)
const { setOrderBy } = useActions(sessionRecordingsPlaylistLogic)

const durationFilter = filters.duration[0]

return (
<div className={clsx('divide-y bg-bg-light rounded', className)}>
<div className="flex justify-between px-2 py-1.5 flex-wrap gap-1">
<div className="flex flex-wrap gap-2">
<div className="flex flex-wrap gap-2 items-center">
<div className="flex items-center">
<AndOrFilterSelect
value={filters.filter_group.type}
Expand Down Expand Up @@ -87,6 +92,56 @@ export const RecordingsUniversalFilters = ({
durationTypeFilter={durationFilter.key}
pageKey="session-recordings"
/>
<span className="font-medium">sorted 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: 'Key presses',
},
{
value: 'mouse_activity_count',
label: 'Mouse activity',
},
],
},
{
value: 'console_error_count',
label: 'Most errors',
},
]}
size="small"
value={orderBy}
onChange={setOrderBy}
/>
</div>
<div>
<TestAccountFilter
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
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,11 @@
import { 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 { orderBy } = useValues(sessionRecordingsPlaylistLogic)
const { hideViewedRecordings } = useValues(playerSettingsLogic)
const { setHideViewedRecordings } = useActions(playerSettingsLogic)

return (
<div className="relative flex flex-col gap-2 p-3 border-b">
Expand All @@ -20,16 +17,6 @@ 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>
)
}
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}`],
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

0 comments on commit f855d23

Please sign in to comment.