I am using cloud functions to listen on new document created on Firestore.
functions.firestore.document('users/{userId}')
.onCreate((snapshot, context) => {
console.log('params', context.params.userId);
});
The logs show undefined instead of the wildcarded param.
This start happening from 15th dec 2018 at midnight.
Is this a bug related to an update of firestore/cloud functions ?
and how we can bypass this issue?
There seems to be a bug in the Firebase Functions SDK or platform currently (15 December 2018).
Work-around:
Update The proper way to access the parent document ID is through change.after.ref.parent.parent.id or snapshot.ref.parent.parent.id. Note the .parent.parent.
If you are expecting parameters with the document IDs, you can probably work around the problem by using the data provided in the first argument to you function.
Here is an example with an onCreate triggered function:
export const myCreateTriggeredFn = firestore
.document("user/{userId}/friends/{friendId}")
.onCreate((snapshot, context) => {
let { userId, friendId } = context.params;
if (typeof userId !== "string" || typeof friendId !== "string") {
console.warn(`Invalid params, expected 'userId' and 'friendId'`, context.params);
userId = snapshot.ref.parent.parent.id;
friendId = snapshot.id;
}
// Continue your logic here...
});
And for an onWrite triggered function:
export const myChangeTriggeredFn = firestore
.document("user/{userId}/friends/{friendId}")
.onWrite((change, context) => {
let { userId, friendId } = context.params;
if (typeof userId !== "string" || typeof friendId !== "string") {
console.warn(`Invalid params, expected 'userId' and 'friendId'`, context.params);
userId = change.after.ref.parent.parent.id;
friendId = change.after.id;
}
// Continue your logic here...
});
For the sake of completeness and to highlight the bug, both examples shows how you would normally extract the IDs from the context.params and then the added work-around to extract the IDs from the snapshot/change objects.
I'm the Google employee working on the incident. There is a known compatibility issue for customers using the SDK when Firestore was in a private alpha and have not upgraded.
Could affected customers who are running their code with an SDK version newer than 0.6.2 respond? If you are running version 0.6.1 you can upgrade to 0.6.2 with no code changes to fix.
Related
I have looked at similar answers to similar questions but the answers don't specifically apply.
I am seeking the simple document parent auto-id in a firestore onCreate function in js?
The firestore function log reads function returned undefined for documentiD
How do you reference the documentID ?
Firestore Log result :sendMailtransaction Function returned undefined, expected Promise or value
//Send the transaction email
exports.sendMailtransaction = functions.firestore
.document('Companys/{companyid}/Transaction/{transactionid}')
.onCreate((snap, context) => {
const transDocument = functions.firestore.document('Companys/{companyid}/Transaction/{transactionid}');
const documentiD = transDocument.documentID;
const mailOptions = {
from: 'L App<report#sample.com>', // You can write any mail Address you want this doesn't effect anything,
to: snap.data().companyemailcf, // This mail address should be filled with any mail you want to read it,
bcc: 'admin#l.com',
subject: 'L Record, New Tranaction ',
html: `<h1>Confirmed Transaction</h1>
<p>
<b>Ref: </b>${documentiD}<br>
<b>Datetime: </b>${snap.data().datetime}<br>
<b>User: </b>${snap.data().user}<br>
<b>Vehicle: </b>${snap.data().vehicle}<br>
</p>`
};
}
You don't need to query the created document to receive it in a function as you already have the document in the onCreate parameters: snapshot is the first parameter.
Example:
export const eventCreated = functions.firestore
.document('/events/{eventId}')
.onCreate(snapshot => {
console.log("this is the new document id: ", snapshot.id)
console.log("Document content:", snapshot.data())
})
In your case, use snap.id
Firebase Functions guide is here
The snapshots reference is explained here
I am aware Firestore doesn't support '!=' queries, but I was wondering if anybody has run into a similar situation as below:
Here is my db structure:
Posts -> postId -> postDocument -> likedBy -> uid
What I'm looking to do is only show posts that don't have the current user's uid in the 'likedBy' subcollection. That itself isn't possible, but I'm struggling to find even a semi-decent work around.
Currently I get all the posts and do the check locally to display the correct ones. Is this possible with perhaps a magic cloud function something?
You might find success and use cases beyond this one by maintaining a user's feed and then only calling that at runtime. I utilize this method and have found I'm given a lot of freedom and Cloud Functions let me dictate what types of posts show and under what changes are new posts added to a user's feed.
The way I do it is I look for new posts via an onCreate cloud function and then look up who should see that post, etc. and add it to each of their feeds.
In your case I can see it being used by looking for new likes on a post. On new likes, it can remove the post from the user's feed.
An example of a function (edited for brevity) that I use to add posts to the user's follower's feeds. By grabbing using a collectionGroup query I can query the list of all users who follow the author of the post.
The schema looks like this:
Users (collection)
--- User1 (document)
------- Following (collection of people User1 is following)
----------- FollowingUser1 (document, contains a uid of "followed" user)
----------- FollowingUser2
and the Cloud Function:
exports.newReview = functions.firestore
.document('reviews/{reviewId}')
.onCreate((snap, context) => {
var reviewId = context.params.reviewId
var reviewData = snap.data()
var userFollowers = db.collectionGroup('following').where('uid', '==', userId)
var followingTransaction = db.runTransaction(transaction => {
return transaction.get(userFollowers).then(restDocs => {
reviewData['added_via'] = 'following'
restDocs.forEach(doc => {
var followerId = doc.ref.parent.parent.id
var followerRef = db.collection(`feeds/${followerId}/posts`).doc(reviewId)
transaction.set(followerRef, reviewData);
})
return true
});
});
return followingTransaction.then(values => {
console.log(reviewData)
var shouldPostToTwitter = reviewData.postToTwitter
return Promise.resolve()
}) .catch(error => {
console.log(error)
return Promise.reject(new Error("Error deleting"));
});
});
DB look something look this:
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL
});
const db = admin.firestore()
I am working on a NodeJs application and I am using mongoose node package.
Sample Code
I am using following method to create dynamic collections and these collections sometimes fail to persist the data in database -
const Mongoose = require("mongoose");
const Schema = new Mongoose.Schema({
// schema goes here
});
module.exports = function (suffix) {
if (!suffix || typeof suffix !== "string" || !suffix.trim()) {
throw Error("Invalid suffix provided!");
}
return Mongoose.model("Model", Schema, `collection_${suffix}`);
};
I am using this exported module to create dynamic collections based on unique ids passed as suffix parameter. Something like this (skipping unnecessary code) -
const saveData = require("./data-service");
const createModel = require("./db-schema");
// test 1
it("should save data1", function (done) {
const data1 = [];
const response1 = saveData(request1); // here response1.id is "cjmt8litu0000ktvamfipm9qn"
const dbModel1 = createModel(response1.id);
dbModel1.insertMany(data1)
.then(dbResponse1 => {
// assert for count
done();
});
});
// test 2
it("should save data2", function (done) {
const data2 = [];
const response2 = saveData(request2); // here response2.id is "cjmt8lm380006ktvafhesadmo"
const dbModel2 = createModel(response2.id);
dbModel2.insertMany(data2)
.then(dbResponse2 => {
// assert for count
done();
});
});
Problem
The issue is, test 2 fails! It the insertmany API results in 0 records failing the count assert.
If we swap the the order of the tests, test 1 will fail.
If I run the two tests separately, both will pass.
If there are n tests, only first test will pass and remaining will fail.
Findings
I suspected the mongoose model creation step to be faulty as it is using the same model name viz. Model while creating multiple model instances.
I changed it to following and the tests worked perfectly fine in all scenarios -
return Mongoose.model(`Model_${suffix}`, Schema, `collection_${suffix}`);
Questions
This leaves me with following questions -
Am I following correct coding conventions while creating dynamic collections?
Is suspected code the actual cause of this issue (should the model name also be unique)?
If yes, why is it failing? (I followed mongoose docs but it doesn't provide any information regarding uniqueness of the model name argument.
Thanks.
I you are calling insertMany method on dbModel1, where you variable is declared to dbModel2.
Change your test 2 from:
dbModel1.insertMany(data2)
.then(dbResponse1 => {
// assert for count
done()
});
To:
dbModel2.insertMany(data2)
.then(dbResponse1 => {
// assert for count
done()
});
I am running a firestore query to get data but the query is returning data from cached data queries earlier and then returns additional data (which was not queried earlier) in the second pass from server. Is there a way I can disable caching for firestore queries so that request goes to DB every time I query something.
this.parts$ = this.db.collection<OrderBom>('OrderBom', ref => {
let query : firebase.firestore.Query = ref;
query = query.where('orderPartLC', '==', this.searchValue.toLowerCase());
return query;
}).valueChanges();
Change that .valueChanges() to a .snapshotChanges() then you can apply a filter. See the example below.
I dont like changing default behavior (default configurations). I saw it's a desired behavior and the good practice is to show the data as soon as possible to the user, even if you refresh twice the screen.
I dont think is a bad practice to filter on fromCache === false when we dont have a choise. (In my case I do more requests after i receive this first one so due to promises and other async 'tasks' cache/server order is completly lost )
See this closed issue
getChats(user : User) {
return this.afs.collection<Chat>("chats",
ref => ref.where('participantsId', 'array-contains', user.id)
.snapshotChanges()
.pipe(filter(c=> c.payload.doc.metadata.fromCache === false)).
.pipe(map(//probaly want to parse your object here))
}
if using AngularFire2 you can try:
I read on the Internet that you can disable offline persistence - which caches your results -by not calling enablePersistence() on AngularFireStoreModule.
I have done the first and still had no success, but try it first. What I managed to do to get rid of caching results was to use the get() method from class DocumentReference. This method receives as parameter a GetOptions, which you can force the data to come from server. Usage example:
// fireStore is a instance of AngularFireStore injected by AngularFire2
let collection = fireStore.collection<any>("my-collection-name");
let options:GetOptions = {source:"server"}
collection.ref.get(options).then(results=>{
// results contains an array property called docs with collection's documents.
});
Persistence and caching should be disabled for angular/fire by default but it is not and there is no way to turn it off. As such, #BorisD's answer is correct but he hasn't explained it too well. Here's a full example for converting valueChanges to snapshotChanges.
constructor(private afs: AngularFirestore) {}
private getSequences(collection: string): Observable<IPaddedSequence[]> {
return this.afs.collection<IFirestoreVideo>('videos', ref => {
return ref
.where('flowPlayerProcessed', '==', true)
.orderBy('sequence', 'asc')
}).valueChanges().pipe(
map((results: IFirestoreVideo[]) => results.map((result: IFirestoreVideo) => ({ videoId: result.id, sequence: result.sequence })))
)
}
Converting the above to use snapshotChanges to filter out stuff from cache:
constructor(private afs: AngularFirestore) {}
private getSequences(collection: string): Observable<IPaddedSequence[]> {
return this.afs.collection<IFirestoreVideo>('videos', ref => {
return ref
.where('flowPlayerProcessed', '==', true)
.orderBy('sequence', 'asc')
}).snapshotChanges().pipe(
filter((actions: DocumentChangeAction<any>[], idx: number) => idx > 0 || actions.every(a => a.payload.doc.metadata.fromCache === false)),
map((actions: DocumentChangeAction<any>[]) => actions.map(a => ({ id: a.payload.doc.id, ...a.payload.doc.data() }))),
map((results: IFirestoreVideo[]) => results.map((result: IFirestoreVideo) => ({ videoId: result.id, sequence: result.sequence })))
)
}
The only differences are that valueChanges changes to snapshotChanges and then add the filter DocumentChangeAction and map DocumentChangeAction lines at the top of the snapshotChanges pipe, everything else remains unchanged.
This approach is discussed here
I'm very new to Sails and noSQL databases and I'm having trouble gathering information together from different collections. Basically I need to gather an object of items from one collection and then use a foreign key stored in that collection to add data from a separate collection so the whole thing can be sent as one object.
Currently I find all the items in a collection called Artwork, then I'm using a for loop to iterate through the artworks. I need to use an id stored in Artworks to query a collection called Contacts but having successfully found the contact I am unable to pass it back out of the function to add it to the Artwork object.
find: function ( req, res, next ) {
Artwork.find().done( function ( err, artwork ) {
// Error handling
if (err) {
return console.log(err);
} else {
for ( x in artwork ) {
var y = artwork[x]['artistID'];
// Get the artsists name
Contact.find(y).done( function( err, contact ) {
// Error handling
if ( err ) {
return console.log(err);
// The Artist was found successfully!
} else {
var artist = contact[0]['fullName'];
}
});
artwork[x]['artistsName'] = artist;
}
res.send(artwork);
}
});
}
The result of the above code is an error thrown that tells me 'artist' is undefined. The variable is not being passed outside the function?
Any advice greatly received.
Sails is about to release an update that will include associations. In the meantime, here's an answer for how you can accomplish it using async. https://stackoverflow.com/a/20050821/1262998