diff --git a/frontend/__snapshots__/exporter-exporter--funnel-left-to-right-insight--dark.png b/frontend/__snapshots__/exporter-exporter--funnel-left-to-right-insight--dark.png index f736d569481b4..593d38dc63ae5 100644 Binary files a/frontend/__snapshots__/exporter-exporter--funnel-left-to-right-insight--dark.png and b/frontend/__snapshots__/exporter-exporter--funnel-left-to-right-insight--dark.png differ diff --git a/frontend/__snapshots__/scenes-app-dashboards--edit--light.png b/frontend/__snapshots__/scenes-app-dashboards--edit--light.png index ea51aea309fff..b1c99ed31db14 100644 Binary files a/frontend/__snapshots__/scenes-app-dashboards--edit--light.png and b/frontend/__snapshots__/scenes-app-dashboards--edit--light.png differ diff --git a/frontend/src/scenes/session-recordings/player/inspector/playerInspectorLogic.test.ts b/frontend/src/scenes/session-recordings/player/inspector/playerInspectorLogic.test.ts index 4f0bf12fc81cd..cf30bb78735c2 100644 --- a/frontend/src/scenes/session-recordings/player/inspector/playerInspectorLogic.test.ts +++ b/frontend/src/scenes/session-recordings/player/inspector/playerInspectorLogic.test.ts @@ -1,9 +1,17 @@ import { expectLogic } from 'kea-test-utils' import { featureFlagLogic } from 'lib/logic/featureFlagLogic' -import { playerInspectorLogic } from 'scenes/session-recordings/player/inspector/playerInspectorLogic' +import { + filterInspectorListItems, + InspectorListBrowserVisibility, + InspectorListItemEvent, + InspectorListOfflineStatusChange, + playerInspectorLogic, +} from 'scenes/session-recordings/player/inspector/playerInspectorLogic' +import { SharedListMiniFilter } from 'scenes/session-recordings/player/playerSettingsLogic' import { useMocks } from '~/mocks/jest' import { initKeaTests } from '~/test/init' +import { PerformanceEvent, SessionRecordingPlayerTab } from '~/types' const playerLogicProps = { sessionRecordingId: '1', playerKey: 'playlist' } @@ -22,6 +30,133 @@ describe('playerInspectorLogic', () => { logic.mount() }) + describe('filtering inspector list items', () => { + describe('the events tab', () => { + it('includes browser visibility', () => { + expect( + filterInspectorListItems({ + allItems: [ + { + type: 'browser-visibility', + } as InspectorListBrowserVisibility, + ], + tab: SessionRecordingPlayerTab.EVENTS, + miniFiltersByKey: { 'all-everything': { enabled: true } as unknown as SharedListMiniFilter }, + showOnlyMatching: false, + showMatchingEventsFilter: false, + windowIdFilter: null, + }) + ).toHaveLength(1) + }) + + it('excludes browser visibility on console filter', () => { + expect( + filterInspectorListItems({ + allItems: [ + { + type: 'browser-visibility', + } as InspectorListBrowserVisibility, + ], + tab: SessionRecordingPlayerTab.EVENTS, + miniFiltersByKey: { 'all-everything': { enabled: false } as unknown as SharedListMiniFilter }, + showOnlyMatching: false, + showMatchingEventsFilter: false, + windowIdFilter: null, + }) + ).toHaveLength(0) + }) + + it('excludes browser visibility when show only matching', () => { + expect( + filterInspectorListItems({ + allItems: [ + { + type: 'browser-visibility', + } as InspectorListBrowserVisibility, + ], + tab: SessionRecordingPlayerTab.EVENTS, + miniFiltersByKey: { 'all-everything': { enabled: true } as unknown as SharedListMiniFilter }, + showOnlyMatching: true, + showMatchingEventsFilter: true, + windowIdFilter: null, + }) + ).toHaveLength(0) + }) + }) + + describe('the doctor tab', () => { + it('ignores events that are not exceptions', () => { + expect( + filterInspectorListItems({ + allItems: [ + { + type: SessionRecordingPlayerTab.EVENTS, + data: { event: 'an event' } as unknown as PerformanceEvent, + } as unknown as InspectorListItemEvent, + ], + tab: SessionRecordingPlayerTab.DOCTOR, + miniFiltersByKey: {}, + showOnlyMatching: false, + showMatchingEventsFilter: false, + windowIdFilter: null, + }) + ).toHaveLength(0) + }) + + it('includes events that are exceptions', () => { + expect( + filterInspectorListItems({ + allItems: [ + { + type: SessionRecordingPlayerTab.EVENTS, + data: { event: '$exception' } as unknown as PerformanceEvent, + } as unknown as InspectorListItemEvent, + ], + tab: SessionRecordingPlayerTab.DOCTOR, + miniFiltersByKey: {}, + showOnlyMatching: false, + showMatchingEventsFilter: false, + windowIdFilter: null, + }) + ).toHaveLength(1) + }) + + it('includes browser offline status', () => { + expect( + filterInspectorListItems({ + allItems: [ + { + type: 'offline-status', + } as unknown as InspectorListOfflineStatusChange, + ], + tab: SessionRecordingPlayerTab.DOCTOR, + miniFiltersByKey: {}, + showOnlyMatching: false, + showMatchingEventsFilter: false, + windowIdFilter: null, + }) + ).toHaveLength(1) + }) + + it('includes browser visibility status', () => { + expect( + filterInspectorListItems({ + allItems: [ + { + type: 'browser-visibility', + } as InspectorListBrowserVisibility, + ], + tab: SessionRecordingPlayerTab.DOCTOR, + miniFiltersByKey: {}, + showOnlyMatching: false, + showMatchingEventsFilter: false, + windowIdFilter: null, + }) + ).toHaveLength(1) + }) + }) + }) + describe('setWindowIdFilter', () => { it('happy case', async () => { await expectLogic(logic).toMatchValues({ diff --git a/frontend/src/scenes/session-recordings/player/inspector/playerInspectorLogic.ts b/frontend/src/scenes/session-recordings/player/inspector/playerInspectorLogic.ts index 207491cbc0fe0..a0a42afade869 100644 --- a/frontend/src/scenes/session-recordings/player/inspector/playerInspectorLogic.ts +++ b/frontend/src/scenes/session-recordings/player/inspector/playerInspectorLogic.ts @@ -12,7 +12,7 @@ import { InspectorListItemPerformance, performanceEventDataLogic, } from 'scenes/session-recordings/apm/performanceEventDataLogic' -import { playerSettingsLogic } from 'scenes/session-recordings/player/playerSettingsLogic' +import { playerSettingsLogic, type SharedListMiniFilter } from 'scenes/session-recordings/player/playerSettingsLogic' import { convertUniversalFiltersToLegacyFilters, MatchingEventsMatchType, @@ -153,6 +153,247 @@ function timeRelativeToStart( return { timestamp, timeInRecording } } +export function filterInspectorListItems({ + allItems, + tab, + miniFiltersByKey, + showMatchingEventsFilter, + showOnlyMatching, + windowIdFilter, +}: { + allItems: InspectorListItem[] + tab: SessionRecordingPlayerTab + miniFiltersByKey: { + [key: string]: SharedListMiniFilter + } + showMatchingEventsFilter: boolean + showOnlyMatching: boolean + windowIdFilter: string | null +}): InspectorListItem[] { + const items: InspectorListItem[] = [] + + for (const item of allItems) { + let include = false + + const isDoctorTab = tab === SessionRecordingPlayerTab.DOCTOR + + if (item.type === 'offline-status' || item.type === 'browser-visibility') { + const allowedMiniFilters = !!( + miniFiltersByKey['performance-all']?.enabled || + miniFiltersByKey['all-everything']?.enabled || + miniFiltersByKey['all-automatic']?.enabled || + miniFiltersByKey['console-all']?.enabled || + miniFiltersByKey['events-all']?.enabled + ) + + const isCurrentlyShowingFilteredEvents = showMatchingEventsFilter && showOnlyMatching + + include = isDoctorTab || (allowedMiniFilters && !isCurrentlyShowingFilteredEvents) + + if (windowIdFilter && item.windowId && item.windowId !== windowIdFilter) { + include = false + } + } + + if (item.type === SessionRecordingPlayerTab.DOCTOR && isDoctorTab) { + include = true + if ( + windowIdFilter && + item.data?.properties?.$window_id && + item.data.properties.$window_id !== windowIdFilter + ) { + include = false + } + } + + // EVENTS + if (item.type === SessionRecordingPlayerTab.EVENTS) { + if (tab === SessionRecordingPlayerTab.DOCTOR) { + if (item.data.event === '$exception' || item.data.event.toLowerCase().includes('error')) { + include = true + } + } else { + if (tab !== SessionRecordingPlayerTab.EVENTS && tab !== SessionRecordingPlayerTab.ALL) { + continue + } + + if (miniFiltersByKey['events-all']?.enabled || miniFiltersByKey['all-everything']?.enabled) { + include = true + } + if (miniFiltersByKey['events-posthog']?.enabled && isPostHogEvent(item)) { + include = true + } + // include Mobile events as part of the Auto-Summary + if (miniFiltersByKey['all-automatic']?.enabled && isMobileEvent(item)) { + include = true + } + if ( + (miniFiltersByKey['events-custom']?.enabled || miniFiltersByKey['all-automatic']?.enabled) && + !isPostHogEvent(item) + ) { + include = true + } + if ( + (miniFiltersByKey['events-pageview']?.enabled || miniFiltersByKey['all-automatic']?.enabled) && + ['$pageview', '$screen'].includes(item.data.event) + ) { + include = true + } + if ( + (miniFiltersByKey['events-autocapture']?.enabled || miniFiltersByKey['all-automatic']?.enabled) && + item.data.event === '$autocapture' + ) { + include = true + } + + if ( + (miniFiltersByKey['all-errors']?.enabled || miniFiltersByKey['events-exceptions']?.enabled) && + (item.data.event === '$exception' || item.data.event.toLowerCase().includes('error')) + ) { + include = true + } + + if (showMatchingEventsFilter && showOnlyMatching) { + // Special case - overrides the others + include = include && item.highlightColor === 'primary' + } + + if (windowIdFilter && item.data.properties?.$window_id !== windowIdFilter) { + include = false + } + } + } + + // CONSOLE LOGS + if (item.type === SessionRecordingPlayerTab.CONSOLE) { + if (isDoctorTab && item.data.level === 'error') { + include = true + } + + if (tab !== SessionRecordingPlayerTab.CONSOLE && tab !== SessionRecordingPlayerTab.ALL) { + continue + } + + if (miniFiltersByKey['console-all']?.enabled || miniFiltersByKey['all-everything']?.enabled) { + include = true + } + if (miniFiltersByKey['console-info']?.enabled && ['log', 'info'].includes(item.data.level)) { + include = true + } + if ( + (miniFiltersByKey['console-warn']?.enabled || miniFiltersByKey['all-automatic']?.enabled) && + item.data.level === 'warn' + ) { + include = true + } + if ( + (miniFiltersByKey['console-error']?.enabled || + miniFiltersByKey['all-errors']?.enabled || + miniFiltersByKey['all-automatic']?.enabled) && + item.data.level === 'error' + ) { + include = true + } + + if (windowIdFilter && item.data.windowId !== windowIdFilter) { + include = false + } + } + + // NETWORK + if (item.type === SessionRecordingPlayerTab.NETWORK) { + if (tab !== SessionRecordingPlayerTab.NETWORK && tab !== SessionRecordingPlayerTab.ALL) { + continue + } + + const responseStatus = item.data.response_status || 200 + const responseTime = item.data.duration || 0 + + if (miniFiltersByKey['performance-all']?.enabled || miniFiltersByKey['all-everything']?.enabled) { + include = true + } + if ( + (miniFiltersByKey['performance-document']?.enabled || miniFiltersByKey['all-automatic']?.enabled) && + ['navigation'].includes(item.data.entry_type || '') + ) { + include = true + } + if ( + miniFiltersByKey['performance-fetch']?.enabled && + item.data.entry_type === 'resource' && + ['fetch', 'xmlhttprequest'].includes(item.data.initiator_type || '') + ) { + include = true + } + + if ( + miniFiltersByKey['performance-assets-js']?.enabled && + item.data.entry_type === 'resource' && + (item.data.initiator_type === 'script' || + (['link', 'other'].includes(item.data.initiator_type || '') && item.data.name?.includes('.js'))) + ) { + include = true + } + + if ( + miniFiltersByKey['performance-assets-css']?.enabled && + item.data.entry_type === 'resource' && + (item.data.initiator_type === 'css' || + (['link', 'other'].includes(item.data.initiator_type || '') && item.data.name?.includes('.css'))) + ) { + include = true + } + + if ( + miniFiltersByKey['performance-assets-img']?.enabled && + item.data.entry_type === 'resource' && + (item.data.initiator_type === 'img' || + (['link', 'other'].includes(item.data.initiator_type || '') && + !!IMAGE_WEB_EXTENSIONS.some((ext) => item.data.name?.includes(`.${ext}`)))) + ) { + include = true + } + + if ( + miniFiltersByKey['performance-other']?.enabled && + item.data.entry_type === 'resource' && + ['other'].includes(item.data.initiator_type || '') && + ![...IMAGE_WEB_EXTENSIONS, 'css', 'js'].some((ext) => item.data.name?.includes(`.${ext}`)) + ) { + include = true + } + + if ( + (miniFiltersByKey['all-errors']?.enabled || miniFiltersByKey['all-automatic']?.enabled) && + responseStatus >= 400 + ) { + include = true + } + + if (miniFiltersByKey['all-automatic']?.enabled && responseTime >= 1000) { + include = true + } + + if (windowIdFilter && item.data.window_id !== windowIdFilter) { + include = false + } + + if (item.data.entry_type === 'paint') { + // We don't include paint events as they are covered in the navigation events + include = false + } + } + + if (!include) { + continue + } + + items.push(item) + } + + return items +} + export const playerInspectorLogic = kea([ path((key) => ['scenes', 'session-recordings', 'player', 'playerInspectorLogic', key]), props({} as PlayerInspectorLogicProps), @@ -579,225 +820,14 @@ export const playerInspectorLogic = kea([ showMatchingEventsFilter, windowIdFilter ): InspectorListItem[] => { - const items: InspectorListItem[] = [] - - for (const item of allItems) { - let include = false - - // always show offline status changes - if (item.type === 'offline-status' || item.type === 'browser-visibility') { - include = - tab === SessionRecordingPlayerTab.DOCTOR || - !!( - miniFiltersByKey['performance-all']?.enabled || - miniFiltersByKey['all-everything']?.enabled || - miniFiltersByKey['all-automatic']?.enabled || - miniFiltersByKey['console-all']?.enabled || - miniFiltersByKey['events-all']?.enabled - ) - } - - if (item.type === SessionRecordingPlayerTab.DOCTOR && tab === SessionRecordingPlayerTab.DOCTOR) { - include = true - } - - // EVENTS - if (item.type === SessionRecordingPlayerTab.EVENTS) { - if ( - tab === SessionRecordingPlayerTab.DOCTOR && - (item.data.event === '$exception' || item.data.event.toLowerCase().includes('error')) - ) { - include = true - } - - if (tab !== SessionRecordingPlayerTab.EVENTS && tab !== SessionRecordingPlayerTab.ALL) { - continue - } - - if (miniFiltersByKey['events-all']?.enabled || miniFiltersByKey['all-everything']?.enabled) { - include = true - } - if (miniFiltersByKey['events-posthog']?.enabled && isPostHogEvent(item)) { - include = true - } - // include Mobile events as part of the Auto-Summary - if (miniFiltersByKey['all-automatic']?.enabled && isMobileEvent(item)) { - include = true - } - if ( - (miniFiltersByKey['events-custom']?.enabled || - miniFiltersByKey['all-automatic']?.enabled) && - !isPostHogEvent(item) - ) { - include = true - } - if ( - (miniFiltersByKey['events-pageview']?.enabled || - miniFiltersByKey['all-automatic']?.enabled) && - ['$pageview', '$screen'].includes(item.data.event) - ) { - include = true - } - if ( - (miniFiltersByKey['events-autocapture']?.enabled || - miniFiltersByKey['all-automatic']?.enabled) && - item.data.event === '$autocapture' - ) { - include = true - } - - if ( - (miniFiltersByKey['all-errors']?.enabled || - miniFiltersByKey['events-exceptions']?.enabled) && - (item.data.event === '$exception' || item.data.event.toLowerCase().includes('error')) - ) { - include = true - } - - if (showMatchingEventsFilter && showOnlyMatching) { - // Special case - overrides the others - include = include && item.highlightColor === 'primary' - } - - if (windowIdFilter && item.data.properties?.$window_id !== windowIdFilter) { - include = false - } - } - - // CONSOLE LOGS - if (item.type === SessionRecordingPlayerTab.CONSOLE) { - if (tab === SessionRecordingPlayerTab.DOCTOR && item.data.level === 'error') { - include = true - } - - if (tab !== SessionRecordingPlayerTab.CONSOLE && tab !== SessionRecordingPlayerTab.ALL) { - continue - } - - if (miniFiltersByKey['console-all']?.enabled || miniFiltersByKey['all-everything']?.enabled) { - include = true - } - if (miniFiltersByKey['console-info']?.enabled && ['log', 'info'].includes(item.data.level)) { - include = true - } - if ( - (miniFiltersByKey['console-warn']?.enabled || miniFiltersByKey['all-automatic']?.enabled) && - item.data.level === 'warn' - ) { - include = true - } - if ( - (miniFiltersByKey['console-error']?.enabled || - miniFiltersByKey['all-errors']?.enabled || - miniFiltersByKey['all-automatic']?.enabled) && - item.data.level === 'error' - ) { - include = true - } - - if (windowIdFilter && item.data.windowId !== windowIdFilter) { - include = false - } - } - - // NETWORK - if (item.type === SessionRecordingPlayerTab.NETWORK) { - if (tab !== SessionRecordingPlayerTab.NETWORK && tab !== SessionRecordingPlayerTab.ALL) { - continue - } - - const responseStatus = item.data.response_status || 200 - const responseTime = item.data.duration || 0 - - if ( - miniFiltersByKey['performance-all']?.enabled || - miniFiltersByKey['all-everything']?.enabled - ) { - include = true - } - if ( - (miniFiltersByKey['performance-document']?.enabled || - miniFiltersByKey['all-automatic']?.enabled) && - ['navigation'].includes(item.data.entry_type || '') - ) { - include = true - } - if ( - miniFiltersByKey['performance-fetch']?.enabled && - item.data.entry_type === 'resource' && - ['fetch', 'xmlhttprequest'].includes(item.data.initiator_type || '') - ) { - include = true - } - - if ( - miniFiltersByKey['performance-assets-js']?.enabled && - item.data.entry_type === 'resource' && - (item.data.initiator_type === 'script' || - (['link', 'other'].includes(item.data.initiator_type || '') && - item.data.name?.includes('.js'))) - ) { - include = true - } - - if ( - miniFiltersByKey['performance-assets-css']?.enabled && - item.data.entry_type === 'resource' && - (item.data.initiator_type === 'css' || - (['link', 'other'].includes(item.data.initiator_type || '') && - item.data.name?.includes('.css'))) - ) { - include = true - } - - if ( - miniFiltersByKey['performance-assets-img']?.enabled && - item.data.entry_type === 'resource' && - (item.data.initiator_type === 'img' || - (['link', 'other'].includes(item.data.initiator_type || '') && - !!IMAGE_WEB_EXTENSIONS.some((ext) => item.data.name?.includes(`.${ext}`)))) - ) { - include = true - } - - if ( - miniFiltersByKey['performance-other']?.enabled && - item.data.entry_type === 'resource' && - ['other'].includes(item.data.initiator_type || '') && - ![...IMAGE_WEB_EXTENSIONS, 'css', 'js'].some((ext) => item.data.name?.includes(`.${ext}`)) - ) { - include = true - } - - if ( - (miniFiltersByKey['all-errors']?.enabled || miniFiltersByKey['all-automatic']?.enabled) && - responseStatus >= 400 - ) { - include = true - } - - if (miniFiltersByKey['all-automatic']?.enabled && responseTime >= 1000) { - include = true - } - - if (windowIdFilter && item.data.window_id !== windowIdFilter) { - include = false - } - - if (item.data.entry_type === 'paint') { - // We don't include paint events as they are covered in the navigation events - include = false - } - } - - if (!include) { - continue - } - - items.push(item) - } - - return items + return filterInspectorListItems({ + allItems, + tab, + miniFiltersByKey, + showMatchingEventsFilter, + showOnlyMatching, + windowIdFilter, + }) }, ],