How can my frozen backup script be improved to avoid timeout? - google-apps-script

I have a frozen backup script that works (below), but it times out due to the high number of rows and formulas. The script is to create a frozen backup with just the values (no formulas) of the entire spreadsheet (or some sheets). I'm trying to improve it by not copying the sheets with formulas to the new spreadsheet, but only copying the "frozen" copies of those sheets. Any help / guidance would be appreciated.
The script below works just fine with a spreadsheet that does not have too many rows and formulas. I've tried to modify it by using SpreadsheetApp.create instead of .copy to create a blank spreadsheet, and then trying to come up with how to use .forEach method with an if statement to identify _temp sheets and only copy them. I've been stuck for a while, hence this question.
The current script is:
function copyEntireSpreadsheet() {
var id = "ID"; // Please set the source Spreadsheet ID.
var ss = SpreadsheetApp.openById(id);
var srcSheets = ss.getSheets();
var tempSheets = srcSheets.map(function(sheet, i) {
var sheetName = sheet.getSheetName();
var dstSheet = sheet.copyTo(ss).setName(sheetName + "_temp");
var src = dstSheet.getDataRange();
src.copyTo(src, {contentsOnly: true});
return dstSheet;
});
var destination = ss.copy(ss.getName() + " - " + new Date().toLocaleString());
tempSheets.forEach(function(sheet) {ss.deleteSheet(sheet)});
var dstSheets = destination.getSheets();
dstSheets.forEach(function(sheet) {
var sheetName = sheet.getSheetName();
if (sheetName.indexOf("_temp") == -1) {
destination.deleteSheet(sheet);
} else {
sheet.setName(sheetName.slice(0, -5));
}
});
}
The script above times out because it takes too long in the new (copied) spreadsheet to process all cells with formulas, but there should be no need to copy them in the first place.
The script above works by:
Creating copies of all sheets and renaming with the addition of
"_temp".
Rewrites each "_temp" sheet over on itself with the parameter contentsOnly: true so that formulas become values.
Creates a copy of the entire spreadsheet.
Deletes the "_temp" sheets from the source spreadsheet.
Deletes the original sheets from the destination spreadsheet.
What I'd like to accomplish is:
Create copies of all sheets ("_temp") and rewrite on themselves to
get rid of formulas. (1 & 2 above)
Create a blank spreadsheet (new)
Copy only "_temp" sheets into the new spreadsheet (new)
Delete "_temp" sheets in source spreadsheet (4 above)
If I can modify it so that I can identify which sheets should be copied, that'd be even better, but a simple copy of all _temp sheets will also do the job.
Thanks in advance for any help!
Final Script
#Tanaike's answer was quick and simple - worked perfectly! Added .slice to remove "_temp" text from the new copied sheets. Here's the final script for anyone looking for a similar script:
function copyEntireSpreadsheetTest() {
var id = "ID"; // Please set the source Spreadsheet ID.
var ss = SpreadsheetApp.openById(id);
var srcSheets = ss.getSheets();
var tempSheets = srcSheets.map(function(sheet, i) {
var sheetName = sheet.getSheetName();
var dstSheet = sheet.copyTo(ss).setName(sheetName + "_temp");
var src = dstSheet.getDataRange();
src.copyTo(src, {contentsOnly: true});
return dstSheet;
});
var destination = SpreadsheetApp.create(ss.getName() + " - " + new Date().toLocaleString());
tempSheets.forEach(function(sheet) {
sheet.copyTo(destination).setName(sheet.getSheetName().slice(0,-5));
ss.deleteSheet(sheet);
});
destination.deleteSheet(destination.getSheets()[0]);
}

You want to achieve the following flow by modifying your script.
Create copies of all sheets ("_temp") and rewrite on themselves to get rid of formulas. (1 & 2 above)
Create a blank spreadsheet (new)
Copy only "_temp" sheets into the new spreadsheet (new)
Delete "_temp" sheets in source spreadsheet (4 above)
If my understanding is correct, how about this modification?
From:
var destination = ss.copy(ss.getName() + " - " + new Date().toLocaleString());
tempSheets.forEach(function(sheet) {ss.deleteSheet(sheet)});
var dstSheets = destination.getSheets();
dstSheets.forEach(function(sheet) {
var sheetName = sheet.getSheetName();
if (sheetName.indexOf("_temp") == -1) {
destination.deleteSheet(sheet);
} else {
sheet.setName(sheetName.slice(0, -5));
}
});
To:
var destination = SpreadsheetApp.create(ss.getName() + " - " + new Date().toLocaleString());
tempSheets.forEach(function(sheet) {
sheet.copyTo(destination).setName(sheet.getSheetName());
ss.deleteSheet(sheet);
});
destination.deleteSheet(destination.getSheets()[0]);
Reference:
copyTo(spreadsheet)
If I misunderstood your question and this was not the result you want, I apologize.

