Google Apps Script - Variable Passed Between Functions Shows Up as Undefined - google-apps-script

I have some code where there are some if/else if statements to determine the reason that a Google Form has closed. I want to pass this reason to another function which will send an email notifying me that the form has closed and the why. I don't know why the reason, which should be a string, is not getting passed to the function that sends the email.
I'm not dealing with the form at all in this code. For simplicity, I opened a new standalone script, not linked to any Google Form or Sheet, to test just this portion (determine reason and send email). Here is the code:
First function to determine reason the form closed and to call second function to send email:
function FindReason() {
var Day1Seats = 5;
var Day2Seats = 5;
var Day1Responses = 6;
var Day2Responses = 1;
var FormClose = false;
if(Day1Responses >= Day1Seats) {
var reason = "All seats taken for Day 1";
SendAnEmail(reason);
}
else if(Day2Responses >= Day2Seats) {
var reason = "All seats taken for Day 2";
SendAnEmail(reason);
}
else if(FormClose) {
var reason = "Form close date";
SendAnEmail(reason);
}
}
Second function, which sends email (email address obviously changed for privacy):
function SendAnEmail(closereason) {
var recipientTO = "name#example.com";
var subject = "This is the subject";
//var closereason = "All seats taken for Day 1";
MailApp.sendEmail({
to: recipientTO,
subject: subject,
htmlBody: (new Date()) + "<br>Your form has been closed because: <br>" + closereason + "<br>End of message."
});
}
When I run FindReason(), I do get an email but the body doesn't properly show the reason. This is an example of the emails I get:
Sun Feb 24 2019 12:26:01 GMT-0800 (PST)
Your form has been closed because:
undefined
End of message.
I don't know why the reason is shown as undefined. I tried setting the reason inside the SendAnEmail function (the line that has been commented out) and that worked properly, but I need to be able to change the reason.
Edit: It works now. It turns out the "run" button was set to run the "SendAnEmail" function rather than the "FindReason" function. Thanks to everyone who offered suggestions.

What's probably happening is that else if(FormClose = TRUE) is not going to work.
= is the assignment operator, you want equality, which is ===.
TRUE is lowercase in js, so you want true.
You also use False, which should be false.
If you look in the google scripts editor's console, you should see an error of some sort, because it won't know what False or TRUE are (it will think they are undeclared variables).
try else if(FormClose === true) or even more simply, else if (FormClose), because if it evaluates to true, that will do the job.
function FindReason() {
var Day1Seats = 5;
var Day2Seats = 5;
var Day1Responses = 6;
var Day2Responses = 1;
var FormClose = false;
if(Day1Responses >= Day1Seats) {
var reason = "All seats taken for Day 1";
SendAnEmail(reason);
} else if(Day2Responses >= Day2Seats) {
var reason = "All seats taken for Day 2";
SendAnEmail(reason);
} else if(FormClose) {
var reason = "Form close date";
SendAnEmail(reason);
}
}

Related

How to add a timer to a Google Script

