Is it possible to set limit for array in firestore? - javascript

i am planning to create an array of only 5 elements max in firestore like this
Array a = [1,2,3,4,5]
then add element 6 it will look like this
Array a = [2,3,4,5,6]

This cloud function (found here: https://github.com/firebase/functions-samples/blob/master/limit-children/functions/index.js) does what you want in Realtime Database:
'use strict';
const functions = require('firebase-functions');
// Max number of lines of the chat history.
const MAX_LOG_COUNT = 5;
// Removes siblings of the node that element that triggered the function if there are more than MAX_LOG_COUNT.
// In this example we'll keep the max number of chat message history to MAX_LOG_COUNT.
exports.truncate = functions.database.ref('/chat').onWrite((change) => {
const parentRef = change.after.ref;
const snapshot = change.after
if (snapshot.numChildren() >= MAX_LOG_COUNT) {
let childCount = 0;
const updates = {};
snapshot.forEach((child) => {
if (++childCount <= snapshot.numChildren() - MAX_LOG_COUNT) {
updates[child.key] = null;
}
});
// Update the parent. This effectively removes the extra children.
return parentRef.update(updates);
}
return null;
});
I believe you can adapt it for Firestore.

Related

firestore query error for subcollections: rest.collection is not a function

I want to count the number of branches of all restaurants. "Branch" is a sub collection of "Restaurants". When I try to do this query, there is an error:
rest.collection is not a function
Here's my code. How can I fix it?
async function getBranch(){
const size = 0;
const restRef = await firebase.firestore().collection('Restaurants').get();
restRef.forEach((rest) => {
const branchRef = rest.collection('Branch').get();
size = size + branchRef.size();
})
return size;
}
you have to provide id of restaurants to get the subcollection. So better make reference for Restaurants and get all branches
async function getBranch(){
const size = 0;
const restRef = firebase.firestore().collection('Restaurants');
const restRes = await restRef.get();
restRef.forEach((rest) => {
const branchRef = await restRef.doc(rest.id).collection('Branch').get();
size = size + branchRef.size();
})
return size;
}
You could do as follow, by using Promise.all() (untested).
async function getBranch(){
let size = 0;
const restQS = await firebase.firestore().collection('Restaurants').get();
const promises = [];
restQS.forEach((rest) => {
promises.push(rest.collection('Branch').get());
});
const querySnapshotsArray = await Promise.all(promises);
querySnapshotsArray.forEach(qs => {
size += qs.size; // <= Note that size is a property of the QuerySnapshot, not a method
})
return size;
}
Another approach would be to query all your branch doc with a Collection group query, if the only collections named Branch are subcollection of Restaurants.
const branchQS = await firebase.firestore().collectionGroup('Branch').get();
return branchQS.size;
HOWEVER,
you should note that this implies that you read ALL the documents of ALL the (sub)collections each time you want to get the number of branch documents and, therefore, it has a cost.
So, if your collections have a lot of documents, a more affordable approach would be to maintain a set of distributed counters that hold the number of documents. Each time you add/remove a document, you increase/decrease the counters.
See here in the doc for more details, as well as this answer.

Why does firebase cloud-function javascript promise run more than the number of loop invocations?

I have a cloud function that is triggered when a sale/purchase is committed into firestore. This function's purpose is to update the inventory level centrally.
The function works just fine if I'm updating an item's inventory at only 1 warehouse, but doing so for multiple warehouses has unexpected behavior. I'm looping through all the warehouses that are affected to calculate the total inventory level changes, and every iteration kicks-off a javascript promise.
The problem seems to occur with the way the promises are invoked. E.g: if I want to update 3 warehouses and loop 3 times, somehow 5 promises are being kicked-off. This is visible through the logs. I've researched similar questions here, but the solutions were suggested while firestore was still in beta and might not be the right way forward. (Firestore transactions getting triggered multiple times resulting in wrong data)
Here is the code
export const onTransactionCreate = functions.firestore
.document('/companies/{companyId}/sub_transactions/{transId}')
.onCreate(async (snapshot, context) => {
const transId = context.params.transId
const stock_transaction: IStockTransaction = <IStockTransaction>snapshot.data()
const trans_type: TRANS_TYPE = stock_transaction.trans_type
const promises: any[] = []
stock_transaction.lineItems.forEach((element, index) => {
const ITEM_GUID = element.item_guid
const is_increasing = isIncreasingTransaction(element.line_trans_type)
const delta_stock = element.qty_transaction * (is_increasing ? 1 : -1)
const TARGET_BRANCH_ID = element.target_branch_guid
const itemRef = db.collection(FIRESTORE_PATHS.COL_COMPANIES).doc(companyId).
collection(FIRESTORE_PATHS.SUB_COMPANIES_ITEMS).
doc("" + ITEM_GUID)
const item_promise = db.runTransaction(async t => {
try {
const item_doc = await t.get(itemRef)
const item_branch_quantities: IBranchQuantity[] = (item_doc.data()!.branch_quantities || new Array())
const item_branch_ids: string[] = (item_doc.data()!.available_branch_ids || new Array())
const branch_index = item_branch_ids.indexOf(TARGET_BRANCH_ID)
console.log(`${transId} Line Item ${index}, after document.get(), search branch index: ${branch_index}`)
if (branch_index !== -1) {
const prev_qty = item_branch_quantities[branch_index]
const updated_qty = prev_qty.quantity + delta_stock
item_branch_quantities[branch_index] = {
item_guid: prev_qty.item_guid,
branch_guid: prev_qty.branch_guid,
quantity: updated_qty
}
console.log(`${transId} Line Item ${index} Updating qty # item ${delta_stock}, prev qty ${prev_qty.quantity}`)
} else {
item_branch_ids.push(TARGET_BRANCH_ID)
item_branch_quantities.push({
item_guid: element.item_guid,
branch_guid: TARGET_BRANCH_ID,
quantity: delta_stock
})
console.log(`${transId} Line Item ${index} Adding qty # item ${delta_stock}`)
}
t.update(itemRef, {
branch_quantities: item_branch_quantities,
available_branch_ids: item_branch_ids
})
} catch (err) {
throw new Error(err)
}
})
promises.push(item_promise)
});
return Promise.all(promises)
})
we have found the solution by reading this article.
A transaction consists of any number of get() operations followed by any number of write operations such as set(), update(), or delete(). In the case of a concurrent edit, Cloud Firestore runs the entire transaction again. For example, if a transaction reads documents and another client modifies any of those documents, Cloud Firestore retries the transaction. This feature ensures that the transaction runs on up-to-date and consistent data.
lineItems.forEach(element => {
const delta_transaction = element.qty * (isLineTransIncrease(element.line_trans_type) ? 1 : -1)
const itemRef = db.collection('companies').doc(companyId).collection('sub_items').doc("" + element.item_guid)
const p = db.runTransaction(t => {
return t.get(itemRef)
.then(doc => {
let item_branch_quantities: IBranchQuantity[] = doc.data()!.branch_quantities
let item_branch_ids: string[] = doc.data()!.available_branch_ids
if (!item_branch_quantities)
item_branch_quantities = new Array()
if (!item_branch_ids)
item_branch_ids = new Array()
const branch_index = item_branch_ids.indexOf(current_branch_id)
if (branch_index !== -1) {
const prev_qty = item_branch_quantities[branch_index]
const updated_qty: number = prev_qty.quantity + delta_transaction
item_branch_quantities[branch_index] = {
item_guid: prev_qty.item_guid,
branch_guid: prev_qty.branch_guid,
quantity: updated_qty
}
} else {
item_branch_ids.push(current_branch_id)
item_branch_quantities.push({
item_guid: element.item_guid,
branch_guid: current_branch_id,
quantity: delta_transaction
})
}
t.update(itemRef, {
branch_quantities: item_branch_quantities,
branch_ids: item_branch_ids
})
})
})
item_update_transactions.push(p)
});
return Promise.all(item_update_transactions)
})
function isLineTransIncrease(line_trans: number): boolean {
return (line_trans === 1) || (line_trans === 2)
}

