Test As Add-on Causes an Error When onOpen Exists - javascript

I'm trying to create an add-on for Google Sheets. When the sheet is opened, the script should create a menu item in Add-ons.
The item is added and works when I open the Sheet it's originally bound to. When trying to test it as an add-on with onOpen as a declared function in my script, it always results in the item not being added and an error being logged in the Google Chrome DevTools console for the Sheet opened for the test:
Google Apps Script: We're sorry, a server error occurred. Please wait a bit and
try again.
This error doesn't occur if I comment out the onOpen method.
I've tried running it as an add-on in the following ways with both Auth.none and Auth.limited:
The original copy of the script attached to its original Sheet.
The original copy of the script running with a separate Sheet.
A copy of the script in a separate Google Scripts file running with a different Sheet.
Creating a new Sheet and bounded script with just an empty onOpen function.
These all result in the error above, and when I uncomment the contents of onOpen functions that do have code, none of their code appears to run.
Here's the original copy's code but keep in mind I still get the error even if the contents are commented out and this states that even if createMenu is used for an add-on, it's handled correctly by Google App Script:
function onOpen(e){
var menu = SpreadsheetApp.getUi().createAddonMenu(); // Or DocumentApp or FormApp.
if (e && e.authMode == ScriptApp.AuthMode.NONE) {
// Add a normal menu item (works in all authorization modes).
menu.addItem("Show Sidebar", "showSidebar");
menu.addToUi();
} else {
// Add a new menu (doesn't work in AuthMode.NONE).
var topUI = SpreadsheetApp.getUi();
topUI.createMenu("Mail Merge")
.addItem("Show Sidebar", "showSidebar")
.addToUi();
}
}
The Logger object also doesn't seem to log my messages while running the script as an add-on.

Copying the Sheet over to another account and just having it be owned by that account (not in a team drive) did allow the menu to appear when testing it as an add-on with both types of authorization. The Google App Script error also stopped occurring after doing this.
The most closely related information I could find about Team Drive restrictions was this, which says it limits cloud platform interaction. But this link is Google's page on setting up and running Test As Add-on and it doesn't state anything about the Team Drive or cloud platform.

Related

Trouble accessing Google Sheet as a TSV file

