-
Notifications
You must be signed in to change notification settings - Fork 15
feat(analytics-js-integrations): add Wingify device mode integration #3056
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 3 commits
79d37be
ffd7d3c
225ddd7
df61980
9435c6b
499bf7a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| import Wingify from '../../../src/integrations/Wingify/browser'; | ||
| import { integrations } from '../../../src/integrations/index'; | ||
|
|
||
| const destinationInfo = { | ||
| areTransformationsConnected: false, | ||
| destinationId: 'sample-destination-id', | ||
| }; | ||
|
|
||
| const mockWingify = new Wingify( | ||
| { | ||
| accountId: '654331', | ||
| settingsTolerance: 2000, | ||
| libraryTolerance: 2500, | ||
| isSPA: 1, | ||
| useExistingJquery: false, | ||
| sendExperimentTrack: false, | ||
| sendExperimentIdentify: false, | ||
| }, | ||
| { loglevel: 'debug', loadOnlyIntegrations: { Wingify: { loadIntegration: true } } }, | ||
| destinationInfo, | ||
| ); | ||
|
|
||
| describe('Wingify registry', () => { | ||
| test('should be registered in integrations map', () => { | ||
| expect(integrations.WINGIFY).toBeDefined(); | ||
| expect(typeof integrations.WINGIFY).toBe('function'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('Wingify init tests', () => { | ||
| let wingify; | ||
|
|
||
| test('Testing init call of Wingify', () => { | ||
| wingify = new Wingify( | ||
| { | ||
| accountId: '654331', | ||
| settingsTolerance: 2000, | ||
| libraryTolerance: 2500, | ||
| isSPA: 1, | ||
| useExistingJquery: false, | ||
| sendExperimentTrack: false, | ||
| sendExperimentIdentify: false, | ||
| }, | ||
| { loglevel: 'debug', loadOnlyIntegrations: { Wingify: { loadIntegration: true } } }, | ||
| destinationInfo, | ||
| ); | ||
| wingify.init(); | ||
| const script = window.document.querySelector( | ||
| 'script[src="https://edge.wingify.net/tag/654331.js"]', | ||
| ); | ||
| expect(script).toBeDefined(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('Wingify Track Event', () => { | ||
| beforeEach(() => { | ||
| jest.restoreAllMocks(); | ||
| jest.spyOn(mockWingify, 'init').mockImplementation(() => { | ||
| window.WINGIFY = { | ||
| push: jest.fn(), | ||
| event: jest.fn(), | ||
| }; | ||
| return Promise.resolve(window.WINGIFY); | ||
| }); | ||
| }); | ||
|
|
||
| test('Track call without parameters', async () => { | ||
| mockWingify.init(); | ||
| mockWingify.track({ | ||
| message: { | ||
| context: {}, | ||
| event: 'buttonClicked', | ||
| }, | ||
| }); | ||
| expect(window.WINGIFY.event).toHaveBeenCalled(); | ||
| expect(window.WINGIFY.event).toHaveBeenCalledWith( | ||
| 'rudder.buttonClicked', | ||
| {}, | ||
| { | ||
| ogName: 'buttonClicked', | ||
| source: 'rudderstack', | ||
| }, | ||
| ); | ||
| }); | ||
|
|
||
| test('Track call with parameters', async () => { | ||
| mockWingify.init(); | ||
| mockWingify.track({ | ||
| message: { | ||
| context: {}, | ||
| event: 'checkoutCompleted', | ||
| properties: { | ||
| category: 'Food', | ||
| currency: 'INR', | ||
| total: 123, | ||
| }, | ||
| }, | ||
| }); | ||
| expect(window.WINGIFY.event).toHaveBeenCalled(); | ||
| expect(window.WINGIFY.event).toHaveBeenCalledWith( | ||
| 'rudder.checkoutCompleted', | ||
| { | ||
| category: 'Food', | ||
| currency: 'INR', | ||
| total: 123, | ||
| }, | ||
| { | ||
| ogName: 'checkoutCompleted', | ||
| source: 'rudderstack', | ||
| }, | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| describe('Wingify Identify Event', () => { | ||
| beforeEach(() => { | ||
| jest.restoreAllMocks(); | ||
| jest.spyOn(mockWingify, 'init').mockImplementation(() => { | ||
| window.WINGIFY = { | ||
| push: jest.fn(), | ||
| visitor: jest.fn(), | ||
| }; | ||
| return Promise.resolve(window.WINGIFY); | ||
| }); | ||
| }); | ||
|
|
||
| test('Vistor call with attributes', async () => { | ||
|
zeeshan-vwo marked this conversation as resolved.
Outdated
zeeshan-vwo marked this conversation as resolved.
Outdated
|
||
| mockWingify.init(); | ||
| mockWingify.identify({ | ||
| message: { | ||
| userId: 'rudder01', | ||
| context: { | ||
| traits: { | ||
| email: 'abc@ruddertack.com', | ||
|
zeeshan-vwo marked this conversation as resolved.
Outdated
|
||
| isRudderEvents: true, | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
| expect(window.WINGIFY.visitor).toHaveBeenCalled(); | ||
| expect(window.WINGIFY.visitor).toHaveBeenCalledWith( | ||
| { | ||
| 'rudder.email': 'abc@ruddertack.com', | ||
| 'rudder.isRudderEvents': true, | ||
| }, | ||
| { | ||
| source: 'rudderstack', | ||
| }, | ||
| ); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import { sanitizeName, sanitizeAttributes } from '../../../src/integrations/Wingify/utils'; | ||
|
|
||
| describe('Wingify utilities tests', () => { | ||
| describe('Sanitize Names', () => { | ||
| it('should trim and add the rudder prefix to the event name', () => { | ||
| const name1 = 'abcd'; | ||
| const name2 = ' abcd '; | ||
|
|
||
| const expectedName = 'rudder.abcd'; | ||
|
|
||
| const result1 = sanitizeName(name1); | ||
| const result2 = sanitizeName(name2); | ||
|
|
||
| expect(result1).toEqual(expectedName); | ||
| expect(result2).toEqual(expectedName); | ||
| }); | ||
| }); | ||
|
|
||
| describe('Sanitize Attributes', () => { | ||
| it('should sanitize all the keys of the traits object', () => { | ||
| const attributes = { | ||
| companySize: 100, | ||
| companyName: 'RudderStack', | ||
| }; | ||
|
|
||
| const expectedAttributes = { | ||
| 'rudder.companySize': 100, | ||
| 'rudder.companyName': 'RudderStack', | ||
| }; | ||
|
|
||
| const result = sanitizeAttributes(attributes); | ||
|
|
||
| expect(result).toEqual(expectedAttributes); | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| /* eslint-disable @typescript-eslint/naming-convention */ | ||
| /* eslint-disable no-undef */ | ||
| /* eslint-disable no-underscore-dangle */ | ||
| /* eslint-disable class-methods-use-this */ | ||
| /* eslint-disable camelcase */ | ||
| import { NAME, DISPLAY_NAME } from './constants'; | ||
| import Logger from '../../utils/logger'; | ||
| import { getDestinationOptions, sanitizeName, sanitizeAttributes } from './utils'; | ||
| import { loadNativeSdk } from './nativeSdkLoader'; | ||
|
|
||
| const logger = new Logger(DISPLAY_NAME); | ||
|
|
||
| class Wingify { | ||
| constructor(config, analytics, destinationInfo) { | ||
| if (analytics.logLevel) { | ||
| logger.setLogLevel(analytics.logLevel); | ||
| } | ||
| this.analytics = analytics; | ||
| this.accountId = config.accountId; | ||
| this.settingsTolerance = config.settingsTolerance; | ||
| this.isSPA = config.isSPA; | ||
| this.libraryTolerance = config.libraryTolerance; | ||
| this.useExistingJquery = config.useExistingJquery; | ||
| this.sendExperimentTrack = config.sendExperimentTrack; | ||
| this.sendExperimentIdentify = config.sendExperimentIdentify; | ||
| this.name = NAME; | ||
| ({ | ||
| shouldApplyDeviceModeTransformation: this.shouldApplyDeviceModeTransformation, | ||
| propagateEventsUntransformedOnError: this.propagateEventsUntransformedOnError, | ||
| destinationId: this.destinationId, | ||
| } = destinationInfo ?? {}); | ||
| } | ||
|
|
||
| init() { | ||
| const wingifyIntgConfig = getDestinationOptions(this.analytics.loadOnlyIntegrations); | ||
| if (wingifyIntgConfig?.loadIntegration) { | ||
| const account_id = this.accountId; | ||
| const settings_tolerance = this.settingsTolerance; | ||
| loadNativeSdk(account_id, settings_tolerance); | ||
| } else { | ||
| logger.info('loadIntegration flag is disabled'); | ||
| } | ||
|
|
||
| window.WINGIFY = window.WINGIFY || []; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we update this integration to use the documented |
||
| window.WINGIFY.event = | ||
| window.WINGIFY.event || | ||
| function (...args) { | ||
| window.WINGIFY.push(['event', ...args]); | ||
| }; | ||
|
|
||
| window.WINGIFY.visitor = | ||
| window.WINGIFY.visitor || | ||
| function (...args) { | ||
| window.WINGIFY.push(['visitor', ...args]); | ||
| }; | ||
|
|
||
| if (this.sendExperimentTrack || this.sendExperimentIdentify) { | ||
| this.experimentViewed(); | ||
| } | ||
| } | ||
|
|
||
| isLoaded() { | ||
| return !!window._wingify_code; | ||
| } | ||
|
|
||
| isReady() { | ||
| return this.isLoaded(); | ||
| } | ||
|
|
||
| experimentViewed() { | ||
| window.WINGIFY = window.WINGIFY || []; | ||
| window.WINGIFY.push([ | ||
| 'onVariationApplied', | ||
| data => { | ||
| if (!data) { | ||
| return; | ||
| } | ||
| const expId = data[1]; | ||
| const variationId = data[2]; | ||
| logger.info( | ||
| 'experiment id:', | ||
| expId, | ||
| 'Variation Name:', | ||
| _wingify_exp[expId].comb_n[variationId], | ||
| ); | ||
| if ( | ||
| typeof _wingify_exp[expId].comb_n[variationId] !== 'undefined' && | ||
| ['VISUAL_AB', 'VISUAL', 'SPLIT_URL', 'SURVEY'].indexOf(_wingify_exp[expId].type) > -1 | ||
| ) { | ||
|
zeeshan-vwo marked this conversation as resolved.
Outdated
zeeshan-vwo marked this conversation as resolved.
|
||
| try { | ||
| if (this.sendExperimentTrack) { | ||
| this.analytics.track('Experiment Viewed', { | ||
| experimentId: expId, | ||
| variationName: _wingify_exp[expId].comb_n[variationId], | ||
| CampaignName: _wingify_exp[expId].name, | ||
| VariationId: variationId, | ||
| }); | ||
| } | ||
| } catch (error) { | ||
| logger.error('experimentViewed', error); | ||
| } | ||
| try { | ||
| if (this.sendExperimentIdentify) { | ||
| this.analytics.identify({ | ||
| [`Experiment: ${expId}`]: _wingify_exp[expId].comb_n[variationId], | ||
| }); | ||
| } | ||
| } catch (error) { | ||
| logger.error('experimentViewed', error); | ||
| } | ||
| } | ||
| }, | ||
| ]); | ||
| } | ||
|
|
||
| identify(rudderElement) { | ||
| const { message } = rudderElement; | ||
| const { traits } = message.context || message; | ||
| const payload = traits || {}; | ||
| const formattedAttributes = sanitizeAttributes(payload); | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| window.WINGIFY.visitor(formattedAttributes, { source: 'rudderstack' }); | ||
| } | ||
|
|
||
| track(rudderElement) { | ||
| const eventName = rudderElement.message.event; | ||
| if (!eventName) { | ||
| logger.error('[WINGIFY] track:: event name is required'); | ||
| return; | ||
| } | ||
|
zeeshan-vwo marked this conversation as resolved.
Outdated
|
||
| const properties = rudderElement.message?.properties || {}; | ||
| window.WINGIFY = window.WINGIFY || []; | ||
| if (eventName === 'Order Completed') { | ||
| const total = rudderElement.message.properties | ||
| ? rudderElement.message.properties.total || rudderElement.message.properties.revenue | ||
| : 0; | ||
| window.WINGIFY = window.WINGIFY || []; | ||
| window.WINGIFY.push(['track.revenueConversion', total]); | ||
| } | ||
| const sanitizedEventName = sanitizeName(eventName); | ||
|
zeeshan-vwo marked this conversation as resolved.
|
||
| logger.debug(`[WINGIFY] eventName: ${sanitizedEventName}`); | ||
| window.WINGIFY.event(sanitizedEventName, properties, { source: 'rudderstack', ogName: eventName }); | ||
| } | ||
| } | ||
|
|
||
| export default Wingify; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { WINGIFY_NAME as NAME, WINGIFY_DISPLAY_NAME as DISPLAY_NAME } from '../../constants/Destinations'; | ||
|
|
||
| const DIR_NAME = 'Wingify'; | ||
|
|
||
| const CNameMapping = { | ||
| [NAME]: NAME, | ||
| Wingify: NAME, | ||
| wingify: NAME, | ||
| WINGIFY: NAME, | ||
| }; | ||
|
|
||
| export { NAME, CNameMapping, DISPLAY_NAME, DIR_NAME }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { default as Wingify } from './browser'; | ||
|
zeeshan-vwo marked this conversation as resolved.
|
||
Uh oh!
There was an error while loading. Please reload this page.