Copy a google script project from a Spreadsheet to another - google-apps-script

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.

Related

Running an Apps Script stored in Google Drive from Google Sheets

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!

How to get last execution time of a google app script?

Does Google App Scripts provide an API to determine when a script was executed the last time?
I'm executing a script periodically and I only like to process data which is new since the last execution, thus I'd like to know when my script was executed the last time. But since I execute it not every x min its hard to hardcode this value.
Thanks in advance, I really appreciate your help and expertise.
I believe your goal as follows.
You want to retrieve the last executed time of the functions of Google Apps Script.
You want to achieve this using Google Apps Script.
In this case, I thought that the method of "Method: processes.list" of Google Apps Script API might be able to be used. In order to use this API, please do the following flow.
Usage:
1. Linking Google Cloud Platform Project to Google Apps Script Project.
In this case, at first, it is required to link Google Cloud Platform Project to Google Apps Script Project. For this, you can see the detail flow at this repository
2. Prepare sample script.
Please copy and paste the following script to the script editor of the Google Apps Script project linking to Google Cloud Platform Project.
function myFunction() {
const scriptId = "###"; // Please set the Google Apps Script project.
const url = "https://script.googleapis.com/v1/processes?pageSize=1&userProcessFilter.scriptId=" + scriptId;
const res = UrlFetchApp.fetch(url, {headers: {authorization: "Bearer " + ScriptApp.getOAuthToken()}});
const obj = JSON.parse(res.getContentText());
const lastExecutedProcess = obj.processes[0];
console.log(lastExecutedProcess)
}
In this sample script, the last executed process of the specific Google Apps Script project can be retrieved. For example, you can also retrieve the last executed process of the specific function of the the specific Google Apps Script project.
When an error occurs, please check https://github.com/tanaikech/Linking-Google-Cloud-Platform-Project-to-Google-Apps-Script-Project-for-New-IDE#sample-1. You can see the method for avoiding the error using Apps Script API.
Note:
For example, if you want to retrieve the last executed process of all functions of all Google Apps Script project in your Google Drive, you can also use the following script.
function myFunction2() {
const url = "https://script.googleapis.com/v1/processes?pageSize=1";
const res = UrlFetchApp.fetch(url, {headers: {authorization: "Bearer " + ScriptApp.getOAuthToken()}});
const obj = JSON.parse(res.getContentText());
const lastExecutedProcess = obj.processes[0];
console.log(lastExecutedProcess)
}
In this API, you can also use from outside of Google. So, you can also achieve above using other languages except for Google Apps Script.
References:
Method: processes.list
Linking Google Cloud Platform Project to Google Apps Script Project for New IDE
I think it is more reliable to use the exact time up to which point you processed your data than the script execution time because there could a small but meaningful difference which may result in either not accounting for some time period or double process some time period.
What you should do instead is to store the exact time up to which you processed the data and store that and then read that value again next time your script runs.
There is an Apps Script service to do that called Properties Service. It is a key/value service to store some basic information in different scopes. What you need is the Script properties so that you can share information between two executions. Something like this:
const propService = PropertiesService.getScriptProperties();
const lastExecutionTime = propService.getProperty('last_execution');
if (lastExecutionTime) {
// Script was executed before.
} else {
// Script has never been executed.
}
const currentTime = Date.now();
// Do your work
propService.setProperty('last_execution', currentTime);
may 2022
if you're not using Google Cloud Platform, the easiest solution might be to add this line of code to all scripts
SpreadsheetApp.openById('id of a log file').getSheets()[0].appendRow([arguments.callee.name,new Date()])
you will find in the log not only the last execution time, but all the use of the script concerned

Using private sheets with tabletop.js

