Auto sync data from firebase to google sheets - javascript

Every time a change is made in my RealtimeDB, I need to get those data to my Google Sheet without pressing any button. How to achieve this?
PS. The following code does exactly what I mentioned but not automatically
function getData() {
var firebaseUrl = "https://databse.firebaseio.com/Items";
var base = FirebaseApp.getDatabaseByUrl(firebaseUrl);
var data = base.getData();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
var num = 2;
range = sheet.getRange("A"+num+":D"+num+"");
var range2 = sheet.getRange("A2:D");
range2.clearContent();
for(var i in data) {
var values = [
[ data[i].itemname, data[i].itembarcode, data[i].itemprice]
];
range.setValues(values);
num += 1;
range = ss.getRange("A"+num+":D"+num+"");
}
}

To automatically update the sheet when the database changes, you have two options:
Periodically refresh the sheet with the database contents. You can accomplish this by running your getData function on a trigger.
Listen to realtime updates from the database. While Firebase has this option, it isn't available in the FirebaseApp library.
If you want to push updates to the sheet as they happen, you'll have to push them into your sheet from an environment that does support listening for realtime updates. Doug Stevenson wrote a great example of this on Copying data from Firebase Realtime Database to a Google Sheet in real time via Cloud Functions.

Related

is there a way I can update multiple non-contiguous google sheet ranges using apps script all at the same time? (using SpreadsheetApp not sheets api)

lets assume we have a table of 4 x 4 starting from A1, now normally if I want to update it's values at once I'll just do
sheet.getRange("A1:D4").setValues(values);
now if the data is not contiguous so I have 3 4 x 4 separate tables with other data between them but I have them named as named ranged i.e (table1, table2, table3), can I do sth like:
sheet.getNamedRanges([table1range, table2range, table3range]).setValues([table1data, table2data, table3data]);
instead of:
sheet.getRange(table1range).setValues(table1data);
sheet.getRange(table2range).setValues(table2data);
sheet.getRange(table3range).setValues(table3data);
so that now all the 3 tables will be updated in the same operation instead of 3 different operations to save execution time?
thank you in advance.
I believe your goal is as follows.
You want to achieve the script like sheet.getNamedRanges([table1range, table2range, table3range]).setValues([table1data, table2data, table3data]); in order to reduce the process cost.
Namely, you want to put the values for each range to Google Spreadsheet using Google Apps Script. And, the range is
In this case, I think that Sheets API can be used for achieving your goal as follows. In this case, this thread might be useful. Ref When Sheets API is used for your situation, the process cost can be reduced rather than that of the method for putting the values to each range in the loop using Spreadsheet Service (SpreadsheetApp). Ref
const spreadsheetId = "###"; // Please set the Spreadsheet ID.
const ranges = [table1range, table2range, table3range]; // Please set table1range, table2range, table3range as A1Notation.
const values = [table1data, table2data, table3data]; // Please set table1data, table2data, table3data.
const data = ranges.map((e, i) => ({range: e, values: [[values[i]]]}));
Sheets.Spreadsheets.Values.batchUpdate({data, valueInputOption: "USER_ENTERED"}, spreadsheetId);
But, in this case, I thought that it might be difficult a little to create the request body. So I created a Google Apps Script library as a wrapper for using Sheets API. When this library is used, the sample script is as follows.
1. Install library.
You can see the method for installing the library at here. And, this script uses Sheets API. So please enable Sheets API at Advanced Google services.
2. Sample script:
const ranges = [table1range, table2range, table3range]; // Please set table1range, table2range, table3range as A1Notation.
const values = [table1data, table2data, table3data]; // Please set table1data, table2data, table3data.
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
RangeListApp.getSpreadsheet(spreadsheet).getRangeList(ranges).setValues(values);
When I saw your script, I thought that this sample script might be in the same direction you expect.
When this script is run, each value of values is put to each range of ranges using Sheets API.
References:
Benchmark: Reading and Writing Spreadsheet using Google Apps Script
Method: spreadsheets.values.batchUpdate
RangeListApp
According to Tanaike's proposal, I suggest a slight modification
const ss = SpreadsheetApp.getActiveSpreadsheet()
const sheet = ss.getActiveSheet()
const ranges = [table1range2D, table2range2D, table3range2D];
const values = [data1values2D, data2values2D, data3values2D];
const data = ranges.map((e, i) => ({ range: `'${sheet.getName()}'!${e}`, values : values[i] }));
Sheets.Spreadsheets.Values.batchUpdate({ data, valueInputOption: "USER_ENTERED" }, ss.getId());
for instance
function updateGoogleSheet() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const sheet = ss.getActiveSheet()
const otherSheet = ss.getSheetByName('other')
const table1range2D = 'A1:B2', table2range2D = 'C4:D5', table3range2D = 'E7:F8'
const data1values2D = otherSheet.getRange('A1:B2').getValues(), data2values2D = otherSheet.getRange('C4:D5').getValues(), data3values2D = otherSheet.getRange('E7:F8').getValues()
const ranges = [table1range2D, table2range2D, table3range2D];
const values = [data1values2D, data2values2D, data3values2D];
const data = ranges.map((e, i) => ({ range: `'${sheet.getName()}'!${e}`, values : values[i] }));
Sheets.Spreadsheets.Values.batchUpdate({ data, valueInputOption: "USER_ENTERED" }, ss.getId());
}
where in sheet named 'other' we can find in A1 =sequence(10,10,0,1)

