Skip to content

Commit 32a6b56

Browse files
feat: Capture details in marks and measures (#1332)
1 parent 429a7b6 commit 32a6b56

File tree

6 files changed

+88
-7
lines changed

6 files changed

+88
-7
lines changed

src/common/config/init.js

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ const model = () => {
6363
set capture_marks (val) { hiddenState.experimental.marks = val },
6464
get capture_measures () { return hiddenState.feature_flags.includes(FEATURE_FLAGS.MEASURES) || hiddenState.experimental.measures },
6565
set capture_measures (val) { hiddenState.experimental.measures = val },
66+
capture_detail: true,
6667
resources: {
6768
// whether to run this subfeature or not in the generic_events feature. false by default through experimental phase, but flipped to true once GA'd
6869
get enabled () { return hiddenState.feature_flags.includes(FEATURE_FLAGS.RESOURCES) || hiddenState.experimental.resources },

src/features/generic_events/aggregate/index.js

+24-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { applyFnToProps } from '../../../common/util/traverse'
1515
import { UserActionsAggregator } from './user-actions/user-actions-aggregator'
1616
import { isIFrameWindow } from '../../../common/dom/iframe'
1717
import { handle } from '../../../common/event-emitter/handle'
18+
import { isPureObject } from '../../../common/util/type-check'
1819

1920
export class Aggregate extends AggregateBase {
2021
static featureName = FEATURE_NAME
@@ -121,14 +122,35 @@ export class Aggregate extends AggregateBase {
121122
list.getEntries().forEach(entry => {
122123
try {
123124
handle(SUPPORTABILITY_METRIC_CHANNEL, ['Generic/Performance/' + type + '/Seen'])
125+
const detailObj = agentRef.init.performance.capture_detail ? createDetailAttrs(entry.detail) : {}
124126
this.addEvent({
127+
...detailObj,
125128
eventType: 'BrowserPerformance',
126129
timestamp: this.toEpoch(entry.startTime),
127130
entryName: cleanURL(entry.name),
128131
entryDuration: entry.duration,
129-
entryType: type,
130-
...(entry.detail && { entryDetail: entry.detail })
132+
entryType: type
131133
})
134+
135+
function createDetailAttrs (detail) {
136+
if (detail === null || detail === undefined) return {}
137+
else if (!isPureObject(detail)) return { entryDetail: detail }
138+
else return flattenJSON(detail)
139+
140+
function flattenJSON (nestedJSON, parentKey = 'entryDetail') {
141+
let items = {}
142+
if (nestedJSON === null || nestedJSON === undefined) return items
143+
Object.keys(nestedJSON).forEach(key => {
144+
let newKey = parentKey + '.' + key
145+
if (isPureObject(nestedJSON[key])) {
146+
Object.assign(items, flattenJSON(nestedJSON[key], newKey))
147+
} else {
148+
if (nestedJSON[key] !== null && nestedJSON[key] !== undefined) items[newKey] = nestedJSON[key]
149+
}
150+
})
151+
return items
152+
}
153+
}
132154
} catch (err) {
133155
}
134156
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<!DOCTYPE html>
2+
<!--
3+
Copyright 2020 New Relic Corporation.
4+
PDX-License-Identifier: Apache-2.0
5+
-->
6+
<html>
7+
<head>
8+
<title>RUM Unit Test</title>
9+
<script>performance.mark('before-agent')</script>
10+
{init} {config} {loader}
11+
<script>performance.mark('after-agent')</script>
12+
</head>
13+
<body>Instrumented
14+
<script>
15+
performance.measure('simple-object', {detail: {foo:'bar'}, start: 'before-agent', end: 'after-agent'} )
16+
performance.measure('nested-object', {detail: {nested1:{nested2:{nested3:{nested4: {foo: 'bar'}}}}, notNested: 'hi'}, start: 'before-agent', end: 'after-agent'} )
17+
performance.measure('string', {detail: 'hi', start: 'before-agent', end: 'after-agent'} )
18+
performance.measure('falsy-string', {detail: '', start: 'before-agent', end: 'after-agent'} )
19+
performance.measure('number', {detail: 1, start: 'before-agent', end: 'after-agent'} )
20+
performance.measure('falsy-number', {detail: 0, start: 'before-agent', end: 'after-agent'} )
21+
performance.measure('boolean', {detail: true, start: 'before-agent', end: 'after-agent'} )
22+
performance.measure('falsy-boolean', {detail: false, start: 'before-agent', end: 'after-agent'} )
23+
performance.measure('array', {detail: [1,2,3], start: 'before-agent', end: 'after-agent'} )
24+
performance.measure('falsy-array', {detail: [], start: 'before-agent', end: 'after-agent'} )
25+
</script>
26+
</body>
27+
</html>

tests/components/generic_events/aggregate/index.test.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,6 @@ describe('sub-features', () => {
166166
genericEventsAggregate.ee.emit('ua', [{ timeStamp: 234567, type: 'blur', target: window }])
167167

168168
const harvest = genericEventsAggregate.makeHarvestPayload() // force it to put the aggregation into the event buffer
169-
console.log(harvest)
170169
expect(harvest[0].payload.body.ins[0]).toMatchObject({
171170
eventType: 'UserAction',
172171
timestamp: expect.any(Number),
@@ -286,7 +285,7 @@ describe('sub-features', () => {
286285
})
287286

288287
test('should record measures when enabled', async () => {
289-
mainAgent.init.performance = { capture_measures: true, resources: { enabled: false, asset_types: [], first_party_domains: [], ignore_newrelic: true } }
288+
mainAgent.init.performance = { capture_measures: true, capture_detail: true, resources: { enabled: false, asset_types: [], first_party_domains: [], ignore_newrelic: true } }
290289
getInfo(mainAgent.agentIdentifier).jsAttributes = { globalFoo: 'globalBar' }
291290
const mockPerformanceObserver = jest.fn(cb => ({
292291
observe: () => {
@@ -295,7 +294,7 @@ describe('sub-features', () => {
295294
cb({getEntries: () => [{
296295
name: 'test',
297296
duration: 10,
298-
detail: JSON.stringify({ foo: 'bar' }),
297+
detail: { foo: 'bar' },
299298
startTime: performance.now()
300299
}]
301300
})
@@ -321,7 +320,7 @@ describe('sub-features', () => {
321320
entryName: 'test',
322321
entryDuration: 10,
323322
entryType: 'measure',
324-
entryDetail: JSON.stringify({ foo: 'bar' })
323+
'entryDetail.foo': 'bar'
325324
})
326325
})
327326
})

tests/specs/ins/harvesting.e2e.js

+32-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ describe('ins harvesting', () => {
224224

225225
expect(insHarvest.length).toEqual(1) // this page sets one measure
226226
expect(insHarvest[0]).toMatchObject({
227-
entryDetail: '{"foo":"bar"}',
227+
'entryDetail.foo': 'bar',
228228
entryDuration: expect.any(Number),
229229
eventType: 'BrowserPerformance',
230230
entryName: 'agent-load',
@@ -235,6 +235,37 @@ describe('ins harvesting', () => {
235235
})
236236
})
237237

238+
it('should spread detail', async () => {
239+
const testUrl = await browser.testHandle.assetURL('marks-and-measures-detail.html', getInsInit({ performance: { capture_measures: true } }))
240+
await browser.url(testUrl).then(() => browser.waitForAgentLoad())
241+
242+
const [[{ request: { body: { ins: insHarvest } } }]] = await Promise.all([
243+
insightsCapture.waitForResult({ totalCount: 1 })
244+
])
245+
246+
expect(insHarvest.length).toEqual(10) // this page sets 10 measures
247+
// detail: {foo:'bar'}
248+
expect(insHarvest.find(x => x.entryName === 'simple-object')['entryDetail.foo']).toEqual('bar')
249+
// detail: {nested1:{nested2:{nested3:{nested4: {foo: 'bar'}}}}
250+
expect(insHarvest.find(x => x.entryName === 'nested-object')['entryDetail.nested1.nested2.nested3.nested4.foo']).toEqual('bar')
251+
// detail: 'hi'
252+
expect(insHarvest.find(x => x.entryName === 'string').entryDetail).toEqual('hi')
253+
// detail: ''
254+
expect(insHarvest.find(x => x.entryName === 'falsy-string').entryDetail).toEqual('')
255+
// detail: 1
256+
expect(insHarvest.find(x => x.entryName === 'number').entryDetail).toEqual(1)
257+
// detail: 0
258+
expect(insHarvest.find(x => x.entryName === 'falsy-number').entryDetail).toEqual(0)
259+
// detail: true
260+
expect(insHarvest.find(x => x.entryName === 'boolean').entryDetail).toEqual(true)
261+
// detail: false
262+
expect(insHarvest.find(x => x.entryName === 'falsy-boolean').entryDetail).toEqual(false)
263+
// detail: [1,2,3]
264+
expect(insHarvest.find(x => x.entryName === 'array').entryDetail).toEqual('[1,2,3]')
265+
// detail: []
266+
expect(insHarvest.find(x => x.entryName === 'falsy-array').entryDetail).toEqual('[]')
267+
})
268+
238269
;[
239270
[getInsInit({ performance: { resources: { enabled: true, ignore_newrelic: false } } }), 'enabled'],
240271
[getInsInit({ performance: { resources: { enabled: false, ignore_newrelic: false } }, feature_flags: [FEATURE_FLAGS.RESOURCES] }), 'feature flag']

tests/unit/common/config/init.test.js

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ test('init props exist and return expected defaults', () => {
8686
expect(config.performance).toEqual({
8787
capture_marks: false,
8888
capture_measures: false,
89+
capture_detail: true,
8990
resources: {
9091
enabled: false,
9192
asset_types: [],

0 commit comments

Comments
 (0)