error - "Method Range.getValue is heavily used by the script" - google-apps-script

I posted this question previously but did not tag it properly (and hence why I likely did not get an answer) so I thought I would give it another shot as I haven't been able to find the answer in the meantime.
The below script is giving me the message in the title. I have another function which is using the same getValue method but it is running fine. What can I change in my script to avoid this issue?
function trashOldFiles() {
var ffile = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CtrlSht").getRange("B3:B3").getValue();
var files = DriveApp.getFilesByName(ffile);
while (files.hasNext()) {
var file = files.next();
var latestfile = DriveApp.getFileById(listLatestFile());
if(file.getId() ==! latestfile){
file.setTrashed(true);
}
}
};

Is it an error or an execution hint(the light bulb in the menu)?
are you using that method on other part of your code? probably in listLatestFile()?
I got the same execution hint by calling getRange().getValue() in listLatestFile() (using a loop)
and the hint always mentioned that the problem was when calling
var ffile = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CtrlSht").getRange("B3:B3").getValue();
in the function trashOldFiles() even when the actual problem was in other function.
Check if you are calling it in other place in your code, probably inside a loop.

OK, so Gerardo's comment about loops started to get me thinking again. I checked some other posts about how to re-use a variable and decided to put the listLatestFile() value in my spreadsheet -
var id = result[0][1];
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CtrlSht").getRange("B5:B5").setValue(id);
//Logger.log(id);
return id;
and then retrieved the latest file ID from the spreadsheet to use as a comparison value for the trashOldFiles() function which worked a treat.
function trashOldFiles() {
var tfile = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CtrlSht").getRange("B3:B3").getValue();
var tfiles = DriveApp.getFilesByName(tfile);
var lfile = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CtrlSht").getRange("B5:B5").getValue();
while (tfiles.hasNext()) {
var tfile = tfiles.next();
if(tfile.getId() !== lfile){
tfile.setTrashed(true);
}
}
};
Not sure if that approach was best practice but it did work for me. If anyone has suggestions for achieving this in a more elegant way, I'm all ears.

Related

Using ReplaceWith to find & replace text in a google sheet

This may be a dumb question but I was unable to find an answer on stackoverflow, youtube, or the developers (google) site either for this issue.
I'm trying to use createTextFinder to find a certain word, and replace it with a new word. Ideally I'd like to replace the 2nd instance of the word instead of the first, however if that isn't possible that's OK. I'm also trying to ensure that my function can find these words dynamically instead of resting on defined ranges such as A1:D2 as an example.
So for our example below trying to change the 2nd instance of Apple to Pie.
What I find really bizarre, is that replaceWith doesn't seem to work, but replaceAllWith did work.
Problem:
Unable to have replaceWith work with the createTextFinder method. Receiving the error
"Exception: Service error: Spreadsheets"
Current Sheet:
Expected Outcome:
Troubleshooting I've tried:
Attempted to use startFrom and use a range prior to the 2nd instance of Apple but this didn't seem to work
Attempted to make another textfinder in the same function, however I don't think you're allowed to? I did this in order to do my prior attempt
changed replaceWith to replaceAllWith which worked, but then tried to have it find "Pie" in the same function and change the first instance back to "Apple" but this didn't work
Tried to use the findNext() feature as well, but this unfortunately did not work, and I'm unsure how to use this with the replaceWith method.
Common errors occuring during these attempts is the program stating I do not have a proper function or that the parameters don't match the method signature
Code:
function findText() {
const workSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('sheet1');//I have a few tabs and would like to call to them directly
const tf = workSheet.createTextFinder('Apple');
tf.matchEntireCell(true).matchCase(false);//finds text "Apple" exactly
tf.replaceWith('Pie');
}//end of function findText
Resources:
Google Developers on replaceWith
function replacesecondinstanceofword( word = "Apple",replacement = "Peach") {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet1');
const tf = sh.createTextFinder(word).matchEntireCell(true).findAll();
tf.forEach((f,i) => {
if(i == 1) {
sh.getRange(f.getRow(),f.getColumn()).setValue(replacement)
}
});
}
Learn More
Based on Cooper's solution:
function replace_second(word = "Apple", replacement = "Peach") {
try {
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('sheet1')
.createTextFinder(word).matchEntireCell(true).findAll()[1]
.setValue(replacement);
} catch(e) {}
}
You need to call .findNext() at least once to replace the first matched cell.
function findText() {
const workSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(
'Sheet1'
);
const tf = workSheet.createTextFinder('Apple');
tf.matchEntireCell(true).matchCase(true); //finds text "Apple" exactly
let match = -1;
while (++match < 2) tf.findNext();//for second match
tf.replaceWith('Pie');
}

Error: Could not locate target object while calling method getPageElementRange on object with id 45

