Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions src/features/generic_events/aggregate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export class Aggregate extends AggregateBase {
}, this.featureName, this.ee)
}

registerHandler('api-measure', (args, n) => {
registerHandler('api-measure', (args, n, target) => {
const { start, duration, customAttributes } = args

const event = {
Expand All @@ -239,7 +239,7 @@ export class Aggregate extends AggregateBase {
entryType: 'measure'
}

this.addEvent(event)
this.addEvent(event, target)
}, this.featureName, this.ee)

this.drain()
Expand Down
11 changes: 11 additions & 0 deletions src/interfaces/registered-entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ export class RegisteredEntity {
warn(35, 'recordCustomEvent')
}

/**
* Measures a task that is recorded as a BrowserPerformance event.
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/measure/}
* @param {string} name The name of the task
* @param {{start: number, end: number, duration: number, customAttributes: object}} [options] An object used to control the way the measure API operates
* @returns {{start: number, end: number, duration: number, customAttributes: object}} Measurement details
*/
measure (name, options) {
warn(35, 'measure')
}

/**
* Adds a user-defined attribute name and value to subsequent events on the page for the registered target. Note -- the persist flag does not work with the register API.
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/setcustomattribute/}
Expand Down
2 changes: 1 addition & 1 deletion src/loaders/api-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export class ApiBase {
* Measures a task that is recorded as a BrowserPerformance event.
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/measure/}
* @param {string} name The name of the task
* @param {object?} options An object used to control the way the measure API operates
* @param {{start: number, end: number, duration: number, customAttributes: object}} [options] An object used to control the way the measure API operates
* @returns {{start: number, end: number, duration: number, customAttributes: object}} Measurement details
*/
measure (name, options) {
Expand Down
67 changes: 34 additions & 33 deletions src/loaders/api/measure.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,44 +10,45 @@ import { prefix, MEASURE } from './constants'
import { setupAPI } from './sharedHandlers'

export function setupMeasureAPI (agent) {
setupAPI(MEASURE, function (name, options) {
const n = now()
const { start, end, customAttributes } = options || {}
const returnObj = { customAttributes: customAttributes || {} }
setupAPI(MEASURE, (name, options) => measure(name, options, agent), agent)
}

export function measure (name, options, agentRef, target, timestamp = now()) {
const { start, end, customAttributes } = options || {}
const returnObj = { customAttributes: customAttributes || {} }

if (typeof returnObj.customAttributes !== 'object' || typeof name !== 'string' || name.length === 0) {
warn(57)
return
}
if (typeof returnObj.customAttributes !== 'object' || typeof name !== 'string' || name.length === 0) {
warn(57)
return
}

/**
/**
* getValueFromTiming - Helper function to extract a numeric value from a supplied option.
* @param {Number|PerformanceMark} [timing] The timing value
* @param {Number} [d] The default value to return if timing is invalid
* @returns {Number} The timing value or the default value
*/
const getValueFromTiming = (timing, d) => {
if (timing == null) return d
if (typeof timing === 'number') return timing
if (timing instanceof PerformanceMark) return timing.startTime
return Number.NaN
}

returnObj.start = getValueFromTiming(start, 0)
returnObj.end = getValueFromTiming(end, n)
if (Number.isNaN(returnObj.start) || Number.isNaN(returnObj.end)) {
warn(57)
return
}

returnObj.duration = returnObj.end - returnObj.start
if (returnObj.duration < 0) {
warn(58)
return
}

handle(prefix + MEASURE, [returnObj, name], undefined, FEATURE_NAMES.genericEvents, agent.ee)

return returnObj
}, agent)
const getValueFromTiming = (timing, d) => {
if (timing == null) return d
if (typeof timing === 'number') return timing
if (timing instanceof PerformanceMark) return timing.startTime
return Number.NaN
}

returnObj.start = getValueFromTiming(start, 0)
returnObj.end = getValueFromTiming(end, timestamp)
if (Number.isNaN(returnObj.start) || Number.isNaN(returnObj.end)) {
warn(57)
return
}

returnObj.duration = returnObj.end - returnObj.start
if (returnObj.duration < 0) {
warn(58)
return
}

handle(prefix + MEASURE, [returnObj, name, target], undefined, FEATURE_NAMES.genericEvents, agentRef.ee)

return returnObj
}
1 change: 1 addition & 0 deletions src/loaders/api/register-api-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* @property {(message: string, options?: { customAttributes?: object, level?: 'ERROR' | 'TRACE' | 'DEBUG' | 'INFO' | 'WARN'}) => void} log - Capture a log for the registered entity.
* @property {(error: Error | string, customAttributes?: object) => void} noticeError - Notice an error for the registered entity.
* @property {(eventType: string, attributes?: Object) => void} recordCustomEvent - Record a custom event for the registered entity.
* @property {(eventType: string, options?: {start: number, end: number, duration: number, customAttributes: object}) => {{start: number, end: number, duration: number, customAttributes: object}}} measure - Measures a task that is recorded as a BrowserPerformance event.
* @property {(value: string | null) => void} setApplicationVersion - Add an application.version attribute to all outgoing data for the registered entity.
* @property {(name: string, value: string | number | boolean | null, persist?: boolean) => void} setCustomAttribute - Add a custom attribute to outgoing data for the registered entity.
* @property {(value: string | null) => void} setUserId - Add an enduser.id attribute to all outgoing API data for the registered entity.
Expand Down
2 changes: 2 additions & 0 deletions src/loaders/api/register.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { log } from './log'
import { addPageAction } from './addPageAction'
import { noticeError } from './noticeError'
import { single } from '../../common/util/invoke'
import { measure } from './measure'
import { recordCustomEvent } from './recordCustomEvent'

/**
Expand Down Expand Up @@ -79,6 +80,7 @@ export function buildRegisterApi (agentRef, target) {
const api = {
addPageAction: (name, attributes = {}) => report(addPageAction, [name, { ...attrs, ...attributes }, agentRef], target),
log: (message, options = {}) => report(log, [message, { ...options, customAttributes: { ...attrs, ...(options.customAttributes || {}) } }, agentRef], target),
measure: (name, options = {}) => report(measure, [name, { ...options, customAttributes: { ...attrs, ...(options.customAttributes || {}) } }, agentRef], target),
noticeError: (error, attributes = {}) => report(noticeError, [error, { ...attrs, ...attributes }, agentRef], target),
recordCustomEvent: (eventType, attributes = {}) => report(recordCustomEvent, [eventType, { ...attrs, ...attributes }, agentRef], target),
setApplicationVersion: (value) => setLocalValue('application.version', value),
Expand Down
1 change: 1 addition & 0 deletions tests/dts/api.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ expectType<MicroAgent>(microAgent)
expectType<(timeStamp?: number) => any>(agent.finished)
expectType<(name: string, id: string) => any>(agent.addRelease)
expectType<(eventType: string, attributes?: Object) => any>(agent.recordCustomEvent)
expectType<(name: string, options?: {start: number, end: number, duration: number, customAttributes: object}) => ({start: number, end: number, duration: number, customAttributes: object})>(agent.measure)
/** micro agent has a different implementation of start api we are stuck with until that entry point is removed */
expectType<(() => any) | ((featureNames?: string | string[] | undefined) => boolean)>(agent.start)
expectType<() => any>(agent.recordReplay)
Expand Down
5 changes: 2 additions & 3 deletions tests/dts/registered-entity.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ const registeredEntityWithNumber = new RegisteredEntity(entityOptsWithNumber)
const registeredEntityWithString = new RegisteredEntity(entityOptsWithString)

// fits the definition of both the RegisterAPI and RegisteredEntity because RegisteredEntity object assigns from RegisterAPI here
expectType<RegisterAPI>(registeredEntityWithNumber)
expectType<RegisteredEntity>(registeredEntityWithNumber)
expectType<RegisterAPI>(registeredEntityWithString)
expectType<RegisteredEntity>(registeredEntityWithNumber)
expectType<RegisteredEntity>(registeredEntityWithString)

// Test all registered entity methods for each variant
Expand All @@ -28,6 +26,7 @@ expectType<RegisteredEntity>(registeredEntityWithString)
expectType<(eventType: string, attributes?: Object) => void>(registeredEntity.recordCustomEvent)
expectType<(value: string | null) => void>(registeredEntity.setApplicationVersion)
expectType<(name: string, value: string | number | boolean | null, persist?: boolean) => void>(registeredEntity.setCustomAttribute)
expectType<(name: string, options?: {start: number, end: number, duration: number, customAttributes: object}) => ({start: number, end: number, duration: number, customAttributes: object})>(registeredEntity.measure)
expectType<(value: string | null) => void>(registeredEntity.setUserId)
expectType<RegisterAPIMetadata>(registeredEntity.metadata)
})
Expand Down
26 changes: 21 additions & 5 deletions tests/specs/api.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ describe('newrelic api', () => {
window.newrelic.recordCustomEvent('CustomEvent', { val: 42 })
window.agent1.recordCustomEvent('CustomEvent', { val: 1 })
window.agent2.recordCustomEvent('CustomEvent', { val: 2 })

// each payload in this test is decorated with data that matches its appId for ease of testing
window.newrelic.measure('42')
window.agent1.measure('1')
window.agent2.measure('2')
})
const [rumHarvests, errorsHarvests, insightsHarvests, logsHarvest] = await Promise.all([
rumCapture.waitForResult({ totalCount: 1, timeout: 10000 }),
Expand All @@ -81,9 +86,9 @@ describe('newrelic api', () => {
// if it gets tried again, the test will fail, since these should all
// only have one distinct matching payload
const tests = {
42: { rum: false, err: false, pa: false, log: false, rce: false }, // container agent defaults to appId 42
1: { err: false, pa: false, log: false, rce: false }, // agent1 instance
2: { err: false, pa: false, log: false, rce: false } // agent2 instance
42: { rum: false, err: false, pa: false, log: false, rce: false, measure: false }, // container agent defaults to appId 42
1: { err: false, pa: false, log: false, rce: false, measure: false }, // agent1 instance
2: { err: false, pa: false, log: false, rce: false, measure: false } // agent2 instance
}

expect(rumHarvests).toHaveLength(1)
Expand Down Expand Up @@ -125,7 +130,7 @@ describe('newrelic api', () => {
insightsHarvests.forEach(({ request: { query, body } }) => {
const data = body.ins
data.forEach((ins, idx) => {
if (ins.eventType === 'PageAction' || ins.eventType === 'CustomEvent') {
if (ins.eventType === 'PageAction' || ins.eventType === 'CustomEvent' || (ins.eventType === 'BrowserPerformance' && ins.entryType === 'measure')) {
const id = ins['mfe.id'] || query.a // MFEs use mfe.id, regular agents use appId
if (Number(id) !== 42 && testSet.includes('register.generic_events')) {
expect(ins['mfe.name']).toEqual('agent' + id)
Expand All @@ -137,7 +142,14 @@ describe('newrelic api', () => {
}
}

const countType = ins.eventType === 'PageAction' ? 'pa' : 'rce'
let countType
if (ins.eventType === 'PageAction') {
countType = 'pa'
} else if (ins.eventType === 'CustomEvent') {
countType = 'rce'
} else if (ins.eventType === 'BrowserPerformance') {
countType = 'measure'
}
countRuns(id, countType)
}
})
Expand All @@ -147,14 +159,18 @@ describe('newrelic api', () => {
// each item gets lumped together under the same id without the feature flags
expect(tests['42'].pa).toEqual(testSet.includes('register') ? 3 : 1)
expect(tests['42'].rce).toEqual(testSet.includes('register') ? 3 : 1)
expect(tests['42'].measure).toEqual(testSet.includes('register') ? 3 : 1)
} else {
if (testSet.includes('register')) {
expect(ranOnce('42', 'pa')).toEqual(true)
expect(ranOnce('42', 'rce')).toEqual(true)
expect(ranOnce('42', 'measure')).toEqual(true)
expect(ranOnce('1', 'pa')).toEqual(true)
expect(ranOnce('1', 'rce')).toEqual(true)
expect(ranOnce('1', 'measure')).toEqual(true)
expect(ranOnce('2', 'pa')).toEqual(true)
expect(ranOnce('2', 'rce')).toEqual(true)
expect(ranOnce('2', 'measure')).toEqual(true)
}
}

Expand Down
27 changes: 21 additions & 6 deletions tests/specs/npm/registered-entity.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ describe('registered-entity', () => {
window.newrelic.recordCustomEvent('CustomEvent', { val: 42 })
window.agent1.recordCustomEvent('CustomEvent', { val: 1 })
window.agent2.recordCustomEvent('CustomEvent', { val: 2 })

// each payload in this test is decorated with data that matches its appId for ease of testing
window.newrelic.measure('42')
window.agent1.measure('1')
window.agent2.measure('2')
})
const [rumHarvests, errorsHarvests, insightsHarvests, logsHarvest] = await Promise.all([
rumCapture.waitForResult({ totalCount: 1, timeout: 10000 }),
Expand All @@ -79,9 +84,9 @@ describe('registered-entity', () => {
// if it gets tried again, the test will fail, since these should all
// only have one distinct matching payload
const tests = {
42: { rum: false, err: false, pa: false, log: false, rce: false }, // container agent defaults to appId 42
1: { err: false, pa: false, log: false, rce: false }, // agent1 instance
2: { err: false, pa: false, log: false, rce: false } // agent2 instance
42: { rum: false, err: false, pa: false, log: false, rce: false, measure: false }, // container agent defaults to appId 42
1: { err: false, pa: false, log: false, rce: false, measure: false }, // agent1 instance
2: { err: false, pa: false, log: false, rce: false, measure: false } // agent2 instance
}

expect(rumHarvests).toHaveLength(1)
Expand Down Expand Up @@ -124,7 +129,7 @@ describe('registered-entity', () => {
insightsHarvests.forEach(({ request: { query, body } }) => {
const data = body.ins
data.forEach((ins, idx) => {
if (ins.eventType === 'PageAction' || ins.eventType === 'CustomEvent') {
if (ins.eventType === 'PageAction' || ins.eventType === 'CustomEvent' || (ins.eventType === 'BrowserPerformance' && ins.entryType === 'measure')) {
const id = ins['mfe.id'] || query.a // MFEs use mfe.id, regular agents use appId
if (Number(id) !== 42 && testSet.includes('register.generic_events')) {
expect(ins['mfe.name']).toEqual('agent' + id)
Expand All @@ -135,8 +140,14 @@ describe('registered-entity', () => {
expect(ins.appId).toEqual(42)
}
}

const countType = ins.eventType === 'PageAction' ? 'pa' : 'rce'
let countType
if (ins.eventType === 'PageAction') {
countType = 'pa'
} else if (ins.eventType === 'CustomEvent') {
countType = 'rce'
} else if (ins.eventType === 'BrowserPerformance') {
countType = 'measure'
}
countRuns(id, countType)
}
})
Expand All @@ -146,14 +157,18 @@ describe('registered-entity', () => {
// each item gets lumped together under the same id without the feature flags
expect(tests['42'].pa).toEqual(testSet.includes('register') ? 3 : 1)
expect(tests['42'].rce).toEqual(testSet.includes('register') ? 3 : 1)
expect(tests['42'].measure).toEqual(testSet.includes('register') ? 3 : 1)
} else {
if (testSet.includes('register')) {
expect(ranOnce('42', 'pa')).toEqual(true)
expect(ranOnce('42', 'rce')).toEqual(true)
expect(ranOnce('42', 'measure')).toEqual(true)
expect(ranOnce('1', 'pa')).toEqual(true)
expect(ranOnce('1', 'rce')).toEqual(true)
expect(ranOnce('1', 'measure')).toEqual(true)
expect(ranOnce('2', 'pa')).toEqual(true)
expect(ranOnce('2', 'rce')).toEqual(true)
expect(ranOnce('2', 'measure')).toEqual(true)
}
}

Expand Down
Loading