Skip to content

Commit 222d4ec

Browse files
authored
[feature] add option to disable converting ISO strings to dates (#635)
* adds disableAutoISOConversion load option * add comment * add changelog entry
1 parent de11054 commit 222d4ec

File tree

6 files changed

+157
-8
lines changed

6 files changed

+157
-8
lines changed

.changeset/khaki-mayflies-punch.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+
Adds a new load option `disableAutoISOConversions` that turns off converting ISO strings in event fields to Dates for integrations.

packages/browser/src/browser/__tests__/integration.test.ts

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { Context } from '@/core/context'
33
import { Plugin } from '@/core/plugin'
44
import { JSDOM } from 'jsdom'
5-
import { Analytics } from '../../core/analytics'
5+
import { Analytics, InitOptions } from '../../core/analytics'
66
import { LegacyDestination } from '../../plugins/ajs-destination'
77
import { PersistedPriorityQueue } from '../../lib/priority-queue/persisted'
88
// @ts-ignore loadLegacySettings mocked dependency is accused as unused
@@ -998,3 +998,137 @@ describe('.Integrations', () => {
998998
`)
999999
})
10001000
})
1001+
1002+
describe('Options', () => {
1003+
beforeEach(async () => {
1004+
jest.restoreAllMocks()
1005+
jest.resetAllMocks()
1006+
1007+
const html = `
1008+
<!DOCTYPE html>
1009+
<head>
1010+
<script>'hi'</script>
1011+
</head>
1012+
<body>
1013+
</body>
1014+
</html>
1015+
`.trim()
1016+
1017+
const jsd = new JSDOM(html, {
1018+
runScripts: 'dangerously',
1019+
resources: 'usable',
1020+
url: 'https://localhost',
1021+
})
1022+
1023+
const windowSpy = jest.spyOn(global, 'window', 'get')
1024+
windowSpy.mockImplementation(
1025+
() => jsd.window as unknown as Window & typeof globalThis
1026+
)
1027+
})
1028+
1029+
describe('disableAutoISOConversion', () => {
1030+
it('converts iso strings to dates be default', async () => {
1031+
const [analytics] = await AnalyticsBrowser.load({
1032+
writeKey,
1033+
})
1034+
1035+
const amplitude = new LegacyDestination(
1036+
'amplitude',
1037+
'latest',
1038+
{
1039+
apiKey: AMPLITUDE_WRITEKEY,
1040+
},
1041+
{}
1042+
)
1043+
1044+
await analytics.register(amplitude)
1045+
await amplitude.ready()
1046+
1047+
const integrationMock = jest.spyOn(amplitude.integration!, 'track')
1048+
await analytics.track('Hello!', {
1049+
date: new Date(),
1050+
iso: '2020-10-10',
1051+
})
1052+
1053+
const [integrationEvent] = integrationMock.mock.lastCall
1054+
1055+
expect(integrationEvent.properties()).toEqual({
1056+
date: expect.any(Date),
1057+
iso: expect.any(Date),
1058+
})
1059+
expect(integrationEvent.timestamp()).toBeInstanceOf(Date)
1060+
})
1061+
1062+
it('converts iso strings to dates be default', async () => {
1063+
const initOptions: InitOptions = { disableAutoISOConversion: false }
1064+
const [analytics] = await AnalyticsBrowser.load(
1065+
{
1066+
writeKey,
1067+
},
1068+
initOptions
1069+
)
1070+
1071+
const amplitude = new LegacyDestination(
1072+
'amplitude',
1073+
'latest',
1074+
{
1075+
apiKey: AMPLITUDE_WRITEKEY,
1076+
},
1077+
initOptions
1078+
)
1079+
1080+
await analytics.register(amplitude)
1081+
await amplitude.ready()
1082+
1083+
const integrationMock = jest.spyOn(amplitude.integration!, 'track')
1084+
await analytics.track('Hello!', {
1085+
date: new Date(),
1086+
iso: '2020-10-10',
1087+
})
1088+
1089+
const [integrationEvent] = integrationMock.mock.lastCall
1090+
1091+
expect(integrationEvent.properties()).toEqual({
1092+
date: expect.any(Date),
1093+
iso: expect.any(Date),
1094+
})
1095+
expect(integrationEvent.timestamp()).toBeInstanceOf(Date)
1096+
})
1097+
1098+
it('does not convert iso strings to dates when `true`', async () => {
1099+
const initOptions: InitOptions = { disableAutoISOConversion: true }
1100+
const [analytics] = await AnalyticsBrowser.load(
1101+
{
1102+
writeKey,
1103+
},
1104+
initOptions
1105+
)
1106+
1107+
const amplitude = new LegacyDestination(
1108+
'amplitude',
1109+
'latest',
1110+
{
1111+
apiKey: AMPLITUDE_WRITEKEY,
1112+
},
1113+
initOptions
1114+
)
1115+
1116+
await analytics.register(amplitude)
1117+
await amplitude.ready()
1118+
1119+
const integrationMock = jest.spyOn(amplitude.integration!, 'track')
1120+
await analytics.track('Hello!', {
1121+
date: new Date(),
1122+
iso: '2020-10-10',
1123+
})
1124+
1125+
const [integrationEvent] = integrationMock.mock.lastCall
1126+
1127+
expect(integrationEvent.properties()).toEqual({
1128+
date: expect.any(Date),
1129+
iso: '2020-10-10',
1130+
})
1131+
expect(integrationEvent.timestamp()).toBeInstanceOf(Date)
1132+
})
1133+
})
1134+
})

packages/browser/src/core/analytics/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ export interface InitOptions {
6767
*
6868
*/
6969
disableClientPersistence?: boolean
70+
/**
71+
* Disables automatically converting ISO string event properties into Dates.
72+
* ISO string to Date conversions occur right before sending events to a classic device mode integration,
73+
* after any destination middleware have been ran.
74+
* Defaults to `false`.
75+
*/
76+
disableAutoISOConversion?: boolean
7077
initialPageview?: boolean
7178
cookie?: CookieOptions
7279
user?: UserOptions

packages/browser/src/lib/klona.ts

Lines changed: 0 additions & 4 deletions
This file was deleted.

packages/browser/src/plugins/ajs-destination/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export class LegacyDestination implements Plugin {
6565
private _initialized = false
6666
private onReady: Promise<unknown> | undefined
6767
private onInitialize: Promise<unknown> | undefined
68+
private disableAutoISOConversion: boolean
6869

6970
integration: LegacyIntegration | undefined
7071

@@ -80,6 +81,7 @@ export class LegacyDestination implements Plugin {
8081
this.name = name
8182
this.version = version
8283
this.settings = { ...settings }
84+
this.disableAutoISOConversion = options.disableAutoISOConversion || false
8385

8486
// AJS-Renderer sets an extraneous `type` setting that clobbers
8587
// existing type defaults. We need to remove it if it's present
@@ -228,7 +230,9 @@ export class LegacyDestination implements Plugin {
228230
return ctx
229231
}
230232

231-
const event = new clz(afterMiddleware, {})
233+
const event = new clz(afterMiddleware, {
234+
traverse: !this.disableAutoISOConversion,
235+
})
232236

233237
ctx.stats.increment('analytics_js.integration.invoke', 1, [
234238
`method:${eventType}`,

packages/browser/src/plugins/middleware/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { SegmentEvent } from '../../core/events'
33
import { Plugin } from '../../core/plugin'
44
import { asPromise } from '../../lib/as-promise'
55
import { SegmentFacade, toFacade } from '../../lib/to-facade'
6-
import { klona } from '../../lib/klona'
76

87
export interface MiddlewareParams {
98
payload: SegmentFacade
@@ -28,7 +27,11 @@ export async function applyDestinationMiddleware(
2827
evt: SegmentEvent,
2928
middleware: DestinationMiddlewareFunction[]
3029
): Promise<SegmentEvent | null> {
31-
let modifiedEvent = klona(evt)
30+
// Clone the event so mutations are localized to a single destination.
31+
let modifiedEvent = toFacade(evt, {
32+
clone: true,
33+
traverse: false,
34+
}).rawEvent() as SegmentEvent
3235
async function applyMiddleware(
3336
event: SegmentEvent,
3437
fn: DestinationMiddlewareFunction

0 commit comments

Comments
 (0)