A function that was working is now showing the following error:
Logger: error=Error: Could not locate target object while calling method getPageElementRange on object with id 45.
Wondering if this is an intermittent bug that has arisen in a recent release of app script as my code has not changed?
Code that does not work is:
var presentation = SlidesApp.getActivePresentation();
var slide = presentation.getSelection().getCurrentPage().asSlide();
var arrPageElementsSelected = presentation.getSelection().getPageElementRange().getPageElements();
If I break this last line down into three lines, it still does not work:
var presentation = SlidesApp.getActivePresentation();
var slide = presentation.getSelection().getCurrentPage().asSlide();
var selection = presentation.getSelection();
var rangePageElements = selection.getPageElementRange();
var arrPageElementsSelected = rangePageElements.getPageElements();
Again, this code used to work up until the last few days. I am thinking that something has become broken in the Google App Script engine.
I think presentation isn't referring to an existing presentation anymore. I believe it's because you're storing the result of getActivePresentation() in presentation, and that the reference is removed by the app or something like that. It might be a kind of garbage collection that removes variables based on time since creation or total memory used.
I encountered the same error in google sheets where I tried to manage several sheets from apps script and I had defined a global variable to the function in a second script like this:
var getSheetByName = SpreadsheetApp.getActive().getSheetByName;
Then I boiled down the problem to this:
function target_error_test()
{
var getSheetByName = SpreadsheetApp.getActive().getSheetByName;
var i = 0
for (var i = 0; true; i++)
{
Logger.log(`try nr ${i}...`)
var input_sheet = getSheetByName("sheet2")
}
}
which produced this output (still with the same error)
So it executes without fail 9 times and then sudden crash - probably due to garbage collection or something like that.
Solution
Only use these variables for a short amount of time. One can store global variables with
PropertiesService.getScriptProperties().setProperty(key, value)
PropertiesService.getScriptProperties().getProperty(key)
And functions are not removed, so it's better to use a function in my case:
function getSheetByName(sheetName)
{
return SpreadsheetApp.getActive().getSheetByName(sheetName);
}
I think I have now solved this, with the following code working:
var presentation = SlidesApp.getActivePresentation();
var arrPageElementsSelected = presentation.getSelection().getPageElementRange().getPageElements();
var slide = presentation.getSelection().getCurrentPage().asSlide();
var arrPageElementsAll = slide.getPageElements();
It would appear that the issue was due to my earlier code defining presentation and slide then calling getPageElementRange() afterwards.
The solution was to first define presentation and call getPageElementRange() before then defining slide and using asSlide() to do that.
The issue I think is to do with asSlide() used to define slide dropping the reference to presentation, which was needed to call getPageElementRange().

Return Collection of Google Drive Files Shared With Specific User

I'm trying to get a collection of files where user (let's use billyTheUser#gmail.com) is an editor.
I know this can be accomplished almost instantly on the front-end of google drive by doing a search for to:billyTheUser#gmail.com in the drive search bar.
I presume this is something that can be done in Google App Scripts, but maybe I'm wrong. I figured DriveApp.searchFiles would work, but I'm having trouble structuring the proper string syntax. I've looked at the Google SDK Documentation and am guessing I am doing something wrong with the usage of the in matched to the user string search? Below is the approaches I've taken, however if there's a different method to accomplishing the collection of files by user, I'd be happy to change my approach.
var files = DriveApp.searchFiles(
//I would expect this to work, but this doesn't return values
'writers in "billyTheUser#gmail.com"');
//Tried these just experimenting. None return values
'writers in "to:billyTheUser#gmail.com"');
'writers in "to:billyTheUser#gmail.com"');
'to:billyTheUser#gmail.com');
// this is just a test to confirm that some string searches successfully work
'modifiedDate > "2013-02-28" and title contains "untitled"');
Try flipping the operands within the in clause to read as:
var files = DriveApp.searchFiles('"billyTheUser#gmail.com" in writers');
Thanks #theAddonDepot! To illustrate specifically how the accepted answer is useful, I used it to assist in building a spreadsheet to help control files shared with various users. The source code for the full procedure is at the bottom of this post. It can be used directly within this this google sheet if you copy it.
The final result works rather nicely for listing out files by rows and properties in columns (i.e. last modified, security, descriptions... etc.).
The ultimate purpose is to be able to update large number of files without impacting other users. (use case scenario for sudden need to immediately revoke security... layoffs, acquisition, divorce, etc).
//code for looking up files by security
//Posted on stackoverlow here: https://stackoverflow.com/questions/62940196/return-collection-of-google-drive-files-shared-with-specific-user
//sample google File here: https://docs.google.com/spreadsheets/d/1jSl_ZxRVAIh9ULQLy-2e1FdnQpT6207JjFoDq60kj6Q/edit?usp=sharing
const ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("FileList");
const clearRange = true;
//const clearRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("ClearRange").getValue();
//if you have the named range setup.
function runReport() {
//var theEmail= SpreadsheetApp.getActiveSpreadsheet().getRangeByName("emailFromExcel").getValue();
//or
var theEmail = 'billyTheUser#gmail.com';
findFilesByUser(theEmail);
}
function findFilesByUser(theUserEmail) {
if(clearRange){
ss.getDataRange().offset(1,0).deleteCells(SpreadsheetApp.Dimension.ROWS)
}
var someFiles = DriveApp.searchFiles('"' + theUserEmail + '" in writers');
var aListOfFiles = []
while(someFiles.hasNext()){
var aFile = someFiles.next();
aListOfFiles.push([aFile.getId()
,aFile.getName()
,aFile.getDescription()
,aFile.getSharingAccess()
,aFile.getSharingPermission()
,listEmails(aFile.getEditors())
,listEmails(aFile.getViewers())
,aFile.getMimeType().replace('application/','').replace('vnd.google-apps.','')
,aFile.getDateCreated()
,aFile.getLastUpdated()
,aFile.getSize()
,aFile.getUrl()
,aFile.getDownloadUrl()
])
}
if(aListOfFiles.length==0){
aListOfFiles.push("no files for " + theUserEmail);
}
ss.getRange(ss.getDataRange().getLastRow()+1,1, aListOfFiles.length, aListOfFiles[0].length).setValues(aListOfFiles);
}
function listEmails(thePeople){
var aList = thePeople;
for (var i = 0; i < aList.length;i++){
aList[i] = aList[i].getEmail();
}
return aList.toString();
}