Firebase issue, I need to take out values from first two nodes

I try to achieve the following: When count is changed to "2" I need the function to push the JSON, named "updates", to the specific place in database, and take names from PlayerQueue node (0:"Mik", 1:"Bg" etc.) and put it into the database as "id". So the thing is that I need it to take first two nodes (0 and 1 in this case) and take names out of it (Mik and Bg) and put them in the database as id1 and id2 (in this database I have only one id value but I will add it later), the issue is that I can't figure out how to take out names from the first two nodes.
My database:
And here is my code
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import { resolve } from 'url';
//Game/queue/{queueId}/PlayerCount
admin.initializeApp(functions.config().firebase);
exports.createGame = functions.database.ref('Game/queue/PlayerCount').onUpdate((change, context) => {
const ref1 = admin.database().ref('/Game/queue/PlayerQueue').limitToFirst(1);
var tmp:String = 'esh';
ref1.once("value")
.then(result => {
tmp = result.val();
console.log(tmp)
var updates = {};
updates['id'] = tmp
updates['visible'] = {
place: 'a1',
sign: 'rock'
};
const after = change.after.val();
if(after.count == 2){
return admin.database().ref('/Game/allGames').push(updates);
}
return null
}).catch(reason => {
console.log(reason)
});
return null;
});
Since you're looking for the first two child nodes in the queue, you should order by their ID and then limit to getting 2 children:
const query = admin.database().ref('/Game/queue/PlayerQueue').orderByKey().limitToFirst(2);
Then you can listen for the value:
query.once("value").then(snapshot => {
snapshot.forEach(child => {
console.log(child.key+": "+child.val());
});
});
The above purely solves the "getting the first two child nodes".
Update: to get the two children into separate variables, you can do something like this:
query.once("value").then(snapshot => {
var first, second;
snapshot.forEach(child => {
console.log(child.key+": "+child.val());
if (!first) {
first = child.val();
}
else if (!second) {
second = child.val();
}
});
if (first && second) {
// TODO: do something with first and second
}
});
Use Firestore unless you have a valid reason to use the Realtime Database.
See Cloud Firestore triggers documentation on how to take action .onUpdate to qty 2. See documentation example below
exports.updateUser = functions.firestore
.document('users/{userId}')
.onUpdate((change, context) => {
// Get an object representing the document
// e.g. {'name': 'Marie', 'age': 66}
const newValue = change.after.data();
// ...or the previous value before this update
const previousValue = change.before.data();
// access a particular field as you would any JS property
const name = newValue.name;
// perform desired operations ...
});