Related

Apps Script - Creating a Spreadsheet then copy all the value and paste it in a different spreadsheet

I'm trying to to do the following tasks using Apps Script:
Create a new spreadsheet. The name will be "My spreadsheet" + Date of the day
Write a random value in the new spreadsheet
Copy the entire sheet (1st tab) from the new spreadsheet
Paste the value in another spreadsheet (in a specific sheet)
Here is the script I've written so far:
function copyPasteAllData() {
var date = Utilities.formatDate(new Date(), "GMT+7", "dd/MM/yyyy");
// get today's date
var ss = SpreadsheetApp.create("Existing Data - Apps Script - "+ date);
// create a new spreadsheet
var ssId = ss.getId();
// get ID of new spreadsheet
var existing = Sheets.Spreadsheets.Values.get('MyspreadsheetID', "Existing Data");
// get existing Spreadsheet + sheet location
var newSheet = Sheets.Spreadsheets.Values.get(ssId, "Sheet1");
// get new Spreadsheet + sheet location
ss.getRange('A1').setValue('1')
// setup a random value
var rangeAllData = newSheet.getRange(1, 1, newSheet.getMaxRows(), newSheet.getMaxColumns());
// copy the entire sheet from the
existing.rangeAllData.setValue()
}
I think the line below need to be changed but I'm not sure how to fix this.
I'm still quite a beginner with Apps Script and coding in general, apologies if it seems obvious.
var existing = Sheets.Spreadsheets.Values.get('MyspreadsheetID', "Existing Data");
var newSheet = Sheets.Spreadsheets.Values.get(ssId, "Sheet1");
Feel free to change anything in the script. Just note that I can only get the sheet ID and not the name
Thank you
Updated from the comment below:
In this case you can use the method getDataRange() to get the data from the new Sheet.
Then on the destination you can use the same range from the new sheet or use the one you want from the old sheet:
function copyPasteAllData() {
//getDate
var date = Utilities.formatDate(new Date(), "GMT+7", "dd/MM/yyyy");
//Create new sheet + set value
var newSheet = SpreadsheetApp.create("Existing Data - Apps Script - " + date);
newSheet.getRange('A1').setValue('this is new');
//get new sheet DataRange
var source = newSheet.getId();
var newSheetDataRange = SpreadsheetApp.openById(source).getSheets()[0].getDataRange().getA1Notation();
Logger.log(newSheetDataRange)
var newSheetValues = SpreadsheetApp.openById(source).getSheets()[0].getDataRange().getValues();
Logger.log(newSheetValues)
//copy the data to the old sheet
SpreadsheetApp.openById('OLD SHEET ID').getSheets()[0].getRange(newSheetDataRange).setValues(newSheetValues);
}

Search & Replace within formula of another entire sheet using Google script