Reading and updating multiple tabs in the same spreadsheet

I have a Google Spreadsheet where I retrieve data using the Google Analytics addon. In a tab I combine all the data and use this specific tab for my Google Datastudio report. In here I analyze my Google Ads campaign data - the costs and the conversions.
This Spreadsheet has multiple tabs (each tab contains a specific conversion) I need to be able to read and write for multiple tabs. It has to read the Google Analytics data and write this data in another sheet.
I originally created this script to read from 1 tab and to write to 1 tab and it was working. Then I noticed I will need to do this for almost all the tabs.
This is my current script:
function readAndWrite() {
var spreadsheetId = 'xxx';
var counter = 0;
var rangeName = ['readingTab1!A16:C','readingTab2!A16:C','readingTab3!A16:C','readingTab4!A16:C','readingTab5!A16:C'];
var rangePush = ['writingTab1','writingTab2','writingTab3','writingTab4','writingTab5','writingTab5'];
var values = Sheets.Spreadsheets.Values.get(spreadsheetId, rangeName[counter]).values;
var data = [];
if (!values) {
Logger.log('No data found.');
} else {
Logger.log('Starting script.');
Logger.log('There are ' + values.length + ' unique rows.');
Logger.log(values[0][2]);
while (counter < 5){
data.length = 0;
for (var row = 0; row < values.length; row++) {
var campaign = values[row][0];
var date = values[row][1];
var cost = values[row][2];
data.push({range: rangePush[counter], values: [[date, campaign, cost]]});
}
counter++;
Sheets.Spreadsheets.Values.batchUpdate({data: data, valueInputOption: "USER_ENTERED"},spreadsheetId);
}
}
Logger.log('Finished.');
}
In the rangeName I have created an array with the names and ranges of my tab where it should read from.
In the rangePush I created an array with the names where it should push the data to.
When I run the script I receive the following error:
GoogleJsonResponseException: API call to sheets.spreadsheets.values.batchUpdate failed with error: Invalid data[1886]: Unable to parse range: writingTab2.
Is there anyone who can see what is going wrong and able to help me out here?
I hope my explanation is clear, if not please let me know.
Thanks!
I believe your goal is as follows.
You want to copy the values from readingTab#!A16:C sheet to writingTab# sheet using Sheets API with Google Apps Script.
Those sheet names are put to the arrays and each index of both is corresponding to the copy and paste sheets.
If my understanding is correct, how about the following modification?
At first, about your error, when I saw your sample Spreadsheet, there are 3 read and write sheets of 'readingTab1!A16:C', 'readingTab2!A16:C', 'readingTab3!A16:C' and 'writingTab1', 'writingTab2', 'writingTab3'. But your script uses while (counter < 5){. By this, the range becomes undefined after counter is more than 4. I thought that this might be the reason of your issue.
In order to achieve your goal by removing this issue, how about the following modified script?
Modified script:
When spreadsheets.values.batchGet and spreadsheets.values.batchUpdate methods are used, the script becomes the following simple script.
function readAndWrite() {
var spreadsheetId = 'xxx';
var rangeName = ['readingTab1!A16:C', 'readingTab2!A16:C', 'readingTab3!A16:C'];
var rangePush = ['writingTab1', 'writingTab2', 'writingTab3'];
var values = Sheets.Spreadsheets.Values.batchGet(spreadsheetId, {ranges: rangeName}).valueRanges;
values.forEach((v, i) => v.range = rangePush[i]);
Sheets.Spreadsheets.Values.batchUpdate({ data: values, valueInputOption: "USER_ENTERED" }, spreadsheetId);
}
In this modified script, it supposes that there are 3 read and write sheets of 'readingTab1!A16:C', 'readingTab2!A16:C', 'readingTab3!A16:C' and 'writingTab1', 'writingTab2', 'writingTab3'. When you add more sheets, please add them to the arrays of rangeName and rangePush.
Note:
In this script, please check the values of rangeName and rangePush and the sheet names of your Spreadsheet again.
This script uses Sheets API. So, please enable Sheets API at Advanced Google services.
I think that in your situation, the Spreadsheet service instead of Sheets API can also achieve your goal. But I think that the process cost will be lower when Sheets API is used. But, when you want to achieve your goal without using Sheets API, you can also use the following script.
function readAndWrite2() {
var spreadsheetId = 'xxx';
var rangeName = ['readingTab1!A16:C', 'readingTab2!A16:C', 'readingTab3!A16:C'];
var rangePush = ['writingTab1', 'writingTab2', 'writingTab3'];
var ss = SpreadsheetApp.openById(spreadsheetId);
rangeName.forEach((r, i) => ss.getRange(r).copyTo(ss.getSheetByName(rangePush[i]).getRange("A1")));
}
References:
Method: spreadsheets.values.batchGet
Method: spreadsheets.values.batchUpdate

manipulate the table locally, i.e. read the table into memory, process and then write to server

I have a script that steps through a sheet, then updates the sheet content and adds calendar items. I noticed that the execution takes up to 3 minutes and I suspect that the server calls are the reason. This is how I access the data today.
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Form Responses");
var ss = SpreadsheetApp.getActiveSpreadsheet();
var startRow = 2; // First row of data to process
Now I wanted to make it better and manipulate the table locally, i.e. read the table into memory, process and then write to server in one go. I am sure there are plenty similar issues on SO for google apps script, if I only could get a good search term. "async bulk request" did not give me good hits on SO. Any hints on available issues or how to search for them?
Then I can start with selfstudy.
This particular best practice is described in Google's documentation here
According to the docs the correct way to read data from a spreadsheet, manipulate it, and then save it again is as follows:
function readAndWriteInBulk() {
// read all the data in the active sheet at once with getValues()
const sheet = SpreadsheetApp.getActiveSheet();
const range = sheet.getDataRange();
const data = range.getValues();
// manipulate the data as you like
for (const row of data) {
// do something to a row
Logger.log(row);
}
// paste the data at once in a new sheet with setValues()
const newSheet = SpreadsheetApp.insertSheet();
const pasteRange = newSheet.getRange(1, 1, data.length, data[0].length);
pasteRange.setValues(data);
}
References:
getValues()
setValues()

SpreadsheetApp / FormApp - Changelog

I have a script that records a log of all changes in a spreadsheet:
function onEdit() {
var timestamp = new Date();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var cell = sheet.getActiveCell();
var user = Session.getActiveUser();
var columnLabel = sheet.getRange(1, cell.getColumn()).getValue();
var changelogSheet = ss.getSheetByName("Changelog");
changelogSheet.appendRow([timestamp, cell.getA1Notation(), columnLabel, cell.getValue(), user.getEmail()]);
}
This codes beautifully creates a row of data in "Changelog" sheet every time there is an edit on "Sheet1". It tells you the date and time, the cell where it was edited, the contents of edit and user who edited it.
My issue is that "Sheet1" is linked to a Google Form that allows users to edit their responses. When users indeed edit their responses on Google Forms, the edit is not registered on my script and there is no log for it.
I presume my script only logs physical edits on the sheet itself.
Is there a way (perhaps using FormApp) to do a similar code that logs every edit on a Google Form?
I really appreciate your help.
You should create a new onFormSubmit trigger that will include similar logic and create a new row in the Google Sheet when a form response is submitted.

Script to delete a user account from G Suite Admin SDK

I am writing a script that fetches a data from a spread sheet and based on every entry entered in the spread sheet, it fetches the data which is basically email addresses and deletes a user's account from the domain.
//** Delete the users on submitting the "Submit" button
function onFormSubmit(e) {
//Logger.log(e);
//Run this function passing on event "e" when the form is submitted.
//Object e has form parameters sourced from the sheet attached to the form
deleteUsers(e);
}
//Logs all the info from the spreadsheet and deletes the user
function deleteUsers() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
for (var i = 0; i < data.length; i++) {
Logger.log('managerEmail: ' + data[i][0]);
Logger.log('email: ' + data[i][1]);
Logger.log('trasferToEmail: ' + data[i][3]);
var managerEmail = data[i][0];
var email = data[i][1];
var trasferToEmail = data[i][3];
var request = {
'url': 'https://www.googleapis.com/admin/directory/v1/users/' + email,
'method' : 'DELETE'
};
}
But I am still unsuccessful in deleting an account. I actually tried to implement it based on this doc https://developers.google.com/admin-sdk/directory/v1/reference/users/delete but didn't know how to use it. Any ideas? Sorry if this is a stupid question! I am a novice in Google scripts.
You're sending e to deleteUsers(), but that function doesn't receive any parameters. No need to access the spreadsheet data when it's already provided by the onFormSubmit()–look at the event object documentation for reference.
function deleteUser(e) {
var data = e.namedValues;
var managerEmail = data["Manager Email"][0]; //You'll need to adjust the field names to match with your form data
var email = data["Email"][0];
var transferToEmail = data["Transfer to Email"][0];
var response = AdminDirectory.Users.remove(email);
}
To make sure that your trigger is correctly set up, first have the form responses get saved to your spreadsheet. Then Edit > Current project's triggers and copy these settings:
To make AdminDirectory work, you need to enable advanced services. (In the script editor, go to Resources > Advanced Google services and switch "on" Admin Directory API. Then click the link at the bottom of the modal to enable the Admin SDK in the API Console.)
If I was mistaken about what data the form is collecting and you really do need to pull the data from spreadsheet (assuming the form isn't connected to the sheet), then you need to create a trigger for when there is a submission to that form. Run this function to install the trigger.
function installFormTrigger() {
var form = FormApp.openById("FORM_ID");
ScriptApp.newTrigger("deleteUsers")
.forForm(form)
.onFormSubmit()
.create();
}
Then your original deleteUsers() function will work almost as you had it, but with the addition of AdminDirectory.
function deleteUsers() {
var sheet = SpreadsheetApp.getActive().getSheetByName("SheetName"); //HIGHLY recommend using this instead
var data = sheet.getDataRange().getValues();
for (var i = 0; i < data.length; i++) {
var managerEmail = data[i][0];
var email = data[i][1];
var trasferToEmail = data[i][3];
var response = AdminDirectory.Users.remove(email);
}
}
Note that in your for loop, you might come across an invalid email or AdminDirectory could through an error, so I'd suggest implementing try...catch and logging.
It would be better to use AdminDirectory.Users.remove(email); rather than making a request to the API like you are doing.
Keep it in a variable if you want to log the response var request = AdminDirectory.Users.remove(data[i][1]);
To activate the AdminDirectory;
Go to Resources -> Advanced Google services
Enable the Admin Directory and then click on the link to the Google API console.
Click Enable APIs and Services
Search for Admin SDK
Click on Admin SDK and then click 'Enable'

Categories