Skip to content

Commit

Permalink
Signals analytics runtime (#1168)
Browse files Browse the repository at this point in the history
  • Loading branch information
silesky authored Oct 30, 2024
1 parent 51167c0 commit ba2f2b1
Show file tree
Hide file tree
Showing 73 changed files with 1,495 additions and 408 deletions.
4 changes: 4 additions & 0 deletions .changeset/proud-meals-cheat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
'@segment/analytics-signals-runtime': major
---
Release analytics-signals-runtime
4 changes: 4 additions & 0 deletions .changeset/proud-meals-ox.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
'@segment/analytics-signals': minor
---
Refactor runtime to use `@segment/analytics-signals-runtime`
8 changes: 7 additions & 1 deletion meta-tests/check-dts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ import fs from 'fs'
*/
const execa = promisify(exec)

const allPublicPackageDirNames = ['browser', 'core', 'node'] as const
const allPublicPackageDirNames = [
'browser',
'core',
'node',
'signals/signals',
'signals/signals-runtime',
] as const

type PackageDirName = typeof allPublicPackageDirNames[number]

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"devDependencies": {
"@changesets/changelog-github": "^0.4.5",
"@changesets/cli": "^2.23.2",
"@microsoft/api-extractor": "^7.47.9",
"@npmcli/promise-spawn": "^7.0.0",
"@types/express": "4",
"@types/jest": "^29.5.11",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class BasePage {
*/
async loadAndWait(...args: Parameters<BasePage['load']>) {
await Promise.all([this.load(...args), this.waitForSettings()])
return this
}

/**
Expand All @@ -48,8 +49,12 @@ export class BasePage {
this.edgeFn = edgeFn
await this.setupMockedRoutes()
const url = options.updateURL ? options.updateURL(this.url) : this.url
await this.page.goto(url)
void this.invokeAnalyticsLoad(signalSettings)
await this.page.goto(url, { waitUntil: 'domcontentloaded' })
void this.invokeAnalyticsLoad({
flushInterval: 500,
...signalSettings,
})
return this
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Page, Route, Request } from '@playwright/test'
import { SegmentEvent } from '@segment/analytics-next'
import { Signal } from '@segment/analytics-signals'
import { waitForCondition } from './playwright-utils'

type FulfillOptions = Parameters<Route['fulfill']>['0']
export interface XHRRequestOptions {
Expand Down Expand Up @@ -160,6 +161,31 @@ export class TrackingAPIRequestBuffer {
}

export class SignalAPIRequestBuffer extends TrackingAPIRequestBuffer {
async waitForEvents(
numberOfSignals: number,
signalType?: Signal['type']
): Promise<SegmentEvent[]> {
await waitForCondition(
() => this.getEvents(signalType).length >= numberOfSignals,
{
timeout: 5000,
errorMessage: `Found ${
this.getEvents(signalType).length
} signals, expected ${numberOfSignals}`,
}
)
const events = this.getEvents(signalType)
if (events.length < numberOfSignals) {
throw new Error(
`Expected ${numberOfSignals} signals of type ${signalType}, but got ${
this.getEvents(signalType).length
}`
)
} else {
return events
}
}

override getEvents(signalType?: Signal['type']): SegmentEvent[] {
if (signalType) {
return this.getEvents().filter((e) => e.properties!.type === signalType)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { test, expect } from '@playwright/test'
import { IndexPage } from './index-page'

const indexPage = new IndexPage()

const basicEdgeFn = `
// this is a process signal function
const processSignal = (signal) => {
Expand All @@ -12,8 +10,10 @@ const basicEdgeFn = `
}
}`

let indexPage: IndexPage

test.beforeEach(async ({ page }) => {
await indexPage.loadAndWait(page, basicEdgeFn)
indexPage = await new IndexPage().loadAndWait(page, basicEdgeFn)
})

test('network signals fetch', async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { test, expect } from '@playwright/test'
import { IndexPage } from './index-page'

const indexPage = new IndexPage()

const basicEdgeFn = `const processSignal = (signal) => {}`

let indexPage: IndexPage
test.beforeEach(async ({ page }) => {
await indexPage.loadAndWait(page, basicEdgeFn)
indexPage = await new IndexPage().loadAndWait(page, basicEdgeFn)
})

const data = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { test, expect } from '@playwright/test'
import { waitForCondition } from '../../helpers/playwright-utils'
import { IndexPage } from './index-page'

const indexPage = new IndexPage()

const basicEdgeFn = `const processSignal = (signal) => {}`

test('Collecting signals whenever a user enters text input', async ({
Expand All @@ -12,7 +10,7 @@ test('Collecting signals whenever a user enters text input', async ({
/**
* Input some text into the input field, see if the signal is emitted correctly
*/
await indexPage.loadAndWait(page, basicEdgeFn, {
const indexPage = await new IndexPage().loadAndWait(page, basicEdgeFn, {
disableSignalsRedaction: true,
enableSignalsIngestion: true,
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { test, expect } from '@playwright/test'
import { IndexPage } from './index-page'

const indexPage = new IndexPage()

const basicEdgeFn = `const processSignal = (signal) => {}`

test('network signals allow and disallow list', async ({ page }) => {
await indexPage.loadAndWait(page, basicEdgeFn, {
const indexPage = await new IndexPage().loadAndWait(page, basicEdgeFn, {
networkSignalsAllowList: ['allowed-api.com'],
networkSignalsDisallowList: ['https://disallowed-api.com/api/foo'],
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ test.describe('network signals - fetch', () => {
let indexPage: IndexPage

test.beforeEach(async ({ page }) => {
indexPage = new IndexPage()
await indexPage.loadAndWait(page, basicEdgeFn)
indexPage = await new IndexPage().loadAndWait(page, basicEdgeFn)
})

test('should not emit anything if neither request nor response are json', async () => {
Expand Down Expand Up @@ -55,20 +54,23 @@ test.describe('network signals - fetch', () => {
(el) => el.properties!.data.action === 'request'
)
expect(requests).toHaveLength(1)
expect(requests[0].properties!.data).toMatchObject({
expect(requests[0].properties!.data).toEqual({
action: 'request',
url: 'http://localhost/test',
method: 'POST',
data: { key: 'value' },
})

const responses = networkEvents.filter(
(el) => el.properties!.data.action === 'response'
)
expect(responses).toHaveLength(1)
expect(responses[0].properties!.data).toMatchObject({
expect(responses[0].properties!.data).toEqual({
action: 'response',
url: 'http://localhost/test',
data: { foo: 'test' },
status: 200,
ok: true,
})
})

Expand Down Expand Up @@ -177,6 +179,8 @@ test.describe('network signals - fetch', () => {
action: 'response',
url: 'http://localhost/test',
data: { errorMsg: 'foo' },
status: 400,
ok: false,
})
expect(responses).toHaveLength(1)
})
Expand Down Expand Up @@ -214,6 +218,8 @@ test.describe('network signals - fetch', () => {
action: 'response',
url: 'http://localhost/test',
data: 'foo',
status: 400,
ok: false,
})
expect(responses).toHaveLength(1)
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { test, expect } from '@playwright/test'
import { IndexPage } from './index-page'
import { sleep } from '@segment/analytics-core'

const basicEdgeFn = `const processSignal = (signal) => {}`

test.describe('network signals - XHR', () => {
let indexPage: IndexPage

test.beforeEach(async ({ page }) => {
indexPage = new IndexPage()
await indexPage.loadAndWait(page, basicEdgeFn)
indexPage = await new IndexPage().loadAndWait(page, basicEdgeFn)
})
test('should not emit anything if neither request nor response are json', async () => {
await indexPage.network.mockTestRoute('http://localhost/test', {
Expand All @@ -24,7 +24,7 @@ test.describe('network signals - XHR', () => {
})

// Wait for the signals to be flushed
await indexPage.waitForSignalsApiFlush()
await sleep(300)

const networkEvents = indexPage.signalsAPI.getEvents('network')

Expand All @@ -46,10 +46,7 @@ test.describe('network signals - XHR', () => {

expect(data).toEqual({ foo: 'test' })

// Wait for the signals to be flushed
await indexPage.waitForSignalsApiFlush()

const networkEvents = indexPage.signalsAPI.getEvents('network')
const networkEvents = await indexPage.signalsAPI.waitForEvents(2, 'network')

// Check the request
const requests = networkEvents.filter(
Expand Down Expand Up @@ -88,9 +85,7 @@ test.describe('network signals - XHR', () => {
})

// Wait for the signals to be flushed
await indexPage.waitForSignalsApiFlush()

const networkEvents = indexPage.signalsAPI.getEvents('network')
const networkEvents = await indexPage.signalsAPI.waitForEvents(2, 'network')

// Check the request
const requests = networkEvents.filter(
Expand Down Expand Up @@ -130,9 +125,8 @@ test.describe('network signals - XHR', () => {
})

// Wait for the signals to be flushed
await indexPage.waitForSignalsApiFlush()

const networkEvents = indexPage.signalsAPI.getEvents('network')
const networkEvents = await indexPage.signalsAPI.waitForEvents(1, 'network')

// Check the response (only response should be captured)
const responses = networkEvents.filter(
Expand Down Expand Up @@ -163,7 +157,7 @@ test.describe('network signals - XHR', () => {
})

// Wait for the signals to be flushed
await indexPage.waitForSignalsApiFlush()
await indexPage.signalsAPI.waitForEvents(1, 'network')

// Retrieve the batch of events from the signals request
const networkEvents = indexPage.signalsAPI.getEvents('network')
Expand Down Expand Up @@ -211,10 +205,8 @@ test.describe('network signals - XHR', () => {
])

// Wait for the signals to be flushed
await indexPage.waitForSignalsApiFlush()
const networkEvents = await indexPage.signalsAPI.waitForEvents(2, 'network')

// Retrieve the batch of events from the signals request
const networkEvents = indexPage.signalsAPI.getEvents('network')
// Check the request
const requests = networkEvents.filter(
(el) => el.properties!.data.action === 'request'
Expand Down Expand Up @@ -252,7 +244,7 @@ test.describe('network signals - XHR', () => {
contentType: 'application/json',
})

await indexPage.waitForSignalsApiFlush()
await indexPage.signalsAPI.waitForEvents(2, 'network')

const networkEvents = indexPage.signalsAPI.getEvents('network')

Expand Down Expand Up @@ -290,7 +282,7 @@ test.describe('network signals - XHR', () => {
contentType: 'application/json',
})

await indexPage.waitForSignalsApiFlush()
await indexPage.signalsAPI.waitForEvents(2, 'network')

const networkEvents = indexPage.signalsAPI.getEvents('network')

Expand Down Expand Up @@ -331,8 +323,7 @@ test.describe('network signals - XHR', () => {
responseType: 'json',
contentType: 'application/json',
})

await indexPage.waitForSignalsApiFlush()
await indexPage.signalsAPI.waitForEvents(2, 'network')

const networkEvents = indexPage.signalsAPI.getEvents('network')

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { test, expect } from '@playwright/test'
import { IndexPage } from './index-page'

const basicEdgeFn = `
const processSignal = (signal) => {
// test that constants are properly injected
if (typeof EventType !== 'object') {
throw new Error('EventType is missing?')
}
if (typeof SignalType !== 'object') {
throw new Error('SignalType is missing?')
}
if (typeof NavigationAction !== 'object') {
throw new Error('NavigationAction is missing?')
}
if (signal.type === SignalType.Interaction) {
const eventName = signal.data.eventType + ' ' + '[' + signal.type + ']'
analytics.track(eventName, signal.data)
}
}`

let indexPage: IndexPage

test.beforeEach(async ({ page }) => {
indexPage = await new IndexPage().loadAndWait(page, basicEdgeFn)
})

test('constants should be accessible in the runtime', async () => {
/**
* Make a button click, see ifdom.window.NavigationAction.URLChange it:
* - creates an interaction signal that sends to the signals endpoint
* - creates an analytics event that sends to the tracking endpoint
*/
await Promise.all([
indexPage.clickButton(),
indexPage.waitForTrackingApiFlush(),
])

const analyticsReqJSON = indexPage.trackingAPI.lastEvent()
expect(analyticsReqJSON).toMatchObject({
event: 'click [interaction]',
})
})
Loading

0 comments on commit ba2f2b1

Please sign in to comment.