Skip to content

Commit ac9e5f3

Browse files
committed
Add anonymousId and timestamp to signalData
1 parent 83c25bb commit ac9e5f3

File tree

32 files changed

+393
-93
lines changed

32 files changed

+393
-93
lines changed

.changeset/late-bikes-notice.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@segment/analytics-signals': minor
3+
'@segment/analytics-signals-runtime': minor
4+
---
5+
6+
Add anonymousID and timestamp to signals

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ module.exports = () =>
1818
'<rootDir>/packages/consent/consent-wrapper-onetrust',
1919
'<rootDir>/scripts',
2020
'<rootDir>/packages/signals/signals',
21+
'<rootDir>/packages/test-helpers',
2122
],
2223
})

packages/signals/signals-integration-tests/src/tests/signals-vanilla/basic.test.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ test('network signals fetch', async () => {
3535
(el) => el.properties!.data.action === 'response'
3636
)
3737
expect(responses).toHaveLength(1)
38-
expect(responses[0].properties!.data.data).toEqual({ someResponse: 'yep' })
38+
const signalData = responses[0].properties!.data
39+
expect(signalData.data).toEqual({ someResponse: 'yep' })
40+
expect(signalData.anonymousId).toBe(responses[0].anonymousId)
41+
expect(signalData.timestamp).toMatch(isoDateRegEx)
3942
})
4043

