Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 260 additions & 0 deletions spec/src/modules/tracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -14539,4 +14539,264 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => {
expect(tracker.trackProductInsightsAgentAnswerFeedback(requiredParameters)).to.equal(true);
});
});

describe('trackMediaImpressionView', () => {
const requiredParameters = {
bannerAdId: 'banner_ad_id',
placementId: 'placement_id',
};

const optionalParameters = {
analyticsTags: testAnalyticsTag,
};

it('Should respond with a valid response when required parameters are provided', (done) => {
const { tracker } = new ConstructorIO({
apiKey: testApiKey,
fetch: fetchSpy,
mediaServiceUrl: 'https://media-cnstrc.com',
...requestQueueOptions,
});

tracker.on('success', (responseParams) => {
const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy);
// Request
expect(fetchSpy).to.have.been.called;
expect(requestParams).to.have.property('key');
expect(requestParams).to.have.property('i');
expect(requestParams).to.have.property('s');
expect(requestParams).to.have.property('c').to.equal(clientVersion);
expect(requestParams).to.have.property('_dt');
expect(requestParams)
.to.have.property('banner_ad_id')
.to.equal(requiredParameters.bannerAdId);
expect(requestParams)
.to.have.property('placement_id')
.to.equal(requiredParameters.placementId);
validateOriginReferrer(requestParams);

// Response
expect(responseParams).to.have.property('method').to.equal('POST');
expect(responseParams).to.have.property('message');

done();
});

expect(tracker.trackMediaImpressionView(requiredParameters)).to.equal(
true,
);
});

it('Should respond with a valid response when required and optional parameters are provided', (done) => {
const { tracker } = new ConstructorIO({
apiKey: testApiKey,
fetch: fetchSpy,
mediaServiceUrl: 'https://media-cnstrc.com',
...requestQueueOptions,
});

tracker.on('success', (responseParams) => {
const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy);

// Request
expect(fetchSpy).to.have.been.called;
expect(requestParams)
.to.have.property('analytics_tags')
.to.deep.equal(testAnalyticsTag);

// Response
expect(responseParams).to.have.property('method').to.equal('POST');
expect(responseParams).to.have.property('message');

done();
});

expect(
tracker.trackMediaImpressionView(
Object.assign(requiredParameters, optionalParameters),
),
).to.equal(true);
});

it('Should throw an error when invalid parameters are provided', () => {
const { tracker } = new ConstructorIO({ apiKey: testApiKey });

expect(tracker.trackMediaImpressionView([])).to.be.an('error');
});

it('Should throw an error when no parameters are provided', () => {
const { tracker } = new ConstructorIO({ apiKey: testApiKey });

expect(tracker.trackMediaImpressionView()).to.be.an('error');
});

it('Should send along origin_referrer query param if sendReferrerWithTrackingEvents is true', (done) => {
const { tracker } = new ConstructorIO({
apiKey: testApiKey,
fetch: fetchSpy,
sendReferrerWithTrackingEvents: true,
mediaServiceUrl: 'https://media-cnstrc.com',
...requestQueueOptions,
});

tracker.on('success', (responseParams) => {
const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy);

// Request
expect(fetchSpy).to.have.been.called;
validateOriginReferrer(requestParams);

// Response
expect(responseParams).to.have.property('method').to.equal('POST');
expect(responseParams).to.have.property('message').to.equal('ok');

done();
});

expect(tracker.trackMediaImpressionView(requiredParameters)).to.equal(
true,
);
});

it('Should not send along origin_referrer query param if sendReferrerWithTrackingEvents is false', (done) => {
const { tracker } = new ConstructorIO({
apiKey: testApiKey,
fetch: fetchSpy,
sendReferrerWithTrackingEvents: false,
mediaServiceUrl: 'https://media-cnstrc.com',
...requestQueueOptions,
});

tracker.on('success', (responseParams) => {
const requestParams = helpers.extractUrlParamsFromFetch(fetchSpy);

// Request
expect(fetchSpy).to.have.been.called;
expect(requestParams).to.not.have.property('origin_referrer');

// Response
expect(responseParams).to.have.property('method').to.equal('POST');
expect(responseParams).to.have.property('message').to.equal('ok');

done();
});

expect(tracker.trackMediaImpressionView(requiredParameters)).to.equal(
true,
);
});

