Skip to content

Commit 22b1acf

Browse files
♻️ [RUMF-1529] use an enum for experimental features
1 parent 28d26bb commit 22b1acf

28 files changed

+153
-83
lines changed

packages/core/src/browser/pageExitObservable.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createNewEvent, restorePageVisibility, setPageVisibility } from '../../test/specHelper'
2-
import { resetExperimentalFeatures, updateExperimentalFeatures } from '../domain/configuration'
2+
import { resetExperimentalFeatures, updateExperimentalFeatures, ExperimentalFeature } from '../domain/configuration'
33
import type { Subscription } from '../tools/observable'
44
import type { PageExitEvent } from './pageExitObservable'
55
import { PageExitReason, createPageExitObservable } from './pageExitObservable'
@@ -20,7 +20,7 @@ describe('createPageExitObservable', () => {
2020
})
2121

2222
it('notifies when the page fires pagehide if ff pagehide is enabled', () => {
23-
updateExperimentalFeatures(['pagehide'])
23+
updateExperimentalFeatures([ExperimentalFeature.PAGEHIDE])
2424
onExitSpy = jasmine.createSpy()
2525
pageExitSubscription = createPageExitObservable().subscribe(onExitSpy)
2626

packages/core/src/browser/pageExitObservable.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isExperimentalFeatureEnabled } from '../domain/configuration'
1+
import { isExperimentalFeatureEnabled, ExperimentalFeature } from '../domain/configuration'
22
import { Observable } from '../tools/observable'
33
import { includes, noop, objectValues } from '../tools/utils'
44
import { addEventListeners, addEventListener, DOM_EVENT } from './addEventListener'
@@ -18,7 +18,7 @@ export interface PageExitEvent {
1818

1919
export function createPageExitObservable(): Observable<PageExitEvent> {
2020
const observable = new Observable<PageExitEvent>(() => {
21-
const pagehideEnabled = isExperimentalFeatureEnabled('pagehide')
21+
const pagehideEnabled = isExperimentalFeatureEnabled(ExperimentalFeature.PAGEHIDE)
2222
const { stop: stopListeners } = addEventListeners(
2323
window,
2424
[DOM_EVENT.VISIBILITY_CHANGE, DOM_EVENT.FREEZE, DOM_EVENT.PAGE_HIDE],

packages/core/src/domain/configuration/configuration.spec.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { RumEvent } from '../../../../rum-core/src'
22
import { display } from '../../tools/display'
33
import type { InitConfiguration } from './configuration'
44
import { validateAndBuildConfiguration } from './configuration'
5-
import { isExperimentalFeatureEnabled, updateExperimentalFeatures } from './experimentalFeatures'
5+
import { ExperimentalFeature, isExperimentalFeatureEnabled, updateExperimentalFeatures } from './experimentalFeatures'
66

77
describe('validateAndBuildConfiguration', () => {
88
const clientToken = 'some_client_token'
@@ -11,9 +11,21 @@ describe('validateAndBuildConfiguration', () => {
1111
updateExperimentalFeatures([])
1212
})
1313

14-
it('updates experimental feature flags', () => {
15-
validateAndBuildConfiguration({ clientToken, enableExperimentalFeatures: ['foo'] })
16-
expect(isExperimentalFeatureEnabled('foo')).toBeTrue()
14+
describe('experimentalFeatures', () => {
15+
const TEST_FEATURE_FLAG = 'foo' as ExperimentalFeature
16+
17+
beforeEach(() => {
18+
;(ExperimentalFeature as any).FOO = TEST_FEATURE_FLAG
19+
})
20+
21+
afterEach(() => {
22+
delete (ExperimentalFeature as any).FOO
23+
})
24+
25+
it('updates experimental feature flags', () => {
26+
validateAndBuildConfiguration({ clientToken, enableExperimentalFeatures: ['foo'] })
27+
expect(isExperimentalFeatureEnabled(TEST_FEATURE_FLAG)).toBeTrue()
28+
})
1729
})
1830

1931
describe('validate init configuration', () => {

packages/core/src/domain/configuration/configuration.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import type { CookieOptions } from '../../browser/cookie'
22
import { getCurrentSite } from '../../browser/cookie'
33
import { catchUserErrors } from '../../tools/catchUserErrors'
44
import { display } from '../../tools/display'
5-
import { assign, isPercentage, ONE_KIBI_BYTE, ONE_SECOND } from '../../tools/utils'
5+
import { assign, isPercentage, objectHasValue, ONE_KIBI_BYTE, ONE_SECOND } from '../../tools/utils'
66
import type { RawTelemetryConfiguration } from '../telemetry'
7-
import { updateExperimentalFeatures } from './experimentalFeatures'
7+
import { ExperimentalFeature, updateExperimentalFeatures } from './experimentalFeatures'
88
import type { TransportConfiguration } from './transportConfiguration'
99
import { computeTransportConfiguration } from './transportConfiguration'
1010

@@ -112,7 +112,13 @@ export function validateAndBuildConfiguration(initConfiguration: InitConfigurati
112112
}
113113

114114
// Set the experimental feature flags as early as possible, so we can use them in most places
115-
updateExperimentalFeatures(initConfiguration.enableExperimentalFeatures)
115+
if (Array.isArray(initConfiguration.enableExperimentalFeatures)) {
116+
updateExperimentalFeatures(
117+
initConfiguration.enableExperimentalFeatures.filter((flag): flag is ExperimentalFeature =>
118+
objectHasValue(ExperimentalFeature, flag)
119+
)
120+
)
121+
}
116122

117123
return assign(
118124
{

packages/core/src/domain/configuration/endpointBuilder.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { BuildEnvWindow } from '../../../test/specHelper'
22
import { startsWith } from '../../tools/utils'
33
import type { InitConfiguration } from './configuration'
44
import { createEndpointBuilder } from './endpointBuilder'
5-
import { resetExperimentalFeatures, updateExperimentalFeatures } from './experimentalFeatures'
5+
import { ExperimentalFeature, resetExperimentalFeatures, updateExperimentalFeatures } from './experimentalFeatures'
66

77
describe('endpointBuilder', () => {
88
const clientToken = 'some_client_token'
@@ -126,7 +126,7 @@ describe('endpointBuilder', () => {
126126
})
127127

128128
it('should contain flush reason when ff collect_flush_reason is enabled', () => {
129-
updateExperimentalFeatures(['collect_flush_reason'])
129+
updateExperimentalFeatures([ExperimentalFeature.COLLECT_FLUSH_REASON])
130130
expect(createEndpointBuilder(initConfiguration, 'rum', []).build('xhr', 'batch_bytes_limit')).toContain(
131131
'flush_reason%3Abatch_bytes_limit'
132132
)

packages/core/src/domain/configuration/endpointBuilder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { normalizeUrl } from '../../tools/urlPolyfill'
44
import { generateUUID } from '../../tools/utils'
55
import type { InitConfiguration } from './configuration'
66
import { INTAKE_SITE_AP1, INTAKE_SITE_US1 } from './intakeSites'
7-
import { isExperimentalFeatureEnabled } from './experimentalFeatures'
7+
import { ExperimentalFeature, isExperimentalFeatureEnabled } from './experimentalFeatures'
88

99
// replaced at build time
1010
declare const __BUILD_ENV__SDK_VERSION__: string
@@ -104,7 +104,7 @@ function buildEndpointParameters(
104104
retry: RetryInfo | undefined
105105
) {
106106
const tags = [`sdk_version:${__BUILD_ENV__SDK_VERSION__}`, `api:${api}`].concat(configurationTags)
107-
if (flushReason && isExperimentalFeatureEnabled('collect_flush_reason')) {
107+
if (flushReason && isExperimentalFeatureEnabled(ExperimentalFeature.COLLECT_FLUSH_REASON)) {
108108
tags.push(`flush_reason:${flushReason}`)
109109
}
110110
if (retry) {
Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,43 @@
1+
import type { ExperimentalFeature } from './experimentalFeatures'
12
import {
23
updateExperimentalFeatures,
34
isExperimentalFeatureEnabled,
45
resetExperimentalFeatures,
56
} from './experimentalFeatures'
67

8+
const TEST_FEATURE_FLAG_ONE = 'foo' as ExperimentalFeature
9+
const TEST_FEATURE_FLAG_TWO = 'bar' as ExperimentalFeature
10+
711
describe('experimentalFeatures', () => {
812
afterEach(() => {
913
resetExperimentalFeatures()
1014
})
1115

1216
it('initial state is empty', () => {
13-
expect(isExperimentalFeatureEnabled('foo')).toBeFalse()
14-
expect(isExperimentalFeatureEnabled('bar')).toBeFalse()
17+
expect(isExperimentalFeatureEnabled(TEST_FEATURE_FLAG_ONE)).toBeFalse()
18+
expect(isExperimentalFeatureEnabled(TEST_FEATURE_FLAG_TWO)).toBeFalse()
1519
})
1620

1721
it('should define enabled experimental features', () => {
18-
updateExperimentalFeatures(['foo'])
19-
expect(isExperimentalFeatureEnabled('foo')).toBeTrue()
20-
expect(isExperimentalFeatureEnabled('bar')).toBeFalse()
22+
updateExperimentalFeatures([TEST_FEATURE_FLAG_ONE])
23+
expect(isExperimentalFeatureEnabled(TEST_FEATURE_FLAG_ONE)).toBeTrue()
24+
expect(isExperimentalFeatureEnabled(TEST_FEATURE_FLAG_TWO)).toBeFalse()
2125
})
2226

2327
it('should allow to be shared between products', () => {
24-
updateExperimentalFeatures(['foo'])
25-
updateExperimentalFeatures(['bar'])
28+
updateExperimentalFeatures([TEST_FEATURE_FLAG_ONE])
29+
updateExperimentalFeatures([TEST_FEATURE_FLAG_TWO])
2630

27-
expect(isExperimentalFeatureEnabled('foo')).toBeTrue()
28-
expect(isExperimentalFeatureEnabled('bar')).toBeTrue()
31+
expect(isExperimentalFeatureEnabled(TEST_FEATURE_FLAG_ONE)).toBeTrue()
32+
expect(isExperimentalFeatureEnabled(TEST_FEATURE_FLAG_TWO)).toBeTrue()
2933
})
3034

3135
it('should support some edge cases', () => {
32-
updateExperimentalFeatures(['foo'])
36+
updateExperimentalFeatures([TEST_FEATURE_FLAG_ONE])
3337
updateExperimentalFeatures(undefined)
3438
updateExperimentalFeatures([])
3539
updateExperimentalFeatures([11 as any])
3640

37-
expect(isExperimentalFeatureEnabled('foo')).toBeTrue()
41+
expect(isExperimentalFeatureEnabled(TEST_FEATURE_FLAG_ONE)).toBeTrue()
3842
})
3943
})

packages/core/src/domain/configuration/experimentalFeatures.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,27 @@
33
* For NPM setup, this feature flag singleton is shared between RUM and Logs product.
44
* This means that an experimental flag set on the RUM product will be set on the Logs product.
55
* So keep in mind that in certain configurations, your experimental feature flag may affect other products.
6+
*
7+
* FORMAT:
8+
* All feature flags should be snake_cased
69
*/
7-
import { includes } from '../../tools/utils'
8-
import { display } from '../../tools/display'
10+
// We want to use a real enum (i.e. not a const enum) here, to be able to check whether an arbitrary
11+
// string is an expected feature flag
12+
// eslint-disable-next-line no-restricted-syntax
13+
export enum ExperimentalFeature {
14+
PAGEHIDE = 'pagehide',
15+
SHADOW_DOM_DEBUG = 'shadow_dom_debug',
16+
FEATURE_FLAGS = 'feature_flags',
17+
RESOURCE_DURATIONS = 'resource_durations',
18+
RESOURCE_PAGE_STATES = 'resource_page_states',
19+
CLICKMAP = 'clickmap',
20+
COLLECT_FLUSH_REASON = 'collect_flush_reason',
21+
SANITIZE_INPUTS = 'sanitize_inputs',
22+
}
923

10-
let enabledExperimentalFeatures: Set<string> | undefined
24+
let enabledExperimentalFeatures: Set<ExperimentalFeature> | undefined
1125

12-
export function updateExperimentalFeatures(enabledFeatures: string[] | undefined): void {
26+
export function updateExperimentalFeatures(enabledFeatures: ExperimentalFeature[] | undefined): void {
1327
// Safely handle external data
1428
if (!Array.isArray(enabledFeatures)) {
1529
return
@@ -19,17 +33,12 @@ export function updateExperimentalFeatures(enabledFeatures: string[] | undefined
1933
enabledExperimentalFeatures = new Set(enabledFeatures)
2034
}
2135

22-
enabledFeatures
23-
.filter((flag) => typeof flag === 'string')
24-
.forEach((flag: string) => {
25-
if (includes(flag, '-')) {
26-
display.warn(`please use snake case for '${flag}'`)
27-
}
28-
enabledExperimentalFeatures!.add(flag)
29-
})
36+
enabledFeatures.forEach((flag) => {
37+
enabledExperimentalFeatures!.add(flag)
38+
})
3039
}
3140

32-
export function isExperimentalFeatureEnabled(featureName: string): boolean {
41+
export function isExperimentalFeatureEnabled(featureName: ExperimentalFeature): boolean {
3342
return !!enabledExperimentalFeatures && enabledExperimentalFeatures.has(featureName)
3443
}
3544

packages/core/src/domain/configuration/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ export {
1212
updateExperimentalFeatures,
1313
resetExperimentalFeatures,
1414
getExperimentalFeatures,
15+
ExperimentalFeature,
1516
} from './experimentalFeatures'
1617
export * from './intakeSites'

packages/core/src/domain/console/consoleObservable.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { find, jsonStringify } from '../../tools/utils'
55
import { ConsoleApiName } from '../../tools/display'
66
import { callMonitored } from '../../tools/monitor'
77
import { sanitize } from '../../tools/sanitize'
8-
import { isExperimentalFeatureEnabled } from '../configuration'
8+
import { ExperimentalFeature, isExperimentalFeatureEnabled } from '../configuration'
99

1010
export interface ConsoleLog {
1111
message: string
@@ -70,10 +70,14 @@ function buildConsoleLog(params: unknown[], api: ConsoleApiName, handlingStack:
7070

7171
function formatConsoleParameters(param: unknown) {
7272
if (typeof param === 'string') {
73-
return isExperimentalFeatureEnabled('sanitize_inputs') ? sanitize(param) : param
73+
return isExperimentalFeatureEnabled(ExperimentalFeature.SANITIZE_INPUTS) ? sanitize(param) : param
7474
}
7575
if (param instanceof Error) {
7676
return formatErrorMessage(computeStackTrace(param))
7777
}
78-
return jsonStringify(isExperimentalFeatureEnabled('sanitize_inputs') ? sanitize(param) : param, undefined, 2)
78+
return jsonStringify(
79+
isExperimentalFeatureEnabled(ExperimentalFeature.SANITIZE_INPUTS) ? sanitize(param) : param,
80+
undefined,
81+
2
82+
)
7983
}

0 commit comments

Comments
 (0)