Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Expose MicroLoader in the npm package #589

Merged
merged 9 commits into from
Jun 21, 2023
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
2 changes: 1 addition & 1 deletion .github/workflows/wdio-single-browser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
- name: Install dependencies
run: npm ci
- name: Build agent
run: npm run cdn:build:local
run: npm run build:all
- name: Run WDIO Tests
run: |
node ./tools/wdio/bin/cli.js \
Expand Down
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,47 @@ import { SessionTrace } from '@newrelic/browser-agent/features/session_trace';
import { Spa } from '@newrelic/browser-agent/features/spa';
```

## Supported Browsers
## Deploying one or more "micro" agents per page

The examples above use the `Agent` class at their core, which is ideal for most cases as it will automatically detect page-level events across your web application.

Using the `MicroAgent` class, it is possible to skip the "auto" instrumentation phases of the other loader types, and provide a *very small* agent designed for capturing data in a controlled manner via the API interfaces. The `MicroAgent` captures a distinct `PageView` event when instantiated, and additional `PageAction` and `JavaScriptError` events may be captured by calling the `noticeError` and `addPageAction` methods.

Because it does not wrap the page-level globals in the same way as the base `Agent` class, the `MicroAgent` is not only smaller but can easily be instantiated multiple times on a single page with low overhead, with each instance configured to report to a different Browser App entity in New Relic One if desired. This accommodates specialized use cases, such as segmented UI designs (e.g., the micro front-end pattern) or applications requiring subsets of manually-handled data to be reported to different application entities.

The example below illustrates how to instantiate and interact with two separate `MicroAgent` instances on one page.

```javascript
import { MicroAgent } from '@newrelic/browser-agent/loaders/micro-agent'

const options_1 = {
info: { ... }, // configuration for application 1
loader_config: { ... },
init: { ... }
}

const microAgent1 = new MicroAgent(options_1)

const options_2 = {
info: { ... }, // configuration for application 2
loader_config: { ... },
init: { ... }
}

const microAgent2 = new MicroAgent(options_2)

// manually handle a JavaScript Error with microAgent1
try{
...
} catch(err) {
microAgent1.noticeError(err)
}

