Google Sheet Script Not Reliably Running - google-apps-script

I've gotten to the point where I have a pretty solid script that:
1) Takes data from a new data tab and posts it to an existing data tab
2) Clears data from the new data tab
3) Deletes duplicates from the existing data tab
When I originally put this script in, it worked great. But after running it a few times, it seems to stall at the de-duping portion of the script. So when I run it, the first two scripts run, but not the third. If I select the de-dupe script to run on it's own, it works just fine.
Has anyone else seen this issue? Is there any way to tweak the script to have a more reliable run so that it will always process all three scripts?
Not sure how to optimize from there.
function Run(){
insert();
clear1();
removeDuplicates();
}
function insert() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheetByName('Candidate Refresh'); // change here
var des = ss.getSheetByName('Candidate Listing'); // change here
var sv = source
.getDataRange()
.getValues();
sv.shift();
des.insertRowsAfter(1, sv.length);
des.getRange(2, 1, sv.length, source.getLastColumn()).setValues(sv);
}
function clear1() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Candidate Refresh');
sheet.getRange('A2:K100').clearContent()
}
function removeDuplicates() {
var sheet=SpreadsheetApp.getActiveSheet();
var rows=sheet.getLastRow();
var firstColumn=sheet.getRange(1, 2, rows, 1).getValues();
firstColumn = firstColumn.map(function(e){return e[0]})
var uA=[];
for (var i=rows;i>0;i--) {
if (uA.indexOf(firstColumn[i-1])!=-1) {
sheet.deleteRow(i);
}else{
uA.push(firstColumn[i-1]);
}
}
}
All three scripts should fire when the Run script is played.

Related

Google Scripts - Can't Find Destination Sheet After Form Creation

My script creates a Google Form programmatically and sets the destination to the current Spreadsheet:
var sheetId = SpreadsheetApp.getActiveSpreadsheet().getId();
var form = FormApp.create(acctName).setTitle(acctName).setDestination(FormApp.DestinationType.SPREADSHEET, sheetId);
// several form items added here
Next, I'd like to rename the destination Sheet and make a few formatting edits. When I do, however, the sheet doesn't seem to exist. For example, getSheets() does not contain the new Sheet! (And yes, ideally I'd like to open the sheet by ID; see function below which also doesn't work.)
var sheets = SpreadsheetApp.openById(form.getDestinationId()).getSheets();
SpreadsheetApp.setActiveSheet(sheets[0]);
The above code opens what I would consider index 1, not index 0, because the sheet isn't indexed. I even tried creating a few second delay (hoping that it was just a matter of time to allow the sync to happen between the client and server) but without any success.
I have also tried something like the following, as suggested elsewhere here on Stack Overflow, but the destination Sheet doesn't come up in getSheets() even when called through a separate function:
function getFormDestinationSheetId(form) {
var destinationId = form.getDestinationId();
if(destinationId) {
var spreadsheet = SpreadsheetApp.openById(destinationId);
spreadsheet.getSheets().forEach(
function(sheet) {
var sheetFormUrl = sheet.getFormUrl();
if(sheetFormUrl) {
var sheetForm = FormApp.openByUrl(sheetFormUrl);
if(sheetForm.getId() == form.getId()) {
return sheet.getSheetId();
}
}
}
);
}
return null;
}
I haven't been able to find anyone have a similar problem on the webs. Any advice would be appreciated. Thanks!
Welcome to Stack!
I assume your script is bound to a sheet? Depending on how you're calling the script you may not see the new sheets because of browser caching.
function myFunction() {
var sheet = SpreadsheetApp.getActive();
var sheetId = sheet.getId();
var form = FormApp.create('acctName').setTitle('acctName').setDestination(FormApp.DestinationType.SPREADSHEET, sheetId);
var ssSheets = sheet.getSheets();
var respSheet = ssSheets[0]
respSheet.getRange('A1').setBackground('RED');
respSheet.getRange('C1').setFormula('=COUNTA($C$2:$C)');
respSheet.setColumnWidth(2, 100);
SpreadsheetApp.flush();
}

Share drive folder to list of user from google spreadsheet

