Skip to content

Commit ad061b6

Browse files
authored
[REM-1773] Add media impression tracking event (#406)
* [REM-1773] Add mediaServiceUrl to js module, add tracking event for media impression view and tests * [REM-1773] Remove subdomain from base mediaURL and add it on the behavior call. Remove snake case params handling * [REM-1773] Add types to tracker.d.ts and remove resultId as its not used
1 parent 5ec0ea7 commit ad061b6

File tree

5 files changed

+341
-0
lines changed

5 files changed

+341
-0
lines changed

spec/src/modules/tracker.js

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14539,4 +14539,264 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => {
1453914539
expect(tracker.trackProductInsightsAgentAnswerFeedback(requiredParameters)).to.equal(true);
1454014540
});
1454114541
});
14542+
14543+
describe('trackMediaImpressionView', () => {
14544+
const requiredParameters = {
14545+
bannerAdId: 'banner_ad_id',
14546+
placementId: 'placement_id',
14547+
};
14548+
14549+
const optionalParameters = {
14550+
analyticsTags: testAnalyticsTag,
14551+
};
14552+
14553+
it('Should respond with a valid response when required parameters are provided', (done) => {
14554+
const { tracker } = new ConstructorIO({
14555+
apiKey: testApiKey,
14556+
fetch: fetchSpy,
14557+
mediaServiceUrl: 'https://media-cnstrc.com',
14558+
...requestQueueOptions,
14559+
});
14560+
14561+
tracker.on('success', (responseParams) => {
14562+
const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy);
14563+
// Request
14564+
expect(fetchSpy).to.have.been.called;
14565+
expect(requestParams).to.have.property('key');
14566+
expect(requestParams).to.have.property('i');
14567+
expect(requestParams).to.have.property('s');
14568+
expect(requestParams).to.have.property('c').to.equal(clientVersion);
14569+
expect(requestParams).to.have.property('_dt');
14570+
expect(requestParams)
14571+
.to.have.property('banner_ad_id')
14572+
.to.equal(requiredParameters.bannerAdId);
14573+
expect(requestParams)
14574+
.to.have.property('placement_id')
14575+
.to.equal(requiredParameters.placementId);
14576+
validateOriginReferrer(requestParams);
14577+
14578+
// Response
14579+
expect(responseParams).to.have.property('method').to.equal('POST');
14580+
expect(responseParams).to.have.property('message');
14581+
14582+
done();
14583+
});
14584+
14585+
expect(tracker.trackMediaImpressionView(requiredParameters)).to.equal(
14586+
true,
14587+
);
14588+
});
14589+
14590+
it('Should respond with a valid response when required and optional parameters are provided', (done) => {
14591+
const { tracker } = new ConstructorIO({
14592+
apiKey: testApiKey,
14593+
fetch: fetchSpy,
14594+
mediaServiceUrl: 'https://media-cnstrc.com',
14595+
...requestQueueOptions,
14596+
});
14597+
14598+
tracker.on('success', (responseParams) => {
14599+
const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy);
14600+
14601+
// Request
14602+
expect(fetchSpy).to.have.been.called;
14603+
expect(requestParams)
14604+
.to.have.property('analytics_tags')
14605+
.to.deep.equal(testAnalyticsTag);
14606+
14607+
// Response
14608+
expect(responseParams).to.have.property('method').to.equal('POST');
14609+
expect(responseParams).to.have.property('message');
14610+
14611+
done();
14612+
});
14613+
14614+
expect(
14615+
tracker.trackMediaImpressionView(
14616+
Object.assign(requiredParameters, optionalParameters),
14617+
),
14618+
).to.equal(true);
14619+
});
14620+
14621+
it('Should throw an error when invalid parameters are provided', () => {
14622+
const { tracker } = new ConstructorIO({ apiKey: testApiKey });
14623+
14624+
expect(tracker.trackMediaImpressionView([])).to.be.an('error');
14625+
});
14626+
14627+
it('Should throw an error when no parameters are provided', () => {
14628+
const { tracker } = new ConstructorIO({ apiKey: testApiKey });
14629+
14630+
expect(tracker.trackMediaImpressionView()).to.be.an('error');
14631+
});
14632+
14633+
it('Should send along origin_referrer query param if sendReferrerWithTrackingEvents is true', (done) => {
14634+
const { tracker } = new ConstructorIO({
14635+
apiKey: testApiKey,
14636+
fetch: fetchSpy,
14637+
sendReferrerWithTrackingEvents: true,
14638+
mediaServiceUrl: 'https://media-cnstrc.com',
14639+
...requestQueueOptions,
14640+
});
14641+
14642+
tracker.on('success', (responseParams) => {
14643+
const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy);
14644+
14645+
// Request
14646+
expect(fetchSpy).to.have.been.called;
14647+
validateOriginReferrer(requestParams);
14648+
14649+
// Response
14650+
expect(responseParams).to.have.property('method').to.equal('POST');
14651+
expect(responseParams).to.have.property('message').to.equal('ok');
14652+
14653+
done();
14654+
});
14655+
14656+
expect(tracker.trackMediaImpressionView(requiredParameters)).to.equal(
14657+
true,
14658+
);
14659+
});
14660+
14661+
it('Should not send along origin_referrer query param if sendReferrerWithTrackingEvents is false', (done) => {
14662+
const { tracker } = new ConstructorIO({
14663+
apiKey: testApiKey,
14664+
fetch: fetchSpy,
14665+
sendReferrerWithTrackingEvents: false,
14666+
mediaServiceUrl: 'https://media-cnstrc.com',
14667+
...requestQueueOptions,
14668+
});
14669+
14670+
tracker.on('success', (responseParams) => {
14671+
const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy);
14672+
14673+
// Request
14674+
expect(fetchSpy).to.have.been.called;
14675+
expect(requestParams).to.not.have.property('origin_referrer');
14676+
14677+
// Response
14678+
expect(responseParams).to.have.property('method').to.equal('POST');
14679+
expect(responseParams).to.have.property('message').to.equal('ok');
14680+
14681+
done();
14682+
});
14683+
14684+
expect(tracker.trackMediaImpressionView(requiredParameters)).to.equal(
14685+
true,
14686+
);
14687+
});
14688+
14689+
if (!skipNetworkTimeoutTests) {
14690+
it('Should be rejected when network request timeout is provided and reached', (done) => {
14691+
const { tracker } = new ConstructorIO({
14692+
apiKey: testApiKey,
14693+
mediaServiceUrl: 'https://media-cnstrc.com',
14694+
...requestQueueOptions,
14695+
});
14696+
14697+
tracker.on('error', ({ message }) => {
14698+
expect(message).to.equal(timeoutRejectionMessage);
14699+
done();
14700+
});
14701+
14702+
expect(
14703+
tracker.trackMediaImpressionView(requiredParameters, { timeout: 10 }),
14704+
).to.equal(true);
14705+
});
14706+
14707+
it('Should be rejected when global network request timeout is provided and reached', (done) => {
14708+
const { tracker } = new ConstructorIO({
14709+
apiKey: testApiKey,
14710+
mediaServiceUrl: 'https://media-cnstrc.com',
14711+
networkParameters: {
14712+
timeout: 20,
14713+
},
14714+
...requestQueueOptions,
14715+
});
14716+
14717+
tracker.on('error', ({ message }) => {
14718+
expect(message).to.equal(timeoutRejectionMessage);
14719+
done();
14720+
});
14721+
14722+
expect(tracker.trackMediaImpressionView(requiredParameters)).to.equal(
14723+
true,
14724+
);
14725+
});
14726+
}
14727+
14728+
it('Should not encode body parameters', (done) => {
14729+
const specialCharacters = '+[]&';
14730+
const userId = `user-id ${specialCharacters}`;
14731+
const bannerAdId = `banner_ad_id ${specialCharacters}`;
14732+
const { tracker } = new ConstructorIO({
14733+
apiKey: testApiKey,
14734+
userId,
14735+
fetch: fetchSpy,
14736+
mediaServiceUrl: 'https://media-cnstrc.com',
14737+
...requestQueueOptions,
14738+
});
14739+
14740+
tracker.on('success', (responseParams) => {
14741+
const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy);
14742+
14743+
// Request
14744+
expect(fetchSpy).to.have.been.called;
14745+
expect(requestParams).to.have.property('ui').to.equal(userId);
14746+
expect(requestParams)
14747+
.to.have.property('banner_ad_id')
14748+
.to.equal(bannerAdId);
14749+
14750+
// Response
14751+
expect(responseParams).to.have.property('method').to.equal('POST');
14752+
expect(responseParams).to.have.property('message').to.equal('ok');
14753+
14754+
done();
14755+
});
14756+
14757+
expect(
14758+
tracker.trackMediaImpressionView({ ...requiredParameters, bannerAdId }),
14759+
).to.equal(true);
14760+
});
14761+
14762+
it('Should properly transform non-breaking spaces in parameters', (done) => {
14763+
const breakingSpaces = '   ';
14764+
const userId = `user-id ${breakingSpaces} user-id`;
14765+
const bannerAdId = `banner_ad_id ${breakingSpaces} banner_ad_id`;
14766+
const bannerAdIdExpected = 'banner_ad_id banner_ad_id';
14767+
const userIdExpected = 'user-id user-id';
14768+
const { tracker } = new ConstructorIO({
14769+
apiKey: testApiKey,
14770+
userId,
14771+
mediaServiceUrl: 'https://media-cnstrc.com',
14772+
fetch: fetchSpy,
14773+
...requestQueueOptions,
14774+
});
14775+
14776+
tracker.on('success', (responseParams) => {
14777+
const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy);
14778+
14779+
// Request
14780+
expect(fetchSpy).to.have.been.called;
14781+
expect(requestParams).to.have.property('ui').to.equal(userIdExpected);
14782+
expect(requestParams)
14783+
.to.have.property('banner_ad_id')
14784+
.to.equal(bannerAdIdExpected);
14785+
14786+
// Response
14787+
expect(responseParams).to.have.property('method').to.equal('POST');
14788+
expect(responseParams).to.have.property('message').to.equal('ok');
14789+
14790+
done();
14791+
});
14792+
14793+
expect(
14794+
tracker.trackMediaImpressionView({
14795+
...requiredParameters,
14796+
userId,
14797+
bannerAdId,
14798+
}),
14799+
).to.equal(true);
14800+
});
14801+
});
1454214802
});

