I have a problem with the class Session in Google Apps Scripts
I wrote a script that records the date and user who edits a sheet in a spreadsheet. It works good with my test page, but when I put it in another spreadsheet (a copy named like "test") it does not work!! I have 8 Spreadsheets with the same number of sheets, all named similar, and the script only works in one of the spreadsheets (and test). This is the script:
function onEdit(event)
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
//Last Session record
var actSht = event.source.getSheetByName("somepage")
var actRng = event.source.getActiveRange();
var lastCell0 = actSht.getRange(2,16);
var lastCell1 = actSht.getRange(2,17);
var date = Utilities.formatDate(new Date(), "GMT-6", "dd/MM/yyyy HH:mm");
var r= actSht.getActiveCell().getRow();
lastCell0.setValue("Date_Mod");
lastCell1.setValue("user");
if(r=='3'){
actSht.getRange(r,17).setValue(Session.getEffectiveUser());
actSht.getRange(r,16).setValue(date);
}
if(r=='4'){
actSht.getRange(r,17).setValue(Session.getEffectiveUser());
actSht.getRange(r,16).setValue(date);
}
}
This is the same code for two spreadsheets, in one it works OK, in the other it does not work.
How do I fix this?
It is most likely a security issue and Session calls are supposed to be avoided in simple triggers due to simple triggers limitations (look at https://developers.google.com/apps-script/guides/triggers/#restrictions, especially in regard to the very vague 5th bullet point - "They may or may not be able to determine the identity of current the user" (mistake on page) and where the link sends you back to the session page). In your case I wonder whether it worked only for the users who shared your domain name.
If you look at https://developers.google.com/apps-script/reference/base/session#getEffectiveUser() there is no reference for simple triggers (which for me is suspicious already) but the statement in regard to installable triggers tells me that it should probably not be used in simple ones.
I just went through a similar exercise myself where both getActiveUser and getEffectiveUser were returning blank when the domain of the script owner did not match the domain of the person who launched the script (in other words this happens when someone shares the script outside the domain and run it under a different username such as their personal Google account). I therefore created an installable trigger instead that calls getEffectiveUser and it works now (note that if you define your installable trigger programmatically rather than through Resources->Current project's triggers make sure the trigger function is in the invoking script file).
Hope this helps.
Related
After much searching, I have solved the first part of my problem: I found the below script which copies data from one range and adds it to another sheet.
However, the range that it is copying from is going to be automatically updating via a formula. So, my next challenge is - how do I get the script to run when the cell changes?
I believe there is a way to 'watch cells' for changes, but I'm really not very technical so I haven't been able to figure it out!
Potentially added complication - I believe 'on edit' scripts only run when the spreadsheet is open, is that right? If so, I'm also going to need to figure out how to get the script to run to check for new values on timed intervals.
Here's my current script:
function moveValuesOnly() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getRange("Sheet1!F1:H3");
var destSheet = ss.getSheetByName("Feuil2"); // Déterminer l'emplacement de la première ligne vide. var destRange = destSheet.getRange(destSheet.getLastRow()+1,1); source.copyTo (destRange, {contentsOnly: true}); source.clear ();
}
How to run a script on time-driven trigger:
You can use the script you already have
You can bind to it a time-driven trigger by
Going on Edit -> Current Project's triggers
Selecting create new trigger
Specifying the function to which you want to bind the trigger
Specifying that the trigger shall be time-driven
Select type of time based trigger and interval as desired
Click on Save
I have a problem with some Google Script stuff. Basically, my goal is to have the script check to see if a client's case was resolved and then send an email to them that the issue has been resolved. I've gotten the logic done on when to send an email, but every time I try and implement it into the spreadsheet, I get the error:
Error
You do not have permission to call MailApp.sendEmail. Required permissions: https://www.googleapis.com/auth/script.send_mail (line 8).
I've got a simple function to test the functionality of it, and when run in the script editor it works fine, but not on the spreadsheet. Here is my sample function:
function myFunction(row) {
var sheet = SpreadsheetApp.getActiveSheet();
var rng = sheet.getRange(row, 1, 1, 2);
var ara = rng.getValues();
var email = ara[0][0];
MailApp.sendEmail(email, "TEST", "This is a test of sendEmail().");
return "Email sent.";}
According to the Apps Script Custom Functions documentation:
If your custom function throws the error message You do not have permission to call X service., the service requires user authorization and thus cannot be used in a custom function.
To use a service other than those listed above, create a custom menu that runs an Apps Script function instead of writing a custom function. A function that is triggered from a menu will ask the user for authorization if necessary and can consequently use all Apps Script services.
Method 1
Basically, you can replicate the wanted behavior of the two functions above with this:
function SendEmail() {
var message = "This is your response";
var subject = "You have feed back in the parking lot";
var ss = SpreadsheetApp.getActiveSheet();
var textrange = ss.getRange("F2");
var emailAddress = ss.getRange("B2").getValue();
if (textrange.isBlank() == false)
MailApp.sendEmail(emailAddress, subject, message);
}
And in order to trigger the execution of this function, you can make use of Apps Script triggers and choose one which is the most convenient for your use-case.
Method 2
You can also create a custom menu and with the option of triggering the above function. You only need to add this:
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu("My Menu")
.addItem("Send Email", "SendEmail")
.addToUi();
}
And this is how it will look like on the Spreadsheet:
Reference
Apps Script Custom Functions;
Apps Script Range Class - isBlank();
Apps Script Custom Menus;
Apps Script Triggers.
I encountered the same problem today "You do not have permission to call MailApp.sendEmail".
I solved this by doing the next steps:
open "Tools" -> "Script editor"
in "Script editor" click on "View" -> "Show manifest file"
open the "appscript.json" file that appeared in the left section of your screen and add "https://www.googleapis.com/auth/script.send_mail" to the oauthScopes, like this:
{
"oauthScopes": ["https://www.googleapis.com/auth/spreadsheets", "https://www.googleapis.com/auth/script.send_mail"],
}
PS: I assigned the script to an image, which basically acts like a button.
In google sheets I'm trying to use a script to make a protected range.
I'd like the script to make a protected range that ONLY the owner can edit, and no one else, however the script has to work when it runs for a user who is NOT the owner.
When I'm logged in as the owner of the spreadsheet and I run the code from script editor, it works fine - it creates a protected range G1:G10 that only the owner can edit.
However when I run the script while logged in as a user who is NOT the owner, the permissions of the protected range allows BOTH the user AND the owner ability to edit the range. I'm aware of this page and this page, on the google developers documentation, however I can't see anything that'll help me.
Here's my code:
function setProtections() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var owner = ss.getOwner();
var effectiveUser = Session.getEffectiveUser();
var range = ss.getRange('G1:G10');
var protection = range.protect()
var editors = protection.getEditors();
protection.removeEditors(editors);
protection.addEditor(owner);
}
Can anyone help?
removeEditors() does not allow the current user to be removed:
Removes the given array of users from the list of editors for the protected sheet or range. Note that if any of the users are members of a Google Group that has edit permission, or if all users in the domain have edit permission, those users will still be able to edit the protected area. Neither the owner of the spreadsheet nor the current user can be removed.
However, you can use an Installable Trigger created by the owner that will run your setProtections() method as the user that created the Trigger [owner] - even if it is triggered by another user.
Example: Installable Trigger onEdit()
function installedOnEditTrigger (e) {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
//Checks if the sheet edit happened in the 7th column "G"
if (e.range.getColumn() == 7) {
var range = ss.getRange('G1:G10');
var protection = range.protect()
var editors = protection.getEditors();
//Removes all editors - owners can not be removed
protection.removeEditors(editors);
}
}
I have a google sheet that is a master database of orders entered by a number of dealers. Therefore, this master sheet must have limited edit access. I want created a number of other sheets (one for each dealer) through which they can enter orders and added to my master sheet via a script running in those sheets. Is this possible? When I tried this, it denied them access to the master sheet. Is there a way around this?
One work around that I've used for this problem is to set an installable trigger to run on a regular time interval.
Installable triggers run on the owner of the trigger's authority, so it will be able to access both the main spreadsheet as well as each dealer's. The trick is to assign a timed trigger to a function that checks each dealer's sheet for updates and then makes the appropraite change to the main spreadsheet. You'll want to be sure that the trigger doesn't take too long to run or run too frequently since you have a limited amount of script run time each day.
Something like the following:
function myTrigger() {
var time = new Date();
// maybe check to see if it's night or other times that dealers won't update
// that way you can return early and don't waste quota hours
var dealerSpreadsheetIds = ["id1", "id2", ...];
var dealerSheetName = "Deals";
for (var i = 0; i < dealerSpreadsheetIds.length; i++) {
var sheet = SpreadsheetApp.openById(dealerSpreadsheetIds[i]).getSheetByName(dealerSheetName);
// check Deals sheet ranges for updates
// if there is an update, update the main spreadsheet
}
}
I am trying to start 3 applications from a browser by use of custom protocol names associated with these applications. This might look familiar to other threads started on stackoverflow, I believe that they do not help in resolving this issue so please dont close this thread just yet, it needs a different approach than those suggested in other threads.
example:
ts3server://a.b.c?property1=value1&property2=value2
...
...
to start these applications I would do
location.href = ts3server://a.b.c?property1=value1&property2=value2
location.href = ...
location.href = ...
which would work in FF but not in Chrome
I figured that it might by optimizing the number of writes when there will be effectively only the last change present.
So i did this:
function a ()
{
var apps = ['ts3server://...', 'anotherapp://...', '...'];
b(apps);
}
function b (apps)
{
if (apps.length == 0) return;
location.href = apps[0]; alert(apps[0]);
setTimeout(function (rest) {return function () {b(rest);};} (apps.slice(1)), 1);
}
But it didn't solve my problem (actually only the first location.href assignment is taken into account and even though the other calls happen long enough after the first one (thanks to changing the timeout delay to lets say 10000) the applications do not get started (the alerts are displayed).
If I try accessing each of the URIs separately the apps get started (first I call location.href = uri1 by clicking on one button, then I call location.href = uri2 by clicking again on another button).
Replacing:
location.href = ...
with:
var form = document.createElement('form');
form.action = ...
document.body.appendChild(form);
form.submit();
does not help either, nor does:
var frame = document.createElement('iframe');
frame.src = ...
document.body.appendChild(frame);
Is it possible to do what I am trying to do? How would it be done?
EDIT:
a reworded summary
i want to start MULTIPLE applications after one click on a link or a button like element. I want to achieve that with starting applications associated to custom protocols ... i would hold a list of links (in each link there is one protocol used) and i would try to do "location.src = link" for all items of the list. Which when used with 'for' does optimize to assigning only once (the last value) so i make the function something like recursive function with delay (which eliminates the optimization and really forces 3 distinct calls of location.src = list[head] when the list gets sliced before each call so that all the links are taken into account and they are assigned to the location.src. This all works just fine in Mozilla Firefox, but in google, after the first assignment the rest of the assignments lose effect (they are probably performed but dont trigger the associated application launch))
Are you having trouble looping through the elements? if so try the for..in statement here
Or are you having trouble navigating? if so try window.location.assign(new_location);
[edit]
You can also use window.location = "...";
[edit]
Ok so I did some work, and here is what I got. in the example I open a random ace of spades link. which is a custom protocol. click here and then click on the "click me". The comments show where the JSFiddle debugger found errors.