diff --git a/spec/src/modules/pia.js b/spec/src/modules/pia.js index 99d4f125..779420e2 100644 --- a/spec/src/modules/pia.js +++ b/spec/src/modules/pia.js @@ -145,6 +145,62 @@ describe(`ConstructorIO - Pia${bundledDescriptionSuffix}`, () => { }); }); + it('Should pass features as query parameters when provided', () => { + const features = { pia_v2: true }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getSuggestedQuestions(validItemId, { features }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('features').to.deep.equal({ pia_v2: 'true' }); + }); + }); + + it('Should pass feature_variants as query parameters when provided', () => { + const featureVariants = { pia_v2: 'control' }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getSuggestedQuestions(validItemId, { featureVariants }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('feature_variants').to.deep.equal({ pia_v2: 'control' }); + }); + }); + + it('Should pass pre_filter_expression as a query parameter when provided', () => { + const preFilterExpression = { brand: 'apple' }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getSuggestedQuestions(validItemId, { preFilterExpression }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('pre_filter_expression').to.equal(JSON.stringify(preFilterExpression)); + }); + }); + + it('Should pass qs as a query parameter when qsParam is provided', () => { + const qsParam = { new_qs_param: 'test_value' }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getSuggestedQuestions(validItemId, { qsParam }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('qs').to.equal(JSON.stringify(qsParam)); + }); + }); + it('Should be rejected if response is malformed', () => { const malformedFetch = () => Promise.resolve({ ok: true, @@ -283,6 +339,95 @@ describe(`ConstructorIO - Pia${bundledDescriptionSuffix}`, () => { }); }); + it('Should pass features as query parameters when provided', function () { + this.timeout(10000); + const features = { pia_v2: true }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getAnswerResults(validItemId, validQuestion, { features }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('features').to.deep.equal({ pia_v2: 'true' }); + }); + }); + + it('Should pass feature_variants as query parameters when provided', function () { + this.timeout(10000); + const featureVariants = { pia_v2: 'control' }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getAnswerResults(validItemId, validQuestion, { featureVariants }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('feature_variants').to.deep.equal({ pia_v2: 'control' }); + }); + }); + + it('Should pass pre_filter_expression as a query parameter when provided', function () { + this.timeout(10000); + const preFilterExpression = { brand: 'apple' }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getAnswerResults(validItemId, validQuestion, { preFilterExpression }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('pre_filter_expression').to.equal(JSON.stringify(preFilterExpression)); + }); + }); + + it('Should pass guard as a query parameter when provided', function () { + this.timeout(10000); + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getAnswerResults(validItemId, validQuestion, { guard: true }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('guard').to.equal('true'); + }); + }); + + it('Should pass fmt_options as query parameters when provided', function () { + this.timeout(10000); + const fmtOptions = { groups_max_depth: 2 }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getAnswerResults(validItemId, validQuestion, { fmtOptions }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('fmt_options').to.deep.equal({ groups_max_depth: '2' }); + }); + }); + + it('Should pass qs as a query parameter when qsParam is provided', function () { + this.timeout(10000); + const qsParam = { new_qs_param: 'test_value' }; + const { agent: { pia } } = new ConstructorIO({ + apiKey: piaApiKey, + fetch: fetchSpy, + }); + + return pia.getAnswerResults(validItemId, validQuestion, { qsParam }).then(() => { + const requestedUrlParams = helpers.extractUrlParamsFromFetch(fetchSpy); + + expect(requestedUrlParams).to.have.property('qs').to.equal(JSON.stringify(qsParam)); + }); + }); + it('Should be rejected if response is malformed', () => { const malformedFetch = () => Promise.resolve({ ok: true, diff --git a/src/modules/pia.js b/src/modules/pia.js index f48a69e4..596fc29c 100644 --- a/src/modules/pia.js +++ b/src/modules/pia.js @@ -39,7 +39,17 @@ function createPiaUrl(itemId, parameters, options, questionPath) { } if (parameters) { - const { threadId, variationId, numResults } = parameters; + const { + threadId, + variationId, + numResults, + features, + featureVariants, + preFilterExpression, + qsParam, + guard, + fmtOptions, + } = parameters; if (threadId) { queryParams.thread_id = threadId; @@ -52,6 +62,30 @@ function createPiaUrl(itemId, parameters, options, questionPath) { if (!helpers.isNil(numResults)) { queryParams.num_results = numResults; } + + if (features) { + queryParams.features = features; + } + + if (featureVariants) { + queryParams.feature_variants = featureVariants; + } + + if (preFilterExpression) { + queryParams.pre_filter_expression = JSON.stringify(preFilterExpression); + } + + if (qsParam) { + queryParams.qs = JSON.stringify(qsParam); + } + + if (!helpers.isNil(guard)) { + queryParams.guard = guard; + } + + if (fmtOptions) { + queryParams.fmt_options = fmtOptions; + } } queryParams._dt = Date.now(); @@ -85,6 +119,9 @@ class Pia { * @param {string} [parameters.threadId] - Thread ID for conversation context (UUID) * @param {string} [parameters.variationId] - Variation ID of the item * @param {number} [parameters.numResults] - Number of suggested questions to return + * @param {object} [parameters.features] - Feature toggles for A/B testing + * @param {object} [parameters.featureVariants] - Feature variant overrides + * @param {object} [parameters.preFilterExpression] - Faceting expression to scope results * @param {object} [networkParameters] - Parameters relevant to the network request * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds) * @returns {Promise} @@ -92,6 +129,7 @@ class Pia { * constructorio.agent.pia.getSuggestedQuestions('item-123', { * variationId: 'variation-456', * numResults: 3, + * features: { my_feature: true }, * }); */ getSuggestedQuestions(itemId, parameters, networkParameters = {}) { @@ -136,6 +174,11 @@ class Pia { * @param {object} [parameters] - Additional parameters to refine result set * @param {string} [parameters.threadId] - Thread ID for conversation context (UUID) * @param {string} [parameters.variationId] - Variation ID of the item + * @param {object} [parameters.features] - Feature toggles for A/B testing + * @param {object} [parameters.featureVariants] - Feature variant overrides + * @param {object} [parameters.preFilterExpression] - Faceting expression to scope results + * @param {boolean} [parameters.guard] - Enable or disable moderation check + * @param {object} [parameters.fmtOptions] - Response format options * @param {object} [networkParameters] - Parameters relevant to the network request * @param {number} [networkParameters.timeout] - Request timeout (in milliseconds) * @returns {Promise} diff --git a/src/types/pia.d.ts b/src/types/pia.d.ts index b7f676d4..3e69f692 100644 --- a/src/types/pia.d.ts +++ b/src/types/pia.d.ts @@ -1,5 +1,7 @@ import { ConstructorClientOptions, + FilterExpression, + FmtOptions, NetworkParameters, Item, } from '.'; @@ -10,15 +12,22 @@ export interface PiaQuestion { value: string; } -export interface PiaSuggestedQuestionsParameters { +export interface PiaBaseParameters { threadId?: string; variationId?: string; + features?: Record; + featureVariants?: Record; + preFilterExpression?: FilterExpression; + qsParam?: Record; +} + +export interface PiaSuggestedQuestionsParameters extends PiaBaseParameters { numResults?: number; } -export interface PiaAnswerResultsParameters { - threadId?: string; - variationId?: string; +export interface PiaAnswerResultsParameters extends PiaBaseParameters { + guard?: boolean; + fmtOptions?: FmtOptions; } export interface PiaSuggestedQuestionsResponse {