Keep track of your AdWords changes with labels and Slack
Sun Sep 19 2021 18:20:53 GMT+0000 (Coordinated Universal Time)
Saved by @admariner #javascript
/** * AdWords Performance Monitoring in Slack via Labels * @author: Dustin Recko * */ // Config Section //> var DB_URL = 'https://...'; // The Firebase Database URL var DB_AUTH = 'xxx'; // The Firebase Database Secret var SLACK_HOOK = 'https://...'; // The Slack Hook URL var SLACK_CHANNEL = '#adwords'; // The Slack Channel var SLACK_EMOJI = ':smile:'; // The Slack Emoji var TRIGGER = { clicks: 30, //Stats as a trigger with a specified threshold weeks: [1,4] //Time as a trigger with a start and end threshold }; var LABEL_NAME = "CONTROL"; // The name of the label used in AdWords to activate monitoring for Keywords, AdGroups, and Ads var NOW = new Date(); // End of Config <// function main() { init(); var ACC = AdWordsApp.currentAccount().getName().split(" ")[0]; var myDb = new firebase(DB_URL,DB_AUTH); var myDbJson = myDb.getJson(LABEL_NAME+'/'+ACC) || nest({},[LABEL_NAME,ACC]); var mySlack = new slack(SLACK_HOOK,SLACK_CHANNEL,SLACK_EMOJI); var myLabel = AdWordsApp.labels().withCondition("Name = '"+LABEL_NAME+"'").get().next(); var process = { "keywords": myLabel.keywords().get(), "adGroups": myLabel.adGroups().get(), "ads": myLabel.ads().get() }; /// Main process for(var handler in process) { /// Check items with the label while(process[handler].hasNext()) { var h = process[handler].next(); if(!myDbJson[handler] || !myDbJson[handler][h.getId()]) { var obj = { "name" : (h.getText instanceof Function) ? h.getText() : ((h.getDescription1 instanceof Function) ? h.getDescription1() : h.getName()), "campaign": h.getCampaign().getName(), "qsStart" : (h.getQualityScore instanceof Function) ? h.getQualityScore() : 0, "started" : NOW.getTime(), "trigger": initTrigger(TRIGGER) }; myDb.patch(obj,LABEL_NAME+'/'+ACC+'/'+handler+'/'+h.getId()); myDbJson = nest(myDbJson,[handler,h.getId()]); } else { for(var i in TRIGGER) { switch(typeof(myDbJson[handler][h.getId()].trigger[i])) { case "boolean": if(!myDbJson[handler][h.getId()].trigger[i]) { if(statsBasedCheck(handler,h,i,myDbJson[handler][h.getId()])) { var status = {}; status[i] = true; myDb.patch(status,LABEL_NAME+'/'+ACC+'/'+handler+'/'+h.getId()+'/trigger'); } } break; case "number": var weeksPassed = Math.round((NOW.getTime() - myDbJson[handler][h.getId()].started)/(1000*60*60*24)) / 7; if(weeksPassed%1 === 0 && TRIGGER[i][0] <= weeksPassed && weeksPassed <= TRIGGER[i][1] && weeksPassed > myDbJson[handler][h.getId()].trigger[i]) { timeBasedCheck(handler,h,i,myDbJson[handler][h.getId()]); var status = {}; status[i] = weeksPassed; myDb.patch(status,LABEL_NAME+'/'+ACC+'/'+handler+'/'+h.getId()+'/trigger'); } break; } } } /// Flag processed items myDbJson[handler][h.getId()].flag = true; } /// Cleanup no longer labelled items, i.e., non-flagged for(var i in myDbJson[handler]) { if(myDbJson[handler][i].flag == undefined) myDb.purge(LABEL_NAME+'/'+ACC+'/'+handler+'/'+i); } } /// Some functions function init() { Date.prototype.yyyymmdd = function(days) { if(days) { this.setDate(this.getDate() + days); } return Utilities.formatDate(this, AdWordsApp.currentAccount().getTimeZone(),'yyyyMMdd'); } } function firebase(db,auth) { this.db = db; this.auth = auth; this.patch = function(payload,path) { path = path+'/.json?auth='; var options = { "method" : "patch", "payload" : JSON.stringify( payload ) }; UrlFetchApp.fetch(this.db+path+this.auth,options); } this.purge = function(path) { path = path+'/.json?auth='; var options = { "method": "delete" }; UrlFetchApp.fetch(this.db+path+this.auth,options); } this.getJson = function(path) { path = path+'/.json?auth='; return JSON.parse( UrlFetchApp .fetch(this.db+path+this.auth) .getContentText() ); } } function slack(hook,channel,emoji) { this.hook = hook; this.channel = channel; this.emoji = emoji; this.msg = function(payload) { payload.channel = this.channel; payload.icon_emoji = this.emoji; var options = { method: "POST", contentType: 'application/json', payload: JSON.stringify(payload) }; UrlFetchApp.fetch(this.hook,options); } } function initTrigger() { var obj = {}; for(var i in TRIGGER) { if(typeof(TRIGGER[i]) == "number") obj[i] = false else obj[i] = 0 } return obj; } function statsBasedCheck(type,handler,trigger,dbData) { var sh = handler.getStatsFor(new Date(dbData.started).yyyymmdd(),NOW.yyyymmdd()); var stats = { avgCpc: sh.getAverageCpc().toFixed(2), avgPos: sh.getAveragePosition(), clicks: sh.getClicks(), conversions: sh.getConversions(), cost: sh.getCost(), qs: (type == 'keywords') ? handler.getQualityScore() : 0 }; if(stats[trigger] >= TRIGGER[trigger]) { var daysPassed = Math.round((NOW.getTime() - dbData.started)/(1000*60*60*24)); var sh = handler.getStatsFor(new Date(dbData.started).yyyymmdd(daysPassed*-1),new Date(dbData.started).yyyymmdd()); var beforeStats = { avgCpc: sh.getAverageCpc().toFixed(2), avgPos: sh.getAveragePosition(), clicks: sh.getClicks(), conversions: sh.getConversions(), cost: sh.getCost(), qs: dbData.qsStart }; var attachments = []; for(var s in stats) { attachments.push({ title: s, text: 'is '+stats[s]+ (stats[s]>=beforeStats[s] ? ' (up' : '(down') +' from '+beforeStats[s]+')' }); } mySlack.msg({ text: LABEL_NAME+" > "+ACC+" > "+type+" > "+dbData.name+" in "+dbData.campaign+" passed "+stats[trigger]+" "+trigger+" in "+daysPassed+" days (was "+beforeStats[trigger]+" in the previous period)", attachments: attachments }); return true; } return false; } function timeBasedCheck(type,handler,trigger,dbData) { var sh = handler.getStatsFor(new Date(dbData.started).yyyymmdd(),NOW.yyyymmdd()); var stats = { avgCpc: sh.getAverageCpc().toFixed(2), avgPos: sh.getAveragePosition(), clicks: sh.getClicks(), conversions: sh.getConversions(), cost: sh.getCost(), qs: (type == 'keywords') ? handler.getQualityScore() : 0 }; var sh = handler.getStatsFor(new Date(dbData.started).yyyymmdd((dbData.trigger[trigger]+1)*-7),new Date(dbData.started).yyyymmdd()); var beforeStats = { avgCpc: sh.getAverageCpc().toFixed(2), avgPos: sh.getAveragePosition(), clicks: sh.getClicks(), conversions: sh.getConversions(), cost: sh.getCost(), qs: dbData.qsStart }; var attachments = []; for(var s in stats) { attachments.push({ title: s, text: 'is '+stats[s]+ (stats[s]>=beforeStats[s] ? ' (up' : '(down') +' from '+beforeStats[s]+')' }); } mySlack.msg({ text: LABEL_NAME+" > "+ACC+" > "+type+" > "+dbData.name+" in "+dbData.campaign+" passed "+(dbData.trigger[trigger]+1)+" "+trigger, attachments: attachments }); } // by Jason Knight function nest(base,arr) { for (var obj = base, ptr = obj, i = 0, j = arr.length; i < j; i++) ptr = (ptr[arr[i]] = {}); return obj; } }
Comments