Can you create spreadsheet in gas & get ID & getActive? - google-apps-script

Multiple versions of the spreadsheet with the subset of data exist on my drive and I have a way to get the most recent but I thought that if I could capture the ID when I created it I would not have to run that extra code. If I just create a spreadsheet the return is a 'spreadsheet' which I can .getActiveSheet().getRange(. . . ).setValues(. . .)
If I create and immediately getId then get use DriveApp.getFileById the return is a 'file'. It has a mime type of spreadsheet but cannot be used with .getActiveSheet() etc. Why? Is there a way to get the ID when creating a spreadsheet and still work with that new sheet?
function createSubset(fileName) {
//
// var mimeType = 'application/vnd.google-apps.spreadsheet';
baseSs = SpreadsheetApp.getActiveSpreadsheet(); // "StudentRosterForPractice"
baseSheet = baseSs.getSheetByName("Roster");
// there are formulas save below actual data but no in column B
var totDataRows = cntDataRows (baseSheet.getRange('B1:B').getValues()) - 1;
dataSubset = baseSheet.getRange(3, 2, totDataRows, 2).getValues();
// ---- this does NOT work ----
// tempSsId = SpreadsheetApp.create(fileName).getId();
// tempSs = DriveApp.getFileById(tempSsId); // returns file
// Logger.log("tempSs.getMimeType(): " + tempSs.getMimeType() );
//// tempSs.getMimeType(): application/vnd.google-apps.spreadsheet
// tempSs.getActiveSheet().getRange(1, 1, totDataRows, 2).setValues(dataSubset);
//// gets TypeError: tempSs.getActiveSheet is not a function
// return tempSsId;
// ---- this does work ----
tempSs = SpreadsheetApp.create(fileName); // return spreadhsheet
tempSs.getActiveSheet().getRange(1, 1, totDataRows, 2).setValues(dataSubset);
return null;
}
/**
* Number rows with actual data. There is a row of forumulas.
*/
function cntDataRows (colB) {
var count = 0 ;
colB.forEach(
function(item) {
if ( item != "" ) { // no formula in last name column
count = count + 1;
}
}
) ;
Logger.log("totDataRows: " + count);
return count
}
function testCreateSubset() {
var ssName = 'tempSs'; //subset name
tempSsId = createSubset(ssName);
}
Thank you Marios.
My subset spreadsheet only has one sheet so that was not a problem. I am surprised that I can getActiveSheet on the subset even though the script is bound to the base spreadsheet.
There is still a problem however. When I create my subset, return the ID and create the PDF I get these messages
totDataRows: 41
Nov 8, 2020, 8:54:51 AM Info spreadsheet ID: 1t16QOEqT2OP8vVmdTueT2hqQjTGdlMQLAc7XnJNWScM
Nov 8, 2020, 8:54:51 AM Info In makeBlobPdf spreadsheet ID: 1t16QOEqT2OP8vVmdTueT2hqQjTGdlMQLAc7XnJNWScM
Nov 8, 2020, 8:54:51 AM Info URL to be fetched: https://docs.google.com/spreadsheets/d/1t16QOEqT2OP8vVmdTueT2hqQjTGdlMQLAc7XnJNWScM/export?exportFormat=pdf&format=pdf&size=A4&portrait=false&fitw=true&sheetnames=false&printtitle=false&printnotes=false&pagenumbers=false&pagenum=CENTER&gridlines=false&fzr=true&top_margin=0.15&left_margin=0.15&right_margin=0.15&bottom_margin=0.15
but the PDF is empty. If I open the subset and get the ID, hardcode it in the script and create the PDF the messages are:
In makeBlobPdf spreadsheet ID: 1t16QOEqT2OP8vVmdTueT2hqQjTGdlMQLAc7XnJNWScM
Nov 8, 2020, 8:57:28 AM Info URL to be fetched: https://docs.google.com/spreadsheets/d/1t16QOEqT2OP8vVmdTueT2hqQjTGdlMQLAc7XnJNWScM/export?exportFormat=pdf&format=pdf&size=A4&portrait=false&fitw=true&sheetnames=false&printtitle=false&printnotes=false&pagenumbers=false&pagenum=CENTER&gridlines=false&fzr=true&top_margin=0.15&left_margin=0.15&right_margin=0.15&bottom_margin=0.15
The ID received in the make PDF code is the same and the URL generated is the same but the PDF where the ID was hardcoded has data and the PDF where it was passed from the create subset is empty. The create PDF code is unchanged.
Works:
var ssName = 'tempSs'; //subset name
// var spreadsheetId = createSubset(ssName);
// Logger.log("spreadsheet ID: " + spreadsheetId );
var spreadsheetId = "1t16QOEqT2OP8vVmdTueT2hqQjTGdlMQLAc7XnJNWScM";
var blobPdf = makeBlobPdf(spreadsheetId, ssName);
Returns empty PDF:
var ssName = 'tempSs'; //subset name
var spreadsheetId = createSubset(ssName);
Logger.log("spreadsheet ID: " + spreadsheetId );
// var spreadsheetId = "1t16QOEqT2OP8vVmdTueT2hqQjTGdlMQLAc7XnJNWScM";
var blobPdf = makeBlobPdf(spreadsheetId, ssName);
Corrected createSubset
function createSubset(fileName) {;
baseSs = SpreadsheetApp.getActiveSpreadsheet(); // "StudentRosterForPractice"
baseSheet = baseSs.getSheetByName("Sheet1");
// there are formulas saved below actual data but no in column B
var totDataRows = cntDataRows (baseSheet.getRange('B1:B').getValues()) - 1;
dataSubset = baseSheet.getRange(3, 2, totDataRows, 2).getValues();
tempSsId = SpreadsheetApp.create(fileName).getId();
tempSs = SpreadsheetApp.openById(tempSsId);
tempSs.getActiveSheet().getRange(1, 1, totDataRows, 2).setValues(dataSubset);
return tempSsId;
}
Code that calls createSubset and makeBlobPdf
function createAndMailPdf () {
var ssName = 'tempSs'; //subset name
var spreadsheetId = createSubset(ssName);
Logger.log("spreadsheet ID: " + spreadsheetId );
// var spreadsheetId = "1t16QOEqT2OP8vVmdTueT2hqQjTGdlMQLAc7XnJNWScM";
var blobPdf = makeBlobPdf(spreadsheetId, ssName);
if (MailApp.getRemainingDailyQuota() > 0) {
var emailAddress = "blah blah # blah";
var subject = "With useless PDF file";
var htmlBody = HtmlService.createHtmlOutputFromFile('letterBody').getContent();
var templ = HtmlService.createTemplateFromFile('letterBody');
var message = templ.evaluate().getContent();
MailApp.sendEmail(
{ to: emailAddress,
subject: subject,
htmlBody: message,
attachments: blobPdf}
);
} else {
Logger.log("Mail quota exceeded but the PDF has been saved");
}
}
The makeBlobPdf code:
function makeBlobPdf (spreadsheetId, ssName) {
Logger.log("In makeBlobPdf spreadsheet ID: " + spreadsheetId );
var marginStringValue = '0.15';
var margin = '_margin=' + marginStringValue;
var margins = '&top' + margin + '&left' + margin
+ '&right' + margin + '&bottom' + margin;
var url = 'https://docs.google.com/spreadsheets/d/'
+ spreadsheetId + '/export?'
+ 'exportFormat=pdf&format=pdf'
+ '&size=A4'
+ '&portrait=false'
+ '&fitw=true' // Fit to width
+ '&sheetnames=false'
+ '&printtitle=false'
+ '&printnotes=false'
+ '&pagenumbers=false'
+ '&pagenum=CENTER'
+ '&gridlines=false'
+ '&fzr=true' // Repeat frozen rows
+ margins;
var token = ScriptApp.getOAuthToken();
Logger.log("URL to be fetched: " + url );
var result = UrlFetchApp.fetch(url, {
headers: {
Authorization: 'Bearer ' + token
}
});
var attachName = ssName + '.pdf';
var pdfBlob = result.getBlob().setName(attachName);
return pdfBlob;
};