if (!skipNetworkTimeoutTests) {
it('Should be rejected when network request timeout is provided and reached', (done) => {
const { tracker } = new ConstructorIO({
apiKey: testApiKey,
mediaServiceUrl: 'https://media-cnstrc.com',
...requestQueueOptions,
});

tracker.on('error', ({ message }) => {
expect(message).to.equal(timeoutRejectionMessage);
done();
});

expect(
tracker.trackMediaImpressionView(requiredParameters, { timeout: 10 }),
).to.equal(true);
});

it('Should be rejected when global network request timeout is provided and reached', (done) => {
const { tracker } = new ConstructorIO({
apiKey: testApiKey,
mediaServiceUrl: 'https://media-cnstrc.com',
networkParameters: {
timeout: 20,
},
...requestQueueOptions,
});

tracker.on('error', ({ message }) => {
expect(message).to.equal(timeoutRejectionMessage);
done();
});

expect(tracker.trackMediaImpressionView(requiredParameters)).to.equal(
true,
);
});
}

it('Should not encode body parameters', (done) => {
const specialCharacters = '+[]&';
const userId = `user-id ${specialCharacters}`;
const bannerAdId = `banner_ad_id ${specialCharacters}`;
const { tracker } = new ConstructorIO({
apiKey: testApiKey,
userId,
fetch: fetchSpy,
mediaServiceUrl: 'https://media-cnstrc.com',
...requestQueueOptions,
});

tracker.on('success', (responseParams) => {
const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy);

// Request
expect(fetchSpy).to.have.been.called;
expect(requestParams).to.have.property('ui').to.equal(userId);
expect(requestParams)
.to.have.property('banner_ad_id')
.to.equal(bannerAdId);

// Response
expect(responseParams).to.have.property('method').to.equal('POST');
expect(responseParams).to.have.property('message').to.equal('ok');

done();
});

expect(
tracker.trackMediaImpressionView({ ...requiredParameters, bannerAdId }),
).to.equal(true);
});