I'm looking at a cooldown to a function I've created so that once the function has been ran and the "else' tag has been ran that it wouldn't attempt to run again for 15 minutes.
Is there a simple way of doing this. My code is below and I've inserted a add here section for where I would want it to run.
function Cancel() {
var SS = SpreadsheetApp.getActiveSpreadsheet();
var ui = SpreadsheetApp.getUi();
// first prompt
var presult = ui.prompt(
"Please Enter the Password!",
ui.ButtonSet.OK_CANCEL);
var password = "Money2022";
var pbutton = presult.getSelectedButton();
var ptext = presult.getResponseText();
// User clicked "OK" on first prompt
if (pbutton == ui.Button.CANCEL) {
ui.alert('Wrong Answer Buddy!');
SpreadsheetApp.getActive().getSheetByName('2022').hideSheet();
} else if (pbutton == ui.Button.CLOSE) {
ui.alert('Wrong Answer Buddy!');
SpreadsheetApp.getActive().getSheetByName('2022').hideSheet();
} else if (ptext != password) {
Password();
} else {
**!!INSERT FUNCTION HERE!!**
}
}
function Password() {
var SS = SpreadsheetApp.getActiveSpreadsheet();
var ui = SpreadsheetApp.getUi();
var response = ui.alert("Wrong Answer Buddy!",
ui.ButtonSet.OK_CANCEL);
if (response == ui.Button.CANCEL) {
ui.alert("BYE BYE!");
SpreadsheetApp.getActive().getSheetByName('2022').hideSheet();
} else if (response == ui.Button.CLOSE) {
ui.alert("BYE BYE!");
SpreadsheetApp.getActive().getSheetByName('2022').hideSheet();
} else {
Cancel();
}
}
Seems like Cooper's suggestion of using Utilities.sleep() already worked for you but I'd like to add a little more information and another way to do this for anyone else with a similar question.
Since you mentioned that you wanted to cooldown for 15 minutes you should know that the maximum sleep time for Utilities.sleep() is 300000 milliseconds or 5 minutes. You can find this documented here.
As an alternative to bypass this limit you can consider using the Properties service. This allows you to save properties for either the users or the entire script, so you can set a property with a time x minutes from now and the script can check if it's passed this time before running. This allows you to set the cooldown as long as you want, and choose between having different cooldowns for each user or lock the entire document.
Here's an example of how you could do it:
function yourProcess(){
var properties = PropertiesService.getUserProperties() //gets all properties
var cooldownprop = properties.getProperty("cooldown") //gets cooldown property
var now = new Date()
//if cooldown has not been set it just sets the current time so the user can go through
var cooldown = cooldownprop == null ? now : new Date(cooldownprop)
if (now >= cooldown){
//run your code
//this sets the cooldown if needed, add the time in milliseconds
properties.setProperty("cooldown", new Date(now.getTime()+900000))
} else {
//tell the user they're on cooldown
}
}
For more information and samples on how to use the properties you can check out the documentation.
Sources:
Utilities.sleep()
Properties Service

How do I send an automated email to a specific person, depending on task status, using an aux sheet to store emails?