Solution:
The issue in your code is that there is no method getActiveSheet()
for a file. It makes sense since a file can be anything in your
google drive. It wouldn't make sense to have a spreadsheet method like getActiveSheet() being applied to an image or a folder for example. As a result tempSs.getActiveSheet() is invalid since tempSs is a type of file in the deleted part of your code.
Here is the proper way to do that.
If you want to create the file and get its ID directly. And then you can use openById to get the spreadsheet.
function myFunction() {
var fileName = 'testname';
var file = Drive.Files.insert({title: fileName, mimeType: MimeType.GOOGLE_SHEETS}); // file
var newFile = SpreadsheetApp.openById(file.id); // Spreadsheet file
newFile.getActiveSheet().getRange(1, 1, totDataRows, 2).setValues(dataSubset);
}
But keep in mind newFile.getActiveSheet() gives the first sheet in your file. You might want to use newFile.getSheetByName('Sheet1') instead.
To use this code please enable Drive API.

From the question
Can you create spreadsheet in gas & get ID & getActive?
Yes. Bear in mind that running getActiveSheet() for the created spreadsheet will return the firts sheet.
Regarding DriveApp.getFileById(id) it returns a File object not a Spreadsheet object.
The Class File hasn't a getActiveSheet() method. If you wan to use this method one opction is to use SpreadsheetApp.openById(id) or SpreadsheetApp.open(file)
Resources
https://developers.google.com/apps-script/reference/drive/drive-app
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet-app

The main problem you are encountering is that your sheet is being created but is taking some time to set the values in the function createSubset(). Therefore, while the creation of the Spreadsheet is complete, the values of the sheet have not been set yet which is done in the background.
This makes that createSubset() returns the Spreadsheet ID to the other functions and the PDF file is immediately created (but the cell values have not been set yet). This leads to your current behaviour where you are sending an empty Spreadsheets PDF because the cell values had not been set yet.
To force the cells to update with the right values and avoid sending the PDF before the Spreadsheet is all ready you can use the function flush which applies all the pending changes to the Spreadsheet. Use it as follows:
function createSubset(fileName) {;
baseSs = SpreadsheetApp.getActiveSpreadsheet(); // "StudentRosterForPractice"
baseSheet = baseSs.getSheetByName("Sheet1");
// there are formulas saved below actual data but no in column B
var totDataRows = cntDataRows (baseSheet.getRange('B1:B').getValues()) - 1;
dataSubset = baseSheet.getRange(3, 2, totDataRows, 2).getValues();
tempSsId = SpreadsheetApp.create(fileName).getId();
tempSs = SpreadsheetApp.openById(tempSsId);
tempSs.getActiveSheet().getRange(1, 1, totDataRows, 2).setValues(dataSubset);
// FORCE ALL UPDATES IN THE SPREADSHEET BEFORE RETURNING THE SPREADHSEET ID
SpreadsheetApp.flush();
return tempSsId;
}

Related

Google Sheets - Convert comma to "#" before generate .CSV

