Error: no permission to append row - google-apps-script

I have the following google script. Trying to append the results to row and I get this error
"You do not have permission to call appendRow (line 11)."
function webs() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var sheet = ss.getActiveSheet();
var url = "https://api.pipedrive.com/v1/mailbox/mailMessages/17685?include_body=1";
var token = "&api_token=token"
var response = UrlFetchApp.fetch(url+token);
var dataSet = JSON.parse(response.getContentText());
sheet.appendRow([dataSet.data.body]);
}
Anyway around this? if I can't append in google sheets, is there anyway to add results to a row?

I believe you need to use an Installable Trigger instead of using a Simple Trigger which have restrictions on what they can do.
See how to install it manually here:
From the script editor, choose Edit > Current project's triggers.
Click the link that says: No triggers set up. Click here to add one now.
Under Run, select the name of function you want to trigger.
Under Events, select either Time-driven or the Google App that the script is bound to (for example, From spreadsheet).
Select and configure the type of trigger you want to create (for example, an Hour timer that runs Every hour or an On open trigger).
Optionally, click Notifications to configure how and when you will be contacted by email if your triggered function fails.
Click Save.
Edit: I could also be restrictions on custom functions:
A custom function cannot affect cells other than those it returns a value to. In other words, a custom function cannot edit arbitrary cells, only the cells it is called from and their adjacent cells. To edit arbitrary cells, use a custom menu to run a function instead.
Edit: Other times you just need to authorize it first. Try going to Run > Run Function > then choose any function to run. Apps Script will pop-up with a authorization window for all the new scopes you are using.

Related

I have an onEdit() trigger in my sheet that calls a script, but when I copy the sheet for another user the trigger is gone

the following is the script that is triggered by any edit
function changeSpeadsheetName() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var range = sheet.getActiveSheet().getRange(7, 1).getValue();
//assuming you want to add the string in A1 to your title
var name = sheet.rename(range);
}
It looks like you have an Installed trigger on this script
I can tell this because the name of the function is not onEdit. If it was the name of the function, it would likely be a Simple trigger, but it could also be an installed trigger.
In a nutshell, simple triggers can be copied with the sheet and it will work "out the box". However, they are very restricted and so, this is why your sheet probably has an installed one.
To install the trigger on the new sheet, you need to open the script editor. Once you are at the editing screen, to the left, click on the triggers icon.
Once there, you can:
It will ask you what function you want to run when the trigger is triggered, and what type of trigger you want it to be (choose onEdit) - then it will likely ask for authorization, which you should grant.
For more details:
Simple triggers
Installable triggers

Calling an API endpoint upon changes to any Google Sheets files

What I wish to achieve:
Whenever a cell is changed in any google sheet on my shared drive (by
any user on the domain) I want to call an API endpoint and include
information about which cell was edited.
My approach:
I believe Google App Scripts Add-on is what I need. Installed for all users on the domain.
I see there are "bound" scripts and standalone scripts. For standalone scripts I am not able to create any other triggers than timer and calender based triggers. Bound scripts seem to be permanently bound to a single sheet and won't impact other sheets in any way.
What am I missing?
I find a few end-to-end tutorials on blogs for making bound scripts, but nothing for generic cross-domain stuff.
You can achieve all this through a standalone script. Create a standalone script and follow these steps:
Step 1: Get spreadsheet ids
First you would have to get the id of the different Spreadsheets in your shared drive. You can do it in Google Apps Script itself if you use the Advanced Drive Service (see Reference below). To activate this service, go to Resources > Advanced Google services... in your script editor and enable Drive API.
Then, write a function that will return an array of the spreadsheet ids in the shared drive. You will have to call Drive.Files.list for that. It could be something along the following lines (please write your shared driveId in the corresponding line):
function getFileIds() {
var params = {
corpora: "drive",
driveId: "your-shared-drive-id", // Please change this accordingly
includeItemsFromAllDrives: true,
q: "mimeType = 'application/vnd.google-apps.spreadsheet'",
supportsAllDrives: true
}
var files = Drive.Files.list(params)["items"];
var ids = files.map(function(file) {
return file["id"];
})
return ids;
}
Step 2: Create triggers for each spreadsheet
Install an onEdit trigger programmatically for each of the spreadsheets (an edit trigger fires a function every time the corresponding spreadsheet is edited, so I assume this is the trigger you want). For this, the ids retrieved in step 1 will be used. It could be something similar to this:
function createTriggers(ids) {
ids.forEach(function(id) {
var ss = SpreadsheetApp.openById(id);
createTrigger(ss);
})
}
function createTrigger(ss) {
ScriptApp.newTrigger('sendDataOnEdit')
.forSpreadsheet(ss)
.onEdit()
.create();
}
The function createTriggers gets an array of ids as a parameter and, for each id, creates an onEdit trigger: everytime any of these spreadsheets is edited, the function sendDataOnEdit will run, and that's where you will want to call your API endpoint with information about the edited cell.
Step 3: Call API endpoint
The function sendDataOnEdit has to get data from the edited cell and send it somewhere.
function sendDataOnEdit(e) {
// Please fill this up accordingly
var range = e.range;
var value = range.getValue();
UrlFetchApp.fetch(url, params) // Please fill this up accordingly
}
First, it can get information about the cell that was edited via the event object, passed to the function as the parameter e (you can get its column, its row, its value, the sheet and the spreadsheet where it is located, etc.). For example, to retrieve the value of the cell you can do e.range.getValue(). Check the link I provide in reference to get more details on this.
Second, when you have correctly retrieved the data you want to send, you can use UrlFetchApp.fetch(url, params) to make a request to your URL. In the link I provide below, you can see the parameters you can specify here (e.g., HTTP method, payload, etc.).
Please bear in mind that you might need to grant some authorization to access the API endpoint, if this is not public. Check the OAuth reference I attach below.
(You have to edit this function accordingly to retrieve and send exactly what you want. What I wrote is an example).
Summing this up:
In order to create the triggers you should run createTriggers once (if you run it more times, it will start creating duplicates). Run for example, this function, that first gets the file ids via Drive API and then creates the corresponding triggers:
function main() {
var ids = getFileIds();
createTriggers(ids);
}
Also, it would be useful to have a function that will delete all the triggers. Run this in case you want to start from fresh and make sure you don't have duplicates:
function deleteTriggers() {
var triggers = ScriptApp.getProjectTriggers();
triggers.forEach(function(trigger) {
ScriptApp.deleteTrigger(trigger);
})
}
Reference:
Advanced Drive Service
Drive.Files.list
onEdit trigger
Install trigger programmatically
onEdit event object
UrlFetchApp.fetch(url, params)
Connecting to external APIs
OAuth2 for Apps Script
ScriptApp.deleteTrigger(trigger)
I hope this is of any help.

Session.getActiveUser().getEmail() results are inconsistent

I am trying to create a google sheets custom menu based on the user email.
Code is as follows:
var AllowedReportRecipients = ["u1#gmail.com", "u2#gmail.com", "u3#gmail.com"];
var ReportRecipient = null;
function onOpen()
{
var ui = SpreadsheetApp.getUi();
var menu = ui.createMenu('Custom');
menu.addItem('Edit', 'editHtm')
.addItem('History', 'historyHtm');
var usr = Session.getActiveUser().getEmail();
var isReportee = false;
for (var i=0;i<AllowedReportRecipients.length; i++)
if (AllowedReportRecipients[i] == usr)
{
ReportRecipient = usr;
isReportee = true;
}
if (isReportee)
menu.addItem('Request abc', 'sendABC');
menu.addToUi();
}
This is an unpublished sheet script. As others have reported the call to getActiveUser().getEmail() returns blank when the sheet is opened by the non-owner of the sheet. However, a call to getActiveUser().getEmail() executed at a later time correctly returns the logged in user email. For example, when called in historyHtm in response to the "History" menu item click it works. Why the difference? How do I properly load my menus?
The onOpen() function is a special reserved function, one of the [Simple Trigger][1] functions built into Google Apps Script. These functions run without requiring user authorization, so there are restrictions on what capabilities of the system they may access.
For your case, the important restriction is this:
They may or may not be able to determine the identity of the current user, depending on a [complex set of security restrictions][2].
ref
For users with consumer accounts or accounts that are not in the same Google Apps Domain as the developer, the onOpen() function will not be able to obtain the email address of the person running the script.
When you run other functions, for example those that are invoked via menu, they kick off the authorization cycle for the script, after which they are able to do more than the simple triggers.
That seems inconsistent - but is by design.
I think you need to rethink your user interface - I haven't been able to find a work-around that will produce the "privileged" portion of your menu upon opening the spreadsheet.
An installed onOpen trigger will have greater abilities than the simple trigger, but will run under the authority of the developer so it won't help your situation, as it will come up with your email, not the active user's.
Setting a timed trigger in the onOpen to update the menu seemed promising... except for the restriction that the timed trigger function has no access to the spreadsheet UI, of course.

