Monthly Sheet Duplication via Time Trigger - google-apps-script

I have a spreadsheet to track monthly memberships and payments. I duplicate the sheet at the end of the month rename it to the date and lock it to archive it. I have a marco recorded and setup to do this and have a button on the sheet to click to do this. It works great.
However, I'd like to make this script triggered via time which I have setup in the Tigger area of google scripts. But when it runs, it fails and I get the following error:
Exception: Please select an active sheet first.
at ArchiveSheet(macros:5:15)
Here is my code:
function ArchiveSheet() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
spreadsheet.duplicateActiveSheet();
var sheetname = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "MMM/yyyy");
spreadsheet.getActiveSheet().setName("Archive "+ sheetname);
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Current Month'), true);
spreadsheet.getRange('J3:J263').activate();
};
Any help would be appreciated.

getActiveSpreasheet() can only be used on bounded scripts or on change, on edit and on open installable triggers created programmatically, on a time-driven trigger you have to use one of the following
open(file) (this should be used together with DriveApp / Advanced Drive Service
openById(id)
openByUrl(url)
Related
Time-Driven triggers not working properly

Sheet Duplication with a trigger
function myfunk101() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
sh.activate();
const nsh = ss.duplicateActiveSheet();
const n = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "HH:mm:ss");
nsh.setName("Archive " + n);
};
The active sheet is always ss.getSheets()[0] so it's better to pick on by name. And it won't activate a sheet on a current session because the instance its running on is only opened on the server.
function createTriggerFormyfunk101() {
if (ScriptApp.getProjectTriggers().filter(t => t.getHandlerFunction() == 'myfunk101').length == 0) {
ScriptApp.newTrigger('myfunk101').timeBased().everyMinutes(5).create();//I used every five minutes because I didn't want to wait for the end of the month
}
}
I think this is a better way to go:
function myfunk101() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");//Pick your sheet
ss.insertSheet(Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "HH:mm:ss"), {template: sh});//change you time format for your name. Since I was running 5 minute triggers I wanted more precision.
};
insertSheet(sheetName, options)

Related

Creating columns across all the spreadsheet + transpose the data

I'm running API requests to export data from third party to a Google Spreadsheet.
This script will be running automatically on a weekly basis (for report purposes).
In this Google Spreadsheet, I need to:
Create columns across all the sheets (they are all structured in the same way).
Transpose the data from previous week to the newly created columns.
Delete the old data from last week in the sheet named 'raw data'
Hide this sheet name 'raw data'
I've created a script with the 4 tasks I need Google Sheet to perform.
I'm guessing there are some issues with my functions/constants in my following script. I just need one function that nests all the other needed 'subfunctions' inside.
A bit of help to fix this would be appreciated. I feel I'm almost there :)
function columnCreation() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheets()[0,1,2,3,4,5];
const range = sheet.getRange('D1:H55').getValues();
const newrange = sheet.getRange('I1:M55');
const rules = sheet.getConditionalFormatRules();
const today = Utilities.formatDate(new Date(), "GMT+7", "MM/dd/yyyy");
sheet.insertColumnsAfter(8,5);
sheet.clearConditionalFormatRules();
sheet.setConditionalFormatRules(rules);
newrange.setValues(range);
sheet.getRange('D1').setValue(today).setNumberFormat("MMMM DD");
}
function clearAllContent() {
var range = SpreadsheetApp.getActive().getSheetByName('Raw Data').getRange();
range.clearContent();
}
function hideSheet() {
SpreadsheetApp.getActive().getSheetByName('Raw Data').hideSheet();
}
As an answer for the comment:
The error shown is on line 4 which refers to line 3 var sheets which is not a single sheet but an array of them.
To solve it you have to select which of the sheets you want to select. As an example, if there is only one Sheet, you have to choose 0:
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheets()[0];
const range = sheet.getRange('D1:H55').getValues();

Timing out issue in Google Sheet

