From f3600bc1eaa45fa12279a5cfa2d568fbd59e52c3 Mon Sep 17 00:00:00 2001 From: Tarek Alqaddy Date: Thu, 1 May 2025 01:47:13 +0300 Subject: [PATCH 1/4] Add optional seed item ids to trackRecommendationView method --- spec/src/modules/tracker.js | 116 ++++++++++++++++++++++++++++++++++++ src/modules/tracker.js | 8 +++ src/utils/request-queue.js | 1 - 3 files changed, 124 insertions(+), 1 deletion(-) diff --git a/spec/src/modules/tracker.js b/spec/src/modules/tracker.js index 4d10d3a3..f1859629 100644 --- a/spec/src/modules/tracker.js +++ b/spec/src/modules/tracker.js @@ -4660,6 +4660,122 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { expect(tracker.trackRecommendationView(requiredParameters)).to.equal(true); }); + + it('Should respond with a valid response when seedItemIds is an array', (done) => { + const seedItemIds = ['123'] + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + }); + const requiredParamsWithSeedItemIds = { + seedItemIds, + ...requiredParameters + } + + 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.trackRecommendationView(requiredParamsWithSeedItemIds)).to.equal(true); + }) + + it('Should respond with a valid response and convert seedItemIds to an array if it\'s a string', (done) => { + const seedItemIds = '123' + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + }); + const requiredParamsWithSeedItemIds = { + seedItemIds, + ...requiredParameters + } + + 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.trackRecommendationView(requiredParamsWithSeedItemIds)).to.equal(true); + }) + + it('Should respond with a valid response and omit seed_item_ids if seedItemIds is a number', (done) => { + const seedItemIds = 123 + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + }); + const requiredParamsWithSeedItemIds = { + seedItemIds, + ...requiredParameters + } + + 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.trackRecommendationView(requiredParamsWithSeedItemIds)).to.equal(true); + }) + + it('Should respond with a valid response and omit seed_item_ids if seedItemIds is an object', (done) => { + const seedItemIds = { seedItemIds: '123' } + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + }); + const requiredParamsWithSeedItemIds = { + seedItemIds, + ...requiredParameters + } + + 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.trackRecommendationView(requiredParamsWithSeedItemIds)).to.equal(true); + }) }); describe('trackRecommendationClick', () => { diff --git a/src/modules/tracker.js b/src/modules/tracker.js index e8f0c6ee..1ba21bce 100644 --- a/src/modules/tracker.js +++ b/src/modules/tracker.js @@ -1222,6 +1222,7 @@ class Tracker { * @param {string} [parameters.resultId] - Recommendation result identifier (returned in response from Constructor) * @param {string} [parameters.section="Products"] - Results section * @param {object} [parameters.analyticsTags] - Pass additional analytics data + * @param {string[]|string} [parameters.seedItemIds] - Item ID(s) to be used as seed * @param {object} [networkParameters] - Parameters relevant to the network request * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds) * @returns {(true|Error)} @@ -1258,6 +1259,7 @@ class Tracker { numResultsViewed = num_results_viewed, items, analyticsTags, + seedItemIds, resultCount = result_count || items?.length || 0, } = parameters; @@ -1299,6 +1301,12 @@ class Tracker { bodyParams.analytics_tags = analyticsTags; } + 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)}`; const requestMethod = 'POST'; const requestBody = applyParams(bodyParams, { ...this.options, requestMethod }); diff --git a/src/utils/request-queue.js b/src/utils/request-queue.js index 2a691815..ea92d22c 100644 --- a/src/utils/request-queue.js +++ b/src/utils/request-queue.js @@ -125,7 +125,6 @@ class RequestQueue { signal, }); } - if (request) { this.requestPending = true; From 23e4ad962ad990b95b5c7bab464443cad33c46f3 Mon Sep 17 00:00:00 2001 From: Tarek Alqaddy Date: Thu, 1 May 2025 20:17:43 +0300 Subject: [PATCH 2/4] Lint --- spec/src/modules/tracker.js | 32 ++++++++++++++++---------------- src/modules/tracker.js | 4 ++-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/spec/src/modules/tracker.js b/spec/src/modules/tracker.js index f1859629..36f03935 100644 --- a/spec/src/modules/tracker.js +++ b/spec/src/modules/tracker.js @@ -4662,7 +4662,7 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { }); it('Should respond with a valid response when seedItemIds is an array', (done) => { - const seedItemIds = ['123'] + const seedItemIds = ['123']; const { tracker } = new ConstructorIO({ apiKey: testApiKey, fetch: fetchSpy, @@ -4670,8 +4670,8 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { }); const requiredParamsWithSeedItemIds = { seedItemIds, - ...requiredParameters - } + ...requiredParameters, + }; tracker.on('success', (responseParams) => { const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy); @@ -4688,10 +4688,10 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { }); expect(tracker.trackRecommendationView(requiredParamsWithSeedItemIds)).to.equal(true); - }) + }); it('Should respond with a valid response and convert seedItemIds to an array if it\'s a string', (done) => { - const seedItemIds = '123' + const seedItemIds = '123'; const { tracker } = new ConstructorIO({ apiKey: testApiKey, fetch: fetchSpy, @@ -4699,8 +4699,8 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { }); const requiredParamsWithSeedItemIds = { seedItemIds, - ...requiredParameters - } + ...requiredParameters, + }; tracker.on('success', (responseParams) => { const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy); @@ -4717,10 +4717,10 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { }); expect(tracker.trackRecommendationView(requiredParamsWithSeedItemIds)).to.equal(true); - }) + }); it('Should respond with a valid response and omit seed_item_ids if seedItemIds is a number', (done) => { - const seedItemIds = 123 + const seedItemIds = 123; const { tracker } = new ConstructorIO({ apiKey: testApiKey, fetch: fetchSpy, @@ -4728,8 +4728,8 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { }); const requiredParamsWithSeedItemIds = { seedItemIds, - ...requiredParameters - } + ...requiredParameters, + }; tracker.on('success', (responseParams) => { const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy); @@ -4746,10 +4746,10 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { }); expect(tracker.trackRecommendationView(requiredParamsWithSeedItemIds)).to.equal(true); - }) + }); it('Should respond with a valid response and omit seed_item_ids if seedItemIds is an object', (done) => { - const seedItemIds = { seedItemIds: '123' } + const seedItemIds = { seedItemIds: '123' }; const { tracker } = new ConstructorIO({ apiKey: testApiKey, fetch: fetchSpy, @@ -4757,8 +4757,8 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { }); const requiredParamsWithSeedItemIds = { seedItemIds, - ...requiredParameters - } + ...requiredParameters, + }; tracker.on('success', (responseParams) => { const requestParams = helpers.extractBodyParamsFromFetch(fetchSpy); @@ -4775,7 +4775,7 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { }); expect(tracker.trackRecommendationView(requiredParamsWithSeedItemIds)).to.equal(true); - }) + }); }); describe('trackRecommendationClick', () => { diff --git a/src/modules/tracker.js b/src/modules/tracker.js index 1ba21bce..a962bd2b 100644 --- a/src/modules/tracker.js +++ b/src/modules/tracker.js @@ -1302,9 +1302,9 @@ class Tracker { } if (seedItemIds?.length && typeof seedItemIds === 'string') { - bodyParams.seed_item_ids = [seedItemIds] + bodyParams.seed_item_ids = [seedItemIds]; } else if (seedItemIds?.length && Array.isArray(seedItemIds)) { - bodyParams.seed_item_ids = seedItemIds + bodyParams.seed_item_ids = seedItemIds; } const requestURL = `${requestPath}${applyParamsAsString({}, this.options)}`; From b3d981115fbba5101ef3cf4b1e9e6c8aa6245df7 Mon Sep 17 00:00:00 2001 From: Tarek Alqaddy Date: Thu, 1 May 2025 20:29:11 +0300 Subject: [PATCH 3/4] Add the empty line back --- src/utils/request-queue.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/request-queue.js b/src/utils/request-queue.js index ea92d22c..2a691815 100644 --- a/src/utils/request-queue.js +++ b/src/utils/request-queue.js @@ -125,6 +125,7 @@ class RequestQueue { signal, }); } + if (request) { this.requestPending = true; From 42f69a7259dd5ea8868e4793aeae1fce4e9cc2ac Mon Sep 17 00:00:00 2001 From: Tarek Alqaddy Date: Mon, 5 May 2025 23:40:52 +0300 Subject: [PATCH 4/4] Accept seedItemIds as a number, add tests --- spec/src/modules/tracker.js | 33 +++++++++++++++++++++++++++++++-- src/modules/tracker.js | 6 ++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/spec/src/modules/tracker.js b/spec/src/modules/tracker.js index 36f03935..955bee1e 100644 --- a/spec/src/modules/tracker.js +++ b/spec/src/modules/tracker.js @@ -4690,6 +4690,35 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { expect(tracker.trackRecommendationView(requiredParamsWithSeedItemIds)).to.equal(true); }); + it('Should respond with a valid response and convert seedItemIds to an array if it\'s is a number', (done) => { + const seedItemIds = 123; + const { tracker } = new ConstructorIO({ + apiKey: testApiKey, + fetch: fetchSpy, + ...requestQueueOptions, + }); + const requiredParamsWithSeedItemIds = { + seedItemIds, + ...requiredParameters, + }; + + 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.trackRecommendationView(requiredParamsWithSeedItemIds)).to.equal(true); + }); + it('Should respond with a valid response and convert seedItemIds to an array if it\'s a string', (done) => { const seedItemIds = '123'; const { tracker } = new ConstructorIO({ @@ -4719,8 +4748,8 @@ describe(`ConstructorIO - Tracker${bundledDescriptionSuffix}`, () => { expect(tracker.trackRecommendationView(requiredParamsWithSeedItemIds)).to.equal(true); }); - it('Should respond with a valid response and omit seed_item_ids if seedItemIds is a number', (done) => { - const seedItemIds = 123; + it('Should respond with a valid response and omit seed_item_ids if seedItemIds is null', (done) => { + const seedItemIds = null; const { tracker } = new ConstructorIO({ apiKey: testApiKey, fetch: fetchSpy, diff --git a/src/modules/tracker.js b/src/modules/tracker.js index a962bd2b..2a2b9922 100644 --- a/src/modules/tracker.js +++ b/src/modules/tracker.js @@ -1222,7 +1222,7 @@ class Tracker { * @param {string} [parameters.resultId] - Recommendation result identifier (returned in response from Constructor) * @param {string} [parameters.section="Products"] - Results section * @param {object} [parameters.analyticsTags] - Pass additional analytics data - * @param {string[]|string} [parameters.seedItemIds] - Item ID(s) to be used as seed + * @param {string[]|string|number} [parameters.seedItemIds] - Item ID(s) to be used as seed * @param {object} [networkParameters] - Parameters relevant to the network request * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds) * @returns {(true|Error)} @@ -1301,7 +1301,9 @@ class Tracker { bodyParams.analytics_tags = analyticsTags; } - if (seedItemIds?.length && typeof seedItemIds === 'string') { + 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;