Firebase function taking incorrect execution path, log messages stop - node.js

I have a function, that among other things calls a function returning a boolean value to determine if the passed in date is greater than the current Date:
function isExpired(expirationDate) {
const date = +new Date(expirationDate);
const now = +new Date();
const expired = (date < now);
functions.logger.log("date expired: ", expired);
return expired;
}
You'll notice in the helper function, I'm logging whether the date is expired or not. For some reason, that log message never appears either in the Firebase console or the CLI. I'm calling it like this:
const functions = require("firebase-functions");
var admin = require('firebase-admin');
const db = admin.database();
exports.generateCode = functions.https.onCall(async (data, context) => {
var expirationDate;
await joinCodeRef.once("value", snapshot => {
expirationDate = snapshot.val().expiresOn;
})
if (isExpired(expirationDate)) {
// do some stuff
} else {
// return some stuff
}
}
isExpired always evaluates to false even when the passed-in date is less than the current date. No log messages past if(isExpired) will appear in any logs either, BUT the proper values are returned in the else block meaning execution is being allowed to continue. I must be doing something wrong, but I'm not seeing it...

Not sure if it's the cause of your problem, but you should not combine using await with using callbacks.
exports.generateCode = functions.https.onCall(async (data, context) => {
const snapshot = await joinCodeRef.once("value");
const expirationDate = snapshot.val().expiresOn;
if (isExpired(expirationDate)) {
// do some stuff
} else {
// return some stuff
}
}

Related

Cloud Functions updates too late firestore

im new at cloud functions
i just want to disable my targets after countdown end. this function works correctly but updates after 3 min after function finished
what am i missing?
const functions = require("firebase-functions");
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.timecontroller = functions.firestore.document("DigitalTargets/{digitalTargetID}").onCreate((snap, context) => {
const id = snap.id
const date = new Date(snap.data().endDate.toDate())
var countDownDate = date.getTime();
var myfunc = setInterval(function () {
var now = new Date().getTime();
var timeleft = countDownDate - now;
if (timeleft < 0) {
db.collection("DigitalTargets").doc(id).update({ isActive: false })
clearInterval(myfunc);
}
}, 1000);
})
Since you pay for the time that your Cloud Functions code executes, the container tries to execute your code and terminate as quickly as possible. Unless you tell it otherwise, that means that it terminates the code as soon as the final statement before the closing } executes.
But since you are executing an asynchronous operation with your setInterval call, the code actually needs to continue to run after the closing }.
To allow that you'll need to return a promise that resolves when the code is complete. Something like:
exports.timecontroller = functions.firestore.document("DigitalTargets/{digitalTargetID}").onCreate((snap, context) => {
const id = snap.id
const date = new Date(snap.data().endDate.toDate())
var countDownDate = date.getTime();
return new Promise((resolve, reject) => { // 👈 This tells Cloud Functions to wait
var myfunc = setInterval(function () {
var now = new Date().getTime();
var timeleft = countDownDate - now;
if (timeleft < 0) {
db.collection("DigitalTargets").doc(id).update({ isActive: false })
clearInterval(myfunc);
resolve(); // 👈 This tells Cloud Functions that you're done
}
}, 1000);
})
})
I recommend reading (and watching the videos in) the Firebase documentation on asynchronous behavior in Cloud Functions.

how to change Firestore document data using Google Cloud Functions in node.js

