Google Sheets: highlight certain users' edits - google-sheets

I want to conditionally format a Google Sheet for specific users. Basically, Teacher#1's edits would appear yellow while Teacher#2's are green. Unfortunately the "conditional formatting" tool in Sheets doesn't have this option. Protecting certain cells doesn't work because of the regularity of changes in editors.. I simply want to see editors, not restrict them. Another suggestion should be to use the "Revision History" option.. but this doesn't seem practical for finding a certain editor's changes over several months using several sheets (that several editors have access to).
So the direction I was thinking of going was having a function with an OnEdit trigger that would grab the email (all users must login) and then highlight the last edit.
Here is the URL for a public Google Sheet I duplicated. There, you'll see one of many Weeks sheets. Then there is a control panel with colors that would be associated with each user (teacher).
I copied some script from another StackOverflow forum to get started.
function setActiveUser() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
// GET EMAIL ADDRESS OF ACTIVE USER
var email = Session.getActiveUser().getEmail();
Logger.log(email);
//HIGHLIGHT LAST EDITED CELL BASED ON OF ACTIVE USER'S PREDETERMINED COLOR
sheet.getRange(here is where I need to locate last edited cell).setBackgroundColor("here is where I need to color it based on the "Control Panel" sheet colors");
Hopefully someday Google will allow this kind of Conditional Formatting, but in the meantime it will help my school in Latin America a ton.
Thank you for considering!

The first line of your code should be:
function onEdit() {
And the last lines should be something like this: (edit)
//HIGHLIGHT LAST EDITED CELL BASED ON OF ACTIVE USER'S PREDETERMINED COLOR
var teacherName = ss.getRangeByName("TeacherName").getValues().map(function(array) { return array[0]; });
var teacherColor = ss.getRangeByName("TeacherColor").getValues().map(function(array) { return array[0]; });
var nameIdx = teacherName.indexOf(email);
if(nameIdx > -1)
sheet.getActiveCell().setBackground(teacherColor[nameIdx]);
To get it working, you should first run the code in the Script Editor and authorize it. And TeacherName and TeacherColor should be set to named ranges beforehand. Test sheet is here.

Related

Can you have a Google form pull data from a Google sheet each time a respondent opens it?

Hi and thanks for reading. I'm trying to make a system where responders can enter the names of participants in one form. In another form, for entering scores of a competition, responders can select the name in a dropdown menu. I'm not a programmer, but I got quite far by Googling and 'stealing' code. What I have now, is a code in the first form (for adding names). When you submit it, the form adds the name to a Google sheet. Then the code adds the names in the Google sheet to the form for entering scores. So far so good!
But now the problem: there is more than one competition. What I could do, is change the code so that is also adds the names in the Google sheet to the form of the other competition, but that's a lot of work everytime there is a new competition. It would be easier to have a code in the competitionform itself that tells the form: every time someone opens you (as in: a responder, not an editor), run this code. Is that possible? I now use the 'on submit' trigger for the other form, but I actually need an 'on entry' trigger or something like that. I tried 'on open' but that only works when the editor opens the form.
Here is the code I have in the 'add name' form (the trigger is located somewhere else)
function addRider(){
// call your form and connect to the drop-down item
var form = FormApp.openById("1NeKicU9d3yqQX1D2ZliE-t87k3OVW8lqn9avhNs54NM");
var riderList = form.getItemById("10469776").asListItem();
// identify the sheet where the data resides needed to populate the drop-down
var rider = SpreadsheetApp.openById("1kLBqtU4ebR3tElrEIZNklmxr0hu5IwE6_UV7AN9T1wE").getSheetByName("Riders");
// grab the values in the first column of the sheet - use 2 to skip header row
var riderValues = rider.getRange(2, 3, rider.getMaxRows() - 1).getValues();
var riderNames = [];
// convert the array ignoring empty cells
for(var i = 0; i < riderValues.length; i++)
if(riderValues[i][0] != "")
riderNames[i] = riderValues[i][0];
// populate the drop-down with the array data
riderList.setChoiceValues(riderNames);
}

How can I highlight a cell when it it is changed from a referenced cell?

I have a Google sheet that is a bit complex.
The first tab is a Summary tab that uses vlookup to collect data from other tabs in the worksheet.
The other tabs are created by the IMPORTRANGE function.
The sources for the IMPORTRANGE tabs are edited and maintained by others.
When someone edits their sheet the IMPORTRANGE sheets get updated automatically and the Summary Tab gets update accordingly.
I need to provide a weekly summary of the changes to the data on the first (Summary) tab. I want to automatically highlights the cells on the Summary Tab when someone updates their private sheet.
I have a script that runs onEdit which updates changes when I update a cell directly on the Summary Tab but when the data is changed on the private sheet, which updates the Summary Tab the changed cell is not highlighted.
Any ideas how to accomplish the highlighting when the Summary Tab data is changed when a referenced cell is changed?
More- I have an installable trigger and a short script which should be run when any change is made:
function createSpreadsheetOpenTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger('onEdit2')
.forSpreadsheet(ss)
.onChange()
.create();
}
function onEdit2(e)
{
var range=e.range;
var column=range.getColumn();
if(column>1 && column<27)
{
range.setBackground('#ffff00');
}
}
The execution fails and provide this error, "TypeError: Cannot read property 'getColumn' of undefinedat onEdit2(Code:12:20)"
Thanks in advance.
I believe you can do this by using an installable onChange(e) trigger instead of an onEdit, since onEdit will not fire when changes are made via script execution.
See this answer: https://webapps.stackexchange.com/a/119702/237316
UPDATE:
The trigger restrictions say that neither onEdit nor onChange are executed when the edit to the bound document is one via script or API. However there is a workaround with the sheets API that would trigger the triggers. If you use ValueInputOption = USER_ENTERED with Sheets API via Apps Script. See https://developers.google.com/sheets/api/reference/rest/v4/ValueInputOption?hl=en