The code below is used to perform some simple functions in a Google Sheet.
It creates two new sheets (makes them (0) and (1), names them, and adds color) and hides another sheet.
The code has been extremely unreliable. If I create a blank google sheet and test it works fine but when I add it to the live version of the sheet it times out and fails to run all the code.
I also tested copying the live version (in case there was some corruption in the file) and initially it seemed to work but then the same problem occurred. I am brand new to app script so perhaps I am missing something obvious. I would appreciate any suggestions.
function New_Tabs() {
var spreadsheet = SpreadsheetApp.getActive();
var curDate = Utilities.formatDate(new Date(), "GMT+1", "M/d") //sets format for current
date as month/day
//Selects and activates the Day_date sheet / Gets calculated date info
var sheet = SpreadsheetApp.getActive().getSheetByName('Day_Date');
sheet.activate();
var val = SpreadsheetApp.getActiveSheet().getRange(2,6).getValue();
//Logger.log(val) Used this to check the value was pulled correctly
//Insert the sheet for today's date - Data from portal/Excel macro will be pasted here
spreadsheet.insertSheet(0); //Makes it the first sheet
spreadsheet.getActiveSheet().setName(val); //pulls calculated date from Day_Date sheet
spreadsheet.getActiveSheet().setTabColor('#00ff00'); //Colors the sheet tab green
//Insert the sheet for Logistics
spreadsheet.insertSheet(1); //Makes it the second sheet
spreadsheet.getActiveSheet().setName("Logistics " + curDate ); //names the sheet with text
and current date
spreadsheet.getActiveSheet().setTabColor('#00ff00'); //Colors the sheet tab green
//trying to slow down to make hiding the tab more reliable
Utilities.sleep(2000);// pause for 2 seconds
//selects the sheet for today's date - Data from portal/Excel macro will be pasted here
spreadsheet.setActiveSheet(spreadsheet.getSheetByName(val), true);
//trying to slow down to make hiding the tab more reliable
Utilities.sleep(2000);// pause for 200 milliseconds
//selects the Day_date sheet and hides it
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Day_Date'), true);
spreadsheet.getActiveSheet().hideSheet();
SpreadsheetApp.getUi().alert("Completed");
};
Try it this way:
function New_Tabs() {
const ss = SpreadsheetApp.getActive();
const curDate = Utilities.formatDate(new Date(), "GMT+1", "M/d");
const sh = SpreadsheetApp.getActive().getSheetByName('Day_Date');
const val = sh.getRange(2,6).getValue();
let ns = ss.insertSheet(0);
ns.setName(val);
ns.setTabColor('#00ff00');
let ns1 = ss.insertSheet(1);
ns1.setName("Logistics " + curDate );
ns1.setTabColor('#00ff00');
ns.hideSheet();
ss.toast("Completed")
}

Standalone script onEdit() not working when cell value changes

I want to put the time stamp every time someone changes anything on a sheet.
This is a standalone code linked to a file via Sheet ID, it works but only when I run the code.
When I edit the sheet, it doesn't automatically update the time.
Any advice?
function onEdit() {
var s = SpreadsheetApp.openById("1Dshq_jln79nDXMVv8O8_BNcpKMf3TrO08aSNQg-abcM"); // bound the file by Sheet ID then open the specific sheet by name
var ss = s.getSheetByName("H2 OKRs");
var r = ss.getRange("A1:Y100").activate(); // Check the range of entry
if (r) { //checks the column
var nextCell = r.offset(0, 1);
if( nextCell.getValue() === '' ) //is empty?
var date = new Date();
date = Utilities.formatDate(new Date(), "GMT+7", "dd/MM/yyyy HH:mm");
nextCell.getCell(1,1).setValue(date);
};
}
UPDATE: Apparently onEdit() doesnt work well as a standalone script so I embedded it back to Gsheet. Thanks for the help!!
See restrictions, onEdit() simple trigger runs only when the script is attached to a document and cannot be used in a standalone script. You must have the script embeded into the spreadsheet or be an addon in order for it to fire off when you edit the file.
please try with
function onEdit(e) {
...
}

Can I use a script to copy and rename a sheet in Google Sheets with the current data as the tab name?

I have a data sheet that will update weekly. I would like a script to copy the sheet and rename it with the current date. The goal is to preserve the data in the sheet weekly before it updates so that I have a record of the data.
This is the script that I have so far:
/** #OnlyCurrentDoc */function myFunction() {
let sh, temp_one_values;
sh = SpreadsheetApp.getActive();
temp_one_values = transpose_(sh.getSheetByName('Complete Tutoring Roster').getRange('A1:Z1000').getValues());
{temp_one.copyTo(sh).setName(szToday);
}
}
function transpose_(array) {
return Object.keys(array[0]).map(function (col) {
return array.map(function (row) {
return row[col];
});
});
}
function CopyDataSheet() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('A1').activate();
spreadsheet.duplicateActiveSheet();
spreadsheet.getActiveSheet().setName('11/19/2020');
};
I used record a macro to get the script.
I know that I need to edit the setName code. But I am not sure how to code it to pull the current date.
Explanation:
You just need to get the date of today in the desired format: MM/dd/yyyy:
const today = Utilities.formatDate(new Date(), spreadsheet.getSpreadsheetTimeZone(), "MM/dd/yyyy");
And then use the copyTo(spreadsheet) method to duplicate the sheet into the existing spreadsheet and then name it with the date of today:
sheet.copyTo(spreadsheet).setName(today);
You don't need to activate ranges or sheet. That is a logic generated by macros but it makes your code confusing and inefficient. Instead of getting the active sheet, use getSheetByName to get the desired sheet based on its name.
Solution:
In this solution, change Sheet1 to the name of the sheet you want to duplicate.
function CopyDataSheet() {
const spreadsheet = SpreadsheetApp.getActive();
const sheet = spreadsheet.getSheetByName('Sheet1');
const today = Utilities.formatDate(new Date(), spreadsheet.getSpreadsheetTimeZone(), "MM/dd/yyyy");
sheet.copyTo(spreadsheet).setName(today);
};

