Skip to content
Merged
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
Expand Up @@ -6,6 +6,7 @@ import { default as clsx } from 'clsx'
import type { TelemetryEvent } from '../../../../../../packages/core/src/domain/telemetry'
import type { LogsEvent } from '../../../../../../packages/logs/src/logsEvent.types'
import type {
DurationProperties,
RumActionEvent,
RumErrorEvent,
RumLongTaskEvent,
Expand Down Expand Up @@ -371,7 +372,7 @@ function LongTaskDescription({ event }: { event: RumLongTaskEvent }) {

function VitalDescription({ event }: { event: RumVitalEvent }) {
const vitalName = event.vital.name
const vitalValue = event.vital.duration
const vitalValue = (event.vital as DurationProperties).duration
const vitalDescription = event.vital.description
return (
<>
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/tools/experimentalFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export enum ExperimentalFeature {
TRACK_INTAKE_REQUESTS = 'track_intake_requests',
WRITABLE_RESOURCE_GRAPHQL = 'writable_resource_graphql',
EARLY_REQUEST_COLLECTION = 'early_request_collection',
WATCH_COOKIE_WITHOUT_LOCK = 'watch_cookie_without_lock',
USE_TREE_WALKER_FOR_ACTION_NAME = 'use_tree_walker_for_action_name',
FEATURE_OPERATION_VITAL = 'feature_operation_vital',
SHORT_SESSION_INVESTIGATION = 'short_session_investigation',
}

Expand Down
11 changes: 11 additions & 0 deletions packages/rum-core/src/boot/preStartRum.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,17 @@ describe('preStartRum', () => {
strategy.init(DEFAULT_INIT_CONFIGURATION, PUBLIC_API)
expect(addDurationVitalSpy).toHaveBeenCalledOnceWith(vitalAdd)
})

it('addOperationStepVital', () => {
const addOperationStepVitalSpy = jasmine.createSpy()
doStartRumSpy.and.returnValue({
addOperationStepVital: addOperationStepVitalSpy,
} as unknown as StartRumResult)

strategy.addOperationStepVital('foo', 'start')
strategy.init(DEFAULT_INIT_CONFIGURATION, PUBLIC_API)
expect(addOperationStepVitalSpy).toHaveBeenCalledOnceWith('foo', 'start', undefined, undefined)
})
})

describe('tracking consent', () => {
Expand Down
25 changes: 24 additions & 1 deletion packages/rum-core/src/boot/preStartRum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
buildGlobalContextManager,
buildUserContextManager,
monitorError,
sanitize,
} from '@datadog/browser-core'
import type { RumConfiguration, RumInitConfiguration } from '../domain/configuration'
import {
Expand All @@ -25,7 +26,12 @@ import {
serializeRumConfiguration,
} from '../domain/configuration'
import type { ViewOptions } from '../domain/view/trackViews'
import type { DurationVital, CustomVitalsState } from '../domain/vital/vitalCollection'
import type {
DurationVital,
CustomVitalsState,
FeatureOperationOptions,
FailureReason,
} from '../domain/vital/vitalCollection'
import { startDurationVital, stopDurationVital } from '../domain/vital/vitalCollection'
import { callPluginsMethod } from '../domain/plugins'
import type { StartRumResult } from './startRum'
Expand Down Expand Up @@ -148,6 +154,22 @@ export function createPreStartStrategy(
bufferApiCalls.add((startRumResult) => startRumResult.addDurationVital(vital))
}

const addOperationStepVital = (
name: string,
stepType: 'start' | 'end',
options?: FeatureOperationOptions,
failureReason?: FailureReason
) => {
bufferApiCalls.add((startRumResult) =>
startRumResult.addOperationStepVital(
sanitize(name)!,
stepType,
sanitize(options) as FeatureOperationOptions,
sanitize(failureReason) as FailureReason | undefined
)
)
}

const strategy: Strategy = {
init(initConfiguration, publicApi) {
if (!initConfiguration) {
Expand Down Expand Up @@ -248,6 +270,7 @@ export function createPreStartStrategy(
},

addDurationVital,
addOperationStepVital,
}

return strategy
Expand Down
52 changes: 52 additions & 0 deletions packages/rum-core/src/boot/rumPublicApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const noopStartRum = (): ReturnType<StartRum> => ({
accountContext: {} as any,
hooks: {} as any,
telemetry: {} as any,
addOperationStepVital: () => undefined,
})
const DEFAULT_INIT_CONFIGURATION = { applicationId: 'xxx', clientToken: 'xxx' }
const FAKE_WORKER = {} as DeflateWorker
Expand Down Expand Up @@ -784,6 +785,57 @@ describe('rum public api', () => {
})
})

describe('startFeatureOperation', () => {
it('should call addOperationStepVital on the startRum result with start status', () => {
const addOperationStepVitalSpy = jasmine.createSpy()
const rumPublicApi = makeRumPublicApi(
() => ({ ...noopStartRum(), addOperationStepVital: addOperationStepVitalSpy }),
noopRecorderApi,
noopProfilerApi
)
rumPublicApi.init(DEFAULT_INIT_CONFIGURATION)
rumPublicApi.startFeatureOperation('foo', { operationKey: '00000000-0000-0000-0000-000000000000' })
expect(addOperationStepVitalSpy).toHaveBeenCalledWith('foo', 'start', {
operationKey: '00000000-0000-0000-0000-000000000000',
})
})
})

describe('succeedFeatureOperation', () => {
it('should call addOperationStepVital on the startRum result with end status', () => {
const addOperationStepVitalSpy = jasmine.createSpy()
const rumPublicApi = makeRumPublicApi(
() => ({ ...noopStartRum(), addOperationStepVital: addOperationStepVitalSpy }),
noopRecorderApi,
noopProfilerApi
)
rumPublicApi.init(DEFAULT_INIT_CONFIGURATION)
rumPublicApi.succeedFeatureOperation('foo', { operationKey: '00000000-0000-0000-0000-000000000000' })
expect(addOperationStepVitalSpy).toHaveBeenCalledWith('foo', 'end', {
operationKey: '00000000-0000-0000-0000-000000000000',
})
})
})

describe('failFeatureOperation', () => {
it('should call addOperationStepVital on the startRum result with end status and failure reason', () => {
const addOperationStepVitalSpy = jasmine.createSpy()
const rumPublicApi = makeRumPublicApi(
() => ({ ...noopStartRum(), addOperationStepVital: addOperationStepVitalSpy }),
noopRecorderApi,
noopProfilerApi
)
rumPublicApi.init(DEFAULT_INIT_CONFIGURATION)
rumPublicApi.failFeatureOperation('foo', 'error', { operationKey: '00000000-0000-0000-0000-000000000000' })
expect(addOperationStepVitalSpy).toHaveBeenCalledWith(
'foo',
'end',
{ operationKey: '00000000-0000-0000-0000-000000000000' },
'error'
)
})
})

it('should provide sdk version', () => {
const rumPublicApi = makeRumPublicApi(noopStartRum, noopRecorderApi, noopProfilerApi)
expect(rumPublicApi.version).toBe('test')
Expand Down
50 changes: 48 additions & 2 deletions packages/rum-core/src/boot/rumPublicApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ import type { RumConfiguration, RumInitConfiguration } from '../domain/configura
import type { ViewOptions } from '../domain/view/trackViews'
import type {
AddDurationVitalOptions,
DurationVitalOptions,
DurationVitalReference,
DurationVitalOptions,
FeatureOperationOptions,
FailureReason,
} from '../domain/vital/vitalCollection'
import { createCustomVitalsState } from '../domain/vital/vitalCollection'
import { callPluginsMethod } from '../domain/plugins'
Expand Down Expand Up @@ -424,9 +426,37 @@ export interface RumPublicApi extends PublicApi {
*
* @category Vital
* @param nameOrRef - Name or reference of the custom vital
* @param options - Options for the custom vital (context, description)
* @param options - Options for the custom vital (operationKey, context, description)
*/
stopDurationVital: (nameOrRef: string | DurationVitalReference, options?: DurationVitalOptions) => void

/**
* [Experimental] start a feature operation
*
* @category Vital
* @param name - Name of the operation step
* @param options - Options for the operation step (operationKey, context, description)
*/
startFeatureOperation: (name: string, options?: FeatureOperationOptions) => void

/**
* [Experimental] succeed a feature operation
*
* @category Vital
* @param name - Name of the operation step
* @param options - Options for the operation step (operationKey, context, description)
*/
succeedFeatureOperation: (name: string, options?: FeatureOperationOptions) => void

/**
* [Experimental] fail a feature operation
*
* @category Vital
* @param name - Name of the operation step
* @param failureReason
* @param options - Options for the operation step (operationKey, context, description)
*/
failFeatureOperation: (name: string, failureReaon: FailureReason, options?: FeatureOperationOptions) => void
}

export interface RecorderApi {
Expand Down Expand Up @@ -494,6 +524,7 @@ export interface Strategy {
startDurationVital: StartRumResult['startDurationVital']
stopDurationVital: StartRumResult['stopDurationVital']
addDurationVital: StartRumResult['addDurationVital']
addOperationStepVital: StartRumResult['addOperationStepVital']
}

export function makeRumPublicApi(
Expand Down Expand Up @@ -771,6 +802,21 @@ export function makeRumPublicApi(
description: sanitize(options && options.description) as string | undefined,
})
}),

startFeatureOperation: monitor((name, options) => {
addTelemetryUsage({ feature: 'add-operation-step-vital', action_type: 'start' })
strategy.addOperationStepVital(name, 'start', options)
}),

succeedFeatureOperation: monitor((name, options) => {
addTelemetryUsage({ feature: 'add-operation-step-vital', action_type: 'succeed' })
strategy.addOperationStepVital(name, 'end', options)
}),

failFeatureOperation: monitor((name, failureReason, options) => {
addTelemetryUsage({ feature: 'add-operation-step-vital', action_type: 'fail' })
strategy.addOperationStepVital(name, 'end', options, failureReason)
}),
})

return rumPublicApi
Expand Down
1 change: 1 addition & 0 deletions packages/rum-core/src/boot/startRum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ export function startRum(
startDurationVital: vitalCollection.startDurationVital,
stopDurationVital: vitalCollection.stopDurationVital,
addDurationVital: vitalCollection.addDurationVital,
addOperationStepVital: vitalCollection.addOperationStepVital,
globalContext,
userContext,
accountContext,
Expand Down
54 changes: 47 additions & 7 deletions packages/rum-core/src/domain/vital/vitalCollection.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Duration } from '@datadog/browser-core'
import { mockClock, type Clock } from '@datadog/browser-core/test'
import { clocksNow } from '@datadog/browser-core'
import { mockClock, mockExperimentalFeatures, type Clock } from '@datadog/browser-core/test'
import { clocksNow, ExperimentalFeature } from '@datadog/browser-core'
import { collectAndValidateRawRumEvents, mockPageStateHistory } from '../../../test'
import type { RawRumEvent, RawRumVitalEvent } from '../../rawRumEvent.types'
import { VitalType, RumEventType } from '../../rawRumEvent.types'
Expand Down Expand Up @@ -220,16 +220,33 @@ describe('vitalCollection', () => {
},
context: undefined,
type: RumEventType.VITAL,
_dd: {
vital: {
computed_value: true,
},
})
expect(rawRumEvents[0].domainContext).toEqual({})
})

it('should collect raw rum event from operation step vital', () => {
mockExperimentalFeatures([ExperimentalFeature.FEATURE_OPERATION_VITAL])
vitalCollection.addOperationStepVital('foo', 'start')

expect(rawRumEvents[0].startTime).toEqual(jasmine.any(Number))
expect(rawRumEvents[0].rawRumEvent).toEqual({
date: jasmine.any(Number),
vital: {
id: jasmine.any(String),
type: VitalType.OPERATION_STEP,
name: 'foo',
step_type: 'start',
operation_key: undefined,
failure_reason: undefined,
description: undefined,
},
context: undefined,
type: RumEventType.VITAL,
})
expect(rawRumEvents[0].domainContext).toEqual({})
})

it('should create a vital from add API', () => {
it('should create a duration vital from add API', () => {
vitalCollection.addDurationVital({
name: 'foo',
type: VitalType.DURATION,
Expand All @@ -244,6 +261,29 @@ describe('vitalCollection', () => {
expect((rawRumEvents[0].rawRumEvent as RawRumVitalEvent).vital.description).toBe('baz')
expect((rawRumEvents[0].rawRumEvent as RawRumVitalEvent).context).toEqual({ foo: 'bar' })
})

it('should create a operation step vital from add API', () => {
mockExperimentalFeatures([ExperimentalFeature.FEATURE_OPERATION_VITAL])
vitalCollection.addOperationStepVital(
'foo',
'end',
{
operationKey: '00000000-0000-0000-0000-000000000000',
context: { foo: 'bar' },
description: 'baz',
},
'error'
)

expect(rawRumEvents.length).toBe(1)
expect((rawRumEvents[0].rawRumEvent as RawRumVitalEvent).vital.step_type).toBe('end')
expect((rawRumEvents[0].rawRumEvent as RawRumVitalEvent).vital.operation_key).toBe(
'00000000-0000-0000-0000-000000000000'
)
expect((rawRumEvents[0].rawRumEvent as RawRumVitalEvent).vital.failure_reason).toBe('error')
expect((rawRumEvents[0].rawRumEvent as RawRumVitalEvent).vital.description).toBe('baz')
expect((rawRumEvents[0].rawRumEvent as RawRumVitalEvent).context).toEqual({ foo: 'bar' })
})
})
})
})
Loading