I am working on a "vehicle scheduler" for my workplace that will display the dates and times a truck will be unavailable. Here's the link to the Google Sheets where the script runs. I have set up two example "busy times" to show what I am doing
One adjustment that was requested from me is to do 30 minute increments, but then the amount of columns gets absolutely bonkers.
I am open to any recommendations to improve this tool, even if it means something better (and free) already exists.
I appreciate your input!
See code below... (I am RIDICULOUSLY novice at coding, so be kind :)
function processForm(formObject){
var day = formObject.dow;
var vehicle = formObject.vehicle;
var jobName = formObject.job;
var driverName = formObject.driver;
var duration = formObject.duration;
var startTime = formObject.startTime;
var ampm = formObject.ampm;
var location = formObject.location;
var returnArray = [day, vehicle, jobName, driverName, duration, startTime, ampm, location]
var startTimeColumn = 0;
var startDayColumn = 2;
var sduration = startTime;
var ss = SpreadsheetApp.getActive().getSheetByName("Week View");
if (day=="Monday"){
startDayColumn = 26;
}
else if (day=="Tuesday"){
startDayColumn = 50;
}
else if (day=="Wednesday"){
startDayColumn = 74;
}
else if (day=="Thursday"){
startDayColumn = 98;
}
else if (day=="Friday"){
startDayColumn = 122;
}
else if (day=="Saturday"){
startDayColumn = 146;
}
if (ampm=="PM"){
startDayColumn+=12;
}
if (startTime==12){
startTime = 0;
}
startTimeColumn = parseInt(startTimeColumn) + parseInt(startDayColumn) + parseInt(startTime);
var columnA = ss.getRange(1,1,ss.getLastRow(),1).getValues();
for (var rr=0;rr<ss.getLastRow();rr++){
if (columnA[rr]==vehicle){
break;
}
}
rr +=1;
for (var cc=0; cc<duration; cc++){
var checkColumn = parseInt(cc) + parseInt(startTimeColumn);
if (ss.getRange(rr, checkColumn).isBlank()){}
else return "It looks like that time is booked :(";
}
var displayString = "Job: " + jobName + String.fromCharCode(10) + "Driver: " + driverName + String.fromCharCode(10) + sduration + ":00 " + ampm + String.fromCharCode(10) + location;
var merging = ss.getRange(rr,startTimeColumn,1,duration).mergeAcross();
var enterInfo = ss.getRange(rr, startTimeColumn).setValue(displayString);
if (rr % 2 == 0){
enterInfo.setBackgroundRGB(45, 114, 157);
}
else {enterInfo.setBackgroundRGB(26, 153, 136);}
enterInfo.setFontColor('white')
return "Success! Load has been booked to the scheduler.";
}
Instead of making Sheets jump thru hoops and create a interface for inserting data and a Calendar to show it, why not use Forms and Calendar to do it all?
What I have in mind
Sheets as a long-time database for Truck Information and schedules
Calendar as a way to visualize the availability of Trucks
Forms as a interface for data-input
Set-up
A Spreadsheet file with a "List of Trucks" Sheet.
A Form attached to it that allows users to insert information about the truck booking.
To make this work, I have inputted some data on out List of Trucks:
Then I associated a form with that sheet and setup some basic questions:
I also modified the forms settings to allow people to edit it on the future, this will allow us to add information to the calendar to tie an event with a response form the form.
I added a script to the Forms that populates the first question with the data from our sheets:
function populateForm() {
var form = FormApp.getActiveForm();
var TruckIdQ = form.getItems()[0];
var truckList = SpreadsheetApp.openByUrl("YOUR SHEET URL").getSheetByName("List of Trucks").getDataRange().getValues().map(function (row) { return row[0]});
truckList.shift(); //Remove header
var choices = [];
for (var i=0; i< truckList.length; i++) {
choices.push(TruckIdQ.asListItem().createChoice(truckList[i]));
}
TruckIdQ.asListItem().setChoices(choices);
}
And set it to be run when the form is opened:
After that I went back to my sheets and modified the automatic Responses sheet to have a more understandable name: "Bookings".
Now we need to create a shared calendar to put all this information on it.
I created one named "Truck Booking Calendar" in the following way:
I then created a new Script on the Forms responsible for populating the calendar with information.
function populateCalendar(e) {
var bookingCalendar = CalendarApp.getCalendarById("CALENDAR ID");
var questions = FormApp.getActiveForm().getItems();
var response = e.response.getItemResponses();
var eventDescription = "";
var truckname;
var fromDate, toDate;
var fromTime, toTime;
for (var i=0; i< questions.length; i++) {
eventDescription = eventDescription + questions[i].getTitle() + " : " + response[i].getResponse() + "\n";
switch (i) {
case 0:
//Truck Id
truckname = response[i].getResponse();
break;
case 1:
//From Date
fromDate = response[i].getResponse();
break;
case 2:
//To Date
toDate = response[i].getResponse();
break;
case 3:
//From Time
fromTime = response[i].getResponse();
break;
case 4:
//To Time
toTime = response[i].getResponse();
break;
}
}
var startDateTime = new Date(fromDate + " " + fromTime);
var endDateTime = new Date(toDate + " " + toTime);
var newEvent = bookingCalendar.createEvent("Booking "+truckname, startDateTime, endDateTime, {description:eventDescription});
newEvent.setTag("truck", truckname);
newEvent.setTag("responseId", e.response.getEditResponseUrl());
newEvent.setTag("responseUrl", e.response.getId());
}
And created a trigger for it like this:
Now, when I submit this into the form:
I get this entry on my calendar:
Hope this helps you!
Related
I am trying to get data from a particular google sheets to create events in google calendar. See the spreadsheet.
https://docs.google.com/spreadsheets/d/1eBEStiTKXI0YPXfQBYzqXjwwv4kZ21033TdtysxdhHI/edit?usp=sharing
Basically, when someone ticks the boxes, it creates an event for the person (row 2) at date (Col B) at 10:00:00 AEST. So for example, when some ticks the box in cell P14, it creates an event with:
name - Lily Ahadi - PC
Date of event - 16-Mar-2020
Time of event: 10:00:00
Here is the code I partially worked on and then got someone else to help and we both didnt get anywhere. I am about to give up but thought I will give a last try with stackflow experts. The code is availabe in the script editor of the sheet.
function onEdit(e) {
try {
var range = e.range;
Browser.msgBox(range);
var nameSheet = e.source.getSheetName();
var rowID = range.rowStart;
var colID = range.columnStart;
var res = e;
var oldValue = res.oldValue;
var newValue = res.value;
var data = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(nameSheet);
if (oldValue == "FALSE" && newValue == "TRUE" && nameSheet == "Master Client List"){
var category = data.getRange(3, colID).getValue();
var date = data.getRange(rowID, 2).getValue();
date = getYesterdaysDate(date)
var timeDiff = 60;
var startTime = "10:00:00";
if (category == "PC"){
var name = data.getRange(2, colID - 1).getValue();
Browser.msgBox(name+'-'+category + date);
}else{
var name = data.getRange(2, colID - 2).getValue();
}
// var startDateTime = date+' '+startTime+':00';
var startDateTime = testMoment1(date, startTime);
var endDateTime = addMins(startDateTime, timeDiff);
var event = CalendarApp.getDefaultCalendar().createEvent(name+'-'+category,
new Date(startDateTime),
new Date(endDateTime),
{description: ''});
Logger.log('Event ID: ' + event.getId());
}
}
catch(err) {
SpreadsheetApp.getUi().alert(err);
}
}
function testMoment(date, time) {
eval(UrlFetchApp.fetch('https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js').getContentText());
var date = moment(date).format('YYYY/MM/DD');
// var time = time;
// Logger.log(moment(date).format('MM/DD/YYYY'));
// tell moment how to parse the input string
var momentObj = moment(date + time, 'YYYY-MM-DDLT');
// conversion
var dateTime = momentObj.format('YYYY-MM-DDTHH:mm:ss');
return dateTime;
}
function testMoment1(date, time) {
eval(UrlFetchApp.fetch('https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js').getContentText());
// var date = moment(date).format('YYYY/MM/DD');
// var time = time;
// Logger.log(moment(date).format('MM/DD/YYYY'));
var c = new Date();
var n = c.getFullYear();
// tell moment how to parse the input string
var momentObj = moment(date + time, 'YYYY-MM-DDLT').set('year', n).add(0, 'days');
// conversion
var dateTime = momentObj.format('YYYY-MM-DDTHH:mm:ss');
// dateTime = moment(dateTime, "YYYY-MM-DDTHH:mm:ss");
return dateTime;
}
function addMins(dateTime, durationInMinutes){
eval(UrlFetchApp.fetch('https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js').getContentText());
var startTime = dateTime;
// var durationInMinutes = '120';
var endTime = moment(startTime, 'YYYY-MM-DDTHH:mm:ss').add(durationInMinutes, 'minutes').format('YYYY-MM-DDTHH:mm:ss');
return endTime;
}
function getYesterdaysDate(date1) {
var date = new Date(date1);
date.setDate(date.getDate());
var day = date.getDate();
var month = (date.getMonth()+1);
var year = date.getFullYear();
month = month < 10 ? '0'+month : month;
day = day < 10 ? '0'+day : day;
// Logger.log(date.getFullYear() + '-' + (date.getMonth()+1) + '-' + date.getDate());
return year + '/' + month + '/' + day;
}
function myFunction() {
var data=SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Master Client List');
// var s=ss.getActiveSheet();
var c=data.getLastColumn();
for (var i = 3; i <= c; i++) {
if (i%3 === 0){
var name = data.getRange(2, i).getValue();
var dob = data.getRange(1, i).getValue();
var timeDiff = 60;
var startTime = "10:00:00";
Logger.log(name);
Logger.log(name);
Logger.log(startTime);
//birthdayevent(name, dob, startTime, timeDiff)
Logger.log(name);
// break;
}
}
}
Your code has two main issues:
1. You are querying for if (oldValue == "FALSE")
If you implement into your code the line Logger.log(oldValue); you will realize that an empty checkbox will return you the value "false" and not "FALSE". You need to modfy your if condition accordingly.
2. You are trying to use UrlFetchApp on simple onEdit trigger
As specified under restrictions for simple triggers:
They cannot access services that require authorization.
This problem can be easily solved by transforming your trigger into an installable one.
For this:
Rename your function onEdit() to something different
Bind to the funciton an installable onEdit trigger as described here
After you implement those two modifications your code will run and create an event when you check a checkbox.
Now, I am not familiar enough to judge either the event parameters (data, event title) are retrieved as you desire. For troubleshooting I recommend you to log all variables - this will help you to spot any error quickly.
I have very limited experience with Google Apps Scripts, but have successfully modified the code from https://developers.google.com/apps-script/quickstart/forms to meet almost all of my needs.
Need to know how to view on my calendar all guests who sign up for a date.
I have investigated CalendarApp options, but can't get anything formatted to work so that the name of anyone who completes the Google Form for a specific date shows up on my calendar on that date -- maybe as a guest or attendee?
var cal = CalendarApp.getOwnedCalendarById(XXX);
for (var i = 1; i < values.length; i++) {
var session = values[i];
var title = session[0];
var start = joinDateAndTime_(session[1], session[2]);
var end = joinDateAndTime_(session[1], session[3]);
var options = {location: session[4], sendInvites: true};
var event = cal.createEvent(title, start, end, options)
.setGuestsCanSeeGuests(false);
session[5] = event.getId();
}
range.setValues(values);
var schedule = {};
for (var i = 1; i < values.length; i++) {
var session = values[i];
var day = session[1].toLocaleDateString();
var time = session[2].toLocaleTimeString();
if (!schedule[day]) {
schedule[day] = {};
}
if (!schedule[day][time]) {
schedule[day][time] = [];
}
schedule[day][time].push(session[0]);
}
// Create the form and add a multiple-choice question for each timeslot.
var form = FormApp.create('2019-2020 Semester 1 Sign up');
form.setDestination(FormApp.DestinationType.SPREADSHEET, ss.getId());
form.addTextItem().setTitle('Name').setRequired(true);
form.addTextItem().setTitle('Email').setRequired(true);
form.addTextItem().setTitle('Phone Number').setRequired(false);
for (var day in schedule) {
var header = form.addSectionHeaderItem().setTitle('Odysseys on ' + day);
for (var time in schedule[day]) {
var item = form.addMultipleChoiceItem().setTitle(time + ' ' + day)
.setChoiceValues(schedule[day][time]);
}
}
}
I see you want to make sure your events have the right guests added to them. After reading the comments I can see that Altigraph put you on the right track. Below is the syntax you need (I’m assuming that “values” refers to a user’s particular response to the form):
For adding guests in the createEvent() options:
for (var i = 1; i < values.length; i++) {
var session = values[i];
var title = session[0];
var start = joinDateAndTime_(session[1], session[2]);
var end = joinDateAndTime_(session[1], session[3]);
var options = {location: session[4], sendInvites: true,
guests: email,
};
var event = cal.createEvent(title, start, end, options)
session[5] = event.getId();
}
This email you can request from the user in the form, like you get their name or their phone, if you use a Google Form directly, there is the option to use this method to get the user’s email.
Alternatively, to use addGuest(), once you have the calendar created (i.e. the “event” object you have at the end of this snippet) you would simply use event.addGuest(email); where email is the same as in the other option. The documentation for it is at this link
I'm currently working on creating a Google Sheet that would allow me to create calendar events (reminders) for when certain reports are due for me.
Basically, on the basis of a given opening date, I have 5 different types of reports due, some at different intervals (ie, 45 days from the open date, 6 months, 12 months). I've been able to modify a sample I found online (http://www.adammcfarland.com/2013/08/09/tutorial-using-google-spreadsheets-to-update-a-google-calendar-our-new-vacation-management-system/), customizing a Google Sheet to generate due dates for each type of report, and create calendar entries based on those due dates. It's actually really cool and powerful.
Unfortunately, I'm stumbling when it comes to creating notifications for the different reports. I know how to create default notifications within the Google Calendar interface, but the quirk I'm currently trying to address is that of these various reports, not all require as much time to complete, so I'm looking to create notifications specific to each report type, and I'm thus far been wholly unable to get things working.
Here's a copy of the script I'm using.
enter code herefunction pushToCalendar() {
var sheet = SpreadsheetApp.getActiveSheet();
var lastRow = sheet.getLastRow();
var range = sheet.getRange(2,1,lastRow,5);
var values = range.getValues();
var updateRange = sheet.getRange('G1');
var calendar = CalendarApp.getCalendarById('jk.com_5e9gk4#group.calendar.google.com')
updateRange.setFontColor('red');
var numValues = 0;
for (var i = 0; i < values.length; i++) {
if ((values[i][0].length > 0) && (values[i][2].length > 0)) {
if (values[i][3] != 'y') {
var newEventTitle = 'Note Due: ' + values[i][0] + ' - ' + values[i][2];
var newEvent = calendar.createAllDayEvent(newEventTitle, values[i][1]);
var newEventId = newEvent.getId();
sheet.getRange(i+2,4).setValue('y');
sheet.getRange(i+2,5).setValue(newEventId);
}
Where "values[i][2]" corresponds to the type of report due.
Thanks in advance.
You can try to addreminder just after creating event based on a table. See the code below :
function pushToCalendar() {
var sheet = SpreadsheetApp.getActiveSheet();
//Define reminder, value in minute and lower 4weeks (=40320 minutes)
var reminder = {"type1":1440,//=1 day
"type2":7200,//5days
"type3":20160//2weeks
};
//End define reminder
var lastRow = sheet.getLastRow();
var range = sheet.getRange(2,1,lastRow,5);
var values = range.getValues();
var updateRange = sheet.getRange('G1');
var calendar = CalendarApp.getCalendarById('jk.com_5e9gk4#group.calendar.google.com')
updateRange.setFontColor('red');
var numValues = 0;
for (var i = 0; i < values.length; i++) {
if ((values[i][0].length > 0) && (values[i][2].length > 0)) {
if (values[i][3] != 'y') {
var newEventTitle = 'Note Due: ' + values[i][0] + ' - ' + values[i][2];
var newEvent = calendar.createAllDayEvent(newEventTitle, values[i][1]);
//Add reminder
//For that we assume you well created the reminder var and all type have the amount of minutes define if not you must implement the check in he code
newEvent.addEmailReminder(reminder[values[i][2]]);
//End add reminder
var newEventId = newEvent.getId();
sheet.getRange(i+2,4).setValue('y');
sheet.getRange(i+2,5).setValue(newEventId);
}
}
}
}
I added some comments in the code for you.
Stéphane
I am still learning the ropes here. Based on code suggested by other contributors, I put together a script to send reminder emails to consultants who record their time entries using a Google Form. The spreadsheet first imports calendar entries with all the Job information for each consultant. After the calendar entries are imported, if the consultant has not yet recorded their time entry, the following script will send them an email with a reminder to do so:
function sendReminder() {
var rmndrFrom = "XYZ, Inc.";
var myemail = "support#xyz.com";
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Pending");
var numRows = sheet.getLastRow();
var lastCol = sheet.getLastColumn();
var dataRange = sheet.getRange(2, 1, numRows, lastCol); // row 1 is the header row
var sheetData = dataRange.getValues();
for (var i = 0; i < sheetData.length; ++i) {
var row = sheetData[i];
if (row[0]){
var jobNumb = row[0]; // Job Number
var conName = row[2]; // Consultant Name
var conMail = row[3]; // Consultant Email
var jobDate = row[4]; // Date
// format email string
var subject = "Time Entry Reminder: " + conName + " / Job " + jobNumb;
try {
var conMsgH = 'This is a reminder for you to record your time entry for Job #<strong>' + jobNum + '/' + jobDate + '</strong>';
// strip HTML for plain text message
var conMsgP = conMsgH.replace(/\<br\/\>/gi, '\n').replace(/(<([^>]+)>)/ig, "");
// send reminder to consultants
MailApp.sendEmail(conMail, subject, conMsgP, { htmlBody:conMsgH, name:rmndrFrom });
} catch (e) { // error handler
MailApp.sendEmail(myemail, "Error in sending reminder email.", e.message);
}
}
}
}
So basically, this script parses the Pending sheet, if column A has a Job Number, it will send a reminder email to the consultant with that Job Number. However, a single consultant may have several job numbers to their name. I would like the script to send a single email to each consultant with a list of the Job Numbers for which they have to record their time entries.
Thanks in advance for your kind help. Any suggestions on how to optimize the code will also be very much appreciated.
There are a number of ways that you can approach this. One way is to keep a sheet with the consultants emails, names and a list of their job numbers. Load this data into your script, a list of all job ids and the job info. Then filter the job ids based on the consultants list and build your email, or you could just send that list of numbers for a very short script.
Another way is to do all of that sorting per consultant in the code and send out the emails that way. This is the approach I've taken, and I've also made use of the iterative JS functions map, filter and reduce more details at MDN.
The code is posted below, but if you would like to take a look at it attached to a spreadsheet and commented (as well as the functions to build that extra sheet with just the consultants info on it) take a look here.
Below is my iteration of your function. I hope it is helpful for your situation:
var rmndrFrom = "XYZ, Inc.";
var myemail = "me#emailisawesome.com";
var subject = "Time Entry Reminder";
function sendReminder() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Pending");
var numRows = sheet.getLastRow();
var lastCol = sheet.getLastColumn();
var sheetData = sheet.getRange(2, 1, numRows-1, lastCol).getValues();
var cons = sheet.getRange(2,3,numRows-1,1).getValues().reduce(flatten_).filter(getUniqueConsultants_);
cons.forEach(sendEmail_, sheetData);
}
function sendEmail_(consultant) {
var consultantsJobs = this.filter(getJobsForConsultant_, consultant);
var jobList = consultantsJobs.map(buildJobLine_).join("<br>");
try {
var conMsgH = "Hi " + consultant + ",<br>";
conMsgH += "This is a reminder for you to record your time entry for the following jobs:<br><br>";
conMsgH += jobList;
conMsgH += "<br><br>Thank you for your cooperation.";
var conMsgP = conMsgH.replace(/\<br\/\>/gi, '\n').replace(/(<([^>]+)>)/ig, "");
MailApp.sendEmail(consultantsJobs[0][3], subject, conMsgP, {htmlBody:conMsgH, name:rmndrFrom});
} catch (e) {
MailApp.sendEmail(myemail, "Error in sending reminder email.", e.message);
}
}
function buildJobLine_(job) {
return "Job #" + job[0] + " on " + Utilities.formatDate(job[4], Session.getTimeZone(), "MMM dd yyyy");
}
function getJobsForConsultant_(row) {
return row[2] == this;
}
function getUniqueConsultants_(v,i,a) {
return a.indexOf(v) == i;
}
function flatten_(a,b) {
return a.concat(b);
}
I must say that fooby's answer is far beyond my JS skills, I'm sure it will work nicely but I still feel like proposing something different (and simpler from my pov), just for the fun of it ;-)
The main difference with your original script is the sorting of the array that allowed me to detect duplicate names and threat it accordingly. The html composition could be far better for sure but that was not your main request.
Here is the code
function sendReminder() {
var rmndrFrom = "XYZ, Inc.";
var myemail = "support#xyz.com";
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Pending");
var initial = "Hi conName,<BR>This is a reminder for you to record your time entry for the following jobs : <BR><BR><table border = 1 cellpadding = 3 bgcolor='#FFFFBB'><TR><TD>";
var sheetData = sheet.getDataRange().getValues();// simplified code to get the data array
sheetData.shift();//skips the headers
sheetData.sort(function(x,y){
var xp = x[2];// sort on column 3 but you can change here...
var yp = y[2];// sort on column 3 but you can change here...
return xp == yp ? 0 : xp < yp ? -1 : 1;// sort ascending, eventually change here...
});
// Logger.log(sheetData);// sorted
var last = sheetData.length
var i = 1;//index 0 is handled outside the loop
var row = sheetData[0];
var subject = "Time Entry Reminder: " + row[2] + " / Job "
var conMsgH = initial
var msgComponent = makeline_(row)
subject += msgComponent[0]
conMsgH += msgComponent[1]
while (i<last){
if(sheetData[i][2]!=sheetData[i-1][2]||i==last-1){
sendData_(sheetData[i-1][3],sheetData[i-1][2],subject,conMsgH)
var subject = "Time Entry Reminder: " + sheetData[i][2] + " / Job "
var conMsgH = initial;
}
msgComponent = makeline_(sheetData[i])
subject += msgComponent[0]
conMsgH += msgComponent[1]
++i
}
}
function sendData_(conMail,conName,subject,conMsgH){
conMsgH = conMsgH.substr(0,conMsgH.length-8)+'</TABLE>'
conMsgH = conMsgH.replace('conName',conName)
var conMsgP = conMsgH.replace(/<\/tr>/ig, '\n').replace(/<br>/ig, '\n').replace(/(<([^>]+)>)/ig, "")
subject = subject.substr(0,subject.length-2);// remove the last '+ '
// Logger.log(subject)
// Logger.log(conMsgH)
Logger.log(conMsgP)
// Logger.log(conMail)
try{
// send reminder to consultants
MailApp.sendEmail(conMail, subject, conMsgP, { htmlBody:conMsgH, name:rmndrFrom });
} catch (e) { // error handler
// MailApp.sendEmail(myemail, "Error in sending reminder email.", e.message);
}
}
function makeline_(row){
var jobNumb = row[0]; // Job Number
var conName = row[2]; // Consultant Name
var conMail = row[3]; // Consultant Email
var descr = row[1]; // description
var FUS1=new Date(row[4]).toString().substr(25,6)+':00';// get timezone of this event, taking care of daylight savings
var jobDate = Utilities.formatDate(row[4], FUS1, "MMM dd yyyy # hh:mm aaa"); // Date
var subject = jobNumb+' + ';
var conMsgH = 'Job #'+jobNumb + '</TD><TD>' + jobDate + '</TD><TD>' + descr + '</TD></TR><TR><TD>';
return [subject,conMsgH];
}
EDIT : made some improvement in the mail format, used a table to show jobs & dates + removed some bugs ;-) (to be honest, I made this also for my personal use as I am having almost the same use case )
I have developed a Google Apps script in a Google Drive spreadsheet that processes e-mails with a certain label (download notifications) and adds these to the spreadsheet. I'm running this through the script editor of the spreadsheet.
My initial solution was quite inefficient - the whole analysis was repeated each time on all e-mails, which caused the runtime to increase for each day. A few days ago I got an error message "runtime exceeded", which is not strange.
My problem is that when trying to update the script to be more efficient, I get some weird problems. The script either stops randomly after processing a few e-mails, or simply refuses to start. Especially the script debugger, it begins to load but never starts.
I have tried several times over the last few days, and even created a new spreadsheet with the same code (in the same account), but still having these problems.
Sometimes when the script manages to run for a while, I have noted that the script output log does not match recent changes in the logging. I have of course saved the script before running it. It feels like there is some lock preventing my script from running/updating/refreshing?
Is there anyone here that recognize these problems?
The code is attached below.
The two relevant entry points are:
processInbox: Updates the spreadsheet based on new (starred) e-mails with a specific label. The label and star is set by an e-mail filter on reception. The star (indicating "new") is removed from each message after processing.
resetAllMsgs: Clears the spreadsheet and stars all relevant messages, causing processInbox to process all messages received with the relevant label.
function resetAllMsgs() {
Logger.log("Starting ResetAll");
var d = SpreadsheetApp.getActive();
var dl_sheet = d.getSheetByName("Download List");
var dlperday_sheet = d.getSheetByName("DownloadsPerDay");
dl_sheet.clear();
dlperday_sheet.clear();
Logger.log("Clearing spreadsheet");
dl_sheet.appendRow(["", "", "Downloads", ""]);
dl_sheet.appendRow(["", "", "Downloaders", ""]);
dl_sheet.appendRow(["Last Download Date", "First Download Date", "Email" , "Product", "Download Count"]);
dlperday_sheet.appendRow(["Date", "Download Count"]);
var label = GmailApp.getUserLabelByName("Download Notification");
// get all threads of the label
var threads = label.getThreads();
for (var i = 0; i < threads.length; i++) {
// get all messages in a given thread
var messages = threads[i].getMessages();
Logger.log("Starring thread " + i);
for (var j = 0; j < messages.length; j++) {
Logger.log(" Starring message " + j);
// Only starred messages are processed by processInbox
messages[j].star();
Utilities.sleep(100);
}
}
};
function processInbox() {
var d = SpreadsheetApp.getActive();
var dl_sheet = d.getSheetByName("Download List");
var dlperday_sheet = d.getSheetByName("DownloadsPerDay");
// If empty spreadsheet, reset the status of all relevant e-mails and add the spreadsheet headers
if (dl_sheet.getLastRow() <= 1) {
resetAll();
}
var label = GmailApp.getUserLabelByName("Download Notification");
var k = 0;
// get all threads of the label
var threads = label.getThreads();
for (var i = 0; i < threads.length; i++) {
if (threads[i].hasStarredMessages()) {
// get all messages in a given thread
var messages = threads[i].getMessages();
// iterate over each message
for (var j = 0; j < messages.length; j++) {
// Unread messages are not previously processed...
if (messages[j].isStarred()) {
var msg = messages[j].getBody();
msg = msg.replace(/\r?\n/g, "");
var email = getDownloader(msg);
if (email == "") {
Logger.log("Found no downloader info: " + messages[j].getSubject() + " " + messages[j].getDate());
}
var date = formatDate(getDownloadDate(msg));
// Check if a new date
var dateCell = find(date, dlperday_sheet.getDataRange(), 0);
if (dateCell == null) {
// If new date, append row in "downloads per day"
dlperday_sheet.appendRow([date, 1]);
dlperday_sheet.getRange(2, 1, dl_sheet.getLastRow()-1, 2).sort([1]);
}
else
{
// If existing date, update row in "downloads per day"
var dlcount = dlperday_sheet.getRange(dateCell.getRow(), dateCell.getColumn()+1).getValue();
}
var productname = getProductName(msg);
// Check if e-mail (user) already exists in the downloads list
var matchingCell = find(email, dl_sheet.getDataRange(), 0);
if ( matchingCell != null ) {
// If user e-mail exists, update this row
var lastDownloadDate = dl_sheet.getRange(matchingCell.getRow(), matchingCell.getColumn()-1).getValue();
var lastDownloadCount = dl_sheet.getRange(matchingCell.getRow(), matchingCell.getColumn()+2).getValue();
if (lastDownloadDate != date) {
dl_sheet.getRange(matchingCell.getRow(), matchingCell.getColumn()-1).setValue(date);
}
dl_sheet.getRange(matchingCell.getRow(), matchingCell.getColumn()+2).setValue(lastDownloadCount+1);
}
else // If new user e-mail, add new download row
{
dl_sheet.appendRow([date, date, email, productname, 1]);
dl_sheet.getRange(2, 4).setValue(dl_sheet.getRange(2, 4).getValue() + 1);
dl_sheet.getRange(4, 1, dl_sheet.getLastRow()-3, 5).sort([1]);
}
// Mark message as processed, to avoid processing it on the next run
messages[j].unstar();
}
}
}
}
};
/**
* Finds a value within a given range.
* #param value The value to find.
* #param range The range to search in.
* #return A range pointing to the first cell containing the value,
* or null if not found.
*/
function find(value, range, log) {
var data = range.getValues();
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].length; j++) {
if (log == 1)
{
Logger.log("Comparing " + data[i][j] + " and " + value);
}
if (data[i][j] == value) {
return range.getCell(i + 1, j + 1);
}
}
}
return null;
};
function getDownloader(bodystring) {
var marker = "Buyer Info";
var marker_begin_index = bodystring.indexOf(marker, 1000);
var email_begin_index = bodystring.indexOf("mailto:", marker_begin_index) + 7;
var email_end_index = bodystring.indexOf("\"", email_begin_index);
if (email_end_index < 1000)
{
return "";
}
var email = bodystring.substring(email_begin_index, email_end_index);
if (log == 1)
{
Logger.log("Found e-mail address: " + email + "");
Logger.log(" marker_begin_index: " + marker_begin_index);
Logger.log(" email_begin_index: " + email_begin_index);
Logger.log(" email_end_index: " + email_end_index);
}
latestIndex = email_end_index;
return email;
};
function formatDate(mydate)
{
var str = "" + mydate;
var dateParts = str.split("/");
var day = dateParts[1];
if (day.length == 1)
day = "0" + day;
var month = dateParts[0];
if (month.length == 1)
month = "0" + month;
return dateParts[2] + "-" + month + "-" + day;
};
function getDownloadDate(bodystring) {
var marker = "Download Date:</strong>";
var marker_begin_index = bodystring.indexOf(marker, latestIndex);
var date_begin_index = marker_begin_index + marker.length;
var date_end_index = bodystring.indexOf("<br>", date_begin_index);
latestIndex = date_end_index;
return bodystring.substring(date_begin_index, date_end_index).trim();
};
function getProductName(bodystring) {
var marker = "Item:</strong>";
var marker_begin_index = bodystring.indexOf(marker, latestIndex);
var pname_begin_index = marker_begin_index + marker.length;
var pname_end_index = bodystring.indexOf("</td>", pname_begin_index);
latestIndex = pname_end_index;
return bodystring.substring(pname_begin_index, pname_end_index).trim();
};
Update: Any script I run stops after about 5 seconds, even if it does not call any services.
I tried the following code:
function test() {
Logger.log("Test begins");
Utilities.sleep(5000);
Logger.log("Test ends");
}
The script terminates after about 5 sec, but the last line is not printed. If decreasing the delay to 3 seconds it behaves as expected.
Moreover, to make the script update properly after modifying it, I need to save it, start it, click cancel, and then start it again, otherwise it the log output seems to come from the old version. (I'm running this through the script editor.)
Also, the debugger refuses to start, even for the small script above. Seems to be some problem with my account (johan.kraft#percepio.se). Are there any google employee out there that can check this?