Gory title but I couldn't find a way of being clearer.
I have no experience with coding and I was wondering if doing something like what I'm about to explain would be possible.
This is my example sheet:
What I'm looking to do is to have automated emails sent out to the person assigned to the task if the task status is set to urgent, while referencing people by names and having an auxiliary sheet with all the names and corresponding emails.
I've browsed around and found some similar questions which I unfortunately had no success in adapting. The one thing I got is that I need to setup an onEdit trigger, which I've done, but I'm completely clueless from here on out.
Can someone point me in the right direction? I don't have a clue where to start.
Looking forward to hearing your advice.
Thanks and stay safe in these crazy times!
It was a funny exercise. I tried to make the script as clean and reusable as possible for others to be able to adapt it to their needs.
Usage
Open spreadsheet you want to add script to.
Open Script Editor: Tools / Script editor.
Add the code. It can be configured by adjusting variables in the top:
var trackerSheetName = 'Tracker 1'
var trackerSheetStatusColumnIndex = 2
var trackerSheetNameColumnIndex = 4
var triggeringStatusValue = 'Urgent'
var peopleSheetName = 'AUX'
var peopleSheetNameColumnIndex = 1
var peopleSheetEmailColumnIndex = 2
var emailSubject = 'We need your attention'
var emailBody = 'It is urgent'
function checkStatusUpdate(e) {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
var activeSheet = spreadsheet.getActiveSheet()
// skip if different sheet edited
if (activeSheet.getName() !== trackerSheetName) {
return
}
var editedRange = e.range
// skip if not a single cell edit
if (editedRange.columnStart !== editedRange.columnEnd || editedRange.rowStart !== editedRange.rowEnd) {
return
}
// skip if edited cell is not from Status column
if (editedRange.columnStart !== trackerSheetStatusColumnIndex) {
return
}
// skip if Status changed to something other than we're looking for
if (e.value !== triggeringStatusValue) {
return
}
var assigneeName = activeSheet.getRange(editedRange.rowStart, trackerSheetNameColumnIndex, 1, 1).getValue()
var peopleSheet = spreadsheet.getSheetByName(peopleSheetName)
var people = peopleSheet.getRange(2, 1, peopleSheet.getMaxRows(), peopleSheet.getMaxColumns()).getValues()
// filter out empty rows
people.filter(function (person) {
return person[peopleSheetNameColumnIndex - 1] && person[peopleSheetEmailColumnIndex - 1]
}).forEach(function (person) {
if (person[peopleSheetNameColumnIndex - 1] === assigneeName) {
var email = person[peopleSheetEmailColumnIndex - 1]
MailApp.sendEmail(email, emailSubject, emailBody)
}
})
}
Save the code in editor.
Open Installable Triggers page: Edit / Current project's triggers.
Create a new trigger. Set Event Type to On edit. Keep other options default.
Save the Trigger and confirm granting the script permissions to access spreadsheets and send email on your behalf.
Go back to your spreadsheet and try changing status in Tracker 1 tab for any of the rows. Corresponding recipient should receive an email shortly.
This should get you started:
You will need to create an installable trigger for onMyEdit function. The dialog will help you to design you email by giving you an html format to display it. When you're ready just comment out the dialog and remove the // from in front of the GmailApp.sendEdmail() line.
function onMyEdit(e) {
//e.source.toast('Entry');
const sh=e.range.getSheet();
if(sh.getName()=="Tracker") {
if(e.range.columnStart==2 && e.value=='Urgent') {
//e.source.toast('flag1');
const title=e.range.offset(0,-1).getValue();
const desc=e.range.offset(0,1).getValue();
const comm=e.range.offset(0,3).getValue();
if(title && desc) {
var html=Utilities.formatString('<br />Task Title:%s<br />Desc:%s<br />Comments:%s',title,desc,comm?comm:"No Additional Comments");
//GmailApp.sendEmail(e.range.offset(0,2).getValue(), "Urgent Message from Tracker", '',{htmlBody:html});
SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutput(html).setWidth(600), 'Tracker Message');
e.source.toast('Email Sent');
}else{
e.source.toast('Missing Inputs');
}
}
}
}
GmailApp.sendEmail()

"Cannot call SpreadsheetApp.getUi() from this context" error while not using getUi() on time-based trigger

