Skip to content

Commit 080155a

Browse files
feat: onboard new destination ninetailed (#1617)
* feat: onboard new destination ninetailed * chore: added test cases and addressed comments * chore: removed sample-app from analysis * fix: remove eslint check sample app * fix: eslint ignore file * fix: eslint ignore file path from root * Update .eslintignore Co-authored-by: Sai Kumar Battinoju <[email protected]> --------- Co-authored-by: Sai Kumar Battinoju <[email protected]>
1 parent 37ac0ea commit 080155a

30 files changed

+18545
-4
lines changed

.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ testBookSuites/
1515
tsconfig.build.tsbuildinfo
1616
nativeSdkLoader.js
1717
nativeSdkLoader.ts
18+
packages/analytics-js-integrations/src/integrations/Ninetailed/sample-app/
1819
assets/

.prettierignore

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ CONTRIBUTING.md
1212
examples/chrome-extension/**/rudderAnalytics.js
1313
examples/chrome-extension/**/foreground.js
1414
examples/**/index.html
15+
packages/analytics-js-integrations/src/integrations/Ninetailed/sample-app/
1516
**/public/index.html
1617

1718
/.nx/cache

packages/analytics-js-common/src/constants/destDisplayNamesToFileNamesMap.ts

+3
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ import {
154154
SpotifyPixelDirectoryName,
155155
CommandBarDisplayName,
156156
CommandBarDirectoryName,
157+
NinetailedDisplayName,
158+
NinetailedDirectoryName,
157159
} from './destinationNames';
158160

159161
// The destination directory name is used as the destination SDK file name in CDN
@@ -235,6 +237,7 @@ const destDisplayNamesToFileNamesMap: Record<string, string> = {
235237
[SprigDisplayName]: SprigDirectoryName,
236238
[SpotifyPixelDisplayName]: SpotifyPixelDirectoryName,
237239
[CommandBarDisplayName]: CommandBarDirectoryName,
240+
[NinetailedDisplayName]: NinetailedDirectoryName,
238241
};
239242

240243
export { destDisplayNamesToFileNamesMap };

packages/analytics-js-common/src/constants/destinationNames.ts

+4
Original file line numberDiff line numberDiff line change
@@ -306,3 +306,7 @@ export {
306306
DISPLAY_NAME as CommandBarDisplayName,
307307
DIR_NAME as CommandBarDirectoryName,
308308
} from './integrations/CommandBar/constants';
309+
export {
310+
DISPLAY_NAME as NinetailedDisplayName,
311+
DIR_NAME as NinetailedDirectoryName,
312+
} from './integrations/Ninetailed/constants';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const DIR_NAME = 'Ninetailed';
2+
const NAME = 'NINETAILED';
3+
const DISPLAY_NAME = 'Ninetailed';
4+
5+
const DISPLAY_NAME_TO_DIR_NAME_MAP = { [DISPLAY_NAME]: DIR_NAME };
6+
const CNameMapping = {
7+
[NAME]: NAME,
8+
Ninetailed: NAME,
9+
ninetailed: NAME,
10+
NineTailed: NAME,
11+
};
12+
13+
export { NAME, CNameMapping, DISPLAY_NAME_TO_DIR_NAME_MAP, DISPLAY_NAME, DIR_NAME };

packages/analytics-js-common/src/v1.1/utils/client_server_name.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,9 @@ const clientToServerNames = {
7474
TIKTOK_ADS: 'TikTok Ads',
7575
ACTIVE_CAMPAIGN: 'ActiveCampaign',
7676
SPRIG: 'Sprig',
77-
SPOTIFYPIXEL:'Spotify Pixel',
78-
COMMANDBAR:'CommandBar',
77+
SPOTIFYPIXEL: 'Spotify Pixel',
78+
COMMANDBAR: 'CommandBar',
79+
NINETAILED: 'Ninetailed',
7980
};
8081

8182
export { clientToServerNames };

packages/analytics-js-common/src/v1.1/utils/config_to_integration_names.js

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ const configToIntNames = {
7777
SPRIG: 'Sprig',
7878
SPOTIFYPIXEL: 'SpotifyPixel',
7979
COMMANDBAR: 'CommandBar',
80+
NINETAILED: 'Ninetailed',
8081
};
8182

8283
export { configToIntNames };

packages/analytics-js-common/src/v1.1/utils/integration_cname.js

+3
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ import { CNameMapping as ActiveCampaign } from '../../constants/integrations/Act
7575
import { CNameMapping as Sprig } from '../../constants/integrations/Sprig/constants';
7676
import { CNameMapping as SpotifyPixel } from '../../constants/integrations/SpotifyPixel/constants';
7777
import { CNameMapping as CommandBar } from '../../constants/integrations/CommandBar/constants';
78+
import { CNameMapping as Ninetailed } from '../../constants/integrations/Ninetailed/constants';
79+
7880
// for sdk side native integration identification
7981
// add a mapping from common names to index.js exported key names as identified by Rudder
8082
const commonNames = {
@@ -113,6 +115,7 @@ const commonNames = {
113115
...Lytics,
114116
...Mixpanel,
115117
...MoEngage,
118+
...Ninetailed,
116119
...Optimizely,
117120
...Pendo,
118121
...PinterestTag,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import Ninetailed from '../../../src/integrations/Ninetailed/browser';
2+
3+
const destinationInfo = {
4+
areTransformationsConnected: false,
5+
destinationId: 'sample-destination-id',
6+
};
7+
8+
describe('Ninetailed Intialization', () => {
9+
let nt;
10+
beforeEach(() => {
11+
nt = new Ninetailed({}, { loglevel: 'DEBUG' }, destinationInfo);
12+
});
13+
afterEach(() => {
14+
jest.restoreAllMocks();
15+
window.ninetailed = undefined;
16+
});
17+
describe(' Is Loaded test Cases', () => {
18+
// when ninetailed is not loaded from webapp
19+
test('isLoaded should return False', () => {
20+
expect(nt.isLoaded()).toBe(false);
21+
});
22+
// when ninetailed is loaded from webapp
23+
test('isLoaded should return True', () => {
24+
// since init call does not have any body so we are intialising the ninetailed object
25+
window.ninetailed = {};
26+
expect(nt.isLoaded()).toBeTruthy();
27+
});
28+
});
29+
describe(' Is Ready test Cases', () => {
30+
// when ninetailed is not loaded from webapp
31+
test('isReady should return False', () => {
32+
expect(nt.isReady()).toBe(false);
33+
});
34+
// when ninetailed is loaded from webapp
35+
test('isReady should return True', () => {
36+
// since init call does not have any body so we are intialising the ninetailed object
37+
window.ninetailed = {};
38+
expect(nt.isReady()).toBeTruthy();
39+
});
40+
});
41+
});
42+
describe('Ninetailed Event APIs', () => {
43+
beforeEach(() => {
44+
window.ninetailed = {};
45+
});
46+
describe('Page', () => {
47+
let nt;
48+
beforeEach(() => {
49+
nt = new Ninetailed({}, { loglevel: 'DEBUG' }, destinationInfo);
50+
window.ninetailed.page = jest.fn();
51+
});
52+
afterAll(() => {
53+
jest.restoreAllMocks();
54+
});
55+
test('send pageview with properties', () => {
56+
const properties = {
57+
category: 'test cat',
58+
path: '/test',
59+
url: 'http://localhost:8080',
60+
referrer: '',
61+
title: 'test page',
62+
testDimension: 'abc',
63+
isRudderEvents: true,
64+
};
65+
66+
nt.page({
67+
message: {
68+
context: {},
69+
properties,
70+
},
71+
});
72+
expect(window.ninetailed.page.mock.calls[0][0]).toEqual(properties);
73+
});
74+
test('send pageview without properties', () => {
75+
nt.page({
76+
message: {
77+
context: {},
78+
},
79+
});
80+
expect(window.ninetailed.page.mock.calls[0][0]).toEqual(undefined);
81+
});
82+
});
83+
describe('Track', () => {
84+
let nt;
85+
beforeEach(() => {
86+
nt = new Ninetailed({}, { loglevel: 'DEBUG' }, destinationInfo);
87+
window.ninetailed.track = jest.fn();
88+
});
89+
afterAll(() => {
90+
jest.restoreAllMocks();
91+
});
92+
test('Testing Track Event with event', () => {
93+
const properties = {
94+
customProp: 'testProp',
95+
checkout_id: 'what is checkout id here??',
96+
event_id: 'purchaseId',
97+
order_id: 'transactionId',
98+
value: 35.0,
99+
shipping: 4.0,
100+
isRudderEvents: true,
101+
};
102+
nt.track({
103+
message: {
104+
context: {},
105+
event: 'Custom',
106+
properties,
107+
},
108+
});
109+
expect(window.ninetailed.track.mock.calls[0][0]).toEqual('Custom');
110+
expect(window.ninetailed.track.mock.calls[0][1]).toEqual(properties);
111+
});
112+
test('Testing Track Event without event', () => {
113+
nt.track({
114+
message: {
115+
context: {},
116+
properties: {},
117+
},
118+
});
119+
expect(window.ninetailed.track).not.toHaveBeenCalledWith();
120+
});
121+
});
122+
describe('Identify', () => {
123+
let nt;
124+
beforeEach(() => {
125+
nt = new Ninetailed({}, { loglevel: 'DEBUG' }, destinationInfo);
126+
window.ninetailed.identify = jest.fn();
127+
});
128+
afterAll(() => {
129+
jest.restoreAllMocks();
130+
});
131+
test('Testing Identify Custom Events', () => {
132+
const traits = {
133+
134+
isRudderEvents: true,
135+
};
136+
nt.identify({
137+
message: {
138+
userId: 'rudder01',
139+
context: {
140+
traits,
141+
},
142+
},
143+
});
144+
expect(window.ninetailed.identify.mock.calls[0][0]).toEqual('rudder01');
145+
expect(window.ninetailed.identify.mock.calls[0][1]).toEqual(traits);
146+
});
147+
});
148+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/* eslint-disable no-underscore-dangle */
2+
/* eslint-disable class-methods-use-this */
3+
import {
4+
NAME,
5+
DISPLAY_NAME,
6+
} from '@rudderstack/analytics-js-common/constants/integrations/Ninetailed/constants';
7+
import Logger from '../../utils/logger';
8+
9+
const logger = new Logger(DISPLAY_NAME);
10+
class Ninetailed {
11+
constructor(config, analytics, destinationInfo) {
12+
if (analytics.logLevel) {
13+
logger.setLogLevel(analytics.logLevel);
14+
}
15+
this.analytics = analytics;
16+
this.name = NAME;
17+
({
18+
shouldApplyDeviceModeTransformation: this.shouldApplyDeviceModeTransformation,
19+
propagateEventsUntransformedOnError: this.propagateEventsUntransformedOnError,
20+
destinationId: this.destinationId,
21+
} = destinationInfo ?? {});
22+
}
23+
24+
init() {
25+
// We expect the customer to load the SDK through NPM package
26+
// Docs: https://docs.ninetailed.io/for-developers/experience-sdk/getting-started
27+
}
28+
29+
isLoaded() {
30+
return !!window.ninetailed;
31+
}
32+
33+
isReady() {
34+
return this.isLoaded();
35+
}
36+
37+
identify(rudderElement) {
38+
const { message } = rudderElement;
39+
const { userId, context } = message;
40+
const userTraits = { ...context?.traits };
41+
// for userId: until we don't pass the id to ninetailed, it will not make server identify call but is accepting the data
42+
window.ninetailed.identify(userId, userTraits);
43+
}
44+
track(rudderElement) {
45+
const { message } = rudderElement;
46+
const { properties, event } = message;
47+
if (!event) {
48+
logger.error('Event name is required');
49+
return;
50+
}
51+
window.ninetailed.track(event, properties);
52+
}
53+
page(rudderElement) {
54+
const { message } = rudderElement;
55+
const { properties } = message;
56+
if (properties) {
57+
properties.url = window.location.href;
58+
window.ninetailed.page(properties);
59+
return;
60+
}
61+
window.ninetailed.page();
62+
}
63+
}
64+
65+
export default Ninetailed;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as Ninetailed } from './browser';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# production
12+
/build
13+
14+
# misc
15+
.DS_Store
16+
.env.local
17+
.env.development.local
18+
.env.test.local
19+
.env.production.local
20+
21+
npm-debug.log*
22+
yarn-debug.log*
23+
yarn-error.log*

0 commit comments

Comments
 (0)