Skip to content

Commit bf86857

Browse files
authored
Add signals middleware (#1220)
1 parent c9f81ac commit bf86857

File tree

24 files changed

+459
-384
lines changed

24 files changed

+459
-384
lines changed

.changeset/warm-lies-rush.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@segment/analytics-signals': minor
3+
---
4+
5+
Allow registration of middleware to allow for dropping and modification of signals
6+
7+
```ts
8+
class MyMiddleware implements SignalsMiddleware {
9+
process(signal: Signal) {
10+
if (
11+
signal.type === 'network' &&
12+
signal.data.action === 'request' &&
13+
...
14+
) {
15+
// drop or modify signal
16+
return null
17+
} else {
18+
return signal
19+
}
20+
}
21+
}
22+
const signalsPlugin = new SignalsPlugin({
23+
middleware: [new MyMiddleware()]
24+
})
25+
```

packages/signals/signals-integration-tests/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
".": "yarn run -T turbo run --filter=@internal/signals-integration-tests...",
1010
"build": "webpack",
1111
"test": "playwright test",
12-
"test:vanilla": "playwright test src/tests/vanilla",
12+
"test:vanilla": "playwright test src/tests/signals-vanilla",
1313
"test:perf": "playwright test src/tests/performance",
1414
"test:custom": "playwright test src/tests/custom",
1515
"watch": "webpack -w",

packages/signals/signals-integration-tests/src/helpers/base-page-object.ts

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ export class BasePage {
1616
public edgeFnDownloadURL = 'https://cdn.edgefn.segment.com/MY-WRITEKEY/foo.js'
1717
public edgeFn!: string
1818
public network!: PageNetworkUtils
19+
public defaultSignalsPluginTestSettings: Partial<SignalsPluginSettingsConfig> =
20+
{
21+
disableSignalsRedaction: true,
22+
enableSignalsIngestion: true,
23+
flushInterval: 500,
24+
}
1925

2026
constructor(path: string) {
2127
this.url = 'http://localhost:5432/src/tests' + path
@@ -41,7 +47,12 @@ export class BasePage {
4147
page: Page,
4248
edgeFn: string,
4349
signalSettings: Partial<SignalsPluginSettingsConfig> = {},
44-
options: { updateURL?: (url: string) => string; sampleRate?: number } = {}
50+
options: {
51+
updateURL?: (url: string) => string
52+
sampleRate?: number
53+
middleware?: string
54+
skipSignalsPluginInit?: boolean
55+
} = {}
4556
) {
4657
logConsole(page)
4758
this.page = page
@@ -50,10 +61,12 @@ export class BasePage {
5061
await this.setupMockedRoutes(options.sampleRate)
5162
const url = options.updateURL ? options.updateURL(this.url) : this.url
5263
await this.page.goto(url, { waitUntil: 'domcontentloaded' })
53-
void this.invokeAnalyticsLoad({
54-
flushInterval: 500,
55-
...signalSettings,
56-
})
64+
if (!options.skipSignalsPluginInit) {
65+
void this.invokeAnalyticsLoad({
66+
flushInterval: 500,
67+
...signalSettings,
68+
})
69+
}
5770
return this
5871
}
5972

@@ -76,18 +89,19 @@ export class BasePage {
7689
signalSettings: Partial<SignalsPluginSettingsConfig> = {}
7790
) {
7891
await this.page.evaluate(
79-
({ signalSettings }) => {
80-
window.signalsPlugin = new window.SignalsPlugin({
81-
disableSignalsRedaction: true,
82-
enableSignalsIngestion: true,
83-
...signalSettings,
84-
})
92+
({ settings }) => {
93+
window.signalsPlugin = new window.SignalsPlugin(settings)
8594
window.analytics.load({
8695
writeKey: '<SOME_WRITE_KEY>',
8796
plugins: [window.signalsPlugin],
8897
})
8998
},
90-
{ signalSettings }
99+
{
100+
settings: {
101+
...this.defaultSignalsPluginTestSettings,
102+
...signalSettings,
103+
},
104+
}
91105
)
92106
return this
93107
}

packages/signals/signals-integration-tests/src/helpers/log-console.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import { Page } from '@playwright/test'
22

33
export const logConsole = (page: Page) => {
44
page.on('console', (msg) => {
5-
console.log(`console.${msg.type()}:`, msg.text())
5+
const text = msg.text()
6+
// keep stdout clean, e.g. by not printing intentional errors
7+
const ignoreList = ['Bad Request']
8+
if (ignoreList.some((str) => text.includes(str))) {
9+
return
10+
}
11+
console.log(`console.${msg.type()}:`, text)
612
})
713
page.on('pageerror', (error) => {
814
console.error('Page error:', error)

packages/signals/signals-integration-tests/src/tests/custom-elements/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
<head>
55
<!-- Dummy favicon -->
6-
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
6+
<link rel="shortcut icon" href="#">>
77
</head>
88

99
<body>

packages/signals/signals-integration-tests/src/tests/performance/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<head>
55
<script src="../../../dist/signals-vanilla.bundle.js"></script>
66
<!-- Dummy favicon -->
7-
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
7+
<link rel="shortcut icon" href="#">
88
</head>
99

1010
<body>

packages/signals/signals-integration-tests/src/tests/signals-vanilla/index.bundle.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { AnalyticsBrowser } from '@segment/analytics-next'
22
import { SignalsPlugin } from '@segment/analytics-signals'
33

44
/**
5-
* Not instantiating the analytics object here, as it will be instantiated in the test
5+
* Not calling analytics.load() or instantiating Signals Plugin here, as all this configuration happens in the page object.
66
*/
77
window.SignalsPlugin = SignalsPlugin
88
window.analytics = new AnalyticsBrowser()

packages/signals/signals-integration-tests/src/tests/signals-vanilla/index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
<head>
55
<script src="../../../dist/signals-vanilla.bundle.js"></script>
66
<!-- Dummy favicon -->
7-
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
7+
<link rel="shortcut icon" href="#">
88
</head>
99

1010
<body>
1111
<button id="some-button">Click me</button>
1212
<button id="complex-button">
13-
<img id="some-image" src="https://via.placeholder.com/150" alt="Placeholder Image">
13+
<img id="some-image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wIAAgEBAyX8UgAAAABJRU5ErkJggg==" alt="Placeholder Image">
1414
<div>
1515
Other Example Button with <h1>Nested Text</h1>
1616
</div>
@@ -26,4 +26,4 @@
2626
<input type="submit" value="Submit">
2727
</form>
2828
</body>
29-
</html>
29+
</html>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { test, expect } from '@playwright/test'
2+
import { IndexPage } from './index-page'
3+
4+
const basicEdgeFn = `const processSignal = (signal) => {}`
5+
6+
let indexPage: IndexPage
7+
8+
test('middleware', async ({ page }) => {
9+
indexPage = await new IndexPage().load(
10+
page,
11+
basicEdgeFn,
12+
{},
13+
{
14+
skipSignalsPluginInit: true,
15+
}
16+
)
17+
18+
await page.evaluate(
19+
({ settings }) => {
20+
window.signalsPlugin = new window.SignalsPlugin({
21+
middleware: [
22+
{
23+
load() {
24+
return undefined
25+
},
26+
process: function (signal) {
27+
// @ts-ignore
28+
signal.data['middleware'] = 'test'
29+
return signal
30+
},
31+
},
32+
],
33+
...settings,
34+
})
35+
window.analytics.load({
36+
writeKey: '<SOME_WRITE_KEY>',
37+
plugins: [window.signalsPlugin],
38+
})
39+
},
40+
{
41+
settings: {
42+
...indexPage.defaultSignalsPluginTestSettings,
43+
flushAt: 1,
44+
},
45+
}
46+
)
47+
48+
/**
49+
* Make an analytics.page() call, see that the middleware can modify the event
50+
*/
51+
await Promise.all([
52+
indexPage.makeAnalyticsPageCall(),
53+
indexPage.waitForSignalsApiFlush(),
54+
])
55+
56+
const instrumentationEvents =
57+
indexPage.signalsAPI.getEvents('instrumentation')
58+
expect(instrumentationEvents).toHaveLength(1)
59+
const ev = instrumentationEvents[0]
60+
expect(ev.properties!.data['middleware']).toEqual('test')
61+
})

packages/signals/signals/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,30 @@ signalsPlugin.onSignal((signal) => console.log(signal))
9595
```
9696

9797

98+
### Middleware / Plugins
99+
#### Drop or modify signals
100+
```ts
101+
import { SignalsPlugin, SignalsMiddleware } from '@segment/analytics-signals'
102+
103+
class MyMiddleware implements SignalsMiddleware {
104+
process(signal: Signal) {
105+
// drop the event if some conditions are met
106+
if (
107+
signal.type === 'network' &&
108+
signal.data.action === 'request' &&
109+
...
110+
) {
111+
return null;
112+
} else {
113+
return signal;
114+
}
115+
}
116+
}
117+
const signalsPlugin = new SignalsPlugin({ middleware: [myMiddleware]})
118+
analytics.register(signalsPlugin)
119+
```
120+
121+
98122
### Playground / Development / Testing
99123
See the [signals example repo](../signals-example).
100124

0 commit comments

Comments
 (0)