Skip to content

Commit

Permalink
Fullstory V2 API Actions (#1431)
Browse files Browse the repository at this point in the history
* Add identifyUserV2 Action (#14)

* Create track event v2 action (#15)

* Update delete to use V2 (#16)
  • Loading branch information
mattgrosvenor-fs authored and marinhero committed Aug 9, 2023
1 parent 23b0350 commit 6cb1d80
Show file tree
Hide file tree
Showing 13 changed files with 580 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export const email = 'fake+email@example.com'
export const displayName = 'fake-display-name'
export const baseUrl = 'https://api.fullstory.com'
export const settings = { apiKey }
export const integrationSourceQueryParam = `integration=segment`
export const integrationSource = 'segment'
export const integrationSourceQueryParam = `integration=${integrationSource}`

const testDestination = createTestIntegration(Definition)

Expand Down Expand Up @@ -155,7 +156,7 @@ describe('FullStory', () => {
describe('onDelete', () => {
const falsyUserIds = ['', undefined, null]
it('makes expected request given a valid user id', async () => {
nock(baseUrl).delete(`/users/v1/individual/${urlEncodedUserId}`).reply(200)
nock(baseUrl).delete(`/v2beta/users?uid=${urlEncodedUserId}`).reply(200)
await expect(testDestination.onDelete!({ type: 'delete', userId }, settings)).resolves.not.toThrowError()
})

Expand All @@ -167,4 +168,134 @@ describe('FullStory', () => {
})
})
})

describe('identifyUserV2', () => {
it('makes expected request with default mappings', async () => {
nock(baseUrl).post(`/v2beta/users?${integrationSourceQueryParam}`).reply(200)
const event = createTestEvent({
type: 'identify',
userId,
anonymousId,
traits: {
email,
name: displayName,
'originally-hyphenated': 'some string',
'originally spaced': true,
'originally.dotted': 1.23,
typeSuffixed_bool: true
}
})

const [response] = await testDestination.testAction('identifyUserV2', {
settings,
event,
useDefaultMappings: true
})

expect(response.status).toBe(200)
expect(JSON.parse(response.options.body as string)).toEqual({
uid: userId,
email,
display_name: displayName,
properties: {
email,
name: displayName,
segmentAnonymousId: anonymousId,
originallyhyphenated: 'some string',
originallyspaced: true,
originallydotted: 1.23,
typeSuffixed_bool: true
}
})
})
})

describe('trackEventV2', () => {
it('makes expected request with default mappings', async () => {
nock(baseUrl).post(`/v2beta/events?${integrationSourceQueryParam}`).reply(200)
const eventName = 'test-event'

const sessionId = '12345:678'

const properties = {
'first-property': 'first-value',
second_property: 'second_value',
thirdProperty: 'thirdValue',
useRecentSession: true,
sessionUrl: `session/url/${encodeURIComponent(sessionId)}`
}

const timestamp = new Date(Date.UTC(2022, 1, 2, 3, 4, 5)).toISOString()

const event = createTestEvent({
type: 'track',
userId,
event: eventName,
timestamp,
properties
})

const [response] = await testDestination.testAction('trackEventV2', {
settings,
event,
// Default mappings defined under fields in ../trackEventV2/index.ts
useDefaultMappings: true,
mapping: {
useRecentSession: {
'@path': '$.properties.useRecentSession'
},
sessionUrl: {
'@path': '$.properties.sessionUrl'
}
}
})

expect(response.status).toBe(200)
expect(JSON.parse(response.options.body as string)).toEqual({
name: eventName,
properties: {
firstproperty: 'first-value',
second_property: 'second_value',
thirdProperty: 'thirdValue',
useRecentSession: true,
sessionUrl: `session/url/${encodeURIComponent(sessionId)}`
},
user: {
uid: userId
},
timestamp,
session: {
id: sessionId,
use_most_recent: properties.useRecentSession
}
})
})

it('handles undefined event values', async () => {
nock(baseUrl).post(`/v2beta/events?${integrationSourceQueryParam}`).reply(200)
const eventName = 'test-event'

const event = createTestEvent({
type: 'track',
userId,
event: eventName,
timestamp: undefined
})

const [response] = await testDestination.testAction('trackEventV2', {
settings,
event,
useDefaultMappings: true
})

expect(response.status).toBe(200)
expect(JSON.parse(response.options.body as string)).toEqual({
name: eventName,
properties: {},
user: {
uid: userId
}
})
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import {
listOperationsRequestParams,
customEventRequestParams,
setUserPropertiesRequestParams,
deleteUserRequestParams
deleteUserRequestParams,
createUserRequestParams,
createEventRequestParams
} from '../request-params'
import {
anonymousId,
Expand All @@ -12,6 +14,7 @@ import {
urlEncodedUserId,
baseUrl,
settings,
integrationSource,
integrationSourceQueryParam
} from './fullstory.test'

Expand All @@ -22,6 +25,7 @@ describe('requestParams', () => {
expect(options.method).toBe('get')
expect(options.headers!['Content-Type']).toBe('application/json')
expect(options.headers!['Authorization']).toBe(`Basic ${settings.apiKey}`)
expect(options.headers!['Integration-Source']).toBe(integrationSource)
expect(url).toBe(`${baseUrl}/operations/v1?limit=1`)
})
})
Expand All @@ -44,6 +48,7 @@ describe('requestParams', () => {
expect(options.method).toBe('post')
expect(options.headers!['Content-Type']).toBe('application/json')
expect(options.headers!['Authorization']).toBe(`Basic ${settings.apiKey}`)
expect(options.headers!['Integration-Source']).toBe(integrationSource)
expect(url).toBe(`${baseUrl}/users/v1/individual/${urlEncodedUserId}/customevent?${integrationSourceQueryParam}`)
expect(options.json).toEqual({
event: {
Expand All @@ -66,6 +71,7 @@ describe('requestParams', () => {
expect(options.method).toBe('post')
expect(options.headers!['Content-Type']).toBe('application/json')
expect(options.headers!['Authorization']).toBe(`Basic ${settings.apiKey}`)
expect(options.headers!['Integration-Source']).toBe(integrationSource)
expect(url).toBe(`${baseUrl}/users/v1/individual/${urlEncodedUserId}/customevent?${integrationSourceQueryParam}`)
expect(options.json).toEqual({
event: {
Expand All @@ -86,6 +92,7 @@ describe('requestParams', () => {
expect(options.method).toBe('post')
expect(options.headers!['Content-Type']).toBe('application/json')
expect(options.headers!['Authorization']).toBe(`Basic ${settings.apiKey}`)
expect(options.headers!['Integration-Source']).toBe(integrationSource)
expect(url).toBe(`${baseUrl}/users/v1/individual/${urlEncodedUserId}/customevent?${integrationSourceQueryParam}`)
expect(options.json).toEqual({
event: {
Expand All @@ -109,6 +116,7 @@ describe('requestParams', () => {
expect(options.method).toBe('post')
expect(options.headers!['Content-Type']).toBe('application/json')
expect(options.headers!['Authorization']).toBe(`Basic ${settings.apiKey}`)
expect(options.headers!['Integration-Source']).toBe(integrationSource)
expect(url).toBe(`${baseUrl}/users/v1/individual/${urlEncodedUserId}/customvars?${integrationSourceQueryParam}`)
expect(options.json).toEqual(requestBody)
})
Expand All @@ -120,7 +128,107 @@ describe('requestParams', () => {
expect(options.method).toBe('delete')
expect(options.headers!['Content-Type']).toBe('application/json')
expect(options.headers!['Authorization']).toBe(`Basic ${settings.apiKey}`)
expect(url).toBe(`${baseUrl}/users/v1/individual/${urlEncodedUserId}`)
expect(options.headers!['Integration-Source']).toBe(integrationSource)
expect(url).toBe(`${baseUrl}/v2beta/users?uid=${urlEncodedUserId}`)
})
})

describe('createUserV2', () => {
it('returns expected request params', () => {
const requestBody = {
userId,
anonymousId,
traits: {
displayName,
email
}
}
const { url, options } = createUserRequestParams(settings, requestBody)
expect(options.method).toBe('post')
expect(options.headers!['Content-Type']).toBe('application/json')
expect(options.headers!['Authorization']).toBe(`Basic ${settings.apiKey}`)
expect(options.headers!['Integration-Source']).toBe(integrationSource)
expect(url).toBe(`${baseUrl}/v2beta/users?${integrationSourceQueryParam}`)
expect(options.json).toEqual(requestBody)
})
})

describe('customEventV2', () => {
it('returns expected request params', () => {
const sessionId = 'ec5218650ee0:a58ec087'
const requestValues = {
userId,
eventName: 'test-event',
properties: {
'first-property': 'first-value',
second_property: 'second_value',
thirdProperty: 'thirdValue'
},
timestamp: new Date(Date.UTC(2022, 1, 2, 3, 4, 5)).toISOString(),
useRecentSession: true,
sessionUrl: `session/url/${encodeURIComponent(sessionId)}`
}
const { url, options } = createEventRequestParams(settings, requestValues)
expect(options.method).toBe('post')
expect(options.headers!['Content-Type']).toBe('application/json')
expect(options.headers!['Authorization']).toBe(`Basic ${settings.apiKey}`)
expect(options.headers!['Integration-Source']).toBe(integrationSource)
expect(url).toBe(`${baseUrl}/v2beta/events?${integrationSourceQueryParam}`)
expect(options.json).toEqual({
name: requestValues.eventName,
properties: requestValues.properties,
user: {
uid: userId
},
timestamp: requestValues.timestamp,
session: {
id: sessionId,
use_most_recent: requestValues.useRecentSession
}
})
})

it('handles undefined request values', () => {
const requestValues = {
userId,
eventName: 'test-event',
properties: {}
}
const { url, options } = createEventRequestParams(settings, requestValues)
expect(options.method).toBe('post')
expect(options.headers!['Content-Type']).toBe('application/json')
expect(options.headers!['Authorization']).toBe(`Basic ${settings.apiKey}`)
expect(options.headers!['Integration-Source']).toBe(integrationSource)
expect(url).toBe(`${baseUrl}/v2beta/events?${integrationSourceQueryParam}`)
expect(options.json).toEqual({
name: requestValues.eventName,
properties: requestValues.properties,
user: {
uid: userId
}
})
})

it('omits use_most_recent request param if false', () => {
const requestValues = {
userId,
eventName: 'test-event',
properties: {},
useRecentSession: false
}
const { url, options } = createEventRequestParams(settings, requestValues)
expect(options.method).toBe('post')
expect(options.headers!['Content-Type']).toBe('application/json')
expect(options.headers!['Authorization']).toBe(`Basic ${settings.apiKey}`)
expect(options.headers!['Integration-Source']).toBe(integrationSource)
expect(url).toBe(`${baseUrl}/v2beta/events?${integrationSourceQueryParam}`)
expect(options.json).toEqual({
name: requestValues.eventName,
properties: requestValues.properties,
user: {
uid: userId
}
})
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('normalizePropertyNames', () => {
const obj = {
someProp: undefined
}
const normalizedObj = normalizePropertyNames(obj)
const normalizedObj = normalizePropertyNames(obj, { typeSuffix: true })
expect(normalizedObj).toEqual(obj)
})

Expand All @@ -35,10 +35,20 @@ describe('normalizePropertyNames', () => {
Object.entries(suffixToExampleValuesMap).forEach(([suffix, value]) => {
obj[`${suffix}_prop_${suffix}`] = value
})
const normalizedObj = normalizePropertyNames(obj)
const normalizedObj = normalizePropertyNames(obj, { typeSuffix: true })
expect(normalizedObj).toEqual(obj)
})

it('does not add type suffix if parameter is undefined/false', () => {
const originalPayload = {
str_prop: suffixToExampleValuesMap.str
}
const normalizedObj = normalizePropertyNames(originalPayload)
expect(normalizedObj).toEqual(originalPayload)
const normalizedObj2 = normalizePropertyNames(originalPayload, { typeSuffix: false })
expect(normalizedObj2).toEqual(originalPayload)
})

it('adds type suffixes when type can be inferred and known type suffix is absent', () => {
const originalPayload = {
str_prop: suffixToExampleValuesMap.str,
Expand Down Expand Up @@ -75,7 +85,7 @@ describe('normalizePropertyNames', () => {
objs_prop_objs: originalPayload.objs_prop
}

const transformedPayload = normalizePropertyNames(originalPayload)
const transformedPayload = normalizePropertyNames(originalPayload, { typeSuffix: true })
expect(transformedPayload).toEqual(expectedPayload)
})

Expand All @@ -96,7 +106,7 @@ describe('normalizePropertyNames', () => {
dottedDate_date: obj['dotted.date'],
spacedReal_real: obj['spaced real']
}
const actual = normalizePropertyNames(obj, { camelCase: true })
const actual = normalizePropertyNames(obj, { camelCase: true, typeSuffix: true })
expect(actual).toEqual(expected)
})

Expand Down Expand Up @@ -129,7 +139,7 @@ describe('normalizePropertyNames', () => {
}
}
}
const transformedPayload = normalizePropertyNames(originalPayload, { camelCase: true })
const transformedPayload = normalizePropertyNames(originalPayload, { camelCase: true, typeSuffix: true })
expect(transformedPayload).toEqual(expectedPayload)
})

Expand All @@ -153,7 +163,7 @@ describe('normalizePropertyNames', () => {
[expectedNameAddingTypeSuffix]: obj[originalNameExcludingTypeSuffix]
}

const actual = normalizePropertyNames(obj)
const actual = normalizePropertyNames(obj, { typeSuffix: true })
expect(actual).toEqual(expected)
})
})
Expand All @@ -170,7 +180,7 @@ describe('normalizePropertyNames', () => {
[expectedNamePreservingTypeSuffix]: obj[originalNameIncludingTypeSuffix]
}

const actual = normalizePropertyNames(obj, { camelCase: true })
const actual = normalizePropertyNames(obj, { camelCase: true, typeSuffix: true })
expect(actual).toEqual(expected)
})
})
Loading

0 comments on commit 6cb1d80

Please sign in to comment.