I originally wrote this answer for your previous question, so it includes answers to some of your comments from there, but since you're continuing to go asking the community to write this step-by-step , here's the next step.
The issue I'm running into is that if three tweets come into the spreadsheet at the same time, with my code, it's only going to update the most recent cell, not all three. Does that make sense?
Yes, it does make sense.
When an onEdit()
trigger function calls Spreadsheet Service functions to get current info from the sheet, it enters a "Race condition". If any changes occur in the sheet after the change that triggered onEdit()
, and the time when it gets scheduled, those changes will be visible when it runs. That's what you see when you assume that the change you're processing is in the last row - by the time you're processing it, there may be a new last row.
Good news, though - the attributes of the event object passed to onEdit
contain the details of the change. (The parameter e
.) See Event objects.
By using e.range
and e.value
you'll find you have the location and content of the edited cell that kicked the trigger. If additional tweets arrive before the trigger is serviced, your function won't be tricked into processing the last row.
In new sheets, the onEdit()
can get triggered for multiple-cell changes, such as cut & paste. However unlikely that it may happen, it's worth covering.
Well, after getting the spreadsheet all setup & actually using the trigger from IFTTT, it doesn't work. :( I'm assuming it's not dubbing it as the active cell whenever it automatically pulls it into the spreadsheet. Any idea on a workaround on that?
Q: When is an edit not an edit? A: When it's made by a script. In that case, it's a change. You can add an installable on Change
function to catch those events. Unfortunately, the change event is less verbose than an edit event, so you are forced to read the spreadsheet to figure out what has changed. My habit is to have the change handler simulate an edit by constructing a fake event (just as we'd do for testing), and passing it to the onEdit
function.
So give this a try. This script:
- handles a list of "bad words". (Could just as easily be monitoring for mentions of your product or cause.)
- has an
onEdit()
function that uses the event object to evaluate the row(s) that triggered the function call.
- colors "bad" tweets
- has a function for testing the onEdit() trigger, based on How can I test a trigger function in GAS?
- includes
playCatchUp(e)
, an installable trigger function (change and/or time-based) that will evaluate any rows that have not been evaluated before. Script property "Last Processed Row"
is used to track that row value. (If you plan to remove rows, you'll need to adjust the property.)
- Has the sendMail function commented out.
Enjoy!
// Array of bad words. Could be replaced with values from a range in spreadsheet.
var badWords = [
"array",
"of",
"unacceptable",
"words",
"separated",
"by",
"commas"
];
function onEdit(e) {
if (!e) throw new Error( "Event object required. Test using test_onEdit()" );
Logger.log( e.range.getA1Notation() );
// e.value is only available if a single cell was edited
if (e.hasOwnProperty("value")) {
var tweets = [[e.value]];
}
else {
tweets = e.range.getValues();
}
var colors = e.range.getBackgrounds();
for (var i=0; i<tweets.length; i++) {
var tweet = tweets[i][0];
for (var j=0; j< badWords.length; j++) {
var badWord = badWords[j];
if (tweet.match(badWord)) {
Logger.log("Notice of possible inappropriate tweet: " + tweet);
colors[i][0] = "red";
//MailApp.sendEmail(myEmail, "Notice of possible inappropriate tweet", tweet);
break;
}
}
}
e.range.setBackgrounds(colors);
PropertiesService.getDocumentProperties()
.setProperty("Last Processed Row",
(e.range.getRowIndex()+tweets.length-1).toString());
}
// Test function, adapted from https://stackoverflow.com/a/16089067/1677912
function test_onEdit() {
var fakeEvent = {};
fakeEvent.authMode = ScriptApp.AuthMode.LIMITED;
fakeEvent.user = "amin@example.com";
fakeEvent.source = SpreadsheetApp.getActiveSpreadsheet();
fakeEvent.range = fakeEvent.source.getActiveSheet().getDataRange();
// e.value is only available if a single cell was edited
if (fakeEvent.range.getNumRows() === 1 && fakeEvent.range.getNumColumns() === 1) {
fakeEvent.value = fakeEvent.range.getValue();
}
onEdit(fakeEvent);
}
// Installable trigger to handle change or timed events
// Something may or may not have changed, but we won't know exactly what
function playCatchUp(e) {
// Check why we've been called
if (!e)
Logger.log("playCatchUp called without Event");
else {
// If onChange and the change is an edit - no work to do here
if (e.hasOwnProperty("changeType") && e.changeType === "EDIT") return;
// If timed trigger, nothing special to do.
if (e.hasOwnProperty("year")) {
var date = new Date(e.year, e.month, e["day-of-month"], e.hour, e.minute, e.second);
Logger.log("Timed trigger: " + date.toString() );
}
}
// Find out where to start processing tweets
// The first time this runs, the property will be null, yielding NaN
var lastProcRow = parseInt(PropertiesService.getDocumentProperties()
.getProperty("Last Processed Row"));
if (isNaN(lastProcRow)) lastProcRow = 0;
// Build a fake event to pass to onEdit()
var fakeEvent = {};
fakeEvent.source = SpreadsheetApp.getActiveSpreadsheet();
fakeEvent.range = fakeEvent.source.getActiveSheet().getDataRange();
var numRows = fakeEvent.range.getLastRow() - lastProcRow;
if (numRows > 0) {
fakeEvent.range = fakeEvent.range.offset(lastProcRow, 0, numRows);
onEdit(fakeEvent);
}
else {
Logger.log("All caught up.");
}
}