I am trying to run a function every night that checks a list of dates and does work if it finds that a date has passed and all the checkboxes on that row are checked. Every day, though, I get an email saying
"Cannot call SpreadsheetApp.getUi() from this context. (line 172, file "Code")".
The weird thing is that I don't use getUi() anywhere in my CheckHireDates function and the line that it specifies is not even in the function that is supposed to run. Line 172 is in my onEdit function which also doesn't use getUi(). Can anybody help me understand why I'm getting this error?
I did use getUi in my onOpen function, so I commented it out when I started having this problem but it still didn't fix anything.
function CheckHireDates() {
var spreadsheet = SpreadsheetApp.getActive();
var PaycomSheet = spreadsheet.getSheetByName('Paycom');
var TechSheet = spreadsheet.getSheetByName("Tech Key");
var SoftwareTracker = spreadsheet.getSheetByName('Software Tracking');
var range = "R2:R";
var Hvals = PaycomSheet.getRange("H2:H").getValues();
var Hlast = Hvals.filter(String).length;
var data = PaycomSheet.getRange(range).getValues();
var today = new Date().toLocaleDateString();
for(var i = 0; i < Hlast;i++)
{
Hvals[i].toLocaleString();
if(Hvals[i] <= today && (PaycomSheet.getRange('R' + (i+2)).getValue() == true))
{
var fullName = PaycomSheet.getRange('D' + (i+2)).getValue();
var techRow = findMatchingRow(fullName, "Tech Key");
var softwareRow = findMatchingRow(fullName, "Software Tracking");
var position = PaycomSheet.getRange('G' + (i+2)).getValue();
TechSheet.getRange('E' + techRow).setValue(1);
SoftwareTracker.getRange('G' + softwareRow).setValue(1);
if (position == "Pre-Employment, Initial")
{
position = "Initial";
}
else if (position == "Pre-Employment, Ace")
{
position = "Route";
}
else if (position == "Pre-Employment, Expert")
{
position = "Route";
}
else
{
Logger.log("Position not known");
}
TechSheet.getRange('G' + techRow).setValue(position);
SoftwareTracker.getRange('D' + softwareRow).setValue(position);
SoftwareTracker.getRange('H' + softwareRow + ':M' + softwareRow).setValue(2);
if (position !== "Initial")
{
SoftwareTracker.getRange('N' + softwareRow).setValue(2);
}
}
}
}
I had that problem and found it involved another google script in the project set up by a different user. The error message had a mixture of details from my script and the other script:
myFunctionName Cannot call SpreadsheetApp.getUi() from this context.
(line 2, file "OtherScript")
Line 2 of the other script did use getUi()
var app = SpreadsheetApp.getUi();
It seemed that when my script ran (triggered by onChange event) then the other script would also get run (maybe also triggered by a change event). The other script set up some UI elements, some simple menus. Normally that caused no problem. However, SpreadsheetApp.getUi() only works in the context of there being a current instance of an open editor (see https://developers.google.com/apps-script/reference/base/ui). So if a change event happened without an open editor then it fails, causing the error.
I resolved the problem by slightly modifying the other script to catch the problem:
try {
app = SpreadsheetApp.getUi();
} catch (error) {}
if (app != null) {
//Use app...
}
A different approach that might also have worked is to speak with the person and how their script was triggered, and see if they'd change it to trigger by onOpen event that just occurs when someone opens a spreadsheet and hence there is a Ui context.
So, I think your problem would be coming from SpreadsheetApp.getUi() in a different script in the project. See if the error message mentions a different file, like mine did. When your script runs in the night, there would be no context, which explains why the error occurs at that time.

Approval Process: 'e.parameter.approval' inside an "if" cycle

I'm trying to build an Approval flow with Google Apps Script. I would also like to add an "if" cycle that creates a Calendar event if e.parameter.approval is true.
So, everything works but the system doesn't recognize the "if" cycle. I've tried this:
function doGet(e){
var answer = (e.parameter.approval == 'true') ? 'Approved!' : 'Not this time';
MailApp.sendEmail(e.parameter.reply, "Request",
"The answer is: "+ answer);
// here I tried both with (e.parameter.approval === 'true') and (check === answer) and also with "=" "==" and "==="...
var check = 'Approved!';
if(e.parameter.approval === 'true'){
var calendar = CalendarApp.getCalendarById('xxxxxxx')
//create event
var newEventTitle = 'Event Test';
var newEvent = calendar.createAllDayEventSeries(newEventTitle,
Date,
CalendarApp.newRecurrence().addDailyRule().times(10),
{description:'test'});
//get ID
var newEventId = newEvent.getId();
}
var app = UiApp.createApplication();
app.add(app.createHTML('<p><br><h2>An email was sent to '+ e.parameter.reply + ' saying: '+ answer + '</h2>'))
return app
}
Do you have any idea why the system skips the "if" cycle and goes directly to "var app"?
e.parameter.approval might not be returning anything. I could be "undefined". It could be "null". You need to know for sure what e.parameter.approval is doing. There is also the possibility that the if condition is actually working, but then there is an error that kills the code and stops it. You'll need to debug every line. Try using the "debug" tool.
Apps Script Debugging Tool
You are using:
if(e.parameter.approval === 'true'){
The word 'true' is in quotes, which means it's a string value, not a true/false value.
Try:
if(e.parameter.approval === true){

Auto Email Google App Script: do while condition

I been working on this script for long time, it is Google App script, it sends email alert automatically to email provided, and I have script triggered at running every 1 minute.
so if cell total is greater than 201 it will send email to user.
but the problem is it send email every minute script is ran.
I need help with coding where if email has been sent already once, it will not send again unless cell value is less than 201 again and goes back to greater than 201,
I was thinking of making a cell which will contain text "Sent" or "Not Sent"
if it says "Not Sent" let the email code run if total is greater than 201
if is says "Sent" and total is greater than 201 don't let email code run..
I know I am not every clear but It has been very hard to get help on this.
Here is the code.
and if this works I'm sure lots of people can use this script for their use.
function sendEmail(email_address, email_subject, email_message) {
MailApp.sendEmail(email_address, email_subject, email_message);
}
function test_sendEmail() {
var sheet = SpreadsheetApp.getActiveSheet();
var cell = sheet.setActiveCell('A2');
var criterion_cutoff = 201;
var i = 0;
var addr;
var subj;
var msg;
do {
addr = cell.offset(i,0).getValue();
subj = cell.offset(i,1).getValue();
msg = cell.offset(i,2).getValue();
criterion = cell.offset(i,3).getValue();
if(criterion == criterion_cutoff) {
sendEmail(addr,subj,msg);
// Browser.msgBox('Sending email to: ' + addr);
}
i++;
} while( cell.offset(i, 0).getValue().length > 0 )
Browser.msgBox('Done!');
}
so I was think of adding if else condition outside of do while
if(SpreadsheetApp.getActiveSheet().getRange(21,6).getValue() != 'Not Sent') {
do {
//same stuff as above
} while(condition)
}
else
//don't know wht else to do in else condtion so just using googleclock
SpreadsheetApp.getActiveSheet().getRange(2,7).setValue('=GoogleClock()')
I guess I've figured it out on my own. I want to share my solution so other can use it.
so I have added 2 if else condtion
first one will check if cell value is < 201
if it is less than 201 than set somecell example F5 = Not Sent
another if else loop that will check
if value of F5 != Sent and cell value == 201
than it will run the email code
and right after running that email code it will set cell F5 to Sent
so next time loop runs again it will not send email again unless value of cell is
but this has one minor issue it will only work for 1st row out of 3 rows, you can make it for all 3 rows by adding more if conditions, I don't need it so I am not worried about it.
function sendEmail(email_address, email_subject, email_message) {
MailApp.sendEmail(email_address, email_subject, email_message);
}
function test_sendEmail() {
var sheet = SpreadsheetApp.getActiveSheet();
var cell = sheet.setActiveCell('A2');
var criterion_cutoff = 201;
var i = 0;
var addr;
var subj;
var msg;
if((SpreadsheetApp.getActiveSheet().getRange(2,4).getValue() < '201')) {
SpreadsheetApp.getActiveSheet().getRange(2,6).setValue('Not Sent');
}
else
if((SpreadsheetApp.getActiveSheet().getRange(2,6).getValue() != 'Sent') && (SpreadsheetApp.getActiveSheet().getRange(2,4).getValue() == '201')) {
do {
addr = cell.offset(i,0).getValue();
subj = cell.offset(i,1).getValue();
msg = cell.offset(i,2).getValue();
criterion = cell.offset(i,3).getValue();
if(criterion == criterion_cutoff) {
sendEmail(addr,subj,msg);
// Browser.msgBox('Sending email to: ' + addr);
SpreadsheetApp.getActiveSheet().getRange(2,6).setValue('Sent');
}
i++;
} while( cell.offset(i, 0).getValue().length > 0 )
Browser.msgBox('Done!');
}
}