Allow "Create New Filter View" feature on Google Sheet with some columns protected

I am working on a large Google Sheet that has many editors from multiple companies, so I have had to give permissions on each tab on a per column basis. The issue is, because most editors only have editor access to 10-20% of the columns, the "Create New Filter View" feature is not working for those users. I am finding Filter View access to be very finicky in general. Sometimes it works, sometimes it doesn't on a sheet with similar protections. Other times "Create New Filter View" is not an available option, but if you select an existing filter view and click "Duplicate" you can create one. Seems like there are a few weird bugs with Google Sheets and permissions that I can't pin down.
Any ideas on how to use Apps Script to unlock the protections on the "Create New Filter View" option? This could be for all editors or all viewers, either would work in this instance. Thanks!
Issue:
While a FilterView can be created in a script if the Advanced Sheets Service is enabled, the same issue you're experiencing while creating it in the UI would show up here: a user cannot create a FilterView if one of the columns is protected from this user.
Naturally, this same user cannot run a script to unprotect the corresponding columns either - what worth would a protection be if the users to which it applies could remove it!
Possible workarounds:
A way around this would be to have a script that runs under the authority -see getEffectiveUser()- of a user who can edit those protected columns. In most situations, this is the user that is triggering the script -see getActiveUser()-, but in certain situations, like an installable trigger or a web app that executes as the user who deployed it, that is not the case.
For example, you could install an onEdit trigger with a user who has access to all the columns in the desired FilterView.
Then, this would fire whenever any user edits the spreadsheet, but it would fire under the authority of the user who installed the trigger (and can access the protected columns), so the FilterView could be created.
In order to create the FilterView only for certain edits (for example, when a specific cell is edited and there is a specific edited value), you could check these conditions at the beggining of the onEdit function. And if you needed to pass more information for customization of the filter view (for example, which columns and rows, which sheet, etc.), you could put that information in other cells and retrieve the corresponding values via getValue()/getValues().
For example, It could be something like this:
function fireOnEdit(e) {
if (e.range.getA1Notation() === "E1" && e.value === "Create FilterView") {
const ss = e.source;
const spreadsheetId = ss.getId();
const sheet = ss.getSheets()[0];
const resource = {
requests: {
addFilterView: {
filter: {
range: {
sheetId: sheet.getSheetId(),
startRowIndex: 0,
endRowIndex: 4,
startColumnIndex: 0,
endColumnIndex: 3
}
}
}
}
}
Sheets.Spreadsheets.batchUpdate(resource, spreadsheetId);
}
}