it('Should properly transform non-breaking spaces in parameters', (done) => {
const breakingSpaces = '   ';
const userId = `user-id ${breakingSpaces} user-id`;
const bannerAdId = `banner_ad_id ${breakingSpaces} banner_ad_id`;
const bannerAdIdExpected = 'banner_ad_id banner_ad_id';
const userIdExpected = 'user-id user-id';
const { tracker } = new ConstructorIO({
apiKey: testApiKey,
userId,
mediaServiceUrl: 'https://media-cnstrc.com',
fetch: fetchSpy,
...requestQueueOptions,
});

tracker.on('success', (responseParams) => {
const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy);

// Request
expect(fetchSpy).to.have.been.called;
expect(requestParams).to.have.property('ui').to.equal(userIdExpected);
expect(requestParams)
.to.have.property('banner_ad_id')
.to.equal(bannerAdIdExpected);

// Response
expect(responseParams).to.have.property('method').to.equal('POST');
expect(responseParams).to.have.property('message').to.equal('ok');

done();
});

expect(
tracker.trackMediaImpressionView({
...requiredParameters,
userId,
bannerAdId,
}),
).to.equal(true);
});
});
});
3 changes: 3 additions & 0 deletions src/constructorio.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class ConstructorIO {
* @param {string} [parameters.serviceUrl='https://ac.cnstrc.com'] - API URL endpoint
* @param {string} [parameters.quizzesServiceUrl='https://quizzes.cnstrc.com'] - Quizzes API URL endpoint
* @param {string} [parameters.agentServiceUrl='https://agent.cnstrc.com'] - AI Shopping Agent API URL endpoint
* @param {string} [parameters.mediaServiceUrl='https://media-cnstrc.com'] - Media API URL endpoint
* @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.
* @param {array} [parameters.segments] - User segments
* @param {object} [parameters.testCells] - User test cells
Expand Down Expand Up @@ -74,6 +75,7 @@ class ConstructorIO {
quizzesServiceUrl,
agentServiceUrl,
assistantServiceUrl,
mediaServiceUrl,
segments,
testCells,
clientId,
Expand Down Expand Up @@ -121,6 +123,7 @@ class ConstructorIO {
quizzesServiceUrl: (quizzesServiceUrl && quizzesServiceUrl.replace(/\/$/, '')) || 'https://quizzes.cnstrc.com',
agentServiceUrl: (agentServiceUrl && agentServiceUrl.replace(/\/$/, '')) || 'https://agent.cnstrc.com',
assistantServiceUrl: (assistantServiceUrl && assistantServiceUrl.replace(/\/$/, '')) || 'https://assistant.cnstrc.com',
mediaServiceUrl: (mediaServiceUrl && mediaServiceUrl.replace(/\/$/, '')) || 'https://media-cnstrc.com',
sessionId: sessionId || session_id,
clientId: clientId || client_id,
userId,
Expand Down
70 changes: 70 additions & 0 deletions src/modules/tracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,76 @@ class Tracker {
return new Error('parameters are required of type object');
}

/**
* Send media impression view event to API
*
* @function trackMediaImpressionView
* @param {object} parameters - Additional parameters to be sent with request
* @param {string} parameters.bannerAdId - Banner ad identifier
* @param {string} parameters.placementId - Placement identifier
* @param {object} [parameters.analyticsTags] - Pass additional analytics data
* @param {object} [networkParameters] - Parameters relevant to the network request
* @param {number} [networkParameters.timeout] - Request timeout (in milliseconds)
* @returns {(true|Error)}
* @description User viewed a media banner
* @example
* constructorio.tracker.trackMediaImpressionView(
* {
* bannerAdId: 'banner_ad_id',
* placementId: 'placement_id',
* },
* );
*/
trackMediaImpressionView(parameters, networkParameters = {}) {
// Ensure parameters are provided (required)
if (parameters && typeof parameters === 'object' && !Array.isArray(parameters)) {
const baseUrl = new URL(this.options.mediaServiceUrl);

if (!baseUrl.hostname.startsWith('behavior')) {
baseUrl.hostname = `behavior.${baseUrl.hostname}`;
}

const requestPath = `${baseUrl.toString()}v2/ad_behavioral_action/display_ad_view?`;

const bodyParams = {};
const {
bannerAdId,
placementId,
analyticsTags,
} = parameters;

if (!helpers.isNil(bannerAdId)) {
bodyParams.banner_ad_id = bannerAdId;
}

if (!helpers.isNil(placementId)) {
bodyParams.placement_id = placementId;
}

if (!helpers.isNil(analyticsTags)) {
bodyParams.analytics_tags = analyticsTags;
}

const requestURL = `${requestPath}${applyParamsAsString({}, this.options)}`;
const requestMethod = 'POST';
const requestBody = applyParams(bodyParams, { ...this.options, requestMethod });

this.requests.queue(
requestURL,
requestMethod,
requestBody,
networkParameters,
);
this.requests.send();

return true;
}

this.requests.send();

return new Error('parameters are required of type object');
}

/**
* Send recommendation click event to API
*
Expand Down
1 change: 1 addition & 0 deletions src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface ConstructorClientOptions {
serviceUrl?: string;
quizzesServiceUrl?: string;
agentServiceUrl?: string;
mediaServiceUrl?: string;
assistantServiceUrl?: string;
sessionId?: number;
clientId?: string;
Expand Down
7 changes: 7 additions & 0 deletions src/types/tracker.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,5 +436,12 @@ declare class Tracker {
networkParameters?: NetworkParameters
): true | Error;

trackMediaImpressionView(parameters: {
bannerAdId: string;
placementId: string;
analyticsTags?: Record<string, string>;
}, networkParameters?: NetworkParameters
): true | Error;

on(messageType: string, callback: Function): true | Error;
}
Loading