For the past few years, I've been using Sheets as a data source for a web app by using the following code to turn the id into a direct link to a TSV file:
let id="1zD3eIL8LCTJ8F_8U3kWA6k5WPJNKr_UZ_93bnARlMxQ"
let str="https://docs.google.com/spreadsheets/d/"+id+"/export?format=tsv";
var xhr=new XMLHttpRequest();
xhr.open("GET",str);
xhr.onload=function() {/* act on data */ };
xhr.send();
xhr.onreadystatechange=function(e) {
if ((xhr.readyState === 4) && (xhr.status !== 200)) {
/* Show error */
}
It still works on old files, but new ones yield a CORS error:
Access to XMLHttpRequest at 'https://doc-00-0g-sheets.googleusercontent.com/export/l5l039s6ni5uumqbsj9o11lmdc/5filqetsf3ohbeiq2e8vbtf8ik/1593267040000/112894833168181755194/*/1zD3eIL8LCTJ8F_8U3kWA6k5WPJNKr_UZ_93bnARlMxQ?format=tsv' (redirected from 'https://docs.google.com/spreadsheets/d/1zD3eIL8LCTJ8F_8U3kWA6k5WPJNKr_UZ_93bnARlMxQ/export?format=tsv') from origin 'https://viseyes.org' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Works: www.viseyes.org/scale?1LSnAM3A62AQipZfqxDtlOjt4MWJ0fBP22cdyqJqEj5M
Error: www.viseyes.org/scale?1zD3eIL8LCTJ8F_8U3kWA6k5WPJNKr_UZ_93bnARlMxQ
I believe your goal as follows.
You want to retrieve the data with TSV format from the Google Spreadsheet using Javascript.
Your spreadsheet is publicly shared.
For this, how about this answer?
Issue and workaround:
I could confirm the same situation from your question. Unfortunately, I couldn't remove this error. So, in this case, as a workaround, I would like to propose to use Web Apps created by Google Apps Script as the wrapper. By this, the error can be removed. The flow of this workaround is as follows.
Request to Web Apps from Javascript.
At Web Apps, the data is retrieved from "https://docs.google.com/spreadsheets/d/"+id+"/export?format=tsv".
Return the data with the TSV format from Web Apps.
Usage:
Please do the following flow.
1. Create new project of Google Apps Script.
Sample script of Web Apps is a Google Apps Script. So please create a project of Google Apps Script.
If you want to directly create it, please access to https://script.new/. In this case, if you are not logged in Google, the log in screen is opened. So please log in to Google. By this, the script editor of Google Apps Script is opened.
2. Prepare script.
Please copy and paste the following script (Google Apps Script) to the script editor. This script is for the Web Apps.
function doGet() {
let id = "1zD3eIL8LCTJ8F_8U3kWA6k5WPJNKr_UZ_93bnARlMxQ"; // This is from your script.
let str = "https://docs.google.com/spreadsheets/d/"+id+"/export?format=tsv"; // This is from your script.
const value = UrlFetchApp.fetch(str);
return ContentService.createTextOutput(value.getContentText());
}
If your Google Spreadsheet is not publicly shared, please modify as follows.
function doGet() {
let id = "1zD3eIL8LCTJ8F_8U3kWA6k5WPJNKr_UZ_93bnARlMxQ"; // This is from your script.
let str = "https://docs.google.com/spreadsheets/d/"+id+"/export?format=tsv"; // This is from your script.
const value = UrlFetchApp.fetch(str, {headers: {authorization: "Bearer " + ScriptApp.getOAuthToken()}});
return ContentService.createTextOutput(value.getContentText());
// DriveApp.getFiles() // This is used for automatically detecting the scope.
}
3. Deploy Web Apps.
On the script editor, Open a dialog box by "Publish" -> "Deploy as web app".
Select "Me" for "Execute the app as:".
By this, the script is run as the owner.
Select "Anyone, even anonymous" for "Who has access to the app:".
In this case, no access token is required to be request. I think that I recommend this setting for your goal.
Of course, you can also use the access token. At that time, please set this to "Anyone". And please include the scope of https://www.googleapis.com/auth/drive.readonly and https://www.googleapis.com/auth/drive to the access token. These scopes are required to access to Web Apps.
Click "Deploy" button as new "Project version".
Automatically open a dialog box of "Authorization required".
Click "Review Permissions".
Select own account.
Click "Advanced" at "This app isn't verified".
Click "Go to ### project name ###(unsafe)"
Click "Allow" button.
Click "OK".
Copy the URL of Web Apps. It's like https://script.google.com/macros/s/###/exec.
When you modified the Google Apps Script, please redeploy as new version. By this, the modified script is reflected to Web Apps. Please be careful this.
4. Run the function using Web Apps.
When you use this, please modify your Javascript script as follows and test it.
From:
let id="1zD3eIL8LCTJ8F_8U3kWA6k5WPJNKr_UZ_93bnARlMxQ"
let str="https://docs.google.com/spreadsheets/d/"+id+"/export?format=tsv";
To:
let str = "https://script.google.com/macros/s/###/exec";
Note:
When you modified the script of Web Apps, please redeploy the Web Apps as new version. By this, the latest script is reflected to the Web Apps. Please be careful this.
In my environment, I could confirm that when above workaround is used, no error occurs and the data with the TSV format can be retrieved.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script

GSuite Sheets addon not launching

I've published a private sheets addon for my company, but unfortunately when I try to run it both onInstall and onOpen fail with no execution logs and a 0s execution time in the Apps Script Console. It works fine from the sheet associated with the script project, just not from anywhere else. All I have to go on is an error in the chrome console:
Google Apps Script: We're sorry, a server error occurred while reading from storage. Error code PERMISSION_DENIED.
Here are the relevant functions:
function onInstall(e) {
onOpen(e);
Logger.log("Installed")
// Perform additional setup as needed.
}
function onOpen(e) {
Logger.log("Opening...")
var ui = SpreadsheetApp.getUi();
ui.createAddonMenu()
.addItem("Launch", "run")
.addToUi()
Logger.log("Added menu items")
}
None of the logger messages are actually logged, making me think that the function is never being launched despite the execution showing up in the console.
Any tips on how to debug/track down the error would be greatly appreciated!
Edit: I've also tried testing it via the script editor with AuthMode.NONE and AuthMode.LIMITED. Both work fine. I only encounter the error when running after installing via the GSuite Marketplace.
Here is the manifest.json:
{
"timeZone": "America/New_York",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8"
}
It appears to be working now. I changed the timeZone in manifest.json to America/Los_Angeles (my local tz) and re-published and all errors are gone. Not sure if this change is what made the difference or not, but thanks to Aerials for the nudge to look there.
Edit: This may also have been related to my use of clasp deployments. The problem recurred for me after modifying the script. I created a new deployment via the script editor UI (Publish -> Publish from manifest) and then clicked the "install addon" next to the new version. I also updated the Gsuite marketplace listing to point at the new version, and everything seems to be working.

Google Sheets onEdit(e) TypeError: cannot read property

I'm trying to implement the onEdit(e) simple trigger of Google Sheets. Everytime I try execute even a simple function such as:
function onEdit(e){
Logger.log(e.oldValue);
}
I get the following error:
My step by step:
I've tried following these two videos with no success:
https://www.youtube.com/watch?v=eWn_JxPSbds
https://www.youtube.com/watch?v=L1_nIhiVc5M
EDIT: SOLUTION
Thanks to #JPV and #TheMaster for the two potential solutions:
I was viewing the "Logs" and not the "Stackdriver Logging".
It seems you need to disable the V8 to view it in Logs, but need the Stackdriver Logging to view it with V8.
At least that seems to be the case with me
EDIT TWO
Here's the documentation where the issue is addressed:
https://developers.google.com/apps-script/guides/v8-runtime#ui_changes_for_logging
"If your script uses the Rhino runtime, selecting View > Logs in the Apps Script editor only shows you recent logs written by the Logger service. You must select View > Stackdriver Logging to see logs written with the console class."
Nothing is wrong with V8. That error is because you ran the script directly by clicking run button before. That caused the error and was logged. When you edit, it is logged to view>stackdriver logging. But the previous error is shown to you. You're looking at old logs.
As per the official documentation,
For scripts using the V8 runtime, the script editor View > Logs menu item shows both Logger and console results for the most recent execution in the current session
Only current session logs is available to View>Logs. To access previous session logs or logs logged not by running a function directly in the current session, use View > Stackdriver Logging
....
This seems to be a glitch in the new V8 engine. Disabling V8 should fix it. Let's hope this will be fixed soon.

Google Sheets View Only Protection for User Running App Script

I am sharing a set of Google Sheet documents with other users. I am the owner of all the spreadsheets and they are housed on my Google Drive. Every user has their own dedicated sheet. Users need to be able to run scripts on their sheet that collect and manipulate data from the sheet, which means the sheet needs to be unprotected while the scripts run. However, at the end end of the script, I want to protect the sheet again so that the user running the script is unable to edit the protected cells once the script is done running.
I am able to easily remove the protections from the sheet at the beginning of the script, but I am unable to protect the sheet again via the script without the user who is running the script being listed as an editor. I have tried using the "removeEditors" function to no avail (see below for basic example for one of the protected ranges).
var userEmail = Session.getActiveUser().getEmail();
var protection = newPickSheet.getRange('A1:M3').protect();
protection.removeEditor(userEmail);
For your reference, here is the code I'm using at the beginning of the script to remove all protections:
var protections = newPickSheet.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for(var i = 0; i < protections.length; i++){
protections[i].remove();
}
To summarize, I need my script to:
Remove all protections
Execute the main part of the script
Re-add all protections so that only me, the sheet owner, but NOT the user running the script, has edit ability.
I was able to accomplish this very easily using VBA when deploying Excel Macros, but it seems this is not nearly as simple using App Scripts.
If anyone can help me figure out a solution, it would be greatly appreciated.
You want to run mainly 3 functions in one function.
Remove the protected the range of newPickSheet.getRange('A1:M3').
Run a script for editing cell in the range.
Protect the range.
You want to run the script by clicking run button which is put on Spreadsheet. You don't want to use the event trigger.
If my understanding is correct, I think that the important point is who run each function. So how about this workaround? I think that there are several workarounds for your situation. So please think of this as just one of them.
Modification points:
In your situation, in order to protect a range from users, it is required to run the function for protecting the range as owner. Because the protected range is created by owner, when the protected range is removed, it is required to also run the function as owner.
Here, in this workaround, it supposes that the script of Execute the main part of the script includes the methods depending on user.
When the script is run with clicking the run button by each user, the script is run as each user. This is the important point for this situation. In order to run the part of script as owner, I used Web Apps here. By using Web Apps, the script for removing and adding the protected range can be run as owner.
By above points, the flow of this workaround is as follows.
1. Remove the protected range by owner using Web Apps.
2. Run the script of Execute the main part of the script by each user.
3. Protect the range by owner using Web Apps.
Preparation for using modified script:
Please run the following flow before you use the modified script.
Copy and paste the modified script to the script editor.
This script supposes that you are using the container-bound script of the Spreadsheet.
In this modified script, I used main() for the main function. If you are using other name, please modify it.
Please set ##### of var newPickSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("#####");.
Put the script of "Execute the main part of the script".
Deploy Web Apps.
On the script editor, open "Publish" -> "Deploy as Web Apps".
Set "Project version" as new. Please input freely to "Describe what has changed".
Set "Execute the app as:" to "Me".
Set "Who has access to the app:" to "Anyone". By this, each user can access to Web Apps using own access token.
Click "Deploy" or "Update" button. By this, Web Apps is deployed.
When you modify the script after Web Apps was deployed, please redeploy as new version. By this, the latest script is reflected to Web Apps. This is an important point for using Web Apps.
Modified script:
In this modified script, I used main() for the main function. If you are using other name, please modify it.
// This is the main function. Please set this function to the run button on Spreadsheet.
function main() {
// DriveApp.getFiles(); // This is a dummy method for detecting a scope by the script editor.
var url = ScriptApp.getService().getUrl() + "?access_token=" + ScriptApp.getOAuthToken();
UrlFetchApp.fetch(url + "&key=removeprotect"); // Remove protected range
// do Something: Please put the script of "Execute the main part of the script" here.
SpreadsheetApp.flush(); // This is required to be here.
UrlFetchApp.fetch(url + "&key=addprotect"); // Add protected range
}
function doGet(e) {
var newPickSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("#####"); // Please set here.
if (e.parameter.key == "removeprotect") {
// Remove protected range.
var protections = newPickSheet.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
protections[i].remove();
}
} else {
// Add protected range.
var ownersEmail = Session.getActiveUser().getEmail();
var protection = newPickSheet.getRange('A1:M3').protect();
var editors = protection.getEditors();
for (var i = 0; i < editors.length; i++) {
var email = editors[i].getEmail();
if (email != ownersEmail) protection.removeEditor(email);
}
}
return ContentService.createTextOutput("ok");
}
Note:
In this modified script, the range of "A1:M3" in the sheet of newPickSheet. newPickSheet is declared as var newPickSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("#####"). If you want to modify this, please modify to your situation.
In my environment, when user clicks the run button soon, after user opens the shared Spreadsheet, there was sometimes the case that the function of the button cannot be found. In this case, please wait for completely loading the Spreadsheet.
This is a simple sample script. So please modify this to your situation.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script

