Skip to content

Instantly share code, notes, and snippets.

@dustinrecko
Created October 1, 2018 07:01
Show Gist options
  • Save dustinrecko/3f356dfcf17ba12c4cd306779387d370 to your computer and use it in GitHub Desktop.
Save dustinrecko/3f356dfcf17ba12c4cd306779387d370 to your computer and use it in GitHub Desktop.
OMR | Performance Monitoring in Slack via Labels
/**
* 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;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment