run some code 1 hour after a document is updated with cloud function and firestore - google-cloud-firestore

The user of my app can receive message and i want them to receive a notification push 1 hour after the document message is created.
Here is my cloud function :
export const scheduler = functions.firestore
.document("profile/{uuid}/message/{messageId}")
.onCreate((change, context) => {
//code that will send a notification push in one hour
return true;
});
I want to try this (i found that solution on that link : https://firebase.google.com/docs/functions/schedule-functions) but i don't know if i can replace "every 5 minutes" with some text saying one hour after :
exports.scheduledFunction = functions.pubsub.schedule('every 5 minutes').onRun((context) => {
console.log('This will be run every 5 minutes!');
return null;
});

The time(s) when scheduled Cloud Functions run must be known at the moment you deploy them. That is not the case for you, since the time depends on each individual document.
In that case you can either periodically run a scheduled Cloud Function that then checks what documents it needs to update, or you can use Cloud Tasks to create a dynamic trigger. For a detailed example of the latter, see Doug's blog post How to schedule a Cloud Function to run in the future with Cloud Tasks (to build a Firestore document TTL).

Related

Can’t access Firestore from lambda(http.get is fine)

I’m trying to push data from lambda to Firestore, but every time trigger time out.
I did some research,
I tested locally.
Lambda with vpc(now it can access internet and call cloud function).
Added
context.callbackWaitsForEmptyEventLoop = false;
After i increase the lambda execution time out from 3s to 10s,it woorked.

How to deploy Firestore listener functions to GCP Cloud?

I'm using the following approach (source) to build a GCP Cloud Function that handles Firestore events.
const functions = require('firebase-functions');
exports.myFunction = functions.firestore
.document('my-collection/{docId}')
.onWrite((change, context) => { /* ... */ });
There is no example on how to deploy this correctly to GCP Functions though, only Firebase ones.
using the regular gcloud deploy commands such as this one won't work.
gcloud functions deploy FUNCTION_NAME \
--entry-point ENTRY_POINT \
--runtime RUNTIME \
--trigger-event "providers/cloud.firestore/eventTypes/document.write" \
--trigger-resource "projects/YOUR_PROJECT_ID/databases/(default)/documents/messages/{pushId}"
Any ideas on how to do this?
You can achieve the required effect with creating Cloud Firestore Trigger of your choice
For Cloud Functions (1st gen):
In the Trigger type field, select Cloud Firestore.
Select Event Type: Write
Mention the Document on which you want to trigger this function, eg.users/{doc_id}
Check Retry on failure Check box if you want to retry if it did not trigger
Click On Save
Modify your Function as per your requirement
Click Deploy
Make Modification on provided documented path, in our case users/{doc_id}
Check the logs for cloud functions. You will see the function got triggered.
For Cloud Functions (2nd gen):
Under HTTPS, in the Authentication field, select an option depending on whether you want to allow unauthenticated invocations of your function. By default, authentication is required.
Click on Add Eventarc Trigger (A modal will appear)
Choose Trigger Type : First Party
Choose Event Provider: Cloud Firestore
Event: google.firestore.v1.Firestore.Write
Resource : If you are having exact path for document choose Specific Resource else you want to target multiple documents using wildcard pattern
choose Path Pattern
Check Retry on failure Check box if you want to retry if it did not trigger
Click on Save Trigger
Click Next
Modify your Function as per your requirement
Click Deploy
Make modification on the targeted documents
Check the logs for cloud functions. You will see the function got triggered.

Sometimes my Cloud Function returns old data from Firestore. Is it a cache problem?

Client-side, I'm using a listener to detect if the "notifications" collection of the user changes. The App calls a Cloud Function that retrieves the last three unread notifications and the total number of unread notifications.
In my App, I have this:
Listener
firestore.collection("users")
.doc(uid)
.collection("notifications")
.snapshots().listen((QuerySnapshot querySnapshot) {
NotificationsPreviewModel notificationsPreview =
await _cloudFunctionsService.getNotificationsPreview(doctor.id)
})
Cloud Function
exports.getNotificationsPreview = functions.https.onCall(async (data, context) => {
const userId = data.userId;
let notifications = [];
const notificationsDocuments = await db
.collection("users")
.doc(userId)
.collection("notifications")
.orderBy("dateTime", "desc")
.get();
notifications = notificationsDocuments.docs.map((rawNotification) =>
rawNotification.data()).filter((element) => element.unread == true);
const notificationsNumber = notifications.length;
notifications = notifications.slice(0, 3);
return { "notifications": notifications, "notificationsNumber": notificationsNumber };
});
The Cloud Function gets called only when a change is detected, so it shouldn't return old data.
The error appears only the first time the Cloud Function is called from the App's start, but not always. The following calls don't generate the error.
How can I solve this? For now, I've added a delay of 500ms, and it works perfectly, but it's not a real solution.
Based on your description, it sounds like you see some form of latency while collecting the data from Firestore. Retrieving data from the Cloud takes time, and a delay of 500ms is not excessive.
I am not familiar with Flutter enough to comment on your code. However, according to the documentation for Java:
By default, get() attempts to provide up-to-date data when possible by waiting for data from the server, but it may return cached data or fail if you are offline and the server cannot be reached. This behavior can be altered via the Source parameter.
Source:
By providing a Source value, these methods can be configured to fetch results only from the server, only from the local cache, or attempt to fetch results from the server and fall back to the cache (which is the default).
If you are online, get() checks the server for the latest data, which can take between 300ms and 1500ms depending on several factors. For example, where is your Firestore instance located in comparison to your Cloud Function and client? Try adjusting the delay and see if you can identify the timing.
There are also some soft limits you should be aware of as this might also impact your timings for how quickly you can retrieve the data. There is a maximum sustained write rate to a document of 1 per second. Sustaining a write rate above once per second increases latency and causes contention errors.
As for the documentation:
When you set a listener, Cloud Firestore sends your listener an initial snapshot of the data, and then another snapshot each time the document changes.
It seems that you are initially receiving the snapshot of the data, and then the following updates, as expected.
You can check some possible solutions to this in this post.

FieldValue.increment not working in offline in flutter

I have to increment a value in firebase firestore and the data is look like this
{
"key":{
"name":"Apple",
"quantity":12
}
}
I need to increment quantity by one. For this I use this Function.
FirebaseFirestore.instance.runTransaction((transaction) async {
transaction.set(
documentReference,
{
"key":{
"quantity" : FieldValue.increment(1)
}
},
SetOptions(merge: true));
return transaction;
});
This is working fine when the app is connected with network. But when the app goes offline, the value just increment by 1 when the app is connected again. Though several times quantity has increased in offline but it just increment by 1 when the connection is back.
The problem is that you are using a transaction for that, the way transactions work is by getting the current value of a field to determine the new value and the Firebase clients don't persist transactions across app restarts because the concept of transactions doesn't work well when a user is not connected.
Therefore the way transactions work makes it impossible for your current code to increment more than one time when offline. So in order to achieve what you want it would be better to take this out of a transaction/increment approach and possibly rethink the structure of your app to accomodate this counter in a different way, like a list of keys and a background function that gets the list when the app gets back online and increments it, for example.

Meteor mocha test subscriptions

I'm currently working on writing client-side tests for my application using practicalmeteor:mocha. Therefor I need to insert a record into a mongo collection before every step.
Following the concepts of meteor, I wrote some meteor methods to insert/update/delete the records. If I want to test the expected behaviour, I assume I have to call the appropiate method (to insert a record for example) and then have to wait for the subscription to synchronize the record to the client so I can verify the insert.
I tried a few things but none of them seems to work:
describe('a topic', function() {
beforeEach(async function(done) {
let res = await new Promise((resolve, reject) => {
Meteor.call('topics.createNew', function(err, res) {
if(err) return reject(err);
resolve(res); //res is the _id from the generated record
});
});
//subscribe to single record (the one just created)
let subscription = Meteor.subscribe('topics.details', res);
//i assume this runs every time the publish function calls ready and therefor is also run when the new record is published
Tracker.autorun((computation) => {
let count = TopicsCollection.find({}).count();
//console.log('topic count: ' + count);
if(subscription.ready() && count === 1) {
computation.stop();
done();
}
});
});
});
I logged the counted documents and had luck when i wrapped the autorun-Funktion into a Promise but in neither of both cases, done() was called causing a timeout in mocha.
I already had a look on this question but I think it doesnt really help in my situation since I wait for every possible callback and I'm working with a plain Collection and the guy in the question above uses an accounts-package.
Can anyone suggest a better/working solution for this problem?
Thanks in advance! :D
Summarizing your current setup:
calling a method on the client
insert some doc on the server
subscribe to the pub from the client
verify inserts by the subscribed data
Which is a big blackbox that is on the scope here. Additionally you are testing things, that are already tested by the Meteor core team: integration tests of Methods and Publications between server and client.
To prevent waste of time, you can split up your setup into two tests:
Test the Meteor method as a unit
Test the Meteor publication as a unit
If those tests are defining the intended behavior with correct permission levels, you can assume, that if you are a logged in user with the correct permission level subscribing to your publication, that this user will get the resulting behavior as you have tested in the units. As I pointed out before, the pub/sub system should be assumed as 'working' by definition.
1. Test the method, server side only
Use hwillson:stub-collections to get a 'mock' of your collection, use practicalmeteor:sinon to mock your Meteor.user() object to test different permission levels.
2. Test the publication, server side only
Use johanbrook:publication-collector to test if your publication is running correctly and dburles:factory create mocks of your collection's data.
No need then for a complex test setup on the client side.