Skip to content
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

PubWise.io Analytics Module Update - SPOT Support, Module Rules & Minor Features #5677

Merged
merged 9 commits into from
Sep 9, 2020
305 changes: 260 additions & 45 deletions modules/pubwiseAnalyticsAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ import {ajax} from '../src/ajax.js';
import adapter from '../src/AnalyticsAdapter.js';
import adapterManager from '../src/adapterManager.js';
import CONSTANTS from '../src/constants.json';
import { getStorageManager } from '../src/storageManager.js';
const utils = require('../src/utils.js');

const storage = getStorageManager();

/****
* PubWise.io Analytics
* Contact: [email protected]
Expand All @@ -17,30 +14,52 @@ const storage = getStorageManager();
pbjs.enableAnalytics({
provider: 'pubwise',
options: {
site: 'test-test-test-test',
endpoint: 'https://api.pubwise.io/api/v4/event/add/',
site: 'test-test-test-test'
}
});
*/

Changes in 4.0 Version
4.0.1 - Initial Version for Prebid 4.x, adds activationId, adds additiona testing, removes prebid global in favor of a prebid.version const

*/

const analyticsType = 'endpoint';
const analyticsName = 'PubWise Analytics: ';
let defaultUrl = 'https://api.pubwise.io/api/v4/event/default/';
let pubwiseVersion = '3.0';
let pubwiseSchema = 'AVOCET';
let configOptions = {site: '', endpoint: 'https://api.pubwise.io/api/v4/event/default/', debug: ''};
const analyticsName = 'PubWise:';
const prebidVersion = '$prebid.version$';
let defaultUrl = 'https://api.pubwise.io/api/v5/event/default/';
let pubwiseVersion = '4.0.1';
let configOptions = {site: '', endpoint: defaultUrl, debug: ''};
let pwAnalyticsEnabled = false;
let utmKeys = {utm_source: '', utm_medium: '', utm_campaign: '', utm_term: '', utm_content: ''};
let sessionData = {sessionId: '', activationId: ''};
let pwNamespace = 'pubwise';
let pwEvents = [];
let metaData = {};
let auctionEnded = false;
let sessTimeout = 60 * 30 * 1000; // 30 minutes, G Analytics default session length
let sessName = 'sess_id';
let sessTimeoutName = 'sess_timeout';

