Alternative to arrayToDataTable for date column - date

I'm new to stackexchange so my apologies if this question is too extensive or already answered somewhere I couldn't find. You can find the spreadsheet here, the script here and the dashboard (dev version) here.
I have been banging my head on handling dates in the google app script visualization for days.
My ultimate goal is to make a dashboard that includes an annotated timeline as well as other charts based on a data set in a spreadsheet. I have started this process using Mogsdad tutorial on creating a 3-tier google visualization dashboard, where the data is pulled from external spreadsheet and then pulled into the DataTable using arrayToDataTable. Everything worked great out of the box. However, my data contains dates, so I added a date column to the original data, but alas arrayToDataTable doesn't accept date type per this post. So when a Date column is added i get the following result:
ScriptError: The script completed but the returned value is not a
supported return type.
I have tried multiple approaches to ensure even date formatting: options includes putting the values in the date column through new Date(dateColumn[i]), dateColumn[i].toJSON() (renders the dash board, but dates aren't able to be processed), forced date formats in the spreadsheet (yyyy-MM-dd), using the DataView outlined in the post above (dashboards don't get past 'Loading'), and such.
So my question is what is the alternatives to arrayToDataTable that will accept date columns in this 3-tier approach? Or alternatively, what are the errors in the below methods?
For all the cases when I have attempted to add columns I have changed the code from var data = google.visualization.arrayToDataTable(response,false) to var data = google.visualization.DataTable()
I have tried the following:
Manually adding columns and manually adding data (not working)
//Add Columns
data.addColumn('string','Name');
data.addColumn('string','Gender');
data.addColumn('number','Age');
data.addColumn('number','Donuts eaten');
data.addColumn('date','Last Donut Eaten');
//Add Rows
data.addRows([
['Miranda','Female', 22,6,6],
['Jessica','Female',22,6,12],
['Aaron','Male',3,1,13]
]);
Automatically adding the rows without dates (The rows are added, but it only works if there are no date columns)
//Add Rows
for (var i=1; i<response.length; i++) {
data.addRow(response[i]);
}
Manually adding columns and automatically adding rows (not working, combination of 1 and 2)
Automatically adding the columns with loops (not working, neither if dates or not)
for (var i=0; i<response[0].length; i++) {
if (response[1][i] instanceof Date) { //Checks if first value is Date
data.addColumn('date',response[0][i]);
};
else if (response[1][i] instanceof Number) //Checks if first value is Number
data.addColum('number',response[0][i]);
else data.addColumn('string',response[0][i]; //Otherwise assume string
};
Thank you so much for your help!

you can use the Query (google.visualization.Query) class to pull the data from the spreadsheet,
this will convert the date column properly...
google.charts.load('current', {
packages:['table']
}).then(function () {
var queryURL = 'https://docs.google.com/spreadsheets/d/1aaxYNLCuPz3o3TA1jdryenUP01Qbkdaut4AR5eIhe9s/edit#gid=0';
var query = new google.visualization.Query(queryURL).send(function (response) {
var data = response.getDataTable();
// show column types
for (var i = 0; i < data.getNumberOfColumns(); i++) {
console.log(data.getColumnLabel(i), '=', data.getColumnType(i));
}
// draw table chart
var table = new google.visualization.Table(document.getElementById('chart-table'));
table.draw(data);
});
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart-table"></div>
note: the example uses jsapi to load the library,
this library should no longer be used.
according to the release notes...
The version of Google Charts that remains available via the jsapi loader is no longer being updated consistently. Please use the new gstatic loader.js from now on.
this will only change the load statement, see above snippet...

Related

How to convert Date and Time with mailmerge google slides from sheets as it is in cell

I have created a table with which I can record our check in times of our employees with the help of a generated Qr code in each line.The data in the table is generated as slides and converted into pdf. For this I use a script that I got to work with your help and it works. Here I would like to thank you especially #tanaike.
My problem is that the date and time are not copied to the slides to be generated as indicated in the cell but completely with Central European time and I added in the script to look in column if its empty to generate the slide. If it's not empty don't do anything. As I said everything is working except this two things.
I must confess I did not try to correct it somehow because I had already shot the script and I made some here despair. It would be really great if you write me the solutions and I can take them over. I will share the spreadsheet with you and the screenshot with ae and time. Thanks for your time and effort to help people like us; we are really trying.
As another approach, when I saw your question, I thought that if your Spreadsheet has the correct date values you expect, and in your script, you are retrieving the values using getValues, getValues is replaced with getDisplayValues(), it might be your expected result.
When I saw your provided sample Spreadsheet, I found your current script, when your script is modified, how about the following modification?
From:
var sheetContents = dataRange.getValues();
To:
sheetContents = dataRange.getDisplayValues();
Note:
When I saw your sample Spreadsheet, it seems that the column of the date has mixed values of both the string value and the date object. So, if you want to use the values as the date object using getValues, please be careful about this.
Reference:
getDisplayValues()
Added:
About your 2nd question of I mean that when a slide has been generated, the script saves the link from the slide in column D if the word YES is in column L. How do I make the script create the slide if there is JA in the column L and there is no link in column D. is a link in column D, the script should not generate a slide again. Thus, the script should only generate a slide if column D is empty and at the same time the word JA is in column L., when I proposed to modify from if (row[2] === "" && row[11] === "JA") { to if (row[3] == "" && ["JA", "YES"].includes(row[11])) {, you say as follows.
If ichanged as you descripted if (row[3] == "" && ["JA", "YES"].includes(row[11])) { i got this error. Syntax error: Unexpected token 'else' Line: 21 File: Code.gs
In this case, I'm worried that you might correctly reflect my proposed script. Because when I tested it, no error occurs. So, just in case, I add the modified script from your provided Spreadsheet as follows. Please test this.
Modified script:
function mailMergeSlidesFromSheets() {
var sheet = SpreadsheetApp.getActiveSheet();
var dataRange = sheet.getDataRange();
sheetContents = dataRange.getDisplayValues(); // Modified
sheetContents.shift();
var updatedContents = [];
var check = 0;
sheetContents.forEach(function (row) {
if (row[3] == "" && ["JA", "YES"].includes(row[11])) { // Modified
check++;
var slides = createSlidesFromRow(row);
var slidesId = slides.getId();
var slidesUrl = `https://docs.google.com/presentation/d/${slidesId}/edit`;
updatedContents.push([slidesUrl]);
slides.saveAndClose();
var pdf = UrlFetchApp.fetch(`https://docs.google.com/feeds/download/presentations/Export?exportFormat=pdf&id=${slidesId}`, { headers: { authorization: "Bearer " + ScriptApp.getOAuthToken() } }).getBlob().setName(slides.getName() + ".pdf");
DriveApp.getFolderById("1tRC505IWtTj8nnPB7XyydvTtCJmOb6Ek").createFile(pdf);
// Or DriveApp.getFolderById("###folderId###").createFile(pdf);
} else {
updatedContents.push([row[3]]);
}
});
if (check == 0) return;
sheet.getRange(2, 4, updatedContents.length).setValues(updatedContents);
}
function todaysDateAndTime() {
const dt = Utilities.formatDate(new Date(),Session.getScriptTimeZone(),"MM:dd:yyyy");
const tm = Utilities.formatDate(new Date(),Session.getScriptTimeZone(),"HH:mm:ss");
Logger.log(dt);
Logger.log(tm);
}

My google sheets function does the job when run from editor but gives different outcome when trigered by Form submit

I have a google form and a sheet that collects the responses which of course always appear at the bottom. I have been using the following script to copy the last response (which is always on the last row) from the Response sheet (Form Responses 2) to row two of another sheet (All Responses). When run by a trigger on Form Submit the script inserts a blank row into All Responses, then the copied values into another row above the blank row. Please can you help and tell me why and how I might change the script so the blank row is not added:
function CopyLastrowformresponse () {
var ss = SpreadsheetApp.getActive();
var AR = ss.getSheetByName("All Responses");
var FR = ss.getSheetByName("Form responses 2");
var FRlastrow = FR.getLastRow();
AR.insertRowBefore(2);
FR.getRange(FRlastrow, 1, FRlastrow, 22).copyTo(AR.getRange("A2"), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}
A few things could be going on here.
You're getting a number of rows equal to FRlastrow, when I think you only want to be getting 1 row.
Apps Script has buggy behavior with onFormSubmit() triggers, so you may to check duplicate triggers (see this answer).
The script isn't fully exploiting the event object provided by onFormSubmit(). Specifically, rather than getting the last row from one sheet, you could use e.values, which is the same data.
I would change the script to be something like this:
function CopyLastrowformresponse (e) {
if (e.values && e.values[1] != "") { // assuming e.values[1] (the first question) is required
SpreadsheetApp.getActive()
.getSheetByName("All Responses")
.insertRowBefore(2)
.getRange(2, 1, 1, e.values.length)
.setValues([e.values]);
}
}
But, ultimately, if all you want to do is simply reverse the order of the results, then I'd ditch Apps Script altogether and just use the =SORT() function.
=SORT('Form responses 2'!A:V, 'Form responses 2'!A:A, FALSE)

What is the easiest way to import excel/google sheet spreadsheet to a Cloud Firestore database?

I need to import a large table of data into my database in one go. I currently have it as an Excel file but I am happy to copy it to Google sheets etc.
So far I've added a few entries manually directly via cloud firestore.
Is there a solution already out there to achieve this?
I think the easiest way to export table data into Firestore is to use a Google Apps Script Library (for Google Sheets).
Step 1
Make a Copy of THIS example Google Spreadsheet I created as an example
Step 2
From the menu of YOUR copy of the Example Google Spreadsheet from step 1, click Tools > Script Editor. This should open up the example Google App Script associated with the example spreadsheet.
Step 3
Follow the Steps for installing this library and then update the script with the following:
email
key
projectID
These variables are generated by going to the Google Service Accounts page. This will require that you already have a Firebase or Google Cloud account setup. I won't repeat all the steps that are already iterated in in the aforementioned Github writeup. Just follow them carefully, and realize that the private_key is THE ENTIRE KEY starting with -----BEGIN PRIVATE KEY-----\n, EVERYTHING in between, and ending with \n-----END PRIVATE KEY-----\n
Step 4
Insert a page on your spreadsheet that contains your data, and EDIT the script to use your new sheet name and your data. I have HEAVILY commented the script so it's pretty clear what almost every line of code is doing. For those of you that just want to peek at the Google App Script that's behind this spreadsheet, here's the code:
// Note this Script uses an external library as per this page:
// https://github.com/grahamearley/FirestoreGoogleAppsScript
// This solution requires a Google Spreadhseet and a Firebase Account
// FOLLOW THE INSTRUCTIONS ON THAT GITHUB REPO TO SETUP NEEDED API KEYS!!!
//Global Variables
const ss = SpreadsheetApp.getActiveSpreadsheet(); // Gets the active "workbook"
const sheet = ss.getSheetByName('Restaurants'); // CHANGE TO YOUR SHEET NAME
const headerRowNumber = 1; // If you have more than one row for your header, then change this value to number of header rows
// If you want to mark modified cells, then set up a trigger for the following function:
// Edit > Current Project Triggers > (+ Add Trigger) > On Edit Spreadsheet etc
function onEdit(e) {
var cell = ss.getActiveCell(); //This will also effectively get our row
var dataRange = sheet.getDataRange(); //This checks for all rows/columns with data
var modifiedCol = dataRange.getLastColumn()-1; //Our "modified" column should be the second to last
if (cell.getColumn() < modifiedCol && cell.getRow() > headerRowNumber) { //If we edit any cells to the left of our modified column and below our header...
var celltoMark = sheet.getRange(cell.getRowIndex(),modifiedCol) //Get the R/C cordinates of cell to place modified time
celltoMark.setValue(new Date()); //write timestamp to that cell
}
};
// This will parse any comma separated lists you create in any of your fields (useful for search words, or attributes, etc)
function listToArray(list) {
var ogArray = list.split(","); //Input is a comma separated list
let trimmedArr = ogArray.map(string => string.trim()); //Let's strip out the leading/trailing whitespaces if any
return trimmedArr; //return the cleaned array
}
function writeToFireStore() {
const email = 'sheets#yourprojectid.iam.gserviceaccount.com'; // CHANGE THIS!!!
const key = '-----BEGIN PRIVATE KEY-----\nYOURPRIVATEKEY\n-----END PRIVATE KEY-----\n'; // CHANGE THIS!!!
const projectID = 'yourprojectid'; // CHANGE THIS!!!
var firestore = FirestoreApp.getFirestore(email, key, projectID);
const collection = "MySpreadsheetData"; // Name of your Firestore Database "Collection"
var dataRange = sheet.getDataRange().offset(headerRowNumber, 0, sheet.getLastRow() - headerRowNumber); //this is your data range
var data = dataRange.getValues(); // this is an array of your datarange's values
var lastCol = dataRange.getLastColumn(); // this is the last column with a header
var newDoc = {}; // Instantiate your data object. Each one will become the data for your firestore documents
// r = row number in this case
for (let r = 0; r <= dataRange.getLastRow(); r++) {
//Logger.log("R = ",r);
var cellMod = dataRange.getCell(r+1, lastCol-1);
var cellFS = dataRange.getCell(r+1, lastCol);
var cellModVal = cellMod.getValue();
var cellFSVal = cellFS.getValue();
//
// IMPORTANT READ THIS IMPORTANT READ THIS IMPORTANT READ THIS IMPORTANT READ THIS IMPORTANT READ THIS!!!
// Well, read the line below...
if (r > 2) break; //Comment Out this line after you're done testing otherwise you'll write all your rows to firestore after every run
newDoc[r] = {
name : data[r][1],
category : data[r][2],
cuisine : data[r][3],
address: {
add1: data[r][4],
add2: data[r][5],
city: data[r][6],
state: data[r][7],
zip: data[r][8]
},
tel: data[r][9],
searchterms: listToArray(data[r][10]) //Let's turn a csv list into an array
}
// For the sake of efficiency and to save $, we WON'T create documents that have already been created...
// ...and we won't update documents that have a fireStore Timestamp that's newer than a Modified Timestamp
// If there's not firestore timestamp in our spreadsheet, then let's create firestore document and update firestore stamp:
if (!cellFSVal) {
var now = new Date(); //Generate timestamp right now
try {
firestore.createDocument(collection + "/" + data[r][0], newDoc[r]); // To Use Your Own Document ID
//Now let's insert a timestamp in our Firestore TS column of the sheet so we know it's been added to Firestore
cellFS.setValue(now);
Logger.log("Row ",r,"(",data[r][1],") is NEW and was added to FireStore Successfully");
} catch (e) {
Logger.log("Error: ",e," : Document with same name already existed in Firestore.");
}
}
//var if FS Timestamp exists but, the modified time stamp is greater, let's update the Firstore Document
else if ((cellFSVal) && (cellModVal > cellFSVal)) {
try {
firestore.updateDocument(collection + "/" + data[r][0], newDoc[r]);
//Now let's insert a timestamp in our Firestore TS column of the sheet so we know it's been updated to Firestore
cellFS.setValue(now);
Logger.log("Row ",r,"(",data[r][1],") updated/edited.");
} catch (e) {
Logger.log("Error: ",e," : Document existed, we tried updating it, but jack shit happened.");
}
}
else {
Logger.log("Row ",r,"(",data[r][1],") Already in Firestore & hasn't been modified. Skipped.");
}
}
}
Step 5
Once your script is modified to your needs, it's time to run the script. Simply save it (File > Save), then choose the function "writeToFireStore" from the "Select function" dropdown selector in the menu bar (in between the icon of the bug, and the lightbulb), then hit the PLAY icon (to the left of the bug icon). At this point, you will likely be prompted to accept permissions to run the script, (which you need to accept if you want to run the script). Once you've accepted the permissions, then run the "writeToFireStore" function again if it hasn't already run, and voila!
NOTES:
I created a function that automatically writes a Modified Timestamp to the second to last column in the target sheet, and when you run the function, writes a Firestore Timestamp (so you know which rows have been successfully exported to Firestore). This way, if you run the firestore function again, and you haven't changed the data on your sheet, it won't bother updating the database with the same data (and will save you money and/or server resources). For this functionality to work, you must setup project Triggers (which is explained inside the script in the comments).

Unable to set a javascript date object to a cell hidden by filter

I have a script which checks certain conditions to send reminders emails or sms to my clients. the only issues I'm finding is that if I try to write a cell that is hidden by a filter, the script executes but the data is not changed in any way.
I'll write a short version of the whole script:
function test(){
var nowTime = new Date();
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var lastrow =sheet.getLastRow();
var lastcol =sheet.getLastColumn();
var fullData =cell.offset(0, 0,lastrow-
cell.getRow()+1,lastcol).getValues();
var cell = sheet.getRange("A2");
var i=0;
while (i<fullData.length){
var reminderType =0;
var row = fullData[i];
if (row[0] == 1) {sendreminder();cell.offset(i, 2).setValue(new Date());}
}
}
if for example the first column has hidden all the rows with 1 the script executes and sends all the reminders but ignore the setvalue(), if the rows are visible it works perfectly.
One solution can de to remove the filter but would be very annoying since we use the filter a lot and the script is triggered by time every 10 minutes so I would be working on the sheet and suddenly the filter get removed to run the script.
I have tried with cell.offset, getrange etc.. without any success ... Ideas?
EDIT: The problem seems to be only if I try to write a date if (row[0] == 1) {cell.offset(i, 1).setValue(new Date());}
For instance I'm writing another information (a number) in a different column and that cell gets updated.
The rest remain the same
here is a test sheet I created:
https://docs.google.com/spreadsheets/d/1-FNDGmvCc8nRFTG65Sj9L2RhGn8R3DtwR3llwBG5-FA/edit#gid=0
For now I added this code to save the state of the filter and reestablish it at the end. It's not really elegant and still add a lot of chances of something being messed up or the script failing but it's the less invasive solution I came out with. Does someone has a better one?
// at the beginning
if (sheet1.getFilter()){
var filterRange= sheet1.getFilter().getRange();
var filterSetting = [];
var i=0;
while (i<filterRange.getNumColumns()-1) {filterSetting[i]= sheet1.getFilter().getColumnFilterCriteria(i+1);sheet1.getFilter().removeColumnFilterCriteria(i+1);i++; }
}
// At the end
if (sheet1.getFilter()){
var i=0;
while (i<filterRange.getNumColumns()-1) {if (filterSetting[i]) sheet1.getFilter().setColumnFilterCriteria(i+1, filterSetting[i]);i++; }
}
Given the fact the problem is not writing in the hidden row as it was pointed out, the solution is to use the formatdate to format the value before writing it in the cell.
Not ideal either since regional formattings might prevent the script from working on different accounts but sure better solution than disabling the filters.
here is the code i needed to add:
var nowTime = new Date();
var timeZone = Session.getScriptTimeZone();
var nowTimeFormatted = Utilities.formatDate(nowTime, timeZone, 'MM/dd/yyyy HH:mm:ss');
any idea on how to improve this script or to solve the original problem without workarounds is appreciated :)

Updating Embedded Charts in Google Docs with Google Apps Script

TLDR; How do I use the Script Editor in Docs to update an embedded Sheets chart in the document?
I know there is a script that does this for Google Slides, but I'm trying to do it in Google Docs and can't find any documentation thereof.
https://developers.google.com/slides/how-tos/add-chart#refreshing_a_chart
To be specific, I have a Google Doc. This doc contains about thirty tables and embedded charts that are all linked to a separate Google Sheet. All thirty come from a single Google Sheet. Now, I can have our non-geeky people click on all thirty "Update" hover buttons every time the spreadsheet changes, but I expect the spreadsheet to change a lot, and I would like to idiot-proof the document to ensure it's always up-to-date. As far as I can tell, this isn't a feature Google Apps does out of the box, so I wanted to write a script to do it.
But I can't find any way to access an EmbeddedChart from a Google Doc.
If I could run something like this like you can in Sheets, I could probably figure it out, but I can't:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var charts = sheet.getCharts();
for (var i in charts) {
var chart = charts[i];
// Update the chart
}
}
Although docs has the following function, DocumentApp.getActiveDocument(), an object Document doesn't contain a function getCharts(). I believe they're considered to be Images, but Images don't have an update function.
Is it even possible to access/update an EmbeddedChart in Docs using a script? Maybe by running the script through the spreadsheet on edit and updating the doc from there? Seems weird that you can do it in Slides of all things, but not Docs.
in Google Docs DOM charts as InlineImages here is the script that logs images attributes. but in they looks to be read only and documentation for this method ends with dead end: "null if the element contains multiple values for this attribute." https://developers.google.com/apps-script/reference/document/inline-image#getLinkUrl()
function myFunction() {
var doc = DocumentApp.getActiveDocument()
var body = doc.getBody()
var pars = body.getParagraphs()
var atts = img.getAttributes();
// Log the paragraph attributes.
for (var att in atts) {
Logger.log(att + ":" + atts[att]);
}
for (var par in pars) {
Logger.log(pars[par].getText());
var chN = pars[par].getNumChildren()
Logger.log(chN);
if(chN>0){
var type = pars[par].getChild(0).getType()
Logger.log(type);
if(type=="INLINE_IMAGE"){
var atts = pars[par].getChild(0).getAttributes()
Logger.log(JSON.stringify(atts));
for (var att in atts) {
Logger.log(att + ":" + atts[att]);
}
}
}
}
return
}
The goal is to update the ranges of each chart to encompass the number of columns and rows of the sheet referenced by the range.
The approach is to enumerate all ranges within all charts within all sheets, clear the range and re-add it with the right size. The getDataRange() function is a convenient way to get the right-sized range. The only tricky part is that you don't update the chart directly -- you need to get its builder, modify that, rebuild it, and then update the chart with the built object.
for (const sheet of SpreadsheetApp.getActive().getSheets()) {
for (const chart of sheet.getCharts()) {
const embeddedChartBuilder = chart.modify();
const ranges = chart.getRanges();
embeddedChartBuilder.clearRanges();
for (const range of ranges) {
const dataRange = range.getSheet().getDataRange();
embeddedChartBuilder.addRange(dataRange);
}
const embeddedChart = embeddedChartBuilder.build();
sheet.updateChart(embeddedChart);
}
}
The code sample assumes that your ranges are intended to encompass all the columns and rows of the referenced sheet.