I have the following script in a Google Sheet:
/**
* Create CSV file of Sheet2
* Modified script written by Tanaike
* https://stackoverflow.com/users/7108653/tanaike
*
* Additional Script by AdamD.PE
* version 13.11.2022.1
* https://support.google.com/docs/thread/188230855
*/
/** Date extraction added by Tyrone */
const date = new Date();
/** Extract today's date */
let day = date.getDate();
let month = date.getMonth() + 1;
let year = date.getFullYear();
if (day < 10) {
day = '0' + day;
}
if (month < 10) {
month = `0${month}`;
}
/** Show today's date */
let currentDate = `${day}-${month}-${year}`;
/** Date extraction added by Tyrone */
function sheetToCsvModelo0101() {
var filename = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getSheetName() + "-01" + " - " + currentDate; // CSV file name
filename = filename + '.csv';
var ssid = SpreadsheetApp.getActiveSpreadsheet().getId();
var folders = DriveApp.getFileById(ssid).getParents();
var folder;
if (folders.hasNext()) {
folder = folders.next();
var user = Session.getEffectiveUser().getEmail();
if (!(folder.getOwner().getEmail() == user || folder.getEditors().some(e => e.getEmail() == user))) {
throw new Error("This user has no write permission for the folder.");
}
} else {
throw new Error("This user has no write permission for the folder.");
}
var SelectedRange = "A2:AB3";
var csv = "";
var v = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(SelectedRange).getValues();
v.forEach(function (e) {
csv += e.join(",") + "\n";
});
var newDoc = folder.createFile(filename, csv, MimeType.CSV);
console.log(newDoc.getId()); // You can see the file ID.
}
This script basically creates a .CSV file in the same folder where the worksheet is, using the range defined in var SelectedRange.
This script is applied to a button on the worksheet.
The question is: how do I make every comma typed in this spreadsheet be converted into another sign, like # before generating the .CSV file in the folder?
I would also like to know if instead of generating 1 file in the folder it is possible to generate 2 files, each with a name.
Issue:
The question is: how do I make every comma typed in this spreadsheet be converted into another sign, like # before generating the .CSV file in the folder?
After you get the sheet values via getValues, replace all instances of , in the resulting 2D array with #, using map and replaceAll.
I think this is a better approach than TextFinder since sheet values are not modified.
Code snippet:
From your original sample, just add the following line:
// ...stuff...
var v = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(SelectedRange).getValues();
v = v.map(r => r.map(c => c.replaceAll(",", "#"))); // Add this line
v.forEach(function (e) {
csv += e.join(",") + "\n";
});
// ...stuff...
If you are doing this to avoid conflicts between the comma in the cells and the csv delimiter then try doing the csv like this:
function sheetToCsv() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0")
const params = { "method": "GET", "headers": { "Authorization": "Bearer " + ScriptApp.getOAuthToken() } };
const url = "https://docs.google.com/spreadsheets/d/" + ss.getId() + "/export?gid=" + sh.getSheetId() + "&format=csv";
const r = UrlFetchApp.fetch(url, params);
const csv = r.getContentText();
return csv;
}
And then put it back in a spreadsheet like this:
function csvToSheet(csv) {
const vs = Utilities.parseCsv(csv,',');
const osh = ss.getSheetByName("Sheet1");
osh.getRange(1,1,vs.length,vs[0].length).setValues(vs);
}
In the meantime I've found a solution that almost works the way I'd like.
I created 2 functions, one to convert , to # and another to convert # to , again, then after the .csv file creation is complete the script switches back from # to , .
/**
* Create CSV file of Sheet2
* Modified script written by Tanaike
* https://stackoverflow.com/users/7108653/tanaike
*
* Additional Script by AdamD.PE
* version 13.11.2022.1
* https://support.google.com/docs/thread/188230855
*/
var SelectedRange = "A2:AB3";
function searchAndReplace_ToHash() {
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(SelectedRange).createTextFinder(',').replaceAllWith('#');
}
function searchAndReplace_ToComma() {
SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(SelectedRange).createTextFinder('#').replaceAllWith(',');
}
function sheetToCsv_02() {
var filename = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getSheetName() + "-01" + " - " + currentDate; // CSV file name
filename = filename + '.csv';
var ssid = SpreadsheetApp.getActiveSpreadsheet().getId();
searchAndReplace_ToHash()
// I modified below script.
var folders = DriveApp.getFileById(ssid).getParents();
var folder;
if (folders.hasNext()) {
folder = folders.next();
var user = Session.getEffectiveUser().getEmail();
if (!(folder.getOwner().getEmail() == user || folder.getEditors().some(e => e.getEmail() == user))) {
throw new Error("This user has no write permission for the folder.");
}
} else {
throw new Error("This user has no write permission for the folder.");
}
var csv = "";
var v = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(SelectedRange).getValues();
v.forEach(function (e) {
csv += e.join(",") + "\n";
});
var newDoc = folder.createFile(filename, csv, MimeType.CSV);
console.log(newDoc.getId()); // You can see the file ID.
searchAndReplace_ToComma()
}
It solves the problem, but it would be perfect if this change was not visible in the spreadsheet.
Is it possible to make this substitution without displaying it in the spreadsheet?
As for your script suggestion, I would like to change as little as possible in this script I'm using, it works exactly the way I need it to work, except for the fact that the commas of words conflict with the column divisions.
Anyway, thank you very much for all your attention and patience!

