From 7d20b91619f5cd00557ba6a66f9d84aa9363eac2 Mon Sep 17 00:00:00 2001 From: Toshiyuki Oike <32355076+toshi17@users.noreply.github.com> Date: Thu, 17 Mar 2022 17:49:13 +0900 Subject: [PATCH] Revert "Refactor fluctAnalyticsAdapter." --- modules/fluctAnalyticsAdapter.js | 460 ++++++++++------ .../modules/fluctAnalyticsAdapter_spec.js | 494 ++++++++++++++---- 2 files changed, 673 insertions(+), 281 deletions(-) diff --git a/modules/fluctAnalyticsAdapter.js b/modules/fluctAnalyticsAdapter.js index 50eb04eefb3..f6398567d4c 100644 --- a/modules/fluctAnalyticsAdapter.js +++ b/modules/fluctAnalyticsAdapter.js @@ -1,196 +1,313 @@ -/* eslint-disable no-console */ -/* eslint-disable indent */ -import { ajax } from '../src/ajax.js' -import adapter from '../src/AnalyticsAdapter.js' -import adapterManager from '../src/adapterManager.js' -// import { config } from '../src/config.js' -import { EVENTS } from '../src/constants.json' -import { - logInfo, - logError, - generateUUID, - deepClone, -} from '../src/utils.js' -import find from 'core-js-pure/features/array/find.js' +import { ajax } from '../src/ajax.js'; +import adapter from '../src/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import { config } from '../src/config.js' +import CONSTANTS from '../src/constants.json'; +import * as utils from '../src/utils.js'; +import find from 'core-js-pure/features/array/find.js'; const url = 'https://an.adingo.jp' -/** @typedef {{ad: string, adId: string, adUnitCode: string, adUrl: string, adserverTargeting: any, auctionId: string, bidder: string, bidderCode: string, cpm: number, creativeId: string, currency: string, dealId: string, height: number, mediaType: string, netRevenue: boolean, originalCpm: number, originalCurrency: string, params: any, pbAg: string, pbCg: string, pbDg: string, pbHg: string, pbLg: string, pbMg: string, pbLg: string, requestId: string, requestTimestamp: number, responseTimestamp: number, size: string, source: string, status: string, statusMessage: string, timeToRespond: string, ttl: string, width: number, bidId?: string}} BidResponse */ -/** @typedef {{bidder: string, params: any}} Bid */ -/** @typedef {{code: string, _code?: string, analytics?: {bidder: string, dwid: string}[], bids?: Bid[], mediaTypes: {banner: {name: string, sizes: number[][]}}, sizes: number[][], transactionId: string}} AdUnit */ -/** @typedef {{adUnitCodes: string[], adUnits: AdUnit[], auctionEnd: number, auctionId: string, auctionStatus: string, bidderRequests: any[], bidsReceived: BidResponse[], labels?: string, noBids: BidResponse[], timeout: number, timestamp: number, winningBids: BidResponse[], bids: {[key: string]: BidResponse}[]}} PbAuction */ -/** @typedef {{registered: boolean}} Gpt */ -/** @typedef {{[key: string]: string }} Slots */ +/** + * @typedef {Object} BidResponse + * @property {string} ad //: html tag + * @property {string} adId + * @property {string} adUnitCode //: 'div-gpt-ad-1612148277593-0' + * @property {string} adUrl + * @property {*} adserverTargeting + * @property {string} auctionId + * @property {string} bidder + * @property {string} bidderCode + * @property {number} cpm + * @property {string} creativeId + * @property {string} currency + * @property {string} dealId + * @property {number} height + * @property {string} mediaType + * @property {boolean} netRevenue + * @property {number} originalCpm + * @property {string} originalCurrency + * @property {*} params + * @property {string} pbAg + * @property {string} pbCg + * @property {string} pbDg + * @property {string} pbHg + * @property {string} pbLg + * @property {string} pbMg + * @property {string} pbLg + * @property {string} requestId + * @property {number} requestTimestamp + * @property {number} responseTimestamp + * @property {string} size + * @property {string} source + * @property {string} status + * @property {string} statusMessage + * @property {string} timeToRespond + * @property {string} ttl + * @property {number} width + * @property {string|undefined} bidId //: exists when 'bidTimeout','noBid' event. + * + */ + +/** + * @typedef {Object} Bid + * @property {string} bidder + * @property {*} params + */ + +/** + * @typedef {Object} Analytics + * @property {string} bidder + * @property {string} dwid + */ + +/** + * @typedef {Object} MediaTypes + * @property {Object} banner + * @property {string} banner.name + * @property {Array>} sizes + */ + +/** + * @typedef {Object} AdUnit + * @property {Array} analytics + * @property {Array} bids + * @property {MediaTypes} mediaTypes + * @property {Array>} sizes + * @property {string} transactionId + */ + +/** + * @typedef {Object} PbAuction + * @property {Array} adUnitCodes //: ['div-gpt-ad-1612148277593-0'] + * @property {Array} adUnits //: [{…}] + * @property {number} auctionEnd - timestamp of when auction ended //: 1586675964364 + * @property {string} auctionId - Auction ID of the request this bid responded to + * @property {string} auctionStatus //: 'inProgress' + * @property {Array<*>} bidderRequests //: (2) [{…}, {…}] + * @property {Array} bidsReceived //: [] + * @property {string|undefined} labels //: undefined + * @property {Array} noBids //: [] + * @property {number} timeout //: 3000 + * @property {number} timestamp //: 1586675964364 + * @property {Array} winningBids //: [] + * @property {Object} bids + */ + +/** + * @typedef {Object} Gpt + * @property {boolean} registered + */ + +/** + * @typedef {Object} Cache + * @property {Object.} auctions + * @property {Object.} timeouts + * @property {Gpt} gpt + */ -/** @type {{auctions: {[auctionId: string]: PbAuction}, adUnits: {[adUnitCode: string]: AdUnit}, gpt: Gpt, timeouts: {[auctionId: string]: number}}} */ +/** @type Cache */ const cache = { auctions: {}, - adUnits: {}, - gpt: {}, timeouts: {}, -} + gpt: {}, +}; -/** @type {(id: string) => boolean} */ -const isBrowsiId = (id) => Boolean(id.match(/^browsi_/g)) +/** + * @returns {string|undefined} + */ +const getSiteKey = () => find(config.getConfig('realTimeData.dataProviders') ?? [], provider => provider.name === 'browsi')?.params.siteKey /** - * 各adUnitCodeに対応したadUnitPathを取得する - * @type {() => Slots} - * @sample - * ``` - * { - * 'div-gpt-ad-1629864618640-0': '/62532913/p_fluctmagazine_320x50_surface_15377', - * 'browsi_ad_0_ai_1_rc_0': '/62532913/p_fluctmagazine_320x50_surface_15377' - * } - * ``` + * @param {string} auctionId + * @returns {boolean} */ -const getAdUnitMap = () => window.googletag.pubads().getSlots().reduce((prev, slot) => Object.assign(prev, { [slot.getSlotElementId()]: slot.getAdUnitPath() }), {}) - -/** @type {(_adUnit: AdUnit, adUnits: AdUnit[], slots: Slots) => AdUnit} */ -export const convertReplicatedAdUnit = (_adUnit, adUnits = cache.adUnits, slots = getAdUnitMap()) => { - /** @type {adUnit} */ - const adUnit = deepClone(_adUnit) - /** @type {?string} */ - const adUnitPath = slots[adUnit.code] - if (adUnitPath) { - const { analytics, code, mediaTypes: { banner: { name } } } = - find(Object.values(adUnits), adUnit => Boolean(adUnitPath.match(new RegExp(`${adUnit.mediaTypes.banner.name}$`, 'g')))) - adUnit.analytics = analytics - adUnit._code = code - adUnit.mediaTypes.banner.name = name - } - adUnit.bids = undefined - return adUnit -} +const isBrowsiAuction = (auctionId) => Boolean(auctionId.match(new RegExp(`^${getSiteKey()}`, 'g'))) +/** + * @param {string} divId + * @returns {boolean} + */ +const isBrowsiDivId = (divId) => Boolean(divId.match(/^browsi_ad_/g)) + +/* eslint-disable-next-line compat/compat */ let fluctAnalyticsAdapter = Object.assign( adapter({ url, analyticsType: 'endpoint' }), { track({ eventType, args }) { - logInfo(`[${eventType}] ${Date.now()} :`, args) - try { - switch (eventType) { - case EVENTS.AUCTION_INIT: { - /** @type {PbAuction} */ - let auctionInitEvent = args - cache.auctions[auctionInitEvent.auctionId] = { ...auctionInitEvent, bids: {} } - if (!cache.gpt.registered) { - window.googletag = window.googletag || { cmd: [] } - cache.gpt.registered = true - $$PREBID_GLOBAL$$.adUnits.forEach(adUnit => Object.assign(cache.adUnits, { [adUnit.code]: adUnit })) - } - break - } - case EVENTS.BID_TIMEOUT: { - /** @type {BidResponse[]} */ - let timeoutEvent = args - timeoutEvent.forEach(bid => { - cache.auctions[bid.auctionId].bids[bid.bidId] = { - ...bid, - noBid: true, - prebidWon: false, - bidWon: false, - timeout: true, - } - }) - break - } - case EVENTS.AUCTION_END: { - /** @type {PbAuction} */ - let auctionEndEvent = args - let { adUnitCodes, auctionId, bidsReceived, noBids } = auctionEndEvent - Object.assign(cache.auctions[auctionId], auctionEndEvent, { aidSuffix: isBrowsiId(auctionId) ? generateUUID() : undefined }) - - let prebidWonBidRequestIds = adUnitCodes.map(adUnitCode => - bidsReceived.reduce((highestCpmBid, bid) => - adUnitCode === bid.adUnitCode - ? highestCpmBid.cpm > bid.cpm ? highestCpmBid : bid - : highestCpmBid - , {}).requestId - ); - - [ - ...bidsReceived.map(bid => ({ - ...bid, - noBid: false, - prebidWon: prebidWonBidRequestIds.includes(bid.requestId), - bidWon: false, - timeout: false, - })), - ...noBids.map(bid => ({ - ...bid, - noBid: true, - prebidWon: false, - bidWon: false, - timeout: false, - })), - ].forEach(bid => { - cache.auctions[auctionId].bids[bid.requestId || bid.bidId] = bid - }) - break + utils.logInfo(`[${eventType}] ${Date.now()} :`, args); + switch (eventType) { + case CONSTANTS.EVENTS.AUCTION_INIT: { + /** @type {PbAuction} */ + let auctionInitEvent = args + cache.auctions[auctionInitEvent.auctionId] = { ...auctionInitEvent, bids: {} } + if (!cache.gpt.registered) { + cache.gpt.registered = true; + window.googletag = window.googletag || { cmd: [] }; } - case EVENTS.SET_TARGETING: { - let setTargetingEvent = args - const adUnitCodes = Object.keys(setTargetingEvent) - adUnitCodes.forEach(adUnitCode => { - if (isBrowsiId(adUnitCode)) { - const adUnit = convertReplicatedAdUnit(find($$PREBID_GLOBAL$$.adUnits, adUnit => adUnit.code === adUnitCode)) - Object.assign(cache.adUnits, { [adUnitCode]: adUnit }) - } - }) - /** @type {PbAuction} */ - const auction = find(Object.values(cache.auctions), auction => - auction.adUnitCodes.every(adUnitCode => adUnitCodes.includes(adUnitCode))) - sendMessage(auction.auctionId) - break - } - case EVENTS.BID_WON: { - /** @type {Bid} */ - let bidWonEvent = args - let { auctionId, requestId } = bidWonEvent - Object.assign(cache.auctions[auctionId].bids[requestId], bidWonEvent, { + break; + } + case CONSTANTS.EVENTS.BID_TIMEOUT: { + /** @type {BidResponse[]} */ + let timeoutEvent = args + timeoutEvent.forEach(bid => { + cache.auctions[bid.auctionId].bids[bid.bidId] = { + ...bid, + noBid: true, + prebidWon: false, + bidWon: false, + timeout: true, + } + }) + break; + } + case CONSTANTS.EVENTS.AUCTION_END: { + /** @type {PbAuction} */ + let auctionEndEvent = args + let { adUnitCodes, auctionId, bidsReceived, noBids } = auctionEndEvent + Object.assign(cache.auctions[auctionId], auctionEndEvent) + + let prebidWonBidRequestIds = adUnitCodes.map(adUnitCode => + bidsReceived.reduce((highestCpmBid, bid) => + adUnitCode === bid.adUnitCode + ? highestCpmBid.cpm > bid.cpm ? highestCpmBid : bid + : highestCpmBid + , {}).requestId + ); + + [ + ...bidsReceived.map(bid => ({ + ...bid, noBid: false, - prebidWon: true, - bidWon: true, + prebidWon: prebidWonBidRequestIds.includes(bid.requestId), + bidWon: false, timeout: false, - }) - // clearTimeout(cache.timeouts[auctionId]) - // cache.timeouts[auctionId] = setTimeout(() => { + })), + ...noBids.map(bid => ({ + ...bid, + noBid: true, + prebidWon: false, + bidWon: false, + timeout: false, + })), + ].forEach(bid => { + cache.auctions[auctionId].bids[bid.requestId || bid.bidId] = bid + }) + if (!isBrowsiAuction(auctionId)) { sendMessage(auctionId) - // }, config.getConfig('bidderTimeout')) - break } - default: - break + break; + } + case CONSTANTS.EVENTS.BID_WON: { + /** @type {Bid} */ + let bidWonEvent = args + let { auctionId, requestId } = bidWonEvent + clearTimeout(cache.timeouts[auctionId]); + Object.assign(cache.auctions[auctionId].bids[requestId], bidWonEvent, { + noBid: false, + prebidWon: true, + bidWon: true, + timeout: false, + }) + if (!isBrowsiAuction(auctionId)) { + cache.timeouts[auctionId] = setTimeout(() => { + sendMessage(auctionId); + }, config.getConfig('bidderTimeout') ?? 3000); + } + break; } - } catch (error) { - logError({ eventType, args, error }) + default: + break; } } -}) +}); + +/** + * GPT slotから共通のpathを持つ、browsi_ad_ではないのadUnitCodeを返す + * @param {Object.} slots + * @param {string} adUnitCode + * @returns {string} + */ +export const getAdUnitCodeBeforeReplication = (slots, adUnitCode) => { + /** @type {string} */ + const path = slots[find(Object.keys(slots), slot => { + /** @type {string|null} @example browsi_ad_0_ai_1_rc_ */ + const browsiPrefix = adUnitCode.match(/^browsi_ad_.*_(?=\d*$)/g)?.[0] + return slot === adUnitCode + || slot.match(new RegExp(`^${browsiPrefix}`, 'g'))?.[0] + })] + return find(Object.keys(slots), slot => !isBrowsiDivId(slots[slot]) && slots[slot] === path) ?? adUnitCode +} -/** @type {(auctionId: string) => void} */ +/** + * 各adUnitCodeに対応したadUnitPathを取得する + * @returns {Object.} + * @sample + * ``` + * { + * 'div-gpt-ad-1629864618640-0': '/62532913/p_fluctmagazine_320x50_surface_15377', + * 'browsi_ad_0_ai_1_rc_0': '/62532913/p_fluctmagazine_320x50_surface_15377' + * } + * ``` + */ +const getAdUnitMap = () => googletag.pubads().getSlots().reduce((prev, slot) => Object.assign(prev, { [slot.getSlotElementId()]: slot.getAdUnitPath() }), {}) + +/** + * @param {string|undefined} adUnitCode + * @returns {string|undefined} + */ +export const getBrowsiRefreshCount = (adUnitCode) => adUnitCode?.match(/browsi_.*_(\d*$)/)?.[1] + +/** + * @param {string} auctionId + * @param {Array} adUnits + * @returns {string} + */ +const modifyBrowsiAuctionId = (auctionId, adUnits) => { + /** @type {string|undefined} */ + const reloadCount = getBrowsiRefreshCount(find(adUnits, adUnit => isBrowsiDivId(adUnit.code))?.code) + return auctionId.match(new RegExp(`^${getSiteKey()}`, 'g')) && reloadCount + ? `${auctionId}_${reloadCount}` + : auctionId +} + +/** + * @param {string} auctionId + */ const sendMessage = (auctionId) => { - let { adUnitCodes, auctionEnd, aidSuffix, auctionStatus, bids } = cache.auctions[auctionId] - const adUnits = Object.values(cache.adUnits).filter(adUnit => adUnitCodes.includes(adUnit.code)) - logInfo(adUnits) + let { adUnits, auctionEnd, auctionStatus, bids } = cache.auctions[auctionId] + const slots = getAdUnitMap() + + adUnits = adUnits.map(adUnit => ({ + ...adUnit, + analytics: adUnit.analytics ?? find(Object.values(cache.auctions).flatMap(auction => auction.adUnits), _adUnit => _adUnit.code === getAdUnitCodeBeforeReplication(slots, adUnit.code)).analytics, + bids: undefined + })) - const payload = { - auctionId: aidSuffix ? `${auctionId}_${aidSuffix}` : auctionId, - adUnits: adUnits.map(adUnit => ({ ...adUnit, code: adUnit._code || adUnit.code, _code: undefined })), + /** + * @param {string} adUnitCode + * @param {string} bidder + * @returns {string|null} + */ + const findDwIdByAdUnitCode = (adUnitCode, bidder) => { + const analytics = find(adUnits, adUnit => adUnit.code === adUnitCode)?.analytics + return find(analytics ?? [], obj => obj.bidder === bidder)?.dwid ?? null + } + + let payload = { + auctionId: modifyBrowsiAuctionId(auctionId, adUnits), + adUnits, bids: Object.values(bids).map(bid => { const { noBid, prebidWon, bidWon, timeout, adId, adUnitCode, adUrl, bidder, status, netRevenue, cpm, currency, originalCpm, originalCurrency, requestId, size, source, timeToRespond } = bid - /** @type {AdUnit} */ - const adUnit = find(adUnits, adUnit => adUnit.code === adUnitCode) || {} return { noBid, prebidWon, bidWon, timeout, - dwid: (find(adUnit.analytics || [], param => param.bidder === bidder) || {}).dwid, + dwid: findDwIdByAdUnitCode(adUnitCode, bidder), status, adId, adUrl, - adUnitCode: adUnit._code || adUnit.code, + adUnitCode: getAdUnitCodeBeforeReplication(slots, adUnitCode), bidder, netRevenue, cpm, @@ -210,19 +327,30 @@ const sendMessage = (auctionId) => { timestamp: Date.now(), auctionEnd, auctionStatus, - } - ajax(url, () => logInfo(`[sendMessage] ${Date.now()} :`, payload), JSON.stringify(payload), { contentType: 'application/json', method: 'POST' }) -} + }; + ajax(url, () => utils.logInfo(`[sendMessage] ${Date.now()} :`, payload), JSON.stringify(payload), { contentType: 'application/json', method: 'POST' }); +}; + +window.addEventListener('browsiImpression', (data) => { + const adUnitCode = Object.entries(getAdUnitMap()) + .reduce((prev, [code, path]) => { + return data.detail.adUnit === path && isBrowsiDivId(code) + ? code + : prev + }, '') + const auction = find(Object.values(cache.auctions), auction => auction.adUnitCodes.includes(adUnitCode)) + sendMessage(auction.auctionId) +}) -fluctAnalyticsAdapter.originEnableAnalytics = fluctAnalyticsAdapter.enableAnalytics +fluctAnalyticsAdapter.originEnableAnalytics = fluctAnalyticsAdapter.enableAnalytics; fluctAnalyticsAdapter.enableAnalytics = (config) => { - fluctAnalyticsAdapter.initOptions = config.options - fluctAnalyticsAdapter.originEnableAnalytics(config) -} + fluctAnalyticsAdapter.initOptions = config.options; + fluctAnalyticsAdapter.originEnableAnalytics(config); +}; adapterManager.registerAnalyticsAdapter({ adapter: fluctAnalyticsAdapter, code: 'fluct', -}) +}); -export default fluctAnalyticsAdapter +export default fluctAnalyticsAdapter; diff --git a/test/spec/modules/fluctAnalyticsAdapter_spec.js b/test/spec/modules/fluctAnalyticsAdapter_spec.js index 4bbc8e80c48..7026e471aac 100644 --- a/test/spec/modules/fluctAnalyticsAdapter_spec.js +++ b/test/spec/modules/fluctAnalyticsAdapter_spec.js @@ -1,122 +1,346 @@ import fluctAnalyticsAdapter, { - convertReplicatedAdUnit, + getAdUnitCodeBeforeReplication, + getBrowsiRefreshCount } from '../../../modules/fluctAnalyticsAdapter'; import { expect } from 'chai'; import * as events from 'src/events.js'; -import find from 'core-js-pure/features/array/find.js'; -import { EVENTS } from 'src/constants.json'; +import * as utils from 'src/utils.js' +import CONSTANTS from 'src/constants.json'; +import { config } from 'src/config.js'; import { server } from 'test/mocks/xhr.js'; import * as mockGpt from '../integration/faker/googletag.js'; -import $$PREBID_GLOBAL$$ from '../../../src/prebid'; +import { + setConfig, + addBidResponseHook, +} from 'modules/currency.js'; +// import * as rtdModule from 'modules/rtdModule/index.js'; -const adUnits = [ - { - 'code': 'div-gpt-ad-1587114265584-0', - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ], - [ - 336, - 280 - ] - ], - 'name': 'p_fluctmagazine_320x50_surface_15377' - } - }, - 'analytics': [ - { - 'bidder': 'bidder1', - 'dwid': 'dwid1' - }, - { - 'bidder': 'bidder2', - 'dwid': 'dwid2' - } - ], - 'ext': { - 'device': 'SP' - } - }, - { - 'code': 'browsi_ad_0_ai_1_rc_0', - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ], - [ - 336, - 280 - ] - ] - } - } - } -] - -describe('複製枠のadUnitsをマッピングできる', () => { +describe('正規表現にマッチしている', () => { const slots = { - 'div-gpt-ad-1587114265584-0': '/62532913/p_fluctmagazine_320x50_surface_15377', + 'div-gpt-ad-1629864618640-0': '/62532913/p_fluctmagazine_320x50_surface_15377', 'browsi_ad_0_ai_1_rc_0': '/62532913/p_fluctmagazine_320x50_surface_15377' } - it('adUnitsに複製元codeとdwidを付与できる', () => { - const expected = { - '_code': 'div-gpt-ad-1587114265584-0', - 'analytics': [ - { - 'bidder': 'bidder1', - 'dwid': 'dwid1', - }, - { - 'bidder': 'bidder2', - 'dwid': 'dwid2', - }, - ], - 'bids': undefined, - 'code': 'browsi_ad_0_ai_1_rc_0', - 'mediaTypes': { - 'banner': { - 'name': 'p_fluctmagazine_320x50_surface_15377', - 'sizes': [ - [ - 300, - 250, - ], - [ - 336, - 280, - ] - ] - } - } - } - const adUnit = find(adUnits, adUnit => adUnit.code === 'browsi_ad_0_ai_1_rc_0') - const actual = convertReplicatedAdUnit(adUnit, adUnits, slots) - expect(expected).to.deep.equal(actual) + it('browsi枠のリフレッシュ回数を取得できる', () => { + const browsiAdUnit = getBrowsiRefreshCount('browsi_ad_0_ai_1_rc_0') + expect(browsiAdUnit).to.equal('0') + }) + + it('browsi枠ではないためリフレッシュ回数を取得できない', () => { + const browsiAdUnit = getBrowsiRefreshCount('div-gpt-ad-1629864618640-0') + expect(browsiAdUnit).to.equal(undefined) + }) + + it('browsi枠codeから複製前の枠codeを取得できる', () => { + const adUnitCode = getAdUnitCodeBeforeReplication(slots, 'browsi_ad_0_ai_1_rc_0') + expect(adUnitCode).to.equal('div-gpt-ad-1629864618640-0') + }) + + it('browsi枠ではない枠codeは変化しない', () => { + const adUnitCode = getAdUnitCodeBeforeReplication(slots, 'div-gpt-ad-1629864618640-0') + expect(adUnitCode).to.equal('div-gpt-ad-1629864618640-0') }) }) +const { + EVENTS: { + AUCTION_INIT, + AUCTION_END, + BID_WON, + } +} = CONSTANTS; + const MOCK = { AUCTION_INIT: { 'auctionId': 'eeca6754-525b-4c4c-a697-b06b1fc6c352', 'timestamp': 1635837149209, 'auctionStatus': 'inProgress', - 'adUnits': adUnits + 'adUnits': [ + { + 'code': 'div-gpt-ad-1587114265584-0', + 'bids': [ + { + 'bidder': 'criteo', + 'params': { + 'networkId': '11021' + }, + 'ortb2Imp': { + 'ext': { + 'data': { + 'adserver': { + 'name': 'gam', + 'adslot': '/11598608/sk_sp_article_footeroverlay' + }, + 'pbadslot': '/11598608/sk_sp_article_footeroverlay' + } + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 320, + 100 + ], + [ + 320, + 50 + ] + ], + 'name': 'sk_sp_article_footeroverlay' + } + }, + 'adUnitCode': 'div-gpt-ad-1587114265584-0', + 'transactionId': '411f223f-299e-484f-8ffd-4ce75db58112', + 'sizes': [ + [ + 320, + 100 + ], + [ + 320, + 50 + ] + ], + 'bidId': '22697ff3e5bf7ee', + 'bidderRequestId': '19ef6cc0e7a746c', + 'auctionId': 'eeca6754-525b-4c4c-a697-b06b1fc6c352', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + }, + ], + 'auctionStart': 1635837149209, + 'timeout': 1500, + 'refererInfo': { + 'referer': 'https://www.soccer-king.jp/news/world/esp/20211013/1578369.html?cx_top=topix&pbjs_debug=true', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'https://www.soccer-king.jp/news/world/esp/20211013/1578369.html?cx_top=topix&pbjs_debug=true' + ], + 'canonicalUrl': 'https://www.soccer-king.jp/news/world/esp/20211013/1578369.html' + }, + 'start': 1635837149282 + } + ], + 'noBids': [], + 'bidsReceived': [ + { + 'bidderCode': 'criteo', + 'width': 320, + 'height': 100, + 'statusMessage': 'Bid available', + 'adId': '5c28bc93-b1a0-481b-ae59-0641f316ad1a', + 'requestId': '22697ff3e5bf7ee', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 5.386152744293213, + 'currency': 'JPY', + 'netRevenue': true, + 'ttl': 60, + 'creativeId': '11017985', + 'dealId': '', + 'ad': 'HTML_CODE', + 'originalCpm': 5.386152744293213, + 'originalCurrency': 'JPY', + 'meta': {}, + 'auctionId': 'eeca6754-525b-4c4c-a697-b06b1fc6c352', + 'responseTimestamp': 1635837149448, + 'requestTimestamp': 1635837149232, + 'bidder': 'criteo', + 'adUnitCode': 'div-gpt-ad-1587114265584-0', + 'timeToRespond': 216, + 'pbLg': '5.00', + 'pbMg': '5.30', + 'pbHg': '5.38', + 'pbAg': '5.30', + 'pbDg': '5.35', + 'pbCg': '4.00', + 'size': '320x100', + 'adserverTargeting': { + 'fbs_bidder': 'criteo', + 'fbs_adid': '5c28bc93-b1a0-481b-ae59-0641f316ad1a', + 'fbs_pb': '4.00', + 'fbs_size': '320x100', + 'fbs_source': 'client', + 'fbs_format': 'banner', + 'fbs_adomain': '' + }, + 'status': 'rendered', + 'params': [ + { + 'networkId': '11021' + } + ] + }, + ], + 'winningBids': [], + 'timeout': 1500 }, AUCTION_END: { 'auctionId': 'eeca6754-525b-4c4c-a697-b06b1fc6c352', 'timestamp': 1635837149209, 'auctionEnd': 1635837149840, 'auctionStatus': 'completed', - 'adUnits': adUnits, - 'adUnitCodes': adUnits.map(adUnit => adUnit.code), + 'adUnits': [ + { + 'code': 'div-gpt-ad-1587114265584-0', + 'bids': [ + { + 'bidder': 'criteo', + 'params': { + 'networkId': '11021' + } + } + ], + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 320, + 100 + ], + [ + 320, + 50 + ] + ], + 'name': 'sk_sp_article_footeroverlay' + } + }, + 'analytics': [ + { + 'bidder': 'appnexus', + 'dwid': 'ecnavi_jp_22749357_soccer_king_hb_sp_network' + }, + { + 'bidder': 'criteo', + 'dwid': '1609486' + }, + { + 'bidder': 'ix', + 'dwid': '721766' + }, + { + 'bidder': 'logicad', + 'dwid': 'JMk4_QAoN' + }, + { + 'bidder': 'pubmatic', + 'dwid': '4065991' + } + ], + 'ext': { + 'device': 'SP' + }, + 'sizes': [ + [ + 320, + 100 + ], + [ + 320, + 50 + ] + ], + 'transactionId': '411f223f-299e-484f-8ffd-4ce75db58112', + 'ortb2Imp': { + 'ext': { + 'data': { + 'adserver': { + 'name': 'gam', + 'adslot': '/11598608/sk_sp_article_footeroverlay' + }, + 'pbadslot': '/11598608/sk_sp_article_footeroverlay' + } + } + }, + 'adserverTargeting': { + 'browsiViewability': [ + '1.00' + ] + } + } + ], + 'adUnitCodes': [ + 'div-gpt-ad-1587114265584-0' + ], + 'bidderRequests': [ + { + 'bidderCode': 'criteo', + 'auctionId': 'eeca6754-525b-4c4c-a697-b06b1fc6c352', + 'bidderRequestId': '19ef6cc0e7a746c', + 'bids': [ + { + 'bidder': 'criteo', + 'params': { + 'networkId': '11021' + }, + 'ortb2Imp': { + 'ext': { + 'data': { + 'adserver': { + 'name': 'gam', + 'adslot': '/11598608/sk_sp_article_footeroverlay' + }, + 'pbadslot': '/11598608/sk_sp_article_footeroverlay' + } + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 320, + 100 + ], + [ + 320, + 50 + ] + ], + 'name': 'sk_sp_article_footeroverlay' + } + }, + 'adUnitCode': 'div-gpt-ad-1587114265584-0', + 'transactionId': '411f223f-299e-484f-8ffd-4ce75db58112', + 'sizes': [ + [ + 320, + 100 + ], + [ + 320, + 50 + ] + ], + 'bidId': '22697ff3e5bf7ee', + 'bidderRequestId': '19ef6cc0e7a746c', + 'auctionId': 'eeca6754-525b-4c4c-a697-b06b1fc6c352', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'auctionStart': 1635837149209, + 'timeout': 1500, + 'refererInfo': { + 'referer': 'https://www.soccer-king.jp/news/world/esp/20211013/1578369.html?cx_top=topix&pbjs_debug=true', + 'reachedTop': true, + 'isAmp': false, + 'numIframes': 0, + 'stack': [ + 'https://www.soccer-king.jp/news/world/esp/20211013/1578369.html?cx_top=topix&pbjs_debug=true' + ], + 'canonicalUrl': 'https://www.soccer-king.jp/news/world/esp/20211013/1578369.html' + }, + 'start': 1635837149232 + } + ], 'noBids': [], 'bidsReceived': [ { @@ -171,39 +395,79 @@ const MOCK = { 'winningBids': [], 'timeout': 1500 }, - SET_TARGETING: adUnits.reduce((prev, current) => Object.assign(prev, { [current.code]: {} }), {}) + BID_WON: { + 'bidderCode': 'criteo', + 'width': 320, + 'height': 100, + 'statusMessage': 'Bid available', + 'adId': '5c28bc93-b1a0-481b-ae59-0641f316ad1a', + 'requestId': '22697ff3e5bf7ee', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 5.386152744293213, + 'currency': 'JPY', + 'netRevenue': true, + 'ttl': 60, + 'creativeId': '11017985', + 'dealId': '', + 'ad': 'HTML_CODE', + 'originalCpm': 5.386152744293213, + 'originalCurrency': 'JPY', + 'meta': {}, + 'auctionId': 'eeca6754-525b-4c4c-a697-b06b1fc6c352', + 'responseTimestamp': 1635837149448, + 'requestTimestamp': 1635837149232, + 'bidder': 'criteo', + 'adUnitCode': 'div-gpt-ad-1587114265584-0', + 'timeToRespond': 216, + 'pbLg': '5.00', + 'pbMg': '5.30', + 'pbHg': '5.38', + 'pbAg': '5.30', + 'pbDg': '5.35', + 'pbCg': '4.00', + 'size': '320x100', + 'adserverTargeting': { + 'fbs_bidder': 'criteo', + 'fbs_adid': '5c28bc93-b1a0-481b-ae59-0641f316ad1a', + 'fbs_pb': '4.00', + 'fbs_size': '320x100', + 'fbs_source': 'client', + 'fbs_format': 'banner', + 'fbs_adomain': '' + }, + 'status': 'rendered', + 'params': [ + { + 'networkId': '11021' + } + ] + }, } describe('fluct analytics adapter', () => { + let sandbox beforeEach(() => { - mockGpt.enable() - window.googletag.pubads().setSlots([ - mockGpt.makeSlot({ - code: '/62532913/p_fluctmagazine_320x50_surface_15377', - divId: 'div-gpt-ad-1587114265584-0' - }), - mockGpt.makeSlot({ - code: '/62532913/p_fluctmagazine_320x50_surface_15377', - divId: 'browsi_ad_0_ai_1_rc_0' - }) - ]) - $$PREBID_GLOBAL$$.adUnits = adUnits + sandbox = sinon.sandbox.create() + sandbox.stub(events, 'getEvents').returns([]) + config.resetConfig() + mockGpt.reset() + mockGpt.makeSlot({ code: '/11598608/sk_sp_article_InArticle', divId: 'div-gpt-ad-1587114265584-0' }) + mockGpt.makeSlot({ code: '/11598608/sk_sp_article_InArticle', divId: 'browsi_ad_0_ai_1_rc_0' }) fluctAnalyticsAdapter.enableAnalytics({ provider: 'fluct' }) }) afterEach(() => { - mockGpt.disable() + config.resetConfig() + mockGpt.reset() + sandbox.restore() fluctAnalyticsAdapter.disableAnalytics() }) it('EVENT: `AUCTION_END` 時に値を送信できる', () => { - events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT) - events.emit(EVENTS.AUCTION_END, MOCK.AUCTION_END) - events.emit(EVENTS.SET_TARGETING, MOCK.SET_TARGETING) + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT) + events.emit(AUCTION_END, MOCK.AUCTION_END) expect(server.requests.length).to.equal(1) - - const actual = JSON.parse(server.requests[0].requestBody) - expect(actual.auctionId).to.equal(MOCK.AUCTION_END.auctionId) - expect(actual.adUnits.every(adUnit => adUnit.analytics)).to.equal(true) + expect(JSON.parse(server.requests[0].requestBody).auctionId).to.equal(MOCK.AUCTION_END.auctionId) }) })