diff --git a/frontend/__snapshots__/components-command-bar--actions.png b/frontend/__snapshots__/components-command-bar--actions.png
index 585c01020c1c5..302fb86f50402 100644
Binary files a/frontend/__snapshots__/components-command-bar--actions.png and b/frontend/__snapshots__/components-command-bar--actions.png differ
diff --git a/frontend/__snapshots__/components-networkrequesttiming--basic.png b/frontend/__snapshots__/components-networkrequesttiming--basic.png
index 8247b433f71c8..effc91c21a0a6 100644
Binary files a/frontend/__snapshots__/components-networkrequesttiming--basic.png and b/frontend/__snapshots__/components-networkrequesttiming--basic.png differ
diff --git a/frontend/src/loadPostHogJS.tsx b/frontend/src/loadPostHogJS.tsx
index 2acd266241f82..bcd03f327260a 100644
--- a/frontend/src/loadPostHogJS.tsx
+++ b/frontend/src/loadPostHogJS.tsx
@@ -27,8 +27,8 @@ export function loadPostHogJS(): void {
bootstrap: window.POSTHOG_USER_IDENTITY_WITH_FLAGS ? window.POSTHOG_USER_IDENTITY_WITH_FLAGS : {},
opt_in_site_apps: true,
loaded: (posthog) => {
- if (posthog.webPerformance) {
- posthog.webPerformance._forceAllowLocalhost = true
+ if (posthog.sessionRecording) {
+ posthog.sessionRecording._forceAllowLocalhostNetworkCapture = true
}
if (window.IMPERSONATED_SESSION) {
diff --git a/frontend/src/scenes/session-recordings/player/inspector/components/ItemPerformanceEvent.tsx b/frontend/src/scenes/session-recordings/player/inspector/components/ItemPerformanceEvent.tsx
index 96c4a9e01a143..592d926958224 100644
--- a/frontend/src/scenes/session-recordings/player/inspector/components/ItemPerformanceEvent.tsx
+++ b/frontend/src/scenes/session-recordings/player/inspector/components/ItemPerformanceEvent.tsx
@@ -140,7 +140,7 @@ export function ItemPerformanceEvent({
expanded,
setExpanded,
}: ItemPerformanceEvent): JSX.Element {
- const [activeTab, setActiveTab] = useState<'timings' | 'headers' | 'payload' | 'response_body'>('timings')
+ const [activeTab, setActiveTab] = useState<'timings' | 'headers' | 'payload' | 'response_body' | 'raw'>('timings')
const bytes = humanizeBytes(item.encoded_body_size || item.decoded_body_size || 0)
const startTime = item.start_time || item.fetch_start || 0
@@ -176,7 +176,11 @@ export function ItemPerformanceEvent({
return acc
}
- if (['response_headers', 'request_headers', 'request_body', 'response_body', 'response_status'].includes(key)) {
+ if (
+ ['response_headers', 'request_headers', 'request_body', 'response_body', 'response_status', 'raw'].includes(
+ key
+ )
+ ) {
return acc
}
@@ -392,6 +396,17 @@ export function ItemPerformanceEvent({
),
}
: false,
+ // raw is only available if the feature flag is enabled
+ // TODO before proper release we should put raw behind its own flag
+ {
+ key: 'raw',
+ label: 'Json',
+ content: (
+
+ {JSON.stringify(item.raw, null, 2)}
+
+ ),
+ },
]}
/>
@@ -470,6 +485,11 @@ function StatusRow({ item }: { item: PerformanceEvent }): JSX.Element | null {
let statusRow = null
let methodRow = null
+ let fromDiskCache = false
+ if (item.transfer_size === 0 && item.response_body && item.response_status && item.response_status < 400) {
+ fromDiskCache = true
+ }
+
if (item.response_status) {
const statusDescription = `${item.response_status} ${friendlyHttpStatus[item.response_status] || ''}`
@@ -483,7 +503,10 @@ function StatusRow({ item }: { item: PerformanceEvent }): JSX.Element | null {
statusRow = (
Status code
-
{statusDescription}
+
+ {statusDescription}
+ {fromDiskCache && (from cache)}
+
)
}
diff --git a/frontend/src/scenes/session-recordings/player/inspector/components/Timing/NetworkRequestTiming.stories.tsx b/frontend/src/scenes/session-recordings/player/inspector/components/Timing/NetworkRequestTiming.stories.tsx
index a02e9bf3dce03..815eefed8bdfb 100644
--- a/frontend/src/scenes/session-recordings/player/inspector/components/Timing/NetworkRequestTiming.stories.tsx
+++ b/frontend/src/scenes/session-recordings/player/inspector/components/Timing/NetworkRequestTiming.stories.tsx
@@ -19,27 +19,30 @@ export function Basic(): JSX.Element {
+
/**
* There are defined sections to performance measurement. We may have data for some or all of them
*
@@ -109,85 +110,114 @@ function colorForSection(section: (typeof perfSections)[number]): string {
*
* see https://nicj.net/resourcetiming-in-practice/
*/
-function calculatePerformanceParts(perfEntry: PerformanceEvent): Record {
+export function calculatePerformanceParts(perfEntry: PerformanceEvent): PerformanceMeasures {
const performanceParts: Record = {}
- if (perfEntry.redirect_start && perfEntry.redirect_end) {
- performanceParts['redirect'] = {
- start: perfEntry.redirect_start,
- end: perfEntry.redirect_end,
- color: colorForSection('redirect'),
+ if (isPresent(perfEntry.redirect_start) && isPresent(perfEntry.redirect_end)) {
+ if (perfEntry.redirect_end - perfEntry.redirect_start > 0) {
+ performanceParts['redirect'] = {
+ start: perfEntry.redirect_start,
+ end: perfEntry.redirect_end,
+ color: colorForSection('redirect'),
+ }
}
}
- if (perfEntry.fetch_start && perfEntry.domain_lookup_start) {
- performanceParts['app cache'] = {
- start: perfEntry.fetch_start,
- end: perfEntry.domain_lookup_start,
- color: colorForSection('app cache'),
+ if (isPresent(perfEntry.fetch_start) && isPresent(perfEntry.domain_lookup_start)) {
+ if (perfEntry.domain_lookup_start - perfEntry.fetch_start > 0) {
+ performanceParts['app cache'] = {
+ start: perfEntry.fetch_start,
+ end: perfEntry.domain_lookup_start,
+ color: colorForSection('app cache'),
+ }
}
}
- if (perfEntry.domain_lookup_end && perfEntry.domain_lookup_start) {
- performanceParts['dns lookup'] = {
- start: perfEntry.domain_lookup_start,
- end: perfEntry.domain_lookup_end,
- color: colorForSection('dns lookup'),
+ if (isPresent(perfEntry.domain_lookup_end) && isPresent(perfEntry.domain_lookup_start)) {
+ if (perfEntry.domain_lookup_end - perfEntry.domain_lookup_start > 0) {
+ performanceParts['dns lookup'] = {
+ start: perfEntry.domain_lookup_start,
+ end: perfEntry.domain_lookup_end,
+ color: colorForSection('dns lookup'),
+ }
}
}
- if (perfEntry.connect_end && perfEntry.connect_start) {
- performanceParts['connection time'] = {
- start: perfEntry.connect_start,
- end: perfEntry.connect_end,
- color: colorForSection('connection time'),
- }
-
- if (perfEntry.secure_connection_start) {
- performanceParts['tls time'] = {
- start: perfEntry.secure_connection_start,
+ if (isPresent(perfEntry.connect_end) && isPresent(perfEntry.connect_start)) {
+ if (perfEntry.connect_end - perfEntry.connect_start > 0) {
+ performanceParts['connection time'] = {
+ start: perfEntry.connect_start,
end: perfEntry.connect_end,
- color: colorForSection('tls time'),
- reducedHeight: true,
+ color: colorForSection('connection time'),
+ }
+
+ if (isPresent(perfEntry.secure_connection_start) && perfEntry.secure_connection_start > 0) {
+ performanceParts['tls time'] = {
+ start: perfEntry.secure_connection_start,
+ end: perfEntry.connect_end,
+ color: colorForSection('tls time'),
+ reducedHeight: true,
+ }
}
}
}
- if (perfEntry.connect_end && perfEntry.request_start && perfEntry.connect_end !== perfEntry.request_start) {
- performanceParts['request queuing time'] = {
- start: perfEntry.connect_end,
- end: perfEntry.request_start,
- color: colorForSection('request queuing time'),
+ if (
+ isPresent(perfEntry.connect_end) &&
+ isPresent(perfEntry.request_start) &&
+ perfEntry.connect_end !== perfEntry.request_start
+ ) {
+ if (perfEntry.request_start - perfEntry.connect_end > 0) {
+ performanceParts['request queuing time'] = {
+ start: perfEntry.connect_end,
+ end: perfEntry.request_start,
+ color: colorForSection('request queuing time'),
+ }
}
}
- if (perfEntry.response_start && perfEntry.request_start) {
- performanceParts['waiting for first byte'] = {
- start: perfEntry.request_start,
- end: perfEntry.response_start,
- color: colorForSection('waiting for first byte'),
+ if (isPresent(perfEntry.response_start) && isPresent(perfEntry.request_start)) {
+ if (perfEntry.response_start - perfEntry.request_start > 0) {
+ performanceParts['waiting for first byte'] = {
+ start: perfEntry.request_start,
+ end: perfEntry.response_start,
+ color: colorForSection('waiting for first byte'),
+ }
}
}
- if (perfEntry.response_start && perfEntry.response_end) {
- performanceParts['receiving response'] = {
- start: perfEntry.response_start,
- end: perfEntry.response_end,
- color: colorForSection('receiving response'),
+ if (isPresent(perfEntry.response_start) && isPresent(perfEntry.response_end)) {
+ if (perfEntry.response_end - perfEntry.response_start > 0) {
+ // if loading from disk cache then response_start is 0 but fetch_start is not
+ let start = perfEntry.response_start
+ if (perfEntry.response_start === 0 && isPresent(perfEntry.fetch_start)) {
+ start = perfEntry.fetch_start
+ }
+ performanceParts['receiving response'] = {
+ start: start,
+ end: perfEntry.response_end,
+ color: colorForSection('receiving response'),
+ }
}
}
- if (perfEntry.response_end && perfEntry.load_event_end) {
- performanceParts['document processing'] = {
- start: perfEntry.response_end,
- end: perfEntry.load_event_end,
- color: colorForSection('document processing'),
+ if (isPresent(perfEntry.response_end) && isPresent(perfEntry.load_event_end)) {
+ if (perfEntry.load_event_end - perfEntry.response_end > 0) {
+ performanceParts['document processing'] = {
+ start: perfEntry.response_end,
+ end: perfEntry.load_event_end,
+ color: colorForSection('document processing'),
+ }
}
}
return performanceParts
}
+function percentage(partDuration: number, totalDuration: number, min: number): number {
+ return Math.min(Math.max(min, (partDuration / totalDuration) * 100), 100)
+}
+
function percentagesWithinEventRange({
partStart,
partEnd,
@@ -203,20 +233,20 @@ function percentagesWithinEventRange({
const partStartRelativeToTimeline = partStart - rangeStart
const partDuration = partEnd - partStart
- const partPercentage = Math.max(0.1, (partDuration / totalDuration) * 100) //less than 0.1% is not visible
- const partStartPercentage = (partStartRelativeToTimeline / totalDuration) * 100
+ const partPercentage = percentage(partDuration, totalDuration, 0.1)
+ const partStartPercentage = percentage(partStartRelativeToTimeline, totalDuration, 0)
return { startPercentage: `${partStartPercentage}%`, widthPercentage: `${partPercentage}%` }
}
-const TimeLineView = ({ performanceEvent }: { performanceEvent: PerformanceEvent }): JSX.Element => {
+const TimeLineView = ({ performanceEvent }: { performanceEvent: PerformanceEvent }): JSX.Element | null => {
const rangeStart = performanceEvent.start_time
- const rangeEnd = performanceEvent.response_end
+ const rangeEnd = performanceEvent.load_event_end ? performanceEvent.load_event_end : performanceEvent.response_end
if (typeof rangeStart === 'number' && typeof rangeEnd === 'number') {
- const performanceParts = calculatePerformanceParts(performanceEvent)
+ const timings = calculatePerformanceParts(performanceEvent)
return (
{perfSections.map((section) => {
- const matchedSection = performanceParts[section]
+ const matchedSection = timings[section]
const start = matchedSection?.start
const end = matchedSection?.end
const partDuration = end - start
@@ -263,7 +293,7 @@ const TimeLineView = ({ performanceEvent }: { performanceEvent: PerformanceEvent
)
}
- return Cannot render performance timeline for this request
+ return null
}
const TableView = ({ performanceEvent }: { performanceEvent: PerformanceEvent }): JSX.Element => {
@@ -283,11 +313,15 @@ export const NetworkRequestTiming = ({
}): JSX.Element | null => {
const [timelineMode, setTimelineMode] = useState(true)
+ // if timeline view renders null then we fall back to table view
+ const timelineView = timelineMode ? : null
+
return (
setTimelineMode(!timelineMode)}
data-attr={`switch-timing-to-${timelineMode ? 'table' : 'timeline'}-view`}
@@ -296,11 +330,11 @@ export const NetworkRequestTiming = ({
- {timelineMode ? (
-
- ) : (
-
- )}
+ {timelineMode && timelineView ? timelineView :
}
)
}
+
+function isPresent(x: number | undefined): x is number {
+ return typeof x === 'number'
+}
diff --git a/frontend/src/scenes/session-recordings/player/inspector/components/Timing/calculatePerformanceParts.test.ts b/frontend/src/scenes/session-recordings/player/inspector/components/Timing/calculatePerformanceParts.test.ts
new file mode 100644
index 0000000000000..24f59d7f4af3d
--- /dev/null
+++ b/frontend/src/scenes/session-recordings/player/inspector/components/Timing/calculatePerformanceParts.test.ts
@@ -0,0 +1,192 @@
+import { mapRRWebNetworkRequest } from 'scenes/session-recordings/player/inspector/performance-event-utils'
+import { InitiatorType } from 'posthog-js'
+import { calculatePerformanceParts } from 'scenes/session-recordings/player/inspector/components/Timing/NetworkRequestTiming'
+
+jest.mock('lib/colors', () => {
+ return {
+ getSeriesColor: jest.fn(() => '#000000'),
+ }
+})
+
+describe('calculatePerformanceParts', () => {
+ it('can calculate TTFB', () => {
+ const perfEvent = {
+ connect_end: 9525.599999964237,
+ connect_start: 9525.599999964237,
+ decoded_body_size: 18260,
+ domain_lookup_end: 9525.599999964237,
+ domain_lookup_start: 9525.599999964237,
+ duration: 935.5,
+ encoded_body_size: 18260,
+ entry_type: 'resource',
+ fetch_start: 9525.599999964237,
+ initiator_type: 'fetch',
+ name: 'http://localhost:8000/api/organizations/@current/plugins/repository/',
+ next_hop_protocol: 'http/1.1',
+ redirect_end: 0,
+ redirect_start: 0,
+ render_blocking_status: 'non-blocking',
+ request_start: 9803.099999964237,
+ response_end: 10461.099999964237,
+ response_start: 10428.399999976158,
+ response_status: 200,
+ secure_connection_start: 0,
+ start_time: 9525.599999964237,
+ time_origin: '1699990397357',
+ timestamp: 1699990406882,
+ transfer_size: 18560,
+ window_id: '018bcf51-b1f0-7fe0-ac05-10543621f4f2',
+ worker_start: 0,
+ uuid: '12345',
+ distinct_id: '23456',
+ session_id: 'abcde',
+ pageview_id: 'fghij',
+ current_url: 'http://localhost:8000/insights',
+ }
+
+ expect(calculatePerformanceParts(perfEvent)).toEqual({
+ 'request queuing time': {
+ color: '#000000',
+ end: 9803.099999964237,
+ start: 9525.599999964237,
+ },
+
+ 'waiting for first byte': {
+ color: '#000000',
+ end: 10428.399999976158,
+ start: 9803.099999964237,
+ },
+ 'receiving response': {
+ color: '#000000',
+ end: 10461.099999964237,
+ start: 10428.399999976158,
+ },
+ })
+ })
+
+ it('can handle gravatar timings', () => {
+ const gravatarReqRes = {
+ name: 'https://www.gravatar.com/avatar/2e7d95b60efbe947f71009a1af1ba8d0?s=96&d=404',
+ entryType: 'resource',
+ initiatorType: 'fetch' as InitiatorType,
+ deliveryType: '',
+ nextHopProtocol: '',
+ renderBlockingStatus: 'non-blocking',
+ workerStart: 0,
+ redirectStart: 0,
+ redirectEnd: 0,
+ domainLookupStart: 0,
+ domainLookupEnd: 0,
+ connectStart: 0,
+ secureConnectionStart: 0,
+ connectEnd: 0,
+ requestStart: 0,
+ responseStart: 0,
+ firstInterimResponseStart: 0,
+ // only fetch start and response end
+ // and transfer size is 0
+ // loaded from disk cache
+ startTime: 18229,
+ fetchStart: 18228.5,
+ responseEnd: 18267.5,
+ endTime: 18268,
+ duration: 39,
+ transferSize: 0,
+ encodedBodySize: 0,
+ decodedBodySize: 0,
+ responseStatus: 200,
+ serverTiming: [],
+ timeOrigin: 1700296048424,
+ timestamp: 1700296066652,
+ method: 'GET',
+ status: 200,
+ requestHeaders: {},
+ requestBody: null,
+ responseHeaders: {
+ 'cache-control': 'max-age=300',
+ 'content-length': '13127',
+ 'content-type': 'image/png',
+ expires: 'Sat, 18 Nov 2023 08:32:46 GMT',
+ 'last-modified': 'Wed, 02 Feb 2022 09:11:05 GMT',
+ },
+ responseBody: '�PNGblah',
+ }
+ const mappedToPerfEvent = mapRRWebNetworkRequest(gravatarReqRes, 'windowId', 1700296066652)
+ expect(calculatePerformanceParts(mappedToPerfEvent)).toEqual({
+ // 'app cache' not included - end would be before beginning
+ // 'connection time' has 0 length
+ // 'dns lookup' has 0 length
+ // 'redirect has 0 length
+ // 'tls time' has 0 length
+ // TTFB has 0 length
+ 'receiving response': {
+ color: '#000000',
+ end: 18267.5,
+ start: 18228.5,
+ },
+ })
+ })
+
+ it('can handle no TLS connection timing', () => {
+ const tlsFreeReqRes = {
+ name: 'http://localhost:8000/decide/?v=3&ip=1&_=1700319068450&ver=1.91.1',
+ entryType: 'resource',
+ startTime: 6648,
+ duration: 93.40000003576279,
+ initiatorType: 'xmlhttprequest' as InitiatorType,
+ deliveryType: '',
+ nextHopProtocol: 'http/1.1',
+ renderBlockingStatus: 'non-blocking',
+ workerStart: 0,
+ redirectStart: 0,
+ redirectEnd: 0,
+ fetchStart: 6647.699999988079,
+ domainLookupStart: 6648.800000011921,
+ domainLookupEnd: 6648.800000011921,
+ connectStart: 6648.800000011921,
+ secureConnectionStart: 0,
+ connectEnd: 6649.300000011921,
+ requestStart: 6649.5,
+ responseStart: 6740.800000011921,
+ firstInterimResponseStart: 0,
+ responseEnd: 6741.100000023842,
+ transferSize: 2383,
+ encodedBodySize: 2083,
+ decodedBodySize: 2083,
+ responseStatus: 200,
+ serverTiming: [],
+ endTime: 6741,
+ timeOrigin: 1700319061802,
+ timestamp: 1700319068449,
+ isInitial: true,
+ }
+ const mappedToPerfEvent = mapRRWebNetworkRequest(tlsFreeReqRes, 'windowId', 1700319068449)
+ expect(calculatePerformanceParts(mappedToPerfEvent)).toEqual({
+ 'app cache': {
+ color: '#000000',
+ end: 6648.800000011921,
+ start: 6647.699999988079,
+ },
+ 'connection time': {
+ color: '#000000',
+ end: 6649.300000011921,
+ start: 6648.800000011921,
+ },
+ 'waiting for first byte': {
+ color: '#000000',
+ end: 6740.800000011921,
+ start: 6649.5,
+ },
+ 'receiving response': {
+ color: '#000000',
+ end: 6741.100000023842,
+ start: 6740.800000011921,
+ },
+ 'request queuing time': {
+ color: '#000000',
+ end: 6649.5,
+ start: 6649.300000011921,
+ },
+ })
+ })
+})
diff --git a/frontend/src/scenes/session-recordings/player/inspector/performance-event-utils.ts b/frontend/src/scenes/session-recordings/player/inspector/performance-event-utils.ts
index 43d0b2ef616b8..564b96fb865db 100644
--- a/frontend/src/scenes/session-recordings/player/inspector/performance-event-utils.ts
+++ b/frontend/src/scenes/session-recordings/player/inspector/performance-event-utils.ts
@@ -1,10 +1,9 @@
import { eventWithTime } from '@rrweb/types'
-// import posthog from 'posthog-js'
+import { CapturedNetworkRequest } from 'posthog-js'
import { PerformanceEvent } from '~/types'
const NETWORK_PLUGIN_NAME = 'posthog/network@1'
-// const RRWEB_NETWORK_PLUGIN_NAME = 'rrweb/network@1'
-// const IGNORED_POSTHOG_PATHS = ['/s/', '/e/', '/i/v0/e/']
+const RRWEB_NETWORK_PLUGIN_NAME = 'rrweb/network@1'
export const PerformanceEventReverseMapping: { [key: number]: keyof PerformanceEvent } = {
// BASE_PERFORMANCE_EVENT_COLUMNS
@@ -58,8 +57,92 @@ export const PerformanceEventReverseMapping: { [key: number]: keyof PerformanceE
40: 'timestamp',
}
+export const RRWebPerformanceEventReverseMapping: Record = {
+ // BASE_PERFORMANCE_EVENT_COLUMNS
+ entryType: 'entry_type',
+ timeOrigin: 'time_origin',
+ name: 'name',
+
+ // RESOURCE_EVENT_COLUMNS
+ startTime: 'start_time',
+ redirectStart: 'redirect_start',
+ redirectEnd: 'redirect_end',
+ workerStart: 'worker_start',
+ fetchStart: 'fetch_start',
+ domainLookupStart: 'domain_lookup_start',
+ domainLookupEnd: 'domain_lookup_end',
+ connectStart: 'connect_start',
+ secureConnectionStart: 'secure_connection_start',
+ connectEnd: 'connect_end',
+ requestStart: 'request_start',
+ responseStart: 'response_start',
+ responseEnd: 'response_end',
+ decodedBodySize: 'decoded_body_size',
+ encodedBodySize: 'encoded_body_size',
+ initiatorType: 'initiator_type',
+ nextHopProtocol: 'next_hop_protocol',
+ renderBlockingStatus: 'render_blocking_status',
+ responseStatus: 'response_status',
+ transferSize: 'transfer_size',
+
+ // LARGEST_CONTENTFUL_PAINT_EVENT_COLUMNS
+ largestContentfulPaintElement: 'largest_contentful_paint_element',
+ largestContentfulPaintRenderTime: 'largest_contentful_paint_render_time',
+ largestContentfulPaintLoadTime: 'largest_contentful_paint_load_time',
+ largestContentfulPaintSize: 'largest_contentful_paint_size',
+ largestContentfulPaintId: 'largest_contentful_paint_id',
+ largestContentfulPaintUrl: 'largest_contentful_paint_url',
+
+ // NAVIGATION_EVENT_COLUMNS
+ domComplete: 'dom_complete',
+ domContentLoadedEvent: 'dom_content_loaded_event',
+ domInteractive: 'dom_interactive',
+ loadEventEnd: 'load_event_end',
+ loadEventStart: 'load_event_start',
+ redirectCount: 'redirect_count',
+ navigationType: 'navigation_type',
+ unloadEventEnd: 'unload_event_end',
+ unloadEventStart: 'unload_event_start',
+
+ // Added after v1
+ duration: 'duration',
+ timestamp: 'timestamp',
+
+ //rrweb/network@1
+ isInitial: 'is_initial',
+ requestHeaders: 'request_headers',
+ responseHeaders: 'response_headers',
+ requestBody: 'request_body',
+ responseBody: 'response_body',
+ method: 'method',
+}
+
+export function mapRRWebNetworkRequest(
+ capturedRequest: CapturedNetworkRequest,
+ windowId: string,
+ timestamp: PerformanceEvent['timestamp']
+): PerformanceEvent {
+ const data: Partial = {
+ timestamp: timestamp,
+ window_id: windowId,
+ raw: capturedRequest,
+ }
+
+ Object.entries(RRWebPerformanceEventReverseMapping).forEach(([key, value]) => {
+ if (key in capturedRequest) {
+ data[value] = capturedRequest[key]
+ }
+ })
+
+ return data as PerformanceEvent
+}
+
export function matchNetworkEvents(snapshotsByWindowId: Record): PerformanceEvent[] {
- const eventsMapping: Record> = {}
+ // we only support rrweb/network@1 events or posthog/network@1 events in any one recording
+ // apart from during testing, where we might have both
+ // if we have both, we only display posthog/network@1 events
+ const events: PerformanceEvent[] = []
+ const rrwebEvents: PerformanceEvent[] = []
// we could do this in one pass, but it's easier to log missing events
// when we have all the posthog/network@1 events first
@@ -83,93 +166,27 @@ export function matchNetworkEvents(snapshotsByWindowId: Record {
+ const data: PerformanceEvent = mapRRWebNetworkRequest(capturedRequest, windowId, snapshot.timestamp)
- eventsMapping[eventName] = eventsMapping[eventName] || {}
- eventsMapping[eventName][startTime] = eventsMapping[eventName][startTime] || []
- eventsMapping[eventName][startTime].push(mappedData)
+ rrwebEvents.push(data)
+ })
}
})
})
- // // now we have all the posthog/network@1 events we can try to match any rrweb/network@1 events
- // Object.entries(snapshotsByWindowId).forEach((snapshotsByWindowId) => {
- // const snapshots = snapshotsByWindowId[1]
- // snapshots.forEach((snapshot: eventWithTime) => {
- // if (
- // snapshot.type === 6 && // RRWeb plugin event type
- // snapshot.data.plugin === RRWEB_NETWORK_PLUGIN_NAME
- // ) {
- // const payload = snapshot.data.payload as any
- // if (!Array.isArray(payload.requests) || payload.requests.length === 0) {
- // return
- // }
- //
- // payload.requests.forEach((capturedRequest: any) => {
- // const matchedURL = eventsMapping[capturedRequest.url]
- //
- // const matchedStartTime = matchedURL ? matchedURL[capturedRequest.startTime] : null
- //
- // if (matchedStartTime && matchedStartTime.length === 1) {
- // matchedStartTime[0].response_status = capturedRequest.status
- // matchedStartTime[0].request_headers = capturedRequest.requestHeaders
- // matchedStartTime[0].request_body = capturedRequest.requestBody
- // matchedStartTime[0].response_headers = capturedRequest.responseHeaders
- // matchedStartTime[0].response_body = capturedRequest.responseBody
- // matchedStartTime[0].method = capturedRequest.method
- // } else if (matchedStartTime && matchedStartTime.length > 1) {
- // // find in eventsMapping[capturedRequest.url][capturedRequest.startTime] by matching capturedRequest.endTime and element.response_end
- // const matchedEndTime = matchedStartTime.find(
- // (x) =>
- // typeof x.response_end === 'number' &&
- // Math.round(x.response_end) === capturedRequest.endTime
- // )
- // if (matchedEndTime) {
- // matchedEndTime.response_status = capturedRequest.status
- // matchedEndTime.request_headers = capturedRequest.requestHeaders
- // matchedEndTime.request_body = capturedRequest.requestBody
- // matchedEndTime.response_headers = capturedRequest.responseHeaders
- // matchedEndTime.response_body = capturedRequest.responseBody
- // matchedEndTime.method = capturedRequest.method
- // } else {
- // const capturedURL = new URL(capturedRequest.url)
- // const capturedPath = capturedURL.pathname
- //
- // if (!IGNORED_POSTHOG_PATHS.some((x) => capturedPath === x)) {
- // posthog.capture('Had matches but still could not match rrweb/network@1 event', {
- // rrwebNetworkEvent: payload,
- // possibleMatches: matchedStartTime,
- // totalMatchedURLs: Object.keys(eventsMapping).length,
- // })
- // }
- // }
- // } else {
- // const capturedURL = new URL(capturedRequest.url)
- // const capturedPath = capturedURL.pathname
- // if (!IGNORED_POSTHOG_PATHS.some((x) => capturedPath === x)) {
- // posthog.capture('Could not match rrweb/network@1 event', {
- // rrwebNetworkEvent: payload,
- // possibleMatches: eventsMapping[capturedRequest.url],
- // totalMatchedURLs: Object.keys(eventsMapping).length,
- // })
- // }
- // }
- // })
- // }
- // })
- // })
-
- // now flatten the eventsMapping into a single array
- return Object.values(eventsMapping).reduce((acc: PerformanceEvent[], eventsByURL) => {
- Object.values(eventsByURL).forEach((eventsByTime) => {
- acc.push(...eventsByTime)
- })
- return acc
- }, [])
+ return events.length ? events : rrwebEvents
}
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index e10cf5628fc73..1273d98ef07e7 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -1151,6 +1151,10 @@ export interface PerformanceEvent {
request_body?: Body
response_body?: Body
method?: string
+
+ //rrweb/network@1 - i.e. not in ClickHouse table
+ is_initial?: boolean
+ raw?: Record
}
export interface CurrentBillCycleType {
diff --git a/package.json b/package.json
index 4587978c3a728..e510b90210c08 100644
--- a/package.json
+++ b/package.json
@@ -136,7 +136,7 @@
"monaco-editor": "^0.39.0",
"papaparse": "^5.4.1",
"pmtiles": "^2.11.0",
- "posthog-js": "1.92.0",
+ "posthog-js": "1.92.1",
"posthog-js-lite": "2.0.0-alpha5",
"prettier": "^2.8.8",
"prop-types": "^15.7.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 730433090dd94..29c30c7e95f81 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -216,8 +216,8 @@ dependencies:
specifier: ^2.11.0
version: 2.11.0
posthog-js:
- specifier: 1.92.0
- version: 1.92.0
+ specifier: 1.92.1
+ version: 1.92.1
posthog-js-lite:
specifier: 2.0.0-alpha5
version: 2.0.0-alpha5
@@ -15793,8 +15793,8 @@ packages:
resolution: {integrity: sha512-tlkBdypJuvK/s00n4EiQjwYVfuuZv6vt8BF3g1ooIQa2Gz9Vz80p8q3qsPLZ0V5ErGRy6i3Q4fWC9TDzR7GNRQ==}
dev: false
- /posthog-js@1.92.0:
- resolution: {integrity: sha512-87bZ/qwBbIqvkIV4YYn65oIPEsRcWihA3jX7WV33LvZWaU1InlE6cwj95SleIVLiND4Ofm+cKXZeWwcRnrXkKA==}
+ /posthog-js@1.92.1:
+ resolution: {integrity: sha512-xtuTfM/acfDauiEfIdKF6d911KUZQ7RLii2COAYEoPWr3cVUFoNUoRQz9QJvgDlV2j22Zwl+mnXacUeua+Yi1A==}
dependencies:
fflate: 0.4.8
dev: false