Skip to content
Draft
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { expectLogic } from 'kea-test-utils'

import { sessionRecordingSavedFiltersLogic } from 'scenes/session-recordings/filters/sessionRecordingSavedFiltersLogic'

import { initKeaTests } from '~/test/init'
import {
AnyPropertyFilter,
FilterLogicalOperator,
PropertyFilterType,
PropertyOperator,
SessionRecordingPlaylistType,
UniversalFiltersGroup,
} from '~/types'

Expand Down Expand Up @@ -377,4 +380,42 @@ describe('universalFiltersLogic', () => {
})
})
})

describe('addGroupFilter with a saved replay filter', () => {
const savedFilter = {
short_id: 'abc123',
name: 'Rage clicks',
filters: { date_from: '-7d' },
} as unknown as SessionRecordingPlaylistType
const savedFiltersGroup = { type: TaxonomicFilterGroupType.ReplaySavedFilters } as TaxonomicFilterGroup

beforeEach(() => {
sessionRecordingSavedFiltersLogic.mount()
})

it('applies the saved filter the Saved filters list hands back', async () => {
await expectLogic(sessionRecordingSavedFiltersLogic, () => {
logic.actions.addGroupFilter(savedFiltersGroup, 'abc123', savedFilter)
}).toDispatchActions([
sessionRecordingSavedFiltersLogic.actionCreators.requestApplySavedFilter(savedFilter),
])
})

it('asks for a Recent row by short id, because the row holds no filters', async () => {
const recentRow = {
name: 'Rage clicks',
_recentContext: {
sourceGroupType: TaxonomicFilterGroupType.ReplaySavedFilters,
sourceGroupName: 'Saved filters',
sourceValue: 'abc123',
},
}

await expectLogic(sessionRecordingSavedFiltersLogic, () => {
logic.actions.addGroupFilter(savedFiltersGroup, 'abc123', recentRow)
}).toDispatchActions([
sessionRecordingSavedFiltersLogic.actionCreators.requestApplySavedFilterByShortId('abc123'),
])
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@
} from '../TaxonomicFilter/types'
import { DEFAULT_UNIVERSAL_GROUP_FILTER } from './constants'

function isApplicableSavedFilter(
item: unknown
): item is SessionRecordingPlaylistType & { filters: NonNullable<SessionRecordingPlaylistType['filters']> } {
type ApplicableSavedFilter = SessionRecordingPlaylistType & {
filters: NonNullable<SessionRecordingPlaylistType['filters']>
}

function isApplicableSavedFilter(item: unknown): item is ApplicableSavedFilter {
return typeof item === 'object' && item !== null && 'short_id' in item && 'filters' in item && item.filters != null
}

Expand Down Expand Up @@ -258,10 +260,16 @@
},
removeGroupValue: () => props.onChange(values.filterGroup),

addGroupFilter: ({ taxonomicGroup, propertyKey, item }) => {

Check warning on line 263 in frontend/src/lib/components/UniversalFilters/universalFiltersLogic.ts

View workflow job for this annotation

GitHub Actions / Frontend formatting

lint:complexity

`addGroupFilter` has cyclomatic complexity 28 (warn >10)
if (taxonomicGroup.type === TaxonomicFilterGroupType.ReplaySavedFilters) {
const savedFiltersLogic = sessionRecordingSavedFiltersLogic.findMounted()
if (isApplicableSavedFilter(item)) {
sessionRecordingSavedFiltersLogic.findMounted()?.actions.requestApplySavedFilter(item)
savedFiltersLogic?.actions.requestApplySavedFilter(item)
} else if (propertyKey) {
// A row from the Recent category is a stored summary of a saved filter: it holds
// the short id the row is keyed by and never the filters, which are too heavy to
// keep in local storage. The replay logic resolves the short id back to a filter.
savedFiltersLogic?.actions.requestApplySavedFilterByShortId(String(propertyKey))
}
return
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { router } from 'kea-router'
import { expectLogic } from 'kea-test-utils'

import { lemonToast } from 'lib/lemon-ui/LemonToast/LemonToast'
import { removeProjectIdIfPresent } from 'lib/utils/kea-router'
import { sessionRecordingSavedFiltersLogic } from 'scenes/session-recordings/filters/sessionRecordingSavedFiltersLogic'
import { playlistFiltersLogic } from 'scenes/session-recordings/playlist/playlistFiltersLogic'
Expand All @@ -13,6 +14,7 @@ import { ReplayTabs } from '~/types'
describe('sessionRecordingSavedFiltersLogic', () => {
let logic: ReturnType<typeof sessionRecordingSavedFiltersLogic.build>
let savedFiltersRequestCount: number
let savedFilterRequestCount: number
const savedFilter = {
id: 'abc',
short_id: 'short_abc',
Expand All @@ -23,13 +25,17 @@ describe('sessionRecordingSavedFiltersLogic', () => {

beforeEach(() => {
savedFiltersRequestCount = 0
savedFilterRequestCount = 0
useMocks({
get: {
'/api/projects/:team/session_recording_playlists': () => {
savedFiltersRequestCount += 1
return { results: [], count: 0 }
},
'/api/projects/:team/session_recording_playlists/:id': savedFilter,
'/api/projects/:team/session_recording_playlists/:id': () => {
savedFilterRequestCount += 1
return savedFilter
},
},
})
initKeaTests()
Expand Down Expand Up @@ -87,6 +93,57 @@ describe('sessionRecordingSavedFiltersLogic', () => {
expect(removeProjectIdIfPresent(router.values.location.pathname)).toBe(urls.replay())
})

describe('requestApplySavedFilterByShortId', () => {
it('applies a saved filter the loaded page holds, without fetching it again', async () => {
useMocks({
get: {
'/api/projects/:team/session_recording_playlists': () => ({
results: [savedFilter],
count: 1,
}),
},
})
logic.mount()
logic.actions.loadSavedFilters()
await expectLogic(logic).toFinishAllListeners()

await expectLogic(logic, () => {
logic.actions.requestApplySavedFilterByShortId(savedFilter.short_id)
})
.toFinishAllListeners()
.toMatchValues({ pendingFilterApplication: savedFilter })
expect(savedFilterRequestCount).toBe(0)
})

it('fetches a saved filter the loaded page does not hold', async () => {
logic.mount()

await expectLogic(logic, () => {
logic.actions.requestApplySavedFilterByShortId(savedFilter.short_id)
})
.toFinishAllListeners()
.toMatchValues({ pendingFilterApplication: savedFilter })
expect(savedFilterRequestCount).toBe(1)
})

it('reports a short id that resolves to no saved filter', async () => {
const errorToast = jest.spyOn(lemonToast, 'error').mockReturnValue('' as any)
useMocks({
get: {
'/api/projects/:team/session_recording_playlists/:id': () => [404, { detail: 'Not found.' }],
},
})
logic.mount()

await expectLogic(logic, () => {
logic.actions.requestApplySavedFilterByShortId('gone')
})
.toFinishAllListeners()
.toMatchValues({ pendingFilterApplication: null })
expect(errorToast).toHaveBeenCalled()
})
})

it('does not redirect a saved filter load that resolves after the user navigated away', async () => {
router.actions.push(urls.replay(ReplayTabs.Home), { savedFilterId: savedFilter.short_id })

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { loaders } from 'kea-loaders'
import { router } from 'kea-router'

import api from 'lib/api'
import { lemonToast } from 'lib/lemon-ui/LemonToast/LemonToast'
import { PaginationManual } from 'lib/lemon-ui/PaginationControl'
import { removeProjectIdIfPresent } from 'lib/utils/kea-router'
import { objectClean } from 'lib/utils/objects'
Expand Down Expand Up @@ -102,6 +103,9 @@ export interface sessionRecordingSavedFiltersLogicActions {
requestApplySavedFilter: (filter: SessionRecordingPlaylistType) => {
filter: SessionRecordingPlaylistType
}
requestApplySavedFilterByShortId: (shortId: SessionRecordingPlaylistType['short_id']) => {
shortId: SessionRecordingPlaylistType['short_id']
}
setAppliedSavedFilter: (appliedSavedFilter: SessionRecordingPlaylistType | null) => {
appliedSavedFilter: SessionRecordingPlaylistType | null
}
Expand Down Expand Up @@ -186,6 +190,7 @@ export const sessionRecordingSavedFiltersLogic = kea<sessionRecordingSavedFilter
checkForSavedFilterRedirect: true,
setAppliedSavedFilter: (appliedSavedFilter: SessionRecordingPlaylistType | null) => ({ appliedSavedFilter }),
requestApplySavedFilter: (filter: SessionRecordingPlaylistType) => ({ filter }),
requestApplySavedFilterByShortId: (shortId: SessionRecordingPlaylistType['short_id']) => ({ shortId }),
clearPendingFilterApplication: true,
})),
reducers(() => ({
Expand Down Expand Up @@ -275,6 +280,21 @@ export const sessionRecordingSavedFiltersLogic = kea<sessionRecordingSavedFilter
},
})),
listeners(({ actions, values }) => ({
requestApplySavedFilterByShortId: async ({ shortId }) => {
const loadedFilter = values.savedFilters.results.find((savedFilter) => savedFilter.short_id === shortId)
if (loadedFilter?.filters) {
actions.requestApplySavedFilter(loadedFilter)
return
}
// `savedFilters` holds one page of the list, narrowed by the search and the created-by
// filter of the saved filters panel, so a filter the picker offers can be absent from it.
const fetchedFilter = await api.recordings.getPlaylist(shortId).catch(() => null)
if (fetchedFilter?.filters) {
actions.requestApplySavedFilter(fetchedFilter)
return
}
lemonToast.error('Could not apply that saved filter. It may have been deleted.')
},
setIsFiltersExpanded: ({ isFiltersExpanded }) => {
if (isFiltersExpanded) {
actions.loadSavedFiltersIfNeeded()
Expand Down
Loading