Copy last row to another spreadsheet upon form submission

I have a Google spreadsheet with an Add-on that takes data from a form and runs on form submission. I also have another Add-on that pushes the data from this spreadsheet to another spreadsheet - let's call it spreadheet2 here. In spreadsheet2 I have my own script with a function copyLastRow() that copies the last row from this spreadsheet to another spreadsheet - let's call it spreadsheet3. My script is supposed to append a new row from spreadsheet2 to spreadsheet3. It runs OK when I run it manually, but it is not running via the project trigger - which I installed for Script editor's Resources - I tried both on Edit and on Change triggers, but they are simply not firing up when data is pushed from spreadsheet2. The script is working when I actually edit spreadsheet2. However, this is not good for what I need - I really need the script to work without manual intervention. Can you, please, help?
function copyLastRow() {
var target = SpreadsheetApp.openById('xxxxxxxxx').getSheetByName('Sheet1');
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var lastrow = sheet.getLastRow();
var sourceData = sheet.getRange(lastrow, 1, 1, 53).getValues();
target.appendRow(sourceData[0]);
EDIT: I updated the code - I realized I left the previous version of code here.
You're right to worry about whether this function will be effective when multiple users are submitting forms... it won't be. But it's easily improved.
What's the problem? When copyLastRow() runs, it assumes that the last row of the source spreadsheet contains the response that also triggered the function. However, before it gets around to reading that row, another user might submit a form. (Eventually, the function will be triggered by that submission as well, and could process the same row a second time.)
The simplest improvement in this situation is to take advantage of the event object that is provided to the trigger function as a parameter. See Google Sheet Events for some background details.
The newly submitted responses are in event.values, which is an array - exactly what is needed for .appendRow(). Here's how we can update your copyLastRow function:
function copyLastRow(event) {
var target = SpreadsheetApp.openById('xxxxxxxxx').getSheetByName('Sheet1');
target.appendRow(event.values);
}
Now it doesn't matter how many users submit forms - each will be handled uniquely by this function.

onedit why cannot send email?

i've this simple script that should send an email when a cell is changed
function onEdit(e) {
var doc = e.source;
var r = doc.getActiveRange().getValue();
if (r == "Niccolò"){
var a = doc.getActiveRange().setBackground('#ff0000');
var b = GmailApp.sendEmail('name#gmail.com', 'subject', 'body');
}
}
This function change also cell colour.
THe problem is that the cell colour works, so it's change while doesn't send any email.
It looks so simple i don't understand why doesn't works!
Simple triggers like onEdit(), onOpen() or onFormSubmit() have a limited set of possible actions because they run without authorization , see the documentation for further details.
So this behavior you describe is normal.
You should use an installable trigger instead as explained in the same doc page.
here is an summary of the documentation :
These simple triggers run in response to actions in Google Spreadsheets, and they run as the active user. For example, if Bob opens the Spreadsheet, then the onOpen function runs as Bob, irrespective of who added the script to the Spreadsheet. For this reason, the simple triggers are restricted in what they are permitted to do:
They cannot execute when the Spreadsheet is opened in read-only mode.
They cannot determine the current user.
They cannot access any services that require authentication as that user. For example, the Google Translate service is anonymous and can be accessed by the simple triggers. Google Calendar, Gmail, and Sites are not anonymous and the simple triggers cannot access those services.
They can only modify the current Spreadsheet. Access to other Spreadsheets is forbidden.
For more information on event permissions, see Executing from a Container-Specific Trigger.