Skip to content

Commit

Permalink
👷 [REPLAY-922] Generate Session Replay types from JSON-schema (#1652)
Browse files Browse the repository at this point in the history
* Add generation of Browser Session Replay types from schema
  • Loading branch information
acorretti committed Jul 27, 2022
1 parent 333ebaa commit e0d3b67
Show file tree
Hide file tree
Showing 24 changed files with 882 additions and 448 deletions.
4 changes: 4 additions & 0 deletions packages/rum-core/src/rumEvent.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,10 @@ export interface CommonProperties {
* Device marketing brand, e.g. Apple, OPPO, Xiaomi, etc.
*/
readonly brand?: string
/**
* The CPU architecture of the device that is reporting the error
*/
readonly architecture?: string
[k: string]: unknown
}
/**
Expand Down
5 changes: 3 additions & 2 deletions packages/rum/src/boot/startRecording.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { setup } from '../../../rum-core/test/specHelper'
import { collectAsyncCalls, recordsPerFullSnapshot } from '../../test/utils'
import { setSegmentBytesLimit, startDeflateWorker } from '../domain/segmentCollection'

import type { Segment } from '../types'
import type { BrowserSegment } from '../types'
import { RecordType } from '../types'
import { resetReplayStats } from '../domain/replayStats'
import { startRecording } from './startRecording'
Expand Down Expand Up @@ -93,6 +93,7 @@ describe('startRecording', () => {
raw_segment_size: jasmine.stringMatching(/^\d+$/),
'view.id': 'view-id',
index_in_view: '0',
source: 'browser',
})
expectNoExtraRequestSendCalls(done)
})
Expand Down Expand Up @@ -291,7 +292,7 @@ function getRequestData(call: jasmine.CallInfo<HttpRequest['send']>) {
return result
}

function readRequestSegment(call: jasmine.CallInfo<HttpRequest['send']>, callback: (segment: Segment) => void) {
function readRequestSegment(call: jasmine.CallInfo<HttpRequest['send']>, callback: (segment: BrowserSegment) => void) {
const encodedSegment = getRequestFormData(call).get('segment')
expect(encodedSegment).toBeInstanceOf(Blob)
const reader = new FileReader()
Expand Down
8 changes: 4 additions & 4 deletions packages/rum/src/domain/record/observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ import type {
InputState,
MousePosition,
MouseInteraction,
MutationPayload,
BrowserMutationPayload,
ScrollPosition,
StyleSheetRule,
ViewportResizeDimension,
MediaInteraction,
FocusRecord,
VisualViewportRecord,
FrustrationRecord,
IncrementalSnapshotRecord,
BrowserIncrementalSnapshotRecord,
MouseInteractionData,
} from '../../types'
import { RecordType, IncrementalSource, MediaInteractionType, MouseInteractionType } from '../../types'
Expand Down Expand Up @@ -58,9 +58,9 @@ type MousemoveCallBack = (
source: typeof IncrementalSource.MouseMove | typeof IncrementalSource.TouchMove
) => void

export type MutationCallBack = (m: MutationPayload) => void
export type MutationCallBack = (m: BrowserMutationPayload) => void

type MouseInteractionCallBack = (record: IncrementalSnapshotRecord) => void
type MouseInteractionCallBack = (record: BrowserIncrementalSnapshotRecord) => void

type ScrollCallback = (p: ScrollPosition) => void

Expand Down
18 changes: 9 additions & 9 deletions packages/rum/src/domain/record/record.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { LifeCycle } from '@datadog/browser-rum-core'
import type { Clock } from '../../../../core/test/specHelper'
import { createNewEvent } from '../../../../core/test/specHelper'
import { collectAsyncCalls, recordsPerFullSnapshot } from '../../../test/utils'
import type { IncrementalSnapshotRecord, FocusRecord, Record } from '../../types'
import type { BrowserIncrementalSnapshotRecord, BrowserRecord, FocusRecord } from '../../types'
import { RecordType, IncrementalSource } from '../../types'
import type { RecordAPI } from './record'
import { record } from './record'

describe('record', () => {
let sandbox: HTMLElement
let recordApi: RecordAPI
let emitSpy: jasmine.Spy<(record: Record) => void>
let emitSpy: jasmine.Spy<(record: BrowserRecord) => void>
let waitEmitCalls: (expectedCallsCount: number, callback: () => void) => void
let expectNoExtraEmitCalls: (done: () => void) => void
let clock: Clock | undefined
Expand Down Expand Up @@ -65,42 +65,42 @@ describe('record', () => {
}

expect(records[i].type).toEqual(RecordType.IncrementalSnapshot)
expect((records[i++] as IncrementalSnapshotRecord).data).toEqual(
expect((records[i++] as BrowserIncrementalSnapshotRecord).data).toEqual(
jasmine.objectContaining({
source: IncrementalSource.StyleSheetRule,
adds: [{ rule: 'body { background: #000; }', index: undefined }],
})
)
expect(records[i].type).toEqual(RecordType.IncrementalSnapshot)
expect((records[i++] as IncrementalSnapshotRecord).data).toEqual(
expect((records[i++] as BrowserIncrementalSnapshotRecord).data).toEqual(
jasmine.objectContaining({
source: IncrementalSource.StyleSheetRule,
adds: [{ rule: 'body { background: #111; }', index: undefined }],
})
)
expect(records[i].type).toEqual(RecordType.IncrementalSnapshot)
expect((records[i++] as IncrementalSnapshotRecord).data).toEqual(
expect((records[i++] as BrowserIncrementalSnapshotRecord).data).toEqual(
jasmine.objectContaining({
source: IncrementalSource.StyleSheetRule,
removes: [{ index: 0 }],
})
)
expect(records[i].type).toEqual(RecordType.IncrementalSnapshot)
expect((records[i++] as IncrementalSnapshotRecord).data).toEqual(
expect((records[i++] as BrowserIncrementalSnapshotRecord).data).toEqual(
jasmine.objectContaining({
source: IncrementalSource.StyleSheetRule,
adds: [{ rule: 'body { color: #fff; }', index: undefined }],
})
)
expect(records[i].type).toEqual(RecordType.IncrementalSnapshot)
expect((records[i++] as IncrementalSnapshotRecord).data).toEqual(
expect((records[i++] as BrowserIncrementalSnapshotRecord).data).toEqual(
jasmine.objectContaining({
source: IncrementalSource.StyleSheetRule,
removes: [{ index: 0 }],
})
)
expect(records[i].type).toEqual(RecordType.IncrementalSnapshot)
expect((records[i++] as IncrementalSnapshotRecord).data).toEqual(
expect((records[i++] as BrowserIncrementalSnapshotRecord).data).toEqual(
jasmine.objectContaining({
source: IncrementalSource.StyleSheetRule,
adds: [{ rule: 'body { color: #ccc; }', index: undefined }],
Expand Down Expand Up @@ -130,7 +130,7 @@ describe('record', () => {
expect(records[i++].type).toEqual(RecordType.VisualViewport)
}
expect(records[i].type).toEqual(RecordType.IncrementalSnapshot)
expect((records[i++] as IncrementalSnapshotRecord).data.source).toEqual(IncrementalSource.Mutation)
expect((records[i++] as BrowserIncrementalSnapshotRecord).data.source).toEqual(IncrementalSource.Mutation)
expect(records[i++].type).toEqual(RecordType.Meta)
expect(records[i++].type).toEqual(RecordType.Focus)
expect(records[i++].type).toEqual(RecordType.FullSnapshot)
Expand Down
8 changes: 4 additions & 4 deletions packages/rum/src/domain/record/record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import type { DefaultPrivacyLevel, TimeStamp } from '@datadog/browser-core'
import type { LifeCycle } from '@datadog/browser-rum-core'
import { getViewportDimension } from '@datadog/browser-rum-core'
import type {
BrowserMutationData,
BrowserRecord,
InputData,
MediaInteractionData,
MousemoveData,
MutationData,
ScrollData,
StyleSheetRuleData,
ViewportResizeData,
Record,
} from '../../types'
import { RecordType, IncrementalSource } from '../../types'
import { serializeDocument } from './serialize'
Expand All @@ -21,7 +21,7 @@ import { getVisualViewport, getScrollX, getScrollY } from './viewports'
import { assembleIncrementalSnapshot } from './utils'

export interface RecordOptions {
emit?: (record: Record) => void
emit?: (record: BrowserRecord) => void
defaultPrivacyLevel: DefaultPrivacyLevel
lifeCycle: LifeCycle
}
Expand Down Expand Up @@ -94,7 +94,7 @@ export function record(options: RecordOptions): RecordAPI {
emit(assembleIncrementalSnapshot<MediaInteractionData>(IncrementalSource.MediaInteraction, p)),
mouseInteractionCb: (mouseInteractionRecord) => emit(mouseInteractionRecord),
mousemoveCb: (positions, source) => emit(assembleIncrementalSnapshot<MousemoveData>(source, { positions })),
mutationCb: (m) => emit(assembleIncrementalSnapshot<MutationData>(IncrementalSource.Mutation, m)),
mutationCb: (m) => emit(assembleIncrementalSnapshot<BrowserMutationData>(IncrementalSource.Mutation, m)),
scrollCb: (p) => emit(assembleIncrementalSnapshot<ScrollData>(IncrementalSource.Scroll, p)),
styleSheetRuleCb: (r) => emit(assembleIncrementalSnapshot<StyleSheetRuleData>(IncrementalSource.StyleSheetRule, r)),
viewportResizeCb: (d) => emit(assembleIncrementalSnapshot<ViewportResizeData>(IncrementalSource.ViewportResize, d)),
Expand Down
6 changes: 3 additions & 3 deletions packages/rum/src/domain/record/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { assign, timeStampNow } from '@datadog/browser-core'
import type { IncrementalData, IncrementalSnapshotRecord } from '../../types'
import type { BrowserIncrementalData, BrowserIncrementalSnapshotRecord } from '../../types'
import { RecordType } from '../../types'

export function isTouchEvent(event: MouseEvent | TouchEvent): event is TouchEvent {
Expand All @@ -13,10 +13,10 @@ export function forEach<List extends { [index: number]: any }>(
Array.prototype.forEach.call(list, callback as any)
}

export function assembleIncrementalSnapshot<Data extends IncrementalData>(
export function assembleIncrementalSnapshot<Data extends BrowserIncrementalData>(
source: Data['source'],
data: Omit<Data, 'source'>
): IncrementalSnapshotRecord {
): BrowserIncrementalSnapshotRecord {
return {
data: assign(
{
Expand Down
15 changes: 10 additions & 5 deletions packages/rum/src/domain/segmentCollection/segment.spec.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import type { TimeStamp } from '@datadog/browser-core'
import { noop, setDebugMode, display, isIE } from '@datadog/browser-core'
import { MockWorker, parseSegment } from '../../../test/utils'
import type { CreationReason, Record, SegmentContext } from '../../types'
import type { CreationReason, BrowserRecord, SegmentContext } from '../../types'
import { RecordType } from '../../types'
import { getReplayStats, resetReplayStats } from '../replayStats'
import { Segment } from './segment'

const CONTEXT: SegmentContext = { application: { id: 'a' }, view: { id: 'b' }, session: { id: 'c' } }
const RECORD_TIMESTAMP = 10 as TimeStamp
const RECORD: Record = { type: RecordType.ViewEnd, timestamp: RECORD_TIMESTAMP }
const FULL_SNAPSHOT_RECORD: Record = { type: RecordType.FullSnapshot, timestamp: RECORD_TIMESTAMP, data: {} as any }
const RECORD: BrowserRecord = { type: RecordType.ViewEnd, timestamp: RECORD_TIMESTAMP }
const FULL_SNAPSHOT_RECORD: BrowserRecord = {
type: RecordType.FullSnapshot,
timestamp: RECORD_TIMESTAMP,
data: {} as any,
}
const ENCODED_SEGMENT_HEADER_BYTES_COUNT = 12 // {"records":[
const ENCODED_RECORD_BYTES_COUNT = 25
const ENCODED_FULL_SNAPSHOT_RECORD_BYTES_COUNT = 35
const ENCODED_SEPARATOR_BYTES_COUNT = 1 // ,
const ENCODED_META_BYTES_COUNT = 173 // this should stay accurate as long as less than 10 records are added
const ENCODED_META_BYTES_COUNT = 192 // this should stay accurate as long as less than 10 records are added

describe('Segment', () => {
let worker: MockWorker
Expand Down Expand Up @@ -49,6 +53,7 @@ describe('Segment', () => {
expect(onFlushedSpy).toHaveBeenCalledTimes(1)

expect(parseSegment(onFlushedSpy.calls.mostRecent().args[0])).toEqual({
source: 'browser' as const,
creation_reason: 'init' as const,
end: 10,
has_full_snapshot: false,
Expand Down Expand Up @@ -227,7 +232,7 @@ describe('Segment', () => {
onFlushed = noop,
}: {
context?: SegmentContext
initialRecord?: Record
initialRecord?: BrowserRecord
creationReason?: CreationReason
onWrote?: (compressedSegmentBytesCount: number) => void
onFlushed?: (data: Uint8Array, rawBytesCount: number) => void
Expand Down
9 changes: 5 additions & 4 deletions packages/rum/src/domain/segmentCollection/segment.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { addTelemetryDebug, assign, monitor } from '@datadog/browser-core'
import type { CreationReason, Record, SegmentContext, SegmentMetadata } from '../../types'
import type { BrowserRecord, BrowserSegmentMetadata, CreationReason, SegmentContext } from '../../types'
import { RecordType } from '../../types'
import * as replayStats from '../replayStats'
import type { DeflateWorker, DeflateWorkerListener } from './deflateWorker'
Expand All @@ -9,15 +9,15 @@ let nextId = 0
export class Segment {
public isFlushed = false

public readonly metadata: SegmentMetadata
public readonly metadata: BrowserSegmentMetadata

private id = nextId++

constructor(
private worker: DeflateWorker,
context: SegmentContext,
creationReason: CreationReason,
initialRecord: Record,
initialRecord: BrowserRecord,
onWrote: (compressedBytesCount: number) => void,
onFlushed: (data: Uint8Array, rawBytesCount: number) => void
) {
Expand All @@ -31,6 +31,7 @@ export class Segment {
records_count: 1,
has_full_snapshot: initialRecord.type === RecordType.FullSnapshot,
index_in_view: replayStats.getSegmentsCount(viewId),
source: 'browser' as const,
},
context
)
Expand Down Expand Up @@ -68,7 +69,7 @@ export class Segment {
this.worker.postMessage({ data: `{"records":[${JSON.stringify(initialRecord)}`, id: this.id, action: 'write' })
}

addRecord(record: Record): void {
addRecord(record: BrowserRecord): void {
this.metadata.start = Math.min(this.metadata.start, record.timestamp)
this.metadata.end = Math.max(this.metadata.end, record.timestamp)
this.metadata.records_count += 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
setPageVisibility,
} from '@datadog/browser-core/test/specHelper'
import { createRumSessionManagerMock } from '../../../../rum-core/test/mockRumSessionManager'
import type { Record, SegmentContext, SegmentMetadata } from '../../types'
import type { BrowserRecord, BrowserSegmentMetadata, SegmentContext } from '../../types'
import { RecordType } from '../../types'
import { MockWorker } from '../../../test/utils'
import {
Expand All @@ -21,10 +21,10 @@ import {
} from './segmentCollection'

const CONTEXT: SegmentContext = { application: { id: 'a' }, view: { id: 'b' }, session: { id: 'c' } }
const RECORD: Record = { type: RecordType.ViewEnd, timestamp: 10 as TimeStamp }
const RECORD: BrowserRecord = { type: RecordType.ViewEnd, timestamp: 10 as TimeStamp }

// A record that will make the segment size reach the SEGMENT_BYTES_LIMIT
const VERY_BIG_RECORD: Record = {
const VERY_BIG_RECORD: BrowserRecord = {
type: RecordType.FullSnapshot,
timestamp: 10 as TimeStamp,
data: Array(SEGMENT_BYTES_LIMIT).join('a') as any,
Expand All @@ -40,7 +40,7 @@ describe('startSegmentCollection', () => {
const lifeCycle = new LifeCycle()
const worker = new MockWorker()
const eventEmitter = document.createElement('div')
const sendSpy = jasmine.createSpy<(data: Uint8Array, metadata: SegmentMetadata) => void>()
const sendSpy = jasmine.createSpy<(data: Uint8Array, metadata: BrowserSegmentMetadata) => void>()

const { stop, addRecord } = doStartSegmentCollection(lifeCycle, () => context, sendSpy, worker, eventEmitter)
stopSegmentCollection = stop
Expand Down
10 changes: 5 additions & 5 deletions packages/rum/src/domain/segmentCollection/segmentCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { EventEmitter, TimeoutId } from '@datadog/browser-core'
import { ONE_SECOND, addEventListener, DOM_EVENT, monitor } from '@datadog/browser-core'
import type { LifeCycle, ViewContexts, RumSessionManager } from '@datadog/browser-rum-core'
import { LifeCycleEventType } from '@datadog/browser-rum-core'
import type { CreationReason, Record, SegmentContext, SegmentMetadata } from '../../types'
import type { BrowserRecord, BrowserSegmentMetadata, CreationReason, SegmentContext } from '../../types'
import type { DeflateWorker } from './deflateWorker'
import { Segment } from './segment'

Expand Down Expand Up @@ -43,7 +43,7 @@ export function startSegmentCollection(
applicationId: string,
sessionManager: RumSessionManager,
viewContexts: ViewContexts,
send: (data: Uint8Array, metadata: SegmentMetadata, rawSegmentBytesCount: number) => void,
send: (data: Uint8Array, metadata: BrowserSegmentMetadata, rawSegmentBytesCount: number) => void,
worker: DeflateWorker
) {
return doStartSegmentCollection(
Expand Down Expand Up @@ -76,7 +76,7 @@ type SegmentCollectionState =
export function doStartSegmentCollection(
lifeCycle: LifeCycle,
getSegmentContext: () => SegmentContext | undefined,
send: (data: Uint8Array, metadata: SegmentMetadata, rawSegmentBytesCount: number) => void,
send: (data: Uint8Array, metadata: BrowserSegmentMetadata, rawSegmentBytesCount: number) => void,
worker: DeflateWorker,
emitter: EventEmitter = window
) {
Expand Down Expand Up @@ -122,7 +122,7 @@ export function doStartSegmentCollection(
}
}

function createNewSegment(creationReason: CreationReason, initialRecord: Record) {
function createNewSegment(creationReason: CreationReason, initialRecord: BrowserRecord) {
const context = getSegmentContext()
if (!context) {
return
Expand Down Expand Up @@ -156,7 +156,7 @@ export function doStartSegmentCollection(
}

return {
addRecord: (record: Record) => {
addRecord: (record: BrowserRecord) => {
switch (state.status) {
case SegmentCollectionStatus.WaitingForInitialRecord:
createNewSegment(state.nextSegmentCreationReason, record)
Expand Down
4 changes: 2 additions & 2 deletions packages/rum/src/transport/send.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { EndpointBuilder } from '@datadog/browser-core'
import { HttpRequest, objectEntries } from '@datadog/browser-core'
import { SEGMENT_BYTES_LIMIT } from '../domain/segmentCollection'
import type { SegmentMetadata } from '../types'
import type { BrowserSegmentMetadata } from '../types'

export function send(
endpointBuilder: EndpointBuilder,
data: Uint8Array,
metadata: SegmentMetadata,
metadata: BrowserSegmentMetadata,
rawSegmentBytesCount: number
): void {
const formData = new FormData()
Expand Down
5 changes: 2 additions & 3 deletions packages/rum/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './segment'
export * from './record'
export * from './serializedNode'
export * from './sessionReplay'
export * from './sessionReplayConstants'

0 comments on commit e0d3b67

Please sign in to comment.