I have a sheet that is dynamically updating with leads from my marketing campaign. Currently, it has over 9K rows and will continue to grow.
I need to check values for Col F for leads that came in today; however, when I run a loop, it takes too long since it has 9k+ rows.
I want to run a loop for rows that have today's date only. Is there a way to have my script efficiently look for today's date first (without having to loop through 9k rows), and then start my loop?
Below is what I had originally. The code would start from the top of the sheet and look at Col F values and then email me if a cell had only "az-" instead of a full location value. Content below has been modified for privacy
function Google_Check_Lead_Values() {
var title = "Google | Leads"
var app = SpreadsheetApp;
var Def_url = ("website")
///EDIT ^ URL
var ss = app.openByUrl(Def_url).getSheetByName("Google | Leads");
var last_row = ss.getLastRow();
for (i = 1;i <=last_row; i++) {
var location = ss.getRange(i,6).getValue();
var location_len = location.length;
if (location_len <= 3){
MailApp.sendEmail({
to: "email1",
//cc: "email2",
subject: "Discrepancy found on " + title,
htmlBody:"Hi Team, <br>This is an automated notifcation to let you know that an issue was found on " + title + ". Please take a look at the sheet linked below: <br><br> email"
});//end of email
}//end of if
}//end of for
}//end of function
Try it like this:
function Google_Check_Lead_Values() {
const title = "Google | Leads"
const ss = SpreadsheetApp.openByUrl("url")
const sh = SpreadsheetApp.openByUrl(Def_url).getSheetByName(title);
const dt = new Date();
const dtv = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate()).valueOf();
const vs = sh.getDataRange().getValues();
vs.forEach(r => {
let loc = r[5].toString();
let d = new Date(r[0]);
let dv = new Date(d.getFullYear(),d.getMonth(),d.getDate()).valueOf();
if( dv == dtv && loc.length < 4) {
MailApp.sendEmail({ to: "email1", subject: "Discrepancy found on " + title, htmlBody: "Hi Team, <br>This is an automated notifcation to let you know that an issue was found on " + title + ". Please take a look at the sheet linked below: <br><br> email", })
}
});
}
I believe your goal is as follows.
You want to reduce the process cost of the search of value from a large sheet.
You want to achieve this using Google Apps Script.
In your situation, by searching the values from the columns "A" and "F", you want to send emails.
In order to search the values from a sheet, there are several methods. Using for loop, TextFinder and query language. In my test, when the query language is used, the process cost was the lowest of these 3 patterns. So, in this answer, I would like to propose to search the values using use the query language. Ref
Sample script:
function Google_Check_Lead_Values() {
var title = "Google | Leads"
var Def_url = "###"; // Please use your Spreadsheet URL.
var ss = SpreadsheetApp.openByUrl(Def_url);
const spreadsheetId = ss.getId();
const today = new Date();
const tommorow = new Date();
tommorow.setDate(tommorow.getDate() + 1);
const vToday = Utilities.formatDate(today, Session.getScriptTimeZone(), "yyyy-MM-dd");
const vTommorow = Utilities.formatDate(tommorow, Session.getScriptTimeZone(), "yyyy-MM-dd");
const sheetName = "Google | Leads";
const query = `SELECT * WHERE A >= DATE '${vToday}' AND A < date '${vTommorow}' AND E = 'az-'`;
const url = `https://docs.google.com/spreadsheets/d/${spreadsheetId}/gviz/tq?sheet=${sheetName}&tqx=out:csv&tq=${query}`;
const res = UrlFetchApp.fetch(encodeURI(url), { headers: { authorization: "Bearer " + ScriptApp.getOAuthToken() } });
const [, ...values] = Utilities.parseCsv(res.getContentText());
if (values.length == 0) return;
values.forEach(_ => {
MailApp.sendEmail({
to: "email1",
//cc: "email2",
subject: "Discrepancy found on " + title,
htmlBody: "Hi Team, <br>This is an automated notifcation to let you know that an issue was found on " + title + ". Please take a look at the sheet linked below: <br><br> email"
});
});
}
When this script is run, the today date values are searched from the column "A", and also it searches whether the cell value of column "F" is the value of az-. When the values are found, the emaiil is sent. In this case, from your showing script, for example, when 2 rows are found, 2 emails are sent.
If you know the Spreadsheet ID, you can also directly use it to const spreadsheetId = ss.getId(); like const spreadsheetId = "###SpreadsheetID###";.
In this sample script, about the search of the column "F", it seaches whether the value is az-. But, if you want to search the length of cell value, please modify as follows.
From
const query = `SELECT * WHERE A >= DATE '${vToday}' AND A < date '${vTommorow}' AND E = 'az-'`;
To
const query = `SELECT * WHERE A >= DATE '${vToday}' AND A < date '${vTommorow}' AND E MATCHES '^.{0,3}$'`;
References:
Query Language Reference (Version 0.7)
Related thread.
https://stackoverflow.com/a/56663884
Related
I have an email script that will send out email to all emails that are in the list of a google sheet. But then, when I run the script, it is also sent to my Inbox instead of only to register into my Sent folder. Before this no issue (after being using the same script for a long time, 1+ year), it just happened a few months back.
I've found similar issue from this post but couldn't get the suggestions work, example:
To replace Mailapp.sendEmail with Gmailapp.sendEmail
Checked if I have any wrong filter in my email
Attached the script as below:
function arrayUnique(arr) {
var tmp = [];
// filter out duplicates
return arr.filter(function(item, index){
var stringItem = item.toString(); // convert row arrays to strings for comparison
tmp.push(stringItem); // push string items into temporary arrays
return tmp.indexOf(stringItem) >= index; // only return the first occurrence of the strings
})}
function sendEmail() {
var sheet = SpreadsheetApp.getActive().getSheetByName("pending list")
var sub = "Error Fix Required"
var data = sheet.getRange(3,1, sheet.getLastRow(), 5).getDisplayValues().filter(function (row){
var date = sheet.getRange(1,2).getDisplayValues()
return row[3] == date
})
var filterunique = arrayUnique(data)
var json = {}
filterunique.forEach(function (item){
if(item[2] || item[2].length > 0){
if(!json[item[2]]){
json[item[2]] = []
}
json[item[2]].push(item[0])
}
})
Object.keys(json).forEach(function(to){
var email = "{{user}}#shopee.com"
email = email.replace(/{{user}}/gi,to)
var sku = "<br><ul><li>" + json[to].join("</li><li>") + "</li></ul>"
var body = "Hi " + to +", <br><br>The following require your kind attention:" + sku + "Please go to this <a href='https://docs.google.com/spreadsheets/d/1nMKeW//'>link</a> to rectify your submissions by today 4pm.<br><br> Thanks."
MailApp.sendEmail({
to: email,
subject: sub,
htmlBody: body
});
})
}
Also attached what I received in my Inbox:
Change this:
MailApp.sendEmail({to: email,subject: sub, htmlBody: body});
to this:
GmailApp.sendEmail(emai,sub,'',{htmlBody:body});
I have been trying to find the right method to pre-fill the start date field in my Google Forms with the current date and time, but I failed to achieve this.
I think the createResponse(hour, minute) would be the best method, however I have absolutely no idea on where to include it in my script:
function book1() {
var form = FormApp.getActiveForm();
var responses = form.getResponses();
var len = responses.length;
var last = len - 1;
var items = responses[last].getItemResponses();
////// var current= items[2].createResponse(10, 00);
var email = responses[last].getRespondentEmail();
var equipment = items[1].getResponse();
var datestart = items[2].getResponse();
var dateend = items[3].getResponse();
var cal = CalendarApp.getCalendarsByName(equipment)[0];
Logger.log('current '+current);
Logger.log(datestart);
Logger.log(dateend);
var start = new Date(datestart);
var end = new Date(dateend);
Logger.log('start '+start);
Logger.log('end '+end);
var allEvents = CalendarApp.getCalendarsByName(equipment)[0].getEvents(start, end);//Returns an array of events
if (allEvents.length < 1) {
var event = cal.createEvent(equipment, start, end)
.addGuest(email);
MailApp.sendEmail({
to: email,
subject: "Equipment " +equipment+ " booking confirmed",
htmlBody: "Equipment " +equipment+ " available, please return it by " +dateend+ " and scan the QR code when returning it.",
});
}
else {
var blob = HtmlService.createHtmlOutputFromFile("calendariframe").getBlob();
MailApp.sendEmail({
to: email,
subject: "Equipment " +equipment+ " not available",
htmlBody: blob.getDataAsString(),
});
};
}
In my latest attempt, I encountered that error;TypeError: items[2].createResponse is not a function
Would you know how to make it work?
Knowing that pre-filling the date and time only simplifies the process of the form users, but that in some cases they will not use the current date and time as a starting time for booking.
Thanks a lot in advance!
I'm close, but my logic just isn't quite working. I am able to run the code below within Google Sheets Scripts to create a calendar event for each row in the sheet. I am trying to get it to only create an event when a new row is entered and to only read the spreadsheet until it comes to the first column/row being empty/null. enter image description here
Here is the associated code:
function createCalendarEvent() {
var sheet = SpreadsheetApp.getActiveSheet();
var calendar = CalendarApp.getCalendarById('dentaldigitaldesign.bkp#gmail.com');
var startRow = 100; // First row of data to process - 100 exempts my header row and adds to the case number count.
var numRows = sheet.getLastRow(); // Number of rows to process
var numColumns = sheet.getLastColumn();
var dataRange = sheet.getRange(startRow, 1, numRows-1, numColumns);
var data = dataRange.getValues();
var complete = "TRUE";
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var caseID = row[0]; //Case ID Number
var patient = row[4]; //Patient Name
var doctor = row[5]; //Doctor
var contact = row[14]; //Doctor contact info
var tooth = row[15]; //Tooth number
var shade = row[10]; //Tooth Shade
var notes = row[8]; //Notes
var callNotes = row[7]; //Call notes if there are any
var timeStamp = new Date (row[2]); //Retrieve the timestamp field
// year as 4 digits (YYYY)
var year = timeStamp.getFullYear();
// month as 2 digits (MM)
var month = ("0" + (timeStamp.getMonth() + 1)).slice(-2);
// date as 2 digits (DD)
var day = ("0" + timeStamp.getDate()).slice(-2);
var dueDate = new Date(month + day, + year); //Due date
var rDate = new Date(row[2]-1); //Remind date
var caseComplete = row[3]; //Case marked as complete
if (caseComplete !== complete && caseID !== null) {
var currentCell = sheet.getRange(startRow + i, numColumns);
calendar.createEvent(patient, rDate, rDate, {
description: doctor + '\r' + patient + '\r' + contact + '\r' + dueDate + '\r' + tooth + '\r' + shade + '\r' + notes + '\r' + callNotes
});
currentCell.setValue(complete);
}
}
}
When these events are created, they are all created for 8:59am. Ideally, I could do a check on the calendare if an event is set for that time, a new event is added immediately after. (perhaps in 15min or 30min slots). For now it works well for me to get the reminders on cases that are due, but eventually an invite to the doctor for them to know the due date might work well, too.
I can also use help in formatting the description field as it is not pushing the return value and everything is on one line.
And finally, the script continues to run on numerous fields beyond the scope of the desired rows, ultimately ending up with the script failing because too many event attempts are created in too short a time. (all the attempts with fields that are empty do not result in any events being created, but it is a resouce issue....and who knows maybe Google eventually blocks me?)
I appreciate any help that can be offered.
Here is the link to the Google Sheet. No data on it is sensitive as it is only test data: https://docs.google.com/spreadsheets/d/1M9qYzSl1PnRv-GehHEYvpiag15UI_GjnanA0wgA4xmg/edit?usp=sharing
There are several issues with your code, causing this expression to always return true, so that the script tries to create an event for each row:
if (caseComplete !== complete && caseID !== null) {
Empty string !== null:
You want to check whether the cell in column A is empty (that is, whether its value is an empty string), and to do that you are comparing its value to null. null is not the same as an empty string, so the following expression will always be true (a cell value never returns null):
caseID !== null
You should be comparing this to an empty string. For example, like this:
caseID !== ""
So the if statement should be:
if (caseComplete !== complete && caseID !== "") {
Wrong complete column:
Once an event has been created, you want to set the corresponding cell in D to TRUE. You're not doing that, since you specify the last column in the sheet (numColumns) when setting the currentCell:
var currentCell = sheet.getRange(startRow + i, numColumns);
Because of this, the value in D never changes to TRUE, so caseComplete !== complete is always true.
You should specify the correct column index for D instead, which is 4. It should be like this:
var currentCell = sheet.getRange(startRow + i, 4);
Edit: Also, the retrieved value for cell with checked checkboxes is the boolean true, and you're trying to compare it with the string TRUE. Because of this, this condition is not working correctly. Please change:
var complete = "TRUE";
To
var complete = true;
Wrong number of rows in source range:
You want to get the value from row 100 till the last one in the sheet. In this case, the total number of rows in the range should be the last row (numRows) minus the first row in the range (startRow) plus one (numRows - startRow + 1). The dataRange should be defined like this instead:
var dataRange = sheet.getRange(startRow, 1, numRows - startRow + 1, numColumns);
Reference:
Class Range
null
My current project is to take a set of emails from my gmail account and create a book from them, where each letter is a chapter.
I wrote up a Google Script snippet that successfully extracts the subject, date, and body text from all emails under a certain
label and writes them to a Google Document. This is not quite ideal, as I would prefer to ultimately have all this text in
markdown or LaTeX for formatting more carefully, but I was only familiar with how to to write to a Google Doc, based on the
Gmail API.
The fastest way for A to B for me would be to figure out how to alter this code so that it writes the text to a CSV file, containing
subject, date, and body columns. From there, I can process and parse it out into LaTeX and assign the necessary header styles and
formats there instead; however, I don't see much guidance on that file format in the API. Any guidance on how to adapt this script for
that purpose would be very welcome. Thanks.
function collectLetters() {
var fileName = 'OUTPUT_GOOGLE_DOC_FILENAME';
var labelName = 'MY_LABEL';
var timeZoneName = 'GMT'
var dateFormat = "dd | MM | yyyy"
var docBody;
var subject;
var date;
var body;
var message;
var num;
var datePar;
var bodyPar;
var subjectPar;
var numPar;
// get the handle for label
var label = GmailApp.getUserLabelByName(labelName);
var threads = label.getThreads();
// Create a new Google Doc
var doc = DocumentApp.create(fileName);
// get all the threads from label
for (var i = threads.length-1; i >= 0; i--) {
// EXTRACT FROM EMAIL: date, subject and body
message = threads[i].getMessages()[0];
subject = message.getSubject();
date = Utilities.formatDate(message.getDate(), timeZoneName, dateFormat)
body = message.getPlainBody().replace(/\r\n\r\n/gm,'aaaLINEBREAKERaaa').replace(/\r\n/gm,' ').replace(/ /gm,' ')
var splitBody = body.split('aaaLINEBREAKERaaa');
// APPEND TO GOOGLE DOC: Date (as Header)
datePar = doc.getBody().appendParagraph(date);
datePar.setHeading(DocumentApp.ParagraphHeading.HEADING1);
// APPEND TO GOOGLE DOC: Subject (as Subtitle)
if (subject.length > 0) {
subjectPar = doc.getBody().appendParagraph(subject);
}
else{
subjectPar = doc.getBody().appendParagraph("no subject");
}
subjectPar.setHeading(DocumentApp.ParagraphHeading.SUBTITLE);
// APPEND TO GOOGLE DOC: Body (as Normal)
docBody = doc.getBody()
splitBody.forEach(function (paragraphText) {
this.appendParagraph(paragraphText).setHeading(DocumentApp.ParagraphHeading.NORMAL);
}, docBody);
// Start a new page
doc.getBody().appendPageBreak();
//Logger.log(subject);
}
}
I should preface by saying I know nothing about scripting. I found this script online that fit my needs, so I was able to re-purpose it for my project. Anyway, this script takes Google form submissions, populates a Google doc template, that template gets copied, converted to PDF, and placed in a specific folder on my Google Drive.
So my question is, I have a simple line that pulls the current date when the script gets run, but I also need some code that can calculate the current date plus 5 weekdays (which should exclude weekends), but I also need it to exclude defined holidays. Any help would be greatly appreciated.
// Work Order
// Get template from Google Docs and name it
var docTemplate = ""; // *** replace with your template ID ***
var docName = "Work Order";
// When Form Gets submitted
function onFormSubmit(e) {
//Get information from form and set as variables
var email_address = "";
var job_name = e.values[1];
var ship_to = e.values[11];
var address = e.values[12];
var order_count = e.values[7];
var program = e.values[2];
var workspace = e.values[3];
var offer = e.values[4];
var sort_1 = e.values[5];
var sort_2 = e.values[6];
var print_services = e.values[10];
var priority = e.values[13];
var notes = e.values[14];
var formattedDate = Utilities.formatDate(new Date(), "EDT", "MM/dd/yyyy");
// Get document template, copy it as a new temp doc, and save the Doc's id
var copyId = DriveApp.getFileById(docTemplate)
.makeCopy(docName + ' for ' + job_name)
.getId();
// Open the temporary document
var copyDoc = DocumentApp.openById(copyId);
// Get the document's body section
var copyBody = copyDoc.getActiveSection();
// Replace place holder keys,in our google doc template
copyBody.replaceText('keyJobName', job_name);
copyBody.replaceText('keyShipTo', ship_to);
copyBody.replaceText('keyAddress', address);
copyBody.replaceText('keyOrderCount', order_count);
copyBody.replaceText('keyProgram', program);
copyBody.replaceText('keyWorkspace', workspace);
copyBody.replaceText('keyOffer', offer);
copyBody.replaceText('keySort1', sort_1);
copyBody.replaceText('keySort2', sort_2);
copyBody.replaceText('keyPrintServices', print_services);
copyBody.replaceText('keyPriority', priority);
copyBody.replaceText('keyNotes', notes);
copyBody.replaceText('keyDate', formattedDate);
copyBody.replaceText('keyDue', expirationDate);
// Save and close the temporary document
copyDoc.saveAndClose();
// Convert temporary document to PDF by using the getAs blob conversion
var pdf = DriveApp.getFileById(copyId).getAs("application/pdf");
// Attach PDF and send the email
var subject = "New Job Submission";
var body = "Here is the work order for " + job_name + "";
MailApp.sendEmail(email_address, subject, body, {
htmlBody: body,
attachments: pdf
});
// Move file to folder
var file = DriveApp.getFileById(copyId);
DriveApp.getFolderById("").addFile(file);
file.getParents().next().removeFile(file);
}
You can use the below function to get future date which excludes weekends and if any holiday declared in the array.
function addDates() {
var date = new Date(); // yor form date
var hodiday = ["08/09/2017","08/15/2017"]; //Define holiday dates in MM/dd/yyyy
var days = 5; //No of days you want to add
date.setDate(date.getDate());
var counter = 0;
if(days > 0 ){
while (counter < days) {
date.setDate(date.getDate() + 1 );
var check = date.getDay();
var holidayCheck = hodiday.indexOf(Utilities.formatDate(date, "GMT", "MM/dd/yyyy"));
if (check != 0 && check != 6 && holidayCheck == -1) {
counter++;
}
}
}
Logger.log(date) //for this example will give 08/16/2017
return date;
}