Export data from Google AppMaker Datasource automatically - javascript

Does anyone know how we can generate report from data in datasource in Google AppMaker automatically (e.g generate report at 12a.m.) instead of manually click export data in deployments every time user need the report.
I have seen something similar on Exporting data out of Google AppMaker but also no one tried to answer that.
Really appreciate if there is anyone who know how to solve this :)

This can be achieved by using Installable Triggers.
Say for example, you have a model with students data that has three fields; name(string), age(number) and grade(number). On the server script you can write something like this:
//define function to do the data export
function dataExport() {
//create sheet to populate data
var fileName = "Students List " + new Date(); //define file name
var newExport = SpreadsheetApp.create(fileName); // create new spreadsheet
var header = ["Name", "Age", "Grade"]; //define header
newExport.appendRow(header); // append header to spreadsheet
//get all students records
var ds = app.models.students.newQuery();
var allStudents = ds.run();
for(var i=0; i< allStudents.length; i++) {
//get each student data
var student = allStudents[i];
var studentName = student.name;
var studentAge = student.age;
var studentGrade = student.grade;
var newRow = [studentName, studentAge, studentGrade]; //save studen data in a row
newExport.appendRow(newRow); //append student data row to spreadsheet
}
console.log("Finished Exporting Student Data");
}
//invoke function to set up the auto export
function exportData(){
//check if there is an existing trigger for this process
var existingTrigger = PropertiesService.getScriptProperties().getProperty("autoExportTrigger");
//if the trigger already exists, inform user about it
if(existingTrigger) {
return "Auto export is already set";
} else { // if the trigger does not exists, continue to set the trigger to auto export data
//runs the script every day at 1am on the time zone specified
var newTrigger = ScriptApp.newTrigger('dataExport')
.timeBased()
.atHour(1)
.everyDays(1)
.inTimezone("America/Chicago")
.create();
var triggerId = newTrigger.getUniqueId();
if(triggerId) {
PropertiesService.getScriptProperties().setProperty("autoExportTrigger", triggerId);
return "Auto export has been set successfully!";
} else {
return "Failed to set auto export. Try again please";
}
}
}
Then, to delete/stop the auto export, in case you need to, you can write the following on the server script too:
function deleteTrigger() {
//get the current auto export trigger id
var triggerId = PropertiesService.getScriptProperties().getProperty("autoExportTrigger");
//get all triggers
var allTriggers = ScriptApp.getProjectTriggers();
//loop over all triggers.
for (var i = 0; i < allTriggers.length; i++) {
// If the current trigger is the correct one, delete it.
if (allTriggers[i].getUniqueId() === triggerId) {
ScriptApp.deleteTrigger(allTriggers[i]);
break;
//else delete all the triggers found
} else {
ScriptApp.deleteTrigger(allTriggers[i]);
}
}
PropertiesService.getScriptProperties().deleteProperty("autoExportTrigger");
return "Auto export has been cancelled";
}
You can check the demo app right here.
The reference to the script properties service is here.
The reference to the Time Zones list is here.
I hope this helps!

It seems that you are looking for daily database backups. App Maker Team recommends migrating apps to Cloud SQL if you haven't done this so far. Once you start using Cloud SQL as your data backend you can configure backups through Google Cloud Console:
https://cloud.google.com/sql/docs/mysql/backup-recovery/backups

Related

How to iterate on the links to google forms in the cells of a spreadsheet column to get their questions?