How to extract URL from link (link feature, not HYPERLINK formula) in Google Sheets?

Google Sheets allows to specify (hyper)links in two ways:
By using HYPERLINK formula/function, e.g. =HYPERLINK("http://example.com/", "Example.com")
By using "linking" feature – Insert » Insert Link
There are a lot of solutions around the web, and StackOverflow, for extracting URL from the first option - the HYPERLINK formula, but I haven't found any way how to extract it from the second option.
Example Sheet
How to extract with Apps Script a link URL inserted with Insert » Insert Link
Apps Script has a class https://developers.google.com/apps-script/reference/spreadsheet/rich-text-value that allows you to retrieve not only the plain text contained in a cell, but also its properties, such as the link URL.
To access this property use the method getRichTextValue() combined with getLinkUrl()
Sample to retrieve the link URL in a custom function:
function getLink(range){
var link = SpreadsheetApp.getActive().getActiveSheet().getRange(range).getRichTextValue().getLinkUrl();
return link;
}
After writing and saving this code, you simply need to call it from any cell, giving it the reference of the cell with the link URL as parameter.
Important:
Since it is the reference and not the value of the cell that should be passed to the function, you need to put the A1 notation in quotes.
Sample:
=getLink("A1")
We can improve ziganotschka's solution to avoid the need for quotation marks.
Enter this in B1 (assuming text with the embedded link is in A1):
=getLink(ADDRESS(row(),column(A1)))
This way it can be dragged down to quickly get data for an entire column.
If want to use like normal formula without quotation marks,
function GetURL(input) {
var formula = SpreadsheetApp.getActiveRange().getFormula();
var rx = /=geturl\((.*)\)/gi;
var rng_txt = rx.exec(formula)[1]
var rng = SpreadsheetApp.getActiveSheet().getRange(rng_txt);
return rng.getRichTextValue().getLinkUrl()
}

Prevent users from creating new sheets in a shared google spreadsheet

I have created a spreadsheet with all sheets protected from editing except for a single cell, where the user is supposed to enter a search value, which filters the sheet. I shared the spreadsheet with enabled editing to allow for that, but that also enables users to create new sheets, which I'd like to prevent.
Perhaps I'm using not the most optimal way of achieving what I want, so suggestions are welcome. I saw people advising to use forms in similar use cases, but I don't see how to apply their survey capabilities to my needs.
EDIt: here is the shared spreadsheet: https://docs.google.com/spreadsheets/d/1C4mrBWJxPLrFQ4bp82UA2ICOr1e6ER47wF7YuElyoZg/edit?usp=sharing
You can use a script to delete every newly created (not by the owner) sheet. Here is an example of such a script :
// Deletes any tab named "SheetN" where N is a number
function DeleteNewSheets() {
var newSheetName = /^Sheet[\d]+$/
var ssdoc = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ssdoc.getSheets();
// is the change made by the owner ?
if (Session.getActiveUser().getEmail() == ssdoc.getOwner().getEmail()) {
return;
}
// if not the owner, delete all unauthorised sheets
for (var i = 0; i < sheets.length; i++) {
if (newSheetName.test(sheets[i].getName())) {
ssdoc.deleteSheet(sheets[i])
}
}
}
Then set a trigger that runs this function DeleteNewSheets when a change (like sheet creation) happens.
Note : If english is not the document language you should change the regular expression /^Sheet[\d]+$/, for example for french it should be /^Feuille [\d]+$/.
Without seeing your existing spreadsheet, Seedmanc, Google's "Filter views" option under the "Data" menu of your Sheet could be helpful, because it lets owners/editors create and save filters that are accessible to view-only users too.
What's more, view-only users can create filter views unique to them. Once they enter a filter range, menu arrows appear...
...allowing them to filter either by condition (including custom formulas!) or by values. So, depending on how elaborate your filter is and how spreadsheet-savvy your users are, filter views can provide a more flexible filter while still protecting your sheets.
(Read more about Google Sheets filter views here.)

Resources