var AdminReporting = function(core, target, data, returnCB){ function Template(){ var fromDate = 'from:
', toDate = 'to:
', downloadReport = '
Full Platform Report:

' + fromDate + toDate + '

'; this.index = '
'; this.notifications = '

Platform Activity:

'; this.general = '
' + downloadReport + '
'; this.exchange = '
'; this.platformAlert = '

Set a global alert to notify clients:

'; this.health = '
'; this.search = ''; this.totalSquare = '

{{ mainValue }}

{{ subTitle }}:

{{ subValue }}

'; this.notificationEvent = ' {{ type }}{{# ip }} ({{ . }}){{/ ip }}: {{# msg }} {{ . }}{{/ msg }}{{# newBudget }} budget ${{ oldBudget }} is now ${{ . }} {{/ newBudget}}{{# agency }}{{.}}, {{/ agency }}{{ advertiser }}{{# user }} ({{ . }}){{/ user }}{{# name }}, {{ . }}{{/ name }}{{ ts }}'; this.searchResult = '{{#items}}{{type}}: {{#campaignID}} {{campaignName}} ({{campaignID}}) | Advertiser:{{/campaignID}}{{#advertiserID}} {{advertiserName}} ({{advertiserID}}) | Agency:{{/advertiserID}} {{#agencyID}} {{agencyName}} ({{agencyID}}){{/agencyID}}.{{/items}}{{^items}}

no matches found :(

{{/items}}'; this.apps = {}; this.apps.tradeShow = { index: '

Tradeshow

Save!
', row: '
' }; } var events = null, template = null, element = null, elements = null, data = null, squares = null, notificationsData = null, exData = {}, health, liveNotifications = new AdminNotifications(100), geo = new MeteoraMap.Geocoder(); this.exit = exit; var req = { 'advStats': { 'source' : 'actionStats/period/advertisers/1/-/-/7', 'noID' : true }, 'adminReporting': null, 'cachedExchangeData': { args: 30 }, 'adminMessage': null, 'health': null, 'system/apps/list': null, }; new ApiManager(req, null, init); function init(err, d){ if (!!err) console.error(err); d = cleanRTBObject(!!core ? core.userID : null, d); health = d.health; notificationsData = d.notifications; if(!!d.cachedExchangeData) { for(var k in d.cachedExchangeData) { var v = d.cachedExchangeData[k]; if(!v || Object.keys(v).length === 0) continue; exData[k] = new ExchangeObject(d.cachedExchangeData[k]); exData[k].name = k[0].toUpperCase() + k.substr(1); } } else { setTimeout(function() { return core.notifications.setError('I’m sorry, but exchange data is currently not available.'); }, 10); } events = new EventManager; template = new Template(); element = getElementFromString(template.index), elements = new Elements(element, { 'tabbed' : {}, 'tabbedWindow' : {}, 'notifications': {}, 'general' : {}, 'impressions' : {}, 'clicks' : {}, 'conversions' : {}, 'graphContainer' : {}, 'toggle' : {}, }); data = new Data(d.adminReporting); squares = new Squares(d.adminReporting, d.advStats); if(returnCB) returnCB(); initIndex(d); if (target) target.appendChild(element); } function initIndex(d){ var e = elements; //e.index = getElementFromString(template.index); e.tabbed = element.querySelector('.tabbed'); e.notifications = getElementFromString(template.notifications); e.general = getElementFromString(template.general); e.exchange = getElementFromString(template.exchange); e.platformAlert = getElementFromString(render(template.platformAlert, d.adminMessage)); e.health = getElementFromString(template.exchange, d.health); e.search = getElementFromString(template.search); if(isDefined(data.graph)) initGraph(e); initTabbedWindow(); } function initTabbedWindow(){ var e = elements; elements.tabbedWindow = new TabbedContainer(e.tabbed, getTabbedObj(e)); if(data.graph) elements.toggle = new TabbedContainer(e.graphContainer, getGraphsObj(e)); } function getCSClosure(g, obj){ return function(g, obj){ return function(){ new ColumnSquares(g, obj); }; }(g, obj); } function initNotifications() { var ele = elements.notifications.querySelector('div'), keepLimit = liveNotifications.limit, //number of notifications to keep tmpl = template.notificationEvent, evtMapping = { 'create:manager':'Manager Created', 'create:advertiser':'Advertiser Created', 'create:agency': 'Agency Created', 'create:campaign': 'Campaign Created', 'edit:campaign': 'Campaign Edited', 'delete:campaign':'Campaign Deleted / Archived', 'create:ad': 'Ad Created', 'delete:advertiser': 'Advertiser Deleted', 'delete:agency': 'Agency Deleted', 'user:signin': 'Logged In', 'security:delete':'**SECURITY WARNING**' }; liveNotifications.on('*', function(data) { if(data.type === 'user:signin' && data.user === 'system@meteora.co') return; data = getSimpleJSONCopy(data); var isNew = data.type.indexOf('create') > -1, isEdit = data.type.indexOf('edit') > -1, isBoring = data.type.indexOf('user') > -1; data.type = evtMapping[data.type] || data.type; data.cls = isNew ? 'mint' : isEdit ? 'cool' : isBoring ? 'stone' : 'hot'; data.ip = trimIP(data.ip); if(typeof data.ts === 'number') data.ts = formatISODate(new Date(data.ts*1000)); var t = getElementFromString(tmpl, data); events.add(t, 'click', switchAction.bind(data)); prependChild(ele, t, keepLimit); }); function switchAction(e) { if(e && e.preventDefault) e.preventDefault(); if(this.uid == null) return; if(this.userType === 'advertiser') { ga('send', 'event', 'Admin Notifications', 'Switch to advertiser', this.uid); } else if(this.userType === 'agency') { ga('send', 'event', 'Admin Notifications', 'Switch to agency', this.uid); } core.switchUser(this.uid); } function trimIP(ip) { if(!ip) return; var idx = ip.lastIndexOf(':'); if(idx === -1) return ip; return ip.substr(0, idx); } } function initGeneral(){ const g = elements.general, downloadDropdownOpts = new DropdownObj([ { title: 'Download', value: '-', placeholder: true }, { title: 'CSV', value: 'csv' }, { title: 'JSON', value: 'json' }, ], '-', 'cool', 'cool', 'cool', true ), reqData = {}, obj = { 'squares' : [ { 'column' : 0, 'size' : 'one', 'title' : 'Total Advertisers', 'element' : getSquare('totalAdvertisers') }, { 'column' : 1, 'size' : 'one', 'title' : 'Total Campaigns', 'element' : getSquare('totalCampaigns') }, { 'column' : 2, 'size' : 'one', 'title' : 'Total Users Tracking', 'element' : getSquare('numberOfUsersTracking') }, { 'column' : 3, 'size' : 'two', 'title' : 'Average Weekly Budget per Campaign', 'element' : getSquare('weeklyBudget') }, { 'column' : 0, 'size' : 'one', 'title' : 'Total Number of Ads', 'element' : getSquare('numberOfAds') }, { 'column' : 1, 'size' : 'one', 'title' : 'Number of Segments', 'element' : getSquare('numberOfSegments') }, { 'column' : 2, 'size' : 'two', 'title' : 'Average Active Campaigns per Client', 'element' : getSquare('averageCampaignsPerClient') } ] }; elements.fromInput = new CalendarInput(g.querySelector('.fromInput'), null, updateFromDate); elements.toInput = new CalendarInput(g.querySelector('.toInput'), null, updateToDate); element.downloadBtn = new Dropdown(g.querySelector('.downloadAnchor'), downloadDropdownOpts, downloadReport); function updateFromDate(e) { if (!e) return; reqData.from = e.year + '-' + getFormattedDate(e.monthValue) + '-' + getFormattedDate(e.date); } function updateToDate(e) { if (!e) return; reqData.to = e.year + '-' + getFormattedDate(e.monthValue) + '-' + getFormattedDate(e.date); } function getFormattedDate(v) { if (v > 9) return v; else return '0' + v; } function downloadReport(e) { preventDefault(e); if (e === '-') return; const req = { allStats: { source: `allTheStats/${reqData.from}/${reqData.to}?fmt=${e}`, noID: true } }; new ApiManager(req, core.userID, () => pop('Pop!', 'Downloads', '/dashboard/downloads')); } setTimeout(function() { getCSClosure(g.querySelector('.quickStats'), obj)(); }, 1); } function initExchange() { if(!Object.keys(exData).length) return; var e = elements.exchange, dropdown = getElementFromString('
Total (last 30d):
'), lastTable = null, dropDownData = [], tables = {}, costs = {}; function setTotal(k) { var sp = dropdown.querySelector('span'); sp.innerHTML = costs[k]; } e.appendChild(dropdown); for(var k in exData) { var ele = getElementFromString('
'), v = exData[k], lr = v.rows[v.rows.length - 1]; dropDownData.push({ 'title': 'Exchange: ' + v.name, 'value': k }); NewMTable(ele, v); tables[k] = ele.classList; e.appendChild(ele); costs[k] = lr ? lr.cost : 'n/a'; if(k === 'casale') { lastTable = ele.classList; lastTable.remove('noDisplay'); setTotal(k); } } var dd = new Dropdown(dropdown, { data: dropDownData, options: {}, }, function(p) { if(lastTable !== null) { lastTable.add('noDisplay'); } lastTable = tables[p]; lastTable.remove('noDisplay'); setTotal(p); }); dd.setValue('Casale'); } function ExchangeObject(d) { this.reqs = 0; this.bids = 0; this.impressions = 0; this.cost = 0; this.columns = this.getColumns(); this.rows = []; this.process(d); } ExchangeObject.prototype = { rowSelect: false, addRow: function(d, o) { if(!d.length) return; var imp = o.impressions, winPer = o.bids > 0 ? (imp / o.bids) * 100 : 0, bidPer = o.requests > 0 ? (o.bids / o.requests) * 100 : 0; this.reqs += o.requests; this.bids += o.bids; this.impressions += imp; this.cost += o.cost; this.rows.push({ 'day': d, 'reqs': {title: fmtNum(o.requests), value: o.requests}, 'bids': {title: fmtNum(o.bids), value: o.bids}, 'winPer': {title: fmtNum(winPer) + '%', value: winPer}, 'bidPer': {title: fmtNum(bidPer) + '%', value: bidPer}, 'imp': {title: fmtNum(imp), value: imp}, 'cost': {title: '$' + o.cost.toFixed(4), value: o.cost}, }); }, getColumns: function() { return { 'day': {key: 'day', sortable: true, title: 'Day', valueType: 'string', width: 140}, 'reqs': {key: 'reqs', sortable: true, title: 'Reqs', valueType: 'number', width: 134}, 'bids': {key: 'bids', sortable: true, title: 'Bids', valueType: 'number', width: 134}, 'bidPer': {key: 'bidPer', sortable: true, title: 'Bid %', valueType: 'number', width: 120}, 'imp': {key: 'imp', sortable: true, title: 'Imp', valueType: 'number', width: 134}, 'winPer': {key: 'winPer', sortable: true, title: 'Win %', valueType: 'number', width: 120}, 'cost': {key: 'cost', sortable: true, title: 'Media Cost', valueType: 'string', width: 134}, }; }, process: function(data) { var dates = Object.keys(data); dates.sort(); for(var i = 0; i < dates.length; i++) { var d = data[dates[i]]; this.addRow(dates[i], d); } this.rows.push({ __ignore__: true, day: 'TOTAL:', reqs: '' + fmtNum(this.reqs) + '', bids: '' + fmtNum(this.bids) + '', winPer: '' + fmtNum((this.imps / this.bids) * 100) + '%', bidPer: '' + fmtNum((this.bids / this.reqs) * 100) + '%', imp: '' + fmtNum(this.impressions) + '', cost: '' + '$' + this.cost.toFixed(4) + '', }); this.maxPerPage = this.rows.length; }, }; function initGraph(){ var e = elements; e.impressions = getElementFromString(''); e.clicks = getElementFromString(''); e.conversions = getElementFromString(''); e.graphContainer = e.index.querySelector('.graphContainer'); new Chart(e.impressions.getContext('2d')).Line(data.graph.impressions); new Chart(e.clicks.getContext('2d')).Line(data.graph.clicks); new Chart(e.conversions.getContext('2d')).Line(data.graph.conversions); } function initPlatformAlert() { var e = elements.platformAlert, btn = e.querySelector('.saveButton'), val = e.querySelector('textarea'); events.add(e, 'click', function() { new HttpRequest('PUT', '/api/v1/adminMessage', {'message': val.value}, 'json', 'json', function(e, st) { if(st !== 200) return core.notifications.setError('Couldn\'t set the platform alert'); core.notifications.setSuccess('Sucessfully set the plaform alert.'); }); }); } function initHealth() { var e = elements.health, tbl = getElementFromString('
'), header = { 'server': {key: 'server', sortable: true, title: 'Server', valueType: 'string', width: 120}, 'status': {key: 'status', sortable: true, title: 'Status', valueType: 'string', width: 420} }, upSpan = 'healthy', naSpan = 'not available', downSpan = 'sick, reason: '; function HealthObject() { this.rowSelect = false; this.columns = header; this.rows = (health||[]).map(function(v) { return { server: v.name, status: {noTitle: true, value: '-', title: v.isHealthy ? upSpan : v.error ? downSpan + v.error : naSpan} }; }); } NewMTable(tbl, new HealthObject()); e.appendChild(tbl); } function initSearch() { var e = elements.search, searchRes = e.querySelector('div:last-child'), searchType = 'Agency', searchInput = e.querySelector('input'), dd = new Dropdown(e.querySelector('.searchFor'), { data: makeOptions('Agency', 'Advertiser', 'Campaign'), options: {}, }, function(v) { searchType = v; }); events.add(e.querySelector('a.go'), 'click', search); events.add(searchInput, 'keypress', function(e) {if(e.keyCode === 13) search();}); function search() { var url = '/api/v1/search/' + searchType.toLowerCase() + '?q=' + encodeURIComponent(searchInput.value); new HttpRequest('GET', url, null, null, 'json', function(e, st) { if(isArray(e)) e.forEach(function(v) { v.type = searchType; }); searchRes.innerHTML = render(template.searchResult, {items: e}); forEach(searchRes.querySelectorAll('item > a[data-uid]'), function(a) { events.add(a, 'click', switchTo); }); }); } function switchTo(evt) { evt.preventDefault(); var e = evt.target, uid = e.dataset.uid, cid = e.dataset.cid; if(!uid) return; ga('send', 'event', 'Admin Notifications', 'Switch to User', uid); core.switchUser(uid, function(){ if(!!cid) pop('Pop!', 'Dashboard', '/campaigns/edit/' + cid); }); } } function makeOptions() { return [].map.call(arguments, function(v) { return {title: v, value: v}; }); } function Data(d){ this.graph = isDefined(d.reporting) ? getGraphData(d) : null; } function Squares(d,u){ var advertisers = { 'count' : d.totalAdvertisers, 'today' : d.newAdvertisers }, activeCampaigns = { 'count' : d.totalCampaigns, 'percent' : getPercentage(d.activeCampaigns, d.totalCampaigns) }, weeklyBudget = { 'average' : getRoundedValue(d.totalBudgets / d.totalCampaigns, 2), 'billedThisWeek' : 300 }, activeAds = { 'count' : d.totalAds, 'average' : getRoundedValue(d.totalAds / d.totalAdvertisers, 2) }, segments = { 'count' : d.totalSegments, 'average' : getRoundedValue(d.totalBudgets / d.totalSegments, 2) }; // General this.notifications = { 'template' : template.notificationSquare, 'data' : notificationsData }; this.exchange = { 'template' : template.exchange }; this.totalAdvertisers = getSquareObj(template.totalSquare, advertisers.count, 'signed up today', advertisers.today); this.numberOfUsersTracking = getSquareObj(template.totalSquare, u.views, 'Unique', u.uniqueUsers); // Ads this.numberOfAds = getSquareObj(template.totalSquare, activeAds.count, 'avg. per client', activeAds.average); // Campaigns this.totalCampaigns = getSquareObj(template.totalSquare, activeCampaigns.count, '% currently active', activeCampaigns.percent + '%'); this.averageCampaignsPerClient = getSquareObj(template.totalSquare, getRoundedValue(activeCampaigns.count / d.totalAdvertisers, 2), '% of total campaigns', activeCampaigns.percent + '%'); this.weeklyBudget = getSquareObj(template.totalSquare, weeklyBudget.average, 'billed this week', weeklyBudget.billedThisWeek); // Segments this.numberOfSegments = getSquareObj(template.totalSquare, segments.count, 'avg. per client', segments.average); } function getPercentage(a, b){ return Math.round(((a / b) * 10000)) / 100; } function getRoundedValue(number, places){ return Math.round(number * Math.pow(10, places)) / Math.pow(10, places); } function getSquareObj(template, mV, sT, sV){ return { 'template' : template, 'data' : { 'mainValue' : mV, 'subTitle' : sT, 'subValue' : sV } }; } function getSquare(k){ var s = squares[k]; if(s.template) return getElementFromString(render(s.template, isDefined(s.data) ? s.data : {})); else return null; } function getGraphData(d){ var userLevel = d.reporting.userLevel, impressions = getValues(userLevel, 'impressions'), clicks = getValues(userLevel, 'clicks'), conversions = getValues(userLevel, 'conversions'); return { 'impressions' : { labels : impressions.labels, datasets : [ { fillColor : 'rgba(151,187,205,0.5)', strokeColor : 'rgba(151,187,205,1)', pointColor : 'rgba(151,187,205,1)', pointStrokeColor : '#fff', data : impressions.data } ] }, 'clicks' : { labels : clicks.labels, datasets : [ { fillColor : 'rgba(151,187,205,0.5)', strokeColor : 'rgba(151,187,205,1)', pointColor : 'rgba(151,187,205,1)', pointStrokeColor : '#fff', data : clicks.data } ] }, 'conversions' : { labels : conversions.labels, datasets : [ { fillColor : 'rgba(151,187,205,0.5)', strokeColor : 'rgba(151,187,205,1)', pointColor : 'rgba(151,187,205,1)', pointStrokeColor : '#fff', data : conversions.data } ] } }; function getDate(s){ var v = s.split(' '); return { 'day' : v[0], 'month' : v[1], 'year' : v[2]} ; } function getValues(d, key){ var data = [], labels = [], listLength = 0; for(var k in d){ var t = d[k][core.userID], date = getDate(k); data.push(t[key]); labels.push(date); } listLength = data.length; if(listLength > 30){ data = data.slice(listLength - 30); labels = labels.slice(listLength - 30); } return getSortedReportingDates(labels, data); } } function getTabbedObj(e){ var obj = { 'data' : [ // Enable notifications when ticket #475 is completed { 'title' : 'Quick Stats', 'element' : e.general, 'initFn' : initGeneral }, { 'title' : 'Notifications', 'element' : e.notifications, 'initFn' : initNotifications }, ] }; if(Object.keys(exData).length) { obj.data.push({ 'title' : 'Exchange Data', 'element' : e.exchange, 'initFn' : initExchange }); } obj.data.push({ 'title' : 'Platform Alert', 'element' : e.platformAlert, 'initFn' : initPlatformAlert }); obj.data.push({ 'title' : 'Platform Health', 'element' : e.health, 'initFn' : initHealth }); obj.data.push({ 'title' : 'Search', 'element' : e.search, 'initFn' : initSearch }); return obj; } function getGraphsObj(e){ return { 'data' : [ { 'title' : 'Impressions', 'element' : e.impressions }, { 'title' : 'Clicks', 'element' : e.clicks }, { 'title' : 'Conversions', 'element' : e.conversions } ], 'config' : { 'className' : 'toggle' } }; } function insertToTarget(e){ target.appendChild(elements[e]); } function exit(){ if(isDefined(element)) removeChild(target, element); if(events) events.reset(); if(elements) elements.exit(); if(liveNotifications) liveNotifications.close(); housekeeping(); } function housekeeping(){ core = target = data = returnCB = events = template = elements = data = squares = notificationsData = null; } };