Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REPLAY] Add public function to get the link to current Replay #2047

Merged
merged 25 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5419e98
[REPLAY] Add function to get the URL to the current replay
ThibautGeriz Mar 1, 2023
0346742
[REPLAY] Add replay link in slim package
ThibautGeriz Apr 3, 2023
ee0d030
[REPLAY] Add error code when browser is not supported
ThibautGeriz Apr 3, 2023
ab1c25e
[REPLAY] Fix tests for IE
ThibautGeriz Apr 4, 2023
c9f32ce
[REPLAY] Simplify unit tests
ThibautGeriz Apr 4, 2023
3e197d7
[REPLAY] Refactor of get replay link function
ThibautGeriz Apr 4, 2023
2ee287d
[REPLAY] Rename functions
ThibautGeriz Apr 5, 2023
34c4f73
[REPLAY] Refactor subdomain logic
ThibautGeriz Apr 5, 2023
06ffd5c
[REPLAY] Simplify tests
ThibautGeriz Apr 5, 2023
31edb21
[REPLAY] Use more explicit session id when no session is recorded
ThibautGeriz Apr 5, 2023
daf32b6
[REPLAY] Fix tests for IE on BS
ThibautGeriz Apr 5, 2023
274d88b
[REPLAY] Add missing case in unit tests
ThibautGeriz Apr 5, 2023
837c3dd
[REPLAY] Use internal stats instead of replay start to find out if re…
ThibautGeriz Apr 5, 2023
6b5d4ac
[REPLAY] Fix tests for IE on BS bis
ThibautGeriz Apr 5, 2023
4d5e6cd
[REPLAY] Extract logic in helper to avoid duplication
ThibautGeriz Apr 6, 2023
4f0704d
Merge remote-tracking branch 'origin/main' into thibaut.gery/add-repl…
ThibautGeriz Apr 6, 2023
4aa65e9
[REPLAY] Remove unused export
ThibautGeriz Apr 6, 2023
78975b3
[REPLAY] Refactor 'getSessionReplayUrl'
ThibautGeriz Apr 6, 2023
58c4d9d
[REPLAY] Move file to match with function name
ThibautGeriz Apr 6, 2023
bd634b3
Merge remote-tracking branch 'origin/main' into thibaut.gery/add-repl…
ThibautGeriz Apr 6, 2023
c6cb2dc
[REPLAY] Fix formatter
ThibautGeriz Apr 6, 2023
0ff7004
[REPLAY] Remove superfluous space
ThibautGeriz Apr 7, 2023
77e94c4
[REPLAY] Remove un-used type
ThibautGeriz Apr 7, 2023
caac7ad
[REPLAY] Improve unit test clarity
ThibautGeriz Apr 7, 2023
0333598
[REPLAY] Fix timer in unit tests
ThibautGeriz Apr 7, 2023
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
1 change: 1 addition & 0 deletions packages/core/src/domain/configuration/intakeSites.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const INTAKE_SITE_STAGING = 'datad0g.com'
export const INTAKE_SITE_US1 = 'datadoghq.com'
export const INTAKE_SITE_EU1 = 'datadoghq.eu'
export const INTAKE_SITE_AP1 = 'ap1.datadoghq.com'
export const INTAKE_SITE_US1_FED = 'ddog-gov.com'
5 changes: 5 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ export {
DefaultPrivacyLevel,
EndpointBuilder,
serializeConfiguration,
INTAKE_SITE_AP1,
INTAKE_SITE_STAGING,
INTAKE_SITE_US1,
INTAKE_SITE_US1_FED,
INTAKE_SITE_EU1,
} from './domain/configuration'
export {
isExperimentalFeatureEnabled,
Expand Down
10 changes: 9 additions & 1 deletion packages/rum-core/src/boot/rumPublicApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ export interface RecorderApi {
) => void
isRecording: () => boolean
getReplayStats: (viewId: string) => ReplayStats | undefined
getSessionReplayLink: (
configuration: RumConfiguration,
sessionManager: RumSessionManager,
viewContexts: ViewContexts
) => string | undefined
}
interface RumPublicApiOptions {
ignoreInitIfSyntheticsWillInjectRum?: boolean
Expand All @@ -69,6 +74,7 @@ export function makeRumPublicApi(
let getInternalContextStrategy: StartRumResult['getInternalContext'] = () => undefined
let getInitConfigurationStrategy = (): InitConfiguration | undefined => undefined
let stopSessionStrategy: () => void = noop
let getSessionReplayLinkStrategy: () => string | undefined = () => undefined

let bufferApiCalls = new BoundedBuffer()
let addTimingStrategy: StartRumResult['addTiming'] = (name, time = timeStampNow()) => {
Expand Down Expand Up @@ -152,7 +158,8 @@ export function makeRumPublicApi(
userContextManager,
initialViewOptions
)

getSessionReplayLinkStrategy = () =>
recorderApi.getSessionReplayLink(configuration, startRumResults.session, startRumResults.viewContexts)
;({
startView: startViewStrategy,
addAction: addActionStrategy,
Expand Down Expand Up @@ -273,6 +280,7 @@ export function makeRumPublicApi(
isExperimentalFeatureEnabled(ExperimentalFeature.SANITIZE_INPUTS) ? sanitize(value) : value
)
}),
getSessionReplayLink: monitor(() => getSessionReplayLinkStrategy()),
})

return rumPublicApi
Expand Down
5 changes: 3 additions & 2 deletions packages/rum-core/src/domain/assembly.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { RelativeTime } from '@datadog/browser-core'
import type { ClocksState, RelativeTime } from '@datadog/browser-core'
import { ErrorSource, ONE_MINUTE, display } from '@datadog/browser-core'
import {
initEventBridgeStub,
Expand Down Expand Up @@ -37,6 +37,7 @@ describe('rum assembly', () => {
findView = () => ({
id: '7890',
name: 'view name',
startClocks: {} as ClocksState,
})
reportErrorSpy = jasmine.createSpy('reportError')
commonContext = {
Expand Down Expand Up @@ -484,7 +485,7 @@ describe('rum assembly', () => {

it('should be overridden by the view context', () => {
const { lifeCycle } = setupBuilder.build()
findView = () => ({ service: 'new service', version: 'new version', id: '1234' })
findView = () => ({ service: 'new service', version: 'new version', id: '1234', startClocks: {} as ClocksState })
notifyRawRumEvent(lifeCycle, {
rawRumEvent: createRawRumEvent(RumEventType.ACTION),
})
Expand Down
3 changes: 3 additions & 0 deletions packages/rum-core/src/domain/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface RumInitConfiguration extends InitConfiguration {

// replay options
defaultPrivacyLevel?: DefaultPrivacyLevel | undefined
subdomain?: string
/**
* @deprecated use sessionReplaySampleRate instead
*/
Expand Down Expand Up @@ -80,6 +81,7 @@ export interface RumConfiguration extends Configuration {
trackResources: boolean | undefined
trackLongTasks: boolean | undefined
version?: string
subdomain?: string
customerDataTelemetrySampleRate: number
}

Expand Down Expand Up @@ -150,6 +152,7 @@ export function validateAndBuildRumConfiguration(
trackViewsManually: !!initConfiguration.trackViewsManually,
trackResources: initConfiguration.trackResources,
trackLongTasks: initConfiguration.trackLongTasks,
subdomain: initConfiguration.subdomain,
defaultPrivacyLevel: objectHasValue(DefaultPrivacyLevel, initConfiguration.defaultPrivacyLevel)
? initConfiguration.defaultPrivacyLevel
: DefaultPrivacyLevel.MASK_USER_INPUT,
Expand Down
4 changes: 3 additions & 1 deletion packages/rum-core/src/domain/contexts/viewContexts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { RelativeTime } from '@datadog/browser-core'
import type { RelativeTime, ClocksState } from '@datadog/browser-core'
import { SESSION_TIME_OUT_DELAY, ValueHistory } from '@datadog/browser-core'
import type { LifeCycle } from '../lifeCycle'
import { LifeCycleEventType } from '../lifeCycle'
Expand All @@ -11,6 +11,7 @@ export interface ViewContext {
version?: string
id: string
name?: string
startClocks: ClocksState
}

export interface ViewContexts {
Expand Down Expand Up @@ -39,6 +40,7 @@ export function startViewContexts(lifeCycle: LifeCycle): ViewContexts {
version: view.version,
id: view.id,
name: view.name,
startClocks: view.startClocks,
}
}

Expand Down
79 changes: 79 additions & 0 deletions packages/rum-core/src/domain/getSessionReplayUrl.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { ClocksState } from '@datadog/browser-core'
import type { RumConfiguration, RumSession } from '@datadog/browser-rum-core'

import { getSessionReplayUrl, getDatadogSiteUrl } from './getSessionReplayUrl'

describe('getDatadogSiteUrl', () => {
const parameters: Array<[string, string | undefined, string]> = [
['datadoghq.com', undefined, 'app.datadoghq.com'],
['datadoghq.com', 'toto', 'toto.datadoghq.com'],
['datad0g.com', undefined, 'dd.datad0g.com'],
['datad0g.com', 'toto', 'toto.datad0g.com'],
['us3.datadoghq.com', undefined, 'us3.datadoghq.com'],
['us3.datadoghq.com', 'toto', 'toto.us3.datadoghq.com'],
['us5.datadoghq.com', undefined, 'us5.datadoghq.com'],
['us5.datadoghq.com', 'toto', 'toto.us5.datadoghq.com'],
]

parameters.forEach(([site, subdomain, host]) => {
it(`should return ${host} for subdomain "${
subdomain ?? 'undefined'
}" on "${site}" with query params if view is found`, () => {
const link = getDatadogSiteUrl({ site, subdomain } as RumConfiguration)

expect(link).toBe(`https://${host}`)
})
})
})

describe('getSessionReplayUrl', () => {
const parameters = [
[
{
testCase: 'session, no view, no error',
session: { id: 'session-id-1' } as RumSession,
viewContext: undefined,
errorType: undefined,
expected: 'https://app.datadoghq.com/rum/replay/sessions/session-id-1?',
},
],
[
{
testCase: 'no session, no view, error',
session: undefined,
viewContext: undefined,
errorType: 'toto',
expected: 'https://app.datadoghq.com/rum/replay/sessions/no-session-id?error-type=toto',
},
],
[
{
testCase: 'session, view, no error',
session: { id: 'session-id-2' } as RumSession,
viewContext: { id: 'view-id-1', startClocks: { relative: 0, timeStamp: 1234 } as ClocksState },
errorType: undefined,
expected: 'https://app.datadoghq.com/rum/replay/sessions/session-id-2?seed=view-id-1&from=1234',
},
],
[
{
testCase: 'session, view, error',
session: { id: 'session-id-3' } as RumSession,
viewContext: { id: 'view-id-2', startClocks: { relative: 0, timeStamp: 1234 } as ClocksState },
errorType: 'titi',
expected: 'https://app.datadoghq.com/rum/replay/sessions/session-id-3?error-type=titi&seed=view-id-2&from=1234',
},
],
]

parameters.forEach(([{ testCase, session, viewContext, errorType, expected }]) => {
it(`should build url when ${testCase}`, () => {
const link = getSessionReplayUrl({ site: 'datadoghq.com' } as RumConfiguration, {
viewContext,
session,
errorType,
})
expect(link).toBe(expected)
})
})
})
49 changes: 49 additions & 0 deletions packages/rum-core/src/domain/getSessionReplayUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { INTAKE_SITE_STAGING, INTAKE_SITE_US1, INTAKE_SITE_EU1 } from '@datadog/browser-core'
import type { RumConfiguration } from './configuration'
import type { ViewContext } from './contexts/viewContexts'
import type { RumSession } from './rumSessionManager'

export function getSessionReplayUrl(
configuration: RumConfiguration,
{
session,
viewContext,
errorType,
}: {
session?: RumSession
viewContext?: ViewContext
errorType?: string
}
): string {
const sessionId = session ? session.id : 'no-session-id'
const parameters: string[] = []
if (errorType !== undefined) {
parameters.push(`error-type=${errorType}`)
}
if (viewContext) {
parameters.push(`seed=${viewContext.id}`)
parameters.push(`from=${viewContext.startClocks.timeStamp}`)
}

const origin = getDatadogSiteUrl(configuration)
const path = `/rum/replay/sessions/${sessionId}`
return `${origin}${path}?${parameters.join('&')}`
}

export function getDatadogSiteUrl(rumConfiguration: RumConfiguration) {
const site = rumConfiguration.site
const subdomain = rumConfiguration.subdomain || getSiteDefaultSubdomain(rumConfiguration)
return `https://${subdomain ? `${subdomain}.` : ''}${site}`
}

function getSiteDefaultSubdomain(configuration: RumConfiguration): string | undefined {
switch (configuration.site) {
case INTAKE_SITE_US1:
case INTAKE_SITE_EU1:
return 'app'
case INTAKE_SITE_STAGING:
return 'dd'
default:
return undefined
}
}
3 changes: 2 additions & 1 deletion packages/rum-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ export { startRum } from './boot/startRum'
export { LifeCycle, LifeCycleEventType } from './domain/lifeCycle'
export { ViewCreatedEvent } from './domain/rumEventsCollection/view/trackViews'
export { ViewContexts, ViewContext } from './domain/contexts/viewContexts'
export { RumSessionManager, RumSessionPlan } from './domain/rumSessionManager'
export { RumSessionManager, RumSessionPlan, RumSession } from './domain/rumSessionManager'
export { getMutationObserverConstructor } from './browser/domMutationObservable'
export { initViewportObservable, getViewportDimension } from './browser/viewportObservable'
export { RumInitConfiguration, RumConfiguration } from './domain/configuration'
export { DEFAULT_PROGRAMMATIC_ACTION_NAME_ATTRIBUTE } from './domain/rumEventsCollection/action/getActionNameFromElement'
export { STABLE_ATTRIBUTES } from './domain/rumEventsCollection/action/getSelectorFromElement'
export * from './browser/htmlDomUtils'
export { getSessionReplayUrl } from './domain/getSessionReplayUrl'
1 change: 1 addition & 0 deletions packages/rum-core/test/noopRecorderApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export const noopRecorderApi: RecorderApi = {
isRecording: () => false,
onRumStart: noop,
getReplayStats: () => undefined,
getSessionReplayLink: () => undefined,
}
14 changes: 14 additions & 0 deletions packages/rum-slim/src/domain/getSessionReplayLink.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { RumConfiguration } from '@datadog/browser-rum-core'
import { getSessionReplayLink } from './getSessionReplayLink'

const DEFAULT_CONFIGURATION = {
site: 'datad0g.com',
} as RumConfiguration

describe('getReplayLink (slim package)', () => {
it('should return the replay link with a "slim-package" error type', () => {
const link = getSessionReplayLink(DEFAULT_CONFIGURATION)

expect(link).toBe('https://dd.datad0g.com/rum/replay/sessions/no-session-id?error-type=slim-package')
})
})
6 changes: 6 additions & 0 deletions packages/rum-slim/src/domain/getSessionReplayLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { RumConfiguration } from '@datadog/browser-rum-core'
import { getSessionReplayUrl } from '@datadog/browser-rum-core'

export function getSessionReplayLink(configuration: RumConfiguration): string | undefined {
return getSessionReplayUrl(configuration, { errorType: 'slim-package' })
}
2 changes: 2 additions & 0 deletions packages/rum-slim/src/entries/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { defineGlobal, getGlobalObject, noop } from '@datadog/browser-core'
import type { RumPublicApi } from '@datadog/browser-rum-core'
import { makeRumPublicApi, startRum } from '@datadog/browser-rum-core'
import { getSessionReplayLink } from '../domain/getSessionReplayLink'

export {
CommonProperties,
Expand Down Expand Up @@ -32,6 +33,7 @@ export const datadogRum = makeRumPublicApi(startRum, {
onRumStart: noop,
isRecording: () => false,
getReplayStats: () => undefined,
getSessionReplayLink,
})

interface BrowserWindow extends Window {
Expand Down
14 changes: 14 additions & 0 deletions packages/rum/src/boot/isBrowserSupported.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Test for Browser features used while recording
*/
export function isBrowserSupported() {
return (
// Array.from is a bit less supported by browsers than CSSSupportsRule, but has higher chances
// to be polyfilled. Test for both to be more confident. We could add more things if we find out
// this test is not sufficient.
typeof Array.from === 'function' &&
typeof CSSSupportsRule === 'function' &&
typeof URL.createObjectURL === 'function' &&
'forEach' in NodeList.prototype
)
}
21 changes: 5 additions & 16 deletions packages/rum/src/boot/recorderApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import type {
} from '@datadog/browser-rum-core'
import { LifeCycleEventType } from '@datadog/browser-rum-core'
import { getReplayStats } from '../domain/replayStats'
import { getSessionReplayLink } from '../domain/getSessionReplayLink'
import { startDeflateWorker } from '../domain/segmentCollection'

import type { startRecording } from './startRecording'
import { isBrowserSupported } from './isBrowserSupported'

export type StartRecording = typeof startRecording

Expand Down Expand Up @@ -51,6 +53,7 @@ export function makeRecorderApi(
getReplayStats: () => undefined,
onRumStart: noop,
isRecording: () => false,
getSessionReplayLink: () => undefined,
}
}

Expand All @@ -68,7 +71,8 @@ export function makeRecorderApi(
start: () => startStrategy(),
stop: () => stopStrategy(),
getReplayStats,

getSessionReplayLink: (configuration, sessionManager, viewContexts) =>
getSessionReplayLink(configuration, sessionManager, viewContexts, state.status !== RecorderStatus.Stopped),
onRumStart: (
lifeCycle: LifeCycle,
configuration: RumConfiguration,
Expand Down Expand Up @@ -155,18 +159,3 @@ export function makeRecorderApi(
isRecording: () => state.status === RecorderStatus.Started,
}
}

/**
* Test for Browser features used while recording
*/
function isBrowserSupported() {
return (
// Array.from is a bit less supported by browsers than CSSSupportsRule, but has higher chances
// to be polyfilled. Test for both to be more confident. We could add more things if we find out
// this test is not sufficient.
typeof Array.from === 'function' &&
typeof CSSSupportsRule === 'function' &&
typeof URL.createObjectURL === 'function' &&
'forEach' in NodeList.prototype
)
}
4 changes: 2 additions & 2 deletions packages/rum/src/boot/startRecording.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TimeStamp, HttpRequest } from '@datadog/browser-core'
import type { TimeStamp, HttpRequest, ClocksState } from '@datadog/browser-core'
import { PageExitReason, DefaultPrivacyLevel, noop, isIE, timeStampNow } from '@datadog/browser-core'
import type { LifeCycle, ViewCreatedEvent } from '@datadog/browser-rum-core'
import { LifeCycleEventType } from '@datadog/browser-rum-core'
Expand Down Expand Up @@ -43,7 +43,7 @@ describe('startRecording', () => {
setupBuilder = setup()
.withViewContexts({
findView() {
return { id: viewId }
return { id: viewId, startClocks: {} as ClocksState }
},
})
.withSessionManager(sessionManager)
Expand Down
Loading