Skip to content

Commit

Permalink
Integrate 2bfb326 (#2589) from benoit/consent--consent-withdraw into …
Browse files Browse the repository at this point in the history
…staging-06

Co-authored-by: Benoît Zugmeyer <benoit.zugmeyer@datadoghq.com>
  • Loading branch information
dd-mergequeue[bot] and BenoitZugmeyer committed Feb 7, 2024
2 parents 82fa91a + 2bfb326 commit d01c92f
Show file tree
Hide file tree
Showing 15 changed files with 188 additions and 89 deletions.
8 changes: 4 additions & 4 deletions packages/core/src/domain/session/sessionManager.spec.ts
Expand Up @@ -508,7 +508,7 @@ describe('startSessionManager', () => {
const trackingConsentState = createTrackingConsentState(TrackingConsent.GRANTED)
const sessionManager = startSessionManagerWithDefaults({ trackingConsentState })

trackingConsentState.set(TrackingConsent.NOT_GRANTED)
trackingConsentState.update(TrackingConsent.NOT_GRANTED)

expectSessionIdToNotBeDefined(sessionManager)
expect(getCookie(SESSION_STORE_KEY)).toBeUndefined()
Expand All @@ -518,7 +518,7 @@ describe('startSessionManager', () => {
const trackingConsentState = createTrackingConsentState(TrackingConsent.GRANTED)
const sessionManager = startSessionManagerWithDefaults({ trackingConsentState })

trackingConsentState.set(TrackingConsent.NOT_GRANTED)
trackingConsentState.update(TrackingConsent.NOT_GRANTED)

document.dispatchEvent(createNewEvent(DOM_EVENT.CLICK))

Expand All @@ -530,11 +530,11 @@ describe('startSessionManager', () => {
const sessionManager = startSessionManagerWithDefaults({ trackingConsentState })
const initialSessionId = sessionManager.findActiveSession()!.id

trackingConsentState.set(TrackingConsent.NOT_GRANTED)
trackingConsentState.update(TrackingConsent.NOT_GRANTED)

expectSessionIdToNotBeDefined(sessionManager)

trackingConsentState.set(TrackingConsent.GRANTED)
trackingConsentState.update(TrackingConsent.GRANTED)

clock.tick(STORAGE_POLL_DELAY)

Expand Down
18 changes: 9 additions & 9 deletions packages/core/src/domain/trackingConsent.spec.ts
Expand Up @@ -23,29 +23,29 @@ describe('createTrackingConsentState', () => {
expect(trackingConsentState.isGranted()).toBeTrue()
})

it('can be set to granted', () => {
it('can be updated to granted', () => {
const trackingConsentState = createTrackingConsentState()
trackingConsentState.set(TrackingConsent.GRANTED)
trackingConsentState.update(TrackingConsent.GRANTED)
expect(trackingConsentState.isGranted()).toBeTrue()
})

it('notifies when the consent is set', () => {
it('notifies when the consent is updated', () => {
const spy = jasmine.createSpy()
const trackingConsentState = createTrackingConsentState()
trackingConsentState.observable.subscribe(spy)
trackingConsentState.set(TrackingConsent.GRANTED)
trackingConsentState.update(TrackingConsent.GRANTED)
expect(spy).toHaveBeenCalledTimes(1)
})

it('can set a consent state if not defined', () => {
it('can init a consent state if not defined yet', () => {
const trackingConsentState = createTrackingConsentState()
trackingConsentState.setIfNotDefined(TrackingConsent.GRANTED)
trackingConsentState.tryToInit(TrackingConsent.GRANTED)
expect(trackingConsentState.isGranted()).toBeTrue()
})

it('does not set a consent state if already defined', () => {
it('does not init a consent state if already defined', () => {
const trackingConsentState = createTrackingConsentState(TrackingConsent.GRANTED)
trackingConsentState.setIfNotDefined(TrackingConsent.NOT_GRANTED)
trackingConsentState.tryToInit(TrackingConsent.NOT_GRANTED)
expect(trackingConsentState.isGranted()).toBeTrue()
})
})
Expand All @@ -64,7 +64,7 @@ describe('createTrackingConsentState', () => {
expect(trackingConsentState.isGranted()).toBeTrue()

trackingConsentState = createTrackingConsentState()
trackingConsentState.set(TrackingConsent.NOT_GRANTED)
trackingConsentState.update(TrackingConsent.NOT_GRANTED)
expect(trackingConsentState.isGranted()).toBeTrue()
})
})
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/domain/trackingConsent.ts
Expand Up @@ -8,8 +8,8 @@ export const TrackingConsent = {
export type TrackingConsent = (typeof TrackingConsent)[keyof typeof TrackingConsent]

export interface TrackingConsentState {
setIfNotDefined: (trackingConsent: TrackingConsent) => void
set: (trackingConsent: TrackingConsent) => void
tryToInit: (trackingConsent: TrackingConsent) => void
update: (trackingConsent: TrackingConsent) => void
isGranted: () => boolean
observable: Observable<void>
}
Expand All @@ -18,12 +18,12 @@ export function createTrackingConsentState(currentConsent?: TrackingConsent): Tr
const observable = new Observable<void>()

return {
setIfNotDefined(trackingConsent: TrackingConsent) {
tryToInit(trackingConsent: TrackingConsent) {
if (!currentConsent) {
currentConsent = trackingConsent
}
},
set(trackingConsent: TrackingConsent) {
update(trackingConsent: TrackingConsent) {
currentConsent = trackingConsent
observable.notify()
},
Expand Down
23 changes: 19 additions & 4 deletions packages/logs/src/boot/logsPublicApi.ts
@@ -1,4 +1,4 @@
import type { Context, User } from '@datadog/browser-core'
import type { Context, TrackingConsent, User } from '@datadog/browser-core'
import {
CustomerDataType,
assign,
Expand All @@ -12,6 +12,7 @@ import {
storeContextManager,
displayAlreadyInitializedError,
deepClone,
createTrackingConsentState,
} from '@datadog/browser-core'
import type { LogsInitConfiguration } from '../domain/configuration'
import type { HandlerType, StatusType } from '../domain/logger'
Expand All @@ -32,7 +33,6 @@ const LOGS_STORAGE_KEY = 'logs'

export interface Strategy {
init: (initConfiguration: LogsInitConfiguration) => void
setTrackingConsent: StartLogsResult['setTrackingConsent']
initConfiguration: LogsInitConfiguration | undefined
getInternalContext: StartLogsResult['getInternalContext']
handleLog: StartLogsResult['handleLog']
Expand All @@ -44,18 +44,19 @@ export function makeLogsPublicApi(startLogsImpl: StartLogs) {
customerDataTrackerManager.getOrCreateTracker(CustomerDataType.GlobalContext)
)
const userContextManager = createContextManager(customerDataTrackerManager.getOrCreateTracker(CustomerDataType.User))
const trackingConsentState = createTrackingConsentState()

function getCommonContext() {
return buildCommonContext(globalContextManager, userContextManager)
}

let strategy = createPreStartStrategy(getCommonContext, (initConfiguration, configuration) => {
let strategy = createPreStartStrategy(getCommonContext, trackingConsentState, (initConfiguration, configuration) => {
if (initConfiguration.storeContextsAcrossPages) {
storeContextManager(configuration, globalContextManager, LOGS_STORAGE_KEY, CustomerDataType.GlobalContext)
storeContextManager(configuration, userContextManager, LOGS_STORAGE_KEY, CustomerDataType.User)
}

const startLogsResult = startLogsImpl(initConfiguration, configuration, getCommonContext)
const startLogsResult = startLogsImpl(initConfiguration, configuration, getCommonContext, trackingConsentState)

strategy = createPostStartStrategy(initConfiguration, startLogsResult)
return startLogsResult
Expand All @@ -73,6 +74,20 @@ export function makeLogsPublicApi(startLogsImpl: StartLogs) {

init: monitor((initConfiguration: LogsInitConfiguration) => strategy.init(initConfiguration)),

/**
* Set the tracking consent of the current user.
*
* @param {"granted" | "not-granted"} trackingConsent The user tracking consent
*
* Logs will be sent only if it is set to "granted". This value won't be stored by the library
* across page loads: you will need to call this method or set the appropriate `trackingConsent`
* field in the init() method at each page load.
*
* If this method is called before the init() method, the provided value will take precedence
* over the one provided as initialization parameter.
*/
setTrackingConsent: monitor((trackingConsent: TrackingConsent) => trackingConsentState.update(trackingConsent)),

getGlobalContext: monitor(() => globalContextManager.getContext()),

setGlobalContext: monitor((context) => globalContextManager.setContext(context)),
Expand Down
33 changes: 21 additions & 12 deletions packages/logs/src/boot/preStartLogs.spec.ts
Expand Up @@ -5,8 +5,14 @@ import {
initEventBridgeStub,
mockExperimentalFeatures,
} from '@datadog/browser-core/test'
import type { TimeStamp } from '@datadog/browser-core'
import { ExperimentalFeature, ONE_SECOND, TrackingConsent, display } from '@datadog/browser-core'
import type { TimeStamp, TrackingConsentState } from '@datadog/browser-core'
import {
ExperimentalFeature,
ONE_SECOND,
TrackingConsent,
createTrackingConsentState,
display,
} from '@datadog/browser-core'
import type { CommonContext } from '../rawLogsEvent.types'
import type { HybridInitConfiguration, LogsConfiguration, LogsInitConfiguration } from '../domain/configuration'
import { StatusType, type Logger } from '../domain/logger'
Expand Down Expand Up @@ -37,7 +43,7 @@ describe('preStartLogs', () => {
handleLog: handleLogSpy,
} as unknown as StartLogsResult)
getCommonContextSpy = jasmine.createSpy()
strategy = createPreStartStrategy(getCommonContextSpy, doStartLogsSpy)
strategy = createPreStartStrategy(getCommonContextSpy, createTrackingConsentState(), doStartLogsSpy)
clock = mockClock()
})

Expand Down Expand Up @@ -203,19 +209,26 @@ describe('preStartLogs', () => {

describe('internal context', () => {
it('should return undefined if not initialized', () => {
const strategy = createPreStartStrategy(getCommonContextSpy, doStartLogsSpy)
const strategy = createPreStartStrategy(getCommonContextSpy, createTrackingConsentState(), doStartLogsSpy)
expect(strategy.getInternalContext()).toBeUndefined()
})
})

describe('tracking consent', () => {
let strategy: Strategy
let trackingConsentState: TrackingConsentState

beforeEach(() => {
trackingConsentState = createTrackingConsentState()
strategy = createPreStartStrategy(getCommonContextSpy, trackingConsentState, doStartLogsSpy)
})

describe('with tracking_consent enabled', () => {
beforeEach(() => {
mockExperimentalFeatures([ExperimentalFeature.TRACKING_CONSENT])
})

it('does not start logs if tracking consent is not granted at init', () => {
const strategy = createPreStartStrategy(getCommonContextSpy, doStartLogsSpy)
strategy.init({
...DEFAULT_INIT_CONFIGURATION,
trackingConsent: TrackingConsent.NOT_GRANTED,
Expand All @@ -224,8 +237,7 @@ describe('preStartLogs', () => {
})

it('starts logs if tracking consent is granted before init', () => {
const strategy = createPreStartStrategy(getCommonContextSpy, doStartLogsSpy)
strategy.setTrackingConsent(TrackingConsent.GRANTED)
trackingConsentState.update(TrackingConsent.GRANTED)
strategy.init({
...DEFAULT_INIT_CONFIGURATION,
trackingConsent: TrackingConsent.NOT_GRANTED,
Expand All @@ -234,8 +246,7 @@ describe('preStartLogs', () => {
})

it('does not start logs if tracking consent is not withdrawn before init', () => {
const strategy = createPreStartStrategy(getCommonContextSpy, doStartLogsSpy)
strategy.setTrackingConsent(TrackingConsent.NOT_GRANTED)
trackingConsentState.update(TrackingConsent.NOT_GRANTED)
strategy.init({
...DEFAULT_INIT_CONFIGURATION,
trackingConsent: TrackingConsent.GRANTED,
Expand All @@ -246,7 +257,6 @@ describe('preStartLogs', () => {

describe('with tracking_consent disabled', () => {
it('ignores the trackingConsent init param', () => {
const strategy = createPreStartStrategy(getCommonContextSpy, doStartLogsSpy)
strategy.init({
...DEFAULT_INIT_CONFIGURATION,
trackingConsent: TrackingConsent.NOT_GRANTED,
Expand All @@ -255,8 +265,7 @@ describe('preStartLogs', () => {
})

it('ignores setTrackingConsent', () => {
const strategy = createPreStartStrategy(getCommonContextSpy, doStartLogsSpy)
strategy.setTrackingConsent(TrackingConsent.NOT_GRANTED)
trackingConsentState.update(TrackingConsent.NOT_GRANTED)
strategy.init(DEFAULT_INIT_CONFIGURATION)
expect(doStartLogsSpy).toHaveBeenCalledTimes(1)
})
Expand Down
11 changes: 5 additions & 6 deletions packages/logs/src/boot/preStartLogs.ts
@@ -1,8 +1,8 @@
import type { TrackingConsentState } from '@datadog/browser-core'
import {
BoundedBuffer,
assign,
canUseEventBridge,
createTrackingConsentState,
display,
displayAlreadyInitializedError,
noop,
Expand All @@ -19,19 +19,20 @@ import type { StartLogsResult } from './startLogs'

export function createPreStartStrategy(
getCommonContext: () => CommonContext,
trackingConsentState: TrackingConsentState,
doStartLogs: (initConfiguration: LogsInitConfiguration, configuration: LogsConfiguration) => StartLogsResult
): Strategy {
const bufferApiCalls = new BoundedBuffer<StartLogsResult>()
let cachedInitConfiguration: LogsInitConfiguration | undefined
let cachedConfiguration: LogsConfiguration | undefined
const trackingConsentState = createTrackingConsentState()
trackingConsentState.observable.subscribe(tryStartLogs)
const trackingConsentStateSubscription = trackingConsentState.observable.subscribe(tryStartLogs)

function tryStartLogs() {
if (!cachedConfiguration || !cachedInitConfiguration || !trackingConsentState.isGranted()) {
return
}

trackingConsentStateSubscription.unsubscribe()
const startLogsResult = doStartLogs(cachedInitConfiguration, cachedConfiguration)

bufferApiCalls.drain(startLogsResult)
Expand Down Expand Up @@ -62,12 +63,10 @@ export function createPreStartStrategy(
}

cachedConfiguration = configuration
trackingConsentState.setIfNotDefined(configuration.trackingConsent)
trackingConsentState.tryToInit(configuration.trackingConsent)
tryStartLogs()
},

setTrackingConsent: trackingConsentState.set,

get initConfiguration() {
return cachedInitConfiguration
},
Expand Down

0 comments on commit d01c92f

Please sign in to comment.