diff --git a/packages/destination-actions/src/destinations/algolia-insights/__tests__/__snapshots__/snapshot.test.ts.snap b/packages/destination-actions/src/destinations/algolia-insights/__tests__/__snapshots__/snapshot.test.ts.snap index ca624207d3b..e2e8aef99d2 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/__tests__/__snapshots__/snapshot.test.ts.snap +++ b/packages/destination-actions/src/destinations/algolia-insights/__tests__/__snapshots__/snapshot.test.ts.snap @@ -4,6 +4,7 @@ exports[`Testing snapshot for actions-algolia-insights destination: conversionEv Object { "events": Array [ Object { + "authenticatedUserToken": "U[ABpE$k", "currency": "HTG", "eventName": "U[ABpE$k", "eventSubtype": "purchase", @@ -86,6 +87,7 @@ exports[`Testing snapshot for actions-algolia-insights destination: productClick Object { "events": Array [ Object { + "authenticatedUserToken": "LLjxSD^^GnH", "eventName": "LLjxSD^^GnH", "eventType": "conversion", "index": "LLjxSD^^GnH", @@ -124,6 +126,7 @@ exports[`Testing snapshot for actions-algolia-insights destination: productListF Object { "events": Array [ Object { + "authenticatedUserToken": "6O0djra", "eventName": "6O0djra", "eventType": "view", "filters": Array [ @@ -155,10 +158,47 @@ Object { } `; +exports[`Testing snapshot for actions-algolia-insights destination: productListViewedEvents action - all fields 1`] = ` +Object { + "events": Array [ + Object { + "authenticatedUserToken": "rV6HQ[S7QuqZWEj%HvC", + "eventName": "rV6HQ[S7QuqZWEj%HvC", + "eventType": "click", + "index": "rV6HQ[S7QuqZWEj%HvC", + "objectIDs": Array [ + "rV6HQ[S7QuqZWEj%HvC", + ], + "queryID": "rV6HQ[S7QuqZWEj%HvC", + "testType": "rV6HQ[S7QuqZWEj%HvC", + "timestamp": null, + "userToken": "rV6HQ[S7QuqZWEj%HvC", + }, + ], +} +`; + +exports[`Testing snapshot for actions-algolia-insights destination: productListViewedEvents action - required fields 1`] = ` +Object { + "events": Array [ + Object { + "eventName": "Product List Viewed", + "eventType": "view", + "index": "rV6HQ[S7QuqZWEj%HvC", + "objectIDs": Array [ + "rV6HQ[S7QuqZWEj%HvC", + ], + "userToken": "rV6HQ[S7QuqZWEj%HvC", + }, + ], +} +`; + exports[`Testing snapshot for actions-algolia-insights destination: productViewedEvents action - all fields 1`] = ` Object { "events": Array [ Object { + "authenticatedUserToken": "BLFCPcmz", "eventName": "BLFCPcmz", "eventType": "view", "index": "BLFCPcmz", diff --git a/packages/destination-actions/src/destinations/algolia-insights/algolia-insight-api.ts b/packages/destination-actions/src/destinations/algolia-insights/algolia-insight-api.ts index b910f83a49f..792ae8e1c8a 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/algolia-insight-api.ts +++ b/packages/destination-actions/src/destinations/algolia-insights/algolia-insight-api.ts @@ -12,6 +12,7 @@ type EventCommon = { eventName: string index: string userToken: string + authenticatedUserToken?: string timestamp?: number queryID?: string eventType: AlgoliaEventType diff --git a/packages/destination-actions/src/destinations/algolia-insights/conversionEvents/__tests__/__snapshots__/snapshot.test.ts.snap b/packages/destination-actions/src/destinations/algolia-insights/conversionEvents/__tests__/__snapshots__/snapshot.test.ts.snap index 4107c80fc1e..982b099b583 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/conversionEvents/__tests__/__snapshots__/snapshot.test.ts.snap +++ b/packages/destination-actions/src/destinations/algolia-insights/conversionEvents/__tests__/__snapshots__/snapshot.test.ts.snap @@ -4,6 +4,7 @@ exports[`Testing snapshot for AlgoliaInsights's conversionEvents destination act Object { "events": Array [ Object { + "authenticatedUserToken": ")j)vR5%1AP*epuo8A%R", "currency": "CUC", "eventName": ")j)vR5%1AP*epuo8A%R", "eventSubtype": "addToCart", @@ -34,6 +35,7 @@ exports[`Testing snapshot for AlgoliaInsights's conversionEvents destination act Object { "events": Array [ Object { + "authenticatedUserToken": "user1234", "eventName": "Conversion Event", "eventSubtype": "purchase", "eventType": "conversion", diff --git a/packages/destination-actions/src/destinations/algolia-insights/conversionEvents/__tests__/index.test.ts b/packages/destination-actions/src/destinations/algolia-insights/conversionEvents/__tests__/index.test.ts index a8727e353db..14e9527131d 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/conversionEvents/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/algolia-insights/conversionEvents/__tests__/index.test.ts @@ -55,7 +55,8 @@ describe('AlgoliaInsights.conversionEvents', () => { expect(algoliaEvent.eventType).toBe('conversion') expect(algoliaEvent.eventSubtype).toBe('purchase') expect(algoliaEvent.index).toBe(event.properties?.search_index) - expect(algoliaEvent.userToken).toBe(event.userId) + expect(algoliaEvent.userToken).toBe(event.anonymousId) + expect(algoliaEvent.authenticatedUserToken).toBe(event.userId) expect(algoliaEvent.objectIDs).toContain('9876') expect(algoliaEvent.objectIDs).toContain('5432') }) @@ -277,4 +278,73 @@ describe('AlgoliaInsights.conversionEvents', () => { expect(algoliaEvent.objectData).toBeUndefined() }) }) + + it('should pass anonymousId as user token if present', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Order Completed', + properties: { + query_id: '1234', + search_index: 'fashion_1', + products: [ + { + product_id: '9876' + }, + { + product_id: '5432' + } + ] + }, + anonymousId: 'anon-user-1234' + }) + const algoliaEvent = await testAlgoliaDestination(event) + expect(algoliaEvent.userToken).toBe(event.anonymousId) + }) + + it('should pass userId as user token if anonymousId not present', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Order Completed', + properties: { + query_id: '1234', + search_index: 'fashion_1', + products: [ + { + product_id: '9876' + }, + { + product_id: '5432' + } + ] + }, + anonymousId: undefined, + userId: 'authed-user-1234' + }) + const algoliaEvent = await testAlgoliaDestination(event) + expect(algoliaEvent.userToken).toBe(event.userId) + }) + + it('should pass userId and anonymousId if present', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Order Completed', + properties: { + query_id: '1234', + search_index: 'fashion_1', + products: [ + { + product_id: '9876' + }, + { + product_id: '5432' + } + ] + }, + anonymousId: 'anon-user-1234', + userId: 'authed-user-1234' + }) + const algoliaEvent = await testAlgoliaDestination(event) + expect(algoliaEvent.userToken).toBe(event.anonymousId) + expect(algoliaEvent.authenticatedUserToken).toBe(event.userId) + }) }) diff --git a/packages/destination-actions/src/destinations/algolia-insights/conversionEvents/generated-types.ts b/packages/destination-actions/src/destinations/algolia-insights/conversionEvents/generated-types.ts index 3bc33277a37..d3fd29d0861 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/conversionEvents/generated-types.ts +++ b/packages/destination-actions/src/destinations/algolia-insights/conversionEvents/generated-types.ts @@ -24,9 +24,13 @@ export interface Payload { */ queryID?: string /** - * The ID associated with the user. + * The ID associated with the user. If a user is authenticated, this should be set to the same value as the Authenticated User Token */ userToken: string + /** + * The authenticated ID associated with the user. + */ + authenticatedUserToken?: string /** * The timestamp of the event. */ diff --git a/packages/destination-actions/src/destinations/algolia-insights/conversionEvents/index.ts b/packages/destination-actions/src/destinations/algolia-insights/conversionEvents/index.ts index fe11e449d6e..2bda291aa61 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/conversionEvents/index.ts +++ b/packages/destination-actions/src/destinations/algolia-insights/conversionEvents/index.ts @@ -86,16 +86,23 @@ const getEventFields = (subtype: AlgoliaEventSubtype = 'purchase'): BaseActionDe userToken: { type: 'string', required: true, - description: 'The ID associated with the user.', + description: + 'The ID associated with the user. If a user is authenticated, this should be set to the same value as the Authenticated User Token', label: 'User Token', default: { '@if': { - exists: { '@path': '$.userId' }, - then: { '@path': '$.userId' }, - else: { '@path': '$.anonymousId' } + exists: { '@path': '$.anonymousId' }, + then: { '@path': '$.anonymousId' }, + else: { '@path': '$.userId' } } } }, + authenticatedUserToken: { + type: 'string', + description: 'The authenticated ID associated with the user.', + label: 'Authenticated User Token', + default: { '@path': '$.userId' } + }, timestamp: { type: 'string', required: false, @@ -178,6 +185,7 @@ export const conversionEvents: ActionDefinition = { value: data.payload.value, currency: data.payload.currency, userToken: data.payload.userToken, + authenticatedUserToken: data.payload.authenticatedUserToken, timestamp: data.payload.timestamp ? new Date(data.payload.timestamp).valueOf() : undefined } const insightPayload = { events: [insightEvent] } diff --git a/packages/destination-actions/src/destinations/algolia-insights/index.ts b/packages/destination-actions/src/destinations/algolia-insights/index.ts index 0cf6afc2228..286c01f6393 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/index.ts +++ b/packages/destination-actions/src/destinations/algolia-insights/index.ts @@ -11,6 +11,7 @@ import { AlgoliaApiPermissions, algoliaApiPermissionsUrl } from './algolia-insig import { productAddedEvents } from './productAddedEvents' import { productListFilteredEvents, productListFilteredPresets } from './productListFilteredEvents' +import { productListViewedEvents, productListViewedPresets } from './productListViewedEvents' export const ALGOLIA_INSIGHTS_USER_AGENT = 'algolia-segment-action-destination: 0.1' @@ -67,14 +68,16 @@ const destination: DestinationDefinition = { purchasePreset, addToCartPreset, productViewedPresets, - productListFilteredPresets + productListFilteredPresets, + productListViewedPresets ], actions: { productClickedEvents, conversionEvents, productViewedEvents, productAddedEvents, - productListFilteredEvents + productListFilteredEvents, + productListViewedEvents } } diff --git a/packages/destination-actions/src/destinations/algolia-insights/productClickedEvents/__tests__/__snapshots__/snapshot.test.ts.snap b/packages/destination-actions/src/destinations/algolia-insights/productClickedEvents/__tests__/__snapshots__/snapshot.test.ts.snap index 171113b996d..ff173936fac 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/productClickedEvents/__tests__/__snapshots__/snapshot.test.ts.snap +++ b/packages/destination-actions/src/destinations/algolia-insights/productClickedEvents/__tests__/__snapshots__/snapshot.test.ts.snap @@ -4,6 +4,7 @@ exports[`Testing snapshot for AlgoliaInsights's productClickedEvents destination Object { "events": Array [ Object { + "authenticatedUserToken": "tTO6#", "eventName": "tTO6#", "eventType": "view", "index": "tTO6#", @@ -26,6 +27,7 @@ exports[`Testing snapshot for AlgoliaInsights's productClickedEvents destination Object { "events": Array [ Object { + "authenticatedUserToken": "user1234", "eventName": "Product Clicked", "eventType": "click", "index": "tTO6#", diff --git a/packages/destination-actions/src/destinations/algolia-insights/productClickedEvents/__tests__/index.test.ts b/packages/destination-actions/src/destinations/algolia-insights/productClickedEvents/__tests__/index.test.ts index f51f18e5d3e..81918ad22bc 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/productClickedEvents/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/algolia-insights/productClickedEvents/__tests__/index.test.ts @@ -45,7 +45,8 @@ describe('AlgoliaInsights.productClickedEvents', () => { expect(algoliaEvent.eventName).toBe('Product Clicked') expect(algoliaEvent.eventType).toBe('click') expect(algoliaEvent.index).toBe(event.properties?.search_index) - expect(algoliaEvent.userToken).toBe(event.userId) + expect(algoliaEvent.userToken).toBe(event.anonymousId) + expect(algoliaEvent.authenticatedUserToken).toBe(event.userId) expect(algoliaEvent.queryID).toBe(event.properties?.query_id) expect(algoliaEvent.objectIDs).toContain('9876') expect(algoliaEvent.positions).toContain(5) @@ -92,4 +93,55 @@ describe('AlgoliaInsights.productClickedEvents', () => { const algoliaEvent = await testAlgoliaDestination(event) expect(algoliaEvent.positions?.[0]).toBe(event.properties?.position) }) + + it('should pass anonymousId as user token if present', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Product Clicked', + properties: { + query_id: '1234', + search_index: 'fashion_1', + product_id: '9876', + position: 5 + }, + anonymousId: 'anon-user-1234' + }) + const algoliaEvent = await testAlgoliaDestination(event) + expect(algoliaEvent.userToken).toBe(event.anonymousId) + }) + + it('should pass userId as user token if anonymousId not present', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Product Clicked', + properties: { + query_id: '1234', + search_index: 'fashion_1', + product_id: '9876', + position: 5 + }, + anonymousId: undefined, + userId: 'authed-user-1234' + }) + const algoliaEvent = await testAlgoliaDestination(event) + expect(algoliaEvent.userToken).toBe(event.userId) + }) + + it('should pass userId and anonymousId if present', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Product Clicked', + properties: { + query_id: '1234', + search_index: 'fashion_1', + product_id: '9876', + position: 5 + }, + anonymousId: 'anon-user-1234', + userId: 'authed-user-1234' + }) + const algoliaEvent = await testAlgoliaDestination(event) + expect(algoliaEvent.userToken).toBe(event.anonymousId) + expect(algoliaEvent.authenticatedUserToken).toBe(event.userId) + }) }) diff --git a/packages/destination-actions/src/destinations/algolia-insights/productClickedEvents/generated-types.ts b/packages/destination-actions/src/destinations/algolia-insights/productClickedEvents/generated-types.ts index d932d1edde4..7e545089b36 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/productClickedEvents/generated-types.ts +++ b/packages/destination-actions/src/destinations/algolia-insights/productClickedEvents/generated-types.ts @@ -18,9 +18,13 @@ export interface Payload { */ position?: number /** - * The ID associated with the user. + * The ID associated with the user. If a user is authenticated, this should be set to the same value as the Authenticated User Token */ userToken: string + /** + * The authenticated ID associated with the user. + */ + authenticatedUserToken?: string /** * The timestamp of the event. */ diff --git a/packages/destination-actions/src/destinations/algolia-insights/productClickedEvents/index.ts b/packages/destination-actions/src/destinations/algolia-insights/productClickedEvents/index.ts index 13229ba8b46..d8286fe97c6 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/productClickedEvents/index.ts +++ b/packages/destination-actions/src/destinations/algolia-insights/productClickedEvents/index.ts @@ -51,16 +51,23 @@ export const productClickedEvents: ActionDefinition = { userToken: { type: 'string', required: true, - description: 'The ID associated with the user.', + description: + 'The ID associated with the user. If a user is authenticated, this should be set to the same value as the Authenticated User Token', label: 'User Token', default: { '@if': { - exists: { '@path': '$.userId' }, - then: { '@path': '$.userId' }, - else: { '@path': '$.anonymousId' } + exists: { '@path': '$.anonymousId' }, + then: { '@path': '$.anonymousId' }, + else: { '@path': '$.userId' } } } }, + authenticatedUserToken: { + type: 'string', + description: 'The authenticated ID associated with the user.', + label: 'Authenticated User Token', + default: { '@path': '$.userId' } + }, timestamp: { type: 'string', required: false, @@ -108,6 +115,7 @@ export const productClickedEvents: ActionDefinition = { queryID: data.payload.queryID, objectIDs: [data.payload.objectID], userToken: data.payload.userToken, + authenticatedUserToken: data.payload.authenticatedUserToken, positions: data.payload.position ? [data.payload.position] : undefined, timestamp: data.payload.timestamp ? new Date(data.payload.timestamp).valueOf() : undefined } diff --git a/packages/destination-actions/src/destinations/algolia-insights/productListFilteredEvents/__tests__/__snapshots__/snapshot.test.ts.snap b/packages/destination-actions/src/destinations/algolia-insights/productListFilteredEvents/__tests__/__snapshots__/snapshot.test.ts.snap index 7297765501f..35c082b7eaa 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/productListFilteredEvents/__tests__/__snapshots__/snapshot.test.ts.snap +++ b/packages/destination-actions/src/destinations/algolia-insights/productListFilteredEvents/__tests__/__snapshots__/snapshot.test.ts.snap @@ -4,6 +4,7 @@ exports[`Testing snapshot for AlgoliaInsights's productListFilteredEvents destin Object { "events": Array [ Object { + "authenticatedUserToken": "E625IsTOULbrg8", "eventName": "E625IsTOULbrg8", "eventType": "conversion", "filters": Array [ diff --git a/packages/destination-actions/src/destinations/algolia-insights/productListFilteredEvents/__tests__/index.test.ts b/packages/destination-actions/src/destinations/algolia-insights/productListFilteredEvents/__tests__/index.test.ts index b4745446551..c9815af9065 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/productListFilteredEvents/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/algolia-insights/productListFilteredEvents/__tests__/index.test.ts @@ -45,7 +45,8 @@ describe('AlgoliaInsights.productListFilteredEvents', () => { expect(algoliaEvent.eventName).toBe('Product List Filtered') expect(algoliaEvent.eventType).toBe('click') expect(algoliaEvent.index).toBe(event.properties?.search_index) - expect(algoliaEvent.userToken).toBe(event.userId) + expect(algoliaEvent.userToken).toBe(event.anonymousId) + expect(algoliaEvent.authenticatedUserToken).toBe(event.userId) expect(algoliaEvent.queryID).toBe(event.properties?.query_id) expect(algoliaEvent.filters).toContain('discount:10%25') }) @@ -76,4 +77,52 @@ describe('AlgoliaInsights.productListFilteredEvents', () => { const algoliaEvent = await testAlgoliaDestination(event) expect(algoliaEvent.queryID).toBe(event.properties?.query_id) }) + + it('should pass anonymousId as user token if present', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Product List Filtered', + properties: { + query_id: '1234', + search_index: 'fashion_1', + filters: [{ attribute: 'discount', value: '10%25' }] + }, + anonymousId: 'anon-user-1234' + }) + const algoliaEvent = await testAlgoliaDestination(event) + expect(algoliaEvent.userToken).toBe(event.anonymousId) + }) + + it('should pass userId as user token if anonymousId not present', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Product List Filtered', + properties: { + query_id: '1234', + search_index: 'fashion_1', + filters: [{ attribute: 'discount', value: '10%25' }] + }, + anonymousId: undefined, + userId: 'authed-user-1234' + }) + const algoliaEvent = await testAlgoliaDestination(event) + expect(algoliaEvent.userToken).toBe(event.userId) + }) + + it('should pass userId and anonymousId if present', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Product List Filtered', + properties: { + query_id: '1234', + search_index: 'fashion_1', + filters: [{ attribute: 'discount', value: '10%25' }] + }, + anonymousId: 'anon-user-1234', + userId: 'authed-user-1234' + }) + const algoliaEvent = await testAlgoliaDestination(event) + expect(algoliaEvent.userToken).toBe(event.anonymousId) + expect(algoliaEvent.authenticatedUserToken).toBe(event.userId) + }) }) diff --git a/packages/destination-actions/src/destinations/algolia-insights/productListFilteredEvents/generated-types.ts b/packages/destination-actions/src/destinations/algolia-insights/productListFilteredEvents/generated-types.ts index 5cc63dbc347..05e617e23ee 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/productListFilteredEvents/generated-types.ts +++ b/packages/destination-actions/src/destinations/algolia-insights/productListFilteredEvents/generated-types.ts @@ -23,9 +23,13 @@ export interface Payload { */ queryID?: string /** - * The ID associated with the user. + * The ID associated with the user. If a user is authenticated, this should be set to the same value as the Authenticated User Token */ userToken: string + /** + * The authenticated ID associated with the user. + */ + authenticatedUserToken?: string /** * The timestamp of the event. */ diff --git a/packages/destination-actions/src/destinations/algolia-insights/productListFilteredEvents/index.ts b/packages/destination-actions/src/destinations/algolia-insights/productListFilteredEvents/index.ts index 79f9359c55f..b57d0231025 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/productListFilteredEvents/index.ts +++ b/packages/destination-actions/src/destinations/algolia-insights/productListFilteredEvents/index.ts @@ -50,16 +50,23 @@ export const productListFilteredEvents: ActionDefinition = { userToken: { type: 'string', required: true, - description: 'The ID associated with the user.', + description: + 'The ID associated with the user. If a user is authenticated, this should be set to the same value as the Authenticated User Token', label: 'User Token', default: { '@if': { - exists: { '@path': '$.userId' }, - then: { '@path': '$.userId' }, - else: { '@path': '$.anonymousId' } + exists: { '@path': '$.anonymousId' }, + then: { '@path': '$.anonymousId' }, + else: { '@path': '$.userId' } } } }, + authenticatedUserToken: { + type: 'string', + description: 'The authenticated ID associated with the user.', + label: 'Authenticated User Token', + default: { '@path': '$.userId' } + }, timestamp: { type: 'string', required: false, @@ -108,6 +115,7 @@ export const productListFilteredEvents: ActionDefinition = { queryID: data.payload.queryID, filters, userToken: data.payload.userToken, + authenticatedUserToken: data.payload.authenticatedUserToken, timestamp: data.payload.timestamp ? new Date(data.payload.timestamp).valueOf() : undefined } const insightPayload = { events: [insightEvent] } diff --git a/packages/destination-actions/src/destinations/algolia-insights/productListViewedEvents/__tests__/__snapshots__/snapshot.test.ts.snap b/packages/destination-actions/src/destinations/algolia-insights/productListViewedEvents/__tests__/__snapshots__/snapshot.test.ts.snap new file mode 100644 index 00000000000..43da696ca69 --- /dev/null +++ b/packages/destination-actions/src/destinations/algolia-insights/productListViewedEvents/__tests__/__snapshots__/snapshot.test.ts.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Testing snapshot for AlgoliaInsights's productListViewedEvents destination action: all fields 1`] = ` +Object { + "events": Array [ + Object { + "authenticatedUserToken": "lx7Lx", + "eventName": "lx7Lx", + "eventType": "view", + "index": "lx7Lx", + "objectIDs": Array [ + "lx7Lx", + ], + "queryID": "lx7Lx", + "testType": "lx7Lx", + "timestamp": null, + "userToken": "lx7Lx", + }, + ], +} +`; + +exports[`Testing snapshot for AlgoliaInsights's productListViewedEvents destination action: required fields 1`] = ` +Object { + "events": Array [ + Object { + "authenticatedUserToken": "user1234", + "eventName": "Product List Viewed", + "eventType": "view", + "index": "lx7Lx", + "objectIDs": Array [ + "lx7Lx", + ], + "timestamp": 1674843786677, + "userToken": "lx7Lx", + }, + ], +} +`; diff --git a/packages/destination-actions/src/destinations/algolia-insights/productListViewedEvents/__tests__/index.test.ts b/packages/destination-actions/src/destinations/algolia-insights/productListViewedEvents/__tests__/index.test.ts new file mode 100644 index 00000000000..aeeae32ae28 --- /dev/null +++ b/packages/destination-actions/src/destinations/algolia-insights/productListViewedEvents/__tests__/index.test.ts @@ -0,0 +1,137 @@ +import nock from 'nock' +import { createTestEvent, createTestIntegration, SegmentEvent } from '@segment/actions-core' +import Destination, { ALGOLIA_INSIGHTS_USER_AGENT } from '../../index' +import { AlgoliaProductViewedEvent, BaseAlgoliaInsightsURL } from '../../algolia-insight-api' + +const testDestination = createTestIntegration(Destination) + +const algoliaDestinationActionSettings = { + appId: 'algolia-application-id', + apiKey: 'algolia-api-key' +} +const testAlgoliaDestination = async (event: SegmentEvent): Promise => { + nock(BaseAlgoliaInsightsURL).post('/1/events').reply(200, {}) + const segmentEvent = { + event: { ...event }, + settings: algoliaDestinationActionSettings, + useDefaultMappings: true + } + const actionResponse = await testDestination.testAction('productListViewedEvents', segmentEvent) + const actionRequest = actionResponse[0].request + + expect(actionResponse.length).toBe(1) + expect(actionRequest.headers.get('X-Algolia-Application-Id')).toBe(algoliaDestinationActionSettings.appId) + expect(actionRequest.headers.get('X-Algolia-API-Key')).toBe(algoliaDestinationActionSettings.apiKey) + expect(actionRequest.headers.get('X-Algolia-Agent')).toBe(ALGOLIA_INSIGHTS_USER_AGENT) + + const rawBody = await actionRequest.text() + return JSON.parse(rawBody)['events'][0] +} + +describe('AlgoliaInsights.productListViewedEvents', () => { + it('should submit click on track "Product List Viewed" event', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Product List Viewed', + properties: { + query_id: '1234', + search_index: 'fashion_1', + products: [ + { product_id: '9876', name: 'foo', price: 10 }, + { product_id: '5432', category: 'bar' } + ] + } + }) + const algoliaEvent = await testAlgoliaDestination(event) + + expect(algoliaEvent.eventName).toBe('Product List Viewed') + expect(algoliaEvent.eventType).toBe('view') + expect(algoliaEvent.index).toBe(event.properties?.search_index) + expect(algoliaEvent.userToken).toBe(event.anonymousId) + expect(algoliaEvent.authenticatedUserToken).toBe(event.userId) + expect(algoliaEvent.objectIDs).toEqual(['9876', '5432']) + }) + + it('should pass timestamp if present', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Product List Viewed', + properties: { + search_index: 'fashion_1', + products: [ + { product_id: '9876', name: 'foo', price: 10 }, + { product_id: '5432', category: 'bar' } + ] + }, + userId: undefined + }) + const algoliaEvent = await testAlgoliaDestination(event) + expect(algoliaEvent.timestamp).toBe(new Date(event.timestamp as string).valueOf()) + }) + + it('should pass queryId if present', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Product List Viewed', + properties: { + query_id: '1234', + search_index: 'fashion_1', + products: [ + { product_id: '9876', name: 'foo', price: 10 }, + { product_id: '5432', category: 'bar' } + ] + }, + userId: undefined + }) + const algoliaEvent = await testAlgoliaDestination(event) + expect(algoliaEvent.queryID).toBe(event.properties?.query_id) + }) + + it('should pass anonymousId as user token if present', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Product List Viewed', + properties: { + query_id: '1234', + search_index: 'fashion_1', + products: [{ product_id: '9876' }] + }, + anonymousId: 'anon-user-1234' + }) + const algoliaEvent = await testAlgoliaDestination(event) + expect(algoliaEvent.userToken).toBe(event.anonymousId) + }) + + it('should pass userId as user token if anonymousId not present', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Product List Viewed', + properties: { + query_id: '1234', + search_index: 'fashion_1', + products: [{ product_id: '9876' }] + }, + anonymousId: undefined, + userId: 'authed-user-1234' + }) + const algoliaEvent = await testAlgoliaDestination(event) + expect(algoliaEvent.userToken).toBe(event.userId) + }) + + it('should pass userId and anonymousId if present', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Product List Viewed', + properties: { + query_id: '1234', + search_index: 'fashion_1', + products: [{ product_id: '9876' }] + }, + anonymousId: 'anon-user-1234', + userId: 'authed-user-1234' + }) + const algoliaEvent = await testAlgoliaDestination(event) + expect(algoliaEvent.userToken).toBe(event.anonymousId) + expect(algoliaEvent.authenticatedUserToken).toBe(event.userId) + }) +}) diff --git a/packages/destination-actions/src/destinations/algolia-insights/productListViewedEvents/__tests__/snapshot.test.ts b/packages/destination-actions/src/destinations/algolia-insights/productListViewedEvents/__tests__/snapshot.test.ts new file mode 100644 index 00000000000..2cc9a7a3389 --- /dev/null +++ b/packages/destination-actions/src/destinations/algolia-insights/productListViewedEvents/__tests__/snapshot.test.ts @@ -0,0 +1,79 @@ +import { createTestEvent, createTestIntegration } from '@segment/actions-core' +import { generateTestData } from '../../../../lib/test-data' +import destination from '../../index' +import nock from 'nock' + +const testDestination = createTestIntegration(destination) +const actionSlug = 'productListViewedEvents' +const destinationSlug = 'AlgoliaInsights' +const seedName = `${destinationSlug}#${actionSlug}` + +describe(`Testing snapshot for ${destinationSlug}'s ${actionSlug} destination action:`, () => { + it('required fields', async () => { + const action = destination.actions[actionSlug] + const [eventData, settingsData] = generateTestData(seedName, destination, action, true) + + nock(/.*/).persist().get(/.*/).reply(200) + nock(/.*/).persist().post(/.*/).reply(200) + nock(/.*/).persist().put(/.*/).reply(200) + + const event = createTestEvent({ + timestamp: new Date('2023-01-27T18:23:06.677Z').toISOString(), + properties: eventData + }) + + const responses = await testDestination.testAction(actionSlug, { + useDefaultMappings: true, + event: event, + mapping: event.properties, + settings: settingsData, + auth: undefined + }) + + const request = responses[0].request + const rawBody = await request.text() + + try { + const json = JSON.parse(rawBody) + expect(json).toMatchSnapshot() + return + } catch (err) { + expect(rawBody).toMatchSnapshot() + } + + expect(request.headers).toMatchSnapshot() + }) + + it('all fields', async () => { + const action = destination.actions[actionSlug] + const [eventData, settingsData] = generateTestData(seedName, destination, action, false) + + nock(/.*/).persist().get(/.*/).reply(200) + nock(/.*/).persist().post(/.*/).reply(200) + nock(/.*/).persist().put(/.*/).reply(200) + + const event = createTestEvent({ + timestamp: new Date('2023-01-27T18:23:06.677Z').toISOString(), + properties: eventData + }) + + const responses = await testDestination.testAction(actionSlug, { + useDefaultMappings: true, + event: event, + mapping: event.properties, + settings: settingsData, + auth: undefined + }) + + const request = responses[0].request + const rawBody = await request.text() + + try { + const json = JSON.parse(rawBody) + expect(json).toMatchSnapshot() + return + } catch (err) { + expect(rawBody).toMatchSnapshot() + } + }) +}) diff --git a/packages/destination-actions/src/destinations/algolia-insights/productListViewedEvents/generated-types.ts b/packages/destination-actions/src/destinations/algolia-insights/productListViewedEvents/generated-types.ts new file mode 100644 index 00000000000..97cbdef04dd --- /dev/null +++ b/packages/destination-actions/src/destinations/algolia-insights/productListViewedEvents/generated-types.ts @@ -0,0 +1,47 @@ +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface Payload { + /** + * The viewed products. Populates the ObjectIDs field in the Algolia Insights API. Each object must contain a product_id field. + */ + products: { + /** + * The unique ID of the product. + */ + product_id: string + }[] + /** + * Name of the targeted search index. + */ + index: string + /** + * Query ID of the list on which the items were viewed. + */ + queryID?: string + /** + * The ID associated with the user. If a user is authenticated, this should be set to the same value as the Authenticated User Token + */ + userToken: string + /** + * The authenticated ID associated with the user. + */ + authenticatedUserToken?: string + /** + * The timestamp of the event. + */ + timestamp?: string + /** + * Additional fields for this event. This field may be useful for Algolia Insights fields which are not mapped in Segment. + */ + extraProperties?: { + [k: string]: unknown + } + /** + * The name of the event to be send to Algolia. Defaults to 'Product List Viewed' + */ + eventName?: string + /** + * The type of event to send to Algolia. Defaults to 'view' + */ + eventType?: string +} diff --git a/packages/destination-actions/src/destinations/algolia-insights/productListViewedEvents/index.ts b/packages/destination-actions/src/destinations/algolia-insights/productListViewedEvents/index.ts new file mode 100644 index 00000000000..5b1f6928e44 --- /dev/null +++ b/packages/destination-actions/src/destinations/algolia-insights/productListViewedEvents/index.ts @@ -0,0 +1,150 @@ +import type { ActionDefinition, Preset } from '@segment/actions-core' +import { defaultValues } from '@segment/actions-core' +import { AlgoliaBehaviourURL, AlgoliaProductViewedEvent, AlgoliaEventType } from '../algolia-insight-api' +import type { Settings } from '../generated-types' +import type { Payload } from './generated-types' + +const segmentEventName = 'Product List Viewed' + +export const productListViewedEvents: ActionDefinition = { + title: `${segmentEventName} Events`, + description: + 'Product list viewed events act as a positive signal for associated record objects — the associated Product IDs. Query ID is optional and indicates that the view events are the result of a search query.', + fields: { + products: { + label: 'Product Details', + description: + 'The viewed products. Populates the ObjectIDs field in the Algolia Insights API. Each object must contain a product_id field.', + type: 'object', + defaultObjectUI: 'keyvalue', + multiple: true, + required: true, + properties: { + product_id: { + label: 'product_id', + type: 'string', + description: 'The unique ID of the product.', + required: true + } + }, + additionalProperties: false, + default: { + '@arrayPath': [ + '$.properties.products', + { + product_id: { + '@path': '$.product_id' + } + } + ] + } + }, + index: { + label: 'Index', + description: 'Name of the targeted search index.', + type: 'string', + required: true, + default: { + '@path': '$.properties.search_index' + } + }, + queryID: { + label: 'Query ID', + description: 'Query ID of the list on which the items were viewed.', + type: 'string', + required: false, + default: { + '@if': { + exists: { '@path': '$.properties.query_id' }, + then: { '@path': '$.properties.query_id' }, + else: { '@path': '$.integrations.Algolia Insights (Actions).query_id' } + } + } + }, + userToken: { + type: 'string', + required: true, + description: + 'The ID associated with the user. If a user is authenticated, this should be set to the same value as the Authenticated User Token', + label: 'User Token', + default: { + '@if': { + exists: { '@path': '$.anonymousId' }, + then: { '@path': '$.anonymousId' }, + else: { '@path': '$.userId' } + } + } + }, + authenticatedUserToken: { + type: 'string', + description: 'The authenticated ID associated with the user.', + label: 'Authenticated User Token', + default: { '@path': '$.userId' } + }, + timestamp: { + type: 'string', + required: false, + description: 'The timestamp of the event.', + label: 'Timestamp', + default: { '@path': '$.timestamp' } + }, + extraProperties: { + label: 'Extra Properties', + required: false, + description: + 'Additional fields for this event. This field may be useful for Algolia Insights fields which are not mapped in Segment.', + type: 'object', + default: { + '@path': '$.properties' + } + }, + eventName: { + label: 'Event Name', + description: `The name of the event to be send to Algolia. Defaults to '${segmentEventName}'`, + type: 'string', + required: false, + default: segmentEventName + }, + eventType: { + label: 'Event Type', + description: "The type of event to send to Algolia. Defaults to 'view'", + type: 'string', + required: false, + default: 'view', + choices: [ + { label: 'view', value: 'view' }, + { label: 'conversion', value: 'conversion' }, + { label: 'click', value: 'click' } + ] + } + }, + defaultSubscription: `type = "track" and event = "${segmentEventName}"`, + perform: (request, data) => { + const insightEvent: AlgoliaProductViewedEvent = { + ...data.payload.extraProperties, + eventName: data.payload.eventName ?? segmentEventName, + eventType: (data.payload.eventType as AlgoliaEventType) ?? ('view' as AlgoliaEventType), + index: data.payload.index, + queryID: data.payload.queryID, + objectIDs: data.payload.products.map((obj) => obj.product_id), + timestamp: data.payload.timestamp ? new Date(data.payload.timestamp).valueOf() : undefined, + userToken: data.payload.userToken, + authenticatedUserToken: data.payload.authenticatedUserToken + } + const insightPayload = { events: [insightEvent] } + + return request(AlgoliaBehaviourURL, { + method: 'post', + json: insightPayload + }) + } +} + +/** used in the quick setup */ +export const productListViewedPresets: Preset = { + name: 'Send product list viewed events to Algolia', + subscribe: productListViewedEvents.defaultSubscription as string, + partnerAction: 'productListViewedEvents', + mapping: defaultValues(productListViewedEvents.fields), + type: 'automatic' +} diff --git a/packages/destination-actions/src/destinations/algolia-insights/productViewedEvents/__tests__/__snapshots__/snapshot.test.ts.snap b/packages/destination-actions/src/destinations/algolia-insights/productViewedEvents/__tests__/__snapshots__/snapshot.test.ts.snap index 499d433b8ec..ada091b9094 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/productViewedEvents/__tests__/__snapshots__/snapshot.test.ts.snap +++ b/packages/destination-actions/src/destinations/algolia-insights/productViewedEvents/__tests__/__snapshots__/snapshot.test.ts.snap @@ -4,6 +4,7 @@ exports[`Testing snapshot for AlgoliaInsights's productViewedEvents destination Object { "events": Array [ Object { + "authenticatedUserToken": "og&DCP)aINw@qxe)", "eventName": "og&DCP)aINw@qxe)", "eventType": "click", "index": "og&DCP)aINw@qxe)", @@ -23,6 +24,7 @@ exports[`Testing snapshot for AlgoliaInsights's productViewedEvents destination Object { "events": Array [ Object { + "authenticatedUserToken": "user1234", "eventName": "Product Viewed", "eventType": "view", "index": "og&DCP)aINw@qxe)", diff --git a/packages/destination-actions/src/destinations/algolia-insights/productViewedEvents/__tests__/index.test.ts b/packages/destination-actions/src/destinations/algolia-insights/productViewedEvents/__tests__/index.test.ts index fcd9dd5c6cb..f8c1182bdb7 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/productViewedEvents/__tests__/index.test.ts +++ b/packages/destination-actions/src/destinations/algolia-insights/productViewedEvents/__tests__/index.test.ts @@ -37,14 +37,16 @@ describe('AlgoliaInsights.productViewedEvents', () => { query_id: '1234', search_index: 'fashion_1', product_id: '9876' - } + }, + anonymousId: 'anon-user-1234' }) const algoliaEvent = await testAlgoliaDestination(event) expect(algoliaEvent.eventName).toBe('Product Viewed') expect(algoliaEvent.eventType).toBe('view') expect(algoliaEvent.index).toBe(event.properties?.search_index) - expect(algoliaEvent.userToken).toBe(event.userId) + expect(algoliaEvent.userToken).toBe(event.anonymousId) + expect(algoliaEvent.authenticatedUserToken).toBe(event.userId) expect(algoliaEvent.objectIDs).toContain('9876') }) @@ -76,4 +78,52 @@ describe('AlgoliaInsights.productViewedEvents', () => { const algoliaEvent = await testAlgoliaDestination(event) expect(algoliaEvent.queryID).toBe(event.properties?.query_id) }) + + it('should pass anonymousId as user token if present', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Product Viewed', + properties: { + query_id: '1234', + search_index: 'fashion_1', + product_id: '9876' + }, + anonymousId: 'anon-user-1234' + }) + const algoliaEvent = await testAlgoliaDestination(event) + expect(algoliaEvent.userToken).toBe(event.anonymousId) + }) + + it('should pass userId as user token if anonymousId not present', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Product Viewed', + properties: { + query_id: '1234', + search_index: 'fashion_1', + product_id: '9876' + }, + anonymousId: undefined, + userId: 'authed-user-1234' + }) + const algoliaEvent = await testAlgoliaDestination(event) + expect(algoliaEvent.userToken).toBe(event.userId) + }) + + it('should pass userId and anonymousId if present', async () => { + const event = createTestEvent({ + type: 'track', + event: 'Product Viewed', + properties: { + query_id: '1234', + search_index: 'fashion_1', + product_id: '9876' + }, + anonymousId: 'anon-user-1234', + userId: 'authed-user-1234' + }) + const algoliaEvent = await testAlgoliaDestination(event) + expect(algoliaEvent.userToken).toBe(event.anonymousId) + expect(algoliaEvent.authenticatedUserToken).toBe(event.userId) + }) }) diff --git a/packages/destination-actions/src/destinations/algolia-insights/productViewedEvents/generated-types.ts b/packages/destination-actions/src/destinations/algolia-insights/productViewedEvents/generated-types.ts index f9dae0471f0..a8a62b2601b 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/productViewedEvents/generated-types.ts +++ b/packages/destination-actions/src/destinations/algolia-insights/productViewedEvents/generated-types.ts @@ -2,7 +2,7 @@ export interface Payload { /** - * Product ID of the clicked item. + * Product ID of the viewed item. */ objectID: string /** @@ -14,9 +14,13 @@ export interface Payload { */ queryID?: string /** - * The ID associated with the user. + * The ID associated with the user. If a user is authenticated, this should be set to the same value as the Authenticated User Token */ userToken: string + /** + * The authenticated ID associated with the user. + */ + authenticatedUserToken?: string /** * The timestamp of the event. */ diff --git a/packages/destination-actions/src/destinations/algolia-insights/productViewedEvents/index.ts b/packages/destination-actions/src/destinations/algolia-insights/productViewedEvents/index.ts index 8b6e3bafd05..a14eac47309 100644 --- a/packages/destination-actions/src/destinations/algolia-insights/productViewedEvents/index.ts +++ b/packages/destination-actions/src/destinations/algolia-insights/productViewedEvents/index.ts @@ -7,11 +7,11 @@ import type { Payload } from './generated-types' export const productViewedEvents: ActionDefinition = { title: 'Product Viewed Events', description: - 'Product view events act as a positive signal for associated record objects — the associated Product ID. Query ID is optional and indicates that the view events is the result of a search query.', + 'Product view events act as a positive signal for associated record objects — the associated Product ID. Query ID is optional and indicates that the view event is the result of a search query.', fields: { objectID: { label: 'Product ID', - description: 'Product ID of the clicked item.', + description: 'Product ID of the viewed item.', type: 'string', required: true, default: { @@ -43,16 +43,23 @@ export const productViewedEvents: ActionDefinition = { userToken: { type: 'string', required: true, - description: 'The ID associated with the user.', + description: + 'The ID associated with the user. If a user is authenticated, this should be set to the same value as the Authenticated User Token', label: 'User Token', default: { '@if': { - exists: { '@path': '$.userId' }, - then: { '@path': '$.userId' }, - else: { '@path': '$.anonymousId' } + exists: { '@path': '$.anonymousId' }, + then: { '@path': '$.anonymousId' }, + else: { '@path': '$.userId' } } } }, + authenticatedUserToken: { + type: 'string', + description: 'The authenticated ID associated with the user.', + label: 'Authenticated User Token', + default: { '@path': '$.userId' } + }, timestamp: { type: 'string', required: false, @@ -100,7 +107,8 @@ export const productViewedEvents: ActionDefinition = { queryID: data.payload.queryID, objectIDs: [data.payload.objectID], timestamp: data.payload.timestamp ? new Date(data.payload.timestamp).valueOf() : undefined, - userToken: data.payload.userToken + userToken: data.payload.userToken, + authenticatedUserToken: data.payload.authenticatedUserToken } const insightPayload = { events: [insightEvent] }