I have a Google spreadsheet with links to questionnaires. I wanted to know how to get the questions from each of the questionnaires.
I guess I have to do: at best, use the script editor and iterate on the lines, and at worst, do webscraping.
const puppeteer = require('puppeteer');
function appendString() {
var range = SpreadsheetApp.getActiveSheet().getActiveRange();
var numRows = range.getNumRows();
var numCols = 0;
for (var i = 1; i <= numRows; i++) {
for (var j = 1; j <= numCols; j++) {
var currentValue = range.getCell(i,j).getValue();
await page.goto(currentValue);
const pollFrame = page.frames().find() # From there I have some difficulties
}
}
}
But I get the following error:
SyntaxError: await is only valid in async function (ligne 10, fichier "Code.gs")
Not to mention the async problem or the buttonthat I still have to click, the selection looks like this:
<div class="freebirdFormviewerViewItemsItemItemTitle exportItemTitle freebirdCustomFont" id="i1" role="heading" aria-level="3" aria-describedby="i.desc.310938276">How often did you fly before the Covid-19 epidemic? </div>
But the IDs don't follow a logical numerical order, so I don't know how to extract them automatically.
Then I don't know how to do it. I wonder if it's simpler because they're products from the same supplier.
Here is the equivalent in csv format:
https://docs.google.com/forms/d/e/1FAIpQLSfzocEm6IEDKVzVGOlg8ijysWZyAvQur0NheJb_I_xozgKusA/viewform?usp=sf_link
https://docs.google.com/forms/d/e/1FAIpQLScrm0ZTrvlONf5MX37N93H_FajNzfbNy9ZtitX-Vq9PPuLPHA/viewform?usp=sf_link
https://docs.google.com/forms/d/e/1FAIpQLSeolFSh3OyS_XpX1lRIJP-8CH8WG0X0hL98SM9d85LqC22Bow/viewform?usp=sf_link
Update
So I tried the anwer kindly posted by Neven Subotic's:
// this array will store forms and their questions
let formAndQuestions = [];
let formIds = ["https://docs.google.com/forms/d/e/1FAIpQLSfzocEm6IEDKVzVGOlg8ijysWZyAvQur0NheJb_I_xozgKusA/viewform?usp=sf_link",
"https://docs.google.com/forms/d/e/1FAIpQLScrm0ZTrvlONf5MX37N93H_FajNzfbNy9ZtitX-Vq9PPuLPHA/viewform?usp=sf_link",
"https://docs.google.com/forms/d/e/1FAIpQLSeolFSh3OyS_XpX1lRIJP-8CH8WG0X0hL98SM9d85LqC22Bow/viewform?usp=sf_link"]
formIds.forEach( formId => {
const form = FormApp.openById( formId );
// lets get the name
const formName = form.getTitle();
// first we get all items
const allItemsInThisForm = form.getItems();
// then we get filter out anything that is not a questions
const allQuestionsInThisForm = allItemsInThisForm.filter( item => {
return isThisItemAQuestion( item )
});
// now we store them in our object
formAndQuestions.push( {
formId: formId,
formName: formName,
questions: allQuestionsInThisForm
})
});
// this function is used to only get the itemTypes you want
// see reference for more information
function isThisItemAQuestion( item ){
const itemType = item.getType();
const validQuestionItemTypes = [ FormApp.ItemType.TEXT, "add others here" ]
let isValid = false;
validQuestionItemsTypes.forEach( validItemType => {
if( itemType == validItemType ) {
isValid = true;
}
});
return isValid
}
Unfortunately I obtain the following error message with the following details Exception: No item with the given ID could be found, or you do not have permission to access it. (line 9, "const form = FormApp.openById( formId );"). I don't understand. As you can see in the gif, I can open these links, so I should have the permission to access them isn't it?
I also tried Ruben's ideas with:
// this array will store forms and their questions
let formAndQuestions = [];
let formIds = ["https://docs.google.com/forms/d/e/1FAIpQLSfzocEm6IEDKVzVGOlg8ijysWZyAvQur0NheJb_I_xozgKusA/viewform?usp=sf_link"]//,
//"https://docs.google.com/forms/d/e/1FAIpQLScrm0ZTrvlONf5MX37N93H_FajNzfbNy9ZtitX-Vq9PPuLPHA/viewform?usp=sf_link",
//"https://docs.google.com/forms/d/e/1FAIpQLSeolFSh3OyS_XpX1lRIJP-8CH8WG0X0hL98SM9d85LqC22Bow/viewform?usp=sf_link"]
function scrapeForms(){
formIds.forEach( formId => {
// The code below logs the HTML code of the Google home page.
var response = UrlFetchApp.fetch(formId);
results = response.getElementsByClassName("freebirdFormviewerViewItemsItemItemTitleContainer");
Logger.log(results.getContentText())
});
}
But got back:
TypeError: response.getElementsByClassName is not a function (ligne 13, fichier "Code")
According to What is this Javascript "require"? require is not part of the standard JavaScript an AFAIK it's not supported by Google Apps Script.
By the other hand, the error message can't be easily solved as Google Apps Script Chrome V8 engine doesn't support async functions. Related Is google apps script synchronous?
If you will be using Google Apps Script, and you are the form owner or a form editor, instead of trying to web scraping a Google Form use the Forms Service of Google Apps Script. For this you will need the form ../edit URLs instead of the ../viewform URLs. On the official docs there is a quickstart that might help you https://developers.google.com/apps-script/quickstart/forms.
You could use openByUrl to "open" a form. It will not be actually opened in your web browser, it will be opened on the server side. Then you could use getItems to get all the questions, sections, images, videos, etc.
If you aren't the form owner or a form editor then you should use UrlFetchApp service and somehow parse the web page source code of each form based on the position of the questions. Related question: Google Sheets: How to import the following data?
Also, if the form has several sections you should do a post request to emulate clicking on the next button in order to get the second and following sections. There are more "also if the form has..." but I will stop here as the main part of question was already answered, I think.
You first want to get all the forms, so place those in an array:
const formIds = ["someId", "anotherId", "andSoOn"]
Then, lets use the FormApp to get the form and all items. Items can be of different types, see documentation.
// this array will store forms and their questions
let formAndQuestions = [];
formIds.forEach( formId => {
const form = FormApp.openById( formId );
// lets get the name
const formName = form.getTitle();
// first we get all items
const allItemsInThisForm = form.getItems();
// then we get filter out anything that is not a questions
const allQuestionsInThisForm = allItemsInThisForm.filter( item => {
return isThisItemAQuestion( item )
});
// now we store them in our object
formAndQuestions.push( {
formId: formId,
formName: formName,
questions: allQuestionsInThisForm
}
});
// this function is used to only get the itemTypes you want
// see reference for more information
function isThisItemAQuestion( item ){
const itemType = item.getType();
const validQuestionItemTypes = [ FormApp.ItemType.TEXT, "add others here" ]
let isValid = false;
validQuestionItemsTypes.forEach( validItemType => {
if( itemType == validItemType ) {
isValid = true;
}
});
return isValid
}
Then you can initially log out the results and see what it looks like:
Logger.log( formAndQuestions )
Item Types

Using Google App Script to get values from sheets and display them in text box

So, Google recently updated Google App Script API and added lots of nice features, however, in the process, they also depreciated LOTS of API. I have been working on a Library Database user interface for the place I work on my college campus, and when I wanted to update my app to the new API, a lot of things broke, and I can't figure out how to make them work again.
What I am trying to do is get a value from a Google Sheets file, and simply put that value in a text box on the web app. Currently I cannot get that work work. In addition, I discovered something that was troublesome, and that is, the debugger seems to not be correct. I know, bold accusation. Let me try to show you.
Code.gs
function doGet(e) {
var html = HtmlService.createHtmlOutputFromFile('index')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
return html;
}
function searchBooks(searchItem, searchType){
var sI = searchItem;
Logger.log(sI);
var sT = searchType;
Logger.log(sT);
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var ss = sheets[0];
var itemDataRange = ss.getRangeByName("itemInformation");
var selectedItem = null; //the item that will be returned
//var selectedSearch = searchItem;
var titles = sheet.getRange("K2:K9507").getValues(); //get the titles of the items
var authors = sheet.getRange("J2:J9507").getValues(); //get the authors in the sheet
var barcodes = sheet.getRange("B2:B9507").getValues(); //get the barcodes in the sheet
var itemsArray = new Array();
if (sT == '')
{
return null;
}
else if (sT.value == 'Please select type...')
{
var test = "this works";
Logger.log(test);
return selectedItem;
}
else if(sT == 'Barcode')
{
var selectedBarcode = sI;
for(var i = 0; i < barcodes.length; i++) //search for the barcode
{
if(barcodes[i] == selectedBarcode)
{
selectedItem = titles[i];
break; //break immediately because barcodes are not duplicated
}
}
if(selectedItem != null)
{
return selectedItem;
}
else
{
selectedItem = "No book(s) found";
return selectedItem;
}
return selectedItem;
}
}
...
index.html
<script>
function bookSearch()
{
var searchItem = String(document.getElementById('searchItem').value.toLowerCase());
var searchType = String(document.getElementById('searchType').value.toLowerCase());
google.script.run.withSuccessHandler(bookFound).searchBooks(searchItem, searchType);
}
...
function bookFound(selectedItem)
{
document.getElementById("bookResultBox").innHTML = selectedItem;
alert(selectedItem);
}
</script>
When I test this code, and put a search value with the category "Barcodes" selected, I successfully get console logs of the data being brought into the function searchBooks, however the debug console says that the variables sI, sT, searchItems, and searchType are all undefined.
I've also been having trouble trying to figure out the proper API calls to use to search through the spreadsheet (when dealing with stuff like getRangeByName). I think there might be a slightly different way to do this since the big update. I may have had it working before I changed some of the code, although I started changing a lot of it when I was trying to figure out WHY nothing was displaying. When I saw at the "undefined" debug console logs, it scared me a bit. I can't tell if I'm messing up, or the API is messing up.
Any help is much appreciated in advance :)
There's probably an error in your code. It's probably coming from line:
var itemDataRange = ss.getRangeByName("itemInformation");
Your variable ss is not a spreadsheet class, it's a sheet class. You can't get a RangeByName of a sheet class. There is no getRangeByName() method of the Sheet class.
I'd change your code to this:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var itemDataRange = ss.getRangeByName("itemInformation");
If you need to get the first sheet:
var theFirstSheet = ss.getSheets()[0];

