Skip to content

Commit

Permalink
feat: Create generic events feature (#1121)
Browse files Browse the repository at this point in the history
  • Loading branch information
metal-messiah authored Jul 26, 2024
1 parent 26146d6 commit 63ab04f
Show file tree
Hide file tree
Showing 23 changed files with 232 additions and 24 deletions.
21 changes: 21 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
"features/page_action": [
"dist/types/features/page_action/index.d.ts"
],
"features/generic_events": [
"dist/types/features/generic_events/index.d.ts"
],
"features/page_view_event": [
"dist/types/features/page_view_event/index.d.ts"
],
Expand All @@ -53,6 +56,9 @@
"features/session_trace": [
"dist/types/features/session_trace/index.d.ts"
],
"features/session_replay": [
"dist/types/features/session_replay/index.d.ts"
],
"features/spa": [
"dist/types/features/spa/index.d.ts"
]
Expand Down Expand Up @@ -89,6 +95,11 @@
"require": "./dist/cjs/features/ajax/index.js",
"default": "./dist/esm/features/ajax/index.js"
},
"./features/generic_events": {
"types": "./dist/types/features/generic_events/index.d.ts",
"require": "./dist/cjs/features/generic_events/index.js",
"default": "./dist/esm/features/generic_events/index.js"
},
"./features/jserrors": {
"types": "./dist/types/features/jserrors/index.d.ts",
"require": "./dist/cjs/features/jserrors/index.js",
Expand Down Expand Up @@ -119,11 +130,21 @@
"require": "./dist/cjs/features/page_view_timing/index.js",
"default": "./dist/esm/features/page_view_timing/index.js"
},
"./features/session_replay": {
"types": "./dist/types/features/session_replay/index.d.ts",
"require": "./dist/cjs/features/session_replay/index.js",
"default": "./dist/esm/features/session_replay/index.js"
},
"./features/session_trace": {
"types": "./dist/types/features/session_trace/index.d.ts",
"require": "./dist/cjs/features/session_trace/index.js",
"default": "./dist/esm/features/session_trace/index.js"
},
"./features/soft_navigations": {
"types": "./dist/types/features/soft_navigations/index.d.ts",
"require": "./dist/cjs/features/soft_navigations/index.js",
"default": "./dist/esm/features/soft_navigations/index.js"
},
"./features/spa": {
"types": "./dist/types/features/spa/index.d.ts",
"require": "./dist/cjs/features/spa/index.js",
Expand Down
2 changes: 2 additions & 0 deletions src/cdn/experimental.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Instrument as InstrumentErrors } from '../features/jserrors/instrument'
import { Instrument as InstrumentXhr } from '../features/ajax/instrument'
import { Instrument as InstrumentSessionTrace } from '../features/session_trace/instrument'
import { Instrument as InstrumentSessionReplay } from '../features/session_replay/instrument'
import { Instrument as InstrumentGenericEvents } from '../features/generic_events/instrument'
// import { Instrument as InstrumentSpa } from '../features/spa/instrument'
import { Instrument as InstrumentSoftNav } from '../features/soft_navigations/instrument'
import { Instrument as InstrumentPageAction } from '../features/page_action/instrument'
Expand All @@ -32,6 +33,7 @@ new Agent({
InstrumentMetrics,
InstrumentPageAction,
InstrumentErrors,
InstrumentGenericEvents,
InstrumentLogs,
// InstrumentSpa,
InstrumentSoftNav
Expand Down
2 changes: 2 additions & 0 deletions src/cdn/polyfills/pro.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Instrument as InstrumentErrors } from '../../features/jserrors/instrume
import { Instrument as InstrumentXhr } from '../../features/ajax/instrument'
import { Instrument as InstrumentSessionTrace } from '../../features/session_trace/instrument'
import { Instrument as InstrumentPageAction } from '../../features/page_action/instrument'
import { Instrument as InstrumentGenericEvents } from '../../features/generic_events/instrument'
import { Instrument as InstrumentLogs } from '../../features/logging/instrument'

new Agent({
Expand All @@ -24,6 +25,7 @@ new Agent({
InstrumentMetrics,
InstrumentPageAction,
InstrumentErrors,
InstrumentGenericEvents,
InstrumentLogs
],
loaderType: 'pro-polyfills'
Expand Down
2 changes: 2 additions & 0 deletions src/cdn/polyfills/spa.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Instrument as InstrumentXhr } from '../../features/ajax/instrument'
import { Instrument as InstrumentSessionTrace } from '../../features/session_trace/instrument'
import { Instrument as InstrumentSpa } from '../../features/spa/instrument'
import { Instrument as InstrumentPageAction } from '../../features/page_action/instrument'
import { Instrument as InstrumentGenericEvents } from '../../features/generic_events/instrument'
import { Instrument as InstrumentLogs } from '../../features/logging/instrument'

new Agent({
Expand All @@ -25,6 +26,7 @@ new Agent({
InstrumentMetrics,
InstrumentPageAction,
InstrumentErrors,
InstrumentGenericEvents,
InstrumentLogs,
InstrumentSpa
],
Expand Down
2 changes: 2 additions & 0 deletions src/cdn/pro.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Instrument as InstrumentXhr } from '../features/ajax/instrument'
import { Instrument as InstrumentSessionTrace } from '../features/session_trace/instrument'
import { Instrument as InstrumentSessionReplay } from '../features/session_replay/instrument'
import { Instrument as InstrumentPageAction } from '../features/page_action/instrument'
import { Instrument as InstrumentGenericEvents } from '../features/generic_events/instrument'
import { Instrument as InstrumentLogs } from '../features/logging/instrument'

new Agent({
Expand All @@ -25,6 +26,7 @@ new Agent({
InstrumentMetrics,
InstrumentPageAction,
InstrumentErrors,
InstrumentGenericEvents,
InstrumentLogs
],
loaderType: 'pro'
Expand Down
2 changes: 2 additions & 0 deletions src/cdn/spa.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Instrument as InstrumentSessionReplay } from '../features/session_repla
import { Instrument as InstrumentSoftNav } from '../features/soft_navigations/instrument'
import { Instrument as InstrumentSpa } from '../features/spa/instrument'
import { Instrument as InstrumentPageAction } from '../features/page_action/instrument'
import { Instrument as InstrumentGenericEvents } from '../features/generic_events/instrument'
import { Instrument as InstrumentLogs } from '../features/logging/instrument'

new Agent({
Expand All @@ -26,6 +27,7 @@ new Agent({
InstrumentMetrics,
InstrumentPageAction,
InstrumentErrors,
InstrumentGenericEvents,
InstrumentLogs,
InstrumentSoftNav,
InstrumentSpa // either the softnav or the old spa will be used (not both), but we still need to pack both to avoid dynamic import for instrument files
Expand Down
1 change: 1 addition & 0 deletions src/common/config/state/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const model = () => {
allowed_origins: undefined
},
feature_flags: [],
generic_events: { enabled: true, harvestTimeSeconds: 10, autoStart: true },
harvest: { tooManyRequestsDelay: 60 },
jserrors: { enabled: true, harvestTimeSeconds: 10, autoStart: true },
logging: { enabled: true, harvestTimeSeconds: 10, autoStart: true, level: LOG_LEVELS.INFO },
Expand Down
114 changes: 114 additions & 0 deletions src/features/generic_events/aggregate/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
import { stringify } from '../../../common/util/stringify'
import { HarvestScheduler } from '../../../common/harvest/harvest-scheduler'
import { cleanURL } from '../../../common/url/clean-url'
import { getConfigurationValue, getInfo, getRuntime } from '../../../common/config/config'
import { FEATURE_NAME } from '../constants'
import { isBrowserScope } from '../../../common/constants/runtime'
import { AggregateBase } from '../../utils/aggregate-base'
import { warn } from '../../../common/util/console'
import { now } from '../../../common/timing/now'
import { deregisterDrain } from '../../../common/drain/drain'

export class Aggregate extends AggregateBase {
#agentRuntime
static featureName = FEATURE_NAME
constructor (agentIdentifier, aggregator) {
super(agentIdentifier, aggregator, FEATURE_NAME)

this.eventsPerHarvest = 1000
this.harvestTimeSeconds = getConfigurationValue(this.agentIdentifier, 'generic_events.harvestTimeSeconds')

this.referrerUrl = undefined
this.currentEvents = []

this.events = []
this.overflow = []

this.#agentRuntime = getRuntime(this.agentIdentifier)

if (isBrowserScope && document.referrer) this.referrerUrl = cleanURL(document.referrer)

this.waitForFlags(['ins']).then(([ins]) => {
if (!ins) {
this.blocked = true
deregisterDrain(this.agentIdentifier, this.featureName)
return
}

// handle page actions and other generic events here
this.harvestScheduler = new HarvestScheduler('ins', { onFinished: (...args) => this.onHarvestFinished(...args) }, this)
this.harvestScheduler.harvest.on('ins', (...args) => this.onHarvestStarted(...args))
// this.harvestScheduler.startTimer(this.harvestTimeSeconds, 0)

this.drain()
})
}

onHarvestStarted (options) {
const { userAttributes, atts } = getInfo(this.agentIdentifier)
const harvestEvents = this.overflow.length ? this.overflow.splice(0, Infinity) : this.events.splice(0, Infinity)
var payload = ({
qs: {
ua: userAttributes,
at: atts
},
body: {
ins: harvestEvents
}
})

if (options.retry) {
this.currentEvents = harvestEvents
}

return payload
}

onHarvestFinished (result) {
if (result && result.sent && result.retry && this.currentEvents.length) {
this.events = this.currentEvents.concat(this.events)
this.currentEvents = []
}
}

// WARNING: Insights times are in seconds. EXCEPT timestamp, which is in ms.
addEvent (obj = {}) {
if (!obj || !Object.keys(obj).length) return
if (!obj.eventType) {
warn('Invalid object passed to generic event aggregate. Missing "eventType".')
return
}

for (let key in obj) {
let val = obj[key]
if (key === 'timestamp') val = this.#agentRuntime.timeKeeper.correctAbsoluteTimestamp(val)
obj[key] = (val && typeof val === 'object' ? stringify(val) : val)
}

const eventAttributes = {
/** Agent-level custom attributes */
...(getInfo(this.agentIdentifier).jsAttributes || {}),
/** Common attributes shared on all generic events */
referrerUrl: this.referrerUrl,
currentUrl: cleanURL('' + location),
pageUrl: cleanURL(getRuntime(this.agentIdentifier).origin),
/** Event-specific attributes take precedence over everything else */
...obj
}

/** should have been provided by reporting feature -- but falls back to now if not */
eventAttributes.timestamp ??= this.#agentRuntime.timeKeeper.convertRelativeTimestamp(now())

this.events.push(eventAttributes)

// check if we've reached the harvest limit...
if (this.events.length >= this.eventsPerHarvest) {
this.overflow = [...this.overflow, ...this.events.splice(0, Infinity)]
this.harvestScheduler.runHarvest()
}
}
}
3 changes: 3 additions & 0 deletions src/features/generic_events/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { FEATURE_NAMES } from '../../loaders/features/features'

export const FEATURE_NAME = FEATURE_NAMES.genericEvents
1 change: 1 addition & 0 deletions src/features/generic_events/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Instrument as GenericEvents } from './instrument/index'
14 changes: 14 additions & 0 deletions src/features/generic_events/instrument/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

import { InstrumentBase } from '../../utils/instrument-base'
import { FEATURE_NAME } from '../constants'

export class Instrument extends InstrumentBase {
static featureName = FEATURE_NAME
constructor (agentIdentifier, aggregator, auto = true) {
super(agentIdentifier, aggregator, FEATURE_NAME, auto)
this.importAggregator()
}
}
4 changes: 3 additions & 1 deletion src/features/utils/aggregate-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { FeatureBase } from './feature-base'
import { getInfo, isConfigured, getRuntime } from '../../common/config/config'
import { configure } from '../../loaders/configure/configure'
import { gosCDN } from '../../common/window/nreum'
import { drain } from '../../common/drain/drain'
import { deregisterDrain, drain } from '../../common/drain/drain'
import { activatedFeatures } from '../../common/util/feature-flags'

export class AggregateBase extends FeatureBase {
Expand Down Expand Up @@ -34,6 +34,8 @@ export class AggregateBase extends FeatureBase {
})
return flagsPromise.catch(err => {
this.ee.emit('internal-error', [err])
this.blocked = true
deregisterDrain(this.agentIdentifier, this.featureName)
})
}

Expand Down
2 changes: 2 additions & 0 deletions src/features/utils/lazy-feature-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export function lazyFeatureLoader (featureName, featurePart) {
return import(/* webpackChunkName: "ajax-aggregate" */ '../ajax/aggregate')
case FEATURE_NAMES.jserrors:
return import(/* webpackChunkName: "jserrors-aggregate" */ '../jserrors/aggregate')
case FEATURE_NAMES.genericEvents:
return import(/* webpackChunkName: "generic_events-aggregate" */ '../generic_events/aggregate')
case FEATURE_NAMES.logging:
return import(/* webpackChunkName: "logging-aggregate" */ '../logging/aggregate')
case FEATURE_NAMES.metrics:
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export { MicroAgent } from './loaders/micro-agent'

export { Ajax } from './features/ajax'
export { JSErrors } from './features/jserrors'
export { GenericEvents } from './features/generic_events'
export { Logging } from './features/logging'
export { Metrics } from './features/metrics'
export { PageAction } from './features/page_action'
export { PageViewEvent } from './features/page_view_event'
Expand Down
6 changes: 5 additions & 1 deletion src/loaders/browser-agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { Instrument as InstrumentSessionTrace } from '../features/session_trace/
import { Instrument as InstrumentSpa } from '../features/spa/instrument'
import { Instrument as InstrumentPageAction } from '../features/page_action/instrument'
import { Instrument as InstrumentSessionReplay } from '../features/session_replay/instrument'
import { Instrument as InstrumentGenericEvents } from '../features/generic_events/instrument'
import { Instrument as InstrumentLogs } from '../features/logging/instrument'

/**
* An agent class with all feature modules available. Features may be disabled and enabled via runtime configuration.
Expand All @@ -27,7 +29,9 @@ export class BrowserAgent extends Agent {
InstrumentPageAction,
InstrumentErrors,
InstrumentSpa,
InstrumentSessionReplay
InstrumentSessionReplay,
InstrumentGenericEvents,
InstrumentLogs
],
loaderType: 'browser-agent'
})
Expand Down
4 changes: 3 additions & 1 deletion src/loaders/features/features.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const FEATURE_NAMES = {
ajax: 'ajax',
genericEvents: 'generic_events',
jserrors: 'jserrors',
logging: 'logging',
metrics: 'metrics',
Expand Down Expand Up @@ -27,5 +28,6 @@ export const featurePriority = {
[FEATURE_NAMES.pageAction]: 8,
[FEATURE_NAMES.softNav]: 9,
[FEATURE_NAMES.sessionReplay]: 10,
[FEATURE_NAMES.logging]: 11
[FEATURE_NAMES.logging]: 11,
[FEATURE_NAMES.genericEvents]: 12
}
7 changes: 6 additions & 1 deletion tests/unit/common/config/state/init.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ test('getConfigurationValue parses path correctly', () => {
test('init props exist and return expected defaults', () => {
setConfiguration('34567', {})
const config = getConfiguration('34567')
expect(Object.keys(config).length).toEqual(19)
expect(Object.keys(config).length).toEqual(20)
expect(config.ajax).toEqual({
autoStart: true,
block_internal: true,
Expand All @@ -48,6 +48,11 @@ test('init props exist and return expected defaults', () => {
enabled: undefined,
exclude_newrelic_header: undefined
})
expect(config.generic_events).toEqual({
autoStart: true,
enabled: true,
harvestTimeSeconds: 10
})
expect(config.feature_flags).toEqual([])
expect(config.harvest).toEqual({
tooManyRequestsDelay: 60
Expand Down
6 changes: 5 additions & 1 deletion tests/unit/loaders/browser-agent.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { Instrument as InstrumentSessionTrace } from '../../../src/features/sess
import { Instrument as InstrumentSpa } from '../../../src/features/spa/instrument'
import { Instrument as InstrumentPageAction } from '../../../src/features/page_action/instrument'
import { Instrument as InstrumentSessionReplay } from '../../../src/features/session_replay/instrument'
import { Instrument as InstrumentGenericEvents } from '../../../src/features/generic_events/instrument'
import { Instrument as InstrumentLogs } from '../../../src/features/logging/instrument'
import * as agentModule from '../../../src/loaders/agent'

jest.enableAutomock()
Expand All @@ -27,7 +29,9 @@ test('should create a new agent with all features', () => {
InstrumentPageAction,
InstrumentErrors,
InstrumentSpa,
InstrumentSessionReplay
InstrumentSessionReplay,
InstrumentGenericEvents,
InstrumentLogs
]
}))
})
Expand Down
Loading

0 comments on commit 63ab04f

Please sign in to comment.