function markEnabled() {
utils.logInfo(`${analyticsName}Enabled`, configOptions);
pwAnalyticsEnabled = true;
function enrichWithSessionInfo(dataBag) {
try {
dataBag['session_id'] = sessionData.sessId;
dataBag['activation_id'] = sessionData.activationId;
} catch (e) {
dataBag['error_sess'] = 1;
}

return dataBag;
}

function enrichWithMetrics(dataBag) {
try {
if (window.PREBID_TIMEOUT) {
dataBag['target_timeout'] = window.PREBID_TIMEOUT;
} else {
dataBag['target_timeout'] = 'NA';
}
dataBag['pw_version'] = pubwiseVersion;
dataBag['pbjs_version'] = $$PREBID_GLOBAL$$.version;
dataBag['pbjs_version'] = prebidVersion;
dataBag['debug'] = configOptions.debug;
} catch (e) {
dataBag['error_metric'] = 1;
Expand All @@ -54,72 +73,268 @@ function enrichWithUTM(dataBag) {
try {
for (let prop in utmKeys) {
utmKeys[prop] = utils.getParameterByName(prop);
if (utmKeys[prop] != '') {
if (utmKeys[prop]) {
newUtm = true;
dataBag[prop] = utmKeys[prop];
}
}

if (newUtm === false) {
for (let prop in utmKeys) {
let itemValue = storage.getDataFromLocalStorage(`pw-${prop}`);
if (itemValue.length !== 0) {
let itemValue = localStorage.getItem(setNamespace(prop));
musikele marked this conversation as resolved.
Show resolved Hide resolved
if (itemValue !== null && typeof itemValue !== 'undefined' && itemValue.length !== 0) {
musikele marked this conversation as resolved.
Show resolved Hide resolved
dataBag[prop] = itemValue;
}
}
} else {
for (let prop in utmKeys) {
storage.setDataInLocalStorage(`pw-${prop}`, utmKeys[prop]);
localStorage.setItem(setNamespace(prop), utmKeys[prop]);
}
}
} catch (e) {
utils.logInfo(`${analyticsName}Error`, e);
pwInfo(`Error`, e);
dataBag['error_utm'] = 1;
}
return dataBag;
}

function sendEvent(eventType, data) {
utils.logInfo(`${analyticsName}Event ${eventType} ${pwAnalyticsEnabled}`, data);
function expireUtmData() {
pwInfo(`Session Expiring UTM Data`);
for (let prop in utmKeys) {
localStorage.removeItem(setNamespace(prop));
}
}

function enrichWithCustomSegments(dataBag) {
// c_site: '', c_script_type: '', c_slot1: '', c_slot2: '', c_slot3: '', c_slot4: ''
if (configOptions.custom) {
if (configOptions.custom.c_script_type) {
dataBag['c_script_type'] = configOptions.custom.c_script_type;
}

if (configOptions.custom.c_host) {
dataBag['c_host'] = configOptions.custom.c_host;
}

if (configOptions.custom.c_slot1) {
dataBag['c_slot1'] = configOptions.custom.c_slot1;
}

if (configOptions.custom.c_slot2) {
dataBag['c_slot2'] = configOptions.custom.c_slot2;
}

if (configOptions.custom.c_slot3) {
dataBag['c_slot3'] = configOptions.custom.c_slot3;
}

if (configOptions.custom.c_slot4) {
dataBag['c_slot4'] = configOptions.custom.c_slot4;
}
}

return dataBag;
}

function setNamespace(itemText) {
return pwNamespace.concat('_' + itemText);
}

function localStorageSessTimeoutName() {
return setNamespace(sessTimeoutName);
}

function localStorageSessName() {
return setNamespace(sessName);
}

function extendUserSessionTimeout() {
localStorage.setItem(localStorageSessTimeoutName(), Date.now().toString());
}

function userSessionID() {
return localStorage.getItem(localStorageSessName()) ? localStorage.getItem(localStorageSessName()) : '';
}

function sessionExpired() {
let sessLastTime = localStorage.getItem(localStorageSessTimeoutName());
return (Date.now() - parseInt(sessLastTime)) > sessTimeout;
}

function flushEvents() {
if (pwEvents.length > 0) {
let dataBag = {metaData: metaData, eventList: pwEvents.splice(0)}; // put all the events together with the metadata and send
ajax(configOptions.endpoint, (result) => pwInfo(`Result`, result), JSON.stringify(dataBag));
}
}

function isIngestedEvent(eventType) {
const ingested = [
CONSTANTS.EVENTS.AUCTION_INIT,
CONSTANTS.EVENTS.BID_REQUESTED,
CONSTANTS.EVENTS.BID_RESPONSE,
CONSTANTS.EVENTS.BID_WON,
CONSTANTS.EVENTS.BID_TIMEOUT,
CONSTANTS.EVENTS.AD_RENDER_FAILED,
CONSTANTS.EVENTS.TCF2_ENFORCEMENT
];
return ingested.indexOf(eventType) !== -1;
}

function markEnabled() {
pwInfo(`Enabled`, configOptions);
pwAnalyticsEnabled = true;
setInterval(flushEvents, 100);
}

function pwInfo(info, context) {
utils.logInfo(`${analyticsName} ` + info, context);
}

/*
musikele marked this conversation as resolved.
Show resolved Hide resolved
// unused currently
function filterNoBid(data) {
let newNoBidData = {};

// put the typical items in the data bag
let dataBag = {
eventType: eventType,
args: data,
target_site: configOptions.site,
pubwiseSchema: pubwiseSchema,
debug: configOptions.debug ? 1 : 0,
};
newNoBidData.auctionId = data.auctionId;
newNoBidData.bidId = data.bidId;
newNoBidData.bidderRequestId = data.bidderRequestId;
newNoBidData.transactionId = data.transactionId;

dataBag = enrichWithMetrics(dataBag);
// for certain events, track additional info
if (eventType == CONSTANTS.EVENTS.AUCTION_INIT) {
dataBag = enrichWithUTM(dataBag);
return newNoBidData;
}
*/

ajax(configOptions.endpoint, (result) => utils.logInfo(`${analyticsName}Result`, result), JSON.stringify(dataBag));
function filterBidResponse(data) {
let modified = Object.assign({}, data);
// clean up some properties we don't track in public version
if (typeof modified.ad !== 'undefined') {
musikele marked this conversation as resolved.
Show resolved Hide resolved
modified.ad = '';
}
if (typeof modified.adUrl !== 'undefined') {
modified.adUrl = '';
}
if (typeof modified.adserverTargeting !== 'undefined') {
modified.adserverTargeting = '';
}
if (typeof modified.ts !== 'undefined') {
modified.ts = '';
}
// clean up a property to make simpler
if (typeof modified.statusMessage !== 'undefined' && modified.statusMessage === 'Bid returned empty or error response') {
modified.statusMessage = 'eoe';
}
modified.auctionEnded = auctionEnded;
return modified;
}

let pubwiseAnalytics = Object.assign(adapter(
{
defaultUrl,
analyticsType
}),
{
function filterAuctionInit(data) {
let modified = Object.assign({}, data);

modified.refererInfo = {};
// handle clean referrer, we only need one
if (typeof modified.bidderRequests !== 'undefined') {
musikele marked this conversation as resolved.
Show resolved Hide resolved
if (typeof modified.bidderRequests[0] !== 'undefined') {
if (typeof modified.bidderRequests[0].refererInfo !== 'undefined') {
modified.refererInfo = modified.bidderRequests[0].refererInfo;
}
}
}

if (typeof modified.adUnitCodes !== 'undefined') {
delete modified.adUnitCodes;
}
if (typeof modified.adUnits !== 'undefined') {
delete modified.adUnits;
}
if (typeof modified.bidderRequests !== 'undefined') {
delete modified.bidderRequests;
}
if (typeof modified.bidsReceived !== 'undefined') {
delete modified.bidsReceived;
}
if (typeof modified.config !== 'undefined') {
delete modified.config;
}
if (typeof modified.noBids !== 'undefined') {
delete modified.noBids;
}
if (typeof modified.winningBids !== 'undefined') {
delete modified.winningBids;
}

return modified;
}

let pubwiseAnalytics = Object.assign(adapter({defaultUrl, analyticsType}), {
// Override AnalyticsAdapter functions by supplying custom methods
track({eventType, args}) {
sendEvent(eventType, args);
this.handleEvent(eventType, args);
}
});

pubwiseAnalytics.handleEvent = function(eventType, data) {
// we log most events, but some are information
if (isIngestedEvent(eventType)) {
pwInfo(`Emitting Event ${eventType} ${pwAnalyticsEnabled}`, data);

// record metadata
metaData = {
target_site: configOptions.site,
debug: configOptions.debug ? 1 : 0,
};
metaData = enrichWithSessionInfo(metaData);
metaData = enrichWithMetrics(metaData);
metaData = enrichWithUTM(metaData);
metaData = enrichWithCustomSegments(metaData);

// add data on init to the metadata container
if (eventType === CONSTANTS.EVENTS.AUCTION_INIT) {
data = filterAuctionInit(data);
} else if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) {
data = filterBidResponse(data);
}

// add all ingested events
pwEvents.push({
eventType: eventType,
args: data
});
} else {
pwInfo(`Skipping Event ${eventType} ${pwAnalyticsEnabled}`, data);
}

// once the auction ends, or the event is a bid won send events
if (eventType === CONSTANTS.EVENTS.AUCTION_END || eventType === CONSTANTS.EVENTS.BID_WON) {
flushEvents();
}
}

pubwiseAnalytics.storeSessionID = function (userSessID) {
localStorage.setItem(localStorageSessName(), userSessID);
pwInfo(`New Session Generated`, userSessID);
};

// ensure a session exists, if not make one, always store it
pubwiseAnalytics.ensureSession = function () {
if (sessionExpired() === true || userSessionID() === null || userSessionID() === '') {
expireUtmData();
this.storeSessionID(utils.generateUUID());
}
extendUserSessionTimeout();
sessionData.sessId = userSessionID();
};

pubwiseAnalytics.adapterEnableAnalytics = pubwiseAnalytics.enableAnalytics;

pubwiseAnalytics.enableAnalytics = function (config) {
if (config.options.debug === undefined) {
config.options.debug = utils.debugTurnedOn();
}
sessionData.activationId = utils.generateUUID();
configOptions = config.options;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I have probably found an issue here. Based on the docs you've written at the top of this file, I added these lines to my test page:

pbjs.enableAnalytics({
          provider: "pubwise",
          options: {
            site: "test-test-test-test",
          },
        });

when the execution gets here, configOptions.endpoint was set to defaultUrl, but that gets removed by this assignment:

configOptions = config.options;

As a result, i see a bunch of errors in console:

POST http://localhost:9999/integrationExamples/gpt/undefined 405 (Method Not Allowed)
(anonymous) @ ajax.js:91
flushEvents @ pubwiseAnalyticsAdapter.js:171
...
Prebid ERROR: xhr error null Method Not Allowed

Note the undefined at the end of the url.

So, configOptions.endpoint does not exist anymore.

I think you wanted to extend configOptions with the data coming from function arguments:

configOptions = {
    ...configOptions, 
    ...config.options  
}

(This can be also achieved by using Object.assign)

Can you have a look into this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@musikele this is resolved now

if (configOptions.debug === undefined) {
configOptions.debug = utils.debugTurnedOn();
}
markEnabled();
this.ensureSession();
pubwiseAnalytics.adapterEnableAnalytics(config);
};

Expand Down
Loading