Situation: This is a Leave (or time off) approval/monitoring system for a small office of around 40 people. They put in for leave via google forms which is fed (eventually) into a google sheet. Once a month, we need to send out a single email to each person who took leave containing every entry they made for the month. I have altered the standard javascript from google that will send an email from a spreadsheet but it sends one email per row. I need to send a consolidated email to each person. Prior to running the script, the spreadsheet will only contain records with applicable dates, and will be sorted by [Email] and [Start Date] columns so there is no need to do any of that work in this script.
Here are the all columns in order: Timestamp, Email, Type of Leave Requested, Starting Date, Last Date, Number of Hours, Optional Note, Approval Decision, Optional Explanation.
I've tried creating a 2D array but my previous attempts at such an array have only resulted in using my 100/day google transactions in a matter of seconds. :/ Help is appreciated.
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menu = [{
name: "Send Email",
functionName: "uiSendEmail"
}];
ss.addMenu("Send Email Test", menu);
}
function uiSendEmail() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var range = sheet.getDataRange();
range= range.offset(1, 0, range.getNumRows()-1);
range.getValues().forEach( function( cell, key, data ){
var body = "Type of Leave - " + cell[2] + "\n\nStarting - " + cell[3] + "\n\nEnding - " + cell[4] + "\n\nHours - " + cell[5] + "\n\nNote Provided - " + cell[6] + "\n\n\n";
// "Type of Leave " + cell[2] \n\n;+ "Starting " + cell[3] \n\n + "Ending " + cell[4] \n\n + "Hours " + cell[5] \n\n + "Note provided " + cell[6]
GmailApp.sendEmail(cell[1], "Recent Requests for Time Off", body);
});
}
You could create an object, loop through the values and add them to the corresponding email.
function uiSendEmail() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var range = sheet.getDataRange();
var emails = {};
range= range.offset(1, 0, range.getNumRows()-1);
range.getValues().forEach( function( cell, key, data ){
var body = "Type of Leave - " + cell[2] + "\n\nStarting - " + cell[3] + "\n\nEnding - " + cell[4] + "\n\nHours - " + cell[5] + "\n\nNote Provided - " + cell[6] + "\n\n\n";
if(typeof emails[cell[1]] === 'undefined'){
emails[cell[1]] = body;
} else {
emails[cell[1]] += body;
}
});
//Now loop through the email object and send each completed one
for(var address in emails) {
GmailApp.sendEmail(address, "Recent Requests for Time Off", emails[address]);
}
Related
The code below is a Google Ads script. It does a fairly simple thing: it grabs the ad group impression share values from any 2 given periods, and issues an alert for ad groups whose impression share values dropped by more than 10% between the "pre" and "post" periods. More details are in the comments in the code.
Here's the really, really weird part: everytime I preview the code (by pressing the "Preview" button in the Google Ads script console), it always fails the first time, but then in subsequent tries (2nd, 3rd, etc) it always works.
If I go back to the Google Ads main "Scripts" page and then open the script and preview it again, it seems to get "reset" and fails again, and then succeeds in the 2nd try onwards.
The exact error is: "TypeError: Cannot read property 'preImpressionShare' of undefined".
According to the Google Ads script console, the line that causes the error is the following:
if ((!isNaN(adgroup.preImpressionShare)))
My questions are:
One would think that if the code is wrong, it should always fail. Yet, it works most of the time, and only fails in the first try. Why is it unstable like that?
How do I fix the error so that it will always work? Is the isNaN() function being used incorrectly or what?
var config = {
campaignsContain: '', // The script will only look for campaigns that contain this string - leave blank to work on all campaigns
imprShareDropThreshold: 0.1, // The percent change to trigger an alert (0.1 = 10% change)
emails: 'email#myemail.com', // Comma-separated list of emails to alert
}
function main() {
/*Date settings for the "pre" period.*/
var preStartDate = new Date('January 1, 2022');
var preFormattedStartDate = Utilities.formatDate(preStartDate, AdsApp.currentAccount().getTimeZone(), 'EEE, MMM d, YYYY');
preStartDate = Utilities.formatDate(preStartDate, AdsApp.currentAccount().getTimeZone(), 'YYYYMMdd');
var preEndDate = new Date('January 31, 2022');
var preFormattedEndDate = Utilities.formatDate(preEndDate, AdsApp.currentAccount().getTimeZone(), 'EEE, MMM d, YYYY');
preEndDate = Utilities.formatDate(preEndDate, AdsApp.currentAccount().getTimeZone(), 'YYYYMMdd');
/*Date settings for the "post" period.*/
var postStartDate = new Date('February 1, 2022');
var postFormattedStartDate = Utilities.formatDate(postStartDate, AdsApp.currentAccount().getTimeZone(), 'EEE, MMM d, YYYY');
postStartDate = Utilities.formatDate(postStartDate, AdsApp.currentAccount().getTimeZone(), 'YYYYMMdd');
var postEndDate = new Date('February 28, 2022');
var postFormattedEndDate = Utilities.formatDate(postEndDate, AdsApp.currentAccount().getTimeZone(), 'EEE, MMM d, YYYY');
postEndDate = Utilities.formatDate(postEndDate, AdsApp.currentAccount().getTimeZone(), 'YYYYMMdd');
/*GAQL setup for the "pre" period query*/
var preCampaignFilter = config.campaignsContain.length > 0 ? ' AND CampaignName CONTAINS "' + config.campaignsContain + '" ' : '';
var preQuery = 'SELECT AdGroupId, CampaignName, AdGroupName, SearchImpressionShare FROM ADGROUP_PERFORMANCE_REPORT WHERE AdGroupStatus = ENABLED ' + 'DURING ' + preStartDate + ',' + preEndDate;
var preReport = AdsApp.report(preQuery);
var preAdgroups = [];
var preRows = preReport.rows();
/*GAQL setup for the "post" period query*/
var postCampaignFilter = config.campaignsContain.length > 0 ? ' AND CampaignName CONTAINS "' + config.campaignsContain + '" ' : '';
var postQuery = 'SELECT AdGroupId, CampaignName, AdGroupName, SearchImpressionShare FROM ADGROUP_PERFORMANCE_REPORT WHERE AdGroupStatus = ENABLED ' + 'DURING ' + postStartDate + ',' + postEndDate;
var postReport = AdsApp.report(postQuery);
var postAdgroups = [];
var postRows = postReport.rows();
/*Traverse the "pre" period query results, and add them to an array.*/
while (preRows.hasNext()) {
var preRow = preRows.next();
var preAdgroupid = preRow.AdGroupId;
var preAdgroup = preRow.AdGroupName;
var preCampaign = preRow.CampaignName;
var preImpressionShare = parseFloat(preRow.SearchImpressionShare.replace('%', '')) / 100;
let preAdGroupObject = {};
preAdGroupObject.adgroupid = preAdgroupid;
preAdGroupObject.adgroup = preAdgroup;
preAdGroupObject.campaign = preCampaign;
preAdGroupObject.preImpressionShare = preImpressionShare;
preAdgroups.push(preAdGroupObject);
}
/*Traverse the "post" period query results, and add them to an array.*/
while (postRows.hasNext()) {
var postRow = postRows.next();
var postAdgroupid = postRow.AdGroupId;
var postAdgroup = postRow.AdGroupName;
var postCampaign = postRow.CampaignName;
var postImpressionShare = parseFloat(postRow.SearchImpressionShare.replace('%', '')) / 100;
let postAdGroupObject = {};
postAdGroupObject.adgroupid = postAdgroupid;
postAdGroupObject.adgroup = postAdgroup;
postAdGroupObject.campaign = postCampaign;
postAdGroupObject.postImpressionShare = postImpressionShare;
//if(postImpressionShare > 0.1) {
postAdgroups.push(postAdGroupObject);
//}
}
/*Merge the "pre" query results with the "post" query results* and store everything into one single array*/
mergedAdGroups = mergeArrayObjects(preAdgroups, postAdgroups);
//Traverse the "mergedAdGroups" array and calculate the impression share difference between the pre vs. post period.
//Add the results to the "alerts" array only if the impression share difference is less than the threshold (10%)
var alerts = [];
mergedAdGroups.forEach(
function(adgroup)
{
if ((!isNaN(adgroup.preImpressionShare)))
{
//Logger.log(adgroup.preImpressionShare + " and " + adgroup.postImpressionShare);
var difference = (adgroup.postImpressionShare - adgroup.preImpressionShare) / adgroup.preImpressionShare;
//campaigns[campaign].difference = difference;
if (difference < -config.imprShareDropThreshold)
{
alerts.push(' - ' + adgroup.adgroup + " of the campaign " + adgroup.campaign + ': from ' + (adgroup.preImpressionShare * 100).toFixed(2) + '% to ' + (adgroup.postImpressionShare * 100).toFixed(2) +'%' + " [" + (difference * 100).toFixed(2) + '%' + " drop.]");
}
}
}
);
//Combine an intro message for the email alert with the contents of the "alerts" variable (see above) and store everything into the "message" variable.
var message =
'The following campaigns had impression share drops by more than ' + (Math.abs(config.imprShareDropThreshold) * 100).toFixed(2) + '% between the pre and post period' + '. \n\n' +
alerts.join('\n') + '\n\n' +
'---END OF MESSAGE---';
//This is for debugging (to see the results in the Google Ads script console)
//Logger.log(message);
//This line passes the "message" variable and sends it as an email alert.
//if (alerts.length > 0) {MailApp.sendEmail(config.emails, 'Impression Share Drop Alert!', message);}
}
//This function merges 2 arrays based on common adgroup IDs
function mergeArrayObjects(arr1,arr2){
return arr1.map((item,i)=>{
if(item.adgroupid === arr2[i].adgroupid){
//merging two objects
return Object.assign({},item,arr2[i])
}
})
}
To log sensor data to Google sheets using ESP32, I found a script online to accomplish that. However, I want to modify it so that I could have additional data fields logged. I don't know much about JavaScript but tried to add a new var and modify the code but it doesn't work. Here is the original script:
function doGet(e){
Logger.log("--- doGet ---");
var tag = "",
value = "";
try {
// this helps during debuggin
if (e == null){e={}; e.parameters = {tag:"test",value:"-1"};}
tag = e.parameters.tag;
value = e.parameters.value;
// save the data to spreadsheet
save_data(tag, value);
return ContentService.createTextOutput("Wrote:\n tag: " + tag + "\n value: " + value);
} catch(error) {
Logger.log(error);
return ContentService.createTextOutput("oops...." + error.message
+ "\n" + new Date()
+ "\ntag: " + tag +
+ "\nvalue: " + value);
}
}
// Method to save given data to a sheet
function save_data(tag, value){
Logger.log("--- save_data ---");
try {
var dateTime = new Date();
// Paste the URL of the Google Sheets starting from https thru /edit
// For e.g.: https://docs.google.com/..../edit
var ss = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1-3NY0nbnJgx_RHJeG6GckfVesUXBI5rgxmTJ40yX0vE/edit");
var dataLoggerSheet = ss.getSheetByName("Datalogger");
// Get last edited row from DataLogger sheet
var row = dataLoggerSheet.getLastRow() + 1;
// Start Populating the data
dataLoggerSheet.getRange("A" + row).setValue(row -1); // ID
dataLoggerSheet.getRange("B" + row).setValue(dateTime); // dateTime
dataLoggerSheet.getRange("C" + row).setValue(tag); // tag
dataLoggerSheet.getRange("D" + row).setValue(value); // value
// Update summary sheet
summarySheet.getRange("B1").setValue(dateTime); // Last modified date
// summarySheet.getRange("B2").setValue(row - 1); // Count
}
catch(error) {
Logger.log(JSON.stringify(error));
}
Logger.log("--- save_data end---");
}
Here is the version with my additions:
function doGet(e){
Logger.log("--- doGet ---");
var tag = "",
value = "",
tag2 = "",
val2 = "";
try {
// this helps during debuggin
if (e == null){e={}; e.parameters = {tag:"test",value:"-1", tag2: "test2", val2: "-1"};}
tag = e.parameters.tag;
value = e.parameters.value;
tag2 = e.parameters.tag2;
val2 = e.parameters.val2;
// save the data to spreadsheet
save_data(tag, value, tag2, val2);
return ContentService.createTextOutput("Wrote:\n tag: " + tag + "\n value: " + value + "\n tag2: " + tag2 + "\n val2: " + val2);
} catch(error) {
Logger.log(error);
return ContentService.createTextOutput("oops...." + error.message
+ "\n" + new Date()
+ "\ntag: " + tag
+ "\nvalue: " + value
+ "\n tag2: " + tag2
+ "\n val2: " + val2);
}
}
// Method to save given data to a sheet
function save_data(tag, value, tag2, val2){
Logger.log("--- save_data ---");
try {
var dateTime = new Date();
// Paste the URL of the Google Sheets starting from https thru /edit
// For e.g.: https://docs.google.com/..../edit
var ss = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1-3NY0nbnJgx_RHJeG6GckfVesUXBI5rgxmTJ40yX0vE/edit");
var dataLoggerSheet = ss.getSheetByName("sht31_1");
// Get last edited row from DataLogger sheet
var row = dataLoggerSheet.getLastRow() + 1;
// Start Populating the data
dataLoggerSheet.getRange("A" + row).setValue(row -1); // ID
dataLoggerSheet.getRange("B" + row).setValue(dateTime); // dateTime
dataLoggerSheet.getRange("C" + row).setValue(tag); // tag
dataLoggerSheet.getRange("D" + row).setValue(val); // value
dataLoggerSheet.getRange("E" + row).setValue(tag2); // tag
dataLoggerSheet.getRange("F" + row).setValue(val2); // value
// Update summary sheet
summarySheet.getRange("B1").setValue(dateTime); // Last modified date
// summarySheet.getRange("B2").setValue(row - 1); // Count
}
catch(error) {
Logger.log(JSON.stringify(error));
}
Logger.log("--- save_data end---");
}
So when I send the data using a GET request, only values of original variables (tag and value) are posted to the google sheet. Could you please tell how can I fix it?
EDIT: I have tried making a GET request manually using Postman to test if the script works. Here is the request:
https://script.google.com/macros/s/AKfycbwibc1auwDUtlRPIkIpO53FM6DivTLLNscMjBPGOaeajcVkZb6i7LH6gwp2Lw8I174B/exec?tag=Temp&value=21.13&tag2=Humid&val2=47.87
I am consistently getting a failure report on a time-based trigger I attempted to put into my app script. The script is intended to fire every morning and send out email reminders based on day counts.
I have tried to adjust the script function name that it is calling and change the type of trigger from days to hours.
function loops() {
// Runs at approximately 8:30am in the timezone of the script
ScriptApp.newTrigger('loops')
.timeBased()
.atHour(8)
.nearMinute(30)
.everyDays(1)
.create();
var report = SpreadsheetApp.getActive();
var ss = SpreadsheetApp.getActiveSpreadsheet()
var emailList = ss.getSheetByName("Email Reminder");
var data = emailList.getRange(3, 19, emailList.getLastRow() - 1, 24).getValues();
//Logger.log(data);
data.forEach(function (row, i) {
var id = row[0];
var customer = row[1];
var user = row[2];
var URL = row[3];
var proposedCount = row[4];
var pendingCount = row[5];
if (proposedCount == 36) {
MailApp.sendEmail(user, customer, customer + " has been in the proposal stage for " + proposedCount + " days." +"\n" +"\n" + URL + "\n" + "\nThis is a reminder to keep this job in front of you :)" + "\n" + "\nYour friendly neighborhood accountability partner");
}
if (pendingCount == 15) {
MailApp.sendEmail(user, customer, customer + " has been in the pending stage for" + pendingCount + " days." + "\n" + "\n" + URL + "\n" + "\nThis is a reminder to keep this job in front of you :)" + "\n" + "\nYour friendly neighborhood accountability partner");
}
});
}
I expect an email to be sent but I keep receiving emails with this error:
Your script, Email Reminder, has recently failed to finish successfully. A summary of the failure(s) is shown below.
6/23/19 10:52 AM: myFunction Script function not found: myFunction time-based 6/23/19 10:52 AM
6/24/19 10:52 AM: myFunction Script function not found: myFunction time-based 6/24/19 10:52 AM
I'm looking some way to bold text in email and when I open the msgBox.
I want bold only headlines, like in picture below:
this is my script, you choose some cell in row that interests you and run the function. Function show information about data from every cell in row, like inforamtion about "Name" and "email". Then if you push send it will send email with this informations. I want bold headlines for better clarity.
function sendEmail(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var fr1 = ss.getSheetByName("Sheet1");
var cell = ss.getActiveCell().getRow();
var lastColumn = fr1.getLastColumn();
var lastRowValues = fr1.getRange(cell,1,1,lastColumn).getValues();
var Nr = lastRowValues[0][0];
var Data = lastRowValues[0][1];
var Information = lastRowValues[0][2];
var Name = lastRowValues[0][3];
var email = lastRowValues[0][4];
var urlOfSS = ss.getUrl();
var message = "Message" +
"\n " +
"\nNr: " + Nr +
"\nData: " + Data +
"\nInformation: " + Information +
"\nName " + Name +
"\nEmail: " + email +
"\n " +
"\n Link to spreadsheet:" +
"\n " + urlOfSS;
var emails = ss.getSheetByName("Sheet1");
var numRows = emails.getLastRow();
var emailTo = email;
var subject = "Zgłoszenie FAS - " + Nr;
if (email == ""){
Browser.msgBox('This row is empty - Choose another');
} else {
var ui = SpreadsheetApp.getUi();
var l = ss.getSheets()[0]
var response = ui.alert('Email', "Do you want email \nNr: " + l.getRange(cell, 1).getValue() + "\nData: " + l.getRange(cell, 2).getValue() + "\nInforamtion: " + l.getRange(cell, 3).getValue()
+ "\nName: " + l.getRange(cell, 4).getValue(), ui.ButtonSet.YES_NO);
if (response == ui.Button.YES) {
GmailApp.sendEmail(emailTo, subject, message);
} else {
Logger.log('The user clicked "No" or the dialog\'s close button.');
}
}
}
Regards
If I understand the requirement, Only the side headers(underlined in the screenshot) needs decoration.
While going through the Google Apps Scripts - Documentation, I came through this.
Hope this helps you.
Using the HtmlServices & HtmlOutputFromFile() would fulfill your requirement.
Please refer to Custom Dialogs. This would help you
https://developers.google.com/apps-script/guides/dialogs
I tried to modify a script to my own needs but I get an error "TypeError: Function getDate not found in the object . (line 13, file "Code")".
In fact, I want to compare today's date to the date contained in row[7] and send a reminder to some people for each line of the spreadsheet containing Projects...
Here is my actual code:
function sendEmails(step) {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 3; // First row of data to process, Start at second row because the first row contains the data labels
var numRows = sheet.getLastRow(); // Number of rows to process -> all rows which are not empty
var totalRows = numRows - startRow +1; // total rows to process is numRows on which we remove start row +1
// Fetch the range of cells
var dataRange = sheet.getRange(startRow, 1, totalRows, 20) // range of columns to process
// Fetch values for each row in the Range
var data = dataRange.getValues();
for (i in data) {
var row = data[i];
var emailAddress = row[2] + ", " + row [3] + ", " + row [4]; // email addresses for email distribution
var messagePVEnd = "The PV of project " + "'"+ row[0] +"'" + " is ending the " + row[7].getDate() + "/" + (row[7].getMonth+1) + "/" + row[7].getFullYear() + " , please push " + row[1] + " to get the reports."; // Email content for PV End
var messagePVMidStatus = "The PV of project " + "'" + row[0] + "'" + " will be at 500h the " + row[6].getDate() + "/" + (row[6].getMonth()+1) + "/" + row[6].getFullYear() + " , please push " + row[1] + " to get the intermediate status."; // Email content for PV after 500h
var messagePVOut = "The PV of project " + "'"+ row[0] +"'" + " is supposed to be finished since " + row[7].getDate() + "/" + (row[7].getMonth()+1) + "/" + row[7].getFullYear() + " , please push " + row[1] + " to get the reports and confirm that all reports are received in PV follow up google sheet."; // Email content for PV Out
var subjectPVEnd = row [0] + " -- Reminder for PV ending to get report from " + row [1]; // Email subject for PV end
var subjectPVMidStatus = row [0] + " -- Reminder to get PV intermediate status after 500h from " + row [1]; // Email subject for PV after 500h
var subjectPVOut = row [0] + " -- Reminder, PV should be finished with all reports received from " + row [1]; // Email subject for PV Out
if (row[8]==5) { // if date of PV status after 500h is equal to 5 days
MailApp.sendEmail(emailAddress, subjectPVMidStatus, messagePVMidStatus)
}
else if (row[8]==1) { // if date of PV status after 500h is equal to 1 day
MailApp.sendEmail(emailAddress, subjectPVMidStatus, messagePVMidStatus)
}
else if (row[9]==5) { // if PV end date is equal to 5 days
MailApp.sendEmail(emailAddress, subjectPVEnd, messagePVEnd)
}
else if (row[9]==1) { // if PV end date is equal to 1 day
MailApp.sendEmail(emailAddress, subjectPVEnd, messagePVEnd)
}
else if (row[10]!="yes") {
for (var j=1; j<=9; j++) {
if (row[9]==-(j*10)) { // if PV end date is out of date by multiple of 10 days up to 100 days (except if report are received)
MailApp.sendEmail(emailAddress, subjectPVOut, messagePVOut)
}
}
}
}
}
The emails are sent properly but I get problems with the date format in the message and I cannot figure out what I did wrong.
Any support would be welcome!
Thanks in advance,
Edit 17/08:
Here is a picture of the corresponding spreadsheet.
enter image description here
Please note that row[7] this is cell value but not the object. So to compare with the current date you need var now = new Date(); and then compare it with the row[7]
It seems like the data returned from spreadsheet in row[7] is not a date type. In order to check manually, double click on the spreadsheet cell and see if a calendar pops up. If not, then the data returned from the sheet is not of date object type.
To do in code, first check if the row[7] is instanceof Date. If yes, then process as is, otherwise, convert the date string returned from that cell to a date object and then process further.
Part of modified code will look like this
var emailAddress = row[2] + ", " + row [3] + ", " + row [4]; // email addresses for email distribution
var dateStr = '';
if(row[7] instanceof Date){
dateStr = row[6].getDate() + "/" + (row[6].getMonth()+1) + "/" + row[6].getFullYear();
}
else {
dateStr = row[7];
}
// var messagePVEnd = "The PV of project " + "'"+ row[0] +"'" + " is ending the " + dateStr + " , please push " + row[1] + " to get the reports."; // Email content for PV End
var messagePVMidStatus = "The PV of project " + "'" + row[0] + "'" + " will be at 500h the " + dateStr + " , please push " + row[1] + " to get the intermediate status."; // Email content for PV after 500h
var messagePVOut = "The PV of project " + "'"+ row[0] +"'" + " is supposed to be finished since " + dateStr + " , please push " + row[1] + " to get the reports and confirm that all reports are received in PV follow up google sheet."; // Email content for PV Out
var subjectPVEnd = row [0] + " -- Reminder for PV ending to get report from " + row [1]; // Email subject for PV end