How to solve #N/A value using Google Sheet =importxml function - google-apps-script

I have a sheet that want to import value value of Google search result using importxml function. Sometimes I get #N/A in cell A2.
I used while loop to keep trying to fetch the data and still I don't get data even if I wait for long time. Is it possible to setup a JavaScript timer so runs until I get value in cell A2 then timer stops and allow the rest of code to continue?
What should I do in order to avoid such cases and always get value on cell A2? Any Alternative solution?
var queryString = Math.random();
var cellFunction1 = '=IMPORTXML("' + SpreadsheetApp.getActiveSheet().getRange('C2').getValue() + '&randomNumber=' + queryString + '","'+ SpreadsheetApp.getActiveSheet().getRange('D2').getValue() + '")';
SpreadsheetApp.getActiveSheet().getRange('A2').setValue(cellFunction1);
var stop = 0;
while (SpreadsheetApp.getActiveSheet().getRange('A2').getValue() === "#N/A" && stop++<10) {
Utilities.sleep(5000);
var queryString3 = Math.random();
var cellFunction1 = '=IMPORTXML("' + SpreadsheetApp.getActiveSheet().getRange('C2').getValue() + '&randomNumber=' + queryString3 + '","'+ SpreadsheetApp.getActiveSheet().getRange('D2').getValue() + '")';
SpreadsheetApp.getActiveSheet().getRange('A2').setValue(cellFunction1);
}

I faced the same problem. I suspect it's because the code tries to .getValue from the cells before the cells' values are updated from IMPORTXML.
My workaround was to write two separate functions, one to force the IMPORTXML to refresh, and then the other to write the data into the sheet. Then I gave the IMPORTXML a trigger to run every 5 minutes, and then the write function with its own trigger.
You might want to try this:
function refresher() {
var ss = SpreadsheetApp.getActiveSheet();
var sheet = ss.getSheetById("input");
// Cell input!A2 holds the formula '=IMPORTXML(A1, XMLpath)'
//So now we give input!A1 a URL with a random ?number behind...
//...to force IMPORTXML to refresh
sheet.getRange(1, 1)
.setValue("http://www.urlWhereInfoResides.com/?" + Math.floor(Math.random() * 40));
}
^ Give refresher() a trigger to run every 5 minutes
function writer() {
var ss2 = SpreadsheetApp.getActiveSheet();
//assumes desired value sits in cell input!A2
var input = ss2.getSheetById("input")
.getRange(2, 1)
.getValue();
//adds input value into a new row in output sheet
var outputSheet = ss2.getSheetById("output");
var output = outputSheet.getRange(outputSheet.getLastRow() + 1, 1)
.setValue(input);
}
^ Give writer() a trigger to run every 5 minutes or whatever is needed

Related

Google Sheets Script Find Cell Errors In Range

I'm trying to find cells that have an error, using a Range in script. The Range consists of a single column AB of cells using Sparkline() getting data from GoogleFinance(), which quite often return Error Google Finance internal error., and display #N/A. Errors are showing:
However, the function is not returning anything when I try to getValues:
function portfolioRefreshSparklines(){
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Portfolio');
const msg = 'Refreshing...';
const err = '#N/A';
var range = sheet.getRange('Portfolio_Sparklines');
var col = range.getColumn();
var rowStart = range.getRow();
Logger.log('col: ' + col + '; rowRange: ' + rowStart);
var data = range.getValues();
for ( i=0; i<data.length; i++ ) {
// this is NOT returning the `#N/A` error (`Google Finance internal error.`)
var rv = data[i][0];
Logger.log('i: ' + i + ' rv: '+ rv)
// If an error is found, set the cell's formula to the msg, then back to the original formula.
// Think I have to reference the cell directly to do the setFormula() switch, not within the data array?
if ( rv.includes(err) ){
var row = rowStart + i;
var cell = sheet.getRange(row, col);
Logger.log('cell: ' + cell.getA1Notation() );
rv = cell.getFormula();
cell.setFormula(msg);
cell.setFormula(rv);
}
}
SpreadsheetApp.flush();
}
I've searched through the Range Class, tried to use function getDisplayValues(), but haven't found anything that returns a cell error.
Any suggestions pls?
From the question
However, the function is not returning anything when I try to getValues:
Google Finance is blocked in Google Apps Script. See Reading the values of cells that summarize Google Finance data results via Apps Script
P.S.
It doesn't make sense to include SpreadsheetApp.flush() as the last function statement. It should be used when you need to force that the changes made are applied before the function ends because you will be reading something that was changed by the script to be used in later part of it.
The Best Practices discourages the use of Google Apps Script classes (in this case var cell = sheet.getRange(row, col);) in loops because they are slow.