I am trying to write a google cloud function that will check all documents under a collection in every minute and if a boolean parameter after 15 minutes later still false, it will change another document parameter.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.scheduledFunctionAppointment =
functions.pubsub.schedule('every 1 minutes').onRun((context) => {
const timestamp = FieldValue.serverTimestamp()
return getReceivers(mUserGroup).then((snapshot) => {
snapshot.forEach(doc => {
const mAppointmentObj = doc.data();
const mAppointmentCreationDate = mAppointmentObj.appointmentCreationDate;
const mAppointmentDate = mAppointmentObj.appointmentDate;
const mChaplainId = mAppointmentObj.chaplainId;
const mAppointmentId = mAppointmentObj.appointmentId;
const mIsAppointmentPaymentDone = mAppointmentObj.isAppointmentPaymentDone;
if (timestamp-mAppointmentCreationDate >= 900000){
if(mIsAppointmentPaymentDone === false){
admin.firestore().collection('chaplains').doc(mChaplainId).collection('chaplainTimes').doc('availableTimes').update({
mAppointmentId: {
isBooked: false
}
});
}
}
});
});
})
async function getTaskAppointments() {
const snapshot = await admin.firestore().collection('appointment_temporary').get();
return snapshot;
}
the code should check the appointment_temporary collection in every 1 minute. In every check, it will take the server time and appointment creation date. If after 15 minutes later, isAppointmentPaymentDone still false, it will make isBooked false that under availableTimes map data.
I wrote a function but it gives error. I am a mobile developer. I am not familiar with the node.js. Thank you very much for the help in advance.

get GCP cloud function logs label.executionid

i want to get the execution id, that GCP gave to the Cloud functions, in order to store it in a database.
this is what i want to get =>
let currId = log.label.execution_id
In order to do it i'm fetching the logs thanks to this function (inside my cloudFunctions):
const logging = new Logging();
console.log(`executed ${eId}`)
printEntryMetadata(eId, sId);
async function printEntryMetadata(eId, sId) {
const options = {
filter: `textPayload = "executed ${eId}"`
};
const [entries] = await logging.getEntries(options);
console.log('Logs:');
console.log(`textPayload = "executed ${eId}"`)
console.log(JSON.stringify(entries))
// const metadata = entries[0].metadata
console.log(`${metadata.labels.execution_id}`)
}
But the JSON.stringify(entries) return an empty array. And when i use the filter mannualy it's working...
is the cloud function unable to fetch it own logs?
This is what i've done:
exports.LogMetadata = async(executionId, scopeId, ProjectID, Logging) => {
const logging = new Logging({ProjectID});
const options = {
filter: `textPayload = "executed ${executionId}.${scopeId}"`
};
const [entries] = await logging.getEntries(options);
console.log(JSON.stringify(entries))
try {
const metadata = entries[0].metadata
console.log(`${metadata.labels.execution_id}`)
} catch (error) {
console.log("can't find the log, cause' it's the first function executed...")
}
}
The only thing that doesn't work is that i can't fetch the first log of the first esxecuted function.

Cloud Functions for Firestore: accessing parent collection data