spreadsheet script running with trigger and manually

This should be simple but I'm stuck on this script...
It has a function (createnewsheet) that runs manually AND on a time trigger so I had to choose openById() to access the spreadsheet but when I'm looking at the sheet and run the function manually I want to set the newly created sheet active and that's what is causing me trouble.
When I run the function (createnewsheet) from the script editor everything is fine but when I call it from the spreadsheet menu I get this error message : Specified sheet must be part of the spreadsheet because of the last line of code. AFAIK I'm not addressing a sheet outside my spreadsheet... Any idea what I'm doing wrong in this context ?
Here is a simplified code that shows the problem, a shared copy is available here
var ss = SpreadsheetApp.openById('0AnqSFd3iikE3dGVtYk5hWUZVUFNZMzAteE9DOUZwaVE');
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('master')
var logsheet = ss.getSheetByName('logger')
var FUS1=new Date().toString().substr(25,6)+":00";
function onOpen() {
var menuEntries = [ {name: "Manual test", functionName: "createnewsheet"},
];
sheet.addMenu("Utilities",menuEntries);
SpreadsheetApp.setActiveSheet(logsheet);// this is working fine
}
function createnewsheet(){
var sheetName = "Control on "+ Utilities.formatDate(new Date(), FUS1, "MMM-dd-yy")
try{
var newsheet = ss.insertSheet(sheetName,2);// creates the new sheet in 3rd position
}catch(error){
FUS1=new Date().toString().substr(25,6)+":00";
var newsheet = ss.insertSheet(sheetName+' - '+Utilities.formatDate(new Date(), FUS1, "HH:mm:ss"),2);// creates the new sheet with a different name if already there
}
newsheet.getRange(1,1).setValue(sheetName);
SpreadsheetApp.setActiveSheet(ss.getSheets()[2]);// should make 3 rd sheet active but works only when run from script editor
// SpreadsheetApp.setActiveSheet(newsheet);// should have same result but works only when run from script editor
}
I found a practical workaround to solve my use case : I use a different function from the menu (in wich I setActive() the sheet I want) and call the main function from this one.
When called from the trigger there is no use to set any active sheet so I removed this part from the main function.
It goes like this :
function manualTest(){ // from the ss menu
createnewsheet();
var sheet = SpreadsheetApp.getActiveSpreadsheet();
SpreadsheetApp.setActiveSheet(sheet.getSheets()[2]);// this works from the menu when ss is open
}
function createnewsheet(){ // from the trigger and from function manualTest()
var sheetName = "Control on "+ Utilities.formatDate(new Date(), FUS1, "MMM-dd-yy");
try{
var newsheet = ss.insertSheet(sheetName,2);
}catch(error){
FUS1=new Date().toString().substr(25,6)+":00";
var newsheet = ss.insertSheet(sheetName+' - '+Utilities.formatDate(new Date(), FUS1, "HH:mm:ss"),2);
}
newsheet.getRange(1,1).setValue(sheetName);
}
There is a catch here
SpreadsheetApp.setActiveSheet(ss.getSheets()[2]);
When you run it from script editor, It automatically gets the container spreadsheet and makes the said sheet active, but it is not the case when you run it from Custom Menu or triggers.
Modify this statement to:
ss.setActiveSheet(ss.getSheets()[2]);
ss.setActiveSheet(newsheet);
The best solution i have found is to reseed sheet. getActiveSpreadsheet() recognises the Spreadsheet currently in use so no need to openById once more, but
// SpreadsheetApp.setActiveSheet(ss.getSheets()[2]);
// SpreadsheetApp.setActiveSheet(newsheet);
sheet = SpreadsheetApp.getActiveSpreadsheet(); // reloads the active sheet (including changes to the Sheets array)
sheet.setActiveSheet(sheet.getSheets()[2]); // works as expected from editor and custom menu
I think this suggests that the original ss and sheet have cached the Spreadsheet to memory and their pointer isn't refreshed to reflect structural changes. I tried .flush() as a shortcut, but that seems a one way refresh to the sheet from the script, not the other way around.