Monday, July 28, 2014

Save a File or Spreadsheet in a Specific Folder of GDrive

Here is a quick code snippet to help you understand how to save a spreadsheet into a specific folder on Google drive.

In order to create a file in a folder, you will need to find the parent folder and pass it to the DriveApp when you create the file. Normally, file paths are in the form of "/path/to/file/file.js", so it makes sense to keep that same construct when saving files to GDrive. The following code accepts the full file path and creates a new file in that folder.

The next obvious question is how to create a spreadsheet. Unfortunately, you can't create it using the DriveApp, you need to use the SpreadsheetApp. Also, since the spreadsheet app cannot create files in a folder, you will need to create the spreadsheet, then move it to the correct folder. I usually create a separate function with similar logic to find the correct folder, then update the create logic to use SpreadsheetApp instead of DriveApp. See the second function for an example.

Thanks,
Russ

/******************************************
* Create File in a Specific Folder Path
* Version 1.0 
* Created By: Russ Savage
* FreeAdWordsScripts.com
******************************************/
function createFile(filePath) {
  var pathArray = filePath.trim().split('/');
  var folder;
  for(var i in pathArray) {
    var parentName = pathArray[i];
    if(!parentName || parentName === '') { continue; } // in the root folder
    // source: http://goo.gl/P0LQ86
    if(filePath.indexOf(parentName, filePath.length - parentName.length) !== -1) {
      // This is creating the actual file
      if(folder) {
        if(folder.getFilesByName(parentName).hasNext()) {
          Logger.log('Using existing file: '+parentName);
          return folder.getFilesByName(parentName).next();
        }
        return folder.createFile(parentName,'');
      } else {
        if(DriveApp.getRootFolder().getFilesByName(parentName).hasNext()) {
          Logger.log('Using existing file: '+parentName);
          return DriveApp.getRootFolder().getFilesByName(parentName).next();
        }
        return DriveApp.getRootFolder().createFile(parentName,'');
      }
    }
    if(!folder && !DriveApp.getRootFolder().getFoldersByName(parentName).hasNext()) {
      Logger.log('Creating folder: '+parentName);
      folder = DriveApp.getRootFolder().createFolder(parentName);
    } else if(folder && !folder.getFoldersByName(parentName).hasNext()) {
      Logger.log('Creating folder: '+parentName);
      folder = folder.createFolder(parentName);
    } else {
      Logger.log('Using existing folder: '+parentName);
      folder = (!folder) ? DriveApp.getRootFolder().getFoldersByName(parentName).next() 
                         : folder.getFoldersByName(parentName).next();
    }
  }
  // Should never get here
  throw "Invalid file path: "+filePath;
}
And to create a spreadsheet:
/******************************************
* Create Spreadsheet in a Specific Folder Path
* Version 1.0 
* Created By: Russ Savage
* FreeAdWordsScripts.com
******************************************/
function createSpreadsheet(filePath) {
  var pathArray = filePath.trim().split('/');
  var folder;
  for(var i in pathArray) {
    var parentName = pathArray[i];
    if(!parentName || parentName === '') { continue; } // in the root folder
    // source: http://goo.gl/P0LQ86
    if(filePath.indexOf(parentName, filePath.length - parentName.length) !== -1) {
      // This is creating the actual file
      if(folder) {
        if(folder.getFilesByName(parentName).hasNext()) {
          Logger.log('Using existing file: '+parentName);
          var file = folder.getFilesByName(parentName).next();
          return SpreadsheetApp.openById(file.getId());
        }
        // creates in folder
        var spreadsheet = SpreadsheetApp.create(parentName);
        var file = DriveApp.getRootFolder().getFilesByName(parentName).next();
        folder.addFile(file);
        DriveApp.getRootFolder().removeFile(file);
        return spreadsheet;
      } else {
        if(DriveApp.getRootFolder().getFilesByName(parentName).hasNext()) {
          Logger.log('Using existing file: '+parentName);
          var file = DriveApp.getRootFolder().getFilesByName(parentName).next();
          return SpreadsheetApp.openById(file.getId());
        }
        // creates in root folder
        return SpreadsheetApp.create(parentName);
      }
    }
    if(!folder && !DriveApp.getRootFolder().getFoldersByName(parentName).hasNext()) {
      Logger.log('Creating folder: '+parentName);
      folder = DriveApp.getRootFolder().createFolder(parentName);
    } else if(folder && !folder.getFoldersByName(parentName).hasNext()) {
      Logger.log('Creating folder: '+parentName);
      folder = folder.createFolder(parentName);
    } else {
      Logger.log('Using existing folder: '+parentName);
      folder = (!folder) ? DriveApp.getRootFolder().getFoldersByName(parentName).next() 
                         : folder.getFoldersByName(parentName).next();
    }
  }
  // Should never get here
  throw "Invalid file path: "+filePath;
}

