I'm trying to implement the solution that Anton Soradoi proposed on 12/22/11 to enable a mail merge script that will exceed its maximum execution time terminate gracefully, wait a period of time, then pick up where it left off and continue doing so until its run is complete. Here is the link to the referenced post.
The script runs fine for the aloted time (5 min), then throws a "Execution failed: Invalid argument: value (line 80). Also, I'm not sure what the "else" part of the script that Anton Soradoi discussed is supposed to do (run my menuItem1 function again?). I feel like I'm pretty close, and any help would be greatly appreciated. My code is below:
//Creates the custom menu in the spreadsheet "Run Script"
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Script')
.addItem('Create Certs', 'menuItem1')
.addToUi();
}//Ends the custom menu in the spreadsheet
//Runs the menuItem 1 operation (Create Certs)
function menuItem1() {
//Defines the start row and calculates the number of rows to be processed
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = Browser.inputBox("Enter Start Row");
var endRow = Browser.inputBox("Enter End Row");
var numRows = (endRow - startRow) + 1; //this is the row height
var dataRange = sheet.getRange(startRow, 1, numRows, 7);
var counter =0;
var data = dataRange.getValues();
var templateDoc = DriveApp.getFileById("1baxSUxfSdzcVheR3Y2qgieWeSAqNybPfWct1913uRIc");
var templateCopy = templateDoc.makeCopy();
var templateCopyId = templateCopy.getId();
var dateOld;
var courseOld;
var fullNameOld;
var mailFrom = GmailApp.getAliases()
var team = "NWC Online PME Help Desk"
var startTime= (new Date()).getTime();
for (var i = 0; i < data.length; ++i) {
var doc = DocumentApp.openById(templateCopyId);
var body = doc.getActiveSection();
var row = data[i];
var date = row[0];
var nic = row[1];
var course = row[2];
var lastname = row[3];
var firstname = row[4];
var middle = row[5]
var email = row[6];
var subjectTxt = "NWC Online PME Course Certificate";
var fullBody = "PME COURSE COMPLETION CERTIFICATE" + "\n\n";
fullBody += "Your course completion certificate is attached." + "\n\n";
fullBody += "NOTES:" + "\n";
fullBody += "1. DO NOT telephone NWC to resolve PME certificate issues, email our Help Desk: pmecerthelp#usnwc.edu." + "\n";
fullBody += "2. NWC does NOT mail hardcopy certificates." + "\n";
fullBody += "3. NWC does not award certificates for the SNCO JPME courses." + "\n";
fullBody += "4. NWC course completion certificates are not automatically entered into your electronic training or service records." + "\n\n";
fullBody += "Regards," + "\n\n";
fullBody += "U.S. Naval War College Online PME Program Team"+ "\n\n";
fullBody += "Learn more about NWC's Online PME Program at the link below:" + "\n";
fullBody += "http://www.usnwc.edu/Academics/College-of-Distance-Education/PME-(1).aspx" + "\n";
var fullName = firstname+' '+middle+''+lastname
var fdate = Utilities.formatDate(new Date(date), "UTC", "d MMMM yyyy"); //converts UTC date
if(counter ==0){
body.replaceText('fullName',fullName);
body.replaceText('course', course);
body.replaceText('date', fdate);
}//Ends the if condition
else {
body.replaceText(fullNameOld,fullName);
body.replaceText(courseOld, course);
body.replaceText(dateOld, fdate);
}//Ends the else condition
dateOld = fdate;
courseOld = course;
fullNameOld = fullName;
counter ++
doc.saveAndClose();
var attachment = doc.getAs('application/pdf');
GmailApp.sendEmail(email, subjectTxt, fullBody, {name: team, attachments: attachment, from: mailFrom[1]});
var scriptProperties = PropertiesService.getScriptProperties();
var newStartRow= scriptProperties.getProperty('row');
for(var ii = newStartRow; ii <= data.length; ii++) {
var currTime = (new Date()).getTime();
if(currTime - startTime >= 300000) {
scriptProperties.setProperty("row", ii);
ScriptApp.newTrigger("menuItem1")
.timeBased()
.at(new Date(currTime+30000))
.create();
break;
}//Ends the if loop
}//Ends the second for loop
}//Ends the first for loop
}//Ends menuItem1
The below code should do the trick, you can not use the code as is. Please read below:
//Creates the custom menu in the spreadsheet "Run Script"
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Script')
.addItem('Create Certs', 'menuItem1')
.addToUi();
}//Ends the custom menu in the spreadsheet
//Runs the menuItem 1 operation (Create Certs)
function menuItem1() {
//Defines the start row and calculates the number of rows to be processed
var sheet = SpreadsheetApp.getActiveSheet();
var scriptProperties = PropertiesService.getScriptProperties();
var startRow= scriptProperties.getProperty('StartRow');
var endRow = scriptProperties.getProperty('EndRow');
// Check to see if any property called startRow is present
if (startRow == null || endRow == null){ //If not present ask for the values from user
startRow = Number(Browser.inputBox("Enter Start Row"));
endRow = Number(Browser.inputBox("Enter End Row"));
scriptProperties.setProperty("EndRow", endRow)
} else { // if present ues those values for this run
startRow = Number(startRow) // Convert String to numbers
endRow = Number(endRow)
}
var numRows = (endRow - startRow) + 1; //this is the row height
var dataRange = sheet.getRange(startRow, 1, numRows, 7);
var counter =0;
var data = dataRange.getValues();
var templateDoc = DriveApp.getFileById("1baxSUxfSdzcVheR3Y2qgieWeSAqNybPfWct1913uRIc");
var templateCopy = templateDoc.makeCopy();
var templateCopyId = templateCopy.getId();
var dateOld;
var courseOld;
var fullNameOld;
var mailFrom = GmailApp.getAliases()
var team = "NWC Online PME Help Desk"
var startTime= (new Date()).getTime(); //set Start time
for (var i = 0; i < data.length; ++i) {
var doc = DocumentApp.openById(templateCopyId);
var body = doc.getActiveSection();
var row = data[i];
var date = row[0];
var nic = row[1];
var course = row[2];
var lastname = row[3];
var firstname = row[4];
var middle = row[5]
var email = row[6];
var subjectTxt = "NWC Online PME Course Certificate";
var fullBody = "PME COURSE COMPLETION CERTIFICATE" + "\n\n";
fullBody += "Your course completion certificate is attached." + "\n\n";
fullBody += "NOTES:" + "\n";
fullBody += "1. DO NOT telephone NWC to resolve PME certificate issues, email our Help Desk: pmecerthelp#usnwc.edu." + "\n";
fullBody += "2. NWC does NOT mail hardcopy certificates." + "\n";
fullBody += "3. NWC does not award certificates for the SNCO JPME courses." + "\n";
fullBody += "4. NWC course completion certificates are not automatically entered into your electronic training or service records." + "\n\n";
fullBody += "Regards," + "\n\n";
fullBody += "U.S. Naval War College Online PME Program Team"+ "\n\n";
fullBody += "Learn more about NWC's Online PME Program at the link below:" + "\n";
fullBody += "http://www.usnwc.edu/Academics/College-of-Distance-Education/PME-(1).aspx" + "\n";
var fullName = firstname+' '+middle+''+lastname
var fdate = Utilities.formatDate(new Date(date), "UTC", "d MMMM yyyy"); //converts UTC date
if(counter ==0){
body.replaceText('fullName',fullName);
body.replaceText('course', course);
body.replaceText('date', fdate);
}//Ends the if condition
else {
body.replaceText(fullNameOld,fullName);
body.replaceText(courseOld, course);
body.replaceText(dateOld, fdate);
}//Ends the else condition
dateOld = fdate;
courseOld = course;
fullNameOld = fullName;
counter ++
doc.saveAndClose();
var attachment = doc.getAs('application/pdf');
//GmailApp.sendEmail(email, subjectTxt, fullBody, {name: team, attachments: attachment, from: mailFrom[1]});
Utilities.sleep(30000)
var currTime = (new Date()).getTime();
if(currTime - startTime >= 240000) { //Check if the script run is over 4minutes , at 5 min the excution might as well might have been terminated
scriptProperties.setProperty("StartRow", startRow + i+1); //The new start, just number of iteration done plus 1 to start from row after that
ScriptApp.newTrigger("menuItem1")
.timeBased()
.at(new Date(currTime+30000)) //restart in 30 secs!
.create();
Logger.log("Last email sent to: " + email)
Logger.log("Next Run start at row: " + startRow + i+1)
return; // End current run.
}//Ends the if Block
}//Ends the first for loop
}//Ends menu
The way this works is to use the scriptProperties to store your lastRow that was processed and your endRow. If it cannot find those values it will ask the user to enter it!
var scriptProperties = PropertiesService.getScriptProperties();
var startRow= scriptProperties.getProperty('StartRow');
var endRow = scriptProperties.getProperty('EndRow');
// Check to see if any property called startRow is present
if (startRow == null || endRow == null){ //If not present ask for the values from user
startRow = Browser.inputBox("Enter Start Row");
endRow = Browser.inputBox("Enter End Row");
scriptProperties.setProperty("EndRow", endRow)
} else { // if present ues those values for this run
startRow = Number(startRow) // Convert String to numbers
endRow = Number(endRow)
}
The below code will check to see if it has gone past the 4-minute mark if so, set up a time trigger and modify the startrow property to the new row after the last processed one. Then exit the function using return.
var currTime = (new Date()).getTime();
if(currTime - startTime >= 240000) { //Check if the script run is over 4minutes , at 5 min the excution might as well might have been terminated
scriptProperties.setProperty("StartRow", startRow + i+1); //The new start, just number of iteration done plus 1 to start from row after that
ScriptApp.newTrigger("menuItem1")
.timeBased()
.at(new Date(currTime+30000)) //restart in 30 secs!
.create();
Logger.log("Last email sent to: " + email)
Logger.log("Next Run start at row: " + startRow + i+1)
return; // End current run.
}//Ends the if Block
Debugging:
You will notice these lines near your GmailApp Lines:
//GmailApp.sendEmail(email, subjectTxt, fullBody, {name: team, attachments: attachment, from: mailFrom[1]});
Utilities.sleep(30000)
Commented out your GamilApp line and instead put a sleep for 30 seconds, this is will help test the code. Once you run the code find the execution transcript under "View". And find these values:
Logger.log("Last email sent to: " + email)
Logger.log("Next Run start at row: " + startRow + i+1)
That way you can manually match the last email sent and next startRow to make sure everything works fine before you go live.
Edit
Finally, to reset your script properties if you run into any issues, run the below function.
function resetScript(){
var scriptProperties = PropertiesService.getScriptProperties();
Logger.log(scriptProperties.getProperties())
scriptProperties.deleteAllProperties()
}
Hope that helps
Working with Jack Brown for most of the day, here is the final answer:
//Creates the custom menu in the spreadsheet "Run Script"
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Script')
.addItem('Create Certs', 'menuItem1')
.addToUi();
}//Ends the custom menu in the spreadsheet
//Runs the menuItem 1 operation (Create Certs)
function menuItem1() { //Defines the start row and calculates the number of rows to be processed
var sheet = SpreadsheetApp.getActiveSheet();
var scriptProperties = PropertiesService.getScriptProperties(); // starts the Script Properties service for storing the last row completed
var startRow= scriptProperties.getProperty('StartRow');
var endRow = scriptProperties.getProperty('EndRow');
// Check to see if any property called startRow is present
if (startRow == null || endRow == null){ //If not present ask for the startRow and endRow values from user
startRow = Number(Browser.inputBox("Enter Start Row"));
endRow = Number(Browser.inputBox("Enter End Row"));
scriptProperties.setProperty("EndRow", endRow)
}// ends the if condition
else { // if present ues those values for this run
startRow = Number(startRow) // Convert startRow string to a number
endRow = Number(endRow) // Convert endRow string to a number
} //end the else condition
var numRows = (endRow - startRow) + 1; //this is the row height
if(numRows < 1){
scriptProperties.deleteAllProperties()
return;
} //ends the if condition
var dataRange = sheet.getRange(startRow, 1, numRows, 7);
var counter =0;
var data = dataRange.getValues();
var templateDoc = DriveApp.getFileById("1baxSUxfSdzcVheR3Y2qgieWeSAqNybPfWct1913uRIc");
var templateCopy = templateDoc.makeCopy();
var templateCopyId = templateCopy.getId();
var dateOld;
var courseOld;
var fullNameOld;
var mailFrom = GmailApp.getAliases() //gets the alias address the email is sent from
var team = "NWC Online PME Help Desk"
var startTime= (new Date()).getTime(); //sets the script Start time
for (var i = 0; i < data.length; ++i) { // Populates the certificate with row data and builds the custom email
var doc = DocumentApp.openById(templateCopyId);
var body = doc.getActiveSection();
var row = data[i]; // specifies the active row [i]
var date = row[0]; // date on the cert for row [i] (column 0)
var nic = row[1]; // course nickname for row [i] (column 1)
var course = row[2]; // course name for row [i] (column 2)
var lastname = row[3]; // learner's last name for row [i] (column 3)
var firstname = row[4]; // learner's first name for row [i] (column 4)
var middle = row[5]; // learner's middle initial for row [i] (column 5)
var email = row[6]; // learner's email address for row [i] (column 6)
var subjectTxt = "NWC Online PME Course Certificate"; // email's subject line
var fullBody = "PME COURSE COMPLETION CERTIFICATE" + "\n\n"; //email's header
fullBody += "Your course completion certificate is attached." + "\n\n";
fullBody += "NOTES:" + "\n";
fullBody += "1. DO NOT telephone NWC to resolve PME certificate issues, email our Help Desk: pmecerthelp#usnwc.edu." + "\n";
fullBody += "2. NWC does NOT mail hardcopy certificates." + "\n";
fullBody += "3. NWC does not award certificates for the SNCO JPME courses." + "\n";
fullBody += "4. NWC course completion certificates are not automatically entered into your electronic training or service records." + "\n\n";
fullBody += "Regards," + "\n\n";
fullBody += "U.S. Naval War College Online PME Program Team"+ "\n\n";
fullBody += "Learn more about NWC's Online PME Program at the link below:" + "\n";
fullBody += "http://www.usnwc.edu/Academics/College-of-Distance-Education/PME-(1).aspx" + "\n";
var fullName = firstname+' '+middle+''+lastname
var fdate = Utilities.formatDate(new Date(date), "UTC", "d MMMM yyyy"); //converts the UTC date to the desired format
if(counter == 0){
body.replaceText('fullName',fullName);
body.replaceText('course', course);
body.replaceText('date', fdate);
}//Ends the if condition
else {
body.replaceText(fullNameOld,fullName);
body.replaceText(courseOld, course);
body.replaceText(dateOld, fdate);
}//Ends the else condition
dateOld = fdate;
courseOld = course;
fullNameOld = fullName;
counter ++
doc.saveAndClose(); //creates the learner's certificate
var attachment = doc.getAs('application/pdf'); // converts the learner's certificate from a doc file to a PDF
GmailApp.sendEmail(email, subjectTxt, fullBody, {name: team, attachments: attachment, from: mailFrom[1]}); // sends the learner's certificate as an attachment to the email
// Utilities.sleep(2500) // When sleeping the GmailApp line, enables testing of the script without impact on Gmail's email quota
sheet.getRange(startRow + i , 8).setValue("Done at: "+ new Date()) // puts the sent date/time in column 8 of the spreadsheet
var currTime = (new Date()).getTime(); //gets the current date/time for the script to determine run time
if(currTime - startTime >= 240000) { //Checks if the script run is over 4 minutes
clearTriggers() //clears the script's triggers
scriptProperties.setProperty("StartRow", startRow + i+1); // The new startRow, just number of iterations done + 1 to start with the next row
ScriptApp.newTrigger("menuItem1")
.timeBased()
.at(new Date(currTime+60000)) //restarts the script in 60 sec to finish cleanly
.create();
Logger.log("Last email sent to: " + email)
Logger.log("Next Run start at row: " + (startRow + i+1))
return; // Ends the current run.
}//Ends the if condition
}//Ends the first for loop
// Logger.log("This shouldnt run") was used in testing, not needed now.
DriveApp.getFileById(templateCopyId).setTrashed(true); // deletes the working copy of the document template
clearTriggers() // function defined in Reset Script
resetScript() // function defined in Reset Script
}//Ends menuItem1
//Comments
//Jagannathan
//Ok this seems to work, also note i modified this:
//.at(new Date(currTime+60000))
//To run after minute, the trigger are not sensititve to 30 sec, so they where not triggering properply.
//Again copy this as is run it your spreadsheet when you get a chance.
//Once you are satisfied with the mock run, remove the comment form Gmail and comment sleep and run it
//Leave the sheet.getRange() line to so that you keep on eye on how it triggers. And in case it fails you know where to start from.
And here is the additional script file that defines resetScript() and clearTriggers()
function resetScript(){
var scriptProperties = PropertiesService.getScriptProperties();
Logger.log(scriptProperties.getProperties())
scriptProperties.deleteAllProperties()
}
function clearTriggers(){
var triggers = ScriptApp.getUserTriggers(SpreadsheetApp.getActive())
for (var i =0 ; i< triggers.length ; i++)
{
if(triggers[i].getHandlerFunction() == "menuItem1") {
ScriptApp.deleteTrigger(triggers[i])
}
}
}
Related
I have a relatively basic set of code here that is programmed to roll 3 dice and give me the results whenever I type "/roll" in discord.
However, the results displayed on my command terminal are always different from those in the discord message from the bot.
async execute(interaction)
{
var num = 3;
num = Number(num);
function rollingDice(num) {
// make the dice rolling "num" times.
// return the results.
var diceResults = "";
for (var i = 0; i < num; i++) {
var resultOfEachDice = "";
resultOfEachDice = Math.floor((Math.random() * 6) + 1);
diceResults += resultOfEachDice + ", ";
}
var lastComma = diceResults.lastIndexOf(", ");
diceResults = diceResults.slice(0, lastComma);
return diceResults;
}
var diceResults = rollingDice()
console.log("Rolled " + num + " dice: " + rollingDice(num));
console.log(process.argv);
await interaction.reply('You rolled: ' + rollingDice(num));
So I will type /roll and my terminal will say I rolled "3, 5, 2" while the message would have something entirely different like "1, 6, 4".
This happens every time I run the command and I am not sure what the issue is.
The code below runs with no apparent errors, but despite I've looked for all possible causes I could think of, I couldn't find the reason why it gets the correct row iterated, marks it as processed ("Sim), and from the 2º iteration on, it gets the new row, but repeats the others already iterated over.
function formToData() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var ss = sheet.getSheetByName("Form Responses 1");
var targetSheet = sheet.getSheetByName("Cadastro de Cliente");
var StartRow = 2;
var RowRange = ss.getLastRow() - StartRow + 1;
var WholeRange = ss.getRange(StartRow, 1, RowRange, 30);
var AllValues = WholeRange.getValues();
var message = "";
for (var i = 0; i < AllValues.length; i++) {
var currentRow = AllValues[i];
//if row has been sent, then continue to next iteration
if (currentRow[0] != "" && currentRow[29] != "Sim") {
//set the row to look at
var setRow = parseInt(i) + StartRow;
var data = currentRow[0];
var dataFormatted = Utilities.formatDate(data, SpreadsheetApp.getActive().getSpreadsheetTimeZone(), "dd/MM/yyyy', às 'HH:mm") + "hs";
//set HTML template for information
message +=
"<p><b>Data: </b>" + dataFormatted + "</p>" +
"<p><b>Unidade: </b>" + currentRow[1] + "</p>"
//mark row as "sent"
ss.getRange(setRow, 30).setValue("Sim");
var values = targetSheet.getRange("A:A").getValues();
var maxIndex = values.reduce(function (maxIndex, row, index) {
return row[0] === "" ? maxIndex : index;
}, 0);
targetSheet.getRange(maxIndex + 2, 1, 1, 30)
.setNumberFormat("#")
.setValues([currentRow]);
var sendTo = "email";
var subject = "Cadastro de cliente novo";
if (message) {
MailApp.sendEmail({
to: sendTo,
subject: subject,
name: "Comercial - Emape",
htmlBody: message,
});
}
}
}//For loop closes
}
I'd appreaciate if you could help me find the flaw.
I tried to replicate this behavior, however, on a static sheet the script performs as expected:
Set unmarked rows in column 30 as 'Sim'.
Copy these rows to a separate sheet starting from the first empty row (or whichever row that Column A is empty).
Given the names of the functions and the sheets, this may be a Sheet generated by a Google Form. These sheets are dynamic, and there is a possibility that the contents may change while your script is running, especially if users are allowed to edit responses.
As a workaround, I suggest to lock the form before running the script:
Also, check the contents of the sheet and form for any possibility that the "Sim" mark might be overwritten by new or edited form data, maybe the sheet is inserting 30 columns instead of 29 or less.
I'm trying to automate the pdf export of a sheet ('BL') which is filled depending on a cell value grabbed on a variable list on sheet 'EiBLdata' by 'i' on each loop.
It seems to work... more or less.
Instead of having 1st pdf with 1st value,
2nd pdf with 2nd value,
3rd pdf with 3rd value etc.
I get 1st pdf with 1st value,
2nd pdf with 1st value,
3rd pdf with 2nd value etc.
In the end only the 1st pdf has the right name, there is a shift in all the others and the last value isn't exported.
I'm quite a newbie with JavaScript and I admit there is a lot of copy/paste in my code, adapted to my purpose. I can't find what I'm doing wrong.
function printSelectedRange() {
var nomfeuille = "EiBLData"
var nomBL = "BL"
var cc = SpreadsheetApp.getActiveSpreadsheet();
var feuille = cc.getSheetByName(nomfeuille);
var BL = cc.getSheetByName(nomBL);
var tr = BL.getRange('B2').getValue();
var plage = feuille.getRange('A1:A15').getValues();
var cell0 = feuille.getRange(1,1).getValue();
BL.getRange('B2').setValue(cell0);
for (var i = 1; i <= 16; i++) {
var cell = feuille.getRange(i,1).getValue();
if (cell > 0) {
BL.getRange('B2').setValue(cell)
var sheetName = "BL";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(sheetName);
var ssUrl = ss.getUrl();
var sheetId= sheet.getSheetId();
var url = ssUrl.replace(/\/edit.*$/,'')
+ '/export?exportformat=pdf&format=pdf'
+ '&size=A6'
+ '&portrait=false'
+ '&fitw=false'
+ '&scale=4'
+ '&top_margin=0.35'
+ '&bottom_margin=0.00'
+ '&left_margin=0.35'
+ '&right_margin=0.0'
+ '&sheetnames=false'
+ '&printtitle=false'
+ '&pagenum=false'
+ '&gridlines=false'
+ '&fzr=FALSE'
+ '&gid='+sheetId;
var token = ScriptApp.getOAuthToken();
var docurl = UrlFetchApp.fetch(url, { headers: { 'Authorization': 'Bearer ' + token } });
var pdf = docurl.getAs('application/pdf');
var file = DriveApp.createFile(pdf);
var docId = sheet.getRange('F13').getValue();
var clientName = sheet.getRange('E9').getValue();
var docDate = sheet.getRange('F14').getValue();
var mois = docDate.getMonth()
docDate.setMonth((mois+1) % 12);
var docDateMMYY = docDate.getMonth()+"-"+docDate.getFullYear().toString().substr(-2);
var docName = "BL-"+docId+"-"+clientName+"-"+docDateMMYY ;
var folder = DriveApp.getFolderById("xxxxxxxxxxxxxxxxxxxx");
var finalFile = file.makeCopy(docName,folder);
file.setTrashed(true);
};
};
var cmdes = ss.getSheetByName('cmdes');
var raz = cmdes.getRange('S2:S501').setValue(false);
}
I think I had a good intuition and finally found the reason of my issue:
how-to-pause-app-scripts-until-spreadsheet-finishes-calculation
It looks like the loop runs faster than the spreadsheet calculation.
Sorry for inconvenience...
I want to compare my list of stocks price with my set Stoploss which is stored and once the condition trigger alert by email. Below is my code
function emailAlert()
{
var stock1nameRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Watchlist").getRange("A5");
var stock1name = stock1nameRange.getValues();
var stock1cmpRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Watchlist").getRange("B5");
var stock1cmp = stock1cmpRange.getValues();
var stock1s1Range = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Watchlist").getRange("AK5");
var s1 = stock1s1Range.getValues();
var stock1s2Range = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Watchlist").getRange("AL5");
var s2 = stock1s2Range.getValues();
var ui = SpreadsheetApp.getUi();
if(stock1cmp<s1)
{
if(stock1cmp<s2)
{
ui.alert( stock1name + stock1cmp + 'is less than' +s2 );
var message = stock1name + stock1cmp + 'is less than' +s2 ;
MailApp.sendEmail("#gmail.com", "Stock Watchlist alert", message)
return s2
}
else
{
ui.alert( stock1name + stock1cmp + 'is less than' +s1 );
var message = stock1name + stock1cmp + 'is less than' +s1 ;
MailApp.sendEmail("#gmail.com", "Stock Watchlist alert", message )
return s1
}
}
}
This is for single stock. How can I make it more generic and compile all the stock list which pass the condition into single mail. Thanks.
Sen
In order to be able to select all the values you will be needing a for loop.
Snippet
function emailAlert() {
var ui = SpreadsheetApp.getUi();
var sheet = SpreadsheetApp.getActive().getSheetByName("Watchlist");
var stockName = sheet.getRange("A5:FINAL_RANGE").getValues();
var stockCmp = sheet.getRange("B5:FINAL_RANGE").getValues();
var s1 = sheet.getRange("AK5:FINAL_RANGE").getValues();
var s2 = sheet.getRange("AL5:FINAL_RANGE").getValues();
for (var i = 0; i < stockName.length; i++) {
if (stockCmp[i][0] < s1[i][0]) {
if (stockCmp[i][0] < s2[i][0]) {
ui.alert(stockName[i][0] + stockCmp[i][0] + ' is less than ' + s2[i][0]);
var message = stockName[i][0] + stockCmp[i][0] + ' is less than ' + s2[i][0];
MailApp.sendEmail("#gmail.com", "Stock Watchlist Alert", message);
return s2[i][0];
} else {
ui.alert(stockName[i][0] + stockCmp[i][0] + ' is less than ' + s1[i][0]);
var message = stockName[i][0] + stockCmp[i][0] + ' is less than ' + s1[i][0];
MailApp.sendEmail("#gmail.com", "Stock Watchlist Alert", message);
return s1[i][0];
}
}
}
}
Explanation
The above code loops through all the values from your columns by using a for loop and then based on the conditions you set, is sending the email and alerting the user. The range is retrieved by using the getRange() method with the a1Notation parameter. The a1Notation parameter here is represented by the start and the end of the range in which you have the values you need for the script.
Note
The above script is built considering the fact that the stockName, stockCmp, s1, s2 are all associated, meaning that they all have the same number of values stored in them.
Reference
Apps Script Range Class - getValues();
Apps Script Sheet Class - getRange(a1Notation);
JavaScript For Loop.
I wrote a script that gets a rows data from a spreadsheet and loops through them, calling a function to send an SMS if the rows' data meets certain conditions (having a phone number and not having already been sent for example).
However after adding about 600 rows, the script execution time exceeds it's limit, that seems to be 5 minutes according to my research. I'm using JavaScript objects to read data and a for loop to iterate through the rows.
Can anyone tel me if it is possible to make it faster? I'm very new to programming but this seems such a light task for all this computing power that I can't understand why it takes so long
Thanks in advance!
Here's the code of the function I'm using:
// Will send SMS on the currently active sheet
function sendSms() {
// Use the send sms menu to trigger reconcile
var user = ScriptProperties.getProperty(PROPERTY_USER_RECONCILE);
if (user == null)
reconcileUser();
// The sheets
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Registo");
var settingsSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Settings");
// Fetch values for each row in the Range.
var startRow = 2;
var apiKey = settingsSheet.getRange("B2").getValue();
var apiSecret = settingsSheet.getRange("B3").getValue();
var prefix = settingsSheet.getRange("B4").getValue();
var numRows = sheet.getMaxRows() - 1;
var numCols = 16;
var statusColNum = 15; // IMPT: To keep track status in col 15
var dataRange = sheet.getRange(startRow, 1, numRows, numCols);
// Make sure there is API key and secret
if (apiKey == "" || apiSecret == "") {
Browser.msgBox("You MUST fill in your API key and secret in Settings sheet first!");
return;
}
// Create one JavaScript object per row of data.
var objects = getRowsData(sheet, dataRange);
var totalSent = 0;
for (var i = 0; i < objects.length; ++i) {
// Get a row object
var rowData = objects[i];
var ss = SpreadsheetApp.getActiveSpreadsheet();
var templateSheet = ss.getSheetByName("SMS Modelo");
var template = templateSheet.getRange("A1").getValue();
// jump loop iteration if conditions not satisied
if (rowData.resolv == "x" || rowData.contactoUtente == null || rowData.contactoUtente == "" || rowData.reserv == null || rowData.reserv == "" || rowData.cont == "x" || rowData.sms !== null) continue;
var message = fillInTemplateFromObject(template, rowData);
var senderName = "Farm Cunha"
var mobile = rowData.contactoUtente;
// Send via Nexmo API
var response = nexmoSendSms(apiKey, apiSecret,"+351" + mobile, message, senderName);
if (response.getResponseCode() == 200) {
var object = JSON.parse(response.getContentText());
if (object.messages[0]['status'] == "0") {
// Set to QUEUE status - We assumed SENT, as we don't handle delivery status.
//sheet.getRange(startRow + i, statusColNum).setValue(STATUS_QUEUE);
sheet.getRange(startRow + i, statusColNum).setValue(STATUS_SENT);
// Set the reference id
sheet.getRange(startRow + i, 19).setValue(object.messages[0]['message-id']);
// sheet.getRange(startRow + i, statusColNum+3).setValue(new Date()); linha pode ser activada para fazer timestamp do envio
totalSent++;
}
else {
// If status is not 0, then it is an error.
// Set status to the error text
sheet.getRange(startRow + i, statusColNum).setValue(object.messages[0]['error-text']);
}
}
else {
// Non 200 OK response
sheet.getRange(startRow + i, statusColNum).setValue("Error Response Code: " + response.getResponseCode);
}
SpreadsheetApp.flush();
// Need a wait. Need to throttle else will have "Route Busy" error.
Utilities.sleep(2000);
}
// Update total sent
var lastTotalSent = parseInt(ScriptProperties.getProperty(PROPERTY_SMS_SENT_FOR_RECONCILE));
if (isNaN(lastTotalSent)) lastTotalSent = 0;
ScriptProperties.setProperty(PROPERTY_SMS_SENT_FOR_RECONCILE, (lastTotalSent + totalSent).toString());
Logger.log("Last sent: " + lastTotalSent + " now sent: " + totalSent);
reconcileApp();
}
You have a few things in your loop that are too time consuming : spreadsheet readings and API calls + 2 seconds sleep !.
I would obviously advise you to take these out of the loop (specially the template sheet reading that is always the same!). A possible solution would be to check the conditions from the row objects and to save the valid entries in an array... THEN iterate in this array to call the API.
If this is still too long then proceed by small batches, saving the end position of the partial iteration in scriptproperties and using a timer trigger that will continue the process every 5 minutes until it is completed (and kill the trigger at the end).
There are a few example of this kind of "mechanics" on this forum, one recent example I suggested is here (it's more like a draft but the idea is there)
Ok, I've solved it by taking these 3 lines out of the loop as Serge (thanks) had told me to:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var templateSheet = ss.getSheetByName("SMS Modelo");
var template = templateSheet.getRange("A1").getValue();
It's so simple that I don't know how I was not seeing that.
This simple change made the script much faster. For example, going through 600 rows would take more than 5 minutes. Now, more than 5000 rows only take seconds.