Pushing a simple Log string from a Google Ad Script to a Google Sheet

I am trying to set up a script which can push data from an App Script into a Google Sheet.
I have the script successfully logging what I want, which goes in the following format Account budget is 12344, but now I want to push this into a Google Sheet. I have set up a variable containing the URL and another variable containing the sheet name, and also a clear method to delete anything already there.
Find the code I have below:
// - The link to the URL
var SPREADSHEET_URL = 'abcdefghijkl'
// - The name of the sheet to write the data
var SHEET_NAME = 'Google';
// No to be changed
function main() {
var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
var sheet = spreadsheet.getSheetByName(SHEET_NAME);
sheet.clearContents();
}
function getActiveBudgetOrder() {
// There will only be one active budget order at any given time.
var budgetOrderIterator = AdsApp.budgetOrders()
.withCondition('status="ACTIVE"')
.get();
while (budgetOrderIterator.hasNext()) {
var budgetOrder = budgetOrderIterator.next();
Logger.log("Budget Order Amount " + budgetOrder.getSpendingLimit());
}
}
Assuming you want to clear the entire Sheet every time you extract the data this should work for you. You will need to set the url and shtName variables.
function getActiveBudgetOrder() {
var url = 'https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxxxxxxxxxx/';
var shtName = 'Sheet1';
var arr = [];
var sht = SpreadsheetApp.openByUrl(url).getSheetByName(shtName);
// There will only be one active budget order at any given time.
var budgetOrderIterator = AdsApp.budgetOrders()
.withCondition('status="ACTIVE"')
.get();
while (budgetOrderIterator.hasNext()) {
var budgetOrder = budgetOrderIterator.next();
arr.push(["Budget Order Amount " + budgetOrder.getSpendingLimit()]);
}
sht.clearContents();
sht.getRange(1, 1, arr.length, arr[0].length).setValues(arr);
}

How to export Mixpanel raw data to Google Sheet with Mixpanel's API?