Sunday, July 27, 2014

Exception Invalid reporting query INVALID_PREDICATE_ENUM_VALUE ACTIVE

UPDATE: Here is a note from the AdWords Scripts team on the topic.

A few days ago, I started getting a bunch of errors from my scripts that looked like this:
Exception: Invalid reporting query: INVALID_PREDICATE_ENUM_VALUE: ACTIVE

If anyone else is seeing this issue, it has to do with an update to the reporting API. Previously, ACTIVE used to be a valid enum value for CampaignStatus, which was always sort of an anomaly since everything else used ENABLED. It has been updated to match the other Status values but in the process, broke any script that was using the ACTIVE status. Here is an example:
    ...
    var query = ['select',cols.join(','),'from',report,
                 'where CampaignStatus = ACTIVE',
                 'and AdGroupStatus = ENABLED',
                 'and Status = ACTIVE',
                 'during','TODAY'].join(' ');
    ...
This should be changed to:
    ...
    var query = ['select',cols.join(','),'from',report,
                 'where CampaignStatus = ENABLED',
                 'and AdGroupStatus = ENABLED',
                 'and Status = ENABLED',
                 'during','TODAY'].join(' ');
    ...

I am trying to go back and update my previous scripts but it might take some time.
Thanks,
Russ

Thursday, June 19, 2014

Calling All AdWords Scripts Developers

I was thinking of putting together a directory for any developers or companies out there that are currently writing Adwords Scripts for clients and would be interested in being contacted. I figured a picture or logo, name, short bio, location, and website would be a good start for this. If you are interested in being listed, please fill out this form and I will add you to the directory that is linked in the sidebar.

Thanks,
Russ

Monday, May 5, 2014

Connect Zoho CRM Data with AdWords Using Scripts

For anyone working in B2B Pay Per Click, one of the biggest headaches is trying to report on the entire sales flow within a single report. The biggest issue is that marketing PPC data lives in AdWords (clicks, impressions, MQLs) and the sales data lives in your CRM (Leads, Contacts, Opportunities, Etc.). So I started looking at ways to connect the two sources of data. I don't use a CRM, so I signed up for a free trial of Zoho CRM and started fiddling with their API. It turns out, they have a REST(ish) API that returns JSON objects, which is perfect for AdWords scripts.

I built the Class below to pull data out of Zoho. It has the ability to pull Leads, Contacts, Potentials and just about any other Zoho object you can think of directly from your CRM. I stopped at just being able to get data out since updating or deleting records seemed less useful for AdWords scripts.

Here is a quick reference guide to the Class. Define a new object with var zoho = new ZohoApi(YOUR_API_TOKEN); If you need help setting up your account for API access or generating a token, check out the Zoho Developer Docs. I generated some simple getters for each Zoho object. ZohoApi.get[Objects]() will pull in all of the particular object. So you can say zoho.getLeads() or zoho.getPotentials(). You can also get only those objects belonging to you with zoho.getMyLeads().

If you have any additional url parameters you want to send over with the request, you can add them as a parameter to the function. For example, if you wanted to return the first 100 records (instead of the default 20), you would say zoho.getLeads({fromIndex:1,toIndex:100});

You can also search for records using zoho.search[Objects](). So to search for Potentials that have been won, you would say zoho.searchPotentials({'searchCondition':'(Stage|=|Closed Won)'}); You can read more about Zoho's searchCondition syntax in their API Docs. As part of that, you can put the columns you want to see or if you don't put anything in there, I pull the full set of columns to display for you using the get[Objects]Fields() method.

As for the response from the class, you will get an array of objects. Each key in the object has been lowercased with spaces replaced by underscores. For example, retVal[0].first_name or retVal[0].annual_revenue.

So give it a shot and let me know what you think in the comments. I put together a simple example script at the very bottom of this post to store Impressions, Clicks, Conversions, and Closed Won Potentials in a Google Doc on a daily basis to give you an idea of what you can do. Let me know what you would like to see next.

Thanks,
Russ



And here is a really simple example of how you could combine conversion data from multiple sources into a single Google Spreadsheet report.