I've used tabletop.js [1] in the past and is amazing! You can simply do anything you want seriously.
The only problem I saw is that you need to publish your spreadsheets to the web, which of course is really risky if you are working with sensitive data.
I'm in need now of using it in a project with sensitive data, so I was hoping someone can guide me on how to use it with spreadsheets that are not published to the web.
I've been searching for this for a long time without any success but seems that tabletop.js does support private sheets (here's the pull request that added this option [2]).
In fact, looking at the documentation they included it [1]:
authkey
authkey is the authorization key for private sheet support.
ASK: How am I suppose to use the authkey? can someone provide me with an example so I can try?
Thanks in advance!
[1] https://github.com/jsoma/tabletop
[2] https://github.com/jsoma/tabletop/pull/64
How about this answer?
Issue and workaround:
At "tabletop.js", from the endpoint (https://spreadsheets.google.com/feeds/list/###/###/private/values?alt=json) of request, it seems that "tabletop.js" uses Sheets API v3. And when authkey is used, oauth_token=authkey is added to the query parameter. In this case, unfortunately, it seems that the private Spreadsheet cannot be accessed with it. From this situation, unfortunately, I thought that in the current stage, "tabletop.js" might not be able to use the private Spreadsheet. But I'm not sure whether this might be resolved in the future update. Of course, it seems that the web-published Spreadsheet can be accessed using this library.
So, in this answer, I would like to propose the workaround for retrieving the values from Spreadsheet as the JSON object.
Pattern 1:
In this pattern, Google Apps Script is used. With Google Apps Script, the private Spreadsheet can be easily accessed.
Sample script:
When you use this script, please copy and paste it to the script editor and run the function myFunction.
function myFunction() {
const spreadsheetId = "###"; // Please set the Spreadsheet ID.
const sheetName = "Sheet1"; // Please set the sheet name.
const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);
const values = sheet.getDataRange().getValues();
const header = values.shift();
const object = values.map(r => r.reduce((o, c, j) => Object.assign(o, {[header[j]]: c}), {}));
console.log(object) // Here, you can see the JSON object from Spreadsheet.
}
I thought that this might be the simple way.
Pattern 2:
In this pattern, the Web Apps created by Google Apps Script is used. When the Web Apps is used, the private Spreadsheet can be easily accessed. Because the Web Apps is created with Google Apps Script. In this case, you can access to the Web Apps from outside by logging in to Google account. And, the JSON object can be retrieved in HTML and Javascript.
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. In order to use Document service, in this case, Web Apps is used as the wrapper.
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.
Google Apps Script side: Code.gs
function doGet() {
return HtmlService.createHtmlOutputFromFile("index");
}
function getObjectFromSpreadsheet(spreadsheetId, sheetName) {
const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(sheetName);
const values = sheet.getDataRange().getValues();
const header = values.shift();
const object = values.map(r => r.reduce((o, c, j) => Object.assign(o, {[header[j]]: c}), {}));
return object;
}
HTML&Javascript side: index.html
<script>
const spreadsheetId = "###"; // Please set the Spreadsheet ID.
const sheetName = "Sheet1"; // Please set the sheet name.
google.script.run.withSuccessHandler(sample).getObjectFromSpreadsheet(spreadsheetId, sheetName);
function sample(object) {
console.log(object);
}
</script>
spreadsheetId and sheetName are given from Javascript side to Google Apps Script side. From this situation, in this case, getObjectFromSpreadsheet might be instead of "tabletop.js".
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 "Only myself" for "Who has access to the app:".
In this case, in order to access to the Web Apps, it is required to login to Google account. From your situation, I thought that this might be useful.
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.
You can test above scripts as follows.
Login to Google account.
Access to the URL of Web Apps like https://script.google.com/macros/s/###/exec using your browser.
By this, you can see the retrieved JSON object at the console.
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.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script

Google Sheets: How to import the following data?