Google Cloud Function OnWrite how to get just the newly added child

I'm trying to get just the newly added child in my database, therefore I tried to subtract snapshot.after from snapshot.before. Unfortunately this one doesnt work. My code below:
const functions = require('firebase-functions');
// Create and Deploy Your First Cloud Functions
// https://firebase.google.com/docs/functions/write-firebase-functions
// exports.helloWorld = functions.https.onRequest((request, response) =>
//{
// response.send("Hello from Firebase!");
// })
exports.gameLoopBeing = functions.database.ref('/Chat/{pushId}')
.onWrite((snapshot, context) => {
//I want to retrieve the pushID
const original = snapshot.before.val();
const newValue = snapshot.after.val();
const difference = newValue-original
console.log('alool',context.params.pushId, difference);
// const uppercase = original.toUpperCase();
return snapshot.ref.set(original);
});
If you want to get the newly added data from a function you need to do this;
exports.gameLoopBeing = functions.database.ref('/Chat/{pushId}').onWrite((snapshot, context) => {
if (snapshot.after.exists() && !snapshot.before.exists()) {
const new_data = snapshot.after.val();
} else {
return null;
}
});

Continue on Null Value of Result (Nodejs, Puppeteer)

I'm just starting to play around with Puppeteer (Headless Chrome) and Nodejs. I'm scraping some test sites, and things work great when all the values are present, but if the value is missing I get an error like:
Cannot read property 'src' of null (so in the code below, the first two passes might have all values, but the third pass, there is no picture, so it just errors out).
Before I was using if(!picture) continue; but I think it's not working now because of the for loop.
Any help would be greatly appreciated, thanks!
for (let i = 1; i <= 3; i++) {
//...Getting to correct page and scraping it three times
const result = await page.evaluate(() => {
let title = document.querySelector('h1').innerText;
let article = document.querySelector('.c-entry-content').innerText;
let picture = document.querySelector('.c-picture img').src;
if (!document.querySelector('.c-picture img').src) {
let picture = 'No Link'; } //throws error
let source = "The Verge";
let categories = "Tech";
if (!picture)
continue; //throws error
return {
title,
article,
picture,
source,
categories
}
});
}
let picture = document.querySelector('.c-picture img').src;
if (!document.querySelector('.c-picture img').src) {
let picture = 'No Link'; } //throws error
If there is no picture, then document.querySelector() returns null, which does not have a src property. You need to check that your query found an element before trying to read the src property.
Moving the null-check to the top of the function has the added benefit of saving unnecessary calculations when you are just going to bail out anyway.
async function scrape3() {
// ...
for (let i = 1; i <= 3; i++) {
//...Getting to correct page and scraping it three times
const result = await page.evaluate(() => {
const pictureElement = document.querySelector('.c-picture img');
if (!pictureElement) return null;
const picture = pictureElement.src;
const title = document.querySelector('h1').innerText;
const article = document.querySelector('.c-entry-content').innerText;
const source = "The Verge";
const categories = "Tech";
return {
title,
article,
picture,
source,
categories
}
});
if (!result) continue;
// ... do stuff with result
}
Answering comment question: "Is there a way just to skip anything blank, and return the rest?"
Yes. You just need to check the existence of each element that could be missing before trying to read a property off of it. In this case we can omit the early return since you're always interested in all the results.
async function scrape3() {
// ...
for (let i = 1; i <= 3; i++) {
const result = await page.evaluate(() => {
const img = document.querySelector('.c-picture img');
const h1 = document.querySelector('h1');
const content = document.querySelector('.c-entry-content');
const picture = img ? img.src : '';
const title = h1 ? h1.innerText : '';
const article = content ? content.innerText : '';
const source = "The Verge";
const categories = "Tech";
return {
title,
article,
picture,
source,
categories
}
});
// ...
}
}
Further thoughts
Since I'm still on this question, let me take this one step further, and refactor it a bit with some higher level techniques you might be interested in. Not sure if this is exactly what you are after, but it should give you some ideas about writing more maintainable code.
// Generic reusable helper to return an object property
// if object exists and has property, else a default value
//
// This is a curried function accepting one argument at a
// time and capturing each parameter in a closure.
//
const maybeGetProp = default => key => object =>
(object && object.hasOwnProperty(key)) ? object.key : default
// Pass in empty string as the default value
//
const getPropOrEmptyString = maybeGetProp('')
// Apply the second parameter, the property name, making 2
// slightly different functions which have a default value
// and a property name pre-loaded. Both functions only need
// an object passed in to return either the property if it
// exists or an empty string.
//
const maybeText = getPropOrEmptyString('innerText')
const maybeSrc = getPropOrEmptyString('src')
async function scrape3() {
// ...
// The _ parameter name is acknowledging that we expect a
// an argument passed in but saying we plan to ignore it.
//
const evaluate = _ => page.evaluate(() => {
// Attempt to retrieve the desired elements
//
const img = document.querySelector('.c-picture img');
const h1 = document.querySelector('h1')
const content = document.querySelector('.c-entry-content')
// Return the results, with empty string in
// place of any missing properties.
//
return {
title: maybeText(h1),
article: maybeText(article),
picture: maybeSrc(img),
source: 'The Verge',
categories: 'Tech'
}
}))
// Start with an empty array of length 3
//
const evaluations = Array(3).fill()
// Then map over that array ignoring the undefined
// input and return a promise for a page evaluation
//
.map(evaluate)
// All 3 scrapes are occuring concurrently. We'll
// wait for all of them to finish.
//
const results = await Promise.all(evaluations)
// Now we have an array of results, so we can
// continue using array methods to iterate over them
// or otherwise manipulate or transform them
//
results
.filter(result => result.title && result.picture)
.forEach(result => {
//
// Do something with each result
//
})
}
Try-catch worked for me:
try {
if (await page.$eval('element')!==null) {
const name = await page.$eval('element')
}
}catch(error){
name = ''
}

Categories