Skip to content

Commit

Permalink
add custom events api
Browse files Browse the repository at this point in the history
  • Loading branch information
metal-messiah committed Nov 23, 2024
1 parent e00a469 commit 69173ab
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 10 deletions.
4 changes: 3 additions & 1 deletion docs/warning-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,6 @@
### 44
`Invalid object passed to generic event aggregate. Missing "eventType".`
### 45
`An internal agent process failed to execute.`
`An internal agent process failed to execute.`
### 46
`A reserved eventType was provided to recordCustomEvent(...) -- The event was not recorded.`
25 changes: 19 additions & 6 deletions src/features/generic_events/aggregate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { stringify } from '../../../common/util/stringify'
import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
import { cleanURL } from '../../../common/url/clean-url'
import { FEATURE_NAME } from '../constants'
import { FEATURE_NAME, RESERVED_EVENT_TYPES } from '../constants'
import { initialLocation, isBrowserScope } from '../../../common/constants/runtime'
import { AggregateBase } from '../../utils/aggregate-base'
import { warn } from '../../../common/util/console'
Expand Down Expand Up @@ -34,12 +34,21 @@ export class Aggregate extends AggregateBase {
return
}

registerHandler('api-recordCustomEvent', (timestamp, eventType, attributes) => {
if (RESERVED_EVENT_TYPES.includes(eventType)) return warn(46)
this.addEvent({

Check warning on line 39 in src/features/generic_events/aggregate/index.js

View check run for this annotation

Codecov / codecov/patch

src/features/generic_events/aggregate/index.js#L39

Added line #L39 was not covered by tests
eventType,
timestamp: this.toEpoch(timestamp),
...attributes
}, false)
}, this.featureName, this.ee)