Send a message from Dialog box (mean-stack site) to the task pane

I am trying to use Dialog API of Office addin.
I can successfully open a Dialog box from my task pane by:
$scope.openDialog = function () {
Office.context.ui.displayDialogAsync('https://localhost:3000/home',
function (asyncResult) {
dialog = asyncResult.value;
dialog.addEventHandler(Office.EventType.DialogMessageReceived, processMessage);
});
}
My Dialog box is a mean-stack site. I have added <script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script> in the index.html. And I tried to use Office.context.ui.messageParent(true);, it shows an error in console:
And I see in the doc that I don't understand quite well:
The Office JavaScript library is loaded in the page. (Like any page
that uses the Office JavaScript library, script for the page must
assign a method to the Office.initialize property, although it can be
an empty method. For details, see Initializing your add-in.)
I also tried to add Office.initialize = function () { }; in index.html, the error is still there, and processMessage of the task pane does not seem to receive anything.
So is there anything special I should do to my mean-stack site so that it could use messagePerent?
The console error will not introduce any bad effect to the dialog. We already fixed it internal. You can just ignore this error. Did you check whether office.context.ui.messageParent is null or undefined ? if it is not, then the dialog has been initialized successfully. Then it will be only something wrong with the postMessage method, what system and browser version are you using ?
1. If it is Win10 and latest version of IE, please make sure the dialog first page domain is same with the taskpane domain. Or you can use other browser to try it.
2. If it is Win7&8&8.1 and IE, then you can just try in other browser to see whether the messageParent api is work. We have already done a code change to fix the IE issue. It will be deployed to prod soon.

Categories