3

What I wish to achieve:

Whenever a cell is changed in any google sheet on my shared drive (by any user on the domain) I want to call an API endpoint and include information about which cell was edited.

My approach: I believe Google App Scripts Add-on is what I need. Installed for all users on the domain.

I see there are "bound" scripts and standalone scripts. For standalone scripts I am not able to create any other triggers than timer and calender based triggers. Bound scripts seem to be permanently bound to a single sheet and won't impact other sheets in any way.

What am I missing?

I find a few end-to-end tutorials on blogs for making bound scripts, but nothing for generic cross-domain stuff.

tehhowch
  • 9,645
  • 4
  • 24
  • 42
WPFUser
  • 403
  • 4
  • 16

1 Answers1

7

You can achieve all this through a standalone script. Create a standalone script and follow these steps:

Step 1: Get spreadsheet ids

First you would have to get the id of the different Spreadsheets in your shared drive. You can do it in Google Apps Script itself if you use the Advanced Drive Service (see Reference below). To activate this service, go to Resources > Advanced Google services... in your script editor and enable Drive API.

Then, write a function that will return an array of the spreadsheet ids in the shared drive. You will have to call Drive.Files.list for that. It could be something along the following lines (please write your shared driveId in the corresponding line):

function getFileIds() {
  var params = {
    corpora: "drive",
    driveId: "your-shared-drive-id", // Please change this accordingly
    includeItemsFromAllDrives: true,
    q: "mimeType = 'application/vnd.google-apps.spreadsheet'",
    supportsAllDrives: true
  }
  var files = Drive.Files.list(params)["items"];
  var ids = files.map(function(file) {
    return file["id"];
  })
  return ids;
}

Step 2: Create triggers for each spreadsheet

Install an onEdit trigger programmatically for each of the spreadsheets (an edit trigger fires a function every time the corresponding spreadsheet is edited, so I assume this is the trigger you want). For this, the ids retrieved in step 1 will be used. It could be something similar to this:

function createTriggers(ids) {
  ids.forEach(function(id) {
    var ss = SpreadsheetApp.openById(id);
    createTrigger(ss);
  })
}

function createTrigger(ss) {
  ScriptApp.newTrigger('sendDataOnEdit')
    .forSpreadsheet(ss)
    .onEdit()
    .create();
}

The function createTriggers gets an array of ids as a parameter and, for each id, creates an onEdit trigger: everytime any of these spreadsheets is edited, the function sendDataOnEdit will run, and that's where you will want to call your API endpoint with information about the edited cell.

Step 3: Call API endpoint

The function sendDataOnEdit has to get data from the edited cell and send it somewhere.

function sendDataOnEdit(e) {
  // Please fill this up accordingly
  var range = e.range;
  var value = range.getValue();
  UrlFetchApp.fetch(url, params) // Please fill this up accordingly
}

First, it can get information about the cell that was edited via the event object, passed to the function as the parameter e (you can get its column, its row, its value, the sheet and the spreadsheet where it is located, etc.). For example, to retrieve the value of the cell you can do e.range.getValue(). Check the link I provide in reference to get more details on this.

Second, when you have correctly retrieved the data you want to send, you can use UrlFetchApp.fetch(url, params) to make a request to your URL. In the link I provide below, you can see the parameters you can specify here (e.g., HTTP method, payload, etc.).

Please bear in mind that you might need to grant some authorization to access the API endpoint, if this is not public. Check the OAuth reference I attach below.

(You have to edit this function accordingly to retrieve and send exactly what you want. What I wrote is an example).

Summing this up:

In order to create the triggers you should run createTriggers once (if you run it more times, it will start creating duplicates). Run for example, this function, that first gets the file ids via Drive API and then creates the corresponding triggers:

function main() {
  var ids = getFileIds();
  createTriggers(ids);
}

Also, it would be useful to have a function that will delete all the triggers. Run this in case you want to start from fresh and make sure you don't have duplicates:

function deleteTriggers() {
  var triggers = ScriptApp.getProjectTriggers();
  triggers.forEach(function(trigger) {
    ScriptApp.deleteTrigger(trigger);
  })
}

Reference:

I hope this is of any help.

Iamblichus
  • 18,540
  • 2
  • 11
  • 27
  • 1
    Wow! That is 10 times more detailed and useful than I had ever hoped for. Huge thanks! – WPFUser Jan 16 '20 at 08:54
  • iamblichus after trying this for a bit I found two unfortunate limitations. 1. There is a quota for 20 triggers per app script, meaning this won't work for more than 20 sheets(unless I make multiple scripts). 2. There doesn't seem to be a good way to autosubscribe to new files being added. Do you have any suggestions around these issues? – WPFUser Jan 27 '20 at 21:13
  • @WPFUser (1) If there is more than 20 sheets, I would consider having them in different folders and have a different script to track the files from each of those different folders (change the call in `getFileIds` so that it tracks the files in each specific folder, not just in all the shared drive). (2) Everytime a new file is added, you would have to delete and create the triggers again. That would be accomplished just by running `deleteTriggers` and then `createTriggers`, which could be bundled in a sole function. – Iamblichus Jan 28 '20 at 08:10
  • In order to make this work for all users on my domain I need to register it as an app and install it for all users. If I understand this right I would need to create more and more apps each with one script each as we get more and more sheets. I also don't see how I can automatically run delete- and create-triggers (or a new update-trigger-function) upon file creation. I could subscribe to drive changes, but then I would not get the first edits of a newly created sheets as there is some delay. For my case this does not seem like a good solution as it simply doesn't scale and/or is too manual. – WPFUser Jan 28 '20 at 08:36
  • @WPFUser consider the possibility that what you're asking for might not be possible. All services have limitations, and some of the functionalities you need were not specified in the original question (the need for all users in the domain to be able to use this, for example). I seriously doubt you can get an approach that gets closer to what you want to accomplish. – Iamblichus Jan 28 '20 at 08:54
  • You might very well be right about that. And I truly appreciate your answer. The need for it to work for all users were and still is specified though. ie "Whenever a cell is changed in any google sheet on my shared drive (by any user on the domain) I want to call an API endpoint and include information about which cell was edited." – WPFUser Jan 28 '20 at 09:14