Inserting sleep inside a particular function in Google Sheets Script

I'm trying to pull data out of an API from a third party and inserting into Google Sheets. However this third party only allows 3 requests per minute, so I'm trying to use a Utilities.sleep feature inside the function I'm building for this request.
My sheet looks like this:
It has the two inputs necessary for the function I'm using (this below):
function GET_DETAILS_RECEITA(CNPJ,sleep_seconds) {
Utilities.sleep(sleep_seconds*1000);
var fields = 'nome,fantasia,email,telefone';
var baseUrl = 'https://www.receitaws.com.br/v1/cnpj/';
var queryUrl = baseUrl + CNPJ;
if (CNPJ == '') {
return 'Give me CNPJ...';
}
var response = UrlFetchApp.fetch(queryUrl);
var json = response.getContentText();
var place = JSON.parse(json);
return [[ place.nome,
place.fantasia,
place.telefone,
place.email,
]];
}
Technically it should work but for some reason I'm getting a return only in the first one.
The error I'm getting is very generic "Erro: Erro interno ao executar a função personalizada." (something like "Error: Internal error in the execution of personalized function").
Any ideas?
From https://developers.google.com/apps-script/guides/sheets/functions
A custom function call must return within 30 seconds. If it does not, the cell will display an error: Internal error executing the custom function.
Considering the above, it's not a good idea to use sleep on a custom function that will be used as intended by the OP. Instead use a custom menu or the Script Editor to execute a script.
In order to minimize changes to your function, you could use a function that read/write the values to the spreadsheet and pass the required arguments to GET_DETAILS_RECEITA
I would consider using something like this in a dialog. You can pass an extra parameter in the set interval as long as your using Chrome.
<script>
var CNPJ='what ever';
window.onload=function(){setInterval(getDetails,25000,CNPJ);}
function getDetails(CNPJ){
google.script.run.GET_DETAILS_RECEITA(CNPJ)
}
</script>
And if you want a callback then use with withSuccessHandler();

Google Docs Custom Refresh Script

I'm trying to write a script that allows me to execute commands as soon as Google finishes making calculations (i.e. I'm trying to add to a script to Google docs that imitates some VBA "Calculate" functionalities).
The script is conceived to work by converting a range into a string and looking for the substring "Loading..." (or "#VALUE!" or "#N/A") in that string. The "while" loop is supposed to sleep until the unwanted substrings are no longer found in the string.
I'm using the following spreadsheet as a sandbox, and the code seems to work okay in the sandbox just searching for "Loading...":
https://docs.google.com/spreadsheet/ccc?key=0AkK50_KKCI_pdHJvQXdnTmpiOWM4Rk5PV2k5OUNudVE#gid=0
In other contexts, however, I have cells whose values may return as "#VALUE!" or "#N/A" for reasons other than the fact that Google is still loading/thinking/calculating. What's the way around this?
function onEdit() {
Refresh();
};
function Refresh () {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var sheet2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet2");
// set the range wherever you want to make sure loading is done
var range = sheet.getRange('A:A')
var values = range.getValues();
var string = values.toString();
var loading = "Loading";
do
{
var randomWait = Math.floor(Math.random()*100+50);
Utilities.sleep(randomWait);
}
while (string.search(loading) ==! null);
range.copyTo(sheet2.getRange('A1'), {contentsOnly:true});
customMsgBox();
};
function customMsgBox() {
Browser.msgBox("Data refreshed.");
};
rather than using a while loop to "sleep" you should add an event handler to your document which captures the update/refresh event and then runs whatever math/processing you need.
Here's a good place to start reading about events: https://developers.google.com/apps-script/understanding_events
but if you search the api documents for eventhandler you can get some example code fast...