Here is a solution to get raw data from Mixpanel to fill a Google spreadsheet. (It's not really a question :-) )
Get your Mixpanel Api secret key: https://help.mixpanel.com/hc/en-us/articles/115004490503-Project-Token-API-Key-API-Secret
Create google spreadsheet or open existing one.
2.1 Open the scripts editor
[Tools] -> [Script Editor]
2.2 Create fonction e.g: exportRawData(){ }
2.3 Prepare an Http Header like that:
/**
* Call Mixpanel raw export endpoint
**/
exportRawData(){
// Prepare header with the secret key
var API_SECRET = "Replace by your Secret Key";
var headers = {
"Authorization" : "Basic " + Utilities.base64Encode(API_SECRET)
};
var params = {
"method":"GET",
"headers":headers
};
// prepare call args
// WARNING: date are yyyy-mm-dd format
// replace fixed date by dynamic range (now - 7 days or other)
var url = "https://data.mixpanel.com/api/2.0/export/?from_date=2018-11-07&to_date=2018-11-14";
Logger.log('Call Mixpanel:' + url);
var response = UrlFetchApp.fetch(url, params);
var rawData = response.getContentText();
Logger.log('Response length:' + rawData.length);
var items = rawData.split("\n");
// prepare the array that it will be pushed into the google spreadsheet
var structuredData; // the json decoded data
var row;
var outer = []; // array of rows
for ( i in items ){
structuredData = JSON.parse( items[i] );
// In this example, we take Mobile usages only
// with Event, Browser, User
if ( structuredData.properties.$browser.indexOf('Mobile') >= 0 ){
row = new Array(structuredData.event, structuredData.properties.$browser, structuredData.properties.distinct_id);
// push the single row into the array of rowS
outer.push(row);
} //eo fi
} //eo rof
pushInSpreadsheet('Mobile Usage', outer);
} // end of exportRawData
/**
* Push data to the Google Spreadsheet
* sheetName: String
* data: Array of rows
* return: nothing
**/
function pushInSpreadsheet(sheetName, data){
Logger.log("push " + data.length + " item(s) into to sheet " + sheetName);
// Prepare the sheet into the active spread sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
// select of exist or create new one
var sheet = ss.getSheetByName(sheetName) ||
ss.insertSheet(sheetName, 1);
sheet.clear();
// The expected structure for the horizontal range setting is:
// [ ["cell A1"],["cell B1"],["cell C1"] ]
var range = sheet.getRange(1, 1, data.length, data[0].length );
range.setValues( data );
}
I hope that it will be helpful. Any comments are welcome.

Trouble deploying sheets addon

Our website spits out CSV files that we use as event rosters, but they include way too much data. There's no feasible way to change what's included in the export, so an admin assistant must edit & format for print. It's repetitive and time consuming, so I figured it's the perfect time to learn Google Apps Script.
Thanks to the incredible knowledge shared here on stack overflow, a total noob like me can cobble together a script that does what I need! Just by using snippets from other answers, I was able to automate:
Delete unwanted & empty columns
Rename & auto-resize columns
Sort by the last name column
Generate print-ready PDF that saves in the same Drive directory.
But now I'm having trouble testing and deploying the script as an addon so my co-workers can use it. When I run a "test as addon" the sheet opens, but nothing happens. I've tried all the variables for installation config and searched for others having the same trouble, but can't find anything so I think the problem is prbly somewhere on my end - script or user error.
Once I get it to test correctly, I'm not entirely sure about how to correctly deploy the addon to our domain and get all the permissions, etc setup correctly. I've read up and now I feel more confused than ever! So two questions:
What's wrong w/ my testing?
Once it tests successfully, what's the easiest way I can let all of our domain's apps users utilize the script?
Here's the script:
function expCalc() {
DeleteColumns();
RemoveEmptyColumns();
RenameColumns();
ResizeColumns();
Sort();
SavePDF();
}
//delete unwanted columns
function DeleteColumns() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getRange("A1:AH200");
var data = sheet.getRange("A1:AH200");
var values = data.getValues();
var numRows = values.length;
var numCols = values[0].length;
for (var col = numCols - 1; col > 0; col--) {
for (var row = 0; row < numRows; row++) {
switch (values[row][col]) {
case "Group":
case "ID":
case "Reg ID":
case "Reg Date":
case "Type of Payment":
case "Transaction ID":
case "Coupon Code":
case "# Attendees":
case "Date Paid":
case "Price Option":
case "Event Date":
case "Event Time":
case "Website Check-in":
case "Tickets Scanned":
case "Check-in Date":
case "Seat Tag":
case "BLS Add-on items (received at class):":
case "Company Name":
case "Address":
case "Address 2":
case "City":
case "State":
case "Zip":
sheet.deleteColumn(col + 1); // delete column in sheet (1-based)
continue; // continue with next column
break; // can't get here, but good practice
}
}
}
}
//Remove Empty Columns
function RemoveEmptyColumns() {
var sh = SpreadsheetApp.getActiveSheet();
var maxColumns = sh.getMaxColumns();
var lastColumn = sh.getLastColumn();
sh.deleteColumns(lastColumn + 1, maxColumns - lastColumn);
}
//Rename Columns
function RenameColumns() {
SpreadsheetApp.getActiveSheet().getRange('A1').setValue('Type');
SpreadsheetApp.getActiveSheet().getRange('B1').setValue('Paid');
SpreadsheetApp.getActiveSheet().getRange('C1').setValue('Price');
SpreadsheetApp.getActiveSheet().getRange('D1').setValue('Amt');
SpreadsheetApp.getActiveSheet().getRange('E1').setValue('Class');
SpreadsheetApp.getActiveSheet().getRange('F1').setValue('First Name');
SpreadsheetApp.getActiveSheet().getRange('G1').setValue('Last Name');
SpreadsheetApp.getActiveSheet().getRange('H1').setValue('Email');
SpreadsheetApp.getActiveSheet().getRange('I1').setValue('Phone');
}
//Auto-Resize Columns
function ResizeColumns() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
sheet.autoResizeColumn(1);
sheet.autoResizeColumn(2);
sheet.autoResizeColumn(3);
sheet.autoResizeColumn(4);
sheet.autoResizeColumn(5);
sheet.autoResizeColumn(6);
sheet.autoResizeColumn(7);
sheet.autoResizeColumn(8);
sheet.autoResizeColumn(9);
}
//Sort by last name
function Sort() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
sheet.sort(7);
}
//Save PDF
function SavePDF(optSSId, optSheetId) {
// If a sheet ID was provided, open that sheet, otherwise assume script is
// sheet-bound, and open the active spreadsheet.
var ss = (optSSId) ? SpreadsheetApp.openById(optSSId) : SpreadsheetApp.getActiveSpreadsheet();
// Get URL of spreadsheet, and remove the trailing 'edit'
var url = ss.getUrl().replace(/edit$/, '');
// Get folder containing spreadsheet, for later export
var parents = DriveApp.getFileById(ss.getId()).getParents();
if (parents.hasNext()) {
var folder = parents.next();
} else {
folder = DriveApp.getRootFolder();
}
// Get array of all sheets in spreadsheet
var sheets = ss.getSheets();
// Loop through all sheets, generating PDF files.
for (var i = 0; i < sheets.length; i++) {
var sheet = sheets[i];
// If provided a optSheetId, only save it.
if (optSheetId && optSheetId !== sheet.getSheetId()) continue;
//additional parameters for exporting the sheet as a pdf
var url_ext = 'export?exportFormat=pdf&format=pdf' //export as pdf
+
'&gid=' + sheet.getSheetId() //the sheet's Id
// following parameters are optional...
+
'&size=letter' // paper size
+
'&portrait=false' // orientation, false for landscape
+
'&fitw=true' // fit to width, false for actual size
+
'&sheetnames=false&printtitle=false&pagenumbers=false' // hide optional headers and footers
+
'&gridlines=true' // hide/show gridlines
+
'&fzr=false'; // do not repeat row headers (frozen rows) on each page
var options = {
headers: {
'Authorization': 'Bearer ' + ScriptApp.getOAuthToken()
}
}
var response = UrlFetchApp.fetch(url + url_ext, options);
var blob = response.getBlob().setName(ss.getName() + ' - ' + sheet.getName() + '.pdf');
folder.createFile(blob);
}
}
/**
* Dummy function for API authorization only.
* From: https://stackoverflow.com/a/37172203/1677912
*/
function forAuth_() {
DriveApp.getFileById("Just for authorization"); // https://code.google.com/p/google-apps-script-issues/issues/detail?id=3579#c36
}
Great use for an add-on. In order to make it work as an add-on you need to create an onOpen() trigger so that the users can interact with your code.
Refer to the onOpen() docs here: https://developers.google.com/apps-script/guides/triggers/#onopen
See an example here: https://developers.google.com/apps-script/add-ons/#user_interfaces