// manually capture a Page Action with microAgent2
microAgent2.addPageAction('myData', {hello: 'world'})
```

## Supported browsers

Our supported browser list can be accessed [here](https://docs.newrelic.com/docs/browser/new-relic-browser/getting-started/compatibility-requirements-browser-monitoring/#browser-types).

Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
"require": "./dist/cjs/loaders/browser-agent.js",
"default": "./dist/esm/loaders/browser-agent.js"
},
"./loaders/micro-agent": {
"types": "./dist/types/loaders/micro-agent.d.ts",
"require": "./dist/cjs/loaders/micro-agent.js",
"default": "./dist/esm/loaders/micro-agent.js"
},
"./loaders/worker-agent": {
"types": "./dist/types/loaders/worker-agent.d.ts",
"require": "./dist/cjs/loaders/worker-agent.js",
Expand Down
4 changes: 1 addition & 3 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
export { Agent } from './loaders/agent'
export { BrowserAgent } from './loaders/browser-agent'
export { WorkerAgent } from './loaders/worker-agent'

/** IN-PROGRESS -- DOES NOT CURRENTLY WORK CORRECTLY */
// export { MicroAgent } from './loaders/micro-agent'
export { MicroAgent } from './loaders/micro-agent'

export { Ajax } from './features/ajax'
export { JSErrors } from './features/jserrors'
Expand Down
56 changes: 26 additions & 30 deletions src/loaders/micro-agent.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// loader files
import { Instrument as PVE } from '../features/page_view_event/instrument'
import { getEnabledFeatures } from './features/enabled-features'
import { configure } from './configure/configure'
// core files
Expand All @@ -8,21 +9,18 @@ import { generateRandomHexString } from '../common/ids/unique-id'
import { getConfiguration, getInfo, getLoaderConfig, getRuntime } from '../common/config/config'
import { FEATURE_NAMES } from './features/features'
import { warn } from '../common/util/console'
import { onWindowLoad } from '../common/window/load'

const nonAutoFeatures = [
FEATURE_NAMES.jserrors,
FEATURE_NAMES.pageAction
]

const autoFeatures = [
FEATURE_NAMES.pageAction,
FEATURE_NAMES.metrics
]

/**
* A minimal agent class designed to be loaded multiple times on the same page, each instance narrowly scoped to the
* concerns of a particular component, as with the micro-frontend architectural pattern. This class does not
* automatically instrument. Instead, each MicroAgent instance may be configured to lazy-load specific
* instrumentations at runtime, and to report desired data, events, and errors programatically.
* A minimal agent class designed to only respond to manual user input. As such, this class does not
* automatically instrument. Instead, each MicroAgent instance will lazy load the required features and can support loading multiple instances on one page.
* Out of the box, it can manually handle and report Page View, Page Action, and Error events.
*/
export class MicroAgent {
/**
Expand Down Expand Up @@ -51,28 +49,26 @@ export class MicroAgent {
start () {
try {
const enabledFeatures = getEnabledFeatures(this.agentIdentifier)
autoFeatures.forEach(f => {
if (enabledFeatures[f]) {
// TODO - THIS does not work, the instrument switch in lazy loader increases the size of the worker build. Needs to be revisited
import(/* webpackChunkName: "lazy-feature-loader" */ '../features/utils/lazy-feature-loader').then(({ lazyFeatureLoader }) => {
return lazyFeatureLoader(f, 'instrument')
}).then(({ Instrument }) => {
this.features[f] = new Instrument(this.agentIdentifier, this.sharedAggregator)
}).catch(err =>
warn('Something prevented the agent from being downloaded.'))
}
})
nonAutoFeatures.forEach(f => {
if (enabledFeatures[f]) {
// TODO - THIS does not work, the instrument switch in lazy loader increases the size of the worker build. Needs to be revisited
// Parts of the lazy-loader were removed because webpack was transpiling them into the worker build, errantly inflating the build size.
import(/* webpackChunkName: "lazy-feature-loader" */ '../features/utils/lazy-feature-loader').then(({ lazyFeatureLoader }) => {
return lazyFeatureLoader(f, 'aggregate')
}).then(({ Aggregate }) => {
this.features[f] = new Aggregate(this.agentIdentifier, this.sharedAggregator)
}).catch(err =>
warn('Something prevented the agent from being downloaded.'))
}

try {
// a biproduct of doing this is that the "session manager" is automatically handled through importing this feature
this.features.page_view_event = new PVE(this.agentIdentifier, this.sharedAggregator)
} catch (err) {
warn('Something prevented the agent from instrumenting.', err)
}

onWindowLoad(() => {
// these features do not import an "instrument" file, meaning they are only hooked up to the API.
nonAutoFeatures.forEach(f => {
if (enabledFeatures[f]) {
import(/* webpackChunkName: "lazy-feature-loader" */ '../features/utils/lazy-feature-loader').then(({ lazyFeatureLoader }) => {
return lazyFeatureLoader(f, 'aggregate')
}).then(({ Aggregate }) => {
this.features[f] = new Aggregate(this.agentIdentifier, this.sharedAggregator)
}).catch(err =>
warn('Something prevented the agent from being downloaded.', err))
}
})
})
gosNREUMInitializedAgents(this.agentIdentifier, this.features, 'features')
return this
Expand Down
25 changes: 0 additions & 25 deletions tests/assets/test-builds/build-time-mfe/index.html

This file was deleted.

79 changes: 79 additions & 0 deletions tests/specs/npm/micro-agent.e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@

describe('micro-agent', () => {
it('Smoke Test - Can send distinct payloads of all relevant data types to 2 distinct app IDs', async () => {
await browser.url(await browser.testHandle.assetURL('test-builds/browser-agent-wrapper/micro-agent.html'))

await browser.execute(function () {
var opts = {
info: NREUM.info,
init: NREUM.init
}
window.agent1 = new MicroAgent({ ...opts, info: { ...opts.info, applicationID: 1 } })
window.agent2 = new MicroAgent({ ...opts, info: { ...opts.info, applicationID: 2 } })

// each payload in this test is decorated with data that matches its appId for ease of testing
window.agent1.setCustomAttribute('customAttr', '1')
window.agent2.setCustomAttribute('customAttr', '2')

// each payload in this test is decorated with data that matches its appId for ease of testing
window.agent1.noticeError('1')
window.agent2.noticeError('2')

// each payload in this test is decorated with data that matches its appId for ease of testing
window.agent1.addPageAction('1', { val: 1 })
window.agent2.addPageAction('2', { val: 2 })
})
const [rumCalls, errCalls, paCalls] = await Promise.all([
Promise.all([
browser.testHandle.expectRum(5000),
browser.testHandle.expectRum(5000)
]),
Promise.all([
browser.testHandle.expectErrors(10000),
browser.testHandle.expectErrors(10000)
]),
Promise.all([
browser.testHandle.expectIns(10000),
browser.testHandle.expectIns(10000)
])
])

// these props will get set to true once a test has matched it
// if it gets tried again, the test will fail, since these should all
// only have one distinct matching payload
const tests = {
1: { rum: false, err: false, pa: false },
2: { rum: false, err: false, pa: false }
}

// each type of test should check that:
// each payload exists once per appId
// each payload should have internal attributes matching it to the right appId
rumCalls.forEach(({ request: { query, body } }) => {
expect(ranOnce(query.a, 'rum')).toEqual(true)
expect(payloadMatchesAppId(query.a, body.ja.customAttr)).toEqual(true)
})

errCalls.forEach(({ request: { query, body } }) => {
expect(ranOnce(query.a, 'err')).toEqual(true)
expect(payloadMatchesAppId(query.a, body.err[0].params.message)).toEqual(true)
})

paCalls.forEach(({ request: { query, body } }) => {
expect(ranOnce(query.a, 'pa')).toEqual(true)
const data = body.ins[0]
expect(payloadMatchesAppId(query.a, data.val, data.actionName, data.customAttr)).toEqual(true)
})

function payloadMatchesAppId (appId, ...props) {
// each payload in this test is decorated with data that matches its appId for ease of testing
return props.every(p => Number(appId) === Number(p))
}

function ranOnce (appId, type) {
if (tests[appId][type]) return false
tests[appId][type] = true
return true
}
})
})
9 changes: 2 additions & 7 deletions tools/test-builds/browser-agent-wrapper/src/micro-agent.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
// import { MicroAgent } from '@newrelic/browser-agent/micro-agent'
import { MicroAgent } from '@newrelic/browser-agent/src/loaders/micro-agent'

// const opts = {
// info: NREUM.info,
// init: NREUM.init
// }

// new MicroAgent(opts)
window.MicroAgent = MicroAgent
2 changes: 1 addition & 1 deletion tools/test-builds/browser-agent-wrapper/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const config = [
'custom-agent-lite': './src/custom-agent-lite.js',
'custom-agent-pro': './src/custom-agent-pro.js',
'custom-agent-spa': './src/custom-agent-spa.js',
// 'micro-agent': './src/micro-agent.js',
'micro-agent': './src/micro-agent.js',
// worker init script
'worker-init': './src/worker-init.js'
},
Expand Down
9 changes: 2 additions & 7 deletions tools/test-builds/raw-src-wrapper/src/micro-agent.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
// import { MicroAgent } from '@newrelic/browser-agent/src/micro-agent'
import { MicroAgent } from '@newrelic/browser-agent/src/loaders/micro-agent'

// const opts = {
// info: NREUM.info,
// init: NREUM.init
// }

// new MicroAgent(opts)
window.MicroAgent = MicroAgent
2 changes: 1 addition & 1 deletion tools/test-builds/raw-src-wrapper/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const config = [
'custom-agent-lite': './src/custom-agent-lite.js',
'custom-agent-pro': './src/custom-agent-pro.js',
'custom-agent-spa': './src/custom-agent-spa.js',
// 'micro-agent': './src/micro-agent.js',
'micro-agent': './src/micro-agent.js',
// worker init script
'worker-init': './src/worker-init.js'
},
Expand Down