From d3f0402dec13baf427249867b25f35a3df985552 Mon Sep 17 00:00:00 2001 From: Enes Kutay SEZEN Date: Tue, 12 Aug 2025 15:27:53 +0300 Subject: [PATCH 1/2] Add support for seedItemIds in trackRecommendationClick --- spec/src/modules/tracker.js | 55 ++++++++++++++++++++++++++++++++++++- src/modules/tracker.js | 13 +++++++++ src/types/tracker.d.ts | 1 + 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/spec/src/modules/tracker.js b/spec/src/modules/tracker.js index e808b818..0b263f2c 100644 --- a/spec/src/modules/tracker.js +++ b/spec/src/modules/tracker.js @@ -5352,6 +5352,59 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { expect(tracker.trackRecommendationClick(requiredParameters)).to.equal(true); }); + + it('Should respond with a valid response when seedItemIds is provided as an array of strings', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + }); + const parametersWithSeedItemIds = { + ...requiredParameters, + seedItemIds: ['item-123', 'item-456', 'item-789'], + }; + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy); + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property('seed_item_ids').to.deep.equal(['item-123', 'item-456', 'item-789']); + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message'); + done(); + }); + expect(tracker.trackRecommendationClick(parametersWithSeedItemIds)).to.equal(true); + }); + + it('Should throw an error when invalid seedItemIds format is provided (number)', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + const parametersWithInvalidSeedItemIds = { + ...requiredParameters, + seedItemIds: 123, + }; + expect(tracker.trackRecommendationClick(parametersWithInvalidSeedItemIds)).to.be.an('error'); + expect(tracker.trackRecommendationClick(parametersWithInvalidSeedItemIds).message).to.equal('seedItemIds must be an array of strings'); + }); + + it('Should throw an error when invalid seedItemIds format is provided (array with non-strings)', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + const parametersWithInvalidSeedItemIds = { + ...requiredParameters, + seedItemIds: ['item-123', 456, 'item-789'], + }; + expect(tracker.trackRecommendationClick(parametersWithInvalidSeedItemIds)).to.be.an('error'); + expect(tracker.trackRecommendationClick(parametersWithInvalidSeedItemIds).message).to.equal('seedItemIds must be an array of strings'); + }); + + it('Should throw an error when invalid seedItemIds format is provided (object)', () => { + const { tracker } = new ConstructorIO({ apiKey: testApiKey }); + const parametersWithInvalidSeedItemIds = { + ...requiredParameters, + seedItemIds: { id: 'item-123' }, + }; + expect(tracker.trackRecommendationClick(parametersWithInvalidSeedItemIds)).to.be.an('error'); + expect(tracker.trackRecommendationClick(parametersWithInvalidSeedItemIds).message).to.equal('seedItemIds must be an array of strings'); + }); }); describe('trackBrowseResultsLoaded', () => { @@ -8385,7 +8438,7 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { it('Should throw an error when providing no messageType parameter', () => { const { tracker } = new ConstructorIO({ apiKey: testApiKey }); - expect(tracker.on(null, () => {})).to.be.an('error'); + expect(tracker.on(null, () => { })).to.be.an('error'); }); it('Should throw an error when providing an invalid callback parameter', () => { diff --git a/src/modules/tracker.js b/src/modules/tracker.js index e6dd639c..1d8e451a 100644 --- a/src/modules/tracker.js +++ b/src/modules/tracker.js @@ -1357,6 +1357,7 @@ class Tracker { * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds) * @param {string} [parameters.slCampaignId] - Pass campaign id of sponsored listing * @param {string} [parameters.slCampaignOwner] - Pass campaign owner of sponsored listing + * @param {string[]} [parameters.seedItemIds] - Item ID(s) of the seed item * @returns {(true|Error)} * @description User clicked an item that appeared within a list of recommended results * @example @@ -1372,6 +1373,7 @@ class Tracker { * strategyId: 'complimentary', * itemId: 'KMH876', * itemName: 'Socks', + * seedItemIds: ['item-123', 'item-456'], * }, * ); */ @@ -1405,6 +1407,7 @@ class Tracker { analyticsTags, slCampaignId, slCampaignOwner, + seedItemIds, } = parameters; if (variationId) { @@ -1463,6 +1466,16 @@ class Tracker { bodyParams.sl_campaign_owner = slCampaignOwner; } + if (seedItemIds) { + // Validate seedItemIds format + if (Array.isArray(seedItemIds) && seedItemIds.every((id) => typeof id === 'string')) { + bodyParams.seed_item_ids = seedItemIds; + } else { + this.requests.send(); + return new Error('seedItemIds must be an array of strings'); + } + } + const requestURL = `${requestPath}${applyParamsAsString({}, this.options)}`; const requestMethod = 'POST'; const requestBody = applyParams(bodyParams, { ...this.options, requestMethod }); diff --git a/src/types/tracker.d.ts b/src/types/tracker.d.ts index ba76b95f..2903e727 100644 --- a/src/types/tracker.d.ts +++ b/src/types/tracker.d.ts @@ -165,6 +165,7 @@ declare class Tracker { analyticsTags?: Record; slCampaignId?: string; slCampaignOwner?: string; + seedItemIds?: string[]; }, networkParameters?: NetworkParameters ): true | Error; From e88c2a06029a94dfb4ddd3319ea233439709ccab Mon Sep 17 00:00:00 2001 From: Enes Kutay SEZEN Date: Wed, 13 Aug 2025 19:06:35 +0300 Subject: [PATCH 2/2] Update the implementation to match the view event --- spec/src/modules/tracker.js | 96 ++++++++++++++++++++++++++++++------- src/modules/tracker.js | 16 +++---- src/types/tracker.d.ts | 2 +- 3 files changed, 87 insertions(+), 27 deletions(-) diff --git a/spec/src/modules/tracker.js b/spec/src/modules/tracker.js index 0b263f2c..fce802ad 100644 --- a/spec/src/modules/tracker.js +++ b/spec/src/modules/tracker.js @@ -5376,34 +5376,96 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { expect(tracker.trackRecommendationClick(parametersWithSeedItemIds)).to.equal(true); }); - it('Should throw an error when invalid seedItemIds format is provided (number)', () => { - const { tracker } = new ConstructorIO({ apiKey: testApiKey }); - const parametersWithInvalidSeedItemIds = { + it('Should respond with a valid response and convert seedItemIds to array when provided as string', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + }); + const parametersWithSeedItemIds = { + ...requiredParameters, + seedItemIds: 'item-123', + }; + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy); + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property('seed_item_ids').to.deep.equal(['item-123']); + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message'); + done(); + }); + expect(tracker.trackRecommendationClick(parametersWithSeedItemIds)).to.equal(true); + }); + + it('Should respond with a valid response and convert seedItemIds to array when provided as number', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + }); + const parametersWithSeedItemIds = { ...requiredParameters, seedItemIds: 123, }; - expect(tracker.trackRecommendationClick(parametersWithInvalidSeedItemIds)).to.be.an('error'); - expect(tracker.trackRecommendationClick(parametersWithInvalidSeedItemIds).message).to.equal('seedItemIds must be an array of strings'); + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy); + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.have.property('seed_item_ids').to.deep.equal(['123']); + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message'); + done(); + }); + expect(tracker.trackRecommendationClick(parametersWithSeedItemIds)).to.equal(true); }); - it('Should throw an error when invalid seedItemIds format is provided (array with non-strings)', () => { - const { tracker } = new ConstructorIO({ apiKey: testApiKey }); - const parametersWithInvalidSeedItemIds = { + it('Should respond with a valid response and omit seed_item_ids when seedItemIds is empty string', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + }); + const parametersWithSeedItemIds = { ...requiredParameters, - seedItemIds: ['item-123', 456, 'item-789'], + seedItemIds: '', }; - expect(tracker.trackRecommendationClick(parametersWithInvalidSeedItemIds)).to.be.an('error'); - expect(tracker.trackRecommendationClick(parametersWithInvalidSeedItemIds).message).to.equal('seedItemIds must be an array of strings'); + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy); + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.not.have.property('seed_item_ids'); + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message'); + done(); + }); + expect(tracker.trackRecommendationClick(parametersWithSeedItemIds)).to.equal(true); }); - it('Should throw an error when invalid seedItemIds format is provided (object)', () => { - const { tracker } = new ConstructorIO({ apiKey: testApiKey }); - const parametersWithInvalidSeedItemIds = { + it('Should respond with a valid response and omit seed_item_ids when seedItemIds is empty array', (done) => { + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + }); + const parametersWithSeedItemIds = { ...requiredParameters, - seedItemIds: { id: 'item-123' }, + seedItemIds: [], }; - expect(tracker.trackRecommendationClick(parametersWithInvalidSeedItemIds)).to.be.an('error'); - expect(tracker.trackRecommendationClick(parametersWithInvalidSeedItemIds).message).to.equal('seedItemIds must be an array of strings'); + tracker.on('success', (responseParams) => { + const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy); + // Request + expect(fetchSpy).to.have.been.called; + expect(requestParams).to.not.have.property('seed_item_ids'); + // Response + expect(responseParams).to.have.property('method').to.equal('POST'); + expect(responseParams).to.have.property('message'); + done(); + }); + expect(tracker.trackRecommendationClick(parametersWithSeedItemIds)).to.equal(true); }); }); diff --git a/src/modules/tracker.js b/src/modules/tracker.js index 1d8e451a..702e3a05 100644 --- a/src/modules/tracker.js +++ b/src/modules/tracker.js @@ -1357,7 +1357,7 @@ class Tracker { * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds) * @param {string} [parameters.slCampaignId] - Pass campaign id of sponsored listing * @param {string} [parameters.slCampaignOwner] - Pass campaign owner of sponsored listing - * @param {string[]} [parameters.seedItemIds] - Item ID(s) of the seed item + * @param {string[]|string|number} [parameters.seedItemIds] - Item ID(s) to be used as seed * @returns {(true|Error)} * @description User clicked an item that appeared within a list of recommended results * @example @@ -1466,14 +1466,12 @@ class Tracker { bodyParams.sl_campaign_owner = slCampaignOwner; } - if (seedItemIds) { - // Validate seedItemIds format - if (Array.isArray(seedItemIds) && seedItemIds.every((id) => typeof id === 'string')) { - bodyParams.seed_item_ids = seedItemIds; - } else { - this.requests.send(); - return new Error('seedItemIds must be an array of strings'); - } + if (typeof seedItemIds === 'number') { + bodyParams.seed_item_ids = [String(seedItemIds)]; + } else if (seedItemIds?.length && typeof seedItemIds === 'string') { + bodyParams.seed_item_ids = [seedItemIds]; + } else if (seedItemIds?.length && Array.isArray(seedItemIds)) { + bodyParams.seed_item_ids = seedItemIds; } const requestURL = `${requestPath}${applyParamsAsString({}, this.options)}`; diff --git a/src/types/tracker.d.ts b/src/types/tracker.d.ts index 2903e727..443bcdca 100644 --- a/src/types/tracker.d.ts +++ b/src/types/tracker.d.ts @@ -165,7 +165,7 @@ declare class Tracker { analyticsTags?: Record; slCampaignId?: string; slCampaignOwner?: string; - seedItemIds?: string[]; + seedItemIds?: string[] | string | number; }, networkParameters?: NetworkParameters ): true | Error;