Skip to content

Commit 249a05f

Browse files
committed
add feature flag controls
1 parent 645ba33 commit 249a05f

8 files changed

Lines changed: 306 additions & 117 deletions

File tree

.github/actions/build-ab/templates/released.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// config
22
window.NREUM={
33
init: {
4-
feature_flags: ['soft_nav', 'user_frustrations'],
4+
feature_flags: ['soft_nav', 'user_frustrations', 'register'], // add jserrors and generic events once the consumer(s) support it
55
distributed_tracing: {
66
enabled: true
77
},

src/common/config/init.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const InitModelFn = () => {
4848
return {
4949
ajax: { deny_list: undefined, block_internal: true, enabled: true, autoStart: true },
5050
api: {
51-
allow_registered_children: true,
51+
allow_registered_children: false,
5252
duplicate_registered_data: false
5353
},
5454
distributed_tracing: {

src/common/constants/agent-constants.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ export const DEFAULT_KEY = 'NR_CONTAINER_AGENT'
1010
export const SESSION_ERROR = 'SESSION_ERROR'
1111

1212
export const SUPPORTS_REGISTERED_ENTITIES = {
13-
[FEATURE_NAMES.logging]: true
14-
// add other features here when they are supported by DEM consumers
13+
[FEATURE_NAMES.logging]: true,
14+
// flip other features here when they are supported by DEM consumers
15+
[FEATURE_NAMES.genericEvents]: false,
16+
[FEATURE_NAMES.jserrors]: false,
17+
[FEATURE_NAMES.ajax]: false
1518
}

src/features/utils/aggregate-base.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export class AggregateBase extends FeatureBase {
7070

7171
/** @type {Boolean} indicates if the feature supports registered entities and the harvest requirements therein. Also read by getter "harvestEndpointVersion". Controlled by feature flag in pre-release phase. */
7272
get supportsRegisteredEntities () {
73-
return SUPPORTS_REGISTERED_ENTITIES[this.featureName] || this.agentRef.init.feature_flags.includes('register-' + this.featureName)
73+
return this.featureName in SUPPORTS_REGISTERED_ENTITIES && (SUPPORTS_REGISTERED_ENTITIES[this.featureName] || this.agentRef.init.feature_flags.includes('register.' + this.featureName))
7474
}
7575

7676
/**

src/loaders/api/register.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export function buildRegisterApi (agentRef, target) {
6767
}
6868

6969
/** primary cases that can block the register API from working at init time */
70-
if (!agentRef.init.api.allow_registered_children) block(() => warn(55))
70+
if (!agentRef.init.api.allow_registered_children && !agentRef.init.feature_flags.includes('register')) block(() => warn(55))
7171
if (!isValidMFETarget(target)) block(() => warn(48, target))
7272

7373
/** @type {RegisterAPI} */

tests/specs/api.e2e.js

Lines changed: 156 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { checkAjaxEvents, checkJsErrors, checkMetrics, checkGenericEvents, checkPVT, checkRumBody, checkRumQuery, checkSessionTrace, checkSpa } from '../util/basic-checks'
2-
import { testAjaxEventsRequest, testAjaxTimeSlicesRequest, testBlobTraceRequest, testCustomMetricsRequest, testErrorsRequest, testEventsRequest, testInsRequest, testInteractionEventsRequest, testLogsRequest, testMetricsRequest, testRumRequest, testTimingEventsRequest } from '../../tools/testing-server/utils/expect-tests'
2+
import { testAjaxEventsRequest, testAjaxTimeSlicesRequest, testBlobTraceRequest, testCustomMetricsRequest, testErrorsRequest, testEventsRequest, testInsRequest, testInteractionEventsRequest, testLogsRequest, testMetricsRequest, testMFEErrorsRequest, testMFEInsRequest, testRumRequest, testTimingEventsRequest } from '../../tools/testing-server/utils/expect-tests'
33
import { rumFlags } from '../../tools/testing-server/constants'
44
import { LOGGING_MODE } from '../../src/features/logging/constants'
55

@@ -8,6 +8,161 @@ describe('newrelic api', () => {
88
await browser.destroyAgentSession()
99
})
1010

11+
describe('registered-entity', () => {
12+
const featureFlags = [
13+
[],
14+
['register'],
15+
['register', 'register.jserrors'],
16+
['register', 'register.generic_events'],
17+
['register', 'register.jserrors', 'register.generic_events'],
18+
['register.jserrors', 'register.generic_events'],
19+
['register.jserrors'],
20+
['register.generic_events']
21+
]
22+
featureFlags.forEach((testSet) => {
23+
it(`RegisteredEntity -- ${testSet.join(' | ') || 'no flags'}`, async () => {
24+
const [rumCapture, mfeErrorsCapture, mfeInsightsCapture, regularErrorsCapture, regularInsightsCapture, logsCapture] = await browser.testHandle.createNetworkCaptures('bamServer', [
25+
{ test: testRumRequest },
26+
{ test: testMFEErrorsRequest },
27+
{ test: testMFEInsRequest },
28+
{ test: testErrorsRequest },
29+
{ test: testInsRequest },
30+
{ test: testLogsRequest }
31+
])
32+
await browser.url(await browser.testHandle.assetURL('instrumented.html', { init: { feature_flags: testSet } }))
33+
34+
await browser.execute(function () {
35+
window.agent1 = newrelic.register({
36+
id: 1,
37+
name: 'agent1'
38+
})
39+
window.agent2 = newrelic.register({
40+
id: 2,
41+
name: 'agent2'
42+
})
43+
44+
// each payload in this test is decorated with data that matches its appId for ease of testing
45+
window.newrelic.setCustomAttribute('customAttr', '42') // container agent
46+
window.agent1.setCustomAttribute('customAttr', '1') // micro agent (agent1)
47+
window.agent2.setCustomAttribute('customAttr', '2') // micro agent (agent2)
48+
49+
// each payload in this test is decorated with data that matches its appId for ease of testing
50+
window.newrelic.noticeError('42')
51+
window.agent1.noticeError('1')
52+
window.agent2.noticeError('2')
53+
54+
// each payload in this test is decorated with data that matches its appId for ease of testing
55+
window.newrelic.addPageAction('42', { val: 42 })
56+
window.agent1.addPageAction('1', { val: 1 })
57+
window.agent2.addPageAction('2', { val: 2 })
58+
59+
// each payload in this test is decorated with data that matches its appId for ease of testing
60+
window.newrelic.log('42')
61+
window.agent1.log('1', { level: 'error' })
62+
window.agent2.log('2', { level: 'error' })
63+
})
64+
const [rumHarvests, errorsHarvests, insightsHarvests, logsHarvest] = await Promise.all([
65+
rumCapture.waitForResult({ totalCount: 1, timeout: 10000 }),
66+
(testSet.includes('register.jserrors') ? mfeErrorsCapture : regularErrorsCapture).waitForResult({ totalCount: 1, timeout: 10000 }),
67+
(testSet.includes('register.generic_events') ? mfeInsightsCapture : regularInsightsCapture).waitForResult({ totalCount: 1, timeout: 10000 }),
68+
logsCapture.waitForResult({ totalCount: 1, timeout: 10000 })
69+
])
70+
71+
const containerAgentEntityGuid = await browser.execute(function () {
72+
return Object.values(newrelic.initializedAgents)[0].runtime.appMetadata.agents[0].entityGuid
73+
})
74+
75+
// these props will get set to true once a test has matched it
76+
// if it gets tried again, the test will fail, since these should all
77+
// only have one distinct matching payload
78+
const tests = {
79+
42: { rum: false, err: false, pa: false, log: false }, // container agent defaults to appId 42
80+
1: { err: false, pa: false, log: false }, // agent1 instance
81+
2: { err: false, pa: false, log: false } // agent2 instance
82+
}
83+
84+
expect(rumHarvests).toHaveLength(1)
85+
expect(errorsHarvests.length).toBeGreaterThanOrEqual(1)
86+
expect(insightsHarvests.length).toBeGreaterThanOrEqual(1)
87+
expect(logsHarvest.length).toBeGreaterThanOrEqual(1)
88+
89+
// each type of test should check that:
90+
// each payload exists once per appId
91+
// each payload should have internal attributes matching it to the right appId
92+
rumHarvests.forEach(({ request: { query, body } }) => {
93+
expect(ranOnce(query.a, 'rum')).toEqual(true)
94+
})
95+
96+
errorsHarvests.forEach(({ request: { query, body } }) => {
97+
const data = body.err
98+
data.forEach((err, idx) => {
99+
const id = err.custom['mfe.id'] || query.a // MFEs use mfe.id, regular agents use appId
100+
if (Number(id) !== 42 && testSet.includes('register.jserrors')) {
101+
expect(err.custom['mfe.name']).toEqual('agent' + id)
102+
expect(err.custom.eventSource).toEqual('MicroFrontendBrowserAgent')
103+
expect(err.custom['parent.id']).toEqual(containerAgentEntityGuid)
104+
}
105+
countRuns(id, 'err')
106+
if (testSet.includes('register.jserrors')) {
107+
expect(ranOnce(id, 'err')).toEqual(true)
108+
expect(Number(id)).toEqual(Number(err.params.message))
109+
} else {
110+
expect(tests[id].err).toEqual(idx + 1) // each error gets lumped together under the same id without the feature flags
111+
expect(Number(id)).toEqual(42)
112+
}
113+
})
114+
})
115+
116+
insightsHarvests.forEach(({ request: { query, body } }) => {
117+
const data = body.ins
118+
data.forEach((ins, idx) => {
119+
if (ins.eventType === 'PageAction') {
120+
const id = ins['mfe.id'] || query.a // MFEs use mfe.id, regular agents use appId
121+
if (Number(id) !== 42 && testSet.includes('register.generic_events')) {
122+
expect(ins['mfe.name']).toEqual('agent' + id)
123+
expect(ins.eventSource).toEqual('MicroFrontendBrowserAgent')
124+
expect(ins['parent.id']).toEqual(containerAgentEntityGuid)
125+
}
126+
countRuns(id, 'pa')
127+
if (testSet.includes('register.generic_events')) {
128+
expect(ranOnce(id, 'pa')).toEqual(true)
129+
expect(Number(id)).toEqual(Number(ins.val))
130+
} else {
131+
expect(tests[id].pa).toEqual(idx + 1) // each error gets lumped together under the same id without the feature flags
132+
expect(Number(id)).toEqual(42)
133+
}
134+
}
135+
})
136+
})
137+
138+
logsHarvest.forEach(({ request: { query, body } }) => {
139+
const data = JSON.parse(body)[0]
140+
data.logs.forEach(log => {
141+
const id = log.attributes['mfe.id'] || 42 // MFEs use mfe.id, regular agents supply entity guid so just force it to 42 here if mfe id is not present
142+
if (Number(id) !== 42 && testSet.includes('register')) {
143+
expect(log.attributes['mfe.name']).toEqual('agent' + id)
144+
expect(log.attributes.eventSource).toEqual('MicroFrontendBrowserAgent')
145+
expect(log.attributes['parent.id']).toEqual(containerAgentEntityGuid)
146+
}
147+
expect(ranOnce(id, 'log')).toEqual(true)
148+
expect(Number(id)).toEqual(Number(log.message))
149+
})
150+
})
151+
152+
function ranOnce (appId, type) {
153+
if (tests[appId][type] > 1) return false
154+
countRuns(appId, type)
155+
return true
156+
}
157+
158+
function countRuns (appId, type) {
159+
tests[appId][type] ??= 0
160+
tests[appId][type]++
161+
}
162+
})
163+
})
164+
})
165+
11166
it('should expose api methods', async () => {
12167
await browser.url(await browser.testHandle.assetURL('api/local-storage-disallowed.html')) // Setup expects before loading the page
13168
.then(() => browser.waitForAgentLoad())

0 commit comments

Comments
 (0)