Many blogs suggest to switch to Cloud Firestore because it's easy and well secured. Coming from Realtime Database and back when using Functions + RD it was easy to navigate through document triggers, like ref.parent
My setup is like this:
Users
{userid}
last_seen: "data"
{forms}
{formid}
However, i have added a document trigger with onCreate, and i want to get the value of last_seen:
exports.updateUser = functions.firestore.document('users/{userId}/forms/{formid}').onCreate((snap, context) => {
const newValue = snap.data();
console.log("test value : " + newValue.test); // works
console.log("form id: " + context.params.formid); // works
console.log("user last seen : " + newValue.last_seen); // doesn't work, can't access the parent collection data
});
I totally get the confusion with the switch to Firestore but it's almost the exact same way in this case.
In realtime, you have the snapshot:
exports.doStuff = functions.database.ref('/users/{userId}/forms/{formId}')
.onCreate((snapshot, context) => {
const ref = snapshot.ref;
const userRef = ref.parent.parent;
userRef.once('value').then(parentSnap => {
const user = parentSnap.val();
const lastSeen = user.last_seen;
});
});
In Firestore:
exports.doStuff = functions.firestore.document.onCreate('/users/{userId}/forms/{formId}')
.onCreate((snapshot, context) => {
const ref = snapshot.ref;
const userRef = ref.parent.parent;
userRef.get().then(parentSnap => {
const user = parentSnap.data();
const lastSeen = user.last_seen;
});
});
Another thing to consider is you are passing the userId in your params so you could just build your own DocumentReference (assuming you're also using firebaseAdmin)
functions.firestore.document.onCreate('/users/{userId}/forms/{formId}')
.onCreate((snapshot, context) => {
const userId = context.params.userId;
const userRef = firebaseAdmin.firestore().collection('users').doc(userId);
userRef.get().then(parentSnap => {
const user = parentSnap.data();
const lastSeen = user.last_seen;
});
});
It also allows you to decouple your logic for functions you may use often, consider it as a "helper" method: (NOTE, I switched to async/await on accident, it's a bit cleaner)
functions.firestore.document.onCreate('/users/{userId}/forms/{formId}')
.onCreate(async (snapshot, context) => {
const userId = context.params.userId;
const lastSeen = await getLastSeen(userId);
});
// == Helper Functions ==-------------------
export async getLastSeen(userId) {
if (!userId) return Promise.reject('no userId');
// User Ref
const userSnap = await firebaseAdmin.firestore().collection('users').doc(userId).get();
return userSnap.data().last_seen;
}
Now you can use getLastSeen() whenever you need it, and if you make a change you only have to adjust that one function. If it's not something you call often then don't worry about it, but I would consider maybe a getUser() helper...
In your code, snap is a DocumentSnapshot type object. As you can see from the linked API documentation, there is a ref property on that object that gets you a DocumentReference object pointing to the document that was added. That object has parent property that gives you a CollectionReference that points to the collection where the document exists, which also has a parent property. So, use these properties to navigate around your database as needed.
Get the reference where the change took place, move 2 levels up and capture data using ref.once() function:
exports.updateUser = functions.firestore.document('users/{userId}/forms/{formid}').onCreate( async (snap, context) => {
// Get the reference where the change took place
const changeRef = snap.after.ref;
// Move to grandad level (2 levels up)
const userIdRef = changeRef.parent.parent;
// Capture data
const snapshot = await userIdRef.once('value');
// Get variable
const lastSeen = snapshot.val().last_seen;
// Do your stuff...
return null;
});

Cloud Functions for Firebase BigQuery sync error

We're working on a cloud function that allows us to keep our bigquery and firebase database in sync. The function triggers when a place is created/updated/deleted.
Based on the trigger action (create/update/delete) we add a property called big_query_active to signal if the object exists or not. Same goes for the date.
Our current problem is that the call to big query sometimes returns an error. So that would mean that the data is not in sync anymore. How can this be prevented?
'use strict';
// Default imports.
const functions = require('firebase-functions');
const bigQuery = require('#google-cloud/bigquery');
// If you want to change the nodes to listen to REMEMBER TO change the constants below.
// The 'id' field is AUTOMATICALLY added to the values, so you CANNOT add it.
const ROOT_NODE = 'places';
const VALUES = [
'country_id',
'category_id',
'name',
'active',
'archived'
];
// This function listens to the supplied root node, but on child added/removed/changed.
// When an object is inserted/deleted/updated the appropriate action will be taken.
exports.children = functions.database.ref(ROOT_NODE + '/{id}').onWrite(event => {
const query = bigQuery();
const dataset = query.dataset('stampwallet');
const table = dataset.table(ROOT_NODE);
if (!event.data.exists() && !event.data.previous.exists()) {
return;
}
const item = event.data.exists() ? event.data.val() : event.data.previous.val();
const data = {};
data['id'] = event.params.id;
for (let index = 0; index < VALUES.length; index++) {
const key = VALUES[index];
data[key] = item[key] !== undefined ? item[key] : null;
}
data['big_query_date'] = new Date().getTime() / 1000;
data['big_query_active'] = event.data.exists();
return table.insert(data).then(() => {
return true;
}).catch((error) => {
if (error.name === 'PartialFailureError') {
console.log('A PartialFailureError happened while uploading to BigQuery...');
} else {
console.log(JSON.stringify(error));
console.log('Random error happened while uploading to BigQuery...');
}
});
});
This is the error that we (sometimes) receive
{"code":"ECONNRESET","errno":"ECONNRESET","syscall":"read"}
How could it be prevented that the data goes out of sync? Or is there a way to retry so that it always succeeds?

Resources