Skip to content

Commit

Permalink
Pubx.ai RTD Provider - Initial Release (prebid#11300)
Browse files Browse the repository at this point in the history
* Add pubx RTD module

* Add pubx RTD module documentation

* Add basic tests for pubx RTD module

* Fix the failing tests

* Added logic to fetch floors and set to auction

* Updated setDataToConfig to attach floors data to bidder requests object

* pubx rtd module:
* fetch floor rules
* timeout defaults
* floors config customizations
* pubx on/off from floors response

* move fetch floors to init and also wait for it before auction

* TESTS: update

* replace createFloorsDataForAuction to avoid circular dep - WIP

* reset default floors when no floor response is received

* TESTS: add test

* tag integration - fetch floors api events

* * Remove coonsole.log
* move __pubxFloorRulesPromise__ to window
* Remove unused var error

* TESTS: add getBidRequestData, refactor stubs

* TESTS: setFloorsApiStatus

* TESTS: add fetchFloorRules

* remove useRtd

* make endpoint potional and take pubxId form provider config

* TESTS: use xhr to fakeServer instead of sinon

* make default data optional

* update README

* TEST: update tests

* add integration example

* remove floorProvider from default config

* fix linting

* update readme.md

---------

Co-authored-by: Phaneendra Hegde <phaneendra@pubx.ai>
Co-authored-by: Phaneendra Hegde <pnhegde@users.noreply.github.com>
  • Loading branch information
3 people authored and mefjush committed May 21, 2024
1 parent 490a4a1 commit 4d6c7a3
Show file tree
Hide file tree
Showing 5 changed files with 725 additions and 0 deletions.
113 changes: 113 additions & 0 deletions integrationExamples/gpt/pubxaiRtdProvider_example.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<html>
<head>
<title>Individual Ad Unit Refresh Example</title>
<script async src="//www.googletagservices.com/tag/js/gpt.js"></script>
<script async src="http://localhost:9999/build/dev/prebid.js"></script>
<script>
var sizes = [[300, 250]];
var PREBID_TIMEOUT = 1000;

var googletag = googletag || {};
googletag.cmd = googletag.cmd || [];

var pbjs = pbjs || {};
pbjs.que = pbjs.que || [];

var adUnits = [
{
code: "/19968336/header-bid-tag-0",
mediaTypes: {
banner: {
sizes: sizes,
},
},
bids: [
{
bidder: "appnexus",
params: {
placementId: 13144370,
},
cpm: 2.5,
ad: '<html><body><img src="https://files.prebid.org/creatives/prebid300x250.png"></body></html>',
},
],
},
];

pbjs.que.push(function () {
pbjs.addAdUnits(adUnits);

// -----------------------------------------------
// pubxai RTD module

pbjs.setConfig({
debug: "true",
realTimeData: {
auctionDelay: 2500,
dataProviders: [
{
name: "pubxai",
waitForIt: true,
params: {
pubxId: "74d8356b-35a7-514b-c648-e7e4ad72af63",
endpoint: "http://localhost:3001/floors", // (optional)
floorMin: 3, // (optional)
enforcement: {
bidAdjustment: true,
enforceJS: false,
}, // (optional)
data: {
currency: "USD",
modelVersion: "default",
schema: { fields: ["gptSlot", "mediaType", "size"] },
values: { "*|banner|*": 0.02 },
}, // (optional)
},
},
],
},
});

// -----------------------------------------------
});
</script>

<script>
var slot1;
googletag.cmd.push(function () {
slot1 = googletag
.defineSlot("/19968336/header-bid-tag-0", [[300, 250]], "div-1")
.addService(googletag.pubads());
googletag.pubads().disableInitialLoad();
googletag.pubads().enableSingleRequest();
googletag.enableServices();
});

function refreshBid() {
pbjs.que.push(function () {
pbjs.requestBids({
timeout: PREBID_TIMEOUT,
adUnitCodes: ["/19968336/header-bid-tag-0"],
bidsBackHandler: function () {
pbjs.setTargetingForGPTAsync(["/19968336/header-bid-tag-0"]);
googletag.pubads().refresh([slot1]);
},
});
});
}
</script>
</head>

<body>
<h1>Individual Ad Unit Refresh Example</h1>
<h5>Div-1</h5>
<p><button onclick="refreshBid()">Refresh Ad Unit</button></p>
<div id="div-1">
<script type="text/javascript">
googletag.cmd.push(function () {
googletag.display("div-1");
});
</script>
</div>
</body>
</html>
1 change: 1 addition & 0 deletions modules/.submodules.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
"optimeraRtdProvider",
"oxxionRtdProvider",
"permutiveRtdProvider",
"pubxaiRtdProvider",
"qortexRtdProvider",
"reconciliationRtdProvider",
"relevadRtdProvider",
Expand Down
146 changes: 146 additions & 0 deletions modules/pubxaiRtdProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { ajax } from '../src/ajax.js';
import { config } from '../src/config.js';
import { submodule } from '../src/hook.js';
import { deepAccess } from '../src/utils.js';
/**
* This RTD module has a dependency on the priceFloors module.
* We utilize the createFloorsDataForAuction function from the priceFloors module to incorporate price floors data into the current auction.
*/
import { createFloorsDataForAuction } from './priceFloors.js'; // eslint-disable-line prebid/validate-imports

const MODULE_NAME = 'realTimeData';
const SUBMODULE_NAME = 'pubxai';
window.__pubxFloorRulesPromise__ = null;
export const FloorsApiStatus = Object.freeze({
IN_PROGRESS: 'IN_PROGRESS',
SUCCESS: 'SUCCESS',
ERROR: 'ERROR',
});
export const FLOORS_EVENT_HANDLE = 'floorsApi';
export const FLOORS_END_POINT = 'https://floor.pbxai.com/';
export const FLOOR_PROVIDER = 'PubxFloorProvider';

export const getFloorsConfig = (provider, floorsResponse) => {
const floorsConfig = {
floors: {
enforcement: { floorDeals: true },
data: floorsResponse,
},
};
const { floorMin, enforcement } = deepAccess(provider, 'params');
if (floorMin) {
floorsConfig.floors.floorMin = floorMin;
}
if (enforcement) {
floorsConfig.floors.enforcement = enforcement;
}
return floorsConfig;
};

export const setFloorsConfig = (provider, data) => {
if (data) {
const floorsConfig = getFloorsConfig(provider, data);
config.setConfig(floorsConfig);
window.__pubxLoaded__ = true;
window.__pubxFloorsConfig__ = floorsConfig;
} else {
config.setConfig({ floors: window.__pubxPrevFloorsConfig__ });
window.__pubxLoaded__ = false;
window.__pubxFloorsConfig__ = null;
}
};

export const setDefaultPriceFloors = (provider) => {
const { data } = deepAccess(provider, 'params');
if (data !== undefined) {
data.floorProvider = FLOOR_PROVIDER;
setFloorsConfig(provider, data);
}
};

export const setPriceFloors = async (provider) => {
window.__pubxPrevFloorsConfig__ = config.getConfig('floors');
setDefaultPriceFloors(provider);
return fetchFloorRules(provider)
.then((floorsResponse) => {
setFloorsConfig(provider, floorsResponse);
setFloorsApiStatus(FloorsApiStatus.SUCCESS);
})
.catch((_) => {
setFloorsApiStatus(FloorsApiStatus.ERROR);
});
};

export const setFloorsApiStatus = (status) => {
window.__pubxFloorsApiStatus__ = status;
window.dispatchEvent(
new CustomEvent(FLOORS_EVENT_HANDLE, { detail: { status } })
);
};

export const getUrl = (provider) => {
const { pubxId, endpoint } = deepAccess(provider, 'params');
return `${endpoint || FLOORS_END_POINT}?pubxId=${pubxId}&page=${
window.location.href
}`;
};

export const fetchFloorRules = async (provider) => {
return new Promise((resolve, reject) => {
setFloorsApiStatus(FloorsApiStatus.IN_PROGRESS);
ajax(getUrl(provider), {
success: (responseText, response) => {
try {
if (response && response.response) {
const floorsResponse = JSON.parse(response.response);
resolve(floorsResponse);
} else {
resolve(null);
}
} catch (error) {
reject(error);
}
},
error: (responseText, response) => {
reject(response);
},
});
});
};

const init = (provider) => {
window.__pubxFloorRulesPromise__ = setPriceFloors(provider);
return true;
};

const getBidRequestData = (() => {
let floorsAttached = false;
return (reqBidsConfigObj, onDone) => {
if (!floorsAttached) {
createFloorsDataForAuction(
reqBidsConfigObj.adUnits,
reqBidsConfigObj.auctionId
);
window.__pubxFloorRulesPromise__.then(() => {
createFloorsDataForAuction(
reqBidsConfigObj.adUnits,
reqBidsConfigObj.auctionId
);
onDone();
});
floorsAttached = true;
}
};
})();

export const pubxaiSubmodule = {
name: SUBMODULE_NAME,
init,
getBidRequestData,
};

export const beforeInit = () => {
submodule(MODULE_NAME, pubxaiSubmodule);
};

beforeInit();
68 changes: 68 additions & 0 deletions modules/pubxaiRtdProvider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
## Overview

- Module Name: pubX.ai RTD Provider
- Module Type: RTD Adapter
- Maintainer: phaneendra@pubx.ai

## Description

This RTD module, provided by pubx.ai, is used to set dynamic floors within Prebid.

## Usage

Ensure that the following modules are listed when building Prebid: `priceFloors`.
For example:

```shell
gulp build --modules=priceFloors
```

To compile the RTD module into your Prebid build:

```shell
gulp build --modules=rtdModule,pubxaiRtdProvider
```

To utilize the pubX.ai RTD module, add `realTimeData` with the parameters mentioned below to the Prebid config.

```js
const AUCTION_DELAY = 100;
pbjs.setConfig({
// rest of the config
...,
realTimeData: {
auctionDelay: AUCTION_DELAY,
dataProviders: {
name: "pubxai",
waitForIt: true,
params: {
pubxId: `<publisher_id>`,
endpoint: `<publisher_endpoint>`, // (optional)
floorMin: `<floorMin>`, // (optional)
enforcement: `<enforcement>`, // (optional)
data: `<defaultConfig>` // (optional)
}
}
}
// rest of the config
...,
});
```

## Parameters

| Name | Type | Description | Default |
| :----------------- | :------ | :------------------------------------------------------------- | :------------------------- |
| name | String | Name of the real-time data module | Always `pubxai` |
| waitForIt | Boolean | Should be `true` if an `auctionDelay` is defined (optional) | `false` |
| params | Object | | |
| params.pubxId | String | Publisher ID | |
| params.endpoint | String | URL to retrieve floor data (optional) | `https://floor.pbxai.com/` |
| params.floorMin | Number | Minimum CPM floor (optional) | `None` |
| params.enforcement | Object | Enforcement behavior within the Price Floors Module (optional) | `None` |
| params.data | Object | Default floor data provided by pubX.ai (optional) | `None` |

## What Should Change in the Bid Request?

There are no direct changes in the bid request due to our RTD module, but floor configuration will be set using the price floors module. These changes will be reflected in adunit bids or bidder requests as floor data.

Loading

0 comments on commit 4d6c7a3

Please sign in to comment.