Get the user that changed specific cell - google-apps-script

I am trying to make spreadsheet with table that could by edited by anyone who is invited. However user can write only into cells that are empty or are filled by him. He cannot overwrite somebody's else work.
I was thinking about saving the editor emails and their cell notations into Properties, however with Session.getActiveUser().getEmail(), SpreadsheetApp.getActive().getActiveRange().getA1Notation() and trigger onEdit I cannot differentiate between two people, if there were adding something at the same time, I would't be able to tell who is doing what... at least I think that is how it works.
Thanks

Session.getActiveUser() is not accesible in the onEdit trigger if you have a normal gmail account (source: https://developers.google.com/apps-script/reference/base/session#getActiveUser())
But I found a very cool workaround :)
The tric lies in the fact that you can not remove yourself and the owner as Editors. So if you remove all editors from a protected range, you are assigning yourself as the editor (and the owner).
In this script, the owner of the spreadsheet can overrule everybody. The others will behave as you wished: they can only edit their own entries and empty fields.
// Test it with colors
// var edittedBackgroundColor = "RED"; // makes the change visible, for test purposes
// var availableBackgroundColor = "LIGHTGREEN"; // makes the change visible, for test purposes
function onEdit(e) {
Logger.log(JSON.stringify(e));
var alphabet = "abcdefghijklmnopqrstuvwxyz".toUpperCase().split("");
var columnStart = e.range.columnStart;
var rowStart = e.range.rowStart;
var columnEnd = e.range.columnEnd;
var rowEnd = e.range.rowEnd;
var startA1Notation = alphabet[columnStart-1] + rowStart;
var endA1Notation = alphabet[columnEnd-1] + rowEnd;
var range = SpreadsheetApp.getActive().getRange(startA1Notation + ":" + endA1Notation);
if(range.getValue() === "") {
Logger.log("Cases in which the entry is empty.");
if(typeof availableBackgroundColor !== 'undefined' && availableBackgroundColor)
range.setBackground(availableBackgroundColor)
removeEmptyProtections();
return;
}
// Session.getActiveUser() is not accesible in the onEdit trigger
// The user's email address is not available in any context that allows a script to run without that user's authorization, like a simple onOpen(e) or onEdit(e) trigger
// Source: https://developers.google.com/apps-script/reference/base/session#getActiveUser()
var protection = range.protect().setDescription('Cell ' + startA1Notation + ' is protected');
if(typeof edittedBackgroundColor !== 'undefined' && edittedBackgroundColor)
range.setBackground(edittedBackgroundColor);
// Though neither the owner of the spreadsheet nor the current user can be removed
// The next line results in only the owner and current user being able to edit
protection.removeEditors(protection.getEditors());
Logger.log("These people can edit now: " + protection.getEditors());
// Doublecheck for empty protections (if for any reason this was missed before)
removeEmptyProtections();
}
function removeEmptyProtections() {
var ss = SpreadsheetApp.getActive();
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
if(! protection.getRange().getValue()) {
Logger.log("Removes protection from empty field " + protection.getRange().getA1Notation());
protection.remove();
}
}
return;
}
function isEmptyObject(obj) {
for(var prop in obj) {
if(obj.hasOwnProperty(prop))
return false;
}
return JSON.stringify(obj) === JSON.stringify({});
}

Related

Getting google spreadsheets to send me an email

I have a spreadsheet on Google spreadsheets that has "D2" through all of "D" (because it will expand as used and when I add more) with a drop down box for priority status. The drop down has "Low, Medium, High". I want to get a script to send me an email when that priority gets edited to "High"
This is the script I did last night at 2AM half asleep.
{
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName("TC52 Bugs or Issues and Improvements.");
var valueToCheck = sheet.getRange(D).getValue();
var rangeEdit = e.range.getA1Notation();
if(rangeEdit == "D")
{
if(valueToCheck > High)
{
MailApp.sendEmail("austin.hendrix#decathlon.com", "High Priority please check now.", "Check spreadsheet" + valueToCheck+ ".");
}
}
}````
This article has a tutorial.
On that note, something along the lines of (Heavily edited by MetaMan)
function sendEmails() {
var sheet = SpreadsheetApp.getActiveSheet();
var dataRange = sheet.getRange(1,4,sh.getLastRow()) or getRange('D1:D'+sh.getLastRow()));
var data = dataRange.getValues();
for (var i in data) {
var row = data[i];
var emailAddress = "youremail#email.com"; // First column
var message = "High priority set with data in A column "+sheet.getRange(("A"+i+1)).getValues()[0]; // Second column
var subject = 'High priority set in D'+i;
if(row[0]=="High"){
MailApp.sendEmail(emailAddress, subject, message);
}
}
}
The function will send you an email when column D is edited to "High".
You will need to fill in 'Your Spreadsheet name' the recipient email address and what ever subject you wish. Also you can edit the body as well. The current body just tells you which row issue the email. Alternately, if you wish to provide more information I'll do it for you.
function onMyEdit(e) {
const sh = e.range.getSheet();
if(sh.getName() == 'Your Sheet Name' && e.range.columnStart == 4 && e.range.rowStart >1 && e.value == 'High') {
GmailApp.sendEmail('recipient','subject',`row ${e.range.rowStart} has been set to high`)
}
}
Since sending an email requires permission you will have to create an installable trigger for this function.
You can do it manually or use a function like the one below which will prevent you from creating more that one trigger.
function createTrigger() {
const ts = ScriptApp.getProjectTriggers().map(t=>t.getHandlerFunction());
if(!~ts.indexOf('onMyEdit')) {
ScriptApp.newTrigger('onMyEdit').forSpreadsheet(SpreadsheetApp.getActive().getId()).onEdit().create();
}
}
ScriptApp
Please Note: that you cannot run onEdit triggered functions from the Script Editor or even from a menu. They require the onEdit trigger which populates the single parameter e with the event object. You can run them from another function as long as it provides the event object.

setbackground not working in google sheets [duplicate]

I have created the following simple function:
function test(r,c) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.getRange(r,c).setBackground("red");
return 1;
}
In the spreadsheet, I write "=test(row(),column())
This results in ERROR with the following message:
Error: You do not have permission to call setBackground (line 3).
It is NO problem if I create another function call in the script as follows:
function test_the_test(){
test(5,4);
}
Why can't I call the test function from the spreadsheet cell?
Thank you in advance
As it is clearly explained in the documentation, Custom functions return values, but they cannot set values outside the cells they are in. In most circumstances, a custom function in cell A1 cannot modify cell A5. That is of course also true for other methods such as setBackground etc.
It's not possible to call anything which sets content from cell, but it is possible to call it from buttons.
Actually is quite easy. Definitely it's not true that you can't change others cell content.
The trick is to not call the function from cell but mount it into drawing/image.
Click insert -> drawing
Draw a image and save it (you should see
your image in spreadsheet)
Click it by right mouse button - in top right corner, there is a little
triangle opening options
Click Assign script and type name of your
script without parenthesis (like "test" not "test()" ) and confirm
Click the button. A pop-up window asking for privileges to access spreadsheet appears.
Confirm it, if problem with refresh occurs just refresh it manually (F5)
Now you can click the button and you can edit any cell you like
This code work fine when mounted to button.
function test() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.getRange(3,5).setBackground("red");
}
The current version of Google Sheets (Jan 2022) uses the Apps Script editor and allows you to put scripts in a container file that automatically attaches to your Google Sheet.
You can open the Apps Script editor from the Google Sheets menu under Extensions -> App Script
In the default Code.gs editor file you can simply extend a default event hook such as onEdit() and put in logic to filter your actions to a specific set of circumstances. Here is a simple example of using this hook:
function onEdit(e) {
if( ! e ){
return;
}
var currentSheet = e.source.getActiveSheet();
var currentRange = e.range;
// only want action to occur when a single cell changes
if( currentRange.getNumRows() === 1 && currentRange.getNumColumns() === 1 ){
var currentColumn = currentRange.getLastColumn();
var currentRow = currentRange.getLastRow();
// only want action to occur for a column on a certain sheet
var myTargetSheet = "Sheet 1";
var myTargetColumn = 1;
if( currentSheet.getName() == myTargetSheet && currentColumn == myTargetColumn ){
// set background color for the selected row based on a lookup
var cellValue = currentRange.getCell(1,1).getValue();
var assignedColor = myCustomSearch( cellValue );
currentSheet.getRange( "A" + currentRow + ":E" + currentRow ).setBackgroundColor( assignedColor );
}
}
}
function myCustomSearch( searchTerm ){
var assignedColor = "#ffffff";
var lookupSheet = SpreadsheetApp.getActive().getSheetByName("Sheet 2");
var lookupRange = lookupSheet.getRange("H1:H20"); // where the search terms live
var numColumns = lookupRange.getNumColumns();
var numRows = lookupRange.getNumRows();
var lookupList = lookupRange.getValues();
for( var myColumn=0; myColumn < numColumns; myColumn++ ){
for( var myRow=0; myRow < numRows; myRow++ ){
if( lookupList[myRow][myColumn] == searchTerm ){
assignedColor = lookupSheet.getRange("H"+(myRow+1)).getBackgroundColor();
break;
}
}
}
return assignedColor;
}

Cell coloring in google-spreadsheet fails if it is called from a cell, but works ok when called from the script.