How to increment simple formula by 1 in Google Apps Script?

Disclaimer: I'm a Google Apps Script newbie.
I'm trying to create a timesheet in Google Sheets that lets a user clock in & clock out to log hours on a given project. I've borrowed code from a YouTube video on the general structure of setting the whole thing up.
Here's what the blank time sheet looks like. It's pretty basic:
I've created a user button (off to the right) where the user presses "Start" and cell A2 will input a timestamp. Then the user can press an "End" button, and a second timestamp, this time in B2, will appear, along with a simple calculation in C2 that measures the delta in the two timestamps, thus giving a duration of time spent on a given task or project. Here's what it looks like:
When the user needs to press "Start" again, a new timestamp appears in cell A3, and so on so forth, along with a new delta calculation for each new row.
Problem: I'm unable to get the simple delta calculation in column C to increment down each new rows so that the setFormula function doesn't contain hardcoded references to cells A2 & B2. See below code for what I have so far:
function setValue(cellName, value) {
SpreadsheetApp.getActiveSpreadsheet().getRange(cellName).setValue(value);
}
function getValue(cellName) {
return SpreadsheetApp.getActiveSpreadsheet().getRange(cellName).getValue();
}
function getNextRow() {
return SpreadsheetApp.getActiveSpreadsheet().getLastRow() + 1;
}
function addStartRecord (a) {
var row = getNextRow();
setValue('A' + row, a);
}
function addEndRecord (b, c) {
var row = getNextRow()-1;
setValue('B' + row, b);
setValue('C' + row, c);
}
function punchIn() {
addSRecord(new Date());
}
function punchOut() {
addERecord(new Date(), '=B2-A2');
}
The problem is with the punchOut() function there at the bottom. Any idea on the best way to increment this delta calculator down each new row?
Note: I saw a pretty good answer to a similar question here, but the code is throwing an error in the script editor after the line containing data[i] = ['=A' + i+1.toString() + ' + 1 ' ]. Also, I don't want to set a definitive last row for the delta calculation (such as 20 in this example). I'd want the user to be able to record as many new start/end times for a project as they'd want.
Edit: Here's a link to the timesheet so you can test the code.
Try modifying your punchOut method like this:
function punchOut() {
var ss = SpreadsheetApp.getActiveSheet();
var row = ss.getLastRow();
addEndRecord(new Date(), '=B' + row + '-A' + row);
}
I tested it in the sheet and it worked well.
setFormula() - this enables you to describe the formula to be inserted into column C.
The following is two simple functions that handle "Punch in" and "Punch Out" (with its calculation).
function so5695101401in() {
// punchin
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lR = sheet.getLastRow();
// Logger.log("DEBUG: the last row is "+lR);
var punchinRange = sheet.getRange(lR+1, 1);
// Logger.log("DEBUG: the punchinRange = "+punchinRange.getA1Notation());
punchinRange.setValue(new Date());
}
function so5695101401out() {
// punchout
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lR = sheet.getLastRow();
//Logger.log("DEBUG: the last row is "+lR);
var punchoutRange = sheet.getRange(lR, 2);
// Logger.log("DEBUG: the punchoutRange = "+punchoutRange.getA1Notation());
punchoutRange.setValue(new Date());
var timeElapsed = sheet.getRange(lR, 3).setNumberFormat("hh:mm:ss");
timeElapsed.setFormula("=B2-A2");
}
setFormula
I use a workaround for this problem, via app script copy the cell with the formula to de new row or range!.
for you problem:
var formula1 = sheetDatos.getRange(lastRow, 3); //get the formula
var copyRange = sheetDatos.getRange(lastRow+1, 3);
formula1.copyTo(copyRange);
for me is more easy in this way, try to do in sheet to understand how this work.
you need a initial formula to go in this way ;)