I have used a variation of this: How to copy format and values, not formulas, when creating a spreadsheet backup in Google Apps Script?
code here:
function copySheet() {
var spreadsheetId = "1O6D59wAwzq45fHHPrlZea_cpZsVdlTuct0YgJRD3iyw"; // Please set the source Spreadsheet ID.
var destFolderId = "18L8k7jtldUQTonp5VO5ZBFd6j_AoC0bj"; // Please set the destination folder ID.
// Copy each sheet in the source Spreadsheet by removing the formulas as the temporal sheets.
var ss = SpreadsheetApp.openById(spreadsheetId);
var tempSheets = ss.getSheets().map(function(sheet) {
var dstSheet = sheet.copyTo(ss).setName(sheet.getSheetName() + "_temp");
var src = dstSheet.getDataRange();
src.copyTo(src, {contentsOnly: false});
return dstSheet;
});
// Copy the source Spreadsheet.
var timeZone = Session.getScriptTimeZone();
date = Utilities.formatDate(new Date(), timeZone, "YYMMdd");
var destination = ss.copy(ss.getName() + " - " + date);
// Delete the temporal sheets in the source Spreadsheet.
tempSheets.forEach(function(sheet) {ss.deleteSheet(sheet)});
// Delete the original sheets from the copied Spreadsheet and rename the copied sheets.
destination.getSheets().forEach(function(sheet) {
var sheetName = sheet.getSheetName();
if (sheetName.indexOf("_temp") == -1) {
destination.deleteSheet(sheet);
} else {
sheet.setName(sheetName.slice(0, -5));
}
});
// Move file to the destination folder.
var file = DriveApp.getFileById(destination.getId());
DriveApp.getFolderById(destFolderId).addFile(file);
file.getParents().next().removeFile(file);
}
function searchReplace() {
var FILE = SpreadsheetApp.openById("1vb3lv7boclruNyy116CN3oZXeqjUpPl5oFDSakEclDI");
var textFinder = FILE.createTextFinder('Raw').matchFormulaText(true);
textFinder.replaceAllWith('Raw');
}
To copy an entire sheet WITH formulas. The problem I have found is that the formulas referencing other sheets/tabs break with "Unresolved Sheet Name" error, even though the sheets do exist.
I have found a workaround here: https://support.google.com/docs/thread/25074318/unresolved-sheet-name-workaround-when-copying-or-renaming-tabs?hl=en
and am hoping to be able to search and replace within the new copied sheet to replace the word "Raw" within the formulas with "Raw" which will fix the broken formulas, all from the original script from the sheet being duplicated.
I hope this makes sense, I'd love to hear any suggestions or workarounds...
Thanks
If you are looking for an apps-script based solution, I answered a similar question here. I also wrote a short tutorial explaining the script. But that one was about replacing text.
You are looking to find-and-replace inside the formula.
To do that you need to tweak my script as follows:
var textFinder = activeSheet.createTextFinder('Raw').matchFormulaText(true);
This will look for the text Raw inside the formulas on the sheet.
To replace it with Cooked, the next line of the script is:
textFinder.replaceAllWith('Cooked');
Please ref my previous answer and tutorial for more.

Google Apps Script - copy to existing spreadsheet

I'm attempting to use Google Apps Script copy() to 'publish' my master spreadsheet to an output spreadsheet, but getting multiple copies each time instead of it replacing the output file. Can anyone suggest a way to replace the contents of a destination spreadsheet so I can keep the same file-ID for the output and manually trigger a 'publish'. Have tried copyTo, but just makes multiple sheets instead.
The master spreadsheet is a staff roster that needs to be able to be worked on by multiple managers without staff seeing the live version. When the manager has finished updating, it can be pushed to staff.
Edit: Got it working
function publishRoster() {
var source = SpreadsheetApp.getActiveSpreadsheet();
var sheet = source.getActiveSheet();
var updatedDateTime = sheet.getRange("A1");
var now = Utilities.formatDate(new Date(), "GMT+10:30", "dd/MM/yyyy H:mm")
updatedDateTime.setValue("Last Published " + now);
var sourceName = source.getSheetName();
// var sValues = source.getDataRange().getValues();
var destination = SpreadsheetApp.openById('my-destination-sheet-id-here');
var destinationSheet = destination.getSheetByName(sourceName);
if (destinationSheet == null ) { }
else { destination.deleteSheet(destinationSheet); }
sheet.copyTo(destination).setName(sourceName);
}
copyTo() creates a copy of existing sheet in the destination spreadsheet.
If you want to publish the changes from a specific sheet to specified destination sheet then you can copy the data from source sheet to destination sheet, instead of copying the whole sheet.[which will of course create new sheets each time you copy]
So the code to copy/publish data from master sheet to slave sheet goes as follows :
var SOURCEID = 'xxxxxxxxxxxxx'; //put your source spreadsheet id here
var SOURCESHEETNAME = 'XXXXX'; //put your source sheet name here
var DESTINATIONID = 'xxxxxxxxxxxxx'; //put your destination spreadsheet id here
var DESTINATIONSHEETNAME = 'XXXXX'; //put your destination sheet name here
var data = SpreadsheetApp.openById(SOURCEID).getSheetByName(SOURCESHEETNAME).getDataRange().getValues();
SpreadsheetApp.openById(DESTINATIONID).getSheetByName(DESTINATIONSHEETNAME).clear(); //This line is to clear the existing data in destination.
SpreadsheetApp.openById(DESTINATIONID).getSheetByName(DESTINATIONSHEETNAME).getRange(1, 1, data.length; data[0].length).setValues(data);
//data.length = no. of rows in source
//data[0].length = no. of columns in source
var now = Utilities.formatDate(new Date(), "GMT+10:30", "dd/MM/yyyy H:mm");
SpreadsheetApp.openById(DESTINATIONID).getSheetByName(DESTINATIONSHEETNAME).getRange("A1").setValue("Last Published " + now);
This is not a tested code, let me know if any issues arises, I'll be happy to help you.
Thanks

