Running an Apps Script stored in Google Drive from Google Sheets - google-apps-script

I have a google sheets workbook that builds a report based on user input, which is run by clicking a "button" (a square shape on the sheet itself). I want to share this workbook with my team, who need to create a copy of the workbook so they can generate multiple reports for themselves.
However, I also want to be able to make changes to the code at a later date, and avoid having them re-download the latest version, so I'm trying to decentralise the Apps Script file by putting it into my company's shared Google Drive, and then the workbook script replaced by a function that loads that file in the drive.
So far I have:
function getApp(){
var folderId = "<folder_id>";
var fileName = "<file_name>";
var scriptId = "<script_id>";
var folder = DriveApp.getFolderById(folderId);
var files = folder.getFilesByName(fileName);
var url = "https://script.google.com/feeds/download/export?id=" +
scriptId + "&format=json"
var options = {
"method": "GET",
"headers": {
"Authorization": "Bearer " + ScriptApp.getOAuthToken()
},
"muteHttpExceptions": true
};
var response = UrlFetchApp.fetch(url, options);
dataContentAsString = response.getContentText();
fileContents = JSON.parse(dataContentAsString);
var codeFile = fileContents.files[1];
if (codeFile){
var code = codeFile.source;
eval(code);
buildReport();
}
}
Which takes the "file" at index 1 (an object containing all functions in the script) and runs the buildReport function. When I do Logger.log(fileContents) I can see the entire script, so I know the retrieval from google drive is working.
buildReport() is the "main" function, which then calls other functions, however when running it, I get the error below, which indicates an Oauth issue:
Exception: You do not have permission to call
SpreadsheetApp.getActive. Required permissions:
(https://www.googleapis.com/auth/spreadsheets.currentonly ||
https://www.googleapis.com/auth/spreadsheets)
Does this mean that despite being able to access the file, the file itself doesn't have access to the sheets (that contain templates which the script manipulates based on the users initial inputs prior to clicking the button) where the macro is being run from?
Is this the best way to achieve what I want?
Update
I added a trigger to the workbook, which runs buildReport just fine when the spreadsheet is opened (not the desired behaviour, but still at least it's working in some way), however when clicking the "Build Report" button it shows the error still.
Why would the local script (i.e. local to the google sheet) be able to successfully import from google drive and run buildReport() when using a trigger, but not when clicking a button to do the same thing?

If you have an standalone script that will "modify" the user current Spreadsheet (or slides, or...) but the script bounded to the Spreadsheet only "calls" the external script, you can add a dummy/commented line to the bound script, so that when the user runs the bound script, permissions for the standalone script will also be asked. Sorry, for my english :D
Adding this line, anywhere, will do the trick:
//SpreadsheetApp.getActive()
BTW, I found very useful your way to share scripts!

Related

google script openByUrl autorization, trigger activation

I'm quite new to programming and struggling with apps authorization :
I created a code that has to access other files of my Gdrive, with the command openByUrl, which sends back an error message, telling me it doesn't have the authorization to perform openByUrl.
I went through the oauth2 scopes but can't understand how to give the authorization to my script.
I already have an internet copied script that runs DriveApp.getFolderById, but it runs through a menu.
I want both getFolderById and openByUrl to run on a trigger (each day at midnight) thus, it can't run through a menu.
The goal of my script is to list all files in a specified folder, get the spreadsheet ones, and extract specific cells from these spreadsheets.
I got something running with importance, but clearly, it's too bulky to run properly on more than 3000 files.
Could anyone explain to me how to give authorizations to openByUrl please? (even better if in French ;-) )
Many thanks in advance for your help.
Answer
I would create a new project and copy the whole code.
If you want to check why in a particular project you have this error, you can open the manifest file that has the required scopes. You can find it clicking on Project Settings on the left bar and then Show "appsscript.json" manifest file in editor. The field oauthScopes has to contain all the required scopes or has to be deleted.
Methods explanation:
DriveApp.getFoldersByName(name) get all the folders with a specified name. Take the first one with .next()
Folder.getFilesByType get all the files with a specific mimetype
SpreadsheetApp.openById open a specific Spreadsheet document
Spreadsheet.getSheets() get all the sheets in an array
Sheet.getRange() get a specific range
Range.getValues() get the values of a specified range
Updated code:
function main() {
var folderName = 'folder'
var type = MimeType.GOOGLE_SHEETS
var folders = DriveApp.getFoldersByName(folderName).next().getFolders()
while (folders.hasNext()) {
var folder = folders.next()
console.log(folder.getName())
var files = folder.getFilesByType(type)
while (files.hasNext()) {
var file = files.next()
console.log(file.getName() + ': ' + file.getid())
var sheets = SpreadsheetApp.openById(file.getid()).getSheets()
var values = sheets[0].getRange('A1:B2').getValues()
console.log(values)
}
}
}

Need to open the spreadsheet while copying from another account

I am trying to copy the sheet from one account from another account while accessing google form. I am able to copy the sheet by using below Appscript. But i need to open the same while copying itself. Currently able open by manually.
function myFunction() {
var app=SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1DpeqCN2Bm2pWxf4TiMEa53xM6MEhSx8GfP6CuyI/edit#gid=0");
app.copy("NewSheettest19");
}
Opening the file in user context
You can't trigger a script on form submission that runs in the user context. Installed triggers run with the owner's privileges. This could result in security issues.
You may email the user with the link or insert it in a shared spreadsheet cell. But you cannot access the user interface in this context.
Please look at this answer for additional information: https://stackoverflow.com/a/26978896.
Opening the file in the editor context:
You won't be able to open the copied file directly from Apps Script. You can use though a workaround building a user interface that uses javascript to open a new page, navigating to your recently copied spreadsheet file.
Code:
var app=SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1DpeqCN2Bm2pWxf4TiMEa53xM6MEhSx8GfP6CuyI/edit#gid=0");
var copied = app.copy("NewSheettest19");
var html = "<script>window.open('" + copied.getUrl() + "');google.script.host.close();</script>";
var userInterface = HtmlService.createHtmlOutput(html);
FormApp.getUi().showModalDialog(userInterface, "Opening Copied Sheet... ");
This code will create a custom dialog containing the Javascript to open the new Spreadsheet and then it will close the dialog.
References:
G Suite Docs Dialogs
Window.open()

Copy a google script project from a Spreadsheet to another

I've seen many answers about how to use scripts to copy sheets to another Spreadsheet in Google Spreadsheets, such as this copyTo method.
But now I have a big spreadsheet with many tabs, and I created a new version of it with updates in the bound scripts. The data in this new sheet is test data. So this is what I have:
Old spreadsheet file (consistent data) / Old script project (obsolete)
New spreadsheet (test data) / New script project (current)
I need then to copy the new script project to the old spreadsheet (which has the consistent data).
I know I could make an effort to copy each sheet instead, but that is really not the question here (besides also creating lots of trouble with named ranges). The question is how to copy the script project to another spreasheet.
Are you looking for the solution of this question yet? If you still do, how about this answer? Recently, Google Apps Script API was added. By this, users got to be able to easily manage GAS project files. This sample script uses this API.
In order to use this sample, please do the following installation flow.
Installation :
Enable Google Apps Script API at API console.
If you have already opened the script editor, you can access there by this link.
Retrieve current scopes.
On script editor, File -> Project properties -> Scopes
Copy scopes.
Add a scope of https://www.googleapis.com/auth/script.projects and https://www.googleapis.com/auth/script.external_request to the Manifests file (appsscript.json) of the script editor.
On script editor, View -> Show manifest file
Add "oauthScopes": ["https://www.googleapis.com/auth/script.projects", "https://www.googleapis.com/auth/script.external_request", "### other scopes ###"] to appsscript.json, and save.
If your script needs other scopes, please add them here.
Copy and paste this sample script, and run. And please authorize.
Flow of script
Retrieve filename of source project.
Retrieve source project.
Create new project (bound script) in the Google Docs.
Update the created new project by retrieved source project.
By these flow, the project (bound script) of source spreadsheet is copied to the destination spreadsheet.
Sample script :
Before launch this sample, please input the project ID of source spreadsheet and the destination spreadsheet ID.
function main() {
var srcProjectId = "### project ID ###"; // Source project ID
var dstGoogleDocsId = "### file ID of Google Docs ###"; // Destination spreadsheet ID
var baseUrl = "https://script.googleapis.com/v1/projects";
var accessToken = ScriptApp.getOAuthToken();
// Retrieve filename of bound-script project.
var srcName = JSON.parse(UrlFetchApp.fetch(baseUrl + "/" + srcProjectId, {
method: "get",
headers: {"Authorization": "Bearer " + accessToken}
}).getContentText()).title;
// Retrieve bound-script project.
var obj = UrlFetchApp.fetch(baseUrl + "/" + srcProjectId + "/content", {
method: "get",
headers: {"Authorization": "Bearer " + accessToken}
}).getContentText();
// Create new bound script and retrieve project ID.
var dstId = JSON.parse(UrlFetchApp.fetch(baseUrl, {
method: "post",
contentType: 'application/json',
headers: {"Authorization": "Bearer " + accessToken},
payload: JSON.stringify({"title": srcName, "parentId": dstGoogleDocsId})
}).getContentText()).scriptId;
// Upload a project to bound-script project.
var res = JSON.parse(UrlFetchApp.fetch(baseUrl + "/" + dstId + "/content", {
method: "put",
contentType: 'application/json',
headers: {"Authorization": "Bearer " + accessToken},
payload: obj
}).getContentText());
}
Note :
This script copies the source project as a new project. So if there are some bound script projects in the Google Docs, this script does NOT affect the existing script.
But, if you use this sample sceipt, at first, please test using a new project and new Google Docs. By this, please understand the work of this script.
References :
Google Apps Script API
Manifests
If I misunderstand your question, I'm sorry.
Updated: July 4th, 2019
At April 8, 2019, the specification of Google Apps Script Project was changed. Please see the detail at Google Cloud Platform Projects. When you use Google Apps Script API with the GAS project created after At April 8, 2019, please create a new Cloud Platform Project and link the GAS project to the created Cloud Platform Project. (At script editor, Resources --> Cloud Platform project) By this, the above flow can be used.
Flow for linking Cloud Platform Project to Google Apps Script Project
I think the Google Clasp tool can help you automate this. I'm starting to use it and it's promising. For instance you may issue a clasp clone [scriptId] of both old and new projects on different folders. Then you can copy all your new scripts files to the old folder and then issue a clasp push. But since I'm not a clasp expert I suggest you tests this on a proof of concept :)
Edit (2019-06-21):
After using the product for several months I confirm that this approach works very well.
There appears to be no direct way to "Copy" the script from on spreadsheet to another But you can assign whatever triggers you want to the second spreadsheet from the script in the first one (or from anywhere actually) using Installable Triggers
e.g. you have an onEdit(e) trigger in the first sheet that does some stuff,
you can easily apply it to the second spreadsheet by using Script App like
var ss = SpreadsheetApp.openById(secod_spreadsheet_id); //or whatever way you like
ScriptApp.newTrigger('onEdit') //or whatever its name is ...
.forSpreadsheet(ss)
.onEdit() //or onCreate() or onChange()
.create();
and this will add the trigger to your second sheet, but you must read the restrictions and Limitaions for the installable triggers as they will always run on your account (see explaination in links)
Last word,
If you know previously that you are going to use many sheets you might want to make a Standalone script with all your triggers and assign installable triggers to your sheets
Also note that these triggers can't be edited only removed and re-assigned from each sheet using a loop or something like that
You might also want to check out This question
Just go to the script editor old spreadsheet and delete the script (or comment it out). Then open the script editor of the new spreadsheet and copy the script. Paste the script in the old spreadsheets script editor. Save it and you should be good to go.

Google Apps Script Error: "You do not have permission to call openById"

I am trying to use a Google Script that I found on GitHub. (It is Posted Below.) It is supposed to merge data from Google sheets into a Google Doc. However, when I try to run the script I get an error: "You do not have permission to call openById". I have researched the problem and the information is conflicting. But, I have tried the following:
Manually run the function in script editor before adding the trigger to Google sheet.
Authorized the script to access my Google Docs data.
Set the triggers for the function to "On Form Submit"
Added the script to my Google Sheet and ran it from the menu as instructed: Merge --> Fill Template
I continue to get the error. I see that there are other people having the same problem, but the answers provided so far have not solved the issue. Any help would be greatly appreciated.
function doMerge() {
var selectedTemplateId = "1foobarfoobarfoobarfoobarfoobarfoobar";//Copy and paste the ID of the template document here (you can find this in the document's URL)
var templateFile = DriveApp.getFileById(selectedTemplateId);
var mergedFile = templateFile.makeCopy();//make a copy of the template file to use for the merged File.
mergedFile.setName("filled_"+templateFile.getName());//give a custom name to the new file (otherwise it is called "copy of ...")
var mergedDoc = DocumentApp.openById(mergedFile.getId());
var bodyElement = mergedDoc.getBody();//the body of the merged document, which is at this point the same as the template doc.
var bodyCopy = bodyElement.copy();//make a copy of the body
bodyElement.clear();//clear the body of the mergedDoc so that we can write the new data in it.

Unable to access (an save) my SpreadSheet Google App Script

I have a Google SpreadSheet (filled through a form) associated to a Google Apps Script.
Now, the spreadsheet contains too many cells and I'm not able to load it anymore (server timeout).
I'm not interested in the data, but I really want to retrieve the script (the source code).
Unfortunately, the only way I know is to load the SpreadSheet first :-(
(Copying the Document ends in a timeout and the script is not copied.)
Any suggestion?
Regards,
RĂ©mi.
Create another script which makes a copy of original spreadsheet to backup the data and then clear the original spreadsheet through the script.
However this seems an issue which should be reported to Google Spreadsheet Forum
Here is an example code which will backup the spreadsheet as a copy and clear the original spreadsheet
function backupDataAndClear(){
var sourceSS = SpreadsheetApp.openById('ID_OF_ORIGINAL_SS');
var newSS = sourceSS.copy('Backup SS');
var sourceSheets = sourceSS.getSheets();
for(var i in sourceSheets){
sourceSheets[i].clear();
}
}