Keep track of your AdWords changes with labels and Slack

PHOTO EMBED

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;
  }

}
content_copyCOPY

https://racecon.io/en/blog/keep-track-of-your-adwords-changes-with-labels-and-slack/