-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
currency module #1374
currency module #1374
Conversation
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.
LGTM other than the conflicts that need to be resolved.
cfffc4b
to
61a576b
Compare
resolved merge conflict |
modules/currency.js
Outdated
var currencyRatesLoaded = false; | ||
var adServerCurrency = 'USD'; | ||
|
||
var oldBidResponse; |
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 you make this name more descriptive? I was confused about what it was until I saw line 51.
modules/currency.js
Outdated
|
||
function wrapFunction(fn, context, params) { | ||
return function() { | ||
utils.logInfo('Invoking wrapped function', arguments); |
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.
not sure how helpful these info statements are as they are deep within internal implementation.
modules/currency.js
Outdated
if(bid) { | ||
// execute immediately if the bid is already in the desired currency | ||
if(bid.currency === adServerCurrency) { | ||
return fn.apply(this, arguments); |
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.
does this have the unintended side effect of not invoking bidmanager.addBidResponse
if bid === undefined
?
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.
yes. I just modified this to call original if no bid, that way it can be responsible for all bad bids.
modules/currency.js
Outdated
utils.logInfo('Invoking addBidResponseDecorator function', arguments); | ||
|
||
// default to USD if currency not set | ||
if(bid && !bid.currency) { |
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 add checks here and line 91 at the top ( if(!bid)
) and exit immediately to reduce code complexity.
modules/currency.js
Outdated
} | ||
} catch (e) { | ||
utils.logWarn('Returning NO_BID, getCurrencyConversion threw error: ', e); | ||
params[1] = bidfactory.createBid(STATUS.NO_BID); |
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.
this error condition will mess up the auction logic because it has a new bid.id
not sure it's possible to fix though.
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 I grab it off the original bid and pass it through to the bid factory?
modules/currency.js
Outdated
bidmanager.addBidResponse = addBidResponseDecorator(bidmanager.addBidResponse); | ||
} | ||
|
||
ajax(url, function(response) { |
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.
Should only load this once per page view.
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.
I'll put a check so it won't load them if they're already loaded. However they can still reset the rates and then it'd have to reload them; i don't know why they'd do that, but at least they have that control.
modules/currency.js
Outdated
conversionCache = {}; | ||
currencySupportEnabled = true; | ||
|
||
if(!oldBidResponse) { |
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.
nit: I think the current style is a space after if
but the linter doesn't seem to be complaining about it being mixed. I would just run a fix on the file.
src/prebid.js
Outdated
@@ -6,7 +6,7 @@ import { videoAdUnit, hasNonVideoBidder } from './video'; | |||
import { nativeAdUnit, nativeBidder, hasNonNativeBidder } from './native'; | |||
import './polyfill'; | |||
import { parse as parseURL, format as formatURL } from './url'; | |||
import { isValidePriceConfig } from './cpmBucketManager'; | |||
import { isValidPriceConfig } from './cpmBucketManager'; |
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.
nice catch.
We shouldn't use prebid.aws.rubiconproject.com as the default conversion location. Options:
My preference is for the second option, but don't know how to make that happen. |
61a576b
to
a346de8
Compare
Made updates based on feedback. Awaiting |
Thanks @snapwich for this addition :) If I understand the description correctly the currency module assumes that all SSPs use It doesn't handle cases where an SSP use different currencies? |
Hi @muuki88
That is correct
The bid adapters will have the ability to specify their currency by attaching As a stop-gap measure, I have just added a requested feature that allows you to specify currencies for bidders in the code configuration. It will be used something like this: pbjs.setConfig({
currency: {
adServerCurrency: 'USD',
overrides: {
rubicon: 'GBP'
}
}
}); It should be noted that if the bid adapter does specify the new currency property, this override will be ignored (and log a warning). |
utils.logInfo('Installing addBidResponse decorator for currency module', arguments); | ||
|
||
originalBidResponse = bidmanager.addBidResponse; | ||
bidmanager.addBidResponse = addBidResponseDecorator(bidmanager.addBidResponse); |
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.
Thinking over the issue @snapwich brought up here: I don't think we should support modules which monkey-patch functions in core.
Reason being: I've heard that we expect other people to write their own external modules. If so, then this behavior means that all of Prebid's internal APIs are now external APIs. If we remove a method or change a signature on anything we export, they could reasonably claim that it's a breaking change to the library.
I propose that we deliberately define all the "plugin points" in prebid-core, which are functions which we expect modules to call. For example, registerBidAdapter()
is a plugin point today.
@snapwich @mkendall07 What do you guys think?
Off the top of my head, something like addBidPostprocessor((bid) => bid))
looks like a reasonable "plugin point" for this feature--although I'm not sure it fits with the spirit of the project. I just think that whatever it is, it should be explicit.
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.
If we remove a method or change a signature on anything we export, they could reasonably claim that it's a breaking change to the library.
That's true but the same would be true if we created a plugin system. I do think creating a standardized way to implement a plugin to Prebid.js core would be nice though; although a big undertaking, so I wonder if we should wait until we have a few more use cases other than currency?
In the current state I'm not sure how we could do this without this decorator. I considered using the event system, but that would require mutating the bid objects, which seemed hacky; plus I don't think there would be any way using events to defer the bid response (which is required if currency file hasn't loaded yet).
If there's a better way to do the currently provided functionality I'm open to hearing it. Otherwise, maybe we could put a plugin system on the backlog for the future when we have more modules that require such features and we have a more formal insight into their needs?
It's possible the need for that plugin system is now though if we can't figure out a solution for this module and concurrent auctions.
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.
oh yeah... I agree, a standardized way to implement plugins is way outside of the scope. I'm not even sure it's possible... but if it is, we definitely need more use-cases.
My suggestion would be something like this (not sure if you need sync or async functions... so I described both):
bidAdjustmentsRegistry.js:
function newBidAdjustmentRegistry() {
const adjustments = []
return {
// adjuster should be a function of the type (bid, callback) => void
register(adjuster) {
adjustments.push(adjuster);
},
adjust(bid, callback) {
// execute each function in the queue, and do the callback on the result. Execute callback(bid) the input if no adjustments exist
}
}
}
export const bidAdjustments = newBidAdjustmentRegistry();
In the currency module, replace bidmanager.addBidResponse = addBidResponseDecorator(bidmanager.addBidResponse);
with an equivalent bidAdjustments.register(impl)
. I think this is doable, but correct me if I'm wrong.
In #1421, @jaiminpanchal27 can call bidAdjustments.adjust(bid)
inside the addBidResponse
function. Or bidAdjustments.adjust(bid, addBidResponse)
if it's async.
Does that sound like it'd work?
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.
Hi. I'm relatively new to the project and have followed this discussion with a lot of interest.
We are currently facing the issue of converting currencies as well and this what we are planning to do,
which might be of interest as well.
Use bidderSettings
At this point using the the pbjs.bidderSettings
to update our CPMs. If the currency module would simply be a library to call then integrating this would be a breeze. Our code looks something like this:
export const bidderSettings: prebidjs.IBidderSettings = {
criteo: {
bidCpmAdjustment: (bidCpm, bid) => {
// do the currency calculation here
return bidCpm;
}
},
appnexusAst: {
bidCpmAdjustment: (bidCpm, bid) => {
// do the currency calculation here
return bidCpm;
}
}
};
If the currency module can just be imported like
pbjs.currency.init(); // or even with a setting
const cpmInEuro = pbjs.currency.convert(cpm, 'USD', 'EUR');
General approach for bid responses
Regarding a general plugin architecture. We have had very good experience with redux middlewares.
Basically you they are functions you can chain together. A BidResponseMiddleware
would be a nice
way to abstract over all the stuff that is currently configured somewhere and applied somehow.
Also a middleware could do all the side-effects, which makes these things trivial to test, because
you can just swap them.
Example
A middleware could have the shape of
(bidResponse: BidResponse) => (bidResponse: BidResponse)
By definition a middleware applies some side effects and calls the next
middleware. A middleware can also change the bidResponse, e.g. with currency calculations
or other stuff.
// apply takes a list of middlewares and chains them
const defaultMiddlewares = apply(
new AdServerTargetingMiddleware(pbjs.bidderSettings),
new BidCpmAdjustmentMiddleware(pbjs.bidderSettings),
new MyCustomMiddleware(),
);
It would also be nice to have access to the raw response from the actual SSP to work around
bugs or missing implementations. What do you think?
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.
Thanks for the ideas @muuki88, but we've decided to move forward with the design as noted in #1089, modified slightly to use the new setConfig() approach. We think this has the advantage of preserving the bidCpmAdjustment for bidder-specific gross-vs-net conversions, separating currency which really isn't bidder-specific.
As for debugging, @snapwich has already implemented what we think is a pretty solid approach -- the bidResponse gets two new attributes: originalCpm and originalCurrency. Then cpm and currency are overwritten with the converted values. So you'll be able to access the before/after bidResponse attributes from the console.
94a6136
to
5dfe5d9
Compare
I've updated the pull-request with the latest changes to use The current configuration accepted is: pbjs.setConfig({
currency: {
adServerCurrency: "GBP", // only required field
granularityMultiplier: 1.2,
conversionRateFile: "localhost:3000/testCurrencyRates.json",
bidderCurrencyDefault: {
rubicon: "JPY"
}
}
}); The @dbemiller I think a plugin system would be useful for Prebid.js in general if we want to start working on defining one that could be used in-place of addBidResponse decorator in this pull-request. I don't think it should hold up this request from being merged though as such a system doesn't become quite necessary until you start dealing with multiple extensions. One useful place to start looking: I am familiar with the plug-in system "Tapable" that Webpack uses (and is created by the same author). It might be a bit heavy for Prebid.js but maybe a similar API with less features would be appropriate. |
@snapwich - going to need one more tweak - the default currency rate file will be https://currency.prebid.org/latest.json -- note the HTTPS rather than HTTP. |
@GLStephen - we'll work on this enhancement. Unclear that currency will make Prebid 0.27 - if it does, then this won't make it. But will likely make 0.28 in either case. |
# Conflicts: # src/bidmanager.js # src/prebid.js
@bretg @GLStephen just added an option to specify the conversions manually in the config over an external file. It can be used like this: pbjs.setConfig({
currency: {
adServerCurrency: 'USD',
rates: {
USD: {
JPY: 100
}
}
}
}); |
modules/currency.js
Outdated
|
||
$$PREBID_GLOBAL$$.currency = setConfig; | ||
|
||
const DEFAULT_CURRENCY_RATE_URL = 'https://currency.prebid.org/latest.json'; |
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.
you need to change this to http
for now.
modules/currency.js
Outdated
import bidmanager from 'src/bidmanager'; | ||
import { config } from 'src/config'; | ||
|
||
$$PREBID_GLOBAL$$.currency = setConfig; |
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.
This should be JSDoc'd with types & all. It's a user-facing API.
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.
oops. this is actually a legacy interface from before setConfig
was implemented. good catch, i'll remove it and document the setConfig
usage.
} | ||
} | ||
|
||
function resetCurrency() { |
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.
A reset()
function (almost?) never makes sense. Any caller who wants to do:
var thing = newThing()
...
thing.reset()
can always do:
var thing = newThing()
...
thing = newThing()
instead.
They're functionally equivalent, but the latter is conceptually simpler. It's also easier to test.
To rigorously test an object with a reset()
, you've effectively gotta run all your tests twice--to make sure all the state was reset properly.
To rigorously test an object with a new
, you only need to run them once.
describe('currency', function () { | ||
describe('setConfig', () => { | ||
it('results in currencySupportEnabled = false when currency not configured', () => { | ||
setConfig({}); |
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.
These tests pollute the global state with side-effects. Tests should be side-effect-free.
@@ -153,7 +153,7 @@ export function newConfig() { | |||
function getConfig(...args) { | |||
if (args.length <= 1 && typeof args[0] !== 'function') { | |||
const option = args[0]; | |||
return option ? config[option] : config; | |||
return option ? utils.deepAccess(config, option) : config; |
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.
Update the JSDocs with the new functionality
src/utils.js
Outdated
@@ -677,3 +677,14 @@ export function groupBy(xs, key) { | |||
return rv; | |||
}, {}); | |||
} | |||
|
|||
export function deepAccess(obj, path) { |
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.
This should have unit tests independent from the rest of the code.
* currency module * currency updates from feedback * support bidder currency override * update currency cdn to new prebid.org endpoint * update lint errors in currency * fix currency to use setConfig and change some variable names * updated currency endpiont to be secure * remove extra line in currency * change override name to default in currency * fix typo in price config * add currency.currencyMultiplier usage from setConfig * currencyMultiplier -> granularityMultiplier * allow currency.rates for conversion in currency over a file * changed https to http for now for currency endpoint * added tests for utils.deepAccess * added JSDocs for currency and utils.deepAccess * update config comment to mention deep access for getConfig
The prebid.org documentation for Currency should soon be reviewed and merged. See prebid/prebid.github.io#323 When merged, it will be under http://prebid.org/dev-docs/modules/index.html |
* currency module * currency updates from feedback * support bidder currency override * update currency cdn to new prebid.org endpoint * update lint errors in currency * fix currency to use setConfig and change some variable names * updated currency endpiont to be secure * remove extra line in currency * change override name to default in currency * fix typo in price config * add currency.currencyMultiplier usage from setConfig * currencyMultiplier -> granularityMultiplier * allow currency.rates for conversion in currency over a file * changed https to http for now for currency endpoint * added tests for utils.deepAccess * added JSDocs for currency and utils.deepAccess * update config comment to mention deep access for getConfig
* tag '0.28.0' of https://github.com/prebid/Prebid.js: (27 commits) Prebid 0.28.0 Release Revert "Upgrade sinon to 3.x (prebid#1491)" (prebid#1563) add () for correct order of operations in scaling increments for currency (prebid#1559) AppnexusAst adapter update: Added source and version to request payload (prebid#1555) remove unnecessary spread operator (prebid#1561) Adxcg adapter (prebid#1554) Upgrade sinon to 3.x (prebid#1491) Rename vastPayload to vastXml (prebid#1556) Single-size sizes array now can be taken, too (prebid#1535) Updated the istanbul-instrumenter-loader (prebid#1550) Add AerServ Adapter (prebid#1538) Fixed imports and made adform support aliasing (prebid#1518) Custom granularity fix (prebid#1546) Fix `documentation lint` issues (prebid#1544) Yieldbot adunit bidder params slot name usage fix (prebid#1394) Update serverbid adapter to use smartsync (prebid#1324) Add improvedigitalBidAdapter (prebid#1381) Fix prebid#1533 spring server typo (prebid#1542) userSync is off by default (prebid#1543) currency module (prebid#1374) ...
* currency module * currency updates from feedback * support bidder currency override * update currency cdn to new prebid.org endpoint * update lint errors in currency * fix currency to use setConfig and change some variable names * updated currency endpiont to be secure * remove extra line in currency * change override name to default in currency * fix typo in price config * add currency.currencyMultiplier usage from setConfig * currencyMultiplier -> granularityMultiplier * allow currency.rates for conversion in currency over a file * changed https to http for now for currency endpoint * added tests for utils.deepAccess * added JSDocs for currency and utils.deepAccess * update config comment to mention deep access for getConfig
* currency module * currency updates from feedback * support bidder currency override * update currency cdn to new prebid.org endpoint * update lint errors in currency * fix currency to use setConfig and change some variable names * updated currency endpiont to be secure * remove extra line in currency * change override name to default in currency * fix typo in price config * add currency.currencyMultiplier usage from setConfig * currencyMultiplier -> granularityMultiplier * allow currency.rates for conversion in currency over a file * changed https to http for now for currency endpoint * added tests for utils.deepAccess * added JSDocs for currency and utils.deepAccess * update config comment to mention deep access for getConfig
Type of change
Description of change
Updated currency from #1209 to be in the new module format. Also replaced
pbjs.setConfig
with a currency configuration function; this can be updated later once we solidify on a configuration format for Prebid.js. The module should be included (gulp build --modules=currency
) and then configured like soAdapters specify currency with the
bid.currency
property on the returned bid. The currency module decoratesbidmanager.addBidResponse
to defer sending through bids until the currency conversion file is loaded and their cpms can be converted. Bids that don't require currency conversion are immediately processed.This pull-request also adds an additional optional second parameter to
pbjs.setPriceGranularity
calledcurrencyMultiplier
. This can be used to set the currency conversion rate fromUSD
to the adServerCurrency that you used to generate your line items. Only needed if your ad server line items are in currency other thanUSD
.Other information
Thanks to Tom here at Rubicon Project for the initial implementation and @harpere for some modifications.