I use a script to automatically insert the date and username to fill in the string. Several people fill out the table. How can I modify the script so that the first name and the last name are inserted instead of the username?
function onEdit(e) {
var sheet = e.source.getActiveSheet();
var idCol = e.range.getColumn();
var idRow = e.range.getRow();
if ( idCol == 4 && sheet.getName() =='LIST1' ) {
var Value = e.range.offset(0, -2).getValues();
if ( Value == "" ) {
var vartoday = getDate(); //Gets the current date//
sheet.getRange(idRow, 2).setValue( vartoday ); //Inserts into column 2 of the current row//
var u = Session.getEffectiveUser().getUsername(); //Gets the username of the editor//
sheet.getRange(idRow, 7).setValue(u); //Inserts into column 7 of the current row//
}
}
function getDate() {
var today = new Date();
today.setDate(today.getDate());
return Utilities.formatDate(today, 'GMT+03:00', 'dd/MM/yy');
}
}
Looks like you are storing the username here -
var u = Session.getEffectiveUser().getUsername(); //Gets the username of the editor//
And then inserting the username here -
sheet.getRange(idRow, 7).setValue(u); //Inserts into column 7 of the current row//
You will want to split the name before you store it. Something like this -
var first_name = // Split by your delimeter here and store first name
var last_name = // Split by your delimeter here and store last name
sheet.getRange(idRow, 7).setValue(first_name); //Inserts into column 7 of the current row//
sheet.getRange(idRow, 8).setValue(last_name); //Inserts into column 8 of the current row//
Issue:
getUsername() only gives the email address without the domain part (that is, the content before #). It doesn't provide information on the name or surname, assuming that not every email address is formatted in a distinguishable way, as suggested in other answers (e.g. name.surname).
If you want to access the actual name and surname of a user, you would need to access the Users resource from Directory API. The easiest way to do that is to use the advanced Admin SDK Directory Service on your script.
Important note: This requires the use of Admin SDK, which can only be used by Google Workspace (formerly G Suite) accounts. If you are not a Workspace user, there's no way to retrieve this information.
Workflow:
Enable the advanced Directory Service on your script by clicking Resources > Advanced Google Services..., set the directory_v1 to on and click OK: see example.
Since accessing this API requires authorization, and simple triggers cannot access service that require authorization (see Restrictions), you will have to install the onEdit(e) trigger. First, change the name of your function so that it's not called onEdit. That's a reserved function name, to be used for simple triggers (I called it fireOnEdit on the code sample below).
Install the trigger, either manually (see example settings) or programmatically.
In installed triggers, the effective user is the user who installed the trigger, not the one whose edit triggered the script. Therefore, you would need to use getActiveUser(), or the event object (property e.user) instead.
Using the Advanced service, retrieve the active user properties by calling Users: get.
Retrieve the two desired fields from the API response: first name (User.name.givenName) and family name (User.name.familyName).
Code sample:
function fireOnEdit(e) {
var sheet = e.source.getActiveSheet();
var idCol = e.range.getColumn();
var idRow = e.range.getRow();
if ( idCol == 4 && sheet.getName() =='LIST1' ) {
var Value = e.range.offset(0, -2).getValues();
if ( Value == "" ) {
var vartoday = getDate(); //Gets the current date//
sheet.getRange(idRow, 2).setValue( vartoday ); //Inserts into column 2 of the current row//
var email = e.user.getEmail();
var user = AdminDirectory.Users.get(email);
var name = user.name.givenName;
var surname = user.name.familyName;
//sheet.getRange(idRow, 7).setValue(name + " " + surname); // Name and surname copied to the same cell in G
sheet.getRange(idRow, 7, 1, 2).setValues([[name, surname]]); // Name and surname copied to G and H
}
}
function getDate() {
var today = new Date();
today.setDate(today.getDate());
return Utilities.formatDate(today, 'GMT+03:00', 'dd/MM/yy');
}
}
Notes:
Information on the active user might not always be available (for example, if the user who created the trigger and the one causing the script to run don't belong to the same domain). See this answer, for example.
You would need to install the trigger with an account that has access to the Users resource from Directory API.
I'm unsure whether you want to concatenate both first name and surname in the same cell, or add one to column G and the other to column H. I added both possibilities in the code sample, please comment/uncomment the corresponding lines according to your preferences.
If you're a Gsuite/Google workspace customer, You can use Directory API as mentioned in this answer.
If not, You may be able to leverage the identity token provided by ScriptApp.
★ You need to get explicit permission from each of your editor to get their name. Without getting explicit permission, You will not be able to log their edits programmatically. The built in feature "edit history" would still log them though.
Flow:
Get current script scopes from File > Project properties> Scopes
Add explicit openid, profile and email scopes to the above scopes and add them to the manifest. For eg, the following sample script requires the following scopes:
"oauthScopes":["openid","profile","email",
"https://www.googleapis.com/auth/script.scriptapp",
"https://www.googleapis.com/auth/spreadsheets.currentonly",
"https://www.googleapis.com/auth/script.container.ui"
],
Ask editors to sign up to add them to the edit log by clicking a menu button
Create a installed Edit trigger for them on clicking the menu button.
Use the installable edit trigger to get a identity token. Parse the token to get given name and family name.
Sample script:
function getNameOfCurrentEditor() {
const idToken = ScriptApp.getIdentityToken();
const body = idToken.split('.')[1];
const decoded = Utilities.newBlob(
Utilities.base64Decode(body)
).getDataAsString();
const { given_name: firstName, family_name: lastName } = JSON.parse(decoded);
return { firstName, lastName };
}
/**
* #param{GoogleAppsScript.Events.SheetsOnEdit} e
*/
function installedEditTrigger(e) {
const eUser = e.user.getEmail();
if (eUser === '') return; //no permission=> exit
const { firstName, lastName } = getNameOfCurrentEditor();
e.range.setNote(
`${e.range.getNote()}\n${e.value} added by ${firstName}_${lastName}`
);
}
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Edit Logger')
.addItem('Sign me up!', 'createEditTrigger')
.addToUi();
}
function createEditTrigger() {
ScriptApp.newTrigger('installedEditTrigger')
.forSpreadsheet(SpreadsheetApp.getActive())
.onEdit()
.create();
}
Note: Multiple edit triggers for all editors, who signed up will run automatically, but only the editor who actually made the edit will be allowed to pass beyond this condition:if (eUser === ''). This works because each editor is unable to get email addresses of other editors. Only a empty string is returned in that case.
Explanation:
Assuming that the username has the format of name.surname then you can use split() to separate the name from the surname by using u.split('.')[0] and u.split('.')[1] respectively.
If the username has a different format then you can change the argument accordingly. For example, if you have name-surname then use u.split('-')[0] and u.split('-')[1].
Solution:
function onEdit(e) {
var sheet = e.source.getActiveSheet();
var idCol = e.range.getColumn();
var idRow = e.range.getRow();
if ( idCol == 4 && sheet.getName() =='LIST1' ) {
var Value = e.range.offset(0, -2).getValues();
if ( Value == "" ) {
var vartoday = getDate(); //Gets the current date//
sheet.getRange(idRow, 2).setValue( vartoday ); //Inserts into column 2 of the current row//
var u = Session.getEffectiveUser().getUsername(); //Gets the username of the editor//
sheet.getRange(idRow, 7).setValue(u.split('.')[0]); //Inserts into column 7 of the current row the firstname of the editor//
sheet.getRange(idRow, 8).setValue(u.split('.')[1]); //Inserts into column 8 of the current row the last of the editor//
}
}
function getDate() {
var today = new Date();
today.setDate(today.getDate());
return Utilities.formatDate(today, 'GMT+03:00', 'dd/MM/yy');
}
}
Related
Javascript newbie here, so apologies if this is incredibly basic and not the prettiest coding.
I'm creating a form that I want to have sent to a different email address depending on which team someone selects (e.g. if you select Sales it will generate an email to Team X, Refunds will email Team Y etc).
So far I've got to the stage where I can click a button to generate an email and attach the form as a PDF, but I don't know how to make it variable depending on the field value.
Current code:
var callerName = this.getField("CallerName").value;
var customSubject = this.getField("WhichTeam").value;
//I've used a fake email address for the next line variable, but this is the one I want to change depending on the "WhichTeam" field value
var mailtoUrl = "mailto:Email#email.com?subject=Callback Referral for " + customSubject;
this.submitForm({
cURL: mailtoUrl,cSubmitAs: "PDF"});
Hope this makes sense. Grateful for any help/advice?
Thanks
You can try using an object to contain the emails with the teams as they values. And to also use a template string to insert the value into the mailtoUrl.
var emails = {
refunds: 'refundsTeamEmail',
sales: 'salesTeamEmail',
}
var callerName = this.getField("CallerName").value;
var customSubject = this.getField("WhichTeam").value;
// ex. customSubject value is 'refunds'
// emails[customSubject] would be the same as doing emails.refunds
// which return the value of 'refundsTeamEmail'
var mailtoUrl = `mailto:${emails[customSubject]}?subject=Callback Referral for ` + customSubject;
this.submitForm({
cURL: mailtoUrl,cSubmitAs: "PDF"});
I think this is the simplest way to do it as the value will change each time you run the function to send the email without having to create or call other functions.
Having just said the last thing, you could also use a switch case in another function to return the value. If you want to. Something like:
function fetchEmail(email) {
switch(email) {
case 'refunds':
return 'refundsTeamEmail'
case 'sales':
return 'salesTeamEmail'
default:
return ''
}
}
var callerName = this.getField("CallerName").value;
var customSubject = this.getField("WhichTeam").value;
// ex. customSubject value is 'refunds'
// emails[customSubject] would be the same as doing emails.refunds
// which return the value of 'refundsTeamEmail'
var mailtoUrl = `mailto:${fetchEmail(customSubject)}?subject=Callback Referral for ` + customSubject;
this.submitForm({
cURL: mailtoUrl,cSubmitAs: "PDF"});
I am new to Google Apps Script. I have a sheet that collects some "Order Number" from form submit. I want to send mails through an event (On form submit) from my spreadsheet. The form will serve an order number. When the form is submitted, it will match the older submitted order numbers throughout the whole column. If it got matched once, the mail won't be sent. If it doesn't match then it will send a mail to the email address next to the order number.
The email address will come from another sheet on the same spreadsheet using VLOOKUP. I managed to do this.
Sorry if I make any mistake with my English.
Edit:
I tried map() , filter() , indexOf() these methods. But I too new with this.
function search(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Copy of orderStatus");
var lr = ss.getLastRow() - 1;
var keyword = ss.getRange("H5").getValue();
var dataSource = ss.getRange(2, 2, lr, 1).getValues();
var mapped = dataSource.map(function(r){
return r[0]});
var showPos = mapped.indexOf(keyword) + 2;
var getMail = ss.getRange(showPos, 4).getValue();
var filted = mapped.filter(filterlogic);
}
var filterlogic = function(r){
if(r !== "zil20200010"){
return true;
} else {
return false;
}
}
On form submit, select the column (range) where you store all the order numbers and create a TextFinder and store it in a variable using the createTextFinder(findText) method for the specified range.
Get the TextFinder from the previous step and search the order number using the findNext() method.
If findNext() returns null then move to the next step. else, do nothing.
Get the email address to which you plan to send the order number.
After having the email address, use the sendEmail(recipient, subject, body, options) method to send the email. If you'd like, you can use HTML in the body to make it more professional.
For additional information, read:
the reference guide on creating TextFinders,
the reference guide on finding text using a TextFinder,
and the reference guide on GmailApp.
Sample code:
// imagine you store all the order numbers in column C, starting from row 2 to the last row in the column:
var emailRecipient = test#test.com;
var ordernumber = 123;
var RangeToSearch = sheet.getRange(2,3,sheet.getLastRow());
var TextFinder = RangeToSearch.createTextFinder(ordernumber);
var found = TextFinder.findNext();
if (found == null) {
MailApp.sendEmail({
to: emailRecipient,
subject: "New Order! Order Number: " + ordernumber,
htmlBody: html
});
}
First of all, thanks to all of you who helped me to reach this point. I found the solution to my problem after some "trial and error". I wanted to limit sending emails.
This code takes the Range. Get its values in an array. I mapped that array to act as a string. Then I added .pop() to that string, it removes our last/newly submitted data in that range. Then I used .includes() method to search my value in the mapped array, and assigned it to a variable called final (just came to my mind). This variable returns true/false depending on search results. If the order number does not exist then it returns false. After that, we set an if statement to execute our mailing function. If our order number does not match and return final as false our mailing function happens. Else it does nothing (means no email sents). And that's it!
Here is the code that solved my problem
function orderStatus(e) {
try {
var theirMail, subject, message;
var ourName, theirName;
var sSheet, orderNum, cosmetics, orderSts, phNum, lr,dataSource, mapped, final;
ourName = "My Company Name";
orderNum = e.namedValues["Order Number"].toString();
sSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("orderStatus");
lr = sSheet.getLastRow() - 1;
dataSource = sSheet.getRange(2, 2, lr).getValues();
mapped = dataSource.map(function(r){
return r[0].toString()});
mapped.pop();
final = mapped.includes(orderNum);
orderSts = sSheet.getRange(sSheet.getLastRow(),1).getValue();
theirMail = sSheet.getRange(sSheet.getLastRow(),4).getValue();
theirName = sSheet.getRange(sSheet.getLastRow(),5).getValue();
phNum = sSheet.getRange(sSheet.getLastRow(),6).getValue();
subject = "Order status notification from " + ourName + " to " + theirName;
if (final == false){
message =
"<div style='text-align: left; padding-left: 30px;'><h2>Dear <b>" + theirName +
",</b></h2><p>Your order no is <b><span style='font-size: 14px;'>" + orderNum +
"</span>.</b> <b><span style='font-size: 14px;'>Your order has been processed.</span>" +
"</b></p><p>We packaged your order and dropped it to the logistics. You will recieve phone call on <b><span style='font-size: 14px;'>" + phNum +
"</span></b> from logistics.<br>Thanks for purchasing from <b><span style='font-size: 14px;'>" + ourName +
"</span></b>.</p><p>Best regards,<br><b><span style='font-size: 14px;'>"+ourName+"</span></b></p></div>"+
"<p style='text-align: center;'><br><b>For further information please visit our facebook page <a href='https://www.facebook.com/' target='_blank' rel='noopener'>"+ourName+"</a>.</b></p><hr />";
textbody = message.replace("<br>", "\n\n");
cosmetics = {name: ourName, htmlBody: message};
MailApp.sendEmail(theirMail, subject, message, cosmetics);
}
}
catch (e) {
Logger.log(e.toString());
}
}
I am using a google spreadsheet which looks like:
A B C D
1 Name e-mail Identifer Status
2 Alex ax#gmail.com ERT ER A
3 Micke miike477#gmail.com Ejyu er w
4 John john7788#tri.com Arb Ed C
I have a drop down list in column D (let say A,B & C for example), now i want that whenever the value changes (Initially the column D would be blank) in column D against a particular Name than an automatic e-mail trigger to e-mail id mentioned in column B by below mentioned sender id and content.
The email should be trigger whenever value changes in column D except for the blank, and if there were previously value was "B" and now it change to "C" than mail should be trigger.
Sender-example#gmail.com
CC-test1#gmail.com,test2#gmail.com
E-mail Body:
Hi Alex (Should be picked from column A depending against which name e-mail trigger)
some sentence here.
some sentence here with your ERT ER (Should be pick from column C) has status A (should be pick from column D).
Regards,
example
123456789
I am trying using below mentioned script:
function onEdit(event){
if(event.range.getColumn() == 4){ //A -> 1, B -> 2, etc
function sendMyEmail(line){
var sendTo = spreadsheet.getRange(row, 2).getValue();
var cc = 'test1#gmail.com'+","+'test2#gmail.com';
var subject = "What is the: "+ spreadsheet.getRange(row, 3).getValue();
var content = "Hi "+spreadsheet.getRange(row, 1).getValue();+","
+"what is the vlaue "+spreadsheet.getRange(row, 3).getValue();+ "with the status"+spreadsheet.getRange(row, 4).getValue();+ "."
MailApp.sendEmail(sendTo,
cc,
subject,
content);
}
}
}
You have two major issues.
Simple triggers cannot access services that require authorization (such as MailApp).
Your usage of MailApp.sendEmail() is incorrect as you're passing cc to where should be passed either the subject or the replyTo address (docs). Argument order is important.
To address the issue of simple triggers, all you need to do is install a trigger manually that calls your function on edit.
All other issues are addressed in the code below.
function sendEmailToUser(event){
var eventRange = event.range;
var sheet = eventRange.getSheet();
var sheetName = sheet.getName();
var column = eventRange.getColumn();
if (sheetName == "Sheet1" && column == 4){ // Make sure the edited column is in the correct sheet, otherwise editing Column D in Sheet3 might trigger this
var row = eventRange.getRow(); // You need to know which row so you can send the email to the correct person
var rowValues = sheet.getRange(row, 1, 1, 4).getValues();
var name = rowValues[0][0];
var sendTo = rowValues[0][1];
var identifier = rowValues[0][2];
var status = rowValues[0][3];
if (status != "") { // Don't send the email if the status is blank
var cc = "test1#example.com, test2#example.com";
var subject = "What is the: " + identifier;
var content = "Hi " + name + "\nWhat is the value " + identifier + " with the status " + status + "?";
MailApp.sendEmail(sendTo, subject, content, {
cc: cc
});
}
}
}
I am trying to set up an instantaneous email notification when a certain value (in this case hydrogen sulphide) exceeds a threshold on google sheets.
An example of the data:
h2s VFA
F1 F2
01/10/17 555 893 786
02/10/17 456 980 654
03/10/17 205 1021 875
04/10/17
05/10/17
06/10/17 345 987
I've got the following working code:
function readCell() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var h2s_value = sheet.getRange("B2").getValues();
if(h2s_value>500) MailApp.sendEmail('emailaddress#gmail.com', 'High
Hydrogen Sulphide Levels', 'Hydrogen Sulphide levels are greater than 500ppm ' + h2s_value );
};
It works when I run the code and an email is sent if the value of B2 exceeds 500. I would like to automate the code to run everytime the value is updated, and so an email is sent instantaneously if the threshold is reached. I tried using onChange triggers, but it's not working.
The problem is that if I put triggers on the main spreadsheet (which records a long list of lots of different parameters), I will get an email notification for every single change made on the spreadsheet- whether it's relevant to the value of interest or not. So I have created another sheet which summarises single key parameters just for that day. The daily key parameters are linked to the main spreadsheet, however when I make a change on the main spreadsheet, the script doesn't recognise the change as the value changes indirectly through the link in the formula.
Does anyone know if there is a way to create a trigger to respond to indirect changes on a spreadsheet? i.e. where the formula remains the same but the value changes.
If it's possible to have instantaneous triggers, this would be much preferred than time driven triggers.
Any help would be much appreciated.
Thanks,
Lisa
--------------------------LATEST VERSION ---------------------------------------
Now I'm trying to work the code with multiple columns including for h2s and VFA (code below only contains code for VFA) , but I haven't been able to define more than one e.value and it only seems to run for the last column. Is it possible to define more than one e.value?
function onEdit (e) {
var ss = e.source;
var range = e.range.columnStart;
var watching_f1 = ss.getRange("Y6:Y");
var watching_f2 = ss.getRange ("Z6:Z");
// Only check the f1 values and send emails if cells in col "Y" changes
if ((watching_f1.getColumn() <= range == range <= watching_f1.getLastColumn())) {
var vfa_f1 = e.values[0];
if(vfa_f1>2500) {
MailApp.sendEmail('email#gmail.com', 'High VFAs in Fermenter 1', 'Hi, High VFAs in Fermenter 1. VFA recorded at ' + vfa_f1);
}};
// Only check the f2 values and send emails if cells in col "Z" changes
if ((watching_f2.getColumn() <= range == range <= watching_f2.getLastColumn())) {
var vfa_f2 = e.values[1];
if(vfa_f2>2500) {
MailApp.sendEmail('email#gmail.com', 'High VFAs in Fermenter 2', 'Hi, High VFAs in Fermenter 2. VFA recorded at ' + vfa_f2);
}};
}
Unless I am misunderstanding the issue, narrowing down the range on which the onEdit() trigger runs the MailApp.sendEmail() would prevent other cell edits on the sheet from sending the extra emails.
function onEdit (e) {
var ss = e.source;
var range = e.range.columnStart;
var watching = ss.getRange("B1:B");
// Only check the values and send emails if cells in col "B" changes
if ((watching.getColumn() <= range == range <= watching.getLastColumn())) {
var h2s_value = e.value; // Use the event object value to save a call to the sheet
if (h2s_value > 500) {
MailApp.sendEmail('emailaddress#gmail.com', 'High Hydrogen Sulphide Levels', 'Hydrogen Sulphide levels are greater than 500ppm ' + h2s_value );
}
}
}
I have a Google Sheet that has form responses. The e-mail address was not required, however it should have been. Either way, I am trying to back-fill the e-mail addresses (in order to make them ready to import as Spiceworks tickets, but I digress). I am going through and typing in usernames, but I want Sheets to auto-fill the domain. I was thinking I could do this by having it detect that the string ended in #, and then just adding the domain to it. Currently I have:
// assumes source data in sheet named Done 14-15
// test column with done is col 9 or I
if(s.getName() == "Done 14-15" && r.getColumn() == 9 && r.getValue() == "?#" ) {
var row = r.getRow();
var value = r.getValue();
r.setValue(value + "example.org");
var numColumns = s.getLastColumn();
s.getRange(row, 1, 1, numColumns).copyTo(target);
}
As you can see, I have a question mark for a wildcard. I have tried using an asterisk or a percentage sign as well, and not gotten anywhere. It will replace if I have literally ?# in there, but I want it to take anything# and append our domain.
RegEx should solve your problem.
Replace the r.getValue() == "?#" with
var regEx = new RegExp('.*#$')
if (regEx.test(r.getValue())) {
// your code
}
Instead of r.getValue() == "?#" you can write r.getValue().endsWith("#")
The email addresses can be easily updated like this:
var newValue = event.value.replace(/#$/,'#example.org');
Where the match is not found, the replacement will not happen... and newValue will equal the original value. Instead of checking for the match before deciding to do something, I'm suggesting doing it then checking the result.
Since you are entering the email addresses by hand, this is a good application of the onEdit() simple trigger and its event object.
function onEdit(event) {
var r = event.range;
var s = r.getSheet();
if (s.getName() == "Done 14-15" && r.getColumn() == 9 && r.getRow() > 1) {
// Replace an # at the end of the string with domain
var newValue = event.value.replace(/#$/,'#example.org');
// If value changed, write it back to spreadsheet
if (event.value !== newValue) {
event.range.setValue(newValue);
}
}
}
If you have rows that have already been edited and need to be checked, this function will take care of them. It uses the technique from How can I test a trigger function in GAS? to create a fake event, then passes it to the onEdit() trigger function.
// Call onEdit for each row in conversion sheet
function convertAllEmails() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName( "Done 14-15" );
var lastRow = sheet.getLastRow();
for (var row=1; row<lastRow; row++) {
var fakeEvent = {
range: sheet.getRange(row,9);
value: range.getValue();
};
onEdit( fakeEvent );
}
}