if (agentRef.init.page_action.enabled) {
registerHandler('api-addPageAction', (timestamp, name, attributes) => {
this.addEvent({
...attributes,
eventType: 'PageAction',
timestamp: Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(timestamp)),
timestamp: this.toEpoch(timestamp),
timeSinceLoad: timestamp / 1000,
actionName: name,
referrerUrl: this.referrerUrl,
Expand All @@ -63,7 +72,7 @@ export class Aggregate extends AggregateBase {
const { target, timeStamp, type } = aggregatedUserAction.event
this.addEvent({
eventType: 'UserAction',
timestamp: Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(timeStamp)),
timestamp: this.toEpoch(timeStamp),
action: type,
actionCount: aggregatedUserAction.count,
actionDuration: aggregatedUserAction.relativeMs[aggregatedUserAction.relativeMs.length - 1],
Expand Down Expand Up @@ -105,7 +114,7 @@ export class Aggregate extends AggregateBase {
try {
this.addEvent({
eventType: 'BrowserPerformance',
timestamp: Math.floor(agentRef.runtime.timeKeeper.correctRelativeTimestamp(entry.startTime)),
timestamp: this.toEpoch(entry.startTime),
entryName: entry.name,
entryDuration: entry.duration,
entryType: type,
Expand Down Expand Up @@ -147,7 +156,7 @@ export class Aggregate extends AggregateBase {
* @param {object=} obj the event object for storing in the event buffer
* @returns void
*/
addEvent (obj = {}) {
addEvent (obj = {}, shouldAddDefaultAttributes = true) {
if (!obj || !Object.keys(obj).length) return
if (!obj.eventType) {
warn(44)
Expand All @@ -171,7 +180,7 @@ export class Aggregate extends AggregateBase {
/** Agent-level custom attributes */
...(this.agentRef.info.jsAttributes || {}),
/** Fallbacks for required properties in-case the event did not supply them, should take precedence over agent-level custom attrs */
...defaultEventAttributes,
...(shouldAddDefaultAttributes && defaultEventAttributes),
/** Event-specific attributes take precedence over agent-level custom attributes and fallbacks */
...obj
}
Expand All @@ -195,4 +204,8 @@ export class Aggregate extends AggregateBase {
queryStringsBuilder () {
return { ua: this.agentRef.info.userAttributes, at: this.agentRef.info.atts }
}

toEpoch (timestamp) {
return Math.floor(this.agentRef.runtime.timeKeeper.correctRelativeTimestamp(timestamp))
}
}
2 changes: 2 additions & 0 deletions src/features/generic_events/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ export const OBSERVED_WINDOW_EVENTS = ['focus', 'blur']

export const RAGE_CLICK_THRESHOLD_EVENTS = 4
export const RAGE_CLICK_THRESHOLD_MS = 1000

export const RESERVED_EVENT_TYPES = ['PageAction', 'UserAction', 'BrowserPerformance']
2 changes: 1 addition & 1 deletion src/loaders/api/api-methods.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SR_EVENT_EMITTER_TYPES } from '../../features/session_replay/constants'

export const apiMethods = [
'setErrorHandler', 'finished', 'addToTrace', 'addRelease',
'setErrorHandler', 'finished', 'addToTrace', 'addRelease', 'recordCustomEvent',
'addPageAction', 'setCurrentRouteName', 'setPageViewName', 'setCustomAttribute',
'interaction', 'noticeError', 'setUserId', 'setApplicationVersion', 'start',
SR_EVENT_EMITTER_TYPES.RECORD, SR_EVENT_EMITTER_TYPES.PAUSE, 'log', 'wrapLogger'
Expand Down
2 changes: 2 additions & 0 deletions src/loaders/api/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ export function setAPI (agentIdentifier, forceDrain, runSoftNavOverSpa = false)

apiInterface.addPageAction = apiCall(prefix, 'addPageAction', true, FEATURE_NAMES.genericEvents)

apiInterface.recordCustomEvent = apiCall(prefix, 'recordCustomEvent', true, FEATURE_NAMES.genericEvents)

apiInterface.setPageViewName = function (name, host) {
if (typeof name !== 'string') return
if (name.charAt(0) !== '/') name = '/' + name
Expand Down
10 changes: 10 additions & 0 deletions src/loaders/micro-agent-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ export class MicroAgentBase {
return this.#callMethod('addPageAction', name, attributes)
}

/**
* Records a custom event with a specified eventType and attributes.
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/recordCustomEvent/}
* @param {string} eventType The eventType to store the event as.
* @param {object} [attributes] JSON object with one or more key/value pairs. For example: {key:"value"}.
*/
recordCustomEvent (eventType, attributes) {
return this.#callMethod('recordCustomEvent', eventType, attributes)

Check warning on line 40 in src/loaders/micro-agent-base.js

View check run for this annotation

Codecov / codecov/patch

src/loaders/micro-agent-base.js#L39-L40

Added lines #L39 - L40 were not covered by tests
}

/**
* Groups page views to help URL structure or to capture the URL's routing information.
* {@link https://docs.newrelic.com/docs/browser/new-relic-browser/browser-apis/setpageviewname/}
Expand Down
3 changes: 2 additions & 1 deletion tests/components/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,13 @@ describe('setAPI', () => {
test('should add expected api methods returned object', () => {
const apiInterface = setAPI(agentId, true)

expect(Object.keys(apiInterface).length).toEqual(17)
expect(Object.keys(apiInterface).length).toEqual(18)
expect(typeof apiInterface.setErrorHandler).toEqual('function')
expect(typeof apiInterface.finished).toEqual('function')
expect(typeof apiInterface.addToTrace).toEqual('function')
expect(typeof apiInterface.addRelease).toEqual('function')
expect(typeof apiInterface.addPageAction).toEqual('function')
expect(typeof apiInterface.recordCustomEvent).toEqual('function')
expect(typeof apiInterface.setCurrentRouteName).toEqual('function')
expect(typeof apiInterface.setPageViewName).toEqual('function')
expect(typeof apiInterface.setCustomAttribute).toEqual('function')
Expand Down
40 changes: 40 additions & 0 deletions tests/specs/ins/harvesting.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,46 @@ describe('ins harvesting', () => {
expect(estimatedEventTime < receiptTime).toEqual(true) //, 'estimated event time (' + estimatedEventTime + ') < receipt time (' + receiptTime + ')')
})

it('should submit Custom Events', async () => {
const testUrl = await browser.testHandle.assetURL('instrumented.html')
await browser.url(testUrl)
.then(() => browser.waitForAgentLoad())

const [[{ request: { body: { ins: customEventsHarvest } } }]] = await Promise.all([
insightsCapture.waitForResult({ totalCount: 1 }),
browser.execute(function () {
newrelic.recordCustomEvent('event0', { index: 0 })
newrelic.recordCustomEvent('event1', { index: 1 })
})
])

expect(customEventsHarvest.length).toEqual(2)
customEventsHarvest.forEach((event, i) => {
expect(event.eventType).toEqual(`event${i}`)
expect(event.index).toEqual(i)
expect(event.timestamp).toBeGreaterThan(0)
expect(event.timestamp).toBeLessThan(Date.now())
expect(Object.keys(event).length).toEqual(3)
})
})

it('should not submit reserved Custom Events', async () => {
const testUrl = await browser.testHandle.assetURL('instrumented.html')
await browser.url(testUrl)
.then(() => browser.waitForAgentLoad())

const [customEventsHarvest] = await Promise.all([
insightsCapture.waitForResult({ timeout: 10000 }),
browser.execute(function () {
newrelic.recordCustomEvent('PageAction', { index: 0 })
newrelic.recordCustomEvent('BrowserPerformance', { index: 1 })
newrelic.recordCustomEvent('UserAction', { index: 2 })
})
])

expect(customEventsHarvest.length).toEqual(0)
})

it('should submit UserAction (when enabled)', async () => {
const testUrl = await browser.testHandle.assetURL('user-actions.html', { init: { user_actions: { enabled: true } } })
await browser.url(testUrl).then(() => browser.waitForAgentLoad())
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/loaders/api/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ describe('setTopLevelCallers', () => {
setTopLevelCallers()

const nreum = gosCDN()
expect(Object.keys(nreum).length).toEqual(17)
expect(Object.keys(nreum).length).toEqual(18)
expect(typeof nreum.setErrorHandler).toEqual('function')
expect(typeof nreum.finished).toEqual('function')
expect(typeof nreum.addToTrace).toEqual('function')
expect(typeof nreum.addRelease).toEqual('function')
expect(typeof nreum.addPageAction).toEqual('function')
expect(typeof nreum.recordCustomEvent).toEqual('function')
expect(typeof nreum.setCurrentRouteName).toEqual('function')
expect(typeof nreum.setPageViewName).toEqual('function')
expect(typeof nreum.setCustomAttribute).toEqual('function')
Expand Down

0 comments on commit 69173ab

Please sign in to comment.