Skip to content

Commit

Permalink
[feature] add option to disable converting ISO strings to dates (#635)
Browse files Browse the repository at this point in the history
* adds disableAutoISOConversion load option

* add comment

* add changelog entry
  • Loading branch information
chrisradek authored Oct 25, 2022
1 parent de11054 commit 222d4ec
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/khaki-mayflies-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@segment/analytics-next': minor
---

Adds a new load option `disableAutoISOConversions` that turns off converting ISO strings in event fields to Dates for integrations.
136 changes: 135 additions & 1 deletion packages/browser/src/browser/__tests__/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { Context } from '@/core/context'
import { Plugin } from '@/core/plugin'
import { JSDOM } from 'jsdom'
import { Analytics } from '../../core/analytics'
import { Analytics, InitOptions } from '../../core/analytics'
import { LegacyDestination } from '../../plugins/ajs-destination'
import { PersistedPriorityQueue } from '../../lib/priority-queue/persisted'
// @ts-ignore loadLegacySettings mocked dependency is accused as unused
Expand Down Expand Up @@ -998,3 +998,137 @@ describe('.Integrations', () => {
`)
})
})

describe('Options', () => {
beforeEach(async () => {
jest.restoreAllMocks()
jest.resetAllMocks()

const html = `
<!DOCTYPE html>
<head>
<script>'hi'</script>
</head>
<body>
</body>
</html>
`.trim()

const jsd = new JSDOM(html, {
runScripts: 'dangerously',
resources: 'usable',
url: 'https://localhost',
})

const windowSpy = jest.spyOn(global, 'window', 'get')
windowSpy.mockImplementation(
() => jsd.window as unknown as Window & typeof globalThis
)
})

describe('disableAutoISOConversion', () => {
it('converts iso strings to dates be default', async () => {
const [analytics] = await AnalyticsBrowser.load({
writeKey,
})

const amplitude = new LegacyDestination(
'amplitude',
'latest',
{
apiKey: AMPLITUDE_WRITEKEY,
},
{}
)

await analytics.register(amplitude)
await amplitude.ready()

const integrationMock = jest.spyOn(amplitude.integration!, 'track')
await analytics.track('Hello!', {
date: new Date(),
iso: '2020-10-10',
})

const [integrationEvent] = integrationMock.mock.lastCall

expect(integrationEvent.properties()).toEqual({
date: expect.any(Date),
iso: expect.any(Date),
})
expect(integrationEvent.timestamp()).toBeInstanceOf(Date)
})

it('converts iso strings to dates be default', async () => {
const initOptions: InitOptions = { disableAutoISOConversion: false }
const [analytics] = await AnalyticsBrowser.load(
{
writeKey,
},
initOptions
)

const amplitude = new LegacyDestination(
'amplitude',
'latest',
{
apiKey: AMPLITUDE_WRITEKEY,
},
initOptions
)

await analytics.register(amplitude)
await amplitude.ready()

const integrationMock = jest.spyOn(amplitude.integration!, 'track')
await analytics.track('Hello!', {
date: new Date(),
iso: '2020-10-10',
})

const [integrationEvent] = integrationMock.mock.lastCall

expect(integrationEvent.properties()).toEqual({
date: expect.any(Date),
iso: expect.any(Date),
})
expect(integrationEvent.timestamp()).toBeInstanceOf(Date)
})

it('does not convert iso strings to dates when `true`', async () => {
const initOptions: InitOptions = { disableAutoISOConversion: true }
const [analytics] = await AnalyticsBrowser.load(
{
writeKey,
},
initOptions
)

const amplitude = new LegacyDestination(
'amplitude',
'latest',
{
apiKey: AMPLITUDE_WRITEKEY,
},
initOptions
)

await analytics.register(amplitude)
await amplitude.ready()

const integrationMock = jest.spyOn(amplitude.integration!, 'track')
await analytics.track('Hello!', {
date: new Date(),
iso: '2020-10-10',
})

const [integrationEvent] = integrationMock.mock.lastCall

expect(integrationEvent.properties()).toEqual({
date: expect.any(Date),
iso: '2020-10-10',
})
expect(integrationEvent.timestamp()).toBeInstanceOf(Date)
})
})
})
7 changes: 7 additions & 0 deletions packages/browser/src/core/analytics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ export interface InitOptions {
*
*/
disableClientPersistence?: boolean
/**
* Disables automatically converting ISO string event properties into Dates.
* ISO string to Date conversions occur right before sending events to a classic device mode integration,
* after any destination middleware have been ran.
* Defaults to `false`.
*/
disableAutoISOConversion?: boolean
initialPageview?: boolean
cookie?: CookieOptions
user?: UserOptions
Expand Down
4 changes: 0 additions & 4 deletions packages/browser/src/lib/klona.ts

This file was deleted.

6 changes: 5 additions & 1 deletion packages/browser/src/plugins/ajs-destination/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export class LegacyDestination implements Plugin {
private _initialized = false
private onReady: Promise<unknown> | undefined
private onInitialize: Promise<unknown> | undefined
private disableAutoISOConversion: boolean

integration: LegacyIntegration | undefined

Expand All @@ -80,6 +81,7 @@ export class LegacyDestination implements Plugin {
this.name = name
this.version = version
this.settings = { ...settings }
this.disableAutoISOConversion = options.disableAutoISOConversion || false

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

const event = new clz(afterMiddleware, {})
const event = new clz(afterMiddleware, {
traverse: !this.disableAutoISOConversion,
})

ctx.stats.increment('analytics_js.integration.invoke', 1, [
`method:${eventType}`,
Expand Down
7 changes: 5 additions & 2 deletions packages/browser/src/plugins/middleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { SegmentEvent } from '../../core/events'
import { Plugin } from '../../core/plugin'
import { asPromise } from '../../lib/as-promise'
import { SegmentFacade, toFacade } from '../../lib/to-facade'
import { klona } from '../../lib/klona'

export interface MiddlewareParams {
payload: SegmentFacade
Expand All @@ -28,7 +27,11 @@ export async function applyDestinationMiddleware(
evt: SegmentEvent,
middleware: DestinationMiddlewareFunction[]
): Promise<SegmentEvent | null> {
let modifiedEvent = klona(evt)
// Clone the event so mutations are localized to a single destination.
let modifiedEvent = toFacade(evt, {
clone: true,
traverse: false,
}).rawEvent() as SegmentEvent
async function applyMiddleware(
event: SegmentEvent,
fn: DestinationMiddlewareFunction
Expand Down

0 comments on commit 222d4ec

Please sign in to comment.