Skip to content

Commit ee855ba

Browse files
authored
Move context augmentation to Page enrichment (#939)
Part of segment.atlassian.net/browse/LIBWEB-1426
1 parent 6a028e0 commit ee855ba

File tree

11 files changed

+531
-475
lines changed

11 files changed

+531
-475
lines changed

.changeset/beige-camels-study.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@segment/analytics-next': minor
3+
---
4+
5+
Move context augmentation to Page Enrichment plugin

.changeset/calm-donkeys-eat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@segment/analytics-core': patch
3+
---
4+
5+
Update Campaign type to be more relaxed

packages/browser/src/browser/browser-umd.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getCDN, setGlobalCDNUrl } from '../lib/parse-cdn'
2-
import { setVersionType } from '../plugins/segmentio/normalize'
2+
import { setVersionType } from '../lib/version-type'
33

44
if (process.env.ASSET_PATH) {
55
if (process.env.ASSET_PATH === '/dist/umd/') {

packages/browser/src/browser/standalone.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable @typescript-eslint/no-floating-promises */
22
import { getCDN, setGlobalCDNUrl } from '../lib/parse-cdn'
3-
import { setVersionType } from '../plugins/segmentio/normalize'
3+
import { setVersionType } from '../lib/version-type'
44

55
if (process.env.ASSET_PATH) {
66
if (process.env.ASSET_PATH === '/dist/umd/') {

packages/browser/src/core/stats/remote-metrics.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { fetch } from '../../lib/fetch'
22
import { version } from '../../generated/version'
3-
import { getVersionType } from '../../plugins/segmentio/normalize'
3+
import { getVersionType } from '../../lib/version-type'
44
import { SEGMENT_API_HOST } from '../constants'
55

66
export interface MetricsOptions {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Default value will be updated to 'web' in `bundle-umd.ts` for web build.
2+
let _version: 'web' | 'npm' = 'npm'
3+
4+
export function setVersionType(version: typeof _version) {
5+
_version = version
6+
}
7+
8+
export function getVersionType(): typeof _version {
9+
return _version
10+
}

packages/browser/src/plugins/page-enrichment/__tests__/index.test.ts

Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
import cookie from 'js-cookie'
2+
import assert from 'assert'
13
import { Analytics } from '../../../core/analytics'
24
import { pageEnrichment, pageDefaults } from '..'
35
import { pick } from '../../../lib/pick'
6+
import { SegmentioSettings } from '../../segmentio'
7+
import { version } from '../../../generated/version'
8+
import { CoreExtraContext } from '@segment/analytics-core'
49

510
let ajs: Analytics
611

@@ -16,6 +21,20 @@ const helpers = {
1621
},
1722
}
1823

24+
/**
25+
* Filters out the calls made for probing cookie availability
26+
*/
27+
const ignoreProbeCookieWrites = (
28+
fn: jest.SpyInstance<
29+
string | undefined,
30+
[
31+
name: string,
32+
value: string | object,
33+
options?: cookie.CookieAttributes | undefined
34+
]
35+
>
36+
) => fn.mock.calls.filter((c) => c[0] !== 'ajs_cookies_check')
37+
1938
describe('Page Enrichment', () => {
2039
beforeEach(async () => {
2140
ajs = new Analytics({
@@ -239,3 +258,310 @@ describe('pageDefaults', () => {
239258
expect(defs.url).toEqual(window.location.href)
240259
})
241260
})
261+
262+
describe('Other visitor metadata', () => {
263+
let options: SegmentioSettings
264+
let analytics: Analytics
265+
266+
const amendSearchParams = (search?: any): CoreExtraContext => ({
267+
page: { search },
268+
})
269+
270+
beforeEach(async () => {
271+
options = { apiKey: 'foo' }
272+
analytics = new Analytics({ writeKey: options.apiKey })
273+
274+
await analytics.register(pageEnrichment)
275+
})
276+
277+
afterEach(() => {
278+
analytics.reset()
279+
Object.keys(cookie.get()).map((k) => cookie.remove(k))
280+
281+
if (window.localStorage) {
282+
window.localStorage.clear()
283+
}
284+
})
285+
286+
it('should add .library', async () => {
287+
const ctx = await analytics.track('test')
288+
assert(ctx.event.context?.library)
289+
assert(ctx.event.context?.library.name === 'analytics.js')
290+
assert(ctx.event.context?.library.version === `npm:next-${version}`)
291+
})
292+
293+
it('should allow override of .library', async () => {
294+
const customContext = {
295+
library: {
296+
name: 'analytics-wordpress',
297+
version: '1.0.3',
298+
},
299+
}
300+
301+
const ctx = await analytics.track('test', {}, { context: customContext })
302+
303+
assert(ctx.event.context?.library)
304+
assert(ctx.event.context?.library.name === 'analytics-wordpress')
305+
assert(ctx.event.context?.library.version === '1.0.3')
306+
})
307+
308+
it('should add .userAgent', async () => {
309+
const ctx = await analytics.track('test')
310+
const removeVersionNum = (agent: string) => agent.replace(/jsdom\/.*/, '')
311+
const userAgent1 = removeVersionNum(ctx.event.context?.userAgent as string)
312+
const userAgent2 = removeVersionNum(navigator.userAgent)
313+
assert(userAgent1 === userAgent2)
314+
})
315+
316+
it('should add .locale', async () => {
317+
const ctx = await analytics.track('test')
318+
assert(ctx.event.context?.locale === navigator.language)
319+
})
320+
321+
it('should not replace .locale if provided', async () => {
322+
const customContext = {
323+
locale: 'foobar',
324+
}
325+
326+
const ctx = await analytics.track('test', {}, { context: customContext })
327+
assert(ctx.event.context?.locale === 'foobar')
328+
})
329+
330+
it('should add .campaign', async () => {
331+
const ctx = await analytics.track(
332+
'test',
333+
{},
334+
{
335+
context: amendSearchParams(
336+
'utm_source=source&utm_medium=medium&utm_term=term&utm_content=content&utm_campaign=name'
337+
),
338+
}
339+
)
340+
341+
assert(ctx.event)
342+
assert(ctx.event.context)
343+
assert(ctx.event.context.campaign)
344+
assert(ctx.event.context.campaign.source === 'source')
345+
assert(ctx.event.context.campaign.medium === 'medium')
346+
assert(ctx.event.context.campaign.term === 'term')
347+
assert(ctx.event.context.campaign.content === 'content')
348+
assert(ctx.event.context.campaign.name === 'name')
349+
})
350+
351+
it('should decode query params', async () => {
352+
const ctx = await analytics.track(
353+
'test',
354+
{},
355+
{
356+
context: amendSearchParams('?utm_source=%5BFoo%5D'),
357+
}
358+
)
359+
360+
assert(ctx.event)
361+
assert(ctx.event.context)
362+
assert(ctx.event.context.campaign)
363+
assert(ctx.event.context.campaign.source === '[Foo]')
364+
})
365+
366+
it('should guard against undefined utm params', async () => {
367+
const ctx = await analytics.track(
368+
'test',
369+
{},
370+
{
371+
context: amendSearchParams('?utm_source'),
372+
}
373+
)
374+
375+
assert(ctx.event)
376+
assert(ctx.event.context)
377+
assert(ctx.event.context.campaign)
378+
assert(ctx.event.context.campaign.source === '')
379+
})
380+
381+
it('should guard against empty utm params', async () => {
382+
const ctx = await analytics.track(
383+
'test',
384+
{},
385+
{
386+
context: amendSearchParams('?utm_source='),
387+
}
388+
)
389+
390+
assert(ctx.event)
391+
assert(ctx.event.context)
392+
assert(ctx.event.context.campaign)
393+
assert(ctx.event.context.campaign.source === '')
394+
})
395+
396+
it('only parses utm params suffixed with _', async () => {
397+
const ctx = await analytics.track(
398+
'test',
399+
{},
400+
{
401+
context: amendSearchParams('?utm'),
402+
}
403+
)
404+
405+
assert(ctx.event)
406+
assert(ctx.event.context)
407+
assert.deepStrictEqual(ctx.event.context.campaign, {})
408+
})
409+
410+
it('should guard against short utm params', async () => {
411+
const ctx = await analytics.track(
412+
'test',
413+
{},
414+
{
415+
context: amendSearchParams('?utm_'),
416+
}
417+
)
418+
419+
assert(ctx.event)
420+
assert(ctx.event.context)
421+
assert.deepStrictEqual(ctx.event.context.campaign, {})
422+
})
423+
424+
it('should allow override of .campaign', async () => {
425+
const ctx = await analytics.track(
426+
'test',
427+
{},
428+
{
429+
context: {
430+
...amendSearchParams(
431+
'?utm_source=source&utm_medium=medium&utm_term=term&utm_content=content&utm_campaign=name'
432+
),
433+
campaign: {
434+
source: 'overrideSource',
435+
medium: 'overrideMedium',
436+
term: 'overrideTerm',
437+
content: 'overrideContent',
438+
name: 'overrideName',
439+
},
440+
},
441+
}
442+
)
443+
444+
assert(ctx.event)
445+
assert(ctx.event.context)
446+
assert(ctx.event.context.campaign)
447+
assert(ctx.event.context.campaign.source === 'overrideSource')
448+
assert(ctx.event.context.campaign.medium === 'overrideMedium')
449+
assert(ctx.event.context.campaign.term === 'overrideTerm')
450+
assert(ctx.event.context.campaign.content === 'overrideContent')
451+
assert(ctx.event.context.campaign.name === 'overrideName')
452+
})
453+
454+
it('should allow override of .search with object', async () => {
455+
const ctx = await analytics.track(
456+
'test',
457+
{},
458+
{
459+
context: amendSearchParams({
460+
someObject: 'foo',
461+
}),
462+
}
463+
)
464+
assert(ctx.event)
465+
assert(ctx.event.context)
466+
assert(ctx.event.context.campaign === undefined)
467+
assert(ctx.event.context.referrer === undefined)
468+
})
469+
470+
it('should add .referrer.id and .referrer.type (cookies)', async () => {
471+
const ctx = await analytics.track(
472+
'test',
473+
{},
474+
{
475+
context: amendSearchParams('?utm_source=source&urid=medium'),
476+
}
477+
)
478+
479+
assert(ctx.event)
480+
assert(ctx.event.context)
481+
assert(ctx.event.context.referrer)
482+
expect(ctx.event.context.referrer.id).toBe('medium')
483+
assert(ctx.event.context.referrer.type === 'millennial-media')
484+
expect(cookie.get('s:context.referrer')).toEqual(
485+
JSON.stringify({
486+
id: 'medium',
487+
type: 'millennial-media',
488+
})
489+
)
490+
})
491+
492+
it('should add .referrer.id and .referrer.type (cookieless)', async () => {
493+
const setCookieSpy = jest.spyOn(cookie, 'set')
494+
analytics = new Analytics(
495+
{ writeKey: options.apiKey },
496+
{ disableClientPersistence: true }
497+
)
498+
499+
await analytics.register(pageEnrichment)
500+
501+
const ctx = await analytics.track(
502+
'test',
503+
{},
504+
{
505+
context: amendSearchParams('utm_source=source&urid=medium'),
506+
}
507+
)
508+
509+
assert(ctx.event)
510+
assert(ctx.event.context)
511+
assert(ctx.event.context.referrer)
512+
expect(ctx.event.context.referrer.id).toEqual('medium')
513+
assert(ctx.event.context.referrer.type === 'millennial-media')
514+
expect(cookie.get('s:context.referrer')).toBeUndefined()
515+
expect(ignoreProbeCookieWrites(setCookieSpy).length).toBe(0)
516+
})
517+
518+
it('should add .referrer.id and .referrer.type from cookie', async () => {
519+
cookie.set('s:context.referrer', '{"id":"baz","type":"millennial-media"}')
520+
const ctx = await analytics.track('test')
521+
522+
assert(ctx.event)
523+
assert(ctx.event.context)
524+
assert(ctx.event.context.referrer)
525+
assert(ctx.event.context.referrer.id === 'baz')
526+
assert(ctx.event.context.referrer.type === 'millennial-media')
527+
})
528+
529+
it('should add .referrer.id and .referrer.type from cookie when no query is given', async () => {
530+
cookie.set(
531+
's:context.referrer',
532+
'{"id":"medium","type":"millennial-media"}'
533+
)
534+
const ctx = await analytics.track('test')
535+
536+
assert(ctx.event)
537+
assert(ctx.event.context)
538+
assert(ctx.event.context.referrer)
539+
assert(ctx.event.context.referrer.id === 'medium')
540+
assert(ctx.event.context.referrer.type === 'millennial-media')
541+
})
542+
543+
it('shouldnt add non amp ga cookie', async () => {
544+
cookie.set('_ga', 'some-nonamp-id')
545+
const ctx = await analytics.track('test')
546+
assert(ctx.event)
547+
assert(ctx.event.context)
548+
assert(!ctx.event.context.amp)
549+
})
550+
551+
it('should add .amp.id from store', async () => {
552+
cookie.set('_ga', 'amp-foo')
553+
const ctx = await analytics.track('test')
554+
assert(ctx.event)
555+
assert(ctx.event.context)
556+
assert(ctx.event.context.amp)
557+
assert(ctx.event.context.amp.id === 'amp-foo')
558+
})
559+
560+
it('should not add .amp if theres no _ga', async () => {
561+
cookie.remove('_ga')
562+
const ctx = await analytics.track('test')
563+
assert(ctx.event)
564+
assert(ctx.event.context)
565+
assert(!ctx.event.context.amp)
566+
})
567+
})

0 commit comments

Comments
 (0)