I am a teacher designing a tool to take attendance from Zoom usage data (the days we live in!)
Zoom provides reports that you can copy and paste and stay formatted as a table. Like this:
Luke Name Name
lr#email.com
09/10/2020 08:22:03 AM 09/10/2020 08:33:36 AM 12
Barbara Name Name
bar#email.com
09/10/2020 08:22:12 AM 09/10/2020 08:31:57 AM 10
Joaquin Name Name Name
joa#email.com
09/10/2020 08:22:12 AM 09/10/2020 08:31:59 AM 10
Rafaella Name Name
raf#email.com
09/10/2020 08:22:18 AM 09/10/2020 08:31:55 AM 10
Andrea Name Name
and#email.com
09/10/2020 08:22:19 AM 09/10/2020 08:32:14 AM 10
Sara Name Name Name
sar#email.com
09/10/2020 08:22:20 AM 09/10/2020 08:31:56 AM 10
If this is posted into a text editor or a Google Sheet it does format into rows and columns correctly.
My code right now takes that long string of info and cuts out anything that is not an email and then checks the email against a class list in order to see who was absent. What I now want to do is to make it check for late students. Ideally the user would input the string and the time class started and it would display absent students and separately late students.
I think the part I can't figure out is how the user can copy paste the info into a prompt or into an html textarea and then correctly write it to rows and columns so I can manipulate the data.
Code:
//this function copies the info to a spreadsheet and sets formulas in order to find just emails and check them against a student list
function checkAttendance() {
var ss = SpreadsheetApp
var sheet = ss.getActiveSheet();
var response = SpreadsheetApp.getUi().prompt("Student Attendance", "Paste emails of students who attended.", SpreadsheetApp.getUi().ButtonSet.OK_CANCEL);
if(response.getSelectedButton() == SpreadsheetApp.getUi().Button.CANCEL) {return}
var responseText = response.getResponseText();
var splitText = responseText.split(" ");
var atHandle = SpreadsheetApp.getActive().getSheetByName("Attendance Handling");
atHandle.getRange(2, 1, 200, 100).clear();
atHandle.getRange(2, 3).setFormula("=FILTER(B2:B, ISNA(MATCH(B2:B, A2:A, 0)))");
var colNum = getColNum2("Student Email");
var rows = sheet.getLastRow();
sheet.getRange(2, colNum, rows-1, 1).copyTo(atHandle.getRange(2, 2, rows, 1));
atHandle.getRange(2, 1).setFormula('=TRANSPOSE(SPLIT(E2, " "))');
atHandle.getRange(2, 4).setFormula('=FILTER(A2:A, ISNA(MATCH(A2:A, B2:B, 0)))')
atHandle.getRange(2, 5).setValue(responseText);
atHandle.getRange(2, 6).setFormula('=if(AND(IFERROR(SEARCH("#", D2), "") <> "", IFERROR(SEARCH("(", D2), "") = ""), D2, "")');
SpreadsheetApp.getActive().getSheetByName("Attendance Handling").getRange(2, 6).copyTo(SpreadsheetApp.getActive().getSheetByName("Attendance Handling").getRange(2, 6, 200, 1));
atHandle.getRange(2, 7).setFormula('=FILTER(F2:F, F2:F <> "")');
showAttendance()
}
function showAttendance() {
var atHandle = SpreadsheetApp.getActive().getSheetByName("Attendance Handling");
var rows1 = getLastRow(3);
var rows2 = getLastRow(7)
var absent = atHandle.getRange(2, 3, rows1-1, 1).getValues().join("\n");
var present = atHandle.getRange(2, 7, rows2-1, 1).getValues().join("\n");
var t = HtmlService.createTemplateFromFile('attendance'); // Modified
t.Absent = absent
t.Present = present
html = t.evaluate().setWidth(600).setHeight(345); // Added
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
.showModalDialog(html, 'Class Attendance');
}
<!DOCTYPE html>
<html>
<head>
<base target="_top">
//style removed for space
</head>
<body>
<form>
<div>
Absent (in student list but not in pasted values):
<br>
<textarea id="tofield" name="tofield" rows="9" cols="60"><?!= Absent ?></textarea>
<br>
Guests (present in pasted values but not in student list):
<br>
<textarea id="tofield" name="tofield" rows="9" cols="60"><?!= Present ?></textarea>
<br>
<p>
</p>
</form>
Note: this information is not saved. Please use or copy and paste this information as needed beforing closing.
<p>
<button id="btn" style="margin-left: 8px; font-size: 15px; padding: 5px" onclick='google.script.host.close();' class="sbtn btn btn-secondary btn-c">Close</button>
</p>
</body>
</html>
You need to use something like google.script.run.doSomething(data) to pass values from the client-side to the server side code, where doSomething is a server side function and data is a a variable holding the value / object that you want to pass to the client side.
Note: There are some limitations regarding what values/object types can be passed to the server-side, i.e. Date objects can't be passed but you can pase the a string o the correspoining milliseconds.
The following code converts the sample data input into a bidimensional array that can be passed to sheet by using setValues(values).
var data = `Luke Name Name
lr#email.com
09/10/2020 08:22:03 AM 09/10/2020 08:33:36 AM 12
Barbara Name Name
bar#email.com
09/10/2020 08:22:12 AM 09/10/2020 08:31:57 AM 10
Joaquin Name Name Name
joa#email.com
09/10/2020 08:22:12 AM 09/10/2020 08:31:59 AM 10
Rafaella Name Name
raf#email.com
09/10/2020 08:22:18 AM 09/10/2020 08:31:55 AM 10
Andrea Name Name
and#email.com
09/10/2020 08:22:19 AM 09/10/2020 08:32:14 AM 10
Sara Name Name Name
sar#email.com
09/10/2020 08:22:20 AM 09/10/2020 08:31:56 AM 10`;
const arr = data.split('\n');
const matrix = [];
for(let i = 0; i < arr.length;i = i + 3){
var row = [arr[i],arr[i+1],
new Date(arr[i+2].slice(0,22)),
new Date(arr[i+2].slice(24,46)),
arr[i+2].slice(48)]
matrix.push(row);
}
console.info(matrix);
I suggest you to use the above on the server side, as mentioned previously, because Date objects can't be passed from client-side to sever-side, just be sure that the Apps Script, the Spreadsheet and Zoom are using the same timezone, otherwise you will have improve it to handling the timezones differences appropiately.
Resources
Client-to-Server communication | HTML Service | Google Apps Script
You can construct a new Date object from the parsed out times and compare them. If you can get the string representaiotn of the dat you can pass it to the construcotr of Date diruectrly
new Date('December 17, 1995 03:24:00');
Once you have the dates you can use operators to compare them, and determine if the student was late or not.
function isLate(classStart) {
var dateStr = /* get the cell value */
var studentArrvial = Date.parse(dateStr)
return studentArrival - dateStr > 0
}
Related
I was hoping I could figure out why the body is not generating in my emails created via Google Apps Script.
My department in our company is responsible for checking over 500 computers in the plant to verify that all software packages are up to date, I have already created a google sheet that has each computer in a "name" field, with a date located in a "last Evaluated" field. The last updated field will highlight red if it has not been updated in 90 days, and there is a COUNTIFs function to find the total of PCs that have not been updated in 90 days, as well as 180 days, 365 days, and last update unknown.
I want to use Google Apps Script to generate an email that sends weekly (or monthly, not sure yet) to other users in the dept where it reads something like this:
Subject: "Weekly PC Update Report
Body: There are {90 days overdue quantity} PCs that are 90 days overdue, {180 days quantity}..., {365 quantity}..., and {unknown quantity}PCs who's last update is unknown.
Searching for solutions, I found an example that pulls data from a stocks spreadsheet, After making the needed changes, I came up with this (I apologize for any readability issues/errors, I am new to Javascript and programming in general):
(days90 is a named range in a google sheet called Update Log)
function sendEmail(){
var overdue90 = get90data();
var body = getEmailText(overdue90);
MailApp.sendEmail({
to: "EMAIL WITHHELD FOR PRIVACY",
subject: "Weekly PC Update Report",
body: body
});
}
function getEmailText(overdue90) {
var text = "";
overdue90.forEach(function(day90) {
text = text + "There are " + days90 + "computers that have not been updated in the last 90 days." + "\n" + "\n";
});
return text;
}
function get90data() {
var values = SpreadsheetApp.getActive().getSheetByName("Update Log").getRange("days90").getValues();
values.shift(); //remove headers
var days90 = [];
values.forEach(function(value) {
var day90 = {};
day90.amount = value[0];
days90.push(day90);
});
//Logger.log(JSON.stringify(days90));
return days90;
}
The email does send to my inbox with the appropriate subject line, but the body is empty, when I tried making small changes I recieved some emails with the text [Ljava.lang.Object;#265ca7f2. (not every one of these emails were identical, but they all had the [Ljava.lang.Object!# portion.
Try to replace return text; in getEmailText function by
return text.toString()
or
return JSON.Stringify(text)
We are moving away from AD and going to Google Work space as our LDAP service. One of my tasks are to reproduce some tools that we have on AD to Google Workspace. One of those tools is the ability to warn a user 14 days before a password is about to expire then 7 days before and send us (IT admins) a warning 4 days before to catch it before they get locked out. So I have created this tool in PowerShell on windows and LDAP and I am trying to use Google App Scripts to do this. I have learned that the Directory API does not expose this metadata on the user (https://developers.google.com/admin-sdk/directory/v1/guides/manage-users) and I have to use the Admin Report API and search the logs for an eventName called CHANGE_PASSWORD and then build an array of user email address and the last time they changed their password.
I have got this successfully working to show me a HTML output of email address and how many days left till their password expires in a table that is generated on demand when you load the web app, but I noticed we have 120 users in our Org and only 78 users show up on the list. So then I realized that the reports section of Google Admin reports only stores 6 months worth of logs. To me the reports section is not a reliable source to determine when the user last changed their password. Does anyone have any other ideas as to how I can accurately get the date a Google Workspace user last changed their password? Here is what I currently have if anyone wants to build on this:
Please note you must add the Admin SDK API service to your script and the user running the script my have the role for reports on your domain. This is not polished code but just a proof of concept so be gentle with your replies and comments about my sloppy code
code.gs
const maxPasswordAge = 90;
function doGet() {
return HtmlService.createHtmlOutputFromFile('Index');
}
function getListOfUsers() {
const userKey = 'all';
const applicationName = 'admin';
const optionalArgs = {
eventName: 'CHANGE_PASSWORD'
};
const logs = AdminReports.Activities.list(userKey, applicationName, optionalArgs);
const activities = logs.items;
if (activities && activities.length > 0) {
var passwordLastChanged = new Object();
for (i = 0; i < activities.length; i++) {
const activity = activities[i];
const key = activity.events[0].parameters[0]['value'];
const value = activity.id.time;
// If the key does not exist then add it
if (!passwordLastChanged.hasOwnProperty(key)) {
passwordLastChanged[key] = value;
}
}
} else {
Logger.log('No results found.');
}
// You will now have an object with emailaddress:Date last Changed
const todaysDate = new Date();
// Change the date to a number of days till it expires from today's date
for (const [key, value] of Object.entries(passwordLastChanged)) {
const dateLastChange = new Date(value);
const milliBetweenDate = todaysDate - dateLastChange;
const diffInDays = milliBetweenDate / (1000 * 3600 * 24);
passwordLastChanged[key] = (maxPasswordAge - diffInDays).toFixed(1);
}
// Change the object to a sorted array based on the days left till password expires
const entries = Object.entries(passwordLastChanged);
const sorted = entries.sort((a, b) => a[1] - b[1]);
return sorted;
}
Index.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<style type="text/css">
body {background-color: gray;}
.tg {border-collapse:collapse;border-spacing:0;margin:0px auto;}
.td {border-color:black;border-style:solid;border-width:1px;font-family:Arial, sans-serif;font-size:14px; overflow:hidden;padding:10px 5px;word-break:normal; color: floralwhite;}
.col1, .col2, .col3 {text-align:left;vertical-align:top}
.col4, .col5 {text-align:center;vertical-align:top}
</style>
<table id="myTable" class="tg">
<tbody>
<tr>
<td class="col3">Email</td>
<td class="col5">Days Left</td>
</tr>
</tbody>
</table>
<script
src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js">
</script>
<script>
// The code in this function runs when the page is loaded.
$(function() {
google.script.run.withSuccessHandler(addUsersToTable).getListOfUsers();
});
function addUsersToTable(userArray) {
for (var i = 0; i < userArray.length; i++) {
$('#myTable > tbody:last-child').append('<tr><td class="col3">' + userArray[i][0] + '</td><td class="col5">' + userArray[i][1] + '</td></tr>');
}
}
</script>
</body>
</html>
So yes , 6 months is the maximum they keep the logs. If you are on the Enterprise SKU (which Is unlikely with 120 users) you can export logs to Bigquery indefinably and Query that. https://support.google.com/a/answer/9079365?hl=en If you have something else that is able to ingest the logs you can do that too.
However , I did something for the suspension date as that date is not set in a user field either.
You are going to need 2 processes.
Create a custom schema attribute for your password date option.
Have a process that routinely scrapes the audit logs. Say everyday you look for password changes in the past 24 hours. You then set the value on the custom schema attribute accordingly.
You second process will monitor that custom schema attribute and do your alerting to users.
To start I had to make an assumption. All users that didn't meet existing criteria would have a fixed date set. Moving forward this new process would ensure everything is inline.
I have created a web app to track the working hours of employees at my company, the web app is simple, it just asks employees to enter their entry time when they arrive to work and the time when they leave. I have them enter first, their ID number and a password, then they just fill out the date, the time at which they arrived/left and I added a string for any other details they would like to add. Bringing this data to a google spreadsheet, as seen in the picture. What I want to do is add a VLOOKUP function, in the column that says "Name", that will search for the employees name by looking up their ID number in a data base, the problem is once I add the formula to the column labeled "Name", the spreadsheet stops receiving new data from the web app. Here's a link to a copy of the sheet (https://docs.google.com/spreadsheets/d/1fjpKRi3k0VQ8MOoa5ruCZurXcz6vmVsQt2g3NbELSJA/edit#gid=1409321990) and an here is the simplified the JavaScript and HTML code.
function doGet(e) {
return HtmlService.createHtmlOutputFromFile('Form');
}
function AddRecord(DateEntry, username, ArrivalTime, ExitTime ) {
// get spreadsheet details
var url = 'https://docs.google.com/spreadsheets/d/1fjpKRi3k0VQ8MOoa5ruCZurXcz6vmVsQt2g3NbELSJA/edit#gid=';
//Paste URL of GOOGLE SHEET
var ss1= SpreadsheetApp.openByUrl(url);
var webAppSheet1 = ss1.getSheetByName('ReceivedData');
const Lrow = webAppSheet1.getLastRow();
const sep_col = 2;
const data = [DateEntry, username, ArrivalTime, ExitTime, new Date()];
const data1 = data.slice(0,sep_col);
const data2 = data.slice(sep_col,data.length);
const start_col = 1;
const space_col = 1;
webAppSheet1.getRange(Lrow+1,start_col, 1, data1.length).setValues([data1]);
webAppSheet1.getRange(Lrow+1,start_col+data1.length + space_col, 1, data2.length).setValues([data2]);
}
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<title>Time Tracking</title>
<script>
function AddRow()
{
var DateEntry = document.getElementById("DateEntry").value;
var username = document.getElementById("username").value;
var ArrivalTime = document.getElementById("ArrivalTime").value;
var ExitTime = document.getElementById("ExitTime").value;
google.script.run.AddRecord(DateEntry, username, ArrivalTime, ExitTime);
document.getElementById("DateEntry").value = '';
document.getElementById("username").value = '';
document.getElementById("ArrivalTime").value = '';
document.getElementById("ExitTime").value = '';
}
</script>
</head>
<body>
<div class="content">
<div class="header">
<h2>Time Tracking</h2>
</div>
<div>
<label>ID</label><br>
<input class="field" id="username" type="text">
</div>
<div>
<label>Date</label><br>
<input class="field" type="date" id="DateEntry" />
</div>
<div >
<label>Arrival Time</label><br>
<input class="time" type="time" id="ArrivalTime" /><br>
</div>
<div >
<label>Exit Time</label><br>
<input class="time" type="time" id="ExitTime" /><br>
</div>
<div class="btn">
<button type="button" value="Add" onclick="AddRow()">Send</button>
</div>
</div>
</body>
</html>
Explanation / Issue:
The issue is that you are using an arrayformula which expands until the last available row in the sheet.
However, your goal is to append a new row everytime after the last row with content.
Therefore, by using getLastRow you are getting the wrong row.
Instead of using arrayformula use a single vlookup formula and
take advantage of template literals in order to dynamically
change the vlookup value:
=IFERROR(VLOOKUP(B${Lrow+1};DataBase!A2:B250;2;0);"")
In this way you don't need two different arrays (data1 and data2) because the data can be pasted directly into the sheet:
const vlp_form = `=IFERROR(VLOOKUP(B${Lrow+1};DataBase!A2:B250;2;0);"")`;
const data = [DateEntry, username ,vlp_form, ArrivalTime, ExitTime, new Date()];
webAppSheet1.getRange(Lrow+1,1, 1, data.length).setValues([data]);
I changed your formula to match the DataBase sheet from BBDD to DataBase. Change that back (in the script) if you are using the other sheet name.
Solution:
Modify only AddRecord as follows:
function AddRecord(DateEntry, username, ArrivalTime, ExitTime ) {
// get spreadsheet details
const url = 'https://docs.google.com/spreadsheets/d/1fjpKRi3k0VQ8MOoa5ruCZurXcz6vmVsQt2g3NbELSJA/edit#gid=';
//Paste URL of GOOGLE SHEET
const ss1= SpreadsheetApp.openByUrl(url);
const webAppSheet1 = ss1.getSheetByName('ReceivedData');
const Lrow = webAppSheet1.getLastRow();
const vlp_form = `=IFERROR(VLOOKUP(B${Lrow+1};DataBase!A2:B250;2;0);"")`;
const data = [DateEntry, username ,vlp_form, ArrivalTime, ExitTime, new Date()];
webAppSheet1.getRange(Lrow+1,1, 1, data.length).setValues([data]);
}
Be careful:
In order for the changes to take effect you need to re-deploy your
webApp (either new deployment in the new script editor) or if you are
using the older editor, deploy the web app again by selecting project
version new and click on update.
Also don't forget to save the script changes.
I am building a simple Google App Script that sends an e-mail every month with a file attached.
The file is updated with :
-the first day of the current month
-the last day of the current month
-the name of the month
All document creation and fields modification works well (I see the gdoc with modified fields few seconds after the script is launched).
The problem is that I receive an e-mail WITHOUT the modified fields in the attachment.
Does anyone knows WHY THE CHILD DOCUMENT IS MODIFIED WELL, BUT THE PDF VERSION ATTACHED TO THE EMAIL HASN'T ITS FIELDS MODIFIED ?
Thank you !
function myFunction() {
//DEALS WITH DATES
//Create a date
var today = new Date();
//Create a variable that will take month's values
var Mois = ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Decembre"];
//Get the current month
var MoisNum = today.getMonth()+1;
var TextMois = Mois[MoisNum-1];
//Get the number of first day of the month, and last day of the month
var Jour = today.getDate();
var NombreJourMois = ["31", "28", "31","30","31","30","31","31","30","31","30","31"];
var DernierJourMois = NombreJourMois[MoisNum-1];
//Get the current year
var Annee = today.getYear();
//Sets two variable with the date of the first day of the month and last day of the month
var DateDebutMois = 01+'/'+MoisNum+'/'+Annee;
var DateFinMois = DernierJourMois+'/'+MoisNum+'/'+Annee;
//CREATE A COPY OF THE PARENT FILE AND PUT IT IN THE RIGHT FOLDER
//Dossier de destination et fichier template
var DossierSource = DriveApp.getFolderById('0B0mCpRQd5BQidmQ2MGl4em9MRzA');
var DossierCible = DriveApp.getFolderById("13pril4REhDrITljX523z7LdQgVpOcazU");
var FichierSource = DriveApp.getFileById('109_Kuoxjh-C56MYeFhyyP-PhYJirCD5OVhuce0v7osI');
//Create a copy of the document and put it in the above folder
var Enfant = FichierSource.makeCopy();
DossierCible.addFile(Enfant);
DossierSource.removeFile(Enfant);
//MODIFY CHILD DOCUMENT WITH THE PROPER DATES AND RENAME IT
//Get the body of the child document
var BodyEnfant = DocumentApp.openById(Enfant.getId()).getBody();
//Replace date fields of the body by the right dates, and changes the name of the child document
BodyEnfant.replaceText('{1erDuMois}', DateDebutMois);
BodyEnfant.replaceText('{DernierDuMois}',DateFinMois);
Enfant.setName('Quittance de loyer - 218 rue de Grenelle - '+DateDebutMois);
//SEND AND EMAIL WITH THE DOCUMENT ATTACHED
var recipient = 'jean-marie.laly#magicmakers.fr';
var subject = 'Quittance de loyer - '+TextMois+' - '+Annee;
var body = 'Bonjour, Veuillez trouver ci-joint la quittance de loyer du mois de '+TextMois;
var pdf = Enfant.getAs("application/pdf");
GmailApp.sendEmail(recipient, subject, body, {attachments: [pdf],name: ''});
}
I think that the reason of your issue is because the modified values are not saved. So please convert to the PDF after the document was saved. In order to save the modified values to the document, how about this modification for your script?
Here, only modification part in your script was shown.
From :
//Get the body of the child document
var BodyEnfant = DocumentApp.openById(Enfant.getId()).getBody();
//Replace date fields of the body by the right dates, and changes the name of the child document
BodyEnfant.replaceText('{1erDuMois}', DateDebutMois);
BodyEnfant.replaceText('{DernierDuMois}',DateFinMois);
Enfant.setName('Quittance de loyer - 218 rue de Grenelle - '+DateDebutMois);
To :
//Get the body of the child document
var doc = DocumentApp.openById(Enfant.getId()); // Modified
var BodyEnfant = doc.getBody(); // Modified
//Replace date fields of the body by the right dates, and changes the name of the child document
BodyEnfant.replaceText('{1erDuMois}', DateDebutMois);
BodyEnfant.replaceText('{DernierDuMois}',DateFinMois);
Enfant.setName('Quittance de loyer - 218 rue de Grenelle - '+DateDebutMois);
doc.saveAndClose(); // Added
Reference :
saveAndClose()
If this was not what you want, please tell me. I would like to modify it.
I am having trouble setting up a script to iterate through a Google spreadsheet and email to the employees the lines that contain the reports about them.
In my tests The email address and subject lines are being emailed but depending on what I try the email is either blank or says "[object Object]".
I am viewing emails from the Gmail site in Chrome.
The HTML code does not have any variables yet. I am still working on getting it to email correctly and then I will try working in scriplets to build a table for the report.
Code.gs
function changeName() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var shA = ss.getSheets()[0]; // Report download
var shB = ss.getSheets()[1]; // Names & email address
var alRow = shA.getLastRow();
var blRow = shB.getLastRow();
var data = shA.getRange(1, 1, alRow, 14).getValues(); // Array of all employees reports
var employees = shB.getRange(1, 1, 2, 2).getValues(); // List of employees [i][0] and email address [i][1] in Scorecard data
for(i=0;i<employees.length;i++){
var html = HtmlService.createTemplateFromFile('Index').evaluate();
MailApp.sendEmail(employees[i][1], employees[i][0] + ' Scorecards Test', {htmlbody: html.getContent()});
Logger.log(html.getContent());
}
}
Index.html
<body>
<p1>Test 5062</p1>
<table>
<tr>
<td>Test Cell 1</td>
<td>Test Cell 2</td>
<td>Test Cell 3</td>
</tr>
</table>
</body>
When I view the log, it shows the HTML file.
What I am doing wrong?
How about a following modification?
From :
function changeName() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var shA = ss.getSheets()[0]; // Report download
var shB = ss.getSheets()[1]; // Names & email address
var alRow = shA.getLastRow();
var blRow = shB.getLastRow();
var data = shA.getRange(1, 1, alRow, 14).getValues(); // Array of all employees reports
var employees = shB.getRange(1, 1, 2, 2).getValues(); // List of employees [i][0] and email address [i][1] in Scorecard data
for(i=0;i<employees.length;i++){
var html = HtmlService.createTemplateFromFile('Index').evaluate();
MailApp.sendEmail(employees[i][1], employees[i][0] + ' Scorecards Test', {htmlbody: html.getContent()});
Logger.log(html.getContent());
}
}
To :
function changeName() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var shA = ss.getSheets()[0]; // Report download
var shB = ss.getSheets()[1]; // Names & email address
var alRow = shA.getLastRow();
var blRow = shB.getLastRow();
var data = shA.getRange(1, 1, alRow, 14).getValues(); // Array of all employees reports
var employees = shB.getRange(1, 1, 2, 2).getValues(); // List of employees [i][0] and email address [i][1] in Scorecard data
for(i=0;i<employees.length;i++){
var html = HtmlService.createTemplateFromFile('Index').evaluate();
MailApp.sendEmail({
to: employees[i][1],
subject: employees[i][0] + ' Scorecards Test',
htmlBody: html.getContent()
});
Logger.log(html.getContent());
}
}
If this didn't work, I'm sorry.
I find it a lot easier to build the html right in the file like this:
var s='<table>';
s+='Utilities.formatString('<tr><td>%s</td><td>%s</td><td>%s</td></tr>',data[i][0],data[i][1],data[i][2]);
s+='</table>'`;
MailApp.sendMail({to:emailAddress, subject: subj, htmlBody: s});
And keep in mind your html doesn't have to be complete.
Often I actually build the email this way and disable the sendEmail while I'm building it and just do this to view my results.
var ui=HtmlService.createHtmlOutput(s);
SpreadsheetApp.getUi().showModeLessDialog(ui,title);
And that way I can see what I'm doing while I built it without having to send emails. Some people use the logger but I like doing it this way.
And often people end up wanting to put some conditions on sending the email.
Email Address is not empty
Body is not empty
The email hasn't already been sent
You haven't exceeded your remaining daily quota
But hey everyone is different and you can choose to do it whatever way that makes you happy.