diff --git a/modules/qortexRtdProvider.js b/modules/qortexRtdProvider.js index 79f2c400dfd..3505f64789a 100644 --- a/modules/qortexRtdProvider.js +++ b/modules/qortexRtdProvider.js @@ -84,7 +84,7 @@ function onAuctionEndEvent (data, config, t) { .then(result => { logMessage('Qortex analytics event sent') }) - .catch(e => logWarn(e?.message)) + .catch(e => logWarn(e.message)) } } @@ -93,7 +93,7 @@ function onAuctionEndEvent (data, config, t) { * @returns {Promise} ortb Content object */ export function getContext () { - if (qortexSessionInfo.currentSiteContext === null) { + if (!qortexSessionInfo.currentSiteContext) { const pageUrlObject = { pageUrl: qortexSessionInfo.indexData?.pageUrl ?? '' } logMessage('Requesting new context data'); return new Promise((resolve, reject) => { @@ -145,7 +145,7 @@ export function getGroupConfig () { */ export function sendAnalyticsEvent(eventType, subType, data) { if (qortexSessionInfo.analyticsUrl !== null) { - if (shouldSendAnalytics()) { + if (shouldSendAnalytics(data)) { const analtyicsEventObject = generateAnalyticsEventObject(eventType, subType, data) logMessage('Sending qortex analytics event'); return new Promise((resolve, reject) => { @@ -153,8 +153,8 @@ export function sendAnalyticsEvent(eventType, subType, data) { success() { resolve(); }, - error(error) { - reject(new Error(error)); + error(e, x) { + reject(new Error('Returned error status code: ' + x.status)); } } ajax(qortexSessionInfo.analyticsUrl, callbacks, JSON.stringify(analtyicsEventObject), {contentType: 'application/json'}) @@ -183,7 +183,7 @@ export function generateAnalyticsEventObject(eventType, subType, data) { } /** - * Creates page index data for Qortex analysis + * Determines API host for Qortex * @param qortexUrlBase api url from config or default * @returns {string} Qortex analytics host url */ @@ -205,15 +205,19 @@ export function addContextToRequests (reqBidsConfig) { if (qortexSessionInfo.currentSiteContext === null) { logWarn('No context data received at this time'); } else { - const fragment = { site: {content: qortexSessionInfo.currentSiteContext} } - if (qortexSessionInfo.bidderArray?.length > 0) { - qortexSessionInfo.bidderArray.forEach(bidder => mergeDeep(reqBidsConfig.ortb2Fragments.bidder, {[bidder]: fragment})) - saveContextAdded(reqBidsConfig, qortexSessionInfo.bidderArray); - } else if (!qortexSessionInfo.bidderArray) { - mergeDeep(reqBidsConfig.ortb2Fragments.global, fragment); - saveContextAdded(reqBidsConfig); + if (checkPercentageOutcome(qortexSessionInfo.groupConfig?.prebidBidEnrichmentPercentage)) { + const fragment = { site: {content: qortexSessionInfo.currentSiteContext} } + if (qortexSessionInfo.bidderArray?.length > 0) { + qortexSessionInfo.bidderArray.forEach(bidder => mergeDeep(reqBidsConfig.ortb2Fragments.bidder, {[bidder]: fragment})) + saveContextAdded(reqBidsConfig); + } else if (!qortexSessionInfo.bidderArray) { + mergeDeep(reqBidsConfig.ortb2Fragments.global, fragment); + saveContextAdded(reqBidsConfig); + } else { + logWarn('Config contains an empty bidders array, unable to determine which bids to enrich'); + } } else { - logWarn('Config contains an empty bidders array, unable to determine which bids to enrich'); + saveContextAdded(reqBidsConfig, true); } } } @@ -273,8 +277,7 @@ export function initializeBidEnrichment() { } }) .catch((e) => { - const errorStatus = e.message; - logWarn('Returned error status code: ' + errorStatus) + logWarn('Returned error status code: ' + e.message) }) } } @@ -302,10 +305,13 @@ export function initializeModuleData(config) { return qortexSessionInfo; } -export function saveContextAdded(reqBids, bidders = null) { +export function saveContextAdded(reqBids, skipped = false) { const id = reqBids.auctionId; - const contextBidders = bidders ?? Array.from(new Set(reqBids.adUnits.flatMap(adunit => adunit.bids.map(bid => bid.bidder)))) - qortexSessionInfo.pageAnalysisData.contextAdded[id] = contextBidders; + const contextBidders = qortexSessionInfo.bidderArray ?? Array.from(new Set(reqBids.adUnits.flatMap(adunit => adunit.bids.map(bid => bid.bidder)))) + qortexSessionInfo.pageAnalysisData.contextAdded[id] = { + bidders: contextBidders, + contextSkipped: skipped + }; } export function setContextData(value) { @@ -316,6 +322,10 @@ export function setGroupConfigData(value) { qortexSessionInfo.groupConfig = value } +export function getContextAddedEntry (id) { + return qortexSessionInfo?.pageAnalysisData?.contextAdded[id] +} + function generateSessionId() { const randomInt = window.crypto.getRandomValues(new Uint32Array(1)); const currentDateTime = Math.floor(Date.now() / 1000); @@ -323,23 +333,32 @@ function generateSessionId() { } function attachContextAnalytics (data) { - let qxData = {}; - let qxDataAdded = false; - if (qortexSessionInfo?.pageAnalysisData?.contextAdded[data.auctionId]) { - qxData = qortexSessionInfo.currentSiteContext; - qxDataAdded = true; + const contextAddedEntry = getContextAddedEntry(data.auctionId); + if (contextAddedEntry) { + data.qortexContext = qortexSessionInfo.currentSiteContext ?? {}; + data.qortexContextBidders = contextAddedEntry?.bidders; + data.qortexContextSkipped = contextAddedEntry?.contextSkipped; + return data; + } else { + logMessage(`Auction ${data.auctionId} did not interact with qortex bid enrichment`) + return null; } - data.qortexData = qxData; - data.qortexDataAdded = qxDataAdded; - return data; } -function shouldSendAnalytics() { - const analyticsPercentage = qortexSessionInfo.groupConfig?.prebidReportingPercentage ?? 0; +function checkPercentageOutcome(percentageValue) { + const analyticsPercentage = percentageValue ?? 0; const randomInt = Math.random().toFixed(5) * 100; return analyticsPercentage > randomInt; } +function shouldSendAnalytics(data) { + if (data) { + return checkPercentageOutcome(qortexSessionInfo.groupConfig?.prebidReportingPercentage) + } else { + return false; + } +} + function shouldAllowBidEnrichment() { if (qortexSessionInfo.bidEnrichmentDisabled) { logWarn('Bid enrichment disabled at prebid config') diff --git a/test/spec/modules/qortexRtdProvider_spec.js b/test/spec/modules/qortexRtdProvider_spec.js index b1a4195fb37..c1ae25e6104 100644 --- a/test/spec/modules/qortexRtdProvider_spec.js +++ b/test/spec/modules/qortexRtdProvider_spec.js @@ -14,7 +14,8 @@ import { initializeModuleData, setGroupConfigData, saveContextAdded, - initializeBidEnrichment + initializeBidEnrichment, + getContextAddedEntry } from '../../../modules/qortexRtdProvider'; import {server} from '../../mocks/xhr.js'; import { cloneDeep } from 'lodash'; @@ -47,6 +48,13 @@ describe('qortexRtdProvider', () => { bidders: validBidderArray } } + const invalidApiUrlModuleConfig = { + params: { + groupId: defaultGroupId, + apiUrl: 'test123', + bidders: validBidderArray + } + } const emptyModuleConfig = { params: {} } @@ -95,6 +103,7 @@ describe('qortexRtdProvider', () => { groupId: defaultGroupId, active: true, prebidBidEnrichment: true, + prebidBidEnrichmentPercentage: 100, prebidReportingPercentage: 100 } const validGroupConfigResponse = JSON.stringify(validGroupConfigResponseObj); @@ -107,6 +116,14 @@ describe('qortexRtdProvider', () => { } const inactiveGroupConfigResponse = JSON.stringify(inactiveGroupConfigResponseObj); + const noEnrichmentGroupConfigResponseObj = { + groupId: defaultGroupId, + active: true, + prebidBidEnrichment: true, + prebidBidEnrichmentPercentage: 0, + prebidReportingPercentage: 100 + } + const reqBidsConfig = { auctionId: '1234', adUnits: [{ @@ -281,6 +298,16 @@ describe('qortexRtdProvider', () => { server.requests[0].respond(200, responseHeaders, contextResponse); }) + it('will log message call callback if context data has already been collected', (done) => { + setContextData(contextResponseObj); + module.getBidRequestData(reqBidsConfig, callbackSpy); + setTimeout(() => { + expect(server.requests.length).to.be.eql(0); + expect(logMessageSpy.calledWith('Adding Content object from existing context data')).to.be.true; + done(); + }, 250) + }) + it('will catch and log error and fire callback', (done) => { module.getBidRequestData(reqBidsConfig, callbackSpy); server.requests[0].respond(404, responseHeaders, JSON.stringify({})); @@ -301,6 +328,18 @@ describe('qortexRtdProvider', () => { } module.getBidRequestData(reqBidsConfig, cb); }) + + it('Logs warning for network error', (done) => { + saveContextAdded(reqBidsConfig); + const testData = {auctionId: reqBidsConfig.auctionId, data: 'data'}; + module.onAuctionEndEvent(testData); + server.requests[0].respond(500, responseHeaders, JSON.stringify({})); + setTimeout(() => { + expect(logWarnSpy.calledWith('Returned error status code: 500')).to.be.eql(true); + done(); + }, 200) + }) + it('will not request context if prebid disable toggle is true', (done) => { initializeModuleData(bidEnrichmentDisabledModuleConfig); const cb = function () { @@ -391,6 +430,27 @@ describe('qortexRtdProvider', () => { }) describe('addContextToRequests', () => { + let testReqBids; + beforeEach(() => { + setGroupConfigData(validGroupConfigResponseObj); + testReqBids = { + auctionId: '1234', + adUnits: [{ + bids: [ + { bidder: 'qortex' } + ] + }], + ortb2Fragments: { + bidder: {}, + global: {} + } + } + }) + + afterEach(() => { + setGroupConfigData(null); + }) + it('logs error if no data was retrieved from get context call', () => { initializeModuleData(validModuleConfig); addContextToRequests(reqBidsConfig); @@ -400,6 +460,16 @@ describe('qortexRtdProvider', () => { expect(reqBidsConfig.ortb2Fragments.bidder).to.be.eql({}); }) + it('saves context added entry with skipped flag if valid request does not meet threshold', () => { + initializeModuleData(validModuleConfig); + setContextData(contextResponseObj.content); + setGroupConfigData(noEnrichmentGroupConfigResponseObj); + addContextToRequests(reqBidsConfig); + const contextAdded = getContextAddedEntry(reqBidsConfig.auctionId); + expect(contextAdded).to.not.be.null; + expect(contextAdded.contextSkipped).to.eql(true); + }) + it('adds site.content only to global ortb2 when bidders array is omitted', () => { const omittedBidderArrayConfig = cloneDeep(validModuleConfig); delete omittedBidderArrayConfig.params.bidders;