I'm trying to write a script to take a folder from my drive and share it with a list of users.
The script is scheduled to run on a sheet which collects answers from a google form and I set the trigger to run the script at every new submission of the form.
First Attempt. I think there is something wrong with how I create the array containing the list of emails, but I couldn't figure out how to fix it.
function driveShare_array() {
var getEmails =[];
var sheet= SpreadsheetApp.getActiveSheet();
var getEmails = sheet.getRange('emailstoshare').getValues(); // emailtoshare is a named range including only the one column where the sheet collects emails from the form submission
var folder = DriveApp.getFolderById('xxxx'); // added the folder ID in here
for ( i in getEmails) {
folder.addViewer(getEmails[i])
}
}
Second attempt to try to control how the array "getEmails" gets shaped by adding elements (push) one by one
function driveShare_push(){
var getEmails =[];
var sheet= SpreadsheetApp.getActiveSheet();
var range = sheet.getRange('emailstoshare'); // emailtoshare is a named range including only the one column where the sheet collects emails from the form submission
for (var i=0;i<range.getNumRows();i++){
getEmails.push(range.offset(i,0,1,1).getValue());
}
var folder = DriveApp.getFolderById('xxxx'); // added the folder ID in here
// for ( var j=0;j<range.getNumColumns();j++) {
for (var j in getEmails){
folder.addViewer(getEmails[j]) ;
}
}
Both versions (and other variations I have tested) give errors. Could someone help me understand what I'm doing wrong and how to fix it?
Thanks!!
It might be possible that the email address may not have a Google account and thus the folder could not be shared with them. You might consider adding a try/catch block to ignore such errors.
function driveShare_array() {
var getEmails =[];
var sheet= SpreadsheetApp.getActiveSheet();
var folder = DriveApp.getFolderById('xxxx');
var getEmails = sheet.getRange('emailstoshare').getValues();
getEmails.forEach(function(email) {
if (/\S+\.\S+/.test(email)) {
try {
folder.addViewer(email)
} catch (f) {
Logger.log("Cannot share with " + email);
}
}
})
}
If your triggering the function with onFormSubmit trigger then you can do something like this.
function driveShare_array(e) {
var folder=DriveApp.getFolderById('xxxx');
folder.addViewer(e.namedValues['Email Address']);
}
You will probably want to store them on some spreadsheet and retrieve them to make sure you haven’t already added it to the list. You will need to create an onFormSubmit trigger for the function in script editor Edit/Project Triggers.
Used logger.log to understand the structure of the data and the code was creating a matrix instead of an array.
Here the corrected code for reference:
function driveShare_push(){
var getEmails =[];
var sheet= SpreadsheetApp.getActiveSheet();
var range = sheet.getRange('emailstoshare');
for (var i=1;i<range.getNumRows();i++){
if(range.offset(i, 0, 1, 1).getValue().indexOf("gmail.com") > -1){
getEmails.push(range.offset(i,0,1,1).getValue());
}
}
var folder = DriveApp.getFolderById('xxxx');
folder.addViewer(getEmails[getEmails.length-1]) ;
}

Google Script to remove duplicates from top to bottom

I have a script which combines three scripts to do the following:
1) Insert rows from one tab to the top of another tab
2) Remove duplicates from the tab in which the data was just added
3) Clear out the old tab from which the data was just ported over from
For the De-dupe script, it deletes rows starting at the bottom and then goes up. So I'm having established and existing data deleted. What I need it to do is start at the top and go down. So if new row records ported over from the first script are found to be a duplicate, it should delete those instead.
How can I get the de-dupe script to essentially process the opposite way?
I did find reverse logic with the below link, but I can't find a way to make it work with my script and keep getting errors. I'm also not sure if this would be the best methodology to fit in with my overall script.
Link: Removing Duplicate Rows in a google Spreadsheet from the end row
function Run(){
insert();
removeDuplicates();
clear1();
}
function insert() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheetByName('Candidate Refresh'); // change here
var des = ss.getSheetByName('Candidate Listing'); // change here
var sv = source
.getDataRange()
.getValues();
sv.shift();
des.insertRowsAfter(1, sv.length);
des.getRange(2, 1, sv.length, source.getLastColumn()).setValues(sv);
}
//Code in Question Start
function removeDuplicates() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getLastRow();
var firstColumn = sheet.getRange(1, 2, rows, 1).getValues();
firstColumn = firstColumn.map(function (e) {return e[0]})
for (var i = rows; i >0; i--) {
if (firstColumn.indexOf(firstColumn[i-1]) != i-1) {
sheet.deleteRow(i);
}
}
}
//Code in Question End
function clear1() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Candidate Refresh');
sheet.getRange('A2:K100').clearContent()
}
If new rows at the top of the sheet are found to be a duplicate, delete the new rows at the top.
try this:
function removeDuplicates() {
var sheet=SpreadsheetApp.getActiveSheet();
var rows=sheet.getLastRow();
var firstColumn=sheet.getRange(1, 2, rows, 1).getValues();
firstColumn = firstColumn.map(function(e){return e[0]})
var uA=[];
for (var i=rows;i>0;i--) {
if (uA.indexOf(firstColumn[i-1])!=-1) {
sheet.deleteRow(i);
}else{
uA.push(firstColumn[i-1]);
}
}
}

Combining two scripts into a google sheets addon