src/constructorio.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class ConstructorIO {
4040
* @param {string} [parameters.serviceUrl='https://ac.cnstrc.com'] - API URL endpoint
4141
* @param {string} [parameters.quizzesServiceUrl='https://quizzes.cnstrc.com'] - Quizzes API URL endpoint
4242
* @param {string} [parameters.agentServiceUrl='https://agent.cnstrc.com'] - AI Shopping Agent API URL endpoint
43+
* @param {string} [parameters.mediaServiceUrl='https://media-cnstrc.com'] - Media API URL endpoint
4344
* @param {string} [parameters.assistantServiceUrl='https://assistant.cnstrc.com'] - AI Shopping Assistant API URL endpoint @deprecated This parameter is deprecated and will be removed in a future version. Use parameters.agentServiceUrl instead.
4445
* @param {array} [parameters.segments] - User segments
4546
* @param {object} [parameters.testCells] - User test cells
@@ -74,6 +75,7 @@ class ConstructorIO {
7475
quizzesServiceUrl,
7576
agentServiceUrl,
7677
assistantServiceUrl,
78+
mediaServiceUrl,
7779
segments,
7880
testCells,
7981
clientId,
@@ -121,6 +123,7 @@ class ConstructorIO {
121123
quizzesServiceUrl: (quizzesServiceUrl && quizzesServiceUrl.replace(/\/$/, '')) || 'https://quizzes.cnstrc.com',
122124
agentServiceUrl: (agentServiceUrl && agentServiceUrl.replace(/\/$/, '')) || 'https://agent.cnstrc.com',
123125
assistantServiceUrl: (assistantServiceUrl && assistantServiceUrl.replace(/\/$/, '')) || 'https://assistant.cnstrc.com',
126+
mediaServiceUrl: (mediaServiceUrl && mediaServiceUrl.replace(/\/$/, '')) || 'https://media-cnstrc.com',
124127
sessionId: sessionId || session_id,
125128
clientId: clientId || client_id,
126129
userId,

src/modules/tracker.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,6 +1336,76 @@ class Tracker {
13361336
return new Error('parameters are required of type object');
13371337
}
13381338

1339+
/**
1340+
* Send media impression view event to API
1341+
*
1342+
* @function trackMediaImpressionView
1343+
* @param {object} parameters - Additional parameters to be sent with request
1344+
* @param {string} parameters.bannerAdId - Banner ad identifier
1345+
* @param {string} parameters.placementId - Placement identifier
1346+
* @param {object} [parameters.analyticsTags] - Pass additional analytics data
1347+
* @param {object} [networkParameters] - Parameters relevant to the network request
1348+
* @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
1349+
* @returns {(true|Error)}
1350+
* @description User viewed a media banner
1351+
* @example
1352+
* constructorio.tracker.trackMediaImpressionView(
1353+
* {
1354+
* bannerAdId: 'banner_ad_id',
1355+
* placementId: 'placement_id',
1356+
* },
1357+
* );
1358+
*/
1359+
trackMediaImpressionView(parameters, networkParameters = {}) {
1360+
// Ensure parameters are provided (required)
1361+
if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
1362+
const baseUrl = new URL(this.options.mediaServiceUrl);
1363+
1364+
if (!baseUrl.hostname.startsWith('behavior')) {
1365+
baseUrl.hostname = `behavior.${baseUrl.hostname}`;
1366+
}
1367+
1368+
const requestPath = `${baseUrl.toString()}v2/ad_behavioral_action/display_ad_view?`;
1369+
1370+
const bodyParams = {};
1371+
const {
1372+
bannerAdId,
1373+
placementId,
1374+
analyticsTags,
1375+
} = parameters;
1376+
1377+
if (!helpers.isNil(bannerAdId)) {
1378+
bodyParams.banner_ad_id = bannerAdId;
1379+
}
1380+
1381+
if (!helpers.isNil(placementId)) {
1382+
bodyParams.placement_id = placementId;
1383+
}
1384+
1385+
if (!helpers.isNil(analyticsTags)) {
1386+
bodyParams.analytics_tags = analyticsTags;
1387+
}
1388+
1389+
const requestURL = `${requestPath}${applyParamsAsString({}, this.options)}`;
1390+
const requestMethod = 'POST';
1391+
const requestBody = applyParams(bodyParams, { ...this.options, requestMethod });
1392+
1393+
this.requests.queue(
1394+
requestURL,
1395+
requestMethod,
1396+
requestBody,
1397+
networkParameters,
1398+
);
1399+
this.requests.send();
1400+
1401+
return true;
1402+
}
1403+
1404+
this.requests.send();
1405+
1406+
return new Error('parameters are required of type object');
1407+
}
1408+
13391409
/**
13401410
* Send recommendation click event to API
13411411
*

src/types/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export interface ConstructorClientOptions {
4747
serviceUrl?: string;
4848
quizzesServiceUrl?: string;
4949
agentServiceUrl?: string;
50+
mediaServiceUrl?: string;
5051
assistantServiceUrl?: string;
5152
sessionId?: number;
5253
clientId?: string;

src/types/tracker.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,5 +437,12 @@ declare class Tracker {
437437
networkParameters?: NetworkParameters
438438
): true | Error;
439439

440+
trackMediaImpressionView(parameters: {
441+
bannerAdId: string;
442+
placementId: string;
443+
analyticsTags?: Record<string, string>;
444+
}, networkParameters?: NetworkParameters
445+
): true | Error;
446+
440447
on(messageType: string, callback: Function): true | Error;
441448
}

0 commit comments

Comments
 (0)