getting responseID of latest form submission in google apps

I have a google form. Every time it is submitted, the answers go into a google spreadsheet. I am trying to write an app script that triggers every time the form is submitted, and adds an "edit" link in the column to the right of the data from the form. The link itself is easy to generate, google has a method called getEditResponseURL(). (https://developers.google.com/apps-script/reference/forms/form-response)
But everytime I run it, I am getting the error "TypeError: Cannot call method "getResponses" of null."
Here is my code:
function addeditlink(e) {
// Get the active sheet
var sheet = SpreadsheetApp.getActiveSheet();
// Get the active row
var row = sheet.getActiveCell().getRowIndex();
//get the form
var form = FormApp.getActiveForm();
//get latest form response
var responses = form.getResponses();
var lastResponse = responses[responses.length - 1];
//get edit URL
var editurl = lastResponse.getEditResponseUrl();
//build link
var editlink = "Edit";
//place edit link in column R (index 18)
if (sheet.getRange(row, 18).getValue() == "") {
sheet.getRange(row, 18).setValue(editlink);
}
}
Any help? Thanks!
The solution is to remove:
var form = FormApp.getActiveForm(); //this is WRONG
and replace with:
var form = FormApp.openByID(' ID here ')
There is no "active form", because this script is being run in sheets, not forms.
I think you can only call FormApp.getActiveForm() from a script attached to a form, whereas your script is contained in a GSheet. I couldn't find a way to easily gets forms that used this sheet as its destination so what I've did was get all of the forms and then looked at the destination id of each and checked if it is the same as this spreadsheet. Once you've got your Form object you can get the responses. Feels a bit long winded would love to know if anyone knows a quicker way.
There are also a few exceptions that FormApp throws that you have to cope with.
Here's the function I use:
/**
* Find the first form that is linked to a specific spreadsheet
*
* #param {string} spreadsheet id
* #return {object} Form or null
*/
function getFormByDestinationId_(spreadsheetId) {
var formFiles = DriveApp.getFilesByType('application/vnd.google-apps.form');
var form;
var formFile;
var formId;
var destinationId;
while (formFiles.hasNext()) {
formFile = formFiles.next();
formId = formFile.getId();
// Throws an error if ID invalid
try {
form = FormApp.openById(formId);
} catch (error) {
if (error.name === "Exception") {
// Just ignore it
} else {
throw error;
}
}
// Form.getDestinationId() throws an error if there is no destination id
try {
destinationId = form.getDestinationId();
} catch (error) {
if (error.name === "Exception") {
// Just ignore it
} else {
throw error;
}
}
if (destinationId !== spreadsheetId) {
continue;
}
return form;
}
return null;
} // getFormByDestinationId_()
The only line using: getResponses() method is this one:
var responses = form.getResponses();
Your error:
Cannot call method "getResponses" of null
Means that form is null. If form is null, then this line:
//get the form
var form = FormApp.getActiveForm();
is not working. So, why isn't it working? There is nothing wrong with the code, so it must be a different problem. If there was an active form, that code would return a form type. This means that there is no form bound to the script. getActiveForm()
Returns the form to which the script is container-bound.
Your script is not "container-bound" to the form. Your script is bound to the spreadsheet.
The documentation states:
To interact with forms to which the script is not container-bound, use openById(id) or openByUrl(url) instead.
You can bind your script to the form by opening the script editor from the edit page of the form. But, there's no need to do that if you want to keep your script bound to the spreadsheet.
The line var form = FormApp.getActiveForm(); isn't going to work in your spreadsheet script.
The problem with using the Event Object e with an installable trigger, is that it looks like you can't get the response URL.
google_sheets_events
This means that you need to use openById(id) or openByUrl(url) inside the script bound to the spreadsheet, or move all your script to the form.
Here is how to get the edit url from script in the spreadsheet:
// Open a form by ID.
var form = FormApp.openById('1234567890abcdefghijklmnopqrstuvwxyz');
Now the problem is, that you can only get the Edit Response URL: getEditResponseUrl() through the "FormResponse" class. So you need the Form Responses.
var formResponses = form.getResponses();
But that's all the responses, you need the last one.
var lastResponseIndex = formResponses.length - 1;
var lastResponse = formResponses[lastResponseIndex];
var editURL = lastResponse.getEditResponseUrl();
or:
function getEditURLofLastResponse() {
// Open a form by ID.
var form = FormApp.openById('Your Form ID');
var formResponses = form.getResponses();
//get last respnse
var lastResponseIndex = formResponses.length - 1;
var lastResponse = formResponses[lastResponseIndex];
var editURL = lastResponse.getEditResponseUrl();
Logger.log(editURL);
}
Just an observation:
You are using an e argument: function addeditlink(e) {. But I don't see it being used in your code. That makes me wonder if you are using an "installable" trigger, as opposed to a "simple" trigger.
It's possible to get the values that were just submitted with e.values or e.namedValues. But you can't get the Edit URL with the Event Object.

How can I automatically update a view using Node.js?

I have a cache and a cron-job for pulling/receiving and saving data. I want that the view updates itself. For testing it should update every 5 seconds. In this way I should see, when the data is pulled, saved to cache and finally will appear on the view.
I found out that socket.io can help me. I haven't found a suited example to my purpose. Can somebody help me?
Here is a snippet of my code from app.js:
var dashboardData = "";
//----fired when cache has changed
myCache.on( "set", function( key, value ){
stats = JSON.stringify(myCache.getStats());
stats = JSON.parse(stats);
console.log(stats.keys);
var content = JSON.stringify(value);
content = JSON.parse(content);
dashboardData = content;
});
//----load dashboard view
app.post('/dashboard', function(req, res) {
projectName = req.body.selectProjectName;
if(dashboardData == ""){
var content = "";
dashboard.dashboard(req,res, projectName, content);
} else {
dashboard.dashboard(req,res, projectName, dashboardData.variable);
}
});
My current solution: I've added a jquery javascript with an asynchronous ajax call that updates the view every 5 seconds.

how does a extension like autofill form save form data of a profile

I was trying to develop a extension like autofill form .it looks simple but while implementing i got stuck at one point. how do we store data of a profile created permanently. i tried using localStorage in a javaScript to save data but its temporary.Any guidance would be very useful .Just show me the way i will reach the destination
function count()
{
var inputs = $("input:text").length;
alert("inputs are = "+inputs);
var cnt = document.forms.length;
alert(cnt);
for (var a=0;a<document.forms.length;a++)
{
var x=0;
var elem = document.forms[a].elements;
for (x=0;x<document.forms[a].elements.length;x++)
{
//elem[x].value="Hello";
if(x==0)
{
localStorage.setItem('userName', elem[x].value);
}
}
alert("Your user is: " + localStorage.getItem('userName'));
}
}
Use the Preferences service to store the data in the user's profile. For example:
var prefService = Cc["#mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService);
var prefs = prefService.getBranch("extensions.YOUREXTENSIONABBREVIATION.");
//elem[x].value = "Hello";
if (x==0) {
prefs.setCharPref('userName', elem[x].value);
prefService.savePrefFile(null);
}
alert("Your user is: " + prefs.getCharPref('userName'));
For more information, look here:
https://developer.mozilla.org/en-US/docs/Adding_preferences_to_an_extension
Include a "defaults.js" file in the "defaults/preferences" folder of your extension. Initialize each item you're going to store in the preference file in case you try to retrieve something before it exists.

Categories