4144
test('network signals xhr', async () => {
@@ -61,7 +64,8 @@ test('network signals xhr', async () => {
6164
expect(responses[0].properties!.data.page).toEqual(commonSignalData.page)
6265
})
6366

64-
test('instrumentation signals', async () => {
67+
const isoDateRegEx = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
68+
test.only('instrumentation signals', async () => {
6569
/**
6670
* Make an analytics.page() call, see if it gets sent to the signals endpoint
6771
*/
@@ -70,17 +74,20 @@ test('instrumentation signals', async () => {
7074
indexPage.waitForSignalsApiFlush(),
7175
])
7276

73-
const isoDateRegEx = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
7477
const instrumentationEvents =
7578
indexPage.signalsAPI.getEvents('instrumentation')
7679
expect(instrumentationEvents).toHaveLength(1)
7780
const ev = instrumentationEvents[0]
7881
expect(ev.event).toBe('Segment Signal Generated')
7982
expect(ev.type).toBe('track')
8083
const rawEvent = ev.properties!.data.rawEvent
84+
expect(ev.properties!.type).toBe('instrumentation')
85+
expect(ev.properties!.timestamp).toMatch(isoDateRegEx)
86+
expect(ev.properties!.anonymousId).toBe(ev.anonymousId)
87+
expect(ev.properties)
8188
expect(rawEvent).toMatchObject({
8289
type: 'page',
83-
anonymousId: expect.any(String),
90+
anonymousId: ev.anonymousId,
8491
timestamp: expect.stringMatching(isoDateRegEx),
8592
})
8693
})
@@ -123,6 +130,8 @@ test('interaction signals', async () => {
123130
type: 'track',
124131
properties: {
125132
type: 'interaction',
133+
anonymousId: interactionSignals[0].anonymousId,
134+
timestamp: expect.stringMatching(isoDateRegEx),
126135
data,
127136
},
128137
})
@@ -154,6 +163,8 @@ test('navigation signals', async ({ page }) => {
154163
const ev = indexPage.signalsAPI.lastEvent('navigation')
155164
expect(ev.properties).toMatchObject({
156165
type: 'navigation',
166+
anonymousId: ev.anonymousId,
167+
timestamp: expect.stringMatching(isoDateRegEx),
157168
data: {
158169
action: 'pageLoad',
159170
url: indexPage.url,
@@ -176,6 +187,8 @@ test('navigation signals', async ({ page }) => {
176187
const ev = indexPage.signalsAPI.lastEvent('navigation')
177188
expect(ev.properties).toMatchObject({
178189
index: expect.any(Number),
190+
anonymousId: ev.anonymousId,
191+
timestamp: expect.stringMatching(isoDateRegEx),
179192
type: 'navigation',
180193
data: {
181194
action: 'urlChange',
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { test, expect } from '@playwright/test'
2+
import { IndexPage } from './index-page'
3+
4+
const basicEdgeFn = `
5+
// this is a process signal function
6+
const processSignal = (signal) => {
7+
if (signal.type === 'interaction') {
8+
const eventName = signal.data.eventType + ' ' + '[' + signal.type + ']'
9+
analytics.track(eventName, signal.data)
10+
}
11+
}`
12+
13+
let indexPage: IndexPage
14+
15+
const isoDateRegEx = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
16+
17+
test.beforeEach(async ({ page }) => {
18+
indexPage = await new IndexPage().loadAndWait(page, basicEdgeFn)
19+
})
20+
21+
test('signal events should have anonymousId and timestamp at top level', async () => {
22+
/**
23+
* Make a fetch call, see if it gets sent to the signals endpoint
24+
*/
25+
await indexPage.network.mockTestRoute()
26+
await indexPage.network.makeFetchCall()
27+
await Promise.all([
28+
indexPage.clickButton(),
29+
indexPage.makeAnalyticsPageCall(),
30+
indexPage.waitForSignalsApiFlush(),
31+
indexPage.waitForTrackingApiFlush(),
32+
])
33+
34+
const types = [
35+
'network',
36+
'interaction',
37+
'instrumentation',
38+
'navigation',
39+
] as const
40+
41+
const evs = types.map((type) => ({
42+
type,
43+
networkCalls: indexPage.signalsAPI.getEvents(type),
44+
}))
45+
46+
evs.forEach((events) => {
47+
if (!events.networkCalls.length) {
48+
throw new Error(`No events found for type ${events.type}`)
49+
}
50+
events.networkCalls.forEach((event) => {
51+
const expected = {
52+
anonymousId: event.anonymousId,
53+
timestamp: expect.stringMatching(isoDateRegEx),
54+
type: event.properties!.type,
55+
}
56+
expect(event.properties).toMatchObject(expected)
57+
})
58+
})
59+
})

packages/signals/signals-runtime/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"build:cjs": "yarn tsc -p tsconfig.build.json --outDir ./dist/cjs --module commonjs",
2727
"build:global": "node build-signals-runtime-global.js",
2828
"assert-generated": "bash scripts/assert-generated.sh",
29-
"watch": "rm -rf dist/esm && yarn build:esm --watch",
29+
"watch": "rm -rf dist/esm && yarn build:esm && yarn build:esm --watch",
3030
"watch:test": "yarn test --watch",
3131
"tsc": "yarn run -T tsc",
3232
"eslint": "yarn run -T eslint",

packages/signals/signals-runtime/src/shared/shared-types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
export type ID = string | null | undefined
2+
13
export interface BaseSignal {
24
type: string
5+
anonymousId: ID
6+
timestamp: string
37
}
48

59
export type SignalOfType<

packages/signals/signals-runtime/src/test-helpers/mocks/mock-signal-types-web.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { BaseSignal } from '../../shared/shared-types'
12
import {
23
InteractionSignal,
34
NavigationSignal,
@@ -8,6 +9,13 @@ import {
89
} from '../../web/web-signals-types'
910
// Mock data for testing
1011

12+
type DefaultProps = Pick<BaseSignal, 'anonymousId' | 'timestamp'>
13+
14+
const baseSignalProps: DefaultProps = {
15+
anonymousId: '123',
16+
timestamp: '2020-01-01T00:00:00.000Z',
17+
}
18+
1119
export const mockPageData: PageData = {
1220
url: 'https://www.segment.com/docs/connections/sources/catalog/libraries/website/javascript/',
1321
path: '/docs/connections/sources/catalog/libraries/website/javascript/',
@@ -29,7 +37,7 @@ export const mockInteractionSignal: InteractionSignal = {
2937
attributes: { id: 'button1', class: 'btn-primary' },
3038
},
3139
},
32-
metadata: { timestamp: Date.now() },
40+
...baseSignalProps,
3341
}
3442

3543
export const mockNavigationSignal: NavigationSignal = {
@@ -41,7 +49,7 @@ export const mockNavigationSignal: NavigationSignal = {
4149
hash: '#section1',
4250
prevUrl: 'https://example.com/home',
4351
},
44-
metadata: { timestamp: Date.now() },
52+
...baseSignalProps,
4553
}
4654

4755
export const mockInstrumentationSignal: InstrumentationSignal = {
@@ -50,7 +58,7 @@ export const mockInstrumentationSignal: InstrumentationSignal = {
5058
page: mockPageData,
5159
rawEvent: { type: 'customEvent', detail: 'example' },
5260
},
53-
metadata: { timestamp: Date.now() },
61+
...baseSignalProps,
5462
}
5563

5664
export const mockNetworkSignal: NetworkSignal = {
@@ -63,7 +71,7 @@ export const mockNetworkSignal: NetworkSignal = {
6371
method: 'GET',
6472
data: { key: 'value' },
6573
},
66-
metadata: { timestamp: Date.now() },
74+
...baseSignalProps,
6775
}
6876

6977
export const mockUserDefinedSignal: UserDefinedSignal = {
@@ -72,5 +80,5 @@ export const mockUserDefinedSignal: UserDefinedSignal = {
7280
page: mockPageData,
7381
customField: 'customValue',
7482
},
75-
metadata: { timestamp: Date.now() },
83+
...baseSignalProps,
7684
}

packages/signals/signals/jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ const { createJestTSConfig } = require('@internal/config')
22

33
module.exports = createJestTSConfig(__dirname, {
44
testEnvironment: 'jsdom',
5-
setupFilesAfterEnv: ['./jest.setup.js'],
5+
setupFilesAfterEnv: ['./jest.setup.ts'],
66
})

packages/signals/signals/jest.setup.js

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/* eslint-disable no-undef */
2+
import './src/test-helpers/jest-extended'
3+
import { JestSerializers } from '@internal/test-helpers'
4+
import 'fake-indexeddb/auto'
5+
globalThis.structuredClone = (v: any) => JSON.parse(JSON.stringify(v))
6+
expect.addSnapshotSerializer(JestSerializers.jestSnapshotSerializerTimestamp)

0 commit comments

Comments
 (0)