I need to add formula to the same cell in all of the sheets in a google sheets workbook

I have a workbook that contains around 150 sheets and I’m trying to create an index of all of the sheets with a hyperlink from the index to each of the individual sheets.
In the index sheet i have a list of all the sheet names in column A and the formula INDIRECT("’"&A1&"'!AF1”) in column B. Then in cell AF1 of each sheet i have used script (I’ve posted this script below) to populate this cell with the spreadsheetID (URL + ID) of that spreadsheet.
Now i have the problem of adding the formula "=sheetURL()” to cell AF1 of all sheets in my workbook without having to do so manually.
function sheetURL()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet().getSheetId();
return ss.getUrl() + '#gid=' + sheet;
}
You were close
function sheetURL(sheetName) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets().filter(function(sheet) {
return sheet.getName() == sheetName;
})[0];
return ss.getUrl() + '#gid=' + sheet.getSheetId();
}
This gets the "first" sheet with the desired name of which there can be only one anyway and then extracting the sheetId.
You can use it by calling =sheetURL(A1) in the index sheet and dragging down.

copyTo using {contentsOnly:true} not working

As a caveat, I am very new to Google Apps scripting. I appreciate any assistance that can be provided.
I am trying to copy the contents of a sheet into a new document. This code works without any problem:
// Create a new Spreadsheet and copy the current sheet into it.
var newSpreadsheet = SpreadsheetApp.create("Spreadsheet to export");
var projectname = SpreadsheetApp.getActiveSpreadsheet();
sheet = originalSpreadsheet.getActiveSheet();
sheet.copyTo(newSpreadsheet);
However, this copies over the formulas from the current sheet - I am trying to copy the values only, as the formulas reference data on other sheets in the original document.
My attempt to do this is as follows:
// Create a new Spreadsheet and copy the current sheet into it.
var newSpreadsheet = SpreadsheetApp.create("Spreadsheet to export");
var projectname = SpreadsheetApp.getActiveSpreadsheet();
sheet = originalSpreadsheet.getActiveSheet();
sheet.copyTo(newSpreadsheet, {contentsOnly:true})
However, this generates the following error: Cannot find method (class)copyTo($Proxy914,object).
I am unsure what I am doing wrong. Any assistance would be appreciated. Thanks in advance!
There are actually 2 copyTo, one applies to sheet and the other applies to Range
According to the documentation (see links above), the second one has optional argument to copy values only while the first has not.
What I guess you could do is use the Range.copyTo() to copy the whole sheet range to a temporary sheet (in the same spreadsheet) and then copy that temporary sheet to the other spreadsheet and finally delete the temporary sheet from the source spreadsheet.
Hoping it is clear enough ;-)
In the script gallery there is a script called spreadsheetFrozenBackup, through which a copy of a spreadsheet can be made.
The range.copyTo that Serge alludes to is used.
It is not a long script, so I reproduce it here for information:
// Make copy of current spreadsheet with backup name.
function spreadsheetFrozenBackup() {
// Get current spreadsheet.
var ss = SpreadsheetApp.getActiveSpreadsheet();
// Name the backup spreadsheet with date.
var bssName = ss.getName() + " frozen at " + Utilities.formatDate(new Date(), "GMT", "yyyyMMdd HHmmss");
var bs = SpreadsheetApp.openById((DocsList.copy(DocsList.getFileById(ss.getId()), bssName)).getId());
// Make sure all the formulae have been evaluated...
SpreadsheetApp.flush();
// Get all the sheets in the spreadsheet
var bsl = bs.getSheets();
var pl = "";
for (var i = 0; i < bsl.length; i++) {
bsl[i].getDataRange().copyTo(bsl[i].getDataRange(), {contentsOnly:true});
pl = pl + " " + bsl[i].getName();
SpreadsheetApp.getActiveSpreadsheet().toast(pl, "Processed Sheets");
}
SpreadsheetApp.getActiveSpreadsheet().toast(bssName, "Frozen Backup");
}