Configure Google Apps Script for Sheets to stop Slack post when no updates

I have set up google sheets to monitor an a folder for updates. When a file is uploaded within the last 24 hours, it updates my google sheet (perfect). Then it messages the designated slack channel (perfect). I want this script to run every minute and only send an update to slack when a new file was uploaded in that 1 minute time frame. How do I update the code?!?
Problems
1) if no files updated, it sends a message to slack still saying undefined for the file URL
problem message in slack
2) if multiple files uploaded, and the google sheet updates, slack only gets a message for one of the files. only one link sent to slack image
Thoughts
1) I could add a function for slack and create a hourly trigger to check the drive file for updates then a trigger for the slack function to go off
2) regardless if one solves it, I need to figure out the code so there is no slack message when there is no update.
/***************************************************
Script will send an slack notification to Slack Channel
when a file is uploaded to monitored Drive Upload folder. SDH 5.29.17
***************************************************/
function checkForChangedFiles() {
// edit this line below with the ID "XXXXXXXxxXXXwwerr0RSQ2ZlZms" of the folder you want to monitor for changes
var folderID = '"' + "XXXXX" + '"';
var folderSearch = folderID + " " + "in parents";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
// Get the spreadsheet slack channel
var channel = sheet.getRange("F1").getValue();
// Get the spreadsheet time zone
var timezone = ss.getSpreadsheetTimeZone();
var today = new Date();
// Find files modified in the last 24 hours, Run script next day, and set below to 24 hours
// 60 * 1000 = 60 second
// 60* (60 * 1000) = 60 mins which is 1 hour
// 24* (60* (60 * 1000)) = 1 day which 24 hours
var oneDayAgo = new Date(today.getTime() - 1 * 60 * 1000);
// var oneDayAgo = new Date(today.getTime() - 1 * 60 * 1000);
var startTime = oneDayAgo.toISOString();
var search = '(trashed = true or trashed = false) and '+ folderSearch +' and (modifiedDate > "' + startTime + '")';
var files = DriveApp.searchFiles(search);
var row = "", count=0;
while( files.hasNext() ) {
var file = files.next();
var fileName = file.getName();
var fileURL = file.getUrl();
var lastUpdated = Utilities.formatDate(file.getLastUpdated(), timezone, "yyyy-MM-dd HH:mm");
var dateCreated = Utilities.formatDate(file.getDateCreated(), timezone, "yyyy-MM-dd HH:mm")
row += "<li>" + lastUpdated + " <a href='" + fileURL + "'>" + fileName + "</a></li>";
sheet.appendRow([dateCreated, lastUpdated, fileName, fileURL]);
count++;
}
// add function for SLACK? then I can have slack run for each update and the first function run daily :)
var url = "https://hooks.slack.com/services/XXXX";
var payload = {
"channel" : channel,
"username" : "DriveUpload", // <-- optional parameter, use if you want to override default "robot" name
"text" : "Upload! *''"+fileName+"''* "+ fileURL, // <-- REQUIRED parameter
"icon_emoji": ":robot_face:", // <-- optional parameter, use if you want to override default icon,
//"icon_url" : "http://image" // <-- optional parameter, use if you want to override default icon
}
sendToSlack_(url,payload)
}
function sendToSlack_(url,payload) {
var options = {
"method" : "post",
"contentType" : "application/json",
"payload" : JSON.stringify(payload)
};
return UrlFetchApp.fetch(url, options)
}
From your question, this sample supposes as follows.
When a file is uploaded within the last 24 hours, it updates my google sheet (perfect).
Then it messages the designated slack channel (perfect).
For Question 1
Reason :
At your script, when it runs, it posts to Slack every time. So even if there no updated files, "undefined" is posted.
Solution :
Using if (updatedfiles.length > 0) {, it posts at only when there are update file.
In this case, when there is no update, the data is not posted.
For Question 2
Reason :
fileName and fileURL created at while loop are variable each other. So only last file in files retrieved by searchFiles() is used for posting.
Solution :
Using var updatedfiles = [];, it imports information of updated files to the array. it post them using "attachments": updatedfiles as the payload.
Help document for attachments is https://api.slack.com/docs/message-attachments.
Modified Script
The modified script is as follows. In my environment, this payload of script works fine. But if this doesn't work at your environment, it may require a debug.
var updatedfiles = []; // <--- ##Added
while( files.hasNext() ) {
var file = files.next();
var fileName = file.getName();
var fileURL = file.getUrl();
var lastUpdated = Utilities.formatDate(file.getLastUpdated(), timezone, "yyyy-MM-dd HH:mm");
var dateCreated = Utilities.formatDate(file.getDateCreated(), timezone, "yyyy-MM-dd HH:mm")
row += "<li>" + lastUpdated + " <a href='" + fileURL + "'>" + fileName + "</a></li>";
sheet.appendRow([dateCreated, lastUpdated, fileName, fileURL]);
count++;
updatedfiles.push({"text": "Upload! *''"+fileName+"''* "+ fileURL}); // <--- ##Added
}
if (updatedfiles.length > 0) { // <--- ##Added
var url = "https://hooks.slack.com/services/XXXX";
var payload = {
"channel" : channel,
"username" : "DriveUpload",
"attachments": updatedfiles, // <--- ##Added
"icon_emoji": ":robot_face:",
}
sendToSlack_(url, payload);
}
It modified checkForChangedFiles(). Script above var row = "", count=0; has not been modified. So above script shows only the modified part. It showed the modified parts as <--- ##Added.
If I misunderstand your questions, I'm sorry.