How to automatically copy/paste data using App Script in Google Sheets

I'm trying to write some code using App Scripts that will (via a daily trigger), copy/paste data from the cells F13:G13 to the first empty cell in column I. Here is my code:
function TrackCurrentValues()
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheets()[0];
var lastRow = getLastRowInColumn(sheet, 'I:I');
// Logger.log('I' + parseInt(lastRow + 1));
var pasteRange = sheet.getRange('I' + parseInt(lastRow + 1) );
pasteRange.activate();
// now that we have the first empty cell in column I, paste the values we found from cells F13:G13
spreadsheet.getRange('F13:G13').copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
};
function getLastRowInColumn(sheetObj, range) {
return sheetObj.getRange(range)
.getValues().filter (String).length + 1
}
When it runs, what happens is that the data is copied from the proper location, but it's always pasted to cell A1. Moreover, the number pasted appears is prefixed with the British pound sterling character (£).
What could be wrong? It (usually) works if I run it manually. The main thing is that it doesn't find the empty cell in I:I.
This code should do what you're looking for:
function TrackCurrentValues(){
var sheet = SpreadsheetApp.getActive().getSheets()[0];
var lastRow = getLastRowInColumn(sheet, 'I:I');
var pasteRange = sheet.getRange(lastRow + 1,9,1,2);
var copyRange = sheet.getRange(13,6,1,2)
pasteRange.setValues(copyRange.getValues())
};
function getLastRowInColumn(sheetObj, range) {
return sheetObj.getRange(range).getValues().filter(String).length
}
On your £ chracter question, that range is likely formatted to display that currency symbol. To update this, select the range, go to the toolbar and select Format > Number > Specify the format you would like
Additional Thoughts:
i) You are adding one to lastRow variable twice (once in getLastRowInCOlumn function and again in pasteRange definition)
ii) I would reocmmend not using "active ranges" to store a location, instead store that range in a variable
iii) It seems your copy range was 2 columns wide but your pasteRange was only 1 column wide

Google Form asks for duration, how to calculate with this in Google App Script

