From 50b1f90b318a3c6bf83edd3f649d18b65c4a3a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Zugmeyer?= Date: Mon, 25 Apr 2022 17:10:26 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20[RUMF-1214]=20implement=20view.frus?= =?UTF-8?q?tration.count?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../view/trackViewMetrics.spec.ts | 32 +++++++++++++++- .../view/trackViewMetrics.ts | 1 + .../view/viewCollection.spec.ts | 4 ++ .../view/viewCollection.ts | 3 ++ .../src/domain/trackEventCounts.spec.ts | 38 +++++++++++++------ .../rum-core/src/domain/trackEventCounts.ts | 9 ++++- packages/rum-core/src/rawRumEvent.types.ts | 1 + packages/rum-core/test/fixtures.ts | 1 + test/e2e/scenario/rum/actions.scenario.ts | 8 ++++ 9 files changed, 81 insertions(+), 16 deletions(-) diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.spec.ts index c2640b2061..b001202c3a 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.spec.ts @@ -4,7 +4,7 @@ import type { RumEvent } from '../../../rumEvent.types' import type { TestSetupBuilder, ViewTest } from '../../../../test/specHelper' import { setup, setupViewTest } from '../../../../test/specHelper' import type { RumPerformanceNavigationTiming } from '../../../browser/performanceCollection' -import { RumEventType } from '../../../rawRumEvent.types' +import { FrustrationType, RumEventType } from '../../../rawRumEvent.types' import type { LifeCycle } from '../../lifeCycle' import { LifeCycleEventType } from '../../lifeCycle' import { PAGE_ACTIVITY_END_DELAY, PAGE_ACTIVITY_VALIDATION_DELAY } from '../../waitIdlePage' @@ -226,7 +226,10 @@ describe('rum track view metrics', () => { expect(getViewUpdateCount()).toEqual(1) expect(getViewUpdate(0).eventCounts.actionCount).toEqual(0) - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { type: RumEventType.ACTION } as RumEvent & Context) + lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { + type: RumEventType.ACTION, + action: { type: 'custom' }, + } as RumEvent & Context) startView() expect(getViewUpdateCount()).toEqual(3) @@ -234,6 +237,29 @@ describe('rum track view metrics', () => { expect(getViewUpdate(2).eventCounts.actionCount).toEqual(0) }) + it('should track frustration count', () => { + const { lifeCycle } = setupBuilder.build() + const { getViewUpdate, getViewUpdateCount, startView } = viewTest + + expect(getViewUpdateCount()).toEqual(1) + expect(getViewUpdate(0).eventCounts.frustrationCount).toEqual(0) + + lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { + type: RumEventType.ACTION, + action: { + type: 'click', + frustration: { + type: [FrustrationType.DEAD, FrustrationType.ERROR], + }, + }, + } as RumEvent & Context) + startView() + + expect(getViewUpdateCount()).toEqual(3) + expect(getViewUpdate(1).eventCounts.frustrationCount).toEqual(2) + expect(getViewUpdate(2).eventCounts.frustrationCount).toEqual(0) + }) + it('should reset event count when the view changes', () => { const { lifeCycle, changeLocation } = setupBuilder.build() const { getViewUpdate, getViewUpdateCount, startView } = viewTest @@ -267,6 +293,7 @@ describe('rum track view metrics', () => { longTaskCount: 0, resourceCount: 0, actionCount: 0, + frustrationCount: 0, }) lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { type: RumEventType.RESOURCE } as RumEvent & Context) @@ -281,6 +308,7 @@ describe('rum track view metrics', () => { longTaskCount: 0, resourceCount: 1, actionCount: 0, + frustrationCount: 0, }) }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.ts b/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.ts index dd33f460c7..a972bb74fb 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/view/trackViewMetrics.ts @@ -28,6 +28,7 @@ export function trackViewMetrics( longTaskCount: 0, resourceCount: 0, actionCount: 0, + frustrationCount: 0, }, } const { stop: stopEventCountsTracking } = trackEventCounts(lifeCycle, (newEventCounts) => { diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.spec.ts index d9455ef9ee..5b1d64686a 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.spec.ts @@ -21,6 +21,7 @@ const VIEW: ViewEvent = { longTaskCount: 10, resourceCount: 10, actionCount: 10, + frustrationCount: 10, }, id: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', name: undefined, @@ -89,6 +90,9 @@ describe('viewCollection', () => { action: { count: 10, }, + frustration: { + count: 10, + }, cumulative_layout_shift: 1, custom_timings: { bar: (20 * 1e6) as ServerDuration, diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.ts b/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.ts index 726483e914..dabd658a2a 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.ts @@ -55,6 +55,9 @@ function processViewUpdate( action: { count: view.eventCounts.actionCount, }, + frustration: { + count: view.eventCounts.frustrationCount, + }, cumulative_layout_shift: view.cumulativeLayoutShift, dom_complete: toServerDuration(view.timings.domComplete), dom_content_loaded: toServerDuration(view.timings.domContentLoaded), diff --git a/packages/rum-core/src/domain/trackEventCounts.spec.ts b/packages/rum-core/src/domain/trackEventCounts.spec.ts index b9747c350d..d61cc13ca1 100644 --- a/packages/rum-core/src/domain/trackEventCounts.spec.ts +++ b/packages/rum-core/src/domain/trackEventCounts.spec.ts @@ -1,7 +1,7 @@ import type { Context } from '@datadog/browser-core' import { objectValues } from '@datadog/browser-core' import type { RumEvent } from '../rumEvent.types' -import { RumEventType } from '../rawRumEvent.types' +import { FrustrationType, RumEventType } from '../rawRumEvent.types' import { LifeCycle, LifeCycleEventType } from './lifeCycle' import type { EventCounts } from './trackEventCounts' import { trackEventCounts } from './trackEventCounts' @@ -13,46 +13,60 @@ describe('trackEventCounts', () => { lifeCycle = new LifeCycle() }) - function notifyCollectedRawRumEvent(type: RumEventType) { - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { type } as RumEvent & Context) + function notifyCollectedRawRumEvent(partialEvent: Partial) { + lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, partialEvent as RumEvent & Context) } it('tracks errors', () => { const { eventCounts } = trackEventCounts(lifeCycle) - notifyCollectedRawRumEvent(RumEventType.ERROR) + notifyCollectedRawRumEvent({ type: RumEventType.ERROR }) expect(eventCounts.errorCount).toBe(1) }) it('tracks long tasks', () => { const { eventCounts } = trackEventCounts(lifeCycle) - notifyCollectedRawRumEvent(RumEventType.LONG_TASK) + notifyCollectedRawRumEvent({ type: RumEventType.LONG_TASK }) expect(eventCounts.longTaskCount).toBe(1) }) it("doesn't track views", () => { const { eventCounts } = trackEventCounts(lifeCycle) - notifyCollectedRawRumEvent(RumEventType.VIEW) + notifyCollectedRawRumEvent({ type: RumEventType.VIEW }) expect(objectValues(eventCounts as unknown as { [key: string]: number }).every((value) => value === 0)).toBe(true) }) it('tracks actions', () => { const { eventCounts } = trackEventCounts(lifeCycle) - notifyCollectedRawRumEvent(RumEventType.ACTION) + notifyCollectedRawRumEvent({ type: RumEventType.ACTION, action: { type: 'custom' } }) expect(eventCounts.actionCount).toBe(1) }) it('tracks resources', () => { const { eventCounts } = trackEventCounts(lifeCycle) - notifyCollectedRawRumEvent(RumEventType.RESOURCE) + notifyCollectedRawRumEvent({ type: RumEventType.RESOURCE }) expect(eventCounts.resourceCount).toBe(1) }) + it('tracks frustration counts', () => { + const { eventCounts } = trackEventCounts(lifeCycle) + notifyCollectedRawRumEvent({ + type: RumEventType.ACTION, + action: { + type: 'click', + frustration: { + type: [FrustrationType.ERROR, FrustrationType.DEAD], + }, + }, + }) + expect(eventCounts.frustrationCount).toBe(2) + }) + it('stops tracking when stop is called', () => { const { eventCounts, stop } = trackEventCounts(lifeCycle) - notifyCollectedRawRumEvent(RumEventType.RESOURCE) + notifyCollectedRawRumEvent({ type: RumEventType.RESOURCE }) expect(eventCounts.resourceCount).toBe(1) stop() - notifyCollectedRawRumEvent(RumEventType.RESOURCE) + notifyCollectedRawRumEvent({ type: RumEventType.RESOURCE }) expect(eventCounts.resourceCount).toBe(1) }) @@ -60,11 +74,11 @@ describe('trackEventCounts', () => { const spy = jasmine.createSpy<(eventCounts: EventCounts) => void>() trackEventCounts(lifeCycle, spy) - notifyCollectedRawRumEvent(RumEventType.RESOURCE) + notifyCollectedRawRumEvent({ type: RumEventType.RESOURCE }) expect(spy).toHaveBeenCalledTimes(1) expect(spy.calls.mostRecent().args[0].resourceCount).toBe(1) - notifyCollectedRawRumEvent(RumEventType.RESOURCE) + notifyCollectedRawRumEvent({ type: RumEventType.RESOURCE }) expect(spy).toHaveBeenCalledTimes(2) expect(spy.calls.mostRecent().args[0].resourceCount).toBe(2) }) diff --git a/packages/rum-core/src/domain/trackEventCounts.ts b/packages/rum-core/src/domain/trackEventCounts.ts index 2be246b1e7..1dd32e0b0d 100644 --- a/packages/rum-core/src/domain/trackEventCounts.ts +++ b/packages/rum-core/src/domain/trackEventCounts.ts @@ -8,6 +8,7 @@ export interface EventCounts { actionCount: number longTaskCount: number resourceCount: number + frustrationCount: number } export function trackEventCounts(lifeCycle: LifeCycle, callback: (eventCounts: EventCounts) => void = noop) { @@ -16,16 +17,20 @@ export function trackEventCounts(lifeCycle: LifeCycle, callback: (eventCounts: E longTaskCount: 0, resourceCount: 0, actionCount: 0, + frustrationCount: 0, } - const subscription = lifeCycle.subscribe(LifeCycleEventType.RUM_EVENT_COLLECTED, ({ type }): void => { - switch (type) { + const subscription = lifeCycle.subscribe(LifeCycleEventType.RUM_EVENT_COLLECTED, (event): void => { + switch (event.type) { case RumEventType.ERROR: eventCounts.errorCount += 1 callback(eventCounts) break case RumEventType.ACTION: eventCounts.actionCount += 1 + if (event.action.frustration) { + eventCounts.frustrationCount += event.action.frustration.type.length + } callback(eventCounts) break case RumEventType.LONG_TASK: diff --git a/packages/rum-core/src/rawRumEvent.types.ts b/packages/rum-core/src/rawRumEvent.types.ts index 9aa36b7022..2b252115ca 100644 --- a/packages/rum-core/src/rawRumEvent.types.ts +++ b/packages/rum-core/src/rawRumEvent.types.ts @@ -89,6 +89,7 @@ export interface RawRumViewEvent { action: Count long_task: Count resource: Count + frustration: Count in_foreground_periods?: InForegroundPeriod[] } session: { diff --git a/packages/rum-core/test/fixtures.ts b/packages/rum-core/test/fixtures.ts index 3ad577d389..931c986351 100644 --- a/packages/rum-core/test/fixtures.ts +++ b/packages/rum-core/test/fixtures.ts @@ -73,6 +73,7 @@ export function createRawRumEvent(type: RumEventType, overrides?: Context): RawR view: { id: generateUUID(), action: { count: 0 }, + frustration: { count: 0 }, error: { count: 0 }, is_active: true, loading_type: ViewLoadingType.INITIAL_LOAD, diff --git a/test/e2e/scenario/rum/actions.scenario.ts b/test/e2e/scenario/rum/actions.scenario.ts index ee7a824a51..9fb2851be1 100644 --- a/test/e2e/scenario/rum/actions.scenario.ts +++ b/test/e2e/scenario/rum/actions.scenario.ts @@ -115,6 +115,9 @@ describe('action collection', () => { expect(actionEvents.length).toBe(1) expect(actionEvents[0].action.frustration!.type).toEqual(['error']) expect(actionEvents[0].action.error!.count).toBe(1) + + expect(serverEvents.rumViews[0].view.frustration!.count).toBe(1) + await withBrowserLogs((browserLogs) => { expect(browserLogs.length).toEqual(1) }) @@ -131,6 +134,8 @@ describe('action collection', () => { expect(actionEvents.length).toBe(1) expect(actionEvents[0].action.frustration!.type).toEqual(['dead']) + + expect(serverEvents.rumViews[0].view.frustration!.count).toBe(1) }) createTest('collect multiple frustrations in one action') @@ -154,6 +159,9 @@ describe('action collection', () => { expect(actionEvents.length).toBe(1) expect(actionEvents[0].action.frustration!.type).toEqual(jasmine.arrayWithExactContents(['error', 'dead'])) + + expect(serverEvents.rumViews[0].view.frustration!.count).toBe(2) + await withBrowserLogs((browserLogs) => { expect(browserLogs.length).toEqual(1) })