From f1d0b4bc73bd3807a0cc0d4293f90a6fa412e345 Mon Sep 17 00:00:00 2001 From: "Munehiro.Taguchi" Date: Thu, 30 Jan 2025 09:59:54 +0900 Subject: [PATCH] Add Allox Analytics Adapter --- modules/alloxAnalyticsAdapter.js | 90 ++++++++++++ modules/alloxAnalyticsAdapter.md | 23 +++ .../modules/alloxAnalyticsAdapter_spec.js | 132 ++++++++++++++++++ 3 files changed, 245 insertions(+) create mode 100644 modules/alloxAnalyticsAdapter.js create mode 100644 modules/alloxAnalyticsAdapter.md create mode 100644 test/spec/modules/alloxAnalyticsAdapter_spec.js diff --git a/modules/alloxAnalyticsAdapter.js b/modules/alloxAnalyticsAdapter.js new file mode 100644 index 00000000000..562e378f577 --- /dev/null +++ b/modules/alloxAnalyticsAdapter.js @@ -0,0 +1,90 @@ +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import { EVENTS } from '../src/constants.js'; +import { MODULE_TYPE_ANALYTICS } from '../src/activities/modules.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { + isEmpty, + triggerPixel +} from '../src/utils.js'; + +const analyticsType = 'endpoint'; +const url = 'URL_TO_SERVER_ENDPOINT'; +const PROVIDER_NAME = 'allox'; + +const TRACKER_TYPE = { + IMP: 1, + LOSE_NOTICE_WHEN_ALLOX_WIN: 101, + LOSE_NOTICE_WHEN_ALLOX_LOSE: 102 +}; + +const LURL_REG = { + CPM: /\$\{ALLOX:AUCTION_PRICE\}/g, + CURRENCY: /\$\{ALLOX:AUCTION_CURRENCY\}/g +}; + +export const STORAGE_KEY = '__allox_trackers'; +export const storage = getStorageManager({ moduleType: MODULE_TYPE_ANALYTICS, moduleName: PROVIDER_NAME }); + +const alloxAnalytics = Object.assign( + adapter({ url, analyticsType }), { + track({ eventType, args }) { + switch (eventType) { + case EVENTS.BID_WON: + this.onBidWon(args); + break; + } + }, + onBidWon(bid) { + if (storage.localStorageIsEnabled()) { + const trackersValue = storage.getDataFromLocalStorage(STORAGE_KEY); + const trackers = trackersValue ? JSON.parse(trackersValue) : {}; + + if (bid.adUnitId in trackers) { + if (bid.bidder === PROVIDER_NAME) { + this.requestLurlFromTrackers(bid.trackers, TRACKER_TYPE.LOSE_NOTICE_WHEN_ALLOX_WIN); + } else { + const trackersBid = trackers[bid.adUnitId]; + if (trackersBid.lurl) { + this.sendLurl(trackersBid.lurl, bid); + }; + if (trackersBid.trackers) { + this.requestLurlFromTrackers(trackersBid.trackers, TRACKER_TYPE.LOSE_NOTICE_WHEN_ALLOX_LOSE, bid); + }; + }; + } + } + }, + sendLurl(url, wonBid) { + if (wonBid) { + const lurl = url + .replace(LURL_REG.CPM, wonBid.originalCpm) + .replace(LURL_REG.CURRENCY, wonBid.originalCurrency); + triggerPixel(lurl); + } else { + triggerPixel(url); + }; + }, + requestLurlFromTrackers(trackers, trackerType, wonBid) { + const lurlTracker = trackers.filter(tracker => tracker.type === trackerType); + if (!isEmpty(lurlTracker)) { + lurlTracker.forEach(tracker => { + this.sendLurl(tracker.url, wonBid); + }); + }; + } + } +); + +alloxAnalytics.originEnableAnalytics = alloxAnalytics.enableAnalytics; + +alloxAnalytics.enableAnalytics = function (config) { + alloxAnalytics.originEnableAnalytics(config); +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: alloxAnalytics, + code: PROVIDER_NAME, +}); + +export default alloxAnalytics; diff --git a/modules/alloxAnalyticsAdapter.md b/modules/alloxAnalyticsAdapter.md new file mode 100644 index 00000000000..c9c2b98b6ff --- /dev/null +++ b/modules/alloxAnalyticsAdapter.md @@ -0,0 +1,23 @@ +# Overview + +```txt +Module Name: allox Analytics Adapter +Module Type: Analytics Adapter +Maintainer: mi-allox-devbot@ml.nttdocomo.com +``` + +# About + +The Allox Analytics Adapter collects and transmits bidding data to Allox's internal analytics server. + +This includes bids that lost within Allox's system as well as cases where Allox lost in the overall auction. + +The collected data is used to analyze auction performance and optimize bidding strategies. + +# Example Configuration + +```js +pbjs.enableAnalytics({ + provider: 'allox' +}); +``` diff --git a/test/spec/modules/alloxAnalyticsAdapter_spec.js b/test/spec/modules/alloxAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..3b7418a0bd3 --- /dev/null +++ b/test/spec/modules/alloxAnalyticsAdapter_spec.js @@ -0,0 +1,132 @@ +import alloxAnalyticsAdapter, { storage, STORAGE_KEY } from 'modules/alloxAnalyticsAdapter.js'; +import { expect } from 'chai'; +import adapterManager from 'src/adapterManager.js'; +import { EVENTS } from 'src/constants.js'; +import * as utils from 'src/utils.js'; + +let events = require('src/events'); +let sinon = require('sinon'); + +describe('allox analytics adapter', function () { + let adUnitId = '9361d2b7-38da-4900-9713-0bc765f7b36b'; + let mockTrackers = { + [adUnitId]: { + trackers: [ + { + type: 1, + url: 'https://alxl-s.allox-s.allox.d2c.ne.jp/529833ce55314b19e8796116/imp', + method: 'GET' + }, + { + type: 1, + url: 'https://alxl-s.allox-s.allox.d2c.ne.jp/prebid/imp', + method: 'GET' + }, + { + type: 101, + url: 'https://alxl-s.allox-s.allox.d2c.ne.jp/529833ce55314b19e8796116/lose_101', + method: 'GET' + }, + { + type: 102, + url: 'https://alxl-s.allox-s.allox.d2c.ne.jp/xdp/v1/notify/lose_102?wprice=${ALLOX:AUCTION_PRICE}&wcur=${ALLOX:AUCTION_CURRENCY}', + method: 'GET' + } + ], + lurl: 'https://alxl-s.allox-s.allox.d2c.ne.jp/xdp/v1/notify/lose?wprice=${ALLOX:AUCTION_PRICE}&wcur=${ALLOX:AUCTION_CURRENCY}', + date: 1736236376718 + } + }; + let wonBid; + let triggerPixelStub; + let replaceMacro = (url) => { + return url.replace('${ALLOX:AUCTION_PRICE}', wonBid.originalCpm).replace('${ALLOX:AUCTION_CURRENCY}', wonBid.originalCurrency); + }; + + before(function () { + storage.setDataInLocalStorage(STORAGE_KEY, JSON.stringify(mockTrackers)); + }); + + beforeEach(function () { + wonBid = { + 'adId': '11a0ac97879de06', + 'adUnitId': adUnitId, + 'mediaType': 'banner', + 'requestId': '10feb9ccd60c8e9', + 'cpm': 20, + 'creativeId': '529833ce55314b19e8796116_1385706446', + 'currency': 'JPY', + 'netRevenue': true, + 'ttl': 300, + 'auctionId': '1e221e41-c811-462b-8def-fea6ecc4d5eb', + 'statusMessage': 'Bid available', + 'responseTimestamp': 1736988970347, + 'requestTimestamp': 1736988970123, + 'bidder': 'allox', + 'adUnitCode': 'banner-test', + 'timeToRespond': 218, + 'size': '300x250', + 'status': 'rendered', + 'trackers': mockTrackers[adUnitId].trackers, + 'originalCpm': 20, + 'originalCurrency': 'JPY', + 'lurl': 'https://alxl-s.allox-s.allox.d2c.ne.jp/xdp/v1/notify/lose?wprice=${ALLOX:AUCTION_PRICE}&wcur=${ALLOX:AUCTION_CURRENCY}', + }; + adapterManager.enableAnalytics({ + provider: 'allox' + }); + sinon.stub(events, 'getEvents').returns([]); + sinon.spy(alloxAnalyticsAdapter, 'onBidWon'); + triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + }); + + afterEach(function () { + utils.triggerPixel.restore(); + events.getEvents.restore(); + alloxAnalyticsAdapter.onBidWon.restore(); + alloxAnalyticsAdapter.disableAnalytics(); + }); + + describe('allox bid adapter with bid response', function () { + describe('win allox', function() { + it('should fire tracker type 101 url on bid won', function() { + events.emit(EVENTS.BID_WON, wonBid); + const type101Tracker = mockTrackers[adUnitId].trackers.find(tracker => tracker.type === 101); + expect(triggerPixelStub.args[0]).to.include(type101Tracker.url); + expect(triggerPixelStub.callCount).to.equal(1); + }); + }); + + describe('lose allox', function() { + it('should fire allox bid response lurl on bid won', function() { + wonBid.bidder = 'otherBidder'; + events.emit(EVENTS.BID_WON, wonBid); + const lurl = replaceMacro(wonBid.lurl); + expect(triggerPixelStub.args[0]).to.include(lurl); + expect(triggerPixelStub.callCount).to.equal(2); + }); + + it('should fire tracker type 102 url on bid won', function() { + wonBid.bidder = 'otherBidder'; + events.emit(EVENTS.BID_WON, wonBid); + const type102Tracker = mockTrackers[adUnitId].trackers.find(tracker => tracker.type === 102); + const lurl = replaceMacro(type102Tracker.url); + expect(triggerPixelStub.args[1]).to.include(lurl); + expect(triggerPixelStub.callCount).to.equal(2); + }); + }); + }); + + describe('allox bid adapter without bid response', function () { + describe('lose allox', function() { + it('should fire tracker type 102 url on bid won', function() { + wonBid.bidder = 'otherBidder'; + events.emit(EVENTS.BID_WON, wonBid); + const type102Tracker = mockTrackers[adUnitId].trackers.find(tracker => tracker.type === 102); + const lurl = replaceMacro(type102Tracker.url); + expect(triggerPixelStub.args[1]).to.include(lurl); + expect(triggerPixelStub.callCount).to.equal(2); + }); + }); + }); +});