I use stackoverflow on a regular basis to find answers to my problems, but I cannot seem to solve this one. So here is my first question:
I have a Google form that (amongst other things) asks for the duration of a job. I want the google spreadsheet to contain the form-answers, and add some columns. In this case, I want to add the cost of the job, using an hour rate of 126,-
But I keep running into problems in calculating with the duration: my script either tells me its a text (if I use getDisplayValue in retrieving the data), or it gives me a #NUM error in the spreadsheet itself.
Can anyone pinpoint me towards a solution how to retrieve the hours and the minutes from the form-field (time as duration), so I can do some basic math with it in the script?
I've setup a small form and connected spreadsheet showing my problems. The example form only asks for the duration, and places this in the spreadsheet in column 2. In the spreadsheet I've setup a script that runs on form submit an I try to explain all steps I do. The script should take the form input, convert it to hours (as a num-field) and multiply that with the PricePerHour. The result should be placed in column 3 on the same row of the form submit.
This is my script so far:
// CALCULATE COST OF JOB
function calculatePriceDuration(e) {
// get source data
var sourceSheet = SpreadsheetApp.getActiveSheet(); // connect to source sheet
var sourceRow = sourceSheet.getActiveRange().getRow(); // connect to event row (form submit)
// get destination data
var destinationSheet = sourceSheet; // connect to destination sheet
var destinationRow = sourceRow; // connect to destination row
var destinationColID = 3; // set column number of value to paste
// set variables
var colID_FormDuration = 2; // set column number where the form places the duration
var formDuration = sourceSheet.getRange(sourceRow, colID_FormDuration).getDisplayValue(); // get value for duration
// set price per hour
var PricePerHour = 126;
// calculate job price
var PriceForJob = formDuration * PricePerHour;
// set destination cell
destinationSheet.getRange(destinationRow,destinationColID).setValue(PriceForJob); // paste value in the 3rd column of the same row of form submit
}
the spreadsheet itself can be found here:
the form can be found here:
Any help is much appreciated!
Kind regards,
Rob
try this:
function calcTimeDifference(Start,End)
{
if(Start && End)
{
var second=1000;
var minute=60*second;
var hour=minute*60;
var day=hour*24;
var t1=new Date(Start).valueOf();
var t2=new Date(End).valueOf();
var d=t2-t1;
var days=Math.floor(d/day);
var hours=Math.floor(d%day/hour);
var minutes=Math.floor(d%day%hour/minute);
var seconds=Math.floor(d%day%hour%minute/second);
return 'dd:hh:mm:ss\n' + days + ':' + hours + ':' + minutes + ':' + seconds;
}
else
{
return 'Invalid Inputs';
}
}
Thanks chirag90 for editing my question and tehhowch and cooper for providing me an answer. Unfortunately, my Javascript skills are too poor to really understand your answer.
Fortunately, I found this post on StackOverflow that is a perfect solution to my problem:
function getValueAsSeconds(range) {
var value = range.getValue();
// Get the date value in the spreadsheet's timezone.
var spreadsheetTimezone = range.getSheet().getParent().getSpreadsheetTimeZone();
var dateString = Utilities.formatDate(value, spreadsheetTimezone,
'EEE, d MMM yyyy HH:mm:ss');
var date = new Date(dateString);
// Initialize the date of the epoch.
var epoch = new Date('Dec 30, 1899 00:00:00');
// Calculate the number of milliseconds between the epoch and the value.
var diff = date.getTime() - epoch.getTime();
// Convert the milliseconds to seconds and return.
return Math.round(diff / 1000);
}
function getValueAsMinutes(range) {
return getValueAsSeconds(range) / 60;
}
function getValueAsHours(range) {
return getValueAsMinutes(range) / 60;
}
Thanks again for your efforts, and perhaps you can direct me to a (online / Netherlands based) course where I can learn to use Javascript properly.

Automate ImportHTML to get twitter followers for many cells

I have a spreadsheet with a cell range of twitter profile urls. I want to automate getting their follower count. This is what I came up with.
function getFollowers1() {
var queryString = Math.random();
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var cellRange = sheet.getRange("L6:L25").getValues();
var startCell = sheet.getRange("K6");
for (var i = 0; i < cellRange.length; i++) {
var value = '=substitute(query(IMPORTHTML("' + cellRange[i] + '?' + queryString + '",' +'"table", 3), "select Col3"), char(10)&"Followers","")';
startCell.setValue(value);
startCell = startCell.offset(1,0);
}
var range = sheet.getRange("K6:K25");
range.copyTo(range, {contentsOnly: true});
}
I have 4 separate functions getFollowers1, getFollowers2, getFollowers3, getFollowers4 the difference between them is the range of cells. I have about 80 cells and doing it all in one function would always have some cells hang at Loading.... I have these 4 functions set to triggers at different times of the day. However, sometimes the function works and sometimes all cells in the function just hang at Loading....
I don't know the reason for this, throttling maybe? I'm wondering how to change things to accomplish what I want in a more efficient and reliable way. To get daily updates of twitter followers.