Data Source
https://www.treasury.gov/resource-center/data-chart-center/interest-rates/Pages/TextView.aspx?data=yieldAll
I am trying to get the following data onto a Google Sheet, but it is looking to be tricky to do so using IMPORTXML. Any idea how to do it?
You want to retrieve a table from the HTML data of the URL.
From I am trying to get the following data onto a Google Sheet, I thought like this.
If my understanding is correct, how about this answer?
Issue and workaround:
Unfortunately, it seems that the file size of HTML is large. So when =IMPORTXML("https://www.treasury.gov/resource-center/data-chart-center/interest-rates/Pages/TextView.aspx?data=yieldAll","//title") is used, an error of Resource at url contents exceeded maximum size. occurs. When I retrieve HTML data from the URL, the size of HTML data was about 9 MB. It is considered that the reason of error is due to this. So as one of workaround, how about using Google Apps Script? In this workaround, the following flow is used.
Retrieve HTML data using UrlFetchApp
Parse the HTML data using Parser which is a GAS library.
Put the parsed data to the active sheet on the Spreadsheet using PasteDataRequest of Sheets API.
Usage:
Preparation:
Please install Parser. About the install of library, you can see it at here.
The project key of the library is M1lugvAXKKtUxn_vdAG9JZleS6DrsjUUV.
Please enable Sheets API at Advanced Google services.
Sample script:
Please copy and paste the following script to the script editor of the container-bound script of the Spreadsheet. After above settings were done, please run the function of myFunction(). When the script is run, the table of HTML is put to the active sheet on the Spreadsheet.
function myFunction() {
// Retrieve HTML data from URL.
var url = "https://www.treasury.gov/resource-center/data-chart-center/interest-rates/Pages/TextView.aspx?data=yieldAll";
var html = UrlFetchApp.fetch(url).getContentText();
// Parse HTML data.
var table = "<table" + Parser.data(html).from("<table class=\"t-chart\"").to("</table>").build() + "</table>";
// Put the values to the Spreadsheet.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var resource = {requests: [{pasteData: {html: true, data: table, coordinate: {sheetId: sheet.getSheetId()}}}]};
Sheets.Spreadsheets.batchUpdate(resource, ss.getId());
}
References:
Parser
PasteDataRequest
Advanced Google services
If I misunderstood your question and this was not the direction you want, I apologize.
Updated at April, 23, 2021:
New IDE for Google Apps Script has finally been released at December 7, 2020. Ref By this, in the current stage, in order to install Google Apps Script library, it is required to use the script ID of Google Apps Script project.
In this case, when the Google Apps Script library of Parser is installed, unfortunately, this ID M1lugvAXKKtUxn_vdAG9JZleS6DrsjUUV cannot be used.
So when you use new IDE, please use the following script ID.
1Mc8BthYthXx6CoIz90-JiSzSafVnT6U3t0z_W3hLTAX5ek4w0G_EIrNw
This script ID is the ID of Google Apps Script project of M1lugvAXKKtUxn_vdAG9JZleS6DrsjUUV. By this, the library of Parser can be installed to the new IDE.
About the method for installing the library, you can see the official document.
Reference:
Libraries

Google Script in spreadsheet copy does not execute

I have a master spreadsheet which contains a script that makes a POST request to my server onEdit with the current spreadsheet ID.
function post2Server(){
ss = SpreadsheetApp.getActiveSpreadsheet();
payload = {};
payload['spreadsheet_id'] = ss.getId();
headers = {
'Content-Type': 'application/json',
'Accept' : 'application/json'
}
options = {
'method' : 'post',
'contentType': 'application/json',
'headers' : headers,
'payload' : JSON.stringify(payload)
}
res = UrlFetchApp.fetch(MY_SERVER_URL, options);
return;
}
This function works as intended on the master sheet. Now, when I use the python google API to create a copy of this master sheet, the script copies over, however, doesn't run. I get an error saying....
Server error occurred. Please try saving the project again.
Why isn't this running? Within the copy of the spreadsheet, I even create a new function which simply logs "hello" and receive the same error. It appears that after the Python SDK copied the master sheet, no functions run. Is this a permissions issue? How can I get the script to execute in any subsequent copy of the master sheet?
Usually my approach to this kind of requirements is to use the scripts.run method of the Google Apps Script REST API. In this way you are in full control of what scripts are executed and of the parameters.
The main conditions you have to met to use this method are the following:
Deploy the script project as an API executable
Provide a properly scoped OAuth token for the execution
Ensure that the script and the calling application share a common Cloud Platform project
If you look for more details there is a dedicated page in the documentation that explains how to run a specific method of your Apps Script and at the bottom of it you have a sample in many different language, including python.
Right now copying a sheet that contains a bound Google App Script using the Drive API authenticated through a Google Service Account will always render the script unusable due to a bug in the Drive API (filed here). Until that's fixed, there's no simple solution to this.
In our case, we were able to eliminate our use of the bound script by utilizing functions in Sheets, specifically the IMPORTDATA function as a means of sending web requests.