I have added the button into spreadsheet and assigned the script to it. Is there a way to determine the user's email of person that clicked it? Script edits the data so probably onEdit trigger should work, however function with Session.getActiveUser().getEmail() set by this trigger doesn't recognize the user.
Thank you
It's possible with normal gmail accounts, with this workaround!
I'm using some protection functionality that reveals the user and owner of the document and I'm storing it in the properties for better performance. Have fun with it!
function onEdit(e) {
SpreadsheetApp.getUi().alert("User Email is " + getUserEmail());
}
function getUserEmail() {
var userEmail = PropertiesService.getUserProperties().getProperty("userEmail");
if(!userEmail) {
var protection = SpreadsheetApp.getActive().getRange("A1").protect();
// tric: the owner and user can not be removed
protection.removeEditors(protection.getEditors());
var editors = protection.getEditors();
if(editors.length === 2) {
var owner = SpreadsheetApp.getActive().getOwner();
editors.splice(editors.indexOf(owner),1); // remove owner, take the user
}
userEmail = editors[0];
protection.remove();
// saving for better performance next run
PropertiesService.getUserProperties().setProperty("userEmail",userEmail);
}
return userEmail;
}
Related
I've created a Google Sheet with an Apps Script to do issue and task tracking. One of the features I'm trying to implement is permissions based assigning of tasks. As a precursor to that, I have a hidden sheet populated with a list of users and their file permissions, using code similar to that in this StackOverflow question
When I manually run the code, it works fine. However, I want it to load every time the sheet is opened in case of new people entering the group or people leaving the group. So I made a call to my function in my onOpen simple trigger. However, when it is called via onOpen, I get the following:
GoogleJsonResponseException: API call to drive.permissions.list failed with error: Login Required
at getPermissionsList(MetaProject:382:33)
at onOpen(MetaProject:44:3)
Here are my functions:
My onOpen Simple Trigger:
function onOpen() {
//Constant Definitions for this function
const name_Access = 'ACCESS';
const row_header = 1;
const col_user = 1;
const col_level = 2;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sht_Access = ss.getSheetByName(name_Access);
var ui = SpreadsheetApp.getUi();
ui.createMenu('MetaProject')
.addSubMenu(
ui.createMenu('View')
.addItem('Restore Default Sheet View', 'restoreDefaultView')
.addItem('Show All Sheets', 'showAllSheets')
)
.addSeparator()
.addToUi();
//Clear Contents, Leave Formatting
sht_Access.clearContents();
//Set Column Headers
var head_name = sht_Access.getRange(row_header,col_user);
var head_level = sht_Access.getRange(row_header,col_level);
head_name.setValue('User');
head_level.setValue('Level');
//Refresh User List for use in this instance
getPermissionsList();
}
Here is getPermissionsList:
function getPermissionsList() {
const fileId = "<REDACTED>"; // ID of your shared drive
const name_Sheet = 'ACCESS';
const row_start = 2;
const col_user = 1;
const col_access = 2;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var thissheet = ss.getSheetByName(name_Sheet);
// THIS IS IMPORTANT! The default value is false, so the call won't
// work with shared drives unless you change this via optional arguments
const args = {
supportsAllDrives: true
};
// Use advanced service to get the permissions list for the shared drive
let pList = Drive.Permissions.list(fileId, args);
//Put email and role in an array
let editors = pList.items;
for (var i = 0; i < editors.length; i++) {
let email = editors[i].emailAddress;
let role = editors[i].role;
//Populate columns with users and access levels / role
thissheet.getRange(row_start + i,col_user).setValue(email);
thissheet.getRange(row_start + i,col_access).setValue(role);
}
I searched before I posted and I found the answer via some related questions in the comments, but I didn't see one with an actual answer. The answer is, you cannot call for Permissions or anything requiring authorization from a Simple Trigger. You have to do an Installable trigger.
In order to do that do the following:
Create your function or rename it something other than "onOpen".
NOTE: If you name it onOpen (as I originally did and posted in this answer) it WILL work, but you will actually run TWO triggers - both the installable trigger AND the simple trigger. The simple trigger will generate an error log, so a different name is recommended.
Click the clock (Triggers) icon on the left hand side in Apps Script.
Click "+ Add Trigger" in the lower right
Select the name of your function for "which function to run"
Select "onOpen" for "select event type"
Click "Save".
The exact same code I have above now runs fine.
I’m a newbie with everything related to Google Apps Scripts and I’m trying to implement a password with a Prompt that shows every time the document is opened, so every person that tries to access the Google Sheets has to type a password, otherwise they won’t be able to edit it. It kind of works, the thing is that if I (the owner of the document) haven’t unlocked it myself, the prompt won’t show to the other editors. Is this the correct way to do so?
function onOpen() {
protectSheet();
password();
}
function protectSheet() {
var sheet = SpreadsheetApp.getActiveSheet();
//Protect the sheet
var protection = sheet.protect().setDescription("Only owner can edit");
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
Logger.log(protection.getEditors());
Logger.log(Session.getEffectiveUser());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
function unprotectSheet() {
var sheet = SpreadsheetApp.getActiveSheet();
//Unprotect sheet
var unprotection = sheet.getProtections(SpreadsheetApp.ProtectionType.SHEET)[0];
if (unprotection && unprotection.canEdit()) {
unprotection.remove();
Logger.log("Entró: "+unprotection.getEditors());
}
}
function password() {
var password = "123";
var textoIngresado;
do {
var passwordPrompt = SpreadsheetApp.getUi().prompt("Protected document", "Type password", SpreadsheetApp.getUi().ButtonSet.OK_CANCEL);
if (passwordPrompt.getSelectedButton() == SpreadsheetApp.getUi().Button.CANCEL) {
protectSheet();
console.log("CANCELLED, PROTECTED");
break;
} else if (passwordPrompt.getSelectedButton == SpreadsheetApp.getUi().Button.CLOSE) {
protectSheet();
console.log("CLOSED, PROTECTED");
break;
}
textoIngresado = passwordPrompt.getResponseText();
}
while (textoIngresado != password);
if (textoIngresado == password) {
SpreadsheetApp.getActiveSheet().getRange("A7").setValue("Unlocked");
unprotectSheet();
console.log("UNLOCKED");
}
}
The code is using a onOpen simple trigger. It looks that you are missing that simple triggers have limitations (for details see https://developers.google.com/apps-script/guides/triggers)
Instead of a simple trigger, use an installable trigger (for details see https://developers.google.com/apps-script/guides/triggers/installable)
Regarding using a custom password it looks that you are trying "to discover the hot water". Instead of creating a custom access control system the best is to use the Google Drive sharing system:
change the sharing settings i.e. change from editor to viewer.
instead of changing the settings by using individual emails you might use a Google Group
Related
Google Sheets Appscript pop up prompt response and hide sheets until response is correct
I hope you can help, I have tried to find a solution but I am unable to find one that resolves my issue.
I have a form that collects information if a student request a change to their course, we are a multi school site and depending on the site, a different member of staff is required to approve the change.
Part of the information collect is the site code OAAd for example, and this information is held in column F.
If a submission is received the 'On form submit' trigger, triggers the script. Currently it doesn't seem to work, yet it triggers without error. If I change the trigger 'On edit' and edit a cell in column F it works fine.
eventually I will use ifElse to add in the additional options for each site, but if I could get it to work for one site that would be great.
function sendEmailapproval(e) {
if(e.range.getColumn()==6 && e.value=='OAAd'){
var emailRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Email Addresses").getRange("B3");
var recipent = emailRange.getValue();
var html = HtmlService.createTemplateFromFile("Email1.html");
var htmlText = html.evaluate().getContent();
var subject = "New Change Log Request Post 16";
var body = "";
var options = { htmlBody: htmlText}
MailApp.sendEmail(recipent, subject, body, options)
}
}
Background
There are 2 options to add an onFormSubmit trigger:
Using the form settings / script.
Using the attached spreadsheet script.
Both methods gives an event object to communicate with new submmited responese, but threre are differaces between those objet so you need to dicide in advance which method to choose.
You can read more about this topic here.
For your propose, would preder to use the spreadsheet script, since it easier to extract specific values from it.
Solution
First, Here is your correct sendEmailapproval function:
function sendEmailapproval(e) {
const namedValues = e.namedValues
const question = 'Question 3'; // copy-paste from the form
switch (namedValues[question][0])
{
case 'OAAd':
const emailRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Email Addresses").getRange("B3");
const recipent = emailRange.getValue();
const html = HtmlService.createTemplateFromFile("Email1.html");
const htmlText = html.evaluate().getContent();
const subject = "New Change Log Request Post 16";
const body = "";
const options = { htmlBody: htmlText}
MailApp.sendEmail(recipent, subject, body, options)
break;
// case 'SOMETHING_ELSE':
// break;
default:
}
}
Note that e.namedValues is an object, where each key mapped to an array.
So namedValues[question][0] refers to the string value of namedValues[question].
Second, add a trigger to the spreadsheet that attached to the form.
function createTrigger() {
const sheet = SpreadsheetApp.openById("YOUR_SPREADSHEET_ID").getSheetByName("THE_SHEET_NAME");
ScriptApp.newTrigger("sendEmailapproval")
.forSpreadsheet(sheet)
.onFormSubmit()
.create();
}
Gory title but I couldn't find a way of being clearer.
I have no experience with coding and I was wondering if doing something like what I'm about to explain would be possible.
This is my example sheet:
What I'm looking to do is to have automated emails sent out to the person assigned to the task if the task status is set to urgent, while referencing people by names and having an auxiliary sheet with all the names and corresponding emails.
I've browsed around and found some similar questions which I unfortunately had no success in adapting. The one thing I got is that I need to setup an onEdit trigger, which I've done, but I'm completely clueless from here on out.
Can someone point me in the right direction? I don't have a clue where to start.
Looking forward to hearing your advice.
Thanks and stay safe in these crazy times!
It was a funny exercise. I tried to make the script as clean and reusable as possible for others to be able to adapt it to their needs.
Usage
Open spreadsheet you want to add script to.
Open Script Editor: Tools / Script editor.
Add the code. It can be configured by adjusting variables in the top:
var trackerSheetName = 'Tracker 1'
var trackerSheetStatusColumnIndex = 2
var trackerSheetNameColumnIndex = 4
var triggeringStatusValue = 'Urgent'
var peopleSheetName = 'AUX'
var peopleSheetNameColumnIndex = 1
var peopleSheetEmailColumnIndex = 2
var emailSubject = 'We need your attention'
var emailBody = 'It is urgent'
function checkStatusUpdate(e) {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
var activeSheet = spreadsheet.getActiveSheet()
// skip if different sheet edited
if (activeSheet.getName() !== trackerSheetName) {
return
}
var editedRange = e.range
// skip if not a single cell edit
if (editedRange.columnStart !== editedRange.columnEnd || editedRange.rowStart !== editedRange.rowEnd) {
return
}
// skip if edited cell is not from Status column
if (editedRange.columnStart !== trackerSheetStatusColumnIndex) {
return
}
// skip if Status changed to something other than we're looking for
if (e.value !== triggeringStatusValue) {
return
}
var assigneeName = activeSheet.getRange(editedRange.rowStart, trackerSheetNameColumnIndex, 1, 1).getValue()
var peopleSheet = spreadsheet.getSheetByName(peopleSheetName)
var people = peopleSheet.getRange(2, 1, peopleSheet.getMaxRows(), peopleSheet.getMaxColumns()).getValues()
// filter out empty rows
people.filter(function (person) {
return person[peopleSheetNameColumnIndex - 1] && person[peopleSheetEmailColumnIndex - 1]
}).forEach(function (person) {
if (person[peopleSheetNameColumnIndex - 1] === assigneeName) {
var email = person[peopleSheetEmailColumnIndex - 1]
MailApp.sendEmail(email, emailSubject, emailBody)
}
})
}
Save the code in editor.
Open Installable Triggers page: Edit / Current project's triggers.
Create a new trigger. Set Event Type to On edit. Keep other options default.
Save the Trigger and confirm granting the script permissions to access spreadsheets and send email on your behalf.
Go back to your spreadsheet and try changing status in Tracker 1 tab for any of the rows. Corresponding recipient should receive an email shortly.
This should get you started:
You will need to create an installable trigger for onMyEdit function. The dialog will help you to design you email by giving you an html format to display it. When you're ready just comment out the dialog and remove the // from in front of the GmailApp.sendEdmail() line.
function onMyEdit(e) {
//e.source.toast('Entry');
const sh=e.range.getSheet();
if(sh.getName()=="Tracker") {
if(e.range.columnStart==2 && e.value=='Urgent') {
//e.source.toast('flag1');
const title=e.range.offset(0,-1).getValue();
const desc=e.range.offset(0,1).getValue();
const comm=e.range.offset(0,3).getValue();
if(title && desc) {
var html=Utilities.formatString('<br />Task Title:%s<br />Desc:%s<br />Comments:%s',title,desc,comm?comm:"No Additional Comments");
//GmailApp.sendEmail(e.range.offset(0,2).getValue(), "Urgent Message from Tracker", '',{htmlBody:html});
SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutput(html).setWidth(600), 'Tracker Message');
e.source.toast('Email Sent');
}else{
e.source.toast('Missing Inputs');
}
}
}
}
GmailApp.sendEmail()
I'm trying to identify current user's name to make notes of who edited what like this:
r.setComment("Edit at " + (new Date()) + " by " + Session.getActiveUser().getEmail());
but it won't work - user's name is an empty string.
Where did I go wrong?
GOOD NEWS: It's possible with this workaround!
I'm using some protection functionality that reveals the user and owner of the document and I'm storing it in the properties for better performance. Have fun with it!
function onEdit(e) {
SpreadsheetApp.getUi().alert("User Email is " + getUserEmail());
}
function getUserEmail() {
var userEmail = PropertiesService.getUserProperties().getProperty("userEmail");
if(!userEmail) {
var protection = SpreadsheetApp.getActive().getRange("A1").protect();
// tric: the owner and user can not be removed
protection.removeEditors(protection.getEditors());
var editors = protection.getEditors();
if(editors.length === 2) {
var owner = SpreadsheetApp.getActive().getOwner();
editors.splice(editors.indexOf(owner),1); // remove owner, take the user
}
userEmail = editors[0];
protection.remove();
// saving for better performance next run
PropertiesService.getUserProperties().setProperty("userEmail",userEmail);
}
return userEmail;
}
I suppose you have this piece of code set to execute inside an onEdit function (or an on edit trigger).
If you are on a consumer account, Session.getActiveUser().getEmail() will return blank. It will return the email address only when both the author of the script and the user are on the same Google Apps domain.
I had trouble with Wim den Herder's solution when I used scripts running from triggers. Any non script owner was unable to edit a protected cell. It worked fine if the script was run from a button. However I needed scripts to run periodically so this was my solution:
When a user uses the sheet the first time he/she should click a button and run this:
function identifyUser(){
var input = Browser.inputBox('Enter User Id which will be used to save user to events (run once)');
PropertiesService.getUserProperties().setProperty("ID", input);
}
This saves the user's input to a user property. It can be read back later at any time with this code:
var user = PropertiesService.getUserProperties().getProperty("ID");
In this code you can use a cell for input. Authorising scripts are not required.
function onEdit(e){
checkUsername(e);
}
function checkUsername(e){
var sheet = e.source.getActiveSheet();
var sheetToCheck = 'Your profile';
var sheetName = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetToCheck);
var CellInputUsername = 'B4';
var ActiveCell = SpreadsheetApp.getActive().getActiveRange().getA1Notation();
if (sheet.getName() !== sheetToCheck || ActiveCell !== CellInputUsername){return;}
var cellInput = sheetName.getRange(CellInputUsername).getValue();
PropertiesService.getUserProperties().setProperty("Name", cellInput);
// Make cell empty again for new user
sheetName.getRange(CellInputUsername).setValue("");
var Username = PropertiesService.getUserProperties().getProperty("Name");
SpreadsheetApp.getUi().alert("Hello " + Username);
}