EDIT thanks for the assistance, I've updated the script as follows, however it still will not create a submenu from the Add-on menu.
// Hide empty rows in PRINT sheet
function onOpen(e) {
var menu = SpreadsheetApp.getUi().createMenu('Print Ready');
menu.addItem('Print Ready', 'PRINTREADY');
menu.addToUi();
}
function PRINTREADY(){
var shname='PRINT';//<<<<<<<<<<<<<<<change to appropriate sheet name
var rw1=4, rw2=258;
var s, nrws, data, i;
nrws=rw2-rw1+1;
s=SpreadsheetApp.getActiveSpreadsheet().getSheetByName(shname);
data=s.getRange(rw1, 1, nrws, s.getLastColumn()).getValues();
s.showRows(rw1, nrws);
for (i in data) if (data[i].every(function(x) {return x=='';})) s.hideRows(1*i+rw1);
}
// Sort PANTRY LIST A-Z
var sh = SpreadsheetApp.getActiveSheet();
if( sh.getName() == "PANTRY LIST" ) {
var editedCell = sh.getActiveRange().getColumnIndex();
if(editedCell == 1) {
var range = sh.getRange("PANTRY LIST!A3:Z");
range.sort({column: 1});
}
}
I'm trying to deploy a sheets addon for some restaurant menu costing spreadsheets I have written. For some of the functionality I needed to include scripts, by themselves they are fairly straightforward:
Script 1: HideEmptyRows - this script hides empty rows to make the recipe ready to print, it should only run on a recipe document with a sheet name PRINT.
Script 2: SortList - this script simply sorts the sheet A-Z onOpen and every 15 minutes, it only runs on the Pantry List document with a sheet name PANTRY LIST.
They run fine individually as scripts, but when I combine them into a sheets addon Script 1 doesn't generate a menu, and I get Range Not Found errors from Script 2.
Script 1
function onOpen(e) {
var menu = SpreadsheetApp.getUi().createMenu('Print Ready')
menu.addItem('Make my recipe ready to print', 'PRINTREADY')
.addToUi();
}
function PRINTREADY(){
var shname='PRINT';//<<<<<<<<<<<<<<<change to appropriate sheet name
var rw1=4, rw2=258;
var s, nrws, data, i;
nrws=rw2-rw1+1;
s=SpreadsheetApp.getActiveSpreadsheet().getSheetByName(shname);
data=s.getRange(rw1, 1, nrws, s.getLastColumn()).getValues();
s.showRows(rw1, nrws);
for (i in data) if (data[i].every(function(x) {return x=='';})) s.hideRows(1*i+rw1);
}
Script 2
function onOpen(){
var sh = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var editedCell = sh.getActiveRange().getColumnIndex();
if(editedCell == 1) {
var range = sh.getRange("PANTRY LIST!A3:Z");
range.sort({column: 1});
}
}
Does anyone know how to combine them into one script that I can deploy as an addon?
Thanks

Trigger importHTML in Google Apps Script

I'd like to know whether it is possible to write a Google Apps Script that will trigger daily importhtml in Google spreadsheets. For example let's assume I want to import updates of following table everyday at 1 pm.
=IMPORTHTML("https://en.wikipedia.org/wiki/Lubbock,_Texas","table",5)
I need this automatic checking for table updates even when my computer is offline.
Thank you
Yes, it is possible to write a script to make a copy of the table every day.
In short, you will have to make a copy of the imported table and paste it into a new Sheet. ImportHTML a gets updated every time you open/access the spreadsheet. So only way to store table is to make a copy and paste it into a new sheet.
Here is code that does just that:
function getImportData()
{
var ss = SpreadsheetApp.getActive()
var sheet =ss.getSheetByName("ImportSheet")
if (sheet == null)
{
sheet = ss.insertSheet("ImportSheet")
sheet.getRange(1,1).setValue( "=IMPORTHTML(\"https://en.wikipedia.org/wiki/Lubbock,_Texas\",\"table\",5)")
}
var dataRange = sheet.getDataRange()
//waitforLoading waits for the function importHTML to finish loading the table, only a problem if table takes a while to load
//for more discussion on this visit : http://stackoverflow.com/questions/12711072/how-to-pause-app-scripts-until-spreadsheet-finishes-calculation
var wait = waitForLoading(dataRange,sheet, 10)
var logSheet = ss.getSheetByName("ImportLogSheet")
if (logSheet == null)
{
logSheet = ss.insertSheet("ImportLogSheet")
}
var timeStampRow = []
timeStampRow[0] = new Date()
if(wait)
{
logSheet.appendRow(timeStampRow)
var lastRow = logSheet.getLastRow() + 1
var destinationRange = logSheet.getRange(lastRow,1)
dataRange.copyTo(destinationRange, {contentsOnly:true})
}
else {
timeStampRow[1] = "Scripted timeout waiting for the table to Load "
logSheet.appendRow(timeStampRow)
}
}
function waitForLoading(dataRange, sheet, maxWaitTimeInSec)
{
// This function is only required if it takes a while for the importHTML to load your data!
// if not you can skip this function
// Function looks to see if the value of the
for(i = 0; i< maxWaitTimeInSec ; i++)
{
var value = dataRange.getCell(1,1).getValue()
if(value.search("Loading") !== -1) {
Utilities.sleep(1000);
dataRange = sheet.getDataRange()
} else {
return true
}
}
return false
}
Edit: Forgot to mention about setting up triggers. You can setup any function to be triggered using time-driven triggers. Details on how to set it up is given here: https://developers.google.com/apps-script/guides/triggers/installable#managing_triggers_manually