function createDocs() {
var headers = Sheets.Spreadsheets.Values.get('1wjmfG-mTYFw_JQZIondppy-VxffdV3fFzcEi8Og', 'Juli 2021!A5:K5');
var tactics = Sheets.Spreadsheets.Values.get('1wjmfG-mTYFw_JQZIondppy-VoCguU_VgVfdsdcEi8Og', 'Juli 2021!H6:K4000');
var templateId = '1B2IzVd03MmoPtT7Tcvce8I9wXGrb6fYXCnz8JikAs';
for(var i = 0; i < tactics.values.length; i++){
var textId = tactics.values[i][0];
var titel = tactics.values[i][1];
var words = tactics.values[i][3];
//Make a copy of the template file
var documentId = DriveApp.getFileById(templateId).makeCopy().getId();
//Rename the copied file
DriveApp.getFileById(documentId).setName('NEW ' + textId +' '+ titel + ' ' + words);
//Get the document body as a variable
var body = DocumentApp.openById(documentId).getBody();
//Insert the textid name
body.replaceText('##textID##', textId)
//Insert the titel name
body.replaceText('##subject##', titel)
//Insert the words name
body.replaceText('##words##', words)
//Append tactics
parseTactics(headers.values[0], tactics.values[i], body);
}
}
function parseTactics(headers, tactics, body){
for(var i = 1; i < tactics.length; i++){
{tactics[i] != '' &&
body.appendListItem(headers[i] + ' | ' + tactics[i] + ' net').setGlyphType(DocumentApp.GlyphType.BULLET);
}
}
}
So I have this code that works fine on specific tabs and creating docs based on template.
How can i make it so I the code works for all tabs?
All tabs of interest have the format: "month year".
There are some tabs not of interest that have different format.
All tabs have there own corresponding folders.
Goal is to just have a template file I can copy the each folder that needs docs created.
If your sheet/tabs are something like this: November 2021
Then you could use something like this to filter them from the rest of your sheets:
function selectSheets() {
const mA = [...Array.from(new Array(12).keys(), x => Utilities.formatDate(new Date(2021, x), Session.getScriptTimeZone(), "MMMM"))];
const ss = SpreadsheetApp.getActive();
let shts = ss.getSheets().filter(sh => {
let t = sh.getName().split(' ');
if (~mA.indexOf(t[0]) && t[1].match(/\d{4}/)) {
return true;
} else {
return false;
}
}).map(sh => sh.getName());
Logger.log(shts.join(','))
}
need some help with a Google Script that's responsible to fetch a CSV and upload to Google Analytics.
This is how I receive my CSV from my fetch:
sku|maxCPO
123-2-F|10
3212-123-G|10
145-1234-S|70.5
53455-245-A|52.5
12455-13-H|15.5
33352-45-E|10
2443-BOX-H|150
3455-BOX-N|200
What I have in my GoogleScript
function findCsvAttachment() {
var csvDoc = UrlFetchApp.fetch("mycsv");
var csvData = Utilities.parseCsv(csvDoc, "|");
return csvData;
}
function processCsv(csvData) {
var dataRows ="";
var headers = 'ga:productSku,ga:metric3\n';
for (var row = 4979; row < csvData.legth && csvData[row][0]!= ''; row++) {
var currentDataRow= csvData[row][0] + ',' + csvData[row][1]+'\n';
dataRows += String(currentDataRow);
}
var dataForUpload = headers + dataRows;
return dataForUpload;
}
//assumes media dataType for upload https://developers.google.com/analytics/devguides/config/mgmt/v3/mgmtReference/management/uploads/uploadData
function uploadDataToAnalytics(data, accountId, webPropertyId, customDataSourceId) {
var dataBlob = Utilities.newBlob(data, "application/octet-stream");
var upload = Analytics.Management.Uploads.uploadData(accountId, webPropertyId, customDataSourceId, dataBlob);
return upload;
}
///////// ACTUAL IMPLEMENTATION OF SCRIPT /////////////
var csv = findCsvAttachment();
var csvForUpload = processCsv(csv);
var analyticsUpload = uploadDataToAnalytics(csvForUpload, CONFIG.analyticsAccountId, CONFIG.analyticsPropertyId, CONFIG.customDataSourceId);
}
I'm only getting the headers when I run the script, I don't get the rows... Any help is MUCH appreciated! Thanks in advance!
I am working to programmatically display a list of residents for a "know your neighbors map" community project. I want to take any number of first & last names for a residence and list them with last names in bold and leading any first names & numbers. The kludge code below or from the link is my first pass. I have tried various iterations to get the exception to go away, but I have been unsuccessful and I can't find anything concrete in the documentation or on the Googles.
Make a copy and run it yourself with this link: https://docs.google.com/spreadsheets/d/1rWHG5CKHvFzohTq9fSl8p8AjNcFjX0Tatior_hvOncE/copy
I'm getting the following error: "Exception: The parameters (SpreadsheetApp.RichTextValueBuilder) don't match the method signature for SpreadsheetApp.Range.setRichTextValue. (line 49, file "Code")"
Any ideas?
Kludge down here!
function main() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataSheet = ss.getSheetByName('Data');
var address_array = dataSheet.getRange('AddressData').getValues();
var bold = SpreadsheetApp.newTextStyle().setBold(true).build();
var resident_count = 1;
for (var i = 0; i < address_array.length; i++) {
var resident_array = address_array[i];
var resident_cell_range_name = 'Resident' + resident_count.toString();
var resident_cell = dataSheet.getRange(resident_cell_range_name);
var resident_text_array = [];
var resident_bold = [];
for (var j = 0; j < resident_array.length; j += 3) {
var last = resident_array[j];
var first = resident_array[j + 1];
var number = resident_array[j + 2];
if (last) {
var bold_start_stop = [];
var total = length_all(resident_text_array);
bold_start_stop.push(total);
bold_start_stop.push(total + last.length);
resident_bold.push(bold_start_stop);
resident_text_array.push(last)
}
if (first) {
if (number) {
var name_num = first + ' - ' + number;
resident_text_array.push(name_num)
}
else {
resident_text_array.push(first)
}
} else {
if (number) {
resident_text_array.push(number)
}
}
}
var value = SpreadsheetApp.newRichTextValue();
var resident_text = 'Some text';
// var resident_text = resident_text_array.join(String.fromCharCode(10));
// var resident_text = resident_text_array.join(' ');
value.setText(resident_text);
// for (start_stop of resident_bold) {
// value.setTextStyle(start_stop[0], start_stop[1], bold);
// }
value.build();
// resident_cell.setValue('text');
resident_cell.setRichTextValue(value);
Logger.log(resident_text_array);
resident_count += 1;
}
Logger.log(address_array);
}
function length_all(resident_text_array){
var total = 0;
for (each of resident_text_array) {
total = total + each.length;
}
return total;
}
The issue was using the build() command outside of the setRichTextValue() function.
Original that led to the exception:
value.build();
resident_cell.setRichTextValue(value);
Fixed:
resident_cell.setRichTextValue(value.build());
I'm not sure why, but that did it!
The function only work once. It does not loop at all.
Tweaking by changing the for looping parameter to just number and changing the position of the lines.
function PDFAbsensi() {
var sourceSpreadsheet = SpreadsheetApp.getActive();
// Get active sheet.
var sheets = sourceSpreadsheet.getSheets();
var sheetName = sourceSpreadsheet.getActiveSheet().getName();
var sourceSheet = sourceSpreadsheet.getSheetByName('PRINT ABSENSI');
// Set the output filename.
var idkelompok = sourceSheet.getRange(2,36).getValues();
var namakelompok = sourceSheet.getRange(2,2).getValues();
var nomorkelompok = sourceSheet.getRange(2,3).getValues();
var pdfName = idkelompok + " - " + namakelompok + " " + nomorkelompok;
// export url
var url = 'https://docs.google.com/spreadsheets/d/'+sourceSpreadsheet.getId()+'/export?exportFormat=pdf&format=pdf' // export as pdf / csv / xls / xlsx
+ '&gid='+sourceSpreadsheet.getSheetByName('PRINT ABSENSI').getSheetId();
var token = ScriptApp.getOAuthToken();
var response = UrlFetchApp.fetch(url, {
headers: {
'Authorization': 'Bearer ' + token} });
var parents = DriveApp.getFileById(sourceSpreadsheet.getId()).getParents();
if (parents.hasNext()) {
var folder = parents.next();}
else {
folder = DriveApp.getRootFolder();}
// Loop
var jumlahpdf = sourceSheet.getRange(5,36).getValue();
for (i=0;i<jumlahpdf;i++) {
var theBlob = response.getBlob().setName(pdfName+'.pdf');
// delete pdf if already exists
var files = folder.getFilesByName(pdfName+'.pdf');
while (files.hasNext())
{files.next().setTrashed(true);}
// create pdf
var newFile = folder.createFile(theBlob);
return true;
// Delete the wasted sheet we created, so our Drive stays tidy.
DocsList.getFileById(newSpreadsheet.getId()).setTrashed(true);
// Add the number for looping function
var updatenomorkelompok = sourceSheet.getRange(2,36).setValue(idkelompok-(-1));}}
I expect the code to create multiple PDF files. As of now it only made one each time it runs.
Issue:
You have return true in the middle of your for loop.
Explanation:
The return is ending the function completely on the first loop. This is expected behaviour, see below from MDN return documentation:
The return statement ends function execution and specifies a value to be returned to the function caller.
Example:
Below is a basic for loop that logs the value of i with every run:
for (i = 0; i < 3; i++) {
console.log(i);
}
This logs every iteration as expected.
Here's an example with the return statement you're using (same for loop, but with return true inside):
testReturn();
function testReturn() {
for (i = 0; i < 3; i++) {
console.log(i);
return true;
}
}
When this function is called, we get a log entry for 0 (the first run), but as we're returning, the function execution is ended, hence why your script is only processing the first PDF.
return
well, i'm trying to do what described in title. Both spreadsheets have only one sheet that are the ones i'm comparing. One spreadsheet is and update of the other, so i'm trying to get only new content. (if it were a fc (dos command) like function this would be easy...)
After doing some search, i have the folloing script that should work on most cases, that uses arrays for each sheet.
function test() {
var Folder = DriveApp.getFoldersByName('theFolder').next();
var FolderId =Folder.getId();
//call old_spreadsheet
var searchFor ="fullText contains 'sheet_old' and '" + FolderId + "' in parents";
var files = DriveApp.searchFiles(searchFor);
var old_file = files.next();
var old_spreadsheet = SpreadsheetApp.openById(old_file.getId());
var old_sheet = old_spreadsheet.getSheets()[0];
var old_sheetname = old_sheet.getName();
var old_array = old_sheet.getDataRange().getValues();
Logger.log(old_file.getName() + ' : ' + old_sheetname + ' : ' + old_array.length);
//call spreadsheet
var searchFor ="fullText contains 'sheet' and '" + FolderId + "' in parents";
var files = DriveApp.searchFiles(searchFor);
var file = files.next();
var spreadsheet = SpreadsheetApp.openById(file.getId());
var sheet = spreadsheet.getSheets()[0];
var sheetname = sheet.getName();
var array = sheet.getDataRange().getValues();
Logger.log(file.getName() + ' : ' + sheetname + ' : ' + array.length);
var newarray = getNewData(array,old_array);
Logger.log('there are ' + newarray.length + 'different rows');
}
function getNewData(array1,array2){
var diff =array2;
for (var i = 0; i<array1.length; i++){
var duplicate = false;
for (var j = 0;j<diff.length;j++){
if (array1[i].join() == diff[j].join()){
Logger.log('duplicated line found on rows ' + i + ':' + j);
diff.splice(j,1);
var duplicate= true;
break;
}
}
if (duplicate==false) {
Logger.log('not duplicated line found on row ' + i);
diff.push(array1[i]);
}
}
return diff;
}
The thing is that the files are too big, almost 30000 rows, so the scripts exceed 5 minutes limit for execution.
Is there a way to improve this, like for instance, eliminate the inner for loop?
Or there is a way to do it in parts? like first the first 5000 rows, and so on.
Regards,
EDIT: after analizing the spreadsheet a little, i found out that there is a ID for every row, so now i can concentrate the search only in one column of each spreadsheet. So here is my new implementation:
function test(){
var Folder = DriveApp.getFoldersByName('theFolder').next();
var FolderId =Folder.getId();
//call old_spreadsheet
var searchFor ="fullText contains 'sheet_old' and '" + FolderId + "' in parents";
var files = DriveApp.searchFiles(searchFor);
var old_file = files.next();
var old_spreadsheet = SpreadsheetApp.openById(old_file.getId());
var old_sheet = old_spreadsheet.getSheets()[0];
var old_sheetname = old_sheet.getName();
var old_array = old_sheet.getDataRange().getValues();
Logger.log(old_file.getName() + ' : ' + old_sheetname + ' : ' + old_array.length);
//call spreadsheet
var searchFor ="fullText contains 'sheet' and '" + FolderId + "' in parents";
var files = DriveApp.searchFiles(searchFor);
var file = files.next();
var spreadsheet = SpreadsheetApp.openById(file.getId());
var sheet = spreadsheet.getSheets()[0];
var sheetname = sheet.getName();
var array = sheet.getDataRange().getValues();
Logger.log(file.getName() + ' : ' + sheetname + ' : ' + array.length);
//The COlumn has an indicator, so i search for that. I don't control the formatting of the files, so i search in both spreadsheet for the indicator
var searchString = 'NAME';
for (var i = 0; i < old_array.length; i++) {
for (var j = 0; j < old_array[i].length; j++) {
if (old_array[i][j] == searchString) {
var Row_old = i+1;
var Column_old = j;
break;
}
}
if (Row_old != undefined){
break;
}
}
for (var i = 0; i < array.length; i++) {
for (var j = 0; j < array[i].length; j++) {
if (array[i][j] == searchString) {
var Row = i+1;
var Column = j;
break;
}
}
if (Row != undefined){
break;
}
}
Logger.log(Row_old+':::'+Column_old+'\n'+Row+':::'+Column);
var diff_index =[];
var row_ind = 0;
for (var i=Row;i<array.length;i++){
Logger.log(i);
var existe = ArrayLib.indexOf(old_array, Column_old, array[i][Column]);
if (existe==-1){
Logger.log(row_ind+'!!!');
diff_index[row_ind]=i;
row_ind++;
}
}
Logger.log(diff_index);
}
This still run out of time... I will now try to incorporate your comments.
Your script has a few major bottlenecks that slow it down massively:
Starting both loops at 0 every time makes its runtime explode
splicing every time you find a duplicate requires to move the array around
string concatenating an array on every iteration
We can circumvent these issues by:
sorting the second range once
I'm sure there's something clever to be done by iteratively binary searching through every column but we'd have to resort every time so we'll binary search the first column and then do a linear search.
We will use ArrayLib for the sorting (I hope it's a fast sorting algorithm).
Let's start with a function to find the first row where the first column matches a value (the first column of the current row):
function firstRowMatchingCol1(target, lookupRange) {
var min = 0;
var max = lookupRange.length - 1;
var guess;
var guessVal;
while(min <= max) {
guess = (min + max) / 2 | 0;
guessVal = lookupRange[guess][0];
if (guessVal < target) {
min = guess + 1;
} else if (guessVal > target) {
max = guess - 1;
} else {
while (guess > 0 && lookupRange[guess - 1][0] === target) {
guess -= 1;
}
return guess;
}
}
return -1;
}
Now we can go linearly go through every row and check if the columns match until the first column doesn't match anymore.
function matchExists(row, lookupRange) {
var index = firstRowMatchingCol1(row[0], lookupRange);
if (index === -1) {return false;}
while (index < lookupRange.length && lookupRange[index][0] === row[0]) {
for (var col = 1; col < row.length; col++) {
if (row[col] !== lookupRange[index][col]) {break;}
if (col === row.length - 1) {return true;} // This only works if the ranges are at least two columns wide but if they are one column wide you can just check if index > -1
}
index += 1;
}
return false;
}
And finally we can get the duplicates like this:
function getNonDuplicates(r1, r2) {
r2 = ArrayLib.sort(r2, 0, true);
return r1.filter(function(row) {return !matchExists(row, r2);});
}
Like mTorres' code this is untested
The solution I'm proposing is a "hack" around the time limit. But if you want a cleaner solution, you could, if possible, reorganize and make your code more efficient by having the arrays ordered somehow.
You don't specify the data inside array1 and array2, if rows had some sort of ID field you could order by this ID and check row i on array1 and row i on array2 instead of comparing every row in array1 with every row in array2 (which is extremely inefficient with 30000 rows).
If your data does not have an ID field to order the rows, then what you could is something based on my proposed solution: add a track for every compared row on array1. When the run reaches the time limit then you run again the function but starting from the last compared row (you would know which was because you'll be tracking the compared rows), and when the second run times out you repeat, and so on.
Every time you run your comparison you ask if it's the first run (or use a boolean - I prefer to ask the user, this way you won't forget to change the boolean), if it's the first run, you delete the tracking
column, if it's not the first run, you'll start with the next to last tracked row so basically continuing your script where it ended. I've been using this technique with good results.
In code (untested, so check it out before running it with real data):
/**
* Only checks if it's the first run and calls the real work function
*/
function test() {
var firstRun = "yes" === Browser.msgBox("Question", "Is this the first run?", Browser.Buttons.YES_NO);
doTest(firstRun);
}
/**
* Gets the data of the 2 spreadsheets and also the starting
* row
*/
function doTest(firstRun) {
var Folder = DriveApp.getFoldersByName('theFolder').next();
var FolderId = Folder.getId();
//call old_spreadsheet
var searchFor ="fullText contains 'sheet_old' and '" + FolderId + "' in parents";
var files = DriveApp.searchFiles(searchFor);
var old_file = files.next();
var old_spreadsheet = SpreadsheetApp.openById(old_file.getId());
var old_sheet = old_spreadsheet.getSheets()[0];
var old_sheetname = old_sheet.getName();
var old_array = old_sheet.getDataRange().getValues();
/**
* Here is the code to create the tracking hability
*/
var strartFromRow = 0; // 0 because row 1 is array 0 index when you getValues();
var trackSheet = old_spreadsheet.getSheetByName("Tracking");
if (trackSheet === null) {
trackSheet = old_spreadsheet.insertSheet("Tracking");
}
if (firstRun) {
trackSheet.getRange("A:A").clearContent(); // make sure there no row is tracked yet
}
else {
// we have to continue from the previous row, keep in mind you're making the comparison
// with array which is 0 based, but sheet is 1 based, but you want the next one so getLasRow()
// should be the first item to compare on your array
strartFromRow = trackSheet.getLastRow();
}
Logger.log(old_file.getName() + ' : ' + old_sheetname + ' : ' + old_array.length);
//call spreadsheet
var searchFor ="fullText contains 'sheet' and '" + FolderId + "' in parents";
var files = DriveApp.searchFiles(searchFor);
var file = files.next();
var spreadsheet = SpreadsheetApp.openById(file.getId());
var sheet = spreadsheet.getSheets()[0];
var sheetname = sheet.getName();
var array = sheet.getDataRange().getValues();
Logger.log(file.getName() + ' : ' + sheetname + ' : ' + array.length);
// when you call the DIFF function, pass the tracking sheet and the start Row
var newarray = getNewData(array,old_array, trackSheet, startFromRow);
Logger.log('there are ' + newarray.length + 'different rows');
}
/**
* Creates a diff array using array1 and array2
* It marks each element on array1 once it has checked if it's in array2
*/
function getNewData(array1, array2, trackingSheet, startFromRow){
var logRow = trackingSheet.getLastRow();
var diff = array2;
for (var i = startFromRow; i < array1.length; i++){
var duplicate = false;
for (var j = 0; j < diff.length;j++){
if (array1[i].join() == diff[j].join()){
Logger.log('duplicated line found on rows ' + i + ':' + j);
diff.splice(j,1);
duplicate = true;
break;
}
}
if (duplicate === false) {
Logger.log('not duplicated line found on row ' + i);
diff.push(array1[i]);
}
trackingSheet.getRange(logRow++, 1).setValue("Checked!"); // Mark i row as checked
}
return diff;
}
Here's an alternate solution that gets around the time limit. Create a new dedicated spreadsheet along with a custom sidebar. The sidebar will require you to create some HTML that will ultimately be embedded and rendered in an iframe on the client. You can embed pure javascript into the HTML via script tags.
The beauty of this approach is that these scripts will not run server-side but on the client independently of Google Apps Script's server-side environment and are not subject to the 6 minute limit. Moreover, they can also call functions in your Google Script. So one approach would be to have the client-side scripts call a Google Script function to retrieve the requisite data, do all the heavy processing in the client-side scripts, and then send the results back to the server-side script to update the sheet.
Here's a link to setting up a custom sidebar to get you started:
https://developers.google.com/apps-script/guides/dialogs#custom_sidebars
Finally, i decided to go for the Cache service option, here is the code and i'm testing it to see if i keep with this.
function getNewData() {
//deleting triggers
var triggers = ScriptApp.getProjectTriggers();
for (var i = 0; i < triggers.length; i++) {
if (triggers[i].getHandlerFunction()=='getNewData'){
ScriptApp.deleteTrigger(triggers[i]);
}
}
//max running time = 5.5 min
var MAX_RUNNING_TIME = 330000;
var startTime= (new Date()).getTime();
//get cache
var cache = CacheService.getUserCache();
var downloaded =JSON.parse(cache.get('downloaded'));
var compared =JSON.parse(cache.get('compared'));
//start
if (downloaded==1 && compared!=1){
//folder
var Folder = DriveApp.getFoldersByName('theFolder').next();
var FolderId = licitacionesFolder.getId();
//call old_spreadsheet
var searchFor ="fullText contains 'sheet_old' and '" + FolderId + "' in parents";
var files = DriveApp.searchFiles(searchFor);
var old_file = files.next();
var old_spreadsheet = SpreadsheetApp.openById(old_file.getId());
var old_sheet = old_spreadsheet.getSheets()[0];
var old_array = old_sheet.getDataRange().getValues();
//call spreadsheet
var searchFor ="fullText contains 'sheet' and '" + FolderId + "' in parents";
var files = DriveApp.searchFiles(searchFor);
var file = files.next();
var spreadsheet = SpreadsheetApp.openById(old_file.getId());
var sheet = spreadsheet.getSheets()[0];
var array = sheet.getDataRange().getValues();
Logger.log(array.length+'::'+old_array.length);
// Column
var searchString = 'NAME';
var RC = getColumn(array,searchString);
var Row = RC.Row;
var Column = RC.Column;
var RC = getColumn(old_array,searchString);
var Row_old = RC.Row;
var Column_old = RC.Column;
Logger.log(Row_old+':::'+Column_old+'\n'+Row+':::'+Column);
//compare
var diff_index =JSON.parse(cache.get('diff_index'));
var row_ind =JSON.parse(cache.get('row_ind'));
var Roww =JSON.parse(cache.get('Row'));
if (diff_index==null){var diff_index = [];}
if (row_ind==null){var row_ind = 0;}
if (Roww==null){var Roww = Row;}
Logger.log(row_ind+'\n'+Roww);
for (var i=Roww;i<array.length;i++){
var currTime = (new Date()).getTime();
if(currTime - startTime >= MAX_RUNNING_TIME){
Logger.log((currTime - startTime)/(1000*60));
Logger.log(i+'::'+row_ind);
cache.putAll({'diff_index': JSON.stringify(diff_index),'row_ind': JSON.stringify(row_ind),'Row': JSON.stringify(i-1)},21600);
ScriptApp.newTrigger('getNewData').timeBased().after(2 * 60 * 1000).create();
return;
} else {
Logger.log(i);
var existe = ArrayLib.indexOf(old_array, Column_old, array[i][Column]);
if (existe==-1){
Logger.log(row_ind+'!!!');
diff_index[row_ind]=i;
row_ind++;
}
}
}
cache.putAll({'diff_index': JSON.stringify(diff_index),'Row': JSON.stringify(Row),'compared': JSON.stringify(1)},21600);
} else {
Logger.log('file not downloaded yet or already compared');
}
}
function getColumn(array,searchString){
for (var i = 0; i < array.length; i++) {
for (var j = 0; j < array[i].length; j++) {
if (array[i][j] == searchString) {
var Row = i+1;
var Column = j;
break;
}
}
if (Row != undefined){
break;
}
}
return {Row: Row, Column: Column};
}