-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
sovrn analytics adapter #12
Changes from 42 commits
c0c28d7
d7df108
0700e91
cf0ca21
f89135c
be091fd
af86376
c2f2350
4867c53
28f3e0e
497723b
f90b2e0
2e9e413
74ed7f9
1269a33
55284d5
3c2f8bc
fee1789
cd78968
68a8afa
35b3f4c
b2bc2a4
7da5620
1f5f2a3
6763f3a
ad9e191
f83afca
de65514
bbcd0b9
0d80a5e
cbcf727
e1310df
232aca6
809a11a
eb4e1bf
198ea7e
d17b2ec
0a051c3
5511c2e
9c69f81
aaa288e
9c98a34
6b28055
fea5f03
5405eb2
3ac0641
87901a1
2fc7d16
4280bae
2abd64a
7f34b16
7c82ce5
c5abc46
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
import adapter from 'src/AnalyticsAdapter' | ||
import adaptermanager from 'src/adaptermanager' | ||
import CONSTANTS from 'src/constants.json' | ||
import {ajaxBuilder} from 'src/ajax' | ||
import * as utils from 'src/utils' | ||
import {config} from 'src/config' | ||
|
||
const ajax = ajaxBuilder(0) | ||
|
||
const { | ||
EVENTS: { | ||
AUCTION_END, | ||
BID_REQUESTED, | ||
BID_ADJUSTMENT, | ||
BID_RESPONSE, | ||
BID_WON | ||
} | ||
} = CONSTANTS | ||
|
||
const PBA_URL = 'http://pba-collector-adapter.us-east-2.elasticbeanstalk.com/analytics' | ||
let currentAuctions = {}; | ||
const analyticsType = 'endpoint'; | ||
|
||
let sovrnAnalyticsAdapter = Object.assign(adapter({url: PBA_URL, analyticsType}), { | ||
track({ eventType, args }) { | ||
if (eventType === BID_WON) { | ||
new BidWinner(this.affiliateId, args).send(); | ||
return | ||
} | ||
if (args.auctionId && currentAuctions[args.auctionId] === undefined) { | ||
currentAuctions[args.auctionId] = new AuctionData(this.affiliateId, args.auctionId); | ||
} | ||
switch (eventType) { | ||
case BID_REQUESTED: | ||
currentAuctions[args.auctionId].bidRequested(args) | ||
break | ||
case BID_ADJUSTMENT: | ||
currentAuctions[args.auctionId].originalBid(args) | ||
break | ||
case BID_RESPONSE: | ||
currentAuctions[args.auctionId].adjustedBid(args) | ||
break | ||
case AUCTION_END: | ||
currentAuctions[args.auctionId].send(); | ||
break | ||
} | ||
}, | ||
}) | ||
|
||
sovrnAnalyticsAdapter.getAuctions = function () { | ||
return currentAuctions; | ||
}; | ||
|
||
sovrnAnalyticsAdapter.originEnableAnalytics = sovrnAnalyticsAdapter.enableAnalytics; | ||
|
||
// override enableAnalytics so we can get access to the config passed in from the page | ||
sovrnAnalyticsAdapter.enableAnalytics = function (config) { | ||
let affiliateId = '' | ||
if (config && config.options && config.options.affiliateId) { | ||
affiliateId = config.options.affiliateId; | ||
} else { | ||
utils.logError('Need affiliate Id to log auction results. Please contact a Sovrn representative if you do not know your affiliate Id.') | ||
return | ||
} | ||
sovrnAnalyticsAdapter.affiliateId = affiliateId | ||
sovrnAnalyticsAdapter.originEnableAnalytics(config) // call the base class function | ||
}; | ||
|
||
adaptermanager.registerAnalyticsAdapter({ | ||
adapter: sovrnAnalyticsAdapter, | ||
code: 'sovrn' | ||
}); | ||
|
||
/** Class Representing a Winning Bid */ | ||
class BidWinner { | ||
/** | ||
* Creates a new bid winner | ||
* @param {string} affiliateId - the affiliate id from the analytics config | ||
* @param {*} event - the args object from the auction event | ||
*/ | ||
constructor(affiliateId, event) { | ||
this.body = {} | ||
this.body.prebidVersion = $$PREBID_GLOBAL$$.version | ||
this.body.affiliateId = affiliateId | ||
this.body.winningBid = JSON.parse(JSON.stringify(event)) | ||
this.body.url = utils.getTopWindowLocation().href | ||
this.body.payload = 'winner' | ||
delete this.body.winningBid.ad | ||
} | ||
|
||
/** | ||
* Sends the auction to the the ingest server | ||
*/ | ||
send() { | ||
utils.logInfo(this.body) | ||
ajax( | ||
PBA_URL, | ||
null, | ||
JSON.stringify(this.body), | ||
{ | ||
contentType: 'application/json', | ||
method: 'POST', | ||
} | ||
) | ||
} | ||
} | ||
|
||
/** Class representing an Auction */ | ||
class AuctionData { | ||
/** | ||
* Create a new auction data collector | ||
* @param {string} affiliateId - the affiliate id from the analytics config | ||
* @param {string} auctionId - the auction id from the auction event | ||
*/ | ||
constructor(affiliateId, auctionId) { | ||
this.auction = {} | ||
this.auction.prebidVersion = $$PREBID_GLOBAL$$.version | ||
this.auction.affiliateId = affiliateId | ||
this.auction.auctionId = auctionId | ||
this.auction.payload = 'auction' | ||
this.auction.timeouts = { | ||
buffer: config.getConfig('timeoutBuffer'), | ||
bidder: config.getConfig('bidderTimeout'), | ||
} | ||
this.auction.priceGranularity = config.getConfig('priceGranularity') | ||
this.auction.url = utils.getTopWindowLocation().href | ||
this.auction.requests = [] | ||
this.auction.unsynced = [] | ||
this.dropBidFields = ['auctionId', 'ad', 'requestId', 'bidderCode'] | ||
} | ||
|
||
/** | ||
* Record a bid request event | ||
* @param {*} event - the args object from the auction event | ||
*/ | ||
bidRequested(event) { | ||
delete event.doneCbCallCount | ||
delete event.auctionId | ||
this.auction.requests.push(JSON.parse(JSON.stringify(event))) | ||
} | ||
|
||
/** | ||
* Finds the bid from the auction that the event is associated with | ||
* @param {*} event - the args object from the auction event | ||
* @return {*} - the bid | ||
*/ | ||
findBid(event) { | ||
const bidder = this.auction.requests.find(r => (r.bidderCode === event.bidderCode)) | ||
if (!bidder) { | ||
this.auction.unsynced.push(event) | ||
} | ||
let bid = bidder.bids.find(b => (b.bidId === event.requestId)) | ||
|
||
if (!bid) { | ||
event.unmatched = true | ||
bidder.bids.push(event) | ||
} | ||
return bid | ||
} | ||
|
||
/** | ||
* Records the original bid before any adjustments have been made | ||
* @param {*} event - the args object from the auction event | ||
* NOTE: the bid adjustment occurs before the bid response | ||
* the bid adjustment seems to be the bid ready to be adjusted | ||
*/ | ||
originalBid(event) { | ||
let bid = this.findBid(event) | ||
if (bid) { | ||
Object.assign(bid, JSON.parse(JSON.stringify(event))) | ||
this.dropBidFields.forEach((f) => delete bid[f]) | ||
} | ||
} | ||
|
||
/** | ||
* Replaces original values with adjusted values and records the original values for changed values | ||
* in bid.originalValues | ||
* @param {*} event - the args object from the auction event | ||
*/ | ||
adjustedBid(event) { | ||
let bid = this.findBid(event) | ||
if (bid) { | ||
bid.originalValues = Object.keys(event).reduce((o, k) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have we run any tests with CPM adjustments to verify this is getting populated? I tried running some tests, but never saw anything in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you're right. I don't think the originalValues are getting populated correctly. Working on that now. (and there will be a test for it) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. False alarm. Jon pointed out that the bidadjustment occurs before the bidresponse. I added criteria to the bidadjustment test to verify the original values get populated. I also commented on this since its not obvious. |
||
if (JSON.stringify(bid[k]) !== JSON.stringify(event[k]) && !this.dropBidFields.includes(k)) { | ||
o[k] = bid[k] | ||
bid[k] = event[k] | ||
} | ||
return o | ||
}, {}) | ||
} | ||
} | ||
|
||
/** | ||
* Sends the auction to the the ingest server | ||
*/ | ||
send() { | ||
let maxbid = {cpm: 0} | ||
this.auction.requests.forEach(request => { | ||
request.bids.forEach(bid => { | ||
if (bid.cpm > maxbid.cpm) { | ||
maxbid = bid | ||
} | ||
}) | ||
}) | ||
maxbid.isAuctionWinner = true | ||
ajax( | ||
PBA_URL, | ||
() => { delete currentAuctions[this.auction.auctionId] }, | ||
JSON.stringify(this.auction), | ||
{ | ||
contentType: 'application/json', | ||
method: 'POST', | ||
} | ||
) | ||
} | ||
} | ||
|
||
export default sovrnAnalyticsAdapter; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should make this a configurable option, with a default value to the production URL. This way we can easily test sending data from the adapter to our Dev and QA environments, in addition to our Prod environment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do this in the open source repo? We can make it configurable in our maestro pipeline, I believe. But, I didn't think the open source repo has env configs available for the adapters. Lets discuss.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we can discuss this today.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For whatever this eventually gets set to, it seems like it might not be appropriate to use a region-specific URL. It seems like the open-source should use a painfully generic name, but anything used internally seems like it should be configurable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the sovrn-pba.js pipeline that was initially setup is using
pba.lijit.com
with a certificate. We will be using that (or something similar) once the pipeline is in the new AWS production. That URL is just for the pipeline I spun up because of changes to the data introduced by the adapter.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can default to the prod url and allow passing in an alt url in the enableAnalytics override along with the affillateId.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we know the prod url yet? And cool, I'll add an the option to pass in an alt url.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, not yet. Hope to have the ingest pipeline in TF and running in the next day or two. Then need to work with REO on names and certificates.