I have created the following simple function:
function test(r,c) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.getRange(r,c).setBackground("red");
return 1;
}
In the spreadsheet, I write "=test(row(),column())
This results in ERROR with the following message:
Error: You do not have permission to call setBackground (line 3).
It is NO problem if I create another function call in the script as follows:
function test_the_test(){
test(5,4);
}
Why can't I call the test function from the spreadsheet cell?
Thank you in advance
As it is clearly explained in the documentation, Custom functions return values, but they cannot set values outside the cells they are in. In most circumstances, a custom function in cell A1 cannot modify cell A5. That is of course also true for other methods such as setBackground etc.
It's not possible to call anything which sets content from cell, but it is possible to call it from buttons.
Actually is quite easy. Definitely it's not true that you can't change others cell content.
The trick is to not call the function from cell but mount it into drawing/image.
Click insert -> drawing
Draw a image and save it (you should see
your image in spreadsheet)
Click it by right mouse button - in top right corner, there is a little
triangle opening options
Click Assign script and type name of your
script without parenthesis (like "test" not "test()" ) and confirm
Click the button. A pop-up window asking for privileges to access spreadsheet appears.
Confirm it, if problem with refresh occurs just refresh it manually (F5)
Now you can click the button and you can edit any cell you like
This code work fine when mounted to button.
function test() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.getRange(3,5).setBackground("red");
}
The current version of Google Sheets (Jan 2022) uses the Apps Script editor and allows you to put scripts in a container file that automatically attaches to your Google Sheet.
You can open the Apps Script editor from the Google Sheets menu under Extensions -> App Script
In the default Code.gs editor file you can simply extend a default event hook such as onEdit() and put in logic to filter your actions to a specific set of circumstances. Here is a simple example of using this hook:
function onEdit(e) {
if( ! e ){
return;
}
var currentSheet = e.source.getActiveSheet();
var currentRange = e.range;
// only want action to occur when a single cell changes
if( currentRange.getNumRows() === 1 && currentRange.getNumColumns() === 1 ){
var currentColumn = currentRange.getLastColumn();
var currentRow = currentRange.getLastRow();
// only want action to occur for a column on a certain sheet
var myTargetSheet = "Sheet 1";
var myTargetColumn = 1;
if( currentSheet.getName() == myTargetSheet && currentColumn == myTargetColumn ){
// set background color for the selected row based on a lookup
var cellValue = currentRange.getCell(1,1).getValue();
var assignedColor = myCustomSearch( cellValue );
currentSheet.getRange( "A" + currentRow + ":E" + currentRow ).setBackgroundColor( assignedColor );
}
}
}
function myCustomSearch( searchTerm ){
var assignedColor = "#ffffff";
var lookupSheet = SpreadsheetApp.getActive().getSheetByName("Sheet 2");
var lookupRange = lookupSheet.getRange("H1:H20"); // where the search terms live
var numColumns = lookupRange.getNumColumns();
var numRows = lookupRange.getNumRows();
var lookupList = lookupRange.getValues();
for( var myColumn=0; myColumn < numColumns; myColumn++ ){
for( var myRow=0; myRow < numRows; myRow++ ){
if( lookupList[myRow][myColumn] == searchTerm ){
assignedColor = lookupSheet.getRange("H"+(myRow+1)).getBackgroundColor();
break;
}
}
}
return assignedColor;
}

How to allow shared users in google sheets to run script that accesses protected sheet [duplicate]

This question already has an answer here:
How to allow onEdit function to affect protected cell in a Google Sheet?
(1 answer)
Closed 11 months ago.
I have a google spreadsheet in which I plan to share with over 50 users who will each have their own sheet. For security measures, I have some script that I would like to run which would allow a user to enter data into their sheet but prevent them from deleting that entry after. The code works fine on my end, but when I tried to test it out by sharing it to one of the users, the script either didn't run or is not allowed to run.
I have done my research on this matter for a while now and cannot seem to apply any of the solutions that I have seen posted on this forum and many others. I am using an onEdit() function which, to the best of my knowledge, is a simple trigger so it shouldn't cause this kind of error. The code is as follows:
function onEdit(event) {
var masterSheetName = "Blank" // sheet where the cells are protected from updates
var helperSheetName = "Blank Copy" // sheet where the values are copied for later checking
// range where edits are "write once": D18:Y157, i.e., rows 18-157 and columns 4-25
var firstDataRow = 18; // only take into account edits on or below this row
var lastDataRow = 157; // only take into account edits on or above this row
var firstDataColumn = 4; // only take into account edits on or to the right of this column
var lastDataColumn = 25; // only take into account edits on or to the left of this column
var miscFirstDataColumnOne = 15; // only take into account edits on or to the right of this column
var miscLastDataColumnOne = 15; // only take into account edits on or to the left of this column
var miscFirstDataColumnTwo = 25; // only take into account edits on or to the right of this column
var miscLastDataColumnTwo = 25; // only take into account edits on or to the right of this column
var miscFirstDataRowTwo = 18;
var miscLastDataRowTwo = 157;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var masterSheet = ss.getActiveSheet();
var masterSheetMiscOne = ss.getActiveSheet();
if (masterSheet.getName() != masterSheetName) return;
if (masterSheetMiscOne.getName() != masterSheetName) return;
var masterCell = masterSheet.getActiveCell();
var masterCellMiscOne = masterSheetMiscOne.getActiveCell();
if (masterCell.getRow() < firstDataRow || masterCell.getColumn() < firstDataColumn ||
masterCell.getRow() > lastDataRow || masterCell.getColumn() > lastDataColumn) return;
var helperSheet = ss.getSheetByName(helperSheetName);
var helperCell = helperSheet.getRange(masterCell.getA1Notation());
var newValue = masterCell.getValue();
var oldValue = helperCell.getValue();
var user = SpreadsheetApp.getActive().getEditors()[1];
var permission = helperSheet.getSheetProtection();
permission.addUser(user);
helperSheet.setSheetProtection(permission);
SpreadsheetApp.flush();
if (oldValue == "") {
helperCell.setValue(newValue);
} else {
masterCell.setValue(oldValue);
Browser.msgBox('You can not delete this value');
}
if ((masterCellMiscOne.getRow() < firstDataRow || masterCellMiscOne.getColumn() < miscFirstDataColumnOne || masterCellMiscOne.getRow() > lastDataRow ||
masterCellMiscOne.getColumn() > miscLastDataColumnOne) & (masterCellMiscOne.getRow() < firstDataRow || masterCellMiscOne.getColumn() < miscLastDataColumnOne ||
masterCellMiscOne.getRow() > lastDataRow || masterCellMiscOne.getColumn() > miscLastDataColumnTwo)) return;
var miscCellValueOne = masterCellMiscOne.getValue();
if (miscCellValueOne !== "") {
Browser.msgBox('Submission Needs To Be Authorized Before Being Added.');
}
SpreadsheetApp.flush();
permission.removeUser(user)
helperSheet.setSheetProtection(permission)
}
}
Each user has a sheet which will also have a copy, (in this code the users sheet is "Blank" and the copy is "Blank Copy"). The blank copy will be protected so they can not edit this because it will allow them to delete the data in their sheet (Blank). This code does work, but I just need for it to work when I share the spreadsheet.
All help is greatly appreciated and here is a link to a copy of the spreadsheet.
https://docs.google.com/spreadsheet/ccc?key=0AhBLjhwt88kUdFZ2cG9CVFNEQy1zVHdJYlp6ZEx5Unc&usp=sharing
Short answer: Create a simple add-on with minimal code refactoring
Explanation
... but when I tried to test it out by sharing it to one of the users, the script either didn't run or is not allowed to run.
Unfortunately this would be the case as the "trigger" that you're currently using (i.e. the onEdit one) -
... cannot access services that require authorization.
Please refer to Simple Triggers > Restrictions.
Alternate solution
You can introduce an onInstall function and publish this code as an add-on with visibility set to Private if folks from only your domain are going to use the sheet/script.
Hope this helps.

Google Spreadsheet function gets data from all sheets instead of just one

This is the function I have written, which sends an email to notify someone whenever a specified cell/row/column is edited, and I have it set to trigger onEdit. It works as is.
/* This function send an email when a specified range is edited
* The spreadsheets triggers must be set to onEdit for the function
*/
function sendNotification() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
//Get Active cell
var mycell = ss.getActiveSelection();
var cellcol = mycell.getColumn();
var cellrow = mycell.getRow();
//Define Notification Details
var recipients = "me#email.com";
var subject = "Update to "+ss.getName();
var body = ss.getName() + "has been updated. Visit " + ss.getUrl() + " to view the changes.";
//Check to see if column is A or B to trigger
if (cellcol == 1, 2)
{
//check for row to trigger
if (cellrow == 1)
{
//Send the Email
MailApp.sendEmail(recipients, subject, body);
}
//End sendNotification
}
}
My issue is that I need this to work only when a column or row on a certain sheet(page) of the spreadsheet is edited, rather than column X in any of the sheets within the spreadsheet.
Any ideas? I'm hoping it's something simple that I missed, but I have been unable to find a solution.
You could interrupt the function after assigning the sheet variable:
if (sheet.getSheetName() != 'SheetIWant') return;
You need to